Convert Node crypto aes-256-cbc to CryptoJS - javascript

How to convert the following Node's built-in crypto module encryption to CryptoJS?
const crypto = require('crypto');
const pass = 'some,password:)with>spec(chars*'
const cipher1 = crypto.createCipher('aes-256-cbc', pass)
const c1 = cipher1.update(input, 'utf8', 'hex') + cipher1.final('hex')
I tried something like this, but the results are not the same:
const CryptoJS = require('crypto-js');
const pass = 'some,password:)with>spec(chars*'
const cipher2 = CryptoJS.AES.encrypt(input, pass, {
mode: CryptoJS.mode.CBC,
});
const c2 = cipher2.ciphertext.toString(CryptoJS.enc.Hex);
I need this to use as a Postman prerequest script as it does not support Node's crypto, but crypto-js.

Both codes use the OpenSSL proprietary key derivation function EVP_BytesToKey() with an iteration count of 1 and MD5 as digest.
NodeJS does not use a salt, while CryptoJS applies a random salt. For this reason, the NodeJS result is unchanged for each encryption, while the CryptoJS result always changes (assuming the same plaintext and passphrase).
Thus, to get the result of the NodeJS code with the CryptoJS code, you must not use a salt. However, by default, a salt is always applied. This can only be circumvented by explicitly determining key and IV with the key derivation function EvpKDF and then using both in the encryption:
var input = "The quick brown fox jumps over the lazy dog";
var pass = 'some,password:)with>spec(chars*'
var keySize = 32/4;
var ivSize = 16/4;
var kdf = CryptoJS.algo.EvpKDF.create({ keySize: keySize + ivSize, hasher: CryptoJS.algo.MD5 }).compute(pass, ''); // no salt!
var key = CryptoJS.lib.WordArray.create(kdf.words.slice(0, keySize), keySize * 4);
var iv = CryptoJS.lib.WordArray.create(kdf.words.slice(keySize), ivSize * 4);
var ciphertextCP = CryptoJS.AES.encrypt(input, key, {iv: iv}); // default: CBC, PKCS#7 padding
var ciphertext = ciphertextCP.ciphertext.toString(CryptoJS.enc.Hex);
document.getElementById("ct").innerHTML = ciphertext; // d98cf2d285bf0c1d796226190bf54d9c5540300ee1c6f35618f8bb3564b5053920ec958d31b41bbe4e4880e23543d709
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
<p style="font-family:'Courier New', monospace;" id="ct"></p>
This CryptoJS code produces the same ciphertext for the same plaintext and passphrase as the NodeJS code.
Note that the key derivation with EVP_BytesToKey() and the chosen parameters is deprecated and considered insecure today. This is true for NodeJS to a greater extent than for CryptoJS, due to the lack of a salt.
It is more secure to avoid the built-in key derivation function and specify the key directly.
To do this, use createCipheriv() in NodeJS and pass the key as WordArray in the CryptoJS code. This way, a random IV must be explicitly generated for each encryption.
Optionally, a reliable key derivation function like PBKDF2 can be used, which is supported by both libraries.

Related

Implement C# encryption in CryptoJS

I have situation where I need to create the same encryption method which is already up and running in C#. The concept behind this is, from where ever this encrypted key is logged, we will use the same C# project to decrypt it.
Below is the logic used in C#:
using var aes = new AesCryptoServiceProvider
{
Key = Encoding.UTF8.GetBytes(key),
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7
};
aes.GenerateIV();
using var encrypter = aes.CreateEncryptor(aes.Key, aes.IV);
using var cipherStream = new MemoryStream();
using (var tCryptoStream = new CryptoStream(cipherStream, encrypter, CryptoStreamMode.Write))
using (var tBinaryWriter = new BinaryWriter(tCryptoStream))
{
cipherStream.Write(aes.IV);
tBinaryWriter.Write(Encoding.UTF8.GetBytes(encryptMe));
tCryptoStream.FlushFinalBlock();
}
return Convert.ToBase64String(cipherStream.ToArray());
Key is the same key used in both C# and JavaScript. But still I am not able to generate the same encryption value as in C#.
I tried to go through other Stack Overflow posts related to this topic, but unable to figure the missing part in JavaScript. Can any one please help?
The key used in the C# code is UTF-8 encoded, so on the CryptoJS side the key must be parsed into a WordArray using the UTF-8 encoder (CryptoJS only interprets the key material as key if it is passed as a WordArray; if it is passed as string, it is interpreted as password and a key derivation function is applied, which would not be compatible with the C# code).
Also, the C# code concatenates IV and ciphertext, which must also happen in the CryptoJS code. This is necessary because the IV is required for decryption.
Fixed code:
var plaintext = 'The quick brown fox jumps over the lazy dog';
var key = CryptoJS.enc.Utf8.parse('01234567890123456789012345678901'); // Fix 1: parse as WordArray
var iv = CryptoJS.lib.WordArray.random(128 / 8);
var encrypted = CryptoJS.AES.encrypt(plaintext, key, {iv: iv}); // CBC, PKCS#7 padding by default
var ivCiphertext = iv.clone().concat(encrypted.ciphertext).toString(CryptoJS.enc.Base64); // Fix 2: concatenate IV and ciphertext
console.log(ivCiphertext); // e.g. e9iXcQ2sZ6AA2ne1c3490pAPWOrTGf4UttSSX1lOiKUqwP0oWRPFF83VhZQZMMBu9JKNWIfgS+9D5V39bI4rqg==
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
For a test, the ciphertexts cannot simply be compared because, due to the random IV, each encryption produces different ciphertexts.
One option for a test is to temporarily use the same IV in the C# code and in the CryptoJS code for the test (and only for the test, since a static IV is insecure!), which would produce the same ciphertexts that could then be compared.
Another option for a test is to decrypt the ciphertext produced with the CryptoJS code with the C# code for decryption.

Ruby OpenSSL to node.js Crypt conversion

I have been struggling with this for a couple of days and was wondering if anyone would have the experience to know these two encryption libraries well enough to help.
I am currently creating a SSO payload according to instructions given to me by a vendor. The steps to have this created are highlighted as follows:
Create an AES 256 CBC cypher of the payload
i. The key will be a SHA256 digest of the site token.
2. Base64 encode the initialization vector (IV) and encrypted payload from above
3. CGI-escape the output from step 2.
4. Your final payload would look something like ikUbqiutwMhi%2Bjg6WwUHyeZB76g6LdLGcrKrEV4YpvQ%3D%0A.
SHA256 will always generate a 32-byte hash, but it can’t be displayed nicely in Base64. When it’s displayed as Hex, it is 32 pairs of Hex values (a total of 64 characters on the screen) but representing only 32 bytes.
I was able to get it to work on Ruby with Open SSL, the code is:
require 'digest'
require 'openssl'
require "base64"
require 'cgi'
require 'json'
cipher = OpenSSL::Cipher.new('aes-256-cbc')
cipher.encrypt
cipher.key = Digest::SHA256.digest(siteToken)
iv = cipher.random_iv
data= unencryptedPayload
encrypted = cipher.update(JSON.generate(data)) + cipher.final
encoded = CGI::escape(Base64.encode64(iv + encrypted))
puts encoded
However, I have not yet had luck with Node.js's Crypto library. This is what I have so far:
const crypto = require('crypto');
// Defining algorithm
const algorithm = 'aes-256-cbc';
// Defining key
//'key' variable is defined and equal to siteToken in the OpenSSL version
//const key = siteToken;
// Defining iv
const iv = crypto.randomBytes(16);
// An encrypt function
function encrypt(text) {
// Creating Cipheriv with its parameter
let cipher = crypto.createCipheriv(
'aes-256-cbc', Buffer.from(key), iv);
// Updating text
let encrypted = cipher.update(text);
// Using concatenation
encrypted = Buffer.concat([encrypted, cipher.final()]);
// Returning iv and encrypted data
return { iv: iv.toString('hex'),
encryptedData: encrypted.toString('hex') };
}
// Displays output
var output = encrypt(unencryptedPayload);
I think my code has so far covered almost all of these except for the SHA256 digest of the site token. Does anyone know how I might achieve this in Node.js terms?
Thanks!

Decrypt openssl AES 256 CBC in browser/CryptoJS

I want to decrypt a string that has been encrypted with openssl on the server like this:
openssl enc -e -aes-256-cbc -pbkdf2 -a -S 0123456789ABCDEF -A -k mypassword
Note this is done providing only a salt and password, and openssl should handle key and IV automatically. Am I too optimistic that this can happen when the browser decrypts too? If at all possible, I want to do it with only those encryption settings, or the bare minimum of increased complexity. In the browser, I'm trying to decrypt with CryptoJS like this:
import * as CryptoJS from 'crypto-js'
const encrypted = <ENCRYPTED_STRING_FROM_SERVER>
const password = 'mypassword'
const salt = '0123456789ABCDEF'
const key = CryptoJS.PBKDF2(password, salt) // Generate key
const bytes = CryptoJS.AES.decrypt(encrypted, key)
const decrypted = bytes.toString(CryptoJS.enc.Utf8)
console.log(decrypted)
But the call to CryptoJS.AES.decrypt errors with Cannot read property '0' of undefined, crypto-js/cipher-core.js:371. The docs for CryptoJS.AES.decrypt are quite thin, and any settings I am aware of to change when calling that func seem to give the same error. Thanks to anyone who can shine light!
In the OpenSSL statement, the iteration count and digest are not specified, so the default values 10000 and SHA256 are used. This is relevant because CryptoJS uses different default values (1 and SHA1).
CryptoJS applies the OpenSSL format for the ciphertext, i.e. the encrypted data starts with the ASCII encoding of Salted__ followed by the salt and then the ciphertext. Therefore the beginning of the Base64 encoded ciphertext starts always with U2FsdGVkX1.
CryptoJS uses the WordArray data type, which encapsulates an array of words. A word consists of 4 bytes.
During decryption, ciphertext and salt must first be separated. Then, key and IV must be determined using PBKDF2. Due to the different default values, iteration count and digest must be specified explicitly. Finally it can be decrypted:
// 1. Separate ciphertext and salt
var encrypted = "U2FsdGVkX18BI0VniavN78vlhR6fryIan0VvUrdIr+YeLkDYhO2xyA+/oVXJj/c35swVVkCqHPh9VdRbNQG6NQ=="
var encryptedWA = CryptoJS.enc.Base64.parse(encrypted);
var prefixWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(0, 8/4)); // Salted__ prefix
var saltWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(8/4, 16/4)); // 8 bytes salt: 0x0123456789ABCDEF
var ciphertextWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(16/4, encryptedWA.words.length)); // ciphertext
// 2. Determine key and IV using PBKDF2
var password = 'mypassword'
var keyIvWA = CryptoJS.PBKDF2(
password,
saltWA,
{
keySize: (32+16)/4, // key and IV
iterations: 10000,
hasher: CryptoJS.algo.SHA256
}
);
var keyWA = CryptoJS.lib.WordArray.create(keyIvWA.words.slice(0, 32/4));
var ivWA = CryptoJS.lib.WordArray.create(keyIvWA.words.slice(32/4, (32+16)/4));
// 3. Decrypt
var decryptedWA = CryptoJS.AES.decrypt(
{ciphertext: ciphertextWA},
keyWA,
{iv: ivWA}
);
var decrypted = decryptedWA.toString(CryptoJS.enc.Utf8)
console.log(decrypted)
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
More details can be found in the CryptoJS documentation.
try this lib in browser
https://www.npmjs.com/package/cryptojs2
More details can be found in the documentation.

Am I doing AES 256 encryption and decryption Node.js correctly?

I need to encrypt a chat message that will be stored a database. The data is a string of characters of various lengths. I want to use the native node.js crypto library and use a symmetric encryption protocol such as AES 256. I have concerns are the following:
Is CBC the correct AES mode for this use case for this type of field stored in a TEXT field in MySQL?
Does the key look like it is generated correctly?
Is the IV correct? Is prepending the IV to the encrypted text a proper way to do it or should it be a separate field?
// AES RFC - https://tools.ietf.org/html/rfc3602
const crypto = require('crypto');
const algorithm = 'aes-256-cbc';
// generate key with crypto.randomBytes(256/8).toString('hex')
const key = '6d858102402dbbeb0f9bb711e3d13a1229684792db4940db0d0e71c08ca602e1';
const IV_LENGTH = 16;
const encrypt = (text) => {
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipheriv(algorithm, Buffer.from(key, 'hex'), iv);
let encrypted = cipher.update(text);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return `${iv.toString('hex')}:${encrypted.toString('hex')}`;
};
const decrypt = (text) => {
const [iv, encryptedText] = text.split(':').map(part => Buffer.from(part, 'hex'));
const decipher = crypto.createDecipheriv(algorithm, Buffer.from(key, 'hex'), iv);
let decrypted = decipher.update(encryptedText);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
};
exports.encrypt = encrypt;
exports.decrypt = decrypt;
Is CBC the correct AES mode for this use case for this type of field stored in a TEXT field in MySQL?
Well, this depends a bit on your text. But probably yes.
Does the key look like it is generated correctly?
yeah, looks good to me. It should look random and it looks random. Not sure what your concern is here.
Is the IV correct? Is prepending the IV to the encrypted text a proper way to do it or should it be a separate field?
The IV looks good to me. I don't see many reasons why you shouldn't do it this way except one: its not very storage efficient. It would be far more efficient to store the data not as hex string but as binary data! And then you can't just use a colon to seperate the data. So either you know that its the first n bytes or you do a seperate field. Both has upsides and downsides, but both is ok. It's primary a question about style.

CryptoJS AES Unable to decrypt

I need to decrypt incoming requests encrypted with AES, I try to use shared example and unable to find right set of parameters
Encryption:AES/CBC/PKCS5Padding AES/CBC/PKCS5
Initialization vector: emptybyte array which length is 16
Test secret key:1234567890123456
Plain text: abcdefghigklmnopqrstuvwxyz0123456789
Encrypted: 8Z3dZzqn05FmiuBLowExK0CAbs4TY2GorC2dDPVlsn/tP+VuJGePqIMv1uSaVErr
I use next
const cryptkey = '1234567890123456';
const cleardata = "abcdefghigklmnopqrstuvwxyz0123456789";
const crypted = "8Z3dZzqn05FmiuBLowExK0CAbs4TY2GorC2dDPVlsn/tP+VuJGePqIMv1uSaVErr";
var decrypt = CryptoJS.AES.decrypt(crypted, cryptkey, {
iv: CryptoJS.enc.Hex.parse('0000000000000000'),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
var ddd = decrypt.toString(CryptoJS.enc.Utf8);
console.log(ddd);
Every time I am getting empty string.
Where do I fail?
------ UPDATE -----
New version with applied comments, still does not work
const cryptkey = CryptoJS.enc.Utf8.parse('1234567890123456');
const crypted = CryptoJS.enc.Base64.parse("8Z3dZzqn05FmiuBLowExK0CAbs4TY2GorC2dDPVlsn/tP+VuJGePqIMv1uSaVErr");
var decrypt = CryptoJS.AES.decrypt(crypted, cryptkey, {
iv: CryptoJS.enc.Hex.parse('00000000000000000000000000000000'),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
You would have to parse the UTF-8 key first:
const cryptkey = CryptoJS.enc.Utf8.parse('1234567890123456');
If you don't do that CryptoJS will assume it is a password and derive the actual key from that.
As Maarten also pointed out...
The ciphertext also must be decoded from Base64:
const crypted = CryptoJS.enc.Base64.parse("8Z3dZzqn05FmiuBLowExK0CAbs4TY2GorC2dDPVlsn/tP+VuJGePqIMv1uSaVErr");
Note that the decryptor expects a CipherParams object, which you can simulate by passing {ciphertext: crypted} to the decrypt function. Alternatively, you can rely on CryptoJS to decode the ciphertext from Base64, you pass in that string as-is.
The IV must be 16 bytes long for AES-CBC which are 32 characters if encoded as Hex:
CryptoJS.enc.Hex.parse('00000000000000000000000000000000')
Examples
const cryptkey = CryptoJS.enc.Utf8.parse('1234567890123456');
const crypted = CryptoJS.enc.Base64.parse("8Z3dZzqn05FmiuBLowExK0CAbs4TY2GorC2dDPVlsn/tP+VuJGePqIMv1uSaVErr");
var decrypt = CryptoJS.AES.decrypt({ciphertext: crypted}, cryptkey, {
iv: CryptoJS.enc.Hex.parse('00000000000000000000000000000000'),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
console.log(decrypt.toString(CryptoJS.enc.Utf8));
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/aes.js"></script>
As Matt correctly noted, CryptoJS can do the ciphertext decoding for you if the ciphertext is encoded as Base64:
const cryptkey = CryptoJS.enc.Utf8.parse('1234567890123456');
const crypted = "8Z3dZzqn05FmiuBLowExK0CAbs4TY2GorC2dDPVlsn/tP+VuJGePqIMv1uSaVErr";
var decrypt = CryptoJS.AES.decrypt(crypted, cryptkey, {
iv: CryptoJS.enc.Hex.parse('00000000000000000000000000000000'),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
console.log(decrypt.toString(CryptoJS.enc.Utf8));
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/aes.js"></script>
Security considerations:
The IV must be unpredictable (read: random). Don't use a static IV, because that makes the cipher deterministic and therefore not semantically secure. An attacker who observes ciphertexts can determine when the same message prefix was sent before. The IV is not secret, so you can send it along with the ciphertext. Usually, it is simply prepended to the ciphertext and sliced off before decryption.
A key should be randomly chosen from all possible bytes, because a key consisting of ASCII characters is much easier to brute-force than a key consisting of all available bytes.
It is better to authenticate your ciphertexts so that attacks like a padding oracle attack are not possible. This can be done with authenticated modes like GCM or EAX, or with an encrypt-then-MAC scheme.
If you're using only symmetric encryption you need the exact same key at the server and the client. If you send the encryption key from the server to the client or the other way around you need to encrypt your symmetric encryption key. The easiest way to do this would be to use TLS. If you use TLS, then the data as well as key are encrypted, so you don't need to encrypt it yourself. This doesn't provide any security, just a little bit of obfuscation. You should read: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/august/javascript-cryptography-considered-harmful/
You are forgetting to base 64 decode the ciphertext in crypted (I guess you'd have to use atob() to do that). Your IV is too small as well, hexadecimals take two hex digits per byte.
This work for me, using enc and not only UTF8
const { AES, enc } = require('crypto-js')
const { decrypt, encrypt } = AES
const message = "Hi my friend"
const messageEncrypt = "oPVu8Dd8ERPIAWr+7rQzIQ=="
const key= "key123456"
const aesDecrypt = decrypt(messageEncrypt,key).toString(enc.Utf8)
console.log(aesDecrypt) // Hi my friend

Categories

Resources