0

Our RijndaelManaged decrypt code no longer decrypts passwords correctly (it truncates) with .NET Core 7 and 6, but used to work with .NET Core 5.

It only decrypts the first 16 bytes, then ignores the rest of the message, so we only get 16 first digits of our passwords :-/

  • .NET 5 used 'Internal.Cryptography.UniversalCryptoDecryptor'.
  • .NET 6 uses the same, but fails in the same way as
  • .NET 7 with 'System.Security.Cryptography.UniversalCryptoDecryptor'.

(this is irrelevant) I have noticed, that the newer versions internally expands a salt buffer from 16 to 20 bytes. In the .NET 5 version that works, that array is padded with the bytes [0 0 0 1], whereas in the .NET 7 version that System.Security.Cryptography.Rfc2898DeriveBytes._salt buffer appears as [0 0 0 0].

It is HMAC-SHA1, as far as I know..

Because Azure seems to recently have dropped all support for .NET 5, it is a rather pressing matter for us :-/.

public static string Decrypt(string cipherText, int BLOCKSIZE, int KeySize) 
{
    // Get the complete stream of bytes that represent:
    // [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText]
    var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
    // Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes.
    var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(KeySize / 8).ToArray();
    // Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes.
    var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(KeySize / 8).Take(KeySize / 8).ToArray();
    // Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string.
    var cipherTextBytes =
        cipherTextBytesWithSaltAndIv.Skip((KeySize / 8) * 2)
            .Take(cipherTextBytesWithSaltAndIv.Length - ((KeySize / 8) * 2))
            .ToArray();
    using (var password = new Rfc2898DeriveBytes(PassPhrase, saltStringBytes, DerivationIterations)) {
        var keyBytes = password.GetBytes(KeySize / 8);
        // RijndaelManaged, BlockSize 256, 128 ..
        using (RijndaelManaged Rijn_symmetricKey = new RijndaelManaged()) {
            // https://stackoverflow.com/questions/52699604/how-to-use-rijndael-algorithm-with-256-long-block-size-in-dotnet-core-2-1
            // https://github.com/dotnet/runtime/issues/18706
            Rijn_symmetricKey.BlockSize = BLOCKSIZE; // 256; // System.PlatformNotSupportedException: 'BlockSize must be 128 in this implementation.'
            Rijn_symmetricKey.Mode = CipherMode.CBC;
            Rijn_symmetricKey.Padding = PaddingMode.PKCS7;
            using (var decryptor = Rijn_symmetricKey.CreateDecryptor(keyBytes, ivStringBytes)) { // ICryptoTransform, System.Security.Cryptography.UniversalCryptoDecryptor 
                using (var memoryStream = new MemoryStream(cipherTextBytes)) {
                    using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) {
                        var plainTextBytes = new byte[cipherTextBytes.Length];

                        // HERE WRONGLY ONLY FIRST 16 BYTES ARRIVES!
                        var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
                        memoryStream.Close();
                        cryptoStream.Close();
                        string decrypted = Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
                        return decrypted;
                    }
                }
            }
        }
    }
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
jakob gaardsted
  • 111
  • 1
  • 7
  • NB! In the shown code, I am not setting the KeySize on RijndaelManaged(). We use KeySize 128 (which I suspect to be non-standard?) I can see that value defaults to 256, but in a strange way - while it appears to be 256, the VS debugger suggests it has the value 128 anyway(?). Whatever. IF I force it to 128, nothing changes - NC5 again works, and NC6/7 still fails. ChatGPT suggest I check the same things I've tried to check - PaddingMode, KeySize, Derivation(salt, iterations). AFAIK these are OK and unchanged. – jakob gaardsted Sep 01 '23 at 11:20
  • for funsies, I also tested in dotnetcore8, for any changes, but it is same wrong behaviour, only first 16 digits arrive. – jakob gaardsted Sep 01 '23 at 11:51
  • I am currently debugging into System\Security\Cryptography\CryptoStream.cs on netcore7. I noticed these lines in the method ReadAsyncCore(..): ``` .. int blocksToProcess = buffer.Length / _outputBlockSize; if (blocksToProcess > 1 && _transform.CanTransformMultipleBlocks) .. ``` I am just reading that part naively/out of context (I will look closer later with more time), but the phrasing of the code looks weird to me: It talks about "when multiple blocks.." But blocksToProcess would still be 1, if you gave it 31 bytes and size 16. I will check nc5. – jakob gaardsted Sep 01 '23 at 12:13
  • 1
    This is almost certainly a duplicate of [this post](https://stackoverflow.com/q/69911084/9014097) and is caused by [this breaking change](https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/6.0/partial-byte-reads-in-streams). – Topaco Sep 01 '23 at 12:26

0 Answers0