Encrypt AES-GCM in JavaScript, decrypt in Java - javascript

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.

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!

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 decrypt changes every time

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>

Different AES implementations don't agree

I have to encrypt a piece of data using both C#, and Javascript. I'm using Bouncy Castle in C# and Crypto-JS in Javascript.
The problem I am facing is that even though all the parameters are equal (OFB with no padding, and the IV is always 0), I don't get the same output with both libraries. The consequence of that is also that I can't decrypt with one what was encrypted with the other.
Here is my C# code for encrypting:
byte[] iv = new byte[16];
BufferedBlockCipher aes = new BufferedBlockCipher(new OfbBlockCipher(new AesEngine(), 16));
ParametersWithIV ivAndKey = new ParametersWithIV(new KeyParameter(stretchedKey), iv);
aes.Init(true, ivAndKey);
int minSize = aes.GetOutputSize(privateKey.Length);
byte[] outBuf = new byte[minSize];
int length1 = aes.ProcessBytes(privateKey, 0, privateKey.Length, outBuf, 0);
int length2 = aes.DoFinal(outBuf, length1);
byte[] encryptedKey = iv.Concat(outBuf.Take(length1 + length2)).ToArray();
My Javascript code is the following for encrypting (try it on JSFiddle here: http://jsfiddle.net/gCHAG/424/):
var key = Crypto.util.hexToBytes('59b50e345cab8b6d421b161918ea3fbd7e5921eea7d43d1ac54fa92cca452bb5');
var iv = Crypto.util.hexToBytes('00000000000000000000000000000000');
var message = Crypto.util.hexToBytes('3b16601d0a7e283c1f24d30ec214676885096cb0bbf3998012a2be87c5a58d89');
var encrypted = Crypto.AES.encrypt(message, key, { iv: iv, asBytes: true, mode: new Crypto.mode.OFB(Crypto.pad.NoPadding) });
I get the following from the bouncy castle implementation: 578934dbb576dc986a531f09e8d5abd5b01dc1bfd3ededd222ff8aa6e4bfdbf2
And the following from Crypto-JS: 578946591ce2d787cbe41bec77a58dac66e6007fb722b1af847ecc3bf4212cea
Note how the first two bytes are the same, but then everything else is different.
To top it all up, when trying on an online tool, I get a third output (see http://aes.online-domain-tools.com/link/bd243g1VXbD7LUAS/): 57804D64A8...
I went through everything several times, but I don't see why I get different outputs.
CryptoJS seems to use an output of 128 bits per block for the key stream. You specify 16 bits per block for Bouncy. As 8 or 128 bits per block are common for OFB, and since 128 is the recommended output size, I guess you are just confusing bits and bytes in the Bouncy code.
If you specify new OfbBlockCipher(new AesEngine(), 128) you should be OK.

Categories

Resources