Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save qwtel/188374d8450ae210ddb1bb21cd389a93 to your computer and use it in GitHub Desktop.
Save qwtel/188374d8450ae210ddb1bb21cd389a93 to your computer and use it in GitHub Desktop.
import md5 from "js-md5";
import { base64ToUint8Array, splitUint8Array, concatUint8Arrays } from "./utils";
const HEAD_SIZE_DWORD = 2;
const SALT_SIZE_DWORD = 2;
export async function decryptCryptoJSCipherBase64(
cryptoJSCipherBase64,
password,
{ keySizeDWORD = 256 / 32, ivSizeDWORD = 128 / 32, iterations = 1 } = {},
) {
const { salt, ciphertext } = parseCryptoJSCipherBase64(cryptoJSCipherBase64);
const { key, iv } = await dangerouslyDeriveParameters(password, salt, keySizeDWORD, ivSizeDWORD, iterations);
const plaintextArrayBuffer = await crypto.subtle.decrypt({ name: "AES-CBC", iv }, key, ciphertext);
return new TextDecoder().decode(plaintextArrayBuffer);
}
function parseCryptoJSCipherBase64(cryptoJSCipherBase64) {
let salt;
let ciphertext = base64ToUint8Array(cryptoJSCipherBase64);
const [head, body] = splitUint8Array(ciphertext, HEAD_SIZE_DWORD * 4);
// This effectively checks if the ciphertext starts with 'Salted__'.
// Alternatively we could do `atob(cryptoJSCipherBase64.substr(0, 11)) === "Salted__"`.
const headDataView = new DataView(head.buffer);
if (headDataView.getInt32(0) === 0x53616c74 && headDataView.getInt32(4) === 0x65645f5f) {
[salt, ciphertext] = splitUint8Array(body, SALT_SIZE_DWORD * 4);
}
return { ciphertext, salt };
}
async function dangerouslyDeriveParameters(password, salt, keySizeDWORD, ivSizeDWORD, iterations) {
const passwordUint8Array = new TextEncoder().encode(password);
const keyPlusIV = dangerousEVPKDF(passwordUint8Array, salt, keySizeDWORD + ivSizeDWORD, iterations);
const [rawKey, iv] = splitUint8Array(keyPlusIV, keySizeDWORD * 4);
const key = await crypto.subtle.importKey("raw", rawKey, "AES-CBC", false, ["decrypt"]);
return { key, iv };
}
function dangerousEVPKDF(passwordUint8Array, saltUint8Array, keySizeDWORD, iterations) {
let derivedKey = new Uint8Array();
let block = new Uint8Array();
while (derivedKey.byteLength < keySizeDWORD * 4) {
block = md5.arrayBuffer(concatUint8Arrays(block, passwordUint8Array, saltUint8Array));
for (let i = 1; i < iterations; i++) {
block = md5.arrayBuffer(block);
}
block = new Uint8Array(block);
derivedKey = concatUint8Arrays(derivedKey, block);
}
return derivedKey;
}
// Usage
import AES from "crypto-js/aes";
const cleartext = "This is a message to be encrypted and decrypted";
const password = "passw0rd!";
const cryptoJSCiphertext = AES.encrypt(cleartext, password).toString();
decryptCryptoJSCipherBase64(cryptoJSCiphertext, password).then(x => {
console.log(x);
console.log(x === cleartext);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment