594 lines
33 KiB
HTML
594 lines
33 KiB
HTML
<!--
|
|
Created By Matthew Francis-Landau (https://matthewfl.com)
|
|
Source code available at https://github.com/in-event-of-death/v1
|
|
|
|
MIT License
|
|
|
|
Copyright (c) 2023 Matthew Francis-Landau
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
|
|
|
|
OpenPGP.js (which is included in this program) is licensed under LGPGv3
|
|
https://github.com/openpgpjs/openpgpjs
|
|
-->
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Encrypted Messages for the Event of Death</title>
|
|
<script>
|
|
if(location.protocol === 'http:') {
|
|
location.protocol = 'https:'; // the crypto APIs require https
|
|
}
|
|
// primes used for Shamir Secret Sharing method
|
|
const large_primes = [
|
|
// generated using `openssl prime -generate -bits XXXX`
|
|
26013800316714693659487360106106576859446408140997727416684553647485913737527506224154159131021149031471925848188633045078893861608747018494713017361294680716680118653824608236875958327849429464985041509482020024640031860838265292357141341135797263686786173873087642663969196834962111089230588045539441312239718192315305970193707150287182381185472989128205869996790062835198989400329480521790509433619234601525286972527863796466644417099290273737897696408900798080603049307089600914440953002542676566189811596585259202284000341527357607212326209139406647000473009380221243221069300299351304668072790318472314168860759n,
|
|
857908717760401559068028658471571657946962457971409672831772838172131478347791874002022287489642233226344550154381694396450325517548082259873894584140331044970735800272584620445118216897059937245611505375289737129464298159348368574296753291623038525750286800721601067857073212179030797160281838407107404517365485346611287684513259639468464324661024847873457888697657005129675229974657842151139338371647475491395012735979136039720996395370528690475711412280056729606823574306401667627041294907691260832046733641716487744410354436009830988814481031969765508301874597416407883672495852313261560858246454276556214721350976268943858089426367354584853954538642652672225376259044410283111961655958346629654520092629849280881747434464878015147446833346558198224631284779186745655600561432054875662125636368490741650406480538918284023345260994849377140284027753905909394839383458005027654353873905144604473432264691766100538256383275596227027381072806473990399132689380793925265914699099790981717613653117821763366356805273903151332410528824908772288826115814531889721492063937956352790086339017420478641853030067684757524530660412039422923461248039020640296031924795730962649482971647253342812605278174053522537678469005083063230322368259957n,
|
|
42051613837217905477400960241727065545380274896740361252119841037129963997644904836403309515887204520414200749416517639193287736259706062451367573395358363849684496293575248988025538418680386412029957484178182381151432240593315274496744810103765229589895469908719506159040264079872721605860140567817015850592209293731392557283429996122755837206428621694497004513972481761702624089712485046944785875181486670173683494560753735074714857534215562577340874184593253689347221882860128032682370551448303580873026166326616209477598115633823050505271356315102765606939581238188251426188017201674208119387531195082316347657727814189720228852856895821190143977110002317484899163610615458298668807431232965915533409795006730645822003650579406085783522341580945676873702767515032776216529999815186426798572468312778484612767864183293313962175919889121117642587107367003216269560230193299927414644382148405314244321657457811744258244133641648132587254837645775760425074420335150703811495133325689910067259365825997132274671492074028291031996763864182304862298487626724124649030861927018311749457437231768992530112593625905142762953078374132065191868729444312132857056069606705492967889716547958438389427695807386216779510341962630888856128872057908771480043568895958299369772600267102448782734788539571073496449290664924598728844493916101753048924710504101306255085153n,
|
|
27322052264104655861400818625895739654685477554826524679792026738754122220380511438339944106961637040793725280215800716381496668776752693326830296769220814390920245572522443487340023818746664748915258432280236761826174095959960275111309248890001057724297198282946508759821401819314930790341102012462960633997713885651819568388398695164955135426939874410245680599717584687234789093192982601142543049994455430191519083313400637113232929083598625381764262518180155040092149584940075560815384021169239192643469752937422155100737882216571747467883325932675103193615220851242805922054990514279884089441982540910521167503943976667483287955121976867078292157406429001021152454912499600934736020762984787628624394212157440831544670637090010733509454390804386326692137929439481287992840008268862362129093150888434364632981690378179056920818887936762708502213590636058169652723826969907527209612926232878110573241960118242777684934368488823371543242472508148893903931475976852778800935257836878044358817919613664964128635433765674577022531941967567046792184615974494283837554355291719273647759791745543196701870835078362996288636404554040877620963492616589948969674377637697017822049422859398350295396213126838342352038294438863785785965087229178419799259552701432956498283521600931650664048313199358697291493715370427944030887169645537937863883460737778953905911888098918493570017000111635315956405999740708517849365569838821154740123475069512138072349027979677684319661908884153163410254816479441406649041625436480655329755420007768187784183169370876746044924805044448454454379833182087399559734454108592699538085057337419847285316366279667816150705569595759757583783829276659314230979478463803378118547344981083307521117773175027567727271832694991086195837991631259211701912348602475899043515299103087874624411122017022243158804321645303871984840122933412767n,
|
|
1067145027806771896736348885201650520217682536144480054962414237260675149345425580833604638553254555553450516905594270310215323220554955743166747664212810667924739323286132444586240362334974379521147620858785277986492501704235616109075864067774820029277982307910523435598035747995561704315623170146250954311839861990164148352059696244492499350745811714406873325781200666772436973779301867636130559940380214236807863978216034608177339239785157816672248111313127934009323837209046997706736924517668495975505109434485814738420126604281177486651942695134987862996087141461138985819668776634974521002672548209946493421911244257865273713298997478148275323403478140262314677990602905369872165803312468468196682089866320691811138046397258988457087599252348435749995677274830713978400396535341627797619039747345374158897705020261205844349478572717354515895071536910728957016820340878297419274409311876169531912679029965314409377133968232741777449081225446017422780740487500490302760038171852724526896870578871873965885630345326776370465627249974269058549505437624688835806153923199404004072461774279546739933771154130132197816275614323408701079856017813257982040041760737088759156983414150654480336132277672003225738445285430047413304772618293704912659529766597970879313533992954211080398555794343386696810547300820057892472631379493468492846464793101134340436484985900056931089743386606670422620801925011810627958444477076039156986802351793781845354101186374167815407429459530425468169262678107647879548917515619061140983268922899874767217134251427822634346444954530041777889149673629521927949770953118691529003011591473442997897411242514693507582583074997782841646405446079405629601243085143096158590569547732987594487737452587151639703336174965339982813747174020001083084219324686589921118108344654274776002924949699514759820503910984443396225085556329453262160312195475332365052302858783105407745663740585803098305023249756111368049950163462388385362335937010786959132576587845583579588056574658340601882631020529595113806813975952098429751621777437426581760176612378200144749546283222278632927625036689224728552301833015747115546334419012004870091311801727234396627282154659341220005712383030184513423146821824749625141220375926274254838083359422096107376456254874227230786791473401400818685683588726561134800854232826696808345124633056379503082658309023861152443764421786399116516058974263475670452557479495335125983247709460380575872076251131971902570828620338999143577n
|
|
];
|
|
|
|
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'); return; }
|
|
if(x < 0n)
|
|
return (mod + x) % mod;
|
|
else
|
|
return x % mod;
|
|
}
|
|
}
|
|
|
|
// for implementing Shamir 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);
|
|
}
|
|
|
|
function generate_key_inp_change() {
|
|
document.getElementById("public_key_gen").disabled = true;
|
|
let ks = document.getElementsByName('gen_decrypt_key');
|
|
for(let i = 0; i < ks.length; i++) ks[i].disabled = true;
|
|
document.getElementById("copy_private").disabled=true;
|
|
}
|
|
|
|
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',
|
|
curve: 'curve25519',
|
|
userIDs: [{ name: "Key for "+name+" in the event of death", email: name.replace(/[^A-Za-z0-9]/g, '-')+"@in-event-of-death.github.io" }],
|
|
format: '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_gen").disabled = false;
|
|
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 = '';
|
|
|
|
for(let i = 0; i < num_pub_generate; i++) {
|
|
let x = BigInt(i+1)*1000000n + 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 = 'https://in-event-of-death.github.io/v1/#' + s;
|
|
inp.name = 'gen_decrypt_key';
|
|
inp.onclick = function () { this.select(); };
|
|
let li = document.createElement('li');
|
|
li.appendChild(inp);
|
|
pk_list.appendChild(li);
|
|
}
|
|
document.getElementById("copy_private").disabled=false;
|
|
}
|
|
|
|
function copy_private_keys() {
|
|
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;
|
|
let s = 'Encryption key for '+name+'\n\n';
|
|
s += document.getElementById('public_key_gen').value;
|
|
s += '\n\n\n\n';
|
|
s += 'Private decryption keys for '+name+'\nAny '+num_required+' decryption keys when combined together at https://in-event-of-death.github.io/v1/ can be used to decrypt all messages'
|
|
const genk = document.getElementsByName('gen_decrypt_key');
|
|
for(let i = 0; i < genk.length; i++) {
|
|
s += '\n\n';
|
|
s += (i + 1) + '. ' + genk[i].value;
|
|
}
|
|
navigator.clipboard.writeText(s);
|
|
alert('All keys copied to clipboard')
|
|
}
|
|
|
|
async function encrypt() {
|
|
document.getElementById("encrypted").value = '';
|
|
const publicKey = document.getElementById("public_key").value;
|
|
if(!publicKey || publicKey.indexOf('Paste your public key here') != -1) {
|
|
alert('[ERROR]\n\nPlease paste in a public key to encrypt a message.\n\n\nIf you do not have a public key, generate a public key by clicking the [Generate Encryption Key] button');
|
|
location.href = '#generate';
|
|
return;
|
|
}
|
|
let key;
|
|
try {
|
|
key = await openpgp.readKey({ armoredKey: publicKey });
|
|
} catch (e) {
|
|
alert('Encryption key is corrupted, unable to encrypt message');
|
|
return;
|
|
}
|
|
|
|
const encrypted = await openpgp.encrypt({
|
|
message: await openpgp.createMessage({ text: document.getElementById("to_encrypt").value }),
|
|
encryptionKeys: key,
|
|
format: 'armored'
|
|
});
|
|
|
|
const with_comment = encrypted.replace('-----BEGIN PGP MESSAGE-----', '-----BEGIN PGP MESSAGE-----\nComment: Decrypt this message at https://in-event-of-death.github.io/v1/')
|
|
|
|
document.getElementById("encrypted").value = with_comment;
|
|
}
|
|
|
|
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 loaded, require '+private_keys[0].required+' keys to decrypt the message');
|
|
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;
|
|
let has_duplicate = 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)) {
|
|
if(k.x in has_x) {
|
|
has_duplicate = true;
|
|
} else {
|
|
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>Decryption key is corrupted</b></span>' : '';
|
|
return;
|
|
}
|
|
let message = 'Decryption keys for <i><b>'+atob(private_keys[0].name)+'</b></i> loaded';
|
|
let error = '';
|
|
|
|
if(has_duplicate) {
|
|
error = '<br><span style="color:red">An decryption key is pasted in twice, each key can only be used once, please combine your decryption key with other decryption keys to decrypt messages</span>';
|
|
}
|
|
|
|
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 decryption 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();
|
|
}
|
|
|
|
async function example() {
|
|
let m = document.getElementById("to_encrypt"), m2 = 'Example message to encrypt generated at '+(new Date())
|
|
m.value = m.value.replace(/Example message to encrypt/, m2) || m2;
|
|
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: #334;
|
|
}
|
|
#content {
|
|
background-color:#fff;
|
|
padding: 10px;
|
|
border-radius: 5px;
|
|
}
|
|
}
|
|
@media (min-width: 1060px) {
|
|
#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: 1060px) {
|
|
#forkongithub { display: none; }
|
|
}
|
|
body {
|
|
font: 16px/1.5 'Helvetica Neue',Arial,'Liberation Sans',FreeSans,sans-serif;
|
|
text-align: justify;
|
|
line-height: 1.4;
|
|
}
|
|
h1 { font-size: 40px; }
|
|
h2 { font-size: 35px; }
|
|
.inp { font-size: 16px; }
|
|
button { font-size: 16px; }
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div id="content">
|
|
<center><a name="description"></a><h1>Encrypted Messages for the Event of Death</h1></center>
|
|
<noscript><div style="font-size:50px; color:red; text-align:center;">Javascript is required!!!</div></noscript>
|
|
<p>
|
|
This webpage encrypt messages that can be passed along in the event of death. This is done by <i>splitting</i> your decryption key into multiple parts. Your encrypted messages can only be decrypted once <i>enough</i> of the decryption keys are combined back together. For example, you can generate 10 different decryption keys such that any 3 decryption keys can be used to decrypt your message. This means that your message can only be decrypted once enough trusted individuals combine their decryption keys together.
|
|
</p>
|
|
<p>
|
|
To see an example of how this webpage works, click the
|
|
<button type="button" onclick="example()" style='font-weight: bold; font-size:14px;'>Generate Example</button>
|
|
button.
|
|
</p>
|
|
|
|
<p>
|
|
This webpage is entirely self contained, so it can be saved as a file for use offline. Encrypted is handled using <a href="https://openpgpjs.org/">OpenPGP.js</a> and the decryption key is securly split using <a href="https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing">Shamir's Secret Sharing</a>.
|
|
</p>
|
|
|
|
<p>
|
|
If you already have a decryption key and want to decrypt a message, go to the <a href="#decrypt">Decrypt Message</a> section below.
|
|
</p>
|
|
|
|
|
|
<center>
|
|
<button type="button" onclick="example()" style='font-size: 20px; margin:10px; padding: 10px; width: 40%; font-weight: bold;'>Generate Example</button>
|
|
</center>
|
|
|
|
<hr>
|
|
<center><a name="generate"></a><h2>Generate Encryption Key</h2></center>
|
|
|
|
<p>Your Name: <input type="text" value="John Doe" id="user_name" class="inp" onchange="generate_key_inp_change()"></p>
|
|
<p>Number of decryption keys to generate: <input type="number" value="10" id="number_private_generate" class="inp" style='width: 70px;' onchange="generate_key_inp_change()"></p>
|
|
<p>Number of decryption keys required to decrypt all messages: <input type="number" value="3" id="number_private_required" class="inp" style='width: 70px;' onchange="generate_key_inp_change()"></p>
|
|
<p><center><button type="button" onclick="generate_key()" style='font-weight: bold; font-size: 20px; margin:10px; padding:10px; width: 40%'>Generate Encryption Key</button></center></p>
|
|
|
|
<br>
|
|
|
|
<p><b>Public key used for <u>Encryption</u>:</b><br>
|
|
|
|
The Public Key can <i><b>only</b></i> be used to encrypt messages. You <i><b>should</b></i> save this key somewhere safe, so that you can create more ecrypted messages in the future. It safe to share this key with other people as it can not be used to decrypt messages.
|
|
<br>
|
|
To use the Public Key to encrypt a message, use the <a href="#encrypt">Encryption</a> section of this webpage below, or 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" spellcheck="false"></textarea>
|
|
</p>
|
|
|
|
|
|
<p>
|
|
<b>Private keys for <u>Decryption</u>:</b><br> The Private key are used to decrypt all messages. Ideally, each of your trusted friends and family members will be given 1 of these keys. Note, <i><u><b>anyone</b></u></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 messages.
|
|
<br>
|
|
<ol id="private_keys">
|
|
<li><input type="text" size=80 readonly /></li>
|
|
<li><input type="text" size=80 readonly /></li>
|
|
<li><input type="text" size=80 readonly /></li>
|
|
</ol>
|
|
<center><button onclick="copy_private_keys()" disabled id="copy_private">Copy all Encryption and Decryption Keys</button></center>
|
|
</p>
|
|
|
|
<hr>
|
|
<center><a name="encrypt"></a><h2>Encrypt Message</h2></center>
|
|
|
|
<p><b>Public Key for Encryption:</b><br>
|
|
This encruption should come from the previous <a href="#generate">generate encryption key section</a>.<br>
|
|
<textarea id="public_key" cols=60 rows=10 autocomplete="off" onfocus="this.value='';">-----BEGIN PGP PUBLIC KEY BLOCK-----
|
|
Paste your public key here to encrypting a message.
|
|
|
|
If you do not already have a public key, you can generate
|
|
a new key in the section above
|
|
...
|
|
-----END PGP PUBLIC KEY BLOCK-----
|
|
</textarea>
|
|
</p>
|
|
|
|
<p><b>Message to Encrypt:</b><br>
|
|
Write the message that you want to encrypt. You can write any message that you want. The message can be as long as you want. You can encrypt multiple messages, and even come back and encrypt more messages later as long as you save the public encryption key.<br>
|
|
<center><textarea id="to_encrypt" style='width: 95%;' rows=15 autocomplete="off">Example message to encrypt
|
|
|
|
This message can contain any content, for example 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></center>
|
|
</p>
|
|
|
|
<p><center><button type="button" style='font-weight: bold; font-size: 20px; width: 40%; margin:10px; padding: 10px;' onclick="encrypt()">Encrypt message</button></center></p>
|
|
|
|
<p><b>Encrypted Message:</b> <br> This is the encrypted message. You must make sure that there is some way this message is shared to your trusted friends and family members. This message can <i><b>only</b></i> be decrypted when enough of the decryption keys (generated above) are combined together. As such, it is safe to share with all of your trusted individuals. For example, you might consider emailing the encrypted message to all of your trusted individuals, or saving the encrypted messages into a shared document.<br>
|
|
<textarea id="encrypted" readonly cols=60 rows=10 autocomplete="off" onclick="this.select();"></textarea>
|
|
</p>
|
|
|
|
|
|
<hr>
|
|
<center><a name="decrypt"></a><h2>Decrypt Message</h2></center>
|
|
|
|
<p><b>Decryption Keys:</b>
|
|
<br>
|
|
These decryption keys should have been provided to you by the individual
|
|
who has encrypted their messages. You will need to collect multiple
|
|
decryption keys before you can decrypt any messages.
|
|
<br><br>
|
|
Paste in your decryption keys here:
|
|
<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><b>Encrypted message:</b><br>
|
|
You should have been provided an encrypted message by the individual who originally gave you the decryption key:
|
|
<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><center><button type="button" onclick="decrypt()" style='font-size: 20px; margin:10px; padding: 10px; width: 40%; font-weight: bold;'>Decrypt message</button></center></p>
|
|
|
|
<p><b>Decrypted Message:</b><br>
|
|
<center><textarea autocomplete="off" id="decrypted" readonly style='width: 95%;' rows=15 ></textarea></center>
|
|
</p>
|
|
|
|
<footer><center>Created By <a href="https://matthewfl.com">Matthew Francis-Landau</a> (2024)</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>
|