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.
Related
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.
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!
I am trying to pass an AES encrypted string from a python script into a nodejs script, using ECB mode. The code used is:
To start, I use pycryptodome to encrypt a string into AES
from Crypto.Cipher import AES
key = b'ipu9TUv54yv]isFMh5#;t.5w34E2Ry#{'
cipher = AES.new(key, AES.MODE_ECB)
print(cipher.encrypt(b"foobar "))
This gives me the string \xb0\x07\x93\xf3\x02\xd0\x87\xa4\xaek\x1bS\xccg\xa4H.
However, when i try to reverse the effect with Javascript:
var crypto = require('crypto')
let key = Buffer.from('ipu9TUv54yv]isFMh5#;t.5w34E2Ry#{');
let decipher = crypto.createDecipheriv("aes-256-ecb", key, '');
let result = decipher.update(Buffer.from('\xb0\x07\x93\xf3\x02\xd0\x87\xa4\xaek\x1bS\xccg\xa4H'));
console.log(result.toString())
It gives me a completely different result from the original text: �k��gR�O
Is there something that I am missing that is changing the way that it decrypts?
There are two core issues:
On the node side, you're treating the output of Python as if it's a UTF-8 string. Node will treat it as a UTF-8 string, and the resulting bytes that make up the Buffer are going to be wrong. Dump it out, you'll see it's a 25 byte buffer, not what you intended.
Once you fix that, you'll find the second issue. The crypto library expects padding bytes, even if the only block is exactly the block size. To fix this, always add padding to the plaintext.
So, the encrypt changes to this:
from Crypto.Cipher import AES
key = b'ipu9TUv54yv]isFMh5#;t.5w34E2Ry#{'
cipher = AES.new(key, AES.MODE_ECB)
# Don't need to ensure the plain text is exactly block-size anymore
data = b'foobar'
# Pad it, regardless of it's size
length = 16 - (len(data) % 16)
data += bytes([length]) * length
# And encode the encrypted text. Using hex here, it's easy, though
# often base64 is used
print(cipher.encrypt(data).hex())
And decoding in Node:
var crypto = require('crypto')
let key = Buffer.from('ipu9TUv54yv]isFMh5#;t.5w34E2Ry#{');
let decipher = crypto.createDecipheriv("aes-256-ecb", key, '');
// Using the hex encoding, let Buffer decode it
let result = decipher.update(Buffer.from('bf8242c6046ad5cb47e733dca4d487f1', 'hex'));
// Make sure to give decipher a chance to operate on the final block
result += decipher.final();
console.log(result.toString())
This outputs foobar as expected.
We are using the SJCL (Stanford Javascript Crypto Library) to encrypt in JavaScript and we are supposed to implement decryption in Java.
The encryption code looks like this:
<script src='https://cdnjs.cloudflare.com/ajax/libs/sjcl/1.0.7/sjcl.js'></script>
<script>
var keyString = '2d73c1dd2f6a3c981afc7c0d49d7b58f';
var key = sjcl.codec.base64.toBits(keyString);
var cipher = new sjcl.cipher.aes(key)
var data = sjcl.codec.utf8String.toBits('Hello Crypto!');
var salt = sjcl.codec.base64url.toBits('kLME6vN-WdU_W9XVN9a1Z3E_p8HQ5C7X1La-3ZjEml1ytVRMfvtEXzeapbce2LjFI1dHEGtWv9bZ_U6K2CG1-K4lQPunFXWxXmsTQIXlGfwmpveg2AFeLFiqGmALnfbP');
var encrypted = sjcl.encrypt(keyString,'Hello Crypto!',{mode:'gcm',salt:salt});
console.log(encrypted)
</script>
The resulting encryption looks like the following:
{
"iv":"+xmg3yZF/LSWNFpXf03wUw==",
"v":1,
"iter":10000,
"ks":128,
"ts":64,
"mode":"gcm",
"adata":"",
"cipher":"aes",
"salt":"kLME6vN+WdU/W9XVN9a1Z3E/p8HQ5C7X1La+3ZjEml1ytVRMfvtEXzeapbce2LjFI1dHEGtWv9bZ/U6K2CG1+K4lQPunFXWxXmsTQIXlGfwmpveg2AFeLFiqGmALnfbP",
"ct":"Nq+9tXfc0zs0/m3OfDp0MmTXc9qD"
}
We were supplied with sample Java code to decrypt the code but this code assumes that IV and salt is the same thing so it won't work with the JSON that we are getting from the JS lib (having IV and salt as two separate values):
final byte[] symKeyData = DatatypeConverter.parseHexBinary(keyHex);
final byte[] ivData = ivSalt.getBytes(Charset.forName("UTF-8"));
final byte[] encryptedMessage = DatatypeConverter.parseBase64Binary(encryptedMessageString);
final Cipher cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding", "BC");
final SecretKeySpec symKey = new SecretKeySpec(symKeyData, "AES");
final IvParameterSpec iv = new IvParameterSpec(ivData);
cipher.init(Cipher.DECRYPT_MODE, symKey, iv);
final byte[] encodedMessage = cipher.doFinal(encryptedMessage);
final String message = new String(encodedMessage, Charset.forName("UTF-8"));
Can anybody explain how the resulting JSON from the JavaScript library can be decrypted correctly in Java?
SJCL convenience libs use PBKDF2 with 1000 iterations to derive the key. The documentation and code can be found here.
However, if this is an assignment:
the IV is the wrong size, it should be 12 bytes, not 16;
the salt size is way too large;
the cipher variable in the JavaScript is entirely ignored;
the iteration count is left at the default, which is normally considered too low.
So I don't know who wrote it, but they should not be releasing such code.
The salt is used for PBKDF2, the IV is for the symmetric cipher, so they are not the same.
I want to decrypt AES by given cipher and key with the Stanford Javascript Crypto Library (SJCL), but i can't pass the key:
var key = 'key';
var cipher = 'abjslö';
var aes = new sjcl.cipher.aes(key);
var plaintext = aes.decrypt(cipher);
alert(plaintext);
This dosen't work. Referred to the documentation, the key has to be "an array of 4, 6 or 8 words".
How could this be done?
The key has to be an AES key, which is 128, 192 or 256 bits. The SJCL library however operates on 32 bit machine "words". Check out the source of the open source library or one of the tests to find out what to pass. Note that a password is not a key, you need a password based key derivation function such as PBKDF2 to convert a password into a key.
Encryption
Create an array of random words which will
serve as our IV(initialisation vector).
Then you need to create a bit array using a random key(size depends upon level
of encryption and the type)
Then you create a cypher using the key array.
And finally encode your data using the cypher and the IV. (you can
also add meta data to check the authenticity)
Now just concat the IV and the encrypted bit array and finally
convert it to a base64 string and pass.
Please note that you cannot decrypt a AES-GCM without IV.
Decryption
While decrypting slice the IV from the encryted string.
Now using the key create a cypher and use that to decrypt the string.
You can find the complete code here.