Created
August 18, 2019 09:32
-
-
Save qwtel/188374d8450ae210ddb1bb21cd389a93 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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