-
-
Save torkelrogstad/4611d73567cdcbc40d1da144169c9b03 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
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