I want to encrypt in c# exactly like in Node.Js which this method in Node.js does:
let encrypted = CryptoJS.AES.encrypt(JSON.stringify(text), passPhrase).toString()
So I could encrypt in c# and be then decrypt it in Node.Js with this method:
var decrypted = CryptoJS.AES.decrypt(encrypted, passPhrase);
I wrote this method to create key and IV from passPhrase in c#:
public static byte[] DeriveKeyAndIVFromPassword(string password, int derivedBytesLength)
{
using (Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(password, derivedBytesLength))
{
return rfc2898.GetBytes(derivedBytesLength);
}
}
and the main method to encrypt this:
public static string EncryptAES(string plainText, string passphrase)
{
try
{
using (AesCryptoServiceProvider aesAlg = new AesCryptoServiceProvider())
{
aesAlg.Mode = CipherMode.CBC; // Use CBC mode
aesAlg.Padding = PaddingMode.PKCS7; // Use PKCS7 padding
aesAlg.KeySize = 256; // Use AES-256
byte[] derivedKeyAndIV = DeriveKeyAndIVFromPassword(passphrase, aesAlg.KeySize / 8 + aesAlg.BlockSize / 8);
byte[] key = derivedKeyAndIV.Take(aesAlg.KeySize / 8).ToArray();
byte[] iv = derivedKeyAndIV.Skip(aesAlg.KeySize / 8).Take(aesAlg.BlockSize / 8).ToArray();
aesAlg.Key = key;
using (var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, iv))
{
using (MemoryStream msEncrypt = new MemoryStream())
{
msEncrypt.Write(iv, 0, iv.Length);
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (var swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(plainText);
}
}
return Convert.ToBase64String(msEncrypt.ToArray());
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message);
return null;
}
}
Based on this answer, I set Mode to CBS, Padding to PaddingMode.PKCS7 and KeySize to 256, which in main document it says:
CryptoJS supports AES-128, AES-192, and AES-256. It will pick the variant by the size of the key you pass in. If you use a passphrase, then it will generate a 256-bit key.
and the code not working, the encryption text which finally is made by c# code cannot be decrypted by Node.Js.
So anyone has any idea what I did wrong?
The reason for the problem is essentially that both codes use two different key derivation functions.
In
CryptoJS.AES.encrypt()
, when the key material is passed as a string (as opposed to aWordArray
), the key material is interpreted as a passphrase, a random 8 bytes salt is generated, and the key and IV are derived from both using a key derivation function, namely the OpenSSL proprietaryEVP_BytesToKey()
.When using
toString()
, the result is returned in OpenSSL format, which corresponds to the Base64 encoding of the concatenation of the ASCII encoding ofSalted__
, the 8 bytes salt and the actual ciphertext.However, in the C# code a different key derivation function is used, namely PBKDF2 (which is implemented by
Rfc2898DeriveBytes
) and the OpenSSL format is not applied!For the fix, an implementation of
EVP_BytesToKey()
is required. There are several on the web. I would recommend BouncyCastle, as this library is comparatively trustworthy:In addition, the result must be provided in OpenSSL format:
All together:
Test:
returns as a possible output:
Keep in mind that because of the random salt and thus the different key and IV, each encryption gives a different result.
The ciphertext generated with the C# code can be decrypted with CryptoJS as the following CryptoJS code shows:
Note that
EVP_BytesToKey()
is nowadays deemed as a vulnerability and is therefore deprecated. Instead, Argon2 or at least PBKDF2 should be used as KDF.Also, CryptoJS is discontinued and no longer maintained (latest version is 4.2.0).