-1

I need to decrypt a string that was encrypted with OpenSSL as following:

openssl rand -out secret.key -hex 192
openssl aes-256-cbc -pbkdf2 -in file_to_encrypt -out encrypted_file -pass file:secret.key

And i can't get it to work in C#

  public void OnStartup()
  {
    using var rsa = RSA.Create();
    var privateKeyContent = File.ReadAllText("/cert/customer.pem");
    rsa.ImportFromPem(privateKeyContent);
    var encryptedSecret = File.ReadAllBytes("license/secret.key.enc");
    var decrypted = rsa.Decrypt(encryptedSecret, RSAEncryptionPadding.Pkcs1);
    _logger.LogInformation(Encoding.UTF8.GetString(decrypted));
    var bytes = File.ReadAllBytes("license/license.json.enc");
    var license = DecryptAesCbc(bytes, decrypted);
    _logger.LogInformation(license);
  }

  public string DecryptAesCbc(byte[] cipheredData, byte[] passphrase)
  {
    string decrypted = null;
    using (var ms = new MemoryStream(cipheredData))
    {
      // Get salt
      var salt = new byte[8];
      ms.Seek(8, SeekOrigin.Begin);
      ms.Read(salt, 0, 8);
      _logger.LogInformation("Salt: {Salt}", string.Concat(Array.ConvertAll(salt, x => x.ToString("X2"))));

      // Derive key and IV
      var pbkdf2 = new Rfc2898DeriveBytes(passphrase, salt, 10000, HashAlgorithmName.SHA256);
      byte[] key = pbkdf2.GetBytes(32);
      byte[] iv = pbkdf2.GetBytes(16);
      _logger.LogInformation("Key: {Key}", string.Concat(Array.ConvertAll(key, x => x.ToString("X2"))));
      _logger.LogInformation("IV: {IV}", string.Concat(Array.ConvertAll(iv, x => x.ToString("X2"))));

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

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

With this code i receive an exception at decrypted = sr.ReadToEnd() Padding is invalid and cannot be removed.

The secret is encrypted via RSA and decrypted, the result is the same as the the decrypted file, so this should be working.

Im very thankful for help

philipk
  • 31
  • 6
  • Please post your most recent C# code and describe the problem. – Topaco Jul 15 '21 at 09:26
  • I updated my question with my c# code – philipk Jul 15 '21 at 09:35
  • The code seems to be OK. I can't reproduce the problem either. Maybe the bug is in the data import (key and ciphertext), which you did not post. – Topaco Jul 15 '21 at 15:40
  • Note that because of the `-hex` option, the 192 byte password is stored in hex encoded form and is also used in hex encoded form when encrypting. Therefore, no hex decoding may be performed during decryption. – Topaco Jul 15 '21 at 15:57
  • Are you aware that the password (and not a key) is read during encryption from _secret.key_? From that password a 256 bits key is then generated using PBKDF2. Therefore the `line aes.KeySize = 192` is wrong (which btw specifies the key size in bits and not bytes), but doesn't cause a problem, since the true key is set after this line. – Topaco Jul 15 '21 at 16:06
  • Thanks for your answer. Yes i have noticed that the keysize should be 256. – philipk Jul 16 '21 at 07:52
  • I will try to generate the secret.key without the hex option and check if that changes anything – philipk Jul 16 '21 at 07:53
  • If you still have trouble, you should post the C# code for importing passphrase and data. – Topaco Jul 16 '21 at 07:56
  • So now i generate the secret with base64. It doesnt work either. I have noticed yesterday that i extract the salt correct, but the key and iv isn't the same as openssl prints with the -p option. I will update the post with the code that loads the data – philipk Jul 16 '21 at 08:04
  • Base64 encoding will probably cause the same issue here, rather try to store the password in binary. The different key/IV indeed speaks for different passwords and a possible problem during import. – Topaco Jul 16 '21 at 08:15
  • What .NET version do you use? – Topaco Jul 16 '21 at 08:17
  • dotnet version 5.0.300 – philipk Jul 16 '21 at 08:19
  • I now have generated the secret as bytes and can verify that the decrypting with RSA results in the same byte array as the non encrypted secret generated by openssl – philipk Jul 16 '21 at 08:21
  • I.e. `decrypted` now corresponds to the _binary_ data of `secret.key`? Then, compare key and IV from openssl and the C# code. – Topaco Jul 16 '21 at 08:28
  • Yes correct. I already compared and they are different. The salt is correct but Key and IV are different – philipk Jul 16 '21 at 08:29
  • Can you post test data? Create via openssl a short password (e.g. 5 bytes) in binary (without hex and base64) and import it on C# side directly without RSA encryption/decryption. Encrypt a short plaintext with openssl. Read the password with a hex editor and post it hex encoded, also plaintext and openssl ciphertext. – Topaco Jul 16 '21 at 08:41
  • Also note that you must not encode arbitrary binary data with UTF8 (as you do when logging), because that corrupts the data. But this seems to happen only when logging and is therefore not critical. – Topaco Jul 16 '21 at 08:45
  • Password: 6A B2 F8 51 25 Plaintext: "any text" (without quotes) Cipher: 53616C7465645F5F905ECB3B440DF58693F4FEA915513820C89457A97D9CE0A3 – philipk Jul 16 '21 at 08:47
  • Can be decrypted on my machine with C# without any problems. Can you run the openssl statement with -P -S 905ECB3B440DF586 and post key and IV? Use a capital -P so that only the data is output, but no encryption is performed. – Topaco Jul 16 '21 at 09:10
  • key=FBA84FB8032AC8EE66C171307AAC3AB4975E8F4E9A3172EEBF70CC22C2FBEE59, iv =33AF2CC39DBD524AA7BB2B393F882D65, did u use my code? – philipk Jul 16 '21 at 09:12
  • Fine, match those on my machine. What does the C# code provide for key and IV? – Topaco Jul 16 '21 at 09:14
  • Key: 0EA4723A0CBA5B2BC6004A1A519CA9D20CAAE19131E4E0FB7D21B46736BD4933, IV: 1CD9D3EC1F9CBD74CB4A24C84C4EA1CA – philipk Jul 16 '21 at 09:16
  • Different. This is the C# code I run with your _unmodified_ function: `var passphrase = File.ReadAllBytes(@""); var ciphertext = File.ReadAllBytes(@""); Console.WriteLine(DecryptAesCbc(ciphertext, passphrase));` Maybe try this. – Topaco Jul 16 '21 at 09:22
  • Oh my god it works! Now is the question why it didn't work with the original data – philipk Jul 16 '21 at 09:25
  • You received a different key and IV with your code during the test. This will likely be the cause. – Topaco Jul 16 '21 at 09:28
  • Okay i reverted a lot of the code, and it now even works with the rsa encyption on the secret. So it may have been a bad secret after all? – philipk Jul 16 '21 at 09:28
  • Maybe, but hard to say. – Topaco Jul 16 '21 at 09:28
  • Okay now i know. I have recreated a secret with 192 bytes and it stopped working. So it seems that a 192 byte secret is too big or such? – philipk Jul 16 '21 at 09:29
  • This is especially possible with respect to RSA encryption, because RSA can only encrypt small messages (smaller than the RSA key size). – Topaco Jul 16 '21 at 09:33
  • Sadly that isn't the problem. I have tried a 192 byte secret without RSA and it didn't work either – philipk Jul 16 '21 at 09:35
  • Nevertheless, how long is the RSA key? – Topaco Jul 16 '21 at 09:38
  • The RSA key is 4096 bytes – philipk Jul 16 '21 at 09:39
  • OK, then you can't encrypt quite 512 bytes. – Topaco Jul 16 '21 at 09:42
  • I have never used such a long password before. Actually, `rand` generates a key. Why do you apply a key as password and why such a long password? Maybe openssl has a problem with such long passwords (but this is just a wild guess), best check the docs. – Topaco Jul 16 '21 at 09:47
  • Openssl itself has no problem decrypting. The encryption with RSA is done with a certificate (which is 4096 bytes) as it is used for other occasions as well. The RSA encryption is used to encrypt the secret.key as it is stored alongside the license file. And to decrypt the license you need the rsa private key to decrypt the secret, with which you can decrypt the license. – philipk Jul 16 '21 at 10:06
  • If I understood you correctly, the long password is the problem (the short one works). Two possible explanations are, RSA (limited message size) or the parsing of such a long key by openssl. You seem to have ruled out RSA, so that leaves parsing. You can evaluate this with further tests. – Topaco Jul 16 '21 at 10:21
  • The problem isn't in RSA and also not in OpenSSL as the decryption works with OpenSSL, but the C# implementation RFC2898DeriveBytes can't seem to handle a passphrase that is longer than 128 bytes. Atleast in my case. – philipk Jul 16 '21 at 11:52
  • It would be interesting to know why. `Rfc2898DeriveBytes` works on my machine (.NET Core 3.1+ and .NET Framework 4.8) even with a 384 byte passphrase. – Topaco Jul 16 '21 at 12:14
  • For information on where the salt is stored, and how the key and iv are derived from the password when using `openssl aes-256-cbc -e -pbkdf2`, see https://stackoverflow.com/questions/16761458/how-to-decrypt-openssl-aes-encrypted-files-in-python. – mti2935 Jul 16 '21 at 19:43
  • For binary data, the following explanation would be plausible: If a random password is generated with `openssl-rand`, one or more line breaks may also be randomly generated. openssl determines the password only up to the first line break (s. `openssl-enc` doc). Decryption with the C# code would fail, because the C# code takes the whole password into account. This could explain your problems pretty well. – Topaco Jul 17 '21 at 10:56
  • Therefore a binary-to-text-encoding must be applied (e.g. Base64 or hex). Since openssl doesn't perform any decoding when loading the password, no decoding must be performed in the C# code under any circumstances either. Basically, the bug is due to a conceptual issue and arises because a key is used as a password, which, unlike a password, consists of arbitrary binary data. – Topaco Jul 17 '21 at 11:00
  • Oh thank you very much! This was it. I now generate the secret with ```openssl rand 192 | openssl enc -A -base64 -out secret.key``` and it works flawlessly even with 192 bytes – philipk Jul 19 '21 at 14:11

1 Answers1

3

Solution:

public static string DecryptLicense(byte[] cipherData, byte[] passphrase)
{
  string decrypted = null;
  using (var ms = new MemoryStream(cipherData))
  {
    // Get salt
    var salt = new byte[8];
    ms.Seek(8, SeekOrigin.Begin);
    ms.Read(salt, 0, 8);

    // Derive key and IV
    var pbkdf2 = new Rfc2898DeriveBytes(passphrase, salt, 10000, HashAlgorithmName.SHA256);
    byte[] key = pbkdf2.GetBytes(32);
    byte[] iv = pbkdf2.GetBytes(16);

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

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

  return decrypted;
}

and generate the passphrase with: openssl rand 192 | openssl enc -A -base64 -out secret.key

The problem was a passphrase which was generated with newlines and openssl uses just the first line from a passphrase file, but C# uses the whole file to derive the keys. In order to prevent that, i now generate a file with no linebreaks.

philipk
  • 31
  • 6