CryptoJS decrypt changes every time - javascript

I am using CryptoJS to manually decrypt a string with a provided set of values. The secret is provided and then an SHA256 has is taken of it. The message and initialization vector are base 64 encoded. Here's what I am trying, but every time I run it, the output changes - how can that be?! I'm at the end of my wits...
// Key and take the hash of it
var secretKey = 'TESTING123Secret_Key';
var secretKeyHash = CryptoJS.SHA256(secretKey).toString(CryptoJS.enc.Hex);
// Base 64 encoded values
var accountNumberBase64 = 'nxjYfo4Stw63YBEcnjo3oQ==';
var initializationVectorBase64 = 'HnNcvu9AP9yl09APWkWnDQ==';
// decode the values provided above
var accountNumberEncrypt = atob(accountNumberBase64);
var initializationVector = atob(initializationVectorBase64);
// Use crypto to decrypt
var decrypted = CryptoJS.AES.decrypt(
{
ciphertext: accountNumberEncrypt,
salt: ''
},
secretKeyHash,
{
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.NoPadding,
iv: initializationVector,
salt: ''
}
);
console.log(' decrypted, by hand: ' + decrypted.toString(CryptoJS.enc.Hex));
the last line changes every time this is run (run it on page load) - same values provided every time, output is different.
How it is supposed to work:
Decryption Instructions:
1. A static, secret key will be shared which will be used for decryption (Secret Key TBD).
a. HASH the secret key with SHA256, encode it to Hex and use the first 32 characters. This will be used as the KEY when decrypting.
2. Two pieces of information will be sent via the POST method
a. Parameter “AN”: A Base64 Encoded, AES-256-CBC Encrypted string which will represent the Account Number when decrypted
b. Parameter “IV”: A Base64 Encoded initialization vector (IV) string which will be used in decrypting the Account Number string
3. Base64 Decode both parameters
4. Using the AES-256-CBC method, decrypt the encrypted string (which was base64 decoded as part of Step #3) with the initialization vector decoded in Step #3 and the hash created in Step #1a
5. The decryption should then provide you the account number.
Java code

There many issues with your code. It is hard to say what is really responsible for the non-deterministic decryption. I guess it is the fact that you're passing the key as a string which means that CryptoJS will assume that it is a password and try to use EVP_BytesToKey to derive a key from that. Since the salt is not set, CryptoJS probably has a bug that it generates a random salt for decryption (which it should not). You need to parse the key into a WordArray if you want to manually provide the key.
The other main issue is using non-CryptoJS methods for decoding (atob) which means that you get some data format that cannot be directly read by CryptoJS. CryptoJS relies on the internal WordArray for representing all binary data or expects all strings to be UTF-8-encoded.
Working code:
// Key and take the hash of it
var secretKey = 'TESTING123Secret_Key';
var secretKeyHash = CryptoJS.SHA256(secretKey).toString(CryptoJS.enc.Hex).slice(0,32);
secretKeyHash = CryptoJS.enc.Utf8.parse(secretKeyHash);
// Base 64 encoded values
var accountNumberBase64 = 'nxjYfo4Stw63YBEcnjo3oQ==';
var initializationVectorBase64 = 'HnNcvu9AP9yl09APWkWnDQ==';
var ct = CryptoJS.enc.Base64.parse(accountNumberBase64);
var iv = CryptoJS.enc.Base64.parse(initializationVectorBase64);
// Use crypto to decrypt
var decrypted = CryptoJS.AES.decrypt({
ciphertext: ct
},
secretKeyHash, {
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.NoPadding,
iv: iv
}
);
console.log(' decrypted, by hand: ' + decrypted.toString(CryptoJS.enc.Utf8));
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/aes.js"></script>
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/sha256.js"></script>
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/components/pad-nopadding-min.js"></script>

Related

Decryption of TripesDES algorithm in CryptoJS returns nothing

I tried encrypting a data using TripleDES in CryptoJS library. I created an encryption example using this tool https://www.devglan.com/online-tools/triple-des-encrypt-decrypt and generated a string encryption data of NbU4PoYHR9IJtSLmHRubpg==
Now I want to decrypt this NbU4PoYHR9IJtSLmHRubpg== generated from the site using javascript code with CryptoJS library.
I have generated also a SharedKey with this string 36fd14ddcd755bb37879cbe99ca26c92
Here is my code:
export const decryptData = (params) => {
const {ClientSecret, ApiKey} = params;
const SharedKey = `${ClientSecret}:${ApiKey}`;
const data = 'NbU4PoYHR9IJtSLmHRubpg==';
let CryptoSharedKey = CryptoJS.MD5(SharedKey).toString();
const ct = CryptoJS.enc.Base64.parse(data);
console.log('decrypt',CryptoJS.TripleDES.decrypt(ct, CryptoSharedKey).toString(CryptoJS.enc.Utf8))
}
now the problem is when i console log the result, it gives me nothing but an empty string.
The web tool simply UTF-8 encodes the key, so in the CryptoJS code the key derivation via MD5 must be removed.
Also, the web tool automatically truncates too long keys to 24 bytes, the key length used by TripleDES in the 3TDEA variant. Since the key you apply is 32 bytes in size, it is truncated accordingly. CryptoJS also shortens keys that are too long implicitly (though it is more transparent to shorten them explicitly).
Furthermore, in the CryptoJS code, the key material must be passed as WordArray to be interpreted as key (if passed as string, it will be interpreted as password and a key derivation function will be applied). For conversion to a WordArray the key has to be parsed with the Utf8 encoder.
In addition, the ciphertext must be passed as CipherParams object or Base64 encoded (the latter is implicitly converted to a CipherParams object).
Moreover, since the ECB mode was used for encryption in the web tool, this mode must also be applied in the CryptoJS code. For this, ECB must be explicitly set, since CBC is the CryptoJS default.
Hence, the code is to be changed as follows:
var key = CryptoJS.enc.Utf8.parse('36fd14ddcd755bb37879cbe99ca26c92'.substr(0, 24)); // Explicitly shorten the key to 24 bytes; parse the key into a WordArray
var data = 'NbU4PoYHR9IJtSLmHRubpg==';
var decrypted = CryptoJS.TripleDES.decrypt(data, key, {mode: CryptoJS.mode.ECB}); // Pass data Base64 encoded; apply ECB mode (default is CBC)
console.log('decrypt: ', decrypted.toString(CryptoJS.enc.Utf8));
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
Note that TripleDES is outdated and that ECB is insecure.

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!

What is the Python equivalent of this JS function to decrypt an AES-CCM-encrypted string?

I’d like to decrypt an AES-encrypted string (CCM mode) in Python 3.
The following JavaScript code which is using the sjcl library is working correctly:
const sjcl = require('sjcl');
const key = "ef530e1d82c154170296467bfe40cdb47b9ad77e685bbf8336b145dfa0e85640";
const keyArray = sjcl.codec.hex.toBits(key);
const iv = sjcl.codec.base64.fromBits(sjcl.codec.hex.toBits(key.substr(0,16)));
const params = {
"iv": iv,
"v": 1,
"iter": 1000,
"ks": 256,
"ts": 128,
"mode": "ccm",
"adata": "",
"cipher": "aes",
"salt": "",
};
function encrypt(data) {
const ct = JSON.parse(sjcl.encrypt(keyArray, data, params)).ct;
return sjcl.codec.hex.fromBits(sjcl.codec.base64.toBits(ct));
}
function decrypt(data) {
const ct = sjcl.codec.base64.fromBits(sjcl.codec.hex.toBits(data));
const paramsWithCt = JSON.stringify({ ...params, ...{ "ct": ct } });
return sjcl.decrypt(keyArray, paramsWithCt);
}
let ct = encrypt("my secret string");
console.log("Cipher Text: " + ct);
let plain = decrypt(ct);
console.log("Plain Text: " + plain);
Output:
$ npm i sjcl
$ node index.js
Cipher Text: fa90bcdedbfe7ba89b69216e352a90fa57a63871fc4da7e69ab7f897f427f8e3
Plain Text: my secret string
Which library can I use to do the same in Python?
I tried using the pycryptodome library, but it accepts a different set of parameters:
key (bytes) – the cryptographic key
mode – the constant Crypto.Cipher.<algorithm>.MODE_CCM
nonce (bytes) – the value of the fixed nonce. It must be unique for the combination message/key. For AES, its length varies from 7 to 13 bytes. The longer the nonce, the smaller the allowed message size (with a nonce of 13 bytes, the message cannot exceed 64KB). If not present, the library creates a 11 bytes random nonce (the maximum message size is 8GB).
mac_len (integer) – the desired length of the MAC tag (default if not present: 16 bytes).
msg_len (integer) – pre-declaration of the length of the message to encipher. If not specified, encrypt() and decrypt() can only be called once.
assoc_len (integer) – pre-declaration of the length of the associated data. If not specified, some extra buffering will take place internally.
The sjcl operates on arrays of 4 byte words. With sjcl.codec.hex.toBits() the hex encoded key is converted into such an array. The first 8 bytes (16 hexdigits) of the key are used as nonce.
Key size, tag size, algorithm and mode are determined from the params object. The params object further contains parameters for the key derivation, e.g. iter, salt, etc.), but these are ignored here since the key is passed as an array and not as a string.
Nonce and ciphertext are passed Base64 encoded within the params object.
The ciphertext is the concatenation of the actual ciphertext and the tag in this order, which must also be passed to the decryption in this format.
While the sjcl processes ciphertext and tag concatenated, PyCryptodome handles both separately. Apart from that, encryption and decryption in Python is straightforward with PyCryptodome:
from Crypto.Cipher import AES
data = b'my secret string'
key = bytes.fromhex('ef530e1d82c154170296467bfe40cdb47b9ad77e685bbf8336b145dfa0e85640')
nonce = bytes.fromhex('ef530e1d82c154170296467bfe40cdb47b9ad77e685bbf8336b145dfa0e85640')[:8]
# Encryption
cipher = AES.new(key, AES.MODE_CCM, nonce)
ciphertext, tag = cipher.encrypt_and_digest(data)
ciphertextTagHex = ciphertext.hex() + tag.hex()
print(ciphertextTagHex) # fa90bcdedbfe7ba89b69216e352a90fa57a63871fc4da7e69ab7f897f427f8e3
# Decryption
ciphertextTag = bytes.fromhex(ciphertextTagHex)
ciphertext = ciphertextTag[:-16]
tag = ciphertextTag[-16:]
cipher = AES.new(key, AES.MODE_CCM, nonce)
try:
decrypted = cipher.decrypt_and_verify(ciphertext, tag)
print(decrypted.decode('utf-8')) # my secret string
except ValueError:
print('Decryption failed')
Note that it is insecure to derive the nonce from the key. This is especially true for CCM, s. e.g. RFC4309, p. 3, last section:
AES CCM employs counter mode for encryption. As with any stream
cipher, reuse of the same IV value with the same key is catastrophic.
Instead, the nonce should be randomly generated for each encryption. The nonce is not secret and is usually concatenated with the ciphertext at byte level, typically nonce|ciphertext|tag.

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.

Categories

Resources