Skip to content

Commit ef1aa56

Browse files
Update
1 parent b4eb313 commit ef1aa56

File tree

3 files changed

+188
-25
lines changed

3 files changed

+188
-25
lines changed

apps/aes/index.htmlx

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
%{HEAD(title='AES Example')}
5+
<script defer src="/assets/js/githubshortcut.min.js?title=Contribute&position=top-right&margin=1.5&href=SteveBeeblebrox/stevebeeblebrox.github.io/blob/main/apps/aes/index.htmlx"></script>
6+
<style type="text/less">
7+
div {
8+
padding: 0.5em;
9+
}
10+
body, html {
11+
color: #111;
12+
background-color: #eee;
13+
margin: 0;
14+
height: 100%;
15+
}
16+
body {
17+
display: flex;
18+
justify-content: center;
19+
align-items: center;
20+
}
21+
::file-selector-button, button {
22+
border: none;
23+
background: silver;
24+
border-radius: 4px;
25+
padding: 0.5em;
26+
cursor: pointer;
27+
&:hover {
28+
filter: brightness(80%);
29+
}
30+
}
31+
input[type="password"] {
32+
border: none;
33+
border-bottom: 1px solid silver;
34+
background-color: transparent;
35+
&:hover {
36+
filter: brightness(80%);
37+
}
38+
}
39+
</style>
40+
<script %{TS}>
41+
let result = JSX.createState(null), fname = name => name;
42+
result.connectCallback(async v => v && await toast('Done!', 'medium'));
43+
async function f2b(file) {
44+
const reader = new FileReader();
45+
46+
const p = new Promise(function(resolve,reject) {
47+
reader.onload = e => resolve(e.target.result);
48+
reader.onerror = e => reject('Error reading file');
49+
});
50+
51+
reader.readAsArrayBuffer(file);
52+
53+
return p;
54+
}
55+
56+
async function fencrypt() {
57+
if(!password.value) {
58+
return await toast('No password provided!', 'medium');
59+
}
60+
if(!file.files.length) {
61+
return await toast('No file provided!', 'medium')
62+
}
63+
64+
result.set(null);
65+
try {
66+
const buf = await f2b(file.files[0]);
67+
result.set(await AESGCMEncryption.encryptBuffer(buf,password.value));
68+
fname = name => name+'.crypt'
69+
} catch(e) {
70+
result.set(null);
71+
console.error(e);
72+
await toast(e,'long');
73+
}
74+
}
75+
76+
async function fdecrypt() {
77+
if(!file.files.length) {
78+
return await toast('No file provided!', 'medium')
79+
}
80+
81+
result.set(null);
82+
try {
83+
const buf = await f2b(file.files[0]);
84+
if(password.value) {
85+
await toast('Decrypting...', 'medium');
86+
result.set(await AESGCMEncryption.decryptBuffer(buf,password.value));
87+
fname = name => name.replace(/\.crypt$/,'');
88+
} else {
89+
await toast('No password provided! Doing brute force...', 'medium');
90+
let r = null, n = 0;
91+
while(!r && n < 99999) {
92+
try {
93+
r = await AESGCMEncryption.decryptBuffer(buf,''+n++);
94+
} catch(_) {}
95+
}
96+
if(r) {
97+
result.set(r);
98+
fname = name => name.replace(/\.crypt$/,'');
99+
} else {
100+
result.set(null);
101+
await toast('Brute force failed!', 'long');
102+
}
103+
}
104+
} catch(e) {
105+
result.set(null);
106+
console.error('e');
107+
await toast(e,'long');
108+
}
109+
}
110+
111+
async function fdownload() {
112+
const objectURL = URL.createObjectURL(new Blob([result.get()]));
113+
Object.assign(document.createElement('a'), {href: objectURL, download: fname(file.files[0].name) ?? '', onclick() {
114+
requestAnimationFrame(()=>URL.revokeObjectURL(objectURL));
115+
}}).click();
116+
}
117+
</script>
118+
</head>
119+
<body>
120+
<main>
121+
<h1>AES GCM File Encryption</h1>
122+
<div>
123+
<label for="file">Input File:</label>
124+
<script %{TS}>
125+
const fin = $ctx = <input id="file" name="file" type="file" accept="tar.gz.crypt"/>;
126+
</script>
127+
</div>
128+
<div>
129+
<label for="password">Password:</label>
130+
<script %{TS}>
131+
const password = $ctx = <input id="password" name="password" type="password"/>;
132+
</script>
133+
</div>
134+
<hr>
135+
<div>
136+
<script %{TS}>
137+
const encrypt = $ctx = <button id="encrypt" name="encrypt" title="Encrypt" onclick={fencrypt}>Encrypt</button>;
138+
</script>
139+
<script %{TS}>
140+
const decrypt = $ctx = <button id="decrypt" name="decrypt" title="Decrypt" onclick={fdecrypt}>Decrypt</button>;
141+
</script>
142+
<script %{TS}>
143+
const download = $ctx = <button id="download" name="download" title="Download" onclick={fdownload}>Download</button>;
144+
result.connectCallback(v => download.style.display = (v ? 'unset' : 'none'));
145+
</script>
146+
</div>
147+
148+
</main>
149+
</body>
150+
</html>

assets/ts/encryption.ts

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,47 @@
11
/**
22
* AES-GCM Encryption
33
* (c) 2017 Chris Veness - MIT Licence
4+
* Adapted for TypeScript and buffers
45
* https://gist.github.com/chrisveness/43bcda93af9f646d083fad678071b90a
56
*/
6-
namespace AESGCMEncryption {
7+
namespace AESGCMEncryption {
78
export async function encrypt(plaintext: string, password: string): Promise<string> {
8-
const passwordUtf8 = new TextEncoder().encode(password)
9-
const passwordHash = await crypto.subtle.digest('SHA-256', passwordUtf8)
10-
const iv = crypto.getRandomValues(new Uint8Array(12))
11-
const algorithm = { name: 'AES-GCM', iv: iv }
12-
const key = await crypto.subtle.importKey('raw', passwordHash, algorithm, false, ['encrypt'])
13-
const ptUint8 = new TextEncoder().encode(plaintext)
14-
const ctBuffer = await crypto.subtle.encrypt(algorithm, key, ptUint8)
15-
const ctArray = Array.from(new Uint8Array(ctBuffer))
16-
const ctStr = ctArray.map(byte => String.fromCharCode(byte)).join('')
17-
const ctBase64 = btoa(ctStr)
18-
const ivHex = Array.from(iv).map(b => ('00' + b.toString(16)).slice(-2)).join('')
19-
return ivHex + ctBase64
9+
const sha256 = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(password));
10+
const algorithm = {name: 'AES-GCM', iv: crypto.getRandomValues(new Uint8Array(12))};
11+
const key = await crypto.subtle.importKey('raw', sha256, algorithm, false, ['encrypt']);
12+
const ctBuffer = await crypto.subtle.encrypt(algorithm, key, new TextEncoder().encode(plaintext));
13+
const ctBase64 = btoa(Array.from(new Uint8Array(ctBuffer)).map(byte => String.fromCharCode(byte)).join(''));
14+
const ivHex = Array.from(algorithm.iv).map(b => b.toString(16).padStart(2,'0')).join('');
15+
return ivHex + ctBase64;
2016
}
2117

2218
export async function decrypt(ciphertext: string, password: string): Promise<string> {
23-
const passwordUtf8 = new TextEncoder().encode(password)
24-
const passwordHash = await crypto.subtle.digest('SHA-256', passwordUtf8)
25-
const iv = ciphertext.slice(0,24).match(/.{2}/g)!.map(byte => parseInt(byte, 16))
26-
const algorithm = { name: 'AES-GCM', iv: new Uint8Array(iv) }
27-
const key = await crypto.subtle.importKey('raw', passwordHash, algorithm, false, ['decrypt'])
28-
const ctStr = atob(ciphertext.slice(24))
29-
const ctUint8 = new Uint8Array(ctStr.match(/[\s\S]/g)!.map(ch => ch.charCodeAt(0)))
30-
const plainBuffer = await crypto.subtle.decrypt(algorithm, key, ctUint8)
31-
const plaintext = new TextDecoder().decode(plainBuffer)
32-
return plaintext
19+
const sha256 = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(password));
20+
const algorithm = {name: 'AES-GCM', iv: new Uint8Array(ciphertext.slice(0,24).match(/.{2}/g)!.map(byte => parseInt(byte, 16)))};
21+
const key = await crypto.subtle.importKey('raw', sha256, algorithm, false, ['decrypt']);
22+
const ctStr = atob(ciphertext.slice(24));
23+
const ctUint8 = new Uint8Array(ctStr.match(/[\s\S]/g)!.map(ch => ch.charCodeAt(0)));
24+
return new TextDecoder().decode(await crypto.subtle.decrypt(algorithm, key, ctUint8));
25+
}
26+
27+
function concatBuffers(left: ArrayBuffer, right: ArrayBuffer): ArrayBuffer {
28+
const x = new Uint8Array(left.byteLength + right.byteLength);
29+
x.set(new Uint8Array(left),0);
30+
x.set(new Uint8Array(right),left.byteLength);
31+
return x;
32+
}
33+
34+
export async function encryptBuffer(plaintext: ArrayBuffer, password: string): Promise<ArrayBuffer> {
35+
const sha256 = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(password));
36+
const algorithm = {name: 'AES-GCM', iv: crypto.getRandomValues(new Uint8Array(12))};
37+
const key = await crypto.subtle.importKey('raw', sha256, algorithm, false, ['encrypt']);
38+
return concatBuffers(algorithm.iv, await crypto.subtle.encrypt(algorithm, key, plaintext));
39+
}
40+
41+
export async function decryptBuffer(ciphertext: ArrayBuffer, password: string): Promise<ArrayBuffer> {
42+
const sha256 = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(password));
43+
const algorithm = {name: 'AES-GCM', iv: new Uint8Array(ciphertext.slice(0,12))};
44+
const key = await crypto.subtle.importKey('raw', sha256, algorithm, false, ['decrypt']);
45+
return await crypto.subtle.decrypt(algorithm, key, ciphertext.slice(12));
3346
}
3447
}

wyvern

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ function tsc() {
8484
out="$BUILD_DIR/$("$WYVERN_DIR/pt$EXE_SUFFIX" $1 ts~js '(?<=\.m|\.)tsx?$~~js' '(?=\..?js.?$)~~.min')"
8585
if isnewer "$1" "$out"; then
8686
mkfile "$out"
87-
"$WYVERN_DIR/mtsc$EXE_SUFFIX" --target=es2019 --jsx=JSX.createElement $1 --minify --preprocessor --out=- > $out
87+
"$WYVERN_DIR/mtsc$EXE_SUFFIX" --target=es2019 --jsx=JSX.createElement $1 --minify --preprocessor comment --out=- > $out
8888
fi
8989
}
9090

@@ -100,7 +100,7 @@ function htmlx() {
100100
out="$BUILD_DIR/$("$WYVERN_DIR/pt$EXE_SUFFIX" $1 -e html)"
101101
if isnewer "$1" "$out"; then
102102
mkfile "$out"
103-
python3 "$WYVERN_DIR/generator.py" $1 | "$WYVERN_DIR/mtsc$EXE_SUFFIX" --verbose --html --target=es2019 --jsx=JSX.createElement --module --out | "$WYVERN_DIR/mless$EXE_SUFFIX" --html --verbose --out= > $out
103+
python3 "$WYVERN_DIR/generator.py" $1 | "$WYVERN_DIR/mtsc$EXE_SUFFIX" --verbose --html --target=es2019 --jsx=JSX.createElement --out | "$WYVERN_DIR/mless$EXE_SUFFIX" --html --verbose --out= > $out
104104
fi
105105
}
106106

0 commit comments

Comments
 (0)