combine into a single file

This commit is contained in:
Matthew Francis-Landau 2023-01-17 22:27:49 -05:00
parent 4cf7438328
commit 6fc7af7686
2 changed files with 510 additions and 0 deletions

9
generate.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
awk '/openpgp.min.js/ {
print "<script>"
while(getline < "openpgp.min.js") { print; }
print "</script>";
next;
}
{print}' < source.html > index.html

501
source.html Normal file
View File

@ -0,0 +1,501 @@
<!DOCTYPE html>
<html>
<head>
<title>Crypto test</title>
<script>
// primes used for Shamir's Secret Sharing method
const large_primes = [
// generated using `openssl prime -generate -bits XXXX`
26013800316714693659487360106106576859446408140997727416684553647485913737527506224154159131021149031471925848188633045078893861608747018494713017361294680716680118653824608236875958327849429464985041509482020024640031860838265292357141341135797263686786173873087642663969196834962111089230588045539441312239718192315305970193707150287182381185472989128205869996790062835198989400329480521790509433619234601525286972527863796466644417099290273737897696408900798080603049307089600914440953002542676566189811596585259202284000341527357607212326209139406647000473009380221243221069300299351304668072790318472314168860759n,
857908717760401559068028658471571657946962457971409672831772838172131478347791874002022287489642233226344550154381694396450325517548082259873894584140331044970735800272584620445118216897059937245611505375289737129464298159348368574296753291623038525750286800721601067857073212179030797160281838407107404517365485346611287684513259639468464324661024847873457888697657005129675229974657842151139338371647475491395012735979136039720996395370528690475711412280056729606823574306401667627041294907691260832046733641716487744410354436009830988814481031969765508301874597416407883672495852313261560858246454276556214721350976268943858089426367354584853954538642652672225376259044410283111961655958346629654520092629849280881747434464878015147446833346558198224631284779186745655600561432054875662125636368490741650406480538918284023345260994849377140284027753905909394839383458005027654353873905144604473432264691766100538256383275596227027381072806473990399132689380793925265914699099790981717613653117821763366356805273903151332410528824908772288826115814531889721492063937956352790086339017420478641853030067684757524530660412039422923461248039020640296031924795730962649482971647253342812605278174053522537678469005083063230322368259957n,
42051613837217905477400960241727065545380274896740361252119841037129963997644904836403309515887204520414200749416517639193287736259706062451367573395358363849684496293575248988025538418680386412029957484178182381151432240593315274496744810103765229589895469908719506159040264079872721605860140567817015850592209293731392557283429996122755837206428621694497004513972481761702624089712485046944785875181486670173683494560753735074714857534215562577340874184593253689347221882860128032682370551448303580873026166326616209477598115633823050505271356315102765606939581238188251426188017201674208119387531195082316347657727814189720228852856895821190143977110002317484899163610615458298668807431232965915533409795006730645822003650579406085783522341580945676873702767515032776216529999815186426798572468312778484612767864183293313962175919889121117642587107367003216269560230193299927414644382148405314244321657457811744258244133641648132587254837645775760425074420335150703811495133325689910067259365825997132274671492074028291031996763864182304862298487626724124649030861927018311749457437231768992530112593625905142762953078374132065191868729444312132857056069606705492967889716547958438389427695807386216779510341962630888856128872057908771480043568895958299369772600267102448782734788539571073496449290664924598728844493916101753048924710504101306255085153n,
27322052264104655861400818625895739654685477554826524679792026738754122220380511438339944106961637040793725280215800716381496668776752693326830296769220814390920245572522443487340023818746664748915258432280236761826174095959960275111309248890001057724297198282946508759821401819314930790341102012462960633997713885651819568388398695164955135426939874410245680599717584687234789093192982601142543049994455430191519083313400637113232929083598625381764262518180155040092149584940075560815384021169239192643469752937422155100737882216571747467883325932675103193615220851242805922054990514279884089441982540910521167503943976667483287955121976867078292157406429001021152454912499600934736020762984787628624394212157440831544670637090010733509454390804386326692137929439481287992840008268862362129093150888434364632981690378179056920818887936762708502213590636058169652723826969907527209612926232878110573241960118242777684934368488823371543242472508148893903931475976852778800935257836878044358817919613664964128635433765674577022531941967567046792184615974494283837554355291719273647759791745543196701870835078362996288636404554040877620963492616589948969674377637697017822049422859398350295396213126838342352038294438863785785965087229178419799259552701432956498283521600931650664048313199358697291493715370427944030887169645537937863883460737778953905911888098918493570017000111635315956405999740708517849365569838821154740123475069512138072349027979677684319661908884153163410254816479441406649041625436480655329755420007768187784183169370876746044924805044448454454379833182087399559734454108592699538085057337419847285316366279667816150705569595759757583783829276659314230979478463803378118547344981083307521117773175027567727271832694991086195837991631259211701912348602475899043515299103087874624411122017022243158804321645303871984840122933412767n,
1067145027806771896736348885201650520217682536144480054962414237260675149345425580833604638553254555553450516905594270310215323220554955743166747664212810667924739323286132444586240362334974379521147620858785277986492501704235616109075864067774820029277982307910523435598035747995561704315623170146250954311839861990164148352059696244492499350745811714406873325781200666772436973779301867636130559940380214236807863978216034608177339239785157816672248111313127934009323837209046997706736924517668495975505109434485814738420126604281177486651942695134987862996087141461138985819668776634974521002672548209946493421911244257865273713298997478148275323403478140262314677990602905369872165803312468468196682089866320691811138046397258988457087599252348435749995677274830713978400396535341627797619039747345374158897705020261205844349478572717354515895071536910728957016820340878297419274409311876169531912679029965314409377133968232741777449081225446017422780740487500490302760038171852724526896870578871873965885630345326776370465627249974269058549505437624688835806153923199404004072461774279546739933771154130132197816275614323408701079856017813257982040041760737088759156983414150654480336132277672003225738445285430047413304772618293704912659529766597970879313533992954211080398555794343386696810547300820057892472631379493468492846464793101134340436484985900056931089743386606670422620801925011810627958444477076039156986802351793781845354101186374167815407429459530425468169262678107647879548917515619061140983268922899874767217134251427822634346444954530041777889149673629521927949770953118691529003011591473442997897411242514693507582583074997782841646405446079405629601243085143096158590569547732987594487737452587151639703336174965339982813747174020001083084219324686589921118108344654274776002924949699514759820503910984443396225085556329453262160312195475332365052302858783105407745663740585803098305023249756111368049950163462388385362335937010786959132576587845583579588056574658340601882631020529595113806813975952098429751621777437426581760176612378200144749546283222278632927625036689224728552301833015747115546334419012004870091311801727234396627282154659341220005712383030184513423146821824749625141220375926274254838083359422096107376456254874227230786791473401400818685683588726561134800854232826696808345124633056379503082658309023861152443764421786399116516058974263475670452557479495335125983247709460380575872076251131971902570828620338999143577n
];
// this is used to encode big nums into json, so it isn't printed directly
//const string_encoding = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'
const string_encoding = ' "+,/0123456789:=ABCDEFGHIJKLMNOPQRSTUVWXYZ\\abcdefghijklmnopqrstuvwxyz{}';
const base58_encoding = '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ';
function encode_bigint(encoding, x) {
let r = '', l = BigInt(encoding.length);
while(x != 0n) {
r += encoding[x % l];
x = x / l;
}
return r;
}
function decode_bigint(encoding, x) {
let r = 0n, l = BigInt(encoding.length);
for(let i = x.length - 1; i >= 0; i--) {
let v = encoding.indexOf(x[i]);
if(v == -1) { alert('invalid character: '+x[i]); return; }
r = r * l + BigInt(v);
}
return r;
}
function uint8ToBigint(x) {
let a = 0n;
for(let i = x.length - 1; i >= 0; i--) {
a = (a << 8n) + BigInt(x[i]);
}
return a;
}
function bigintToUint8(a) {
let num_bytes = 1;
while(a > (1n << BigInt(num_bytes*8)))
num_bytes++;
let r = new Uint8Array(num_bytes);
for(let i = 0; i < num_bytes; i++) {
r[i] = Number(a & 255n);
a = a >> 8n;
}
return r;
}
function extended_gcd(a, b) {
if(a == 0n) {
return [b, 0n, 1n];
} else {
let [g, y, x] = extended_gcd(b % a, a);
return [g, x - (b / a) * y, y];
}
}
function inverse(v, mod) {
if(v < 0n) {
return mod - inverse(-v, mod);
} else {
let [g, x, y] = extended_gcd(v, mod);
if(g != 1n) alert('no inverse');
if(x < 0n)
return (mod + x) % mod;
else
return x % mod;
}
}
// for implementing Shamir's Secret Sharing
function evaluatePolynomialAtPoint(poly, mod, x) {
let r = 0n;
for(let i = 0; i < poly.length; i++) {
let p = 1n;
let d = 1n;
for(let k = 0; k < poly.length; k++) {
if(k != i) {
p = (p * (x - poly[k][0])) % mod;
d = (d * (poly[i][0] - poly[k][0])) % mod;
}
}
r = (r + poly[i][1] * p * inverse(d, mod)) % mod;
}
if(r < 0n) r += mod;
return r;
}
async function hash(str) {
const encoder = new TextEncoder();
const data = encoder.encode(str);
const h = await crypto.subtle.digest('SHA-1', data);
return encode_bigint(string_encoding, uint8ToBigint(new Uint8Array(h)));
}
async function is_key(x) {
return ('x' in x) && ('y' in x) && ('fingerprint' in x) && ('mod' in x) && ('required' in x) && ('name' in x) && ('version' in x) && x.version == 1 && x.hash == await hash(x.y + x.x + x.mod);
}
async function generate_key() {
const num_pub_generate = document.getElementById("number_private_generate").value * 1;
const num_required = document.getElementById("number_private_required").value * 1;
const name = document.getElementById("user_name").value
if(num_pub_generate < num_required) {
alert('The number of public keys that are to be generated is less than the number of public keys which will be required for decryption\nThis will not work');
return;
}
const { privateKey, publicKey } = await openpgp.generateKey({
type: 'ecc', // Type of the key, defaults to ECC
curve: 'curve25519', // ECC curve name, defaults to curve25519
userIDs: [{ name: "Key for "+name+" in the event of death", email: name.replace(/[^A-Za-z0-9]/g, '-')+"@in-event-of-death.none" }],
format: 'object'
//format: 'armored' // output key format, defaults to 'armored' (other options: 'binary' or 'object')
});
const pk = publicKey.armor();
const priv = privateKey.write();
const fingerprint = publicKey.getFingerprint();
//debugger;
document.getElementById("num_required_span").innerHTML = num_required;
//document.getElementById("private_key").value = privateKey;
document.getElementById("public_key_gen").value = pk;
document.getElementById("public_key_gen").rows = pk.split('\n').length + 1;
document.getElementById("public_key").value = pk;
let poly_values = [[0n, uint8ToBigint(priv)]];
for(let i = 1; i < num_required; i++) {
let arr = new Uint8Array(priv.length);
crypto.getRandomValues(arr);
poly_values.push([BigInt(i), uint8ToBigint(arr)]);
}
let mod = large_primes[0];
for(let i = 1; i < large_primes.length && mod < (1n << BigInt(priv.length*8)); i++)
mod = large_primes[i];
if(mod < (1n << BigInt(priv.length*8))) {
alert('FAILURE: Encryption key too big');
return;
}
let pk_list = document.getElementById('private_keys');
pk_list.innerHTML = '';
location.hash = '';
const key_prefix= 'https://in-event-of-death.github.io/v1/#';
for(let i = 0; i < num_pub_generate; i++) {
let x = BigInt(i)*1191531196035921311916814842367n + 1196999169309667173465223891789n + BigInt(num_required);
if(x == 0n) x = 123n; // zero encodes the secret key, so we do not want to generate its value
let y = evaluatePolynomialAtPoint(poly_values, mod, x);
let k = {
'y': encode_bigint(string_encoding, y),
'id': i,
'x': encode_bigint(string_encoding, x),
'required': num_required,
'fingerprint': fingerprint,
'mod': encode_bigint(string_encoding, mod),
'name': btoa(name), // the string encoding cant handle non-standard characters
'version': 1,
};
k.hash = await hash(k.y + k.x + k.mod);
let s = encode_bigint(base58_encoding, decode_bigint(string_encoding, JSON.stringify(k)));
let inp = document.createElement('input');
inp.type = 'text';
inp.size = 80;
inp.readOnly = true;
inp.value = key_prefix + s;
inp.name = 'gen_decrypt_key';
inp.onclick = function () { this.select(); }
let li = document.createElement('li');
li.appendChild(inp);
pk_list.appendChild(li);
}
}
async function encrypt() {
const publicKey = document.getElementById("public_key").value;
const encrypted = await openpgp.encrypt({
message: await openpgp.createMessage({ text: document.getElementById("to_encrypt").value }),
encryptionKeys: await openpgp.readKey({ armoredKey: publicKey }),
format: 'armored'
});
document.getElementById("encrypted").value = encrypted;
}
function load_json(s) {
s = s.replace(/^(.+#)/, '').replace(new RegExp('[^'+base58_encoding+']', 'g'), '')
return JSON.parse(encode_bigint(string_encoding, decode_bigint(base58_encoding, s)));
}
async function decrypt() {
let private_keys = [];
let has_x = {};
let keys = document.getElementsByName('decrypt_key');
for(let i = 0; i < keys.length; i++) {
try {
let k = load_json(keys[i].value);
if(!(await is_key(k)) || k.x in has_x) {
keys[i].value = ''; // this is redudant
} else {
private_keys.push(k);
has_x[k.x] = true;
}
} catch (e) {
keys[i].value = ''; // this was not loaded correctly
}
}
if(private_keys.length == 0) {
alert('Private keys are entered incorrectly');
return;
}
for(let i = 1; i < private_keys.length; i++) {
if(private_keys[i].fingerprint != private_keys[0].fingerprint) {
alert('Private keys have been mixed up with different encryption keys');
return;
}
}
if(private_keys.length < private_keys[0].required) {
alert('Not enough private keys, require '+private_keys[0].required+' keys to decrypt the data');
return;
}
let poly_values = [];
for(let i = 0; i < private_keys[0].required; i++) {
poly_values.push([decode_bigint(string_encoding, private_keys[i].x), decode_bigint(string_encoding, private_keys[i].y)]);
}
let mod = decode_bigint(string_encoding, private_keys[0].mod);
let private_key = bigintToUint8(evaluatePolynomialAtPoint(poly_values, mod, 0n));
let pkey = await openpgp.readPrivateKey({ binaryKey: private_key });
let message = await openpgp.readMessage({
armoredMessage: document.getElementById('to_decrypt').value
});
let { data: decrypted } = await openpgp.decrypt({
message,
decryptionKeys: pkey
});
document.getElementById('decrypted').value = decrypted;
}
function add_decrypt_key() {
let d = document.getElementById('decryption_keys');
let inp = document.createElement('input');
inp.type = 'text';
inp.size = 80;
inp.onchange = on_decrypt_key_change;
inp.name = 'decrypt_key';
let li = document.createElement('li');
li.appendChild(inp);
d.appendChild(li);
}
async function on_decrypt_key_change() {
let private_keys = [];
let has_x = {};
let keys = document.getElementsByName('decrypt_key');
let has_invalid = false;
for(let i = 0; i < keys.length; i++) {
try {
let v = keys[i].value;
if(v) {
let k = load_json(v);
if((await is_key(k)) && !(k.x in has_x)) {
private_keys.push(k);
has_x[k.x] = true;
}
}
} catch (e) {
has_invalid = true;
}
}
if(private_keys.length == 0) {
document.getElementById('decrypt_key_status').innerHTML = has_invalid ? '<span style="color:red"><b>Encryption key is corrupted</b></span>' : '';
return;
}
let message = 'Decryption keys for <i><b>'+atob(private_keys[0].name)+'</b></i> loaded';
let error = '';
for(let i = 1; i < private_keys.length; i++) {
if(private_keys[i].fingerprint != private_keys[0].fingerprint) {
error = '<br><span style="color:red"><b>Different encryption keys are mixed together. This will not work</b></span>';
break;
}
}
if(!error && private_keys.length < private_keys[0].required) {
error = '<br><span style="color:red"><b>'+private_keys.length+' decryption '+(private_keys.length==1 ? 'key' : 'keys')+' loaded, '+(private_keys[0].required-private_keys.length)+' additional '+(private_keys[0].required-private_keys.length==1 ? 'key' : 'keys')+' required</b></span>';
for(let i = keys.length; i < private_keys[0].required; i++)
add_decrypt_key();
}
if(!error) {
error = '<br><span style="color:green">Ready to decrypt</span>';
}
document.getElementById('decrypt_key_status').innerHTML = '<p>' + message + error + '</p>';
}
window.onload = function () {
try {
load_json(location.hash);
let v = document.getElementsByName('decrypt_key');
for(let a in v) a.value = '';
v[0].value = location.href;
} catch(e) {}
on_decrypt_key_change();
}
if(location.protocol === 'http:') {
location.protocol = 'https:'; // the crypto APIs require https
}
async function example() {
await generate_key();
await encrypt();
const num_required = document.getElementById("number_private_required").value * 1;
while(document.getElementsByName('decrypt_key').length < num_required)
add_decrypt_key();
const genk = document.getElementsByName('gen_decrypt_key');
const deck = document.getElementsByName('decrypt_key');
for(let i = 0; i < num_required; i++) {
deck[i].value = genk[i].value;
}
await on_decrypt_key_change();
document.getElementById('to_decrypt').value = document.getElementById('encrypted').value;
await decrypt();
}
</script>
<style>
@media (min-width: 970px) {
body {
width:960px;
margin:auto;
padding: 10px;
background-color: #445;
}
#content {
background-color:#fff;
padding: 10px;
border-radius: 5px;
}
#forkongithub a{background:#c00;color:#fff;text-decoration:none;font-family:arial,sans-serif;text-align:center;font-weight:bold;padding:5px 40px;font-size:1rem;line-height:2rem;position:relative;transition:0.5s;}#forkongithub a:hover{background:#c11;color:#fff;}#forkongithub a::before,#forkongithub a::after{content:"";width:100%;display:block;position:absolute;top:1px;left:0;height:1px;background:#fff;}#forkongithub a::after{bottom:1px;top:auto;}@media screen and (min-width:800px){#forkongithub{position:absolute;display:block;top:0;right:0;width:200px;overflow:hidden;height:200px;z-index:9999;}#forkongithub a{width:200px;position:absolute;top:60px;right:-60px;transform:rotate(45deg);-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);-moz-transform:rotate(45deg);-o-transform:rotate(45deg);box-shadow:4px 4px 10px rgba(0,0,0,0.8);}}
}
@media (max-width: 970px) {
#forkongithub { display: none; }
}
body {
font: 13px/1.5 'Helvetica Neue',Arial,'Liberation Sans',FreeSans,sans-serif;
}
</style>
</head>
<body>
<div id="content">
<center><a name="description" /><h1>Description</h1></a></center>
<p>
This is designed to encrypt messages which can be passed along in the
event of death. This webpage is entirely self contained, which means that
it can be used offline and saved to a file. This webpage generates
encryption keys using <a href="https://openpgpjs.org/">OpenPGP.js</a> and
<a href="https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing">Shamir's
Secret Sharing</a>. This means that a your data is encrypted such that it
requires <i>multiple</i> description keys to descrypt a message. For
example, you can generate 10 encryption keys such that any 3 encryption
keys can be used to decrypt the data.
</p>
<p>
To see an example of how this works, click the
<button type="button" onclick="example()">Show Example</button>
button.
</p>
<p>
If you have a decryption key and want to decrypt a message, go to
the <a href="#decrypt">Decrypt Message</a> section below.
</p>
<button type="button" onclick="example()">Show Example</button>
<hr>
<center><a name="generate" /><h1>Generate Encryption Key</h1></center>
<p>Name: <input type="text" value="John Doe" id="user_name"></p>
<p>Number of private keys to generate: <input type="number" value="10" id="number_private_generate"></p>
<p>Number of private keys required to decrypt: <input type="number" value="3" id="number_private_required"></p>
<p><button type="button" onclick="generate_key()">Generate Encryption Key</button></p>
<br>
<p><b>Public key used for encryption:</b>
<br>You should save the public key somewhere safe. The Public Key
can <i>only</i> be used to encrypt messages. You can encrypt messages in
the <a href="#encrypt">Encryption section</a> of this webpage or by
using <a href="https://gnupg.org/">PGP</a> software on your computer. <br>
<textarea id="public_key_gen" readonly cols=60 rows=10 autocomplete="off"></textarea>
</p>
<p>
<b>Private keys for decryption:</b><br> The Private key can be used to
decrypt a message. Ideally, each trusted individual will be given 1 of
these keys. Note, <i>anyone</i> who gets access to more
than <b><span id="num_required_span">3</span></b> of these private keys
(as configured above) can decrypt all of your messages.
<!-- These keys should be distributed such that 1 key is provide to each individual. As configured above, any <b><span id="num_required_span">3</span></b> of these keys are required to decrypt your data. -->
<br>
<ol id="private_keys">
<li><input type="text" size=80 readonly /></li>
<li><input type="text" size=80 readonly /></li>
</ol>
</p>
<hr>
<center><a name="encrypt" /><h1>Encrypt Message</h1></center>
<p><b>Public Key used for Encryption:</b><br>
<textarea id="public_key" cols=60 rows=10 autocomplete="off"></textarea>
</p>
<p><b>Message to Encrypt:</b><br>
<textarea id="to_encrypt" cols=60 rows=10 autocomplete="off">Example message to encrypt
This message can contain any content, for exmaple you might encrypt your email password to be passed along.
You can come back and encrypt multiple messages as long as you still have the encryption key.
</textarea>
</p>
<p><button type="button" onclick="encrypt()">Encrypt message</button></p>
<p><b>Encrypted data:</b> <br>
<textarea id="encrypted" readonly cols=60 rows=10 autocomplete="off"></textarea>
</p>
<hr>
<center><a name="decrypt" /><h1>Decrypt Message</h1></center>
<p>Decryption Keys:
<ol id="decryption_keys">
<li><input name="decrypt_key" type="text" size=80 onchange="on_decrypt_key_change()" /></li>
<li><input name="decrypt_key" type="text" size=80 onchange="on_decrypt_key_change()" /></li>
<li><input name="decrypt_key" type="text" size=80 onchange="on_decrypt_key_change()" /></li>
</ol>
<button type="button" onclick="add_decrypt_key()">Add addition decryption Key <b>+</b></button><br>
<div id="decrypt_key_status"></div>
</p>
<p>Encrypted message:<br>
<textarea autocomplete="off" id="to_decrypt" cols=60 rows=10 onfocus="this.value='';" spellcheck="false">-----BEGIN PGP MESSAGE-----
Paste your encrypted message here
....
-----END PGP MESSAGE-----
</textarea>
</p>
<p><button type="button" onclick="decrypt()">Decrypt message</button></p>
<p>Decrypted message:<br>
<textarea autocomplete="off" id="decrypted" readonly cols=60 rows=10></textarea>
</p>
<footer><center>Created By <a href="https://matthewfl.com">Matthew Francis-Landau</a> 2023</center></footer>
</div>
<script>
if(true || location.hostname.indexOf('github') != -1)
document.write('<span id="forkongithub"><a href="https://github.com/in-event-of-death/v1/">Fork me on GitHub</a></span>')
</script>
<script src="openpgp.min.js"></script>
</body>
</html>