I have an application that encrypts and decrypts a field in C# using Rfc2898DeriveBytes
. I have been trying to work out a cross platform solution using CryptoJS PBKDF2 to write a decrypt method in JavaScript. However, I have not been able to figure out the following 2 issues:
- I can't get the keys to match
- I can't figure out what the IV should be in JavaScript.
Key generation
Generating the key in C# (unfortunately I don't have much control over this as it supports a lot of legacy systems)
private readonly RijndaelManaged _alg = new RijndaelManaged();
public EncryptionManager()
{
var secret = 'D2s1d_5$_t0t3||y_4c3$0m3!1!1!!';
var salt = 'o6805542kcM7c5';
var saltBytes = Encoding.ASCII.GetBytes(salt);
using (var keyDeriver = new Rfc2898DeriveBytes(secret, saltBytes))
{
_alg.Key = keyDeriver.GetBytes(_alg.KeySize / 8); // _alg.KeySize = 256
}
}
The JS code I have for the key generation is:
const secret = CryptoJS.enc.Utf8.parse('D2s1d_5$_t0t3||y_4c3$0m3!1!1!!');
// Encoding the Salt in from UTF8 to byte array
const salt = CryptoJS.enc.Utf8.parse('o6805542kcM7c5');
// Creating the key in PBKDF2 format to be used during the decryption
const key = CryptoJS.PBKDF2(secret.toString(CryptoJS.enc.Utf8), salt, {
keySize: 128 / 32,
iterations: 1000,
});
This should ideally work according to my research but the keys generated in both the codes are never the same. I have burnt a lot of midnight oil scratching my head over what I am doing wrong but I don't see why.
Decryption
The decryption method in C# is as follows:
public string Decrypt(string ciphertext)
{
var cipherTextBytes = Convert.FromBase64String(ciphertext);
var ivSize = BitConverter.ToInt32(cipherTextBytes, 0);
var iv = new byte[ivSize];
var offset = sizeof(int);
Array.Copy(cipherTextBytes, offset, iv, 0, ivSize);
offset += ivSize;
using (var msDecrypt = new MemoryStream(cipherTextBytes, offset, cipherTextBytes.Length - offset))
{
lock (_syncLock)
{
using (var decryptor = _alg.CreateDecryptor(_alg.Key, iv))
using (var decryptStream = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
using (var reader = new StreamReader(decryptStream))
{
return reader.ReadToEnd();
}
}
}
}
My solution for the decryption in JS is:
const decrypt = (encryptedData: string): string => {
// Enclosing the test to be decrypted in a CipherParams object as supported by the CryptoJS libarary
const cipherParams = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Base64.parse(encryptedData),
});
// What should be the IV be here?
const iv = CryptoJS.enc.Hex.parse(encryptedData);
// Decrypting the string contained in cipherParams using the PBKDF2 key
const decrypted = CryptoJS.AES.decrypt(cipherParams, key, {
mode: CryptoJS.mode.CBC,
// iv,
padding: CryptoJS.pad.Pkcs7,
});
decryptedText = decrypted.toString(CryptoJS.enc.Utf8);
return decryptedText;
}
I am assuming if I could get the correct key and IV, I could solve this. I have looked around a lot for compatible solutions but I'm at a loss as to what I am doing wrong.
Any assistance would be greatly appreciated.
As already mentioned in my comment, the C# code returns the following 32 bytes key with the posted salt and passphrase (hex encoded):
The CryptoJS code generates only a 16 bytes key. If the key size is adjusted with
keySize: 256 / 32
, the CryptoJS code returns the same 32 bytes key.The following Base64 encoded ciphertext can be decrypted with the C# code using the posted passphrase and salt:
If the ciphertext is hex encoded, the result is:
The first 4 bytes contain the information about the IV length. Since the length of the IV is known (16 bytes for AES), it would not really be necessary to store this information, as already mentioned in Robert's comment. The following 16 bytes correspond to the IV, the remaining bytes to the actual ciphertext. These data have to be separated. After that the decryption can be done: