Skip to content

Instantly share code, notes, and snippets.

@torkelrogstad torkelrogstad/aes.js Secret
Last active Jun 25, 2019

Embed
What would you like to do?
const CryptoJS = require("crypto-js");
const crypto = require("crypto");
/**
* Generates a random 16 byte IV
*
* @return {CryptoJS.WordArray}
*/
function getIV() {
/*
If you're in the browser, you'd do something like this:
const array = new Int8Array(16);
crypto.getRandomValues(array);
retun CryptoJS.enc.Hex.parse(array.buffer.toString("hex"))
*/
const buf = crypto.randomBytes(16);
const iv = CryptoJS.enc.Hex.parse(buf.toString("hex"));
return iv;
}
/**
* Serializes the given encrypted value.
* @param {CryptoJS.WordArray} encrypted
*/
function serializeEncrypted(encrypted) {
return encrypted.iv
.concat(encrypted.ciphertext)
.toString(CryptoJS.enc.Base64);
}
/**
* Trims any trailing zero bytes from the given
* word array
*
* @param {CryptoJS.WordArray} words
* @return {CryptoJS.WordArray}
*/
function trimWordArray(words) {
const hex = words.toString(CryptoJS.enc.Hex);
const lastNonZero =
hex.length - [...hex].reverse().findIndex(char => char !== "0");
const trimmedHex = hex.slice(0, lastNonZero === 0 ? undefined : lastNonZero);
return CryptoJS.enc.Hex.parse(trimmedHex);
}
/**
* Deserializes the given base64 into IV and
* ciphertext
* @param {string} encrypted
* @returns {[CryptoJS.WordArray, CryptoJS.WordArray]} ciphertext and IV
*/
function deserializeEncrypted(encrypted) {
const deserialized = CryptoJS.enc.Base64.parse(encrypted);
const deserializedIV = CryptoJS.lib.WordArray.create(
deserialized.words.slice(0, 4) // each word is 4 bytes
);
const deserializedCipherText = CryptoJS.lib.WordArray.create(
deserialized.words.slice(4)
);
return [trimWordArray(deserializedCipherText), deserializedIV];
}
/**
* Encrypts the given cleartext with the
* given preimage
*
* @param {string} cleartext The string to encrypt
* @param {string} preimage Hex string of the preimage
* @returns Base64 string of the ciphertext and IV
*/
function encrypt(cleartext, preimage) {
const parsedPreimage = CryptoJS.enc.Hex.parse(preimage);
const encrypted = CryptoJS.AES.encrypt(cleartext, parsedPreimage, {
mode: CryptoJS.mode.CFB,
padding: CryptoJS.pad.NoPadding,
iv: getIV()
});
return serializeEncrypted(encrypted);
}
/**
* @param {string} base64 Base64 encoded IV and ciphertext
* @param {string} preimage hex string of the preimage
* @param {string} publicKey hex string of the public key
*/
function decrypt(base64, preimage) {
const preimageByteLength = preimage.length / 2;
if ([16, 24, 32].every(l => preimageByteLength !== l)) {
throw new TypeError(
`The given preimage is ${preimageByteLength} bytes long! Valid lengths: 16, 24, 32`
);
}
const parsedPreimage = CryptoJS.enc.Hex.parse(preimage);
const [ciphertext, IV] = deserializeEncrypted(base64);
const decrypted = CryptoJS.AES.decrypt(
{ ciphertext: ciphertext },
parsedPreimage,
{
mode: CryptoJS.mode.CFB,
padding: CryptoJS.pad.NoPadding,
iv: IV
}
);
return decrypted.toString(CryptoJS.enc.Utf8);
}
const cleartext = "The quick brown fox jumps over the lazy dog. 👻 👻";
console.log("cleartext:", cleartext);
const preimage = "1200f3c48578ac8740782fb03c7b9a08";
const publicKey =
"ec9d90c0951e21f46d4e81724f93e5a151df119529af999d13d1b4eb79da275c75";
const encrypted = encrypt(cleartext, preimage, publicKey);
console.log("encrypted:", encrypted);
const encryptedStr =
"N+bMpWhmAOvNg865byOkPi3KCLxwBF8YIFANkzuhdRsIdp9f6EuBnLbz48fmVn6J8Sn9E6Nt64MX42n05PRJUciTfIKKGA==";
const decrypted = decrypt(encryptedStr, preimage, publicKey);
console.log("decrypted:", decrypted);
console.log();
const encryptedFromPython =
"j2zaYqdP5ouCZHmhhVOgu7syZp3VHtptR6aedsPxYJwwY1hnFXzmC8j/b2TChdZDh/c5nYws5Ty1zdpJ3siXlA=='";
const preimageFromPython = "53ab04c0b3a7189f4c98bc86e8a021f7";
const decryptedFromPython = decrypt(encryptedFromPython, preimageFromPython);
console.log("decryptedFromPython:", decryptedFromPython);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.