2

I am searching for C# Code to reproduce the following openssl command.

openssl enc -d -aes-256-cbc -in my_encrypted_file.csv.enc -out my_decrypted_file.csv -pass file:key.bin

Additional information:

  • The encrypted file in present as byte[]
  • The key.bin is a byte[] with length of 256 (the key is obtained by a more simple decryption of yet another file, which i managed to realize in C#).

I have been trying out various examples found by searching the web. The problem is, that all of these examples require an IV (initialization vector). Unfortunately, I don't have an IV and no one on the team knows what this is or how it could be defined. The openssl command does not seem to need one, so I am a bit confused about this.

Currently, the code, I am trying with, looks as follows:

public static string DecryptAesCbc(byte[] cipheredData, byte[] key)
{
    string decrypted;

    System.Security.Cryptography.Aes aes = System.Security.Cryptography.Aes.Create();
    aes.KeySize = 256;
    aes.Key = key;
    byte[] iv = new byte[aes.BlockSize / 8];
    aes.IV = iv;
    aes.Mode = CipherMode.CBC;

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

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

The code fails saying that my byte[256] key has the wrong length for this kind of algorithm.

Thanks for any help with this!

Cheers, Mike

MikeJ82
  • 153
  • 1
  • 12
  • 1
    You have a 256 _byte_ key? Surely you should be passing 2048 to [`KeySize`](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.symmetricalgorithm.keysize?view=netcore-3.1) then? You'd have to check if that's a legal key size using the `LegalKeySizes` property. I'm not sure if it is. – ProgrammingLlama Aug 24 '20 at 05:26
  • Thank you for that hint, John. It almost certainly is a 256 bit (and not byte) key. I will have to examine the first step of my algorithm (deciphering the key) again.However, if you could give me some background on the problem with the IV, that would be very helpful, too. – MikeJ82 Aug 24 '20 at 06:07
  • I'm not 100% sure really. I think if you don't set it to anything that it's 0s. IV is basically like a salt with password hashing. It ensures that encrypting the same thing twice doesn't yield the exact same result. If you use it you would need to include that alongside the encrypted data. If you don't then I think 0s should work. I've never used it without an IV though so I could be wrong on that. – ProgrammingLlama Aug 24 '20 at 06:11
  • If the 256 byte key length were correct, how would I have to address this? – MikeJ82 Aug 24 '20 at 06:15
  • 2
    AES supports key sizes of 128, 192 and 256 **bits**. – Martin Liversage Aug 24 '20 at 07:40

1 Answers1

7

The posted OpenSSL statement uses the -pass file: option and thus a passphrase (which is read from a file), see openssl enc. This causes the encryption process to first generate a random 8 bytes salt and then, together with the passphrase, derive a 32 bytes key and 16 bytes IV using the (not very secure) proprietary OpenSSL function EVP_BytesToKey. This function uses several parameters, e.g. a digest and an iteration count. The default digest for key derivation is MD5 and the iteration count is 1. Note that OpenSSL version 1.1.0 and later uses SHA256 as default digest, i.e. depending on the OpenSSL version used to generate the ciphertext, the appropriate digest must be used for decryption. Preceding the ciphertext is a block whose first 8 bytes is the ASCII encoding of Salted__, followed by the 8 bytes salt.

Therefore, the decryption must first determine the salt. Based on the salt, together with the passphrase, key and IV must be derived and then the rest of the encrypted data can be decrypted. Thus, first of all an implementation of EVP_BytesToKey in C# is required, e.g. here. Then a possible implementation could be (using MD5 as digest):

public static string DecryptAesCbc(byte[] cipheredData, string passphrase)
{
    string decrypted = null;

    using (MemoryStream ms = new MemoryStream(cipheredData))
    {
        // Get salt
        byte[] salt = new byte[8];
        ms.Seek(8, SeekOrigin.Begin);
        ms.Read(salt, 0, 8);

        // Derive key and IV
        OpenSslCompat.OpenSslCompatDeriveBytes db = new OpenSslCompat.OpenSslCompatDeriveBytes(passphrase, salt, "MD5", 1);
        byte[] key = db.GetBytes(32);
        byte[] iv = db.GetBytes(16);

        using (Aes aes = Aes.Create())
        {
            aes.Padding = PaddingMode.PKCS7;
            aes.Mode = CipherMode.CBC;
            aes.Key = key;
            aes.IV = iv;

            // Decrypt
            ICryptoTransform decipher = aes.CreateDecryptor(aes.Key, aes.IV);
            using (CryptoStream cs = new CryptoStream(ms, decipher, CryptoStreamMode.Read))
            {
                using (StreamReader sr = new StreamReader(cs, Encoding.UTF8))
                {
                    decrypted = sr.ReadToEnd();
                }
            }
        }
    }

    return decrypted;
}

Note that the 2nd parameter of DecryptAesCbc is the passphrase (as string) and not the key (as byte[]). Also note that StreamReader uses an encoding (UTF-8 by default), which requires compatible data (i.e. text data, but this should be met for csv files). Otherwise (i.e. for binary data as opposed to text data) StreamReader must not be used.

Topaco
  • 40,594
  • 4
  • 35
  • 62
  • Hi Topaco, thank you very much for this in-dept answer! Knowing what's going on under the hood here helps a lot. Cheers, Mike – MikeJ82 Aug 24 '20 at 08:55