2

The data which i try to decrypt in C# is encrypted using AES-256 alorithm in Nodejs and the code is below.

const crypto = require('crypto');
const validator = require('validator');
const algorithm = 'aes256';
const inputEncoding = 'utf8';
const outputEncoding = 'hex';
const iv = crypto.randomBytes(16)

function encrypt(key,text) {
key = processKey(key);
let cipher = crypto.createCipheriv(algorithm, key, iv);
let ciphered = cipher.update(text, inputEncoding, outputEncoding);
ciphered += cipher.final(outputEncoding);
return ciphered;
}

Now i'm provided with a encrypted data of length 32 like "1234567304e07a5d2e93fbeefd0e417e" and key of length 32 like "123456673959499f9d37623168b2c977".

I'm trying to decrypt the same using the below c# code and getting a error as 'Length of the data to decrypt is invalid". kindly advice.

public static string Decrypt(string combinedString, string keyString)
{
    string plainText;
    byte[] combinedData = StringToByteArray(combinedString);
    Aes aes = Aes.Create();
    aes.Key = Encoding.UTF8.GetBytes(keyString);
    byte[] iv = new byte[aes.BlockSize / 8];
    byte[] cipherText = new byte[combinedData.Length - iv.Length];
    Array.Copy(combinedData, iv, iv.Length);
    Array.Copy(combinedData, iv.Length, cipherText, 0, cipherText.Length);
    aes.IV = iv;
    aes.Mode = CipherMode.CBC;
    ICryptoTransform decipher = aes.CreateDecryptor(aes.Key, aes.IV);

    using (MemoryStream ms = new MemoryStream(cipherText))
    {
        using (CryptoStream cs = new CryptoStream(ms, decipher, CryptoStreamMode.Read))
        {
            using (StreamReader sr = new StreamReader(cs))
            {
                plainText = sr.ReadToEnd();                 
            }
        }

        return plainText;
    }
}
public static byte[] StringToByteArray(string hex) {
return Enumerable.Range(0, hex.Length)
                 .Where(x => x % 2 == 0)
                 .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
                 .ToArray();
}

Below is the Decryption code in Node.js which works fine

const crypto = require('../functions/crypto');
const assert = require('assert');
const { v4: uuidv4 } = require('uuid');
describe('crypto module', function() {
it('should work', function(done) {
    const toHash = 'Octomate';
    const hashKey = uuidv4();

    const hash = crypto.encrypt(hashKey, toHash);
    const decrypted = crypto.decrypt(hashKey, hash);

    assert.strictEqual(toHash, decrypted);
    done();
});
});
Mini-Con
  • 397
  • 1
  • 4
  • 20
  • please check here, the same error - https://stackoverflow.com/questions/22466858/decryption-exception-length-of-the-data-to-decrypt-is-invalid – Denisix Aug 13 '20 at 09:05
  • In the NodeJS code the `processKey` method is missing. It would also be helpful if a complete set of test data is posted (key, IV, plaintext and ciphertext). In any case, the NodeJS code lacks the concatenation of IV and ciphertext, which is assumed in the C# code. – Topaco Aug 13 '20 at 10:22
  • Furthermore, the ciphertext in the NodeJS code is hex encoded, while the C# code expects a Base64 encoded ciphertext. Also it's not clear to me from the NodeJS code why the ciphertext should start with `qwerty` as in your example. – Topaco Aug 13 '20 at 10:30
  • @Topaco On purpose I tampered the ciphertext with random letters, I tried converting hex string instead of base64 and now the output is empty. – Mini-Con Aug 13 '20 at 11:04
  • The concatenation of IV and ciphertext is still missing (see my 1st comment). In the case of a hex encoding this is simply: `let ciphered = iv.toString(outputEncoding); ciphered += cipher.update(text, inputEncoding, outputEncoding); ciphered += cipher.final(outputEncoding);` with `const outputEncoding = 'hex';`. In the case of a Base64 encoding (originally used in the C# code) it is instead: `let ciphered = Buffer.concat([iv, cipher.update(text, inputEncoding), cipher.final()]).toString(outputEncoding);` with `const outputEncoding = 'base64';`. – Topaco Aug 13 '20 at 14:03
  • ciphered += cipher.final(outputEncoding); does the concatenation. In fact the decrypt node.js code updated above works fine in server. – Mini-Con Aug 18 '20 at 02:49
  • @Topaco As you suggested, I tried let ciphered = iv.toString(outputEncoding) and getting the output after decryption as empty – Mini-Con Aug 25 '20 at 04:20
  • In my answer you will find a C# code that can be used to decrypt the ciphertext of the NodeJS code. By the way, `ciphered += cipher.final(outputEncoding);` completes the encryption, considers a padding, but does _not_ concatenate the IV. In the C# code posted by me there is no separation anymore, so that in the NodeJS code there is no need to concatenate. – Topaco Aug 25 '20 at 09:03

1 Answers1

5

The posted NodeJS performs an encryption with AES-256 in CBC mode. The plaintext is encoded with UTF8 and the ciphertext is hex encoded. Furthermore a random IV is generated and used for the encryption. Since the method processKey was not posted, it will not be considered for further considerations, so the following NodeJS code is used to derive a C# code for the decryption:

const crypto = require('crypto');
const validator = require('validator');
const algorithm = 'aes256';
const inputEncoding = 'utf8';
const outputEncoding = 'hex';
const iv = crypto.randomBytes(16)

function encrypt(key,text) {
    //key = processKey(key);    // not posted
    let cipher = crypto.createCipheriv(algorithm, key, iv);
    let ciphered = cipher.update(text, inputEncoding, outputEncoding);
    ciphered += cipher.final(outputEncoding);
    return ciphered;
}

const key = '123456673959499f9d37623168b2c977';
const text = 'The quick brown fox jumps over the lazy dog'
const encrypted = encrypt(key, text);

console.log("IV (hex):         " + iv.toString('hex'));
console.log("Ciphertext (hex): " + encrypted);

Using the posted key 123456673959499f9d37623168b2c977 the plaintext The quick brown fox jumps over the lazy dog produces the following output:

IV (hex):         850bd88afd08c4ea14e75276277644f0
Ciphertext (hex): 5167ac87ebc79d5240255ff687c6bc8981c8791c353367a2e238a10e0983bf16e230ccf0511096f60c224b99927b3364

Note that because of the random IV a different ciphertext is generated for each encryption.

The decryption in C# can be done as follows:

string ciphertext = "5167ac87ebc79d5240255ff687c6bc8981c8791c353367a2e238a10e0983bf16e230ccf0511096f60c224b99927b3364";
string key = "123456673959499f9d37623168b2c977";
string iv = "850bd88afd08c4ea14e75276277644f0";
string decryptedText = Decrypt(ciphertext, key, iv);
Console.WriteLine("Decrypted text: " + decryptedText);

with

public static string Decrypt(string ciphertextHex, string keyUtf8, string ivHex)
{
    byte[] ciphertext = StringToByteArray(ciphertextHex);
    byte[] iv = StringToByteArray(ivHex);

    string plaintext = "";
    using (Aes aes = Aes.Create())
    {
        aes.Key = Encoding.UTF8.GetBytes(keyUtf8);
        aes.IV = iv;
        aes.Mode = CipherMode.CBC;          // default
        aes.Padding = PaddingMode.PKCS7;    // default

        ICryptoTransform decipher = aes.CreateDecryptor(aes.Key, aes.IV);

        using (MemoryStream ms = new MemoryStream(ciphertext))
        {
            using (CryptoStream cs = new CryptoStream(ms, decipher, CryptoStreamMode.Read))
            {
                using (StreamReader sr = new StreamReader(cs, Encoding.UTF8)) // UTF8: default
                {
                    plaintext = sr.ReadToEnd();
                }
            }
        }
    }
    return plaintext;
}

and

// from https://stackoverflow.com/a/321404/9014097
public static byte[] StringToByteArray(string hex)
{
    return Enumerable.Range(0, hex.Length)
                     .Where(x => x % 2 == 0)
                     .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
                     .ToArray();
}

Please note the following:

  • The same IV must be used for decryption as for encryption. Since the size of the IV is known (identical to the blocksize) and the IV is not secret, it is usually placed in front of the ciphertext on byte level (unencrypted and without separator) and the result is Base64 encoded. This is sent to the receiver, who Base64 decodes and then separates the received data.

    This does not happen in the posted NodeJS code. In the C# code posted by you however exactly this concatenation is expected and therefore the separation of IV and ciphertext is performed. In my comment I have described the changes in the NodeJS code to concatenate IV and ciphertext so that the result can be decrypted with the C# code.

    Since apparently the NodeJS code is the leading code and it does not concatenate, the C# code posted in my answer does not concatenate either. In this case, however, the IV must be passed e.g. as a parameter, otherwise, as already mentioned, a decryption is not possible.

  • The posted example is unfortunately not very helpful, because you only post the key and the ciphertext (which is only 16 bytes long, by the way, because it is hex encoded), but not the randomly generated IV. So a decryption is not possible. Neither can the example be used for a comparison of the ciphertexts, because due to the random IV a different ciphertext is generated each time.

  • Since the NodeJS code uses AES-256, the key must be 32 bytes in size. Therefore the key is probably encoded with UTF8. From the values a hex encoding would also be possible, but this would only result in a 16 bytes key. Since the method processKey was not posted, a further processing of the key cannot be ruled out, which is then not considered here.

  • The posted NodeJS code for the decryption unfortunately doesn't help much either, because several methods are not defined (e.g. crypto.encrypt, crypto.decrypt) and at least I can't see any useful relation to the posted NodeJS code for the encryption.

Topaco
  • 40,594
  • 4
  • 35
  • 62