Home > Software design >  How to decrypt AES with crypto-js from C#
How to decrypt AES with crypto-js from C#

Time:11-20

I am trying to decrypt a value that is encrypted with AES in backend with C#. The decryption part will happen in the front end with Angular (using crypto-js ) The problem that I am having is that I'm always getting an empty string as the result of the decryption. I don't know what am I doing wrong. Am I missing some sort of configuration?

My C# code to Encrypt looks like this:

//
EncryptAES("XEMFkT92UtR1VJI8kU8XQJALk98GGEFM", "random text to encrypt");

       public static string EncryptAES(string passPhrase, string plainText)
        {
            byte[] iv = Generate256BitsOfRandomEntropy();
            byte[] temp;
            byte[] array;

            using (Aes aes = Aes.Create())
            {
                byte[] salt = Generate256BitsOfRandomEntropy();

                Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(passPhrase, salt, 100);

                aes.Key = pdb.GetBytes(32);
                aes.KeySize = 256;
                aes.Padding = PaddingMode.PKCS7;
                aes.Mode = CipherMode.CBC;
                aes.IV = iv;

                ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);

                using (MemoryStream memoryStream = new MemoryStream())
                {
                    using (CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, encryptor, CryptoStreamMode.Write))
                    {
                        using (StreamWriter streamWriter = new StreamWriter((Stream)cryptoStream, Encoding.UTF8))
                        {
                            streamWriter.Write(plainText);
                        }


                        temp = memoryStream.ToArray();
                        array = salt.Concat(iv).Concat(temp).ToArray();
                        
                        cryptoStream.Flush();
                        encryptor.Dispose();
                    }
                }
            }

            return Convert.ToBase64String(array);
        }

//Random byte[] generator
 private static byte[] Generate256BitsOfRandomEntropy()
        {
            var randomBytes = new byte[16];
            using (var rngCsp = new RNGCryptoServiceProvider())
            {
                rngCsp.GetBytes(randomBytes);
            }
            return randomBytes;
        }

The decryption part in the.ts file is:


//The param "key" will be same as the C# code: XEMFkT92UtR1VJI8kU8XQJALk98GGEFM
//The param "toDecrypt" will the the Base64 returned by the service in C#

 decryptAES(key: string, toDecrypt: string) {

        var data = Buffer.from(toDecrypt, 'base64');
        var salt = data.slice(0, 16); //first 16 bytes to get the salt
        var iv = data.slice(16, 32);// next 16 bytes to get the IV

        const wordArrayIV = CryptoJS.lib.WordArray.create(Array.from(iv));
        const wordArraySalt = CryptoJS.lib.WordArray.create(Array.from(salt))

        var keyPBKDF2 = CryptoJS.PBKDF2(key, wordArraySalt, {
            keySize: 256 / 32,
            iterations: 100
        });

        var decrypted = CryptoJS.AES.decrypt(toDecrypt, keyPBKDF2, 
            {   
                mode: CryptoJS.mode.CBC, 
                padding: CryptoJS.pad.Pkcs7, 
                iv: wordArrayIV
            });

//Return empty string 
        return decrypted.toString();
    }

CodePudding user response:

In the C# code, the key derived with PBKDF2 is not used, but a randomly generated key. This is because when the key size is set, a new key is implicitly generated.
As fix simply remove the setting of the key size, i.e. the line aes.KeySize = 256 (the key size is implicitly set when the key is set).

...
aes.Key = pdb.GetBytes(32);
//aes.KeySize = 256;                // Fix: remove
//aes.Padding = PaddingMode.PKCS7;  // default
//aes.Mode = CipherMode.CBC;        // default
aes.IV = iv;
...

In addition, there are several issues in the CryptoJS code: First, the Buffers are incorrectly converted to WordArrays, so that IV and salt are wrong.
Also, the ciphertext is not taken into account when separating and is furthermore passed incorrectly to AES.decrypt().
And the decrypted data is hex encoded, but should be UTF-8 decoded.

function decryptAES(key, toDecrypt) {
    var data = CryptoJS.enc.Base64.parse(toDecrypt);
    var wordArraySalt = CryptoJS.lib.WordArray.create(data.words.slice(0, 4)); // Fix: Array -> WordArray conversion 
    var wordArrayIV = CryptoJS.lib.WordArray.create(data.words.slice(4, 8)); // Fix: Array -> WordArray conversion
    var wordArrayCt = CryptoJS.lib.WordArray.create(data.words.slice(8)); // Fix: Consider ciphertext
    var keyPBKDF2 = CryptoJS.PBKDF2(key, wordArraySalt, {keySize: 256 / 32, iterations: 100});
    var decrypted = CryptoJS.AES.decrypt({ciphertext: wordArrayCt}, keyPBKDF2, {iv: wordArrayIV}); // Fix: Pass ciphertext as CipherParams object
    return decrypted.toString(CryptoJS.enc.Utf8); // Fix: UTF-8 decode
}

var decrypted = decryptAES('XEMFkT92UtR1VJI8kU8XQJALk98GGEFM', '4YI4unJecVXvvNQVgBsdUwrr7rlwcImDb7t1LT88UO0w8BdFpOp5PLsu6PRJ eCeKB01rWdVVrGMLj7tOi3KHg==');
console.log(decrypted);
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>

Note that the ciphertext in above code was generated with the fixed C# code.


Regarding vulnerabilities: An iteration count of 100 in key derivation with PBKDF2 is generally too small.

  • Related