diff --git a/README.md b/README.md
index 7141b13..e1e8b02 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,11 @@
# AES Crypto Playground [![License](https://img.shields.io/github/license/dscheg/aes.svg)](https://raw.githubusercontent.com/dscheg/aes/main/LICENSE)
-Play around with AES encryption and decryption to learn about the different complexities of symmetric encryption implementations. This page uses only client-side cryptography and does not send data anywhere. But still, do not play with sensitive data. The playground provided "AS IS" without any warranties
+Play around with AES encryption and decryption to learn about the different complexities of symmetric encryption implementations. This page uses only client-side cryptography and does not send data anywhere. But still, do not play with sensitive data. The playground provided "AS IS" without any warranties.
+
+Try attacks:
+* Bit Flipping
+* Padding Oracle
+* Compression Oracle
+* ...
https://dscheg.github.io/aes
diff --git a/aes.css b/aes.css
index 5d98e3a..a65cd38 100644
--- a/aes.css
+++ b/aes.css
@@ -45,8 +45,8 @@ input {
#dir {
position: absolute;
- bottom: 0;
- right: 0;
+ bottom: 4px;
+ right: 4px;
font-size: 50%;
}
@@ -87,10 +87,7 @@ input {
background-image: url("modes/CFB_encryption.min.svg");
}
-.xed.cipher button {
- display: none;
-}
-.xed.decrypt button {
+.xed.decrypt .xed-load {
display: none;
}
@@ -100,16 +97,21 @@ input {
.xor:has(textarea:focus) {
opacity: 1;
}
-.grayed {
+.grayed, .grayed textarea {
color: #777;
}
#result {
- white-space: pre;
font: min(1.8vw, 1.8vh)/1em 'Julia';
+ width: min(80vw, 78vh);
+ resize: none;
+ border: 1px dotted #bbb;
+}
+#result:focus {
+ outline: transparent !important;
}
.night #result {
- filter: invert(1) brightness(0.5);
+ filter: hue-rotate(180deg);
}
#result.error {
color: red;
diff --git a/aes.js b/aes.js
index eaf860b..749043b 100644
--- a/aes.js
+++ b/aes.js
@@ -9,8 +9,10 @@ const $modeimg = $("#mode-img");
const $padding = $("#padding");
const $keysize = $("#keysize");
const $blocksize = $("#blocksize");
+const $compress = $("#compress");
const $plain = $(".xed.plain");
+const $deflate = $(".xed.deflate");
const $cipher = $(".xed.cipher");
const $decrypt = $(".xed.decrypt");
const $xor = $(".xed.xor");
@@ -23,7 +25,7 @@ const update = () => $plain.dispatchEvent(new Event("change"));
const setResult = (isOk, text) => {
$result.classList[isOk ? 'add' : 'remove']('success');
$result.classList[isOk ? 'remove' : 'add']('error');
- $result.textContent = text;
+ $result.value = text;
};
$dir.value = localStorage.getItem("dir") || $dir[0].value;
@@ -71,6 +73,15 @@ $key.oninput = e => {
};
$key.oninput();
+$compress.value = localStorage.getItem("compression") || "";
+$compress.oninput = e => {
+ localStorage.setItem("compression", $compress.value);
+ const hide = $compress.value.length === 0;
+ $deflate.previousElementSibling.hidden = hide;
+ $deflate.hidden = hide;
+ update();
+};
+
//const importKey = () => crypto.subtle.importKey("raw", fromHex($key.value), {name: "AES-CBC"}, true, ["encrypt", "decrypt"]);
const getCryptoParams = () => Object({
@@ -79,14 +90,26 @@ const getCryptoParams = () => Object({
padding: CryptoJS.pad[$padding.value]
});
+const compress = (data, mode) => stream = mode != 0 && $compress.value != ""
+ ? new Response(new Blob([data]).stream().pipeThrough(mode > 0 ? new CompressionStream($compress.value) : new DecompressionStream($compress.value)))
+ .arrayBuffer().then(buf => new Uint8Array(buf))
+ : new Promise((resolve, _) => resolve(data));
+
$plain.addEventListener("change", e => {
- const plain = $plain.getDataHex();
+ const plain = $plain.getData();
+ compress(plain, 1).then(data => {
+ $deflate.setData(data);
+ localStorage.setItem("hex", toHex(plain));
+ }).catch(e => setResult(false, e));
+});
+
+$deflate.addEventListener("change", e => {
+ const compressed = $deflate.getDataHex();
const enc = CryptoJS.AES.encrypt(
- CryptoJS.enc.Hex.parse(plain),
+ CryptoJS.enc.Hex.parse(compressed),
CryptoJS.enc.Hex.parse($key.value),
getCryptoParams());
$cipher.setDataHex(enc.ciphertext.toString(CryptoJS.enc.Hex));
- localStorage.setItem("hex", plain);
/*importKey().then(key => crypto.subtle.encrypt({name: "AES-" + $mode.value, iv: fromHex($iv.value)}, key, $plain.getData()).then(cipher => {
$cipher.setData(new Uint8Array(cipher));
@@ -95,20 +118,24 @@ $plain.addEventListener("change", e => {
$cipher.addEventListener("change", e => {
try {
+ const cipher = $cipher.getDataHex();
const decrypt = CryptoJS.AES.decrypt(
- CryptoJS.lib.CipherParams.create({ciphertext: CryptoJS.enc.Hex.parse($cipher.getDataHex())}),
+ CryptoJS.lib.CipherParams.create({ciphertext: CryptoJS.enc.Hex.parse(cipher)}),
CryptoJS.enc.Hex.parse($key.value),
getCryptoParams());
const hex = decrypt.toString(CryptoJS.enc.Hex);
if($plain.getDataHex().length > 0 && hex.length === 0) {
- $decrypt.setDataHex('');
+ $decrypt.classList.add('grayed');
throw new Error("Decryption failed");
}
- $decrypt.setDataHex(hex);
- const text = new TextDecoder().decode(fromHex(hex));
- setResult(true, JSON.stringify(JSON.parse(text.replace(/[\x00-\x1f]/ig, '\uFFFD')), null, 4));
+ compress(fromHex(hex), -1).then(data => {
+ $decrypt.setData(data);
+ $decrypt.classList.remove('grayed');
+ const text = new TextDecoder().decode(data);
+ setResult(true, JSON.stringify(JSON.parse(text.replace(/[\x00-\x1f]/ig, '\uFFFD')), null, 4));
+ }).catch(e => setResult(false, e));
} catch(e) {
setResult(false, e);
}
diff --git a/favicon.png b/favicon.png
new file mode 100644
index 0000000..72d620d
Binary files /dev/null and b/favicon.png differ
diff --git a/hex.css b/hex.css
index 431ae52..704ce70 100644
--- a/hex.css
+++ b/hex.css
@@ -31,3 +31,11 @@
background-color: #fff5d9;
box-shadow: 0 0 10px #39e;
}
+
+.xed-top, .xed-left, .xed-size {
+ color: #aaa;
+}
+
+.xed-size {
+ text-align: right;
+}
diff --git a/hex.js b/hex.js
index 065a9e9..66ab56b 100644
--- a/hex.js
+++ b/hex.js
@@ -43,8 +43,10 @@ $$(".xed").forEach($xed => {
$a.click();
};
+ const $size = $(".xed-size", $xed);
const $left = $(".xed-left", $xed);
const $right = $(".xed-right", $xed);
+ $right.value = '';
$right.oninput = function() {
const pos = $right.value
@@ -81,11 +83,14 @@ $$(".xed").forEach($xed => {
.map(i => (1E7 + (16 * i).toString(16)).slice(-8))
.join('\n');
- $right.value = range(Math.floor(($hex.value.length + 1) / 3))
+ const len = Math.floor(($hex.value.length + 1) / 3);
+ $right.value = range(len)
.map(i => parseInt($hex.value.substr(i * 3, 2), 16))
.map(c => String.fromCodePoint(c + (0x20 <= c && c < 0x7F ? 0 : 0xF000)))
.join('').replace(/(.{16})(?=.)/g, '$1\n');
+ $size.textContent = 'size=' + len;
+
const newpos = pos > 0 && [' ', '\n'].includes($hex.value[pos - 1]) ? pos - 1 : pos;
$hex.setSelectionRange(newpos, newpos);
diff --git a/index.html b/index.html
index a69ab27..7babe7f 100644
--- a/index.html
+++ b/index.html
@@ -9,6 +9,7 @@
+
@@ -26,7 +27,7 @@ AES Crypto Playground
- |
- |
+ | |
|
@@ -61,6 +68,23 @@ Plain
+Compressed
+
+
Cipher
@@ -69,12 +93,12 @@ Cipher
- |
+ | |
|
- | |
+ |
@@ -86,12 +110,12 @@ Decrypted
- |
+ | |
|
-
- | |
+
+ | |
@@ -103,17 +127,17 @@ XOR Lines
- |
+ | |
|
- | |
+ |
Result
-n/a
+