10

I'm using Rijndael to encrypt some sensitive data in my program.

When the user enters an incorrect password, most of the time a CryptographicException is thrown with the message "Padding is invalid and cannot be removed.".

However, with very small probability, the CryptStream does not throw an exception with the wrong password, but instead gives back an incorrectly decrypted stream. In other words, it decrypts to garbage.

Any idea how to detect/prevent this? The simplest way I can think of would be to put a "magic number" at the start of the message when encrypting, and check if it's still there after decrypting.

But if there's an easier way, I'd love to hear it!

Ozzah
  • 10,386
  • 15
  • 69
  • 112
  • 3
    Anything you do to prevent this will make it easier to crack the password. – SLaks Apr 28 '11 at 00:24
  • Since I'm encrypting binary-serialised objects, I guess I can handle this case with at the SerializationException level? – Ozzah Apr 28 '11 at 00:29
  • @Slaks: Yes, but that does not necessarily compromise the encryption itself. Generally, there is already an expectation that if the user guesses the password, they will be able to determine if that password was correct. That is somewhat true of any encryption where the key length is shorter than the message length. – Brian Apr 28 '11 at 14:09
  • @Brian: If you you have is an arbitrary message body, it can be difficult to verify that you've cracked the right password. His message body is a .Net serialized object, though, and I assume that it has a standard header. – SLaks Apr 28 '11 at 14:13
  • Also, if the hash has a weakness, it will be easier. – SLaks Apr 28 '11 at 14:13
  • Is there a way to reliably reproduce this issue? (ie to get a garbage result instead of a CryptographicException) – Andreas Öhlund Aug 15 '14 at 11:01
  • Do not use padding errors, or lack of them, to determine if the decryption was successfu, it does not do thatl. If you report this back to the caller you have created a [padding oracle](https://en.wikipedia.org/wiki/Padding_oracle_attack) which can be attacked. As mentioned in an answer the solution is an HMAC. – zaph Aug 25 '17 at 16:02

6 Answers6

7

HMAC is what you need. It is exactly made for this purpose. It combines the key and the message (which in this case, will be your password) and hashes them in a way that it will ensure the authenticity and integrity of the content, as long as the hash function used is secure. You can attach the HMAC to the encrypted data, and it can be used later to validate if the decryption was made correctly.

Can Gencer
  • 8,424
  • 5
  • 29
  • 51
3

Checksums are exactly for this purpose. Get a hash of your data before encrypting. Encrypt the data and put it along with the hash into storage. After decrypting, get the hash of the decrypted data and compare it with the former. If you use a crypto grade hash (i.e. SHA512) your data will be safe. After all, this is exactly what encrypted compression software does.

For ultimate security, you can encrypt both the hashes and data separately then decrypt and compare. If both data and hash decrypts to corrupted data, there is very minuscule chances that they will match.

BlueRaja - Danny Pflughoeft
  • 75,675
  • 28
  • 177
  • 259
Teoman Soygul
  • 24,721
  • 6
  • 63
  • 78
  • @SLaks: Then how does it know when what's being decrypted is invalid? Is the padding not random? – BlueRaja - Danny Pflughoeft Apr 28 '11 at 16:08
  • @Teoman, while on the correct track, not all the way there. For best security, you need to use something like HMAC (see my answer). – Can Gencer Apr 28 '11 at 18:20
  • @Can Gencer, thanks mate, I wasn't even aware of the existence of an HMAC implementation in .NET library at all. – Teoman Soygul Apr 29 '11 at 00:39
  • It is arguably better to encrypt and then MAC. As [Moxie points out](https://moxie.org/blog/the-cryptographic-doom-principle/): it is better to do as little as possible prior to authentication. Additionally require use of a slow key derivation function such as PBKDF2 with ~100ms CPU time to derive the HMAC key. – zaph Aug 25 '17 at 16:22
1

To check if the password you are using is correct, you can use this code

            Dim decryptedByteCount As Integer
            Try
                decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length)
            Catch exp As System.Exception
                Return "Password Not Correct"
            End Try

in essence, check if an error message is generated during decryption.

I report all the decoding code below

    Public Shared Function Decrypt(ByVal cipherText As String) As String

    If System.Web.HttpContext.Current.Session("Crypto") = "" Then
        HttpContext.Current.Response.Redirect("http://yoursite.com")
    Else
        If cipherText <> "" Then
            'Setto la password per criptare il testo
            Dim passPhrase As String = System.Web.HttpContext.Current.Session("Crypto")

            'Ottieni lo stream completo di byte che rappresentano: [32 byte di Salt] + [32 byte di IV] + [n byte di testo cifrato]
            Dim cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText)

            'Ottieni i Salt bytes estraendo i primi 32 byte dai byte di testo cifrato forniti
            Dim saltStringBytes = cipherTextBytesWithSaltAndIv.Take((Keysize)).ToArray

            'Ottieni i IV byte estraendo i successivi 32 byte dai byte testo cifrato forniti.
            Dim ivStringBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize)).Take((Keysize)).ToArray

            'Ottieni i byte del testo cifrato effettivo rimuovendo i primi 64 byte dal testo cifrato.
            Dim cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip(((Keysize) * 2)).Take((cipherTextBytesWithSaltAndIv.Length - ((Keysize) * 2))).ToArray

            Dim password = New Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)
            Dim keyBytes = password.GetBytes((Keysize))
            Dim symmetricKey = New RijndaelManaged
            symmetricKey.BlockSize = 256
            symmetricKey.Mode = CipherMode.CBC
            symmetricKey.Padding = PaddingMode.PKCS7

            Dim decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes)
            Dim memoryStream = New MemoryStream(cipherTextBytes)
            Dim cryptoStream = New CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)
            Dim plainTextBytes = New Byte((cipherTextBytes.Length) - 1) {}

            Dim decryptedByteCount As Integer
            Try
                decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length)
            Catch exp As System.Exception
                Return "La password di Cryptazione non è corretta"
            End Try

            memoryStream.Close()
            cryptoStream.Close()
            Return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount)
        Else
            Decrypt = ""
        End If
    End If

End Function
Tommaso
  • 211
  • 3
  • 5
0

Though I can agree somewhat with Teoman Soygul post about CRC/Hash there is one very important thing to note. Never encrypt the hash as this can make it easier to find the resulting key. Even without encrypting the hash you still gave them an easy way to test if they have successfully gained the correct password; however, let's assume that is already possible. Since I know what kind of data you encrypted, be it text, or serialized objects, or whatever, it's likely I can write code to recognize it.

That said, I've used derivations of the following code to encrypt/decrypt data:

    static void Main()
    {
        byte[] test = Encrypt(Encoding.UTF8.GetBytes("Hello World!"), "My Product Name and/or whatever constant", "password");
        Console.WriteLine(Convert.ToBase64String(test));
        string plain = Encoding.UTF8.GetString(Decrypt(test, "My Product Name and/or whatever constant", "passwords"));
        Console.WriteLine(plain);
    }
    public static byte[] Encrypt(byte[] data, string iv, string password)
    {
        using (RijndaelManaged m = new RijndaelManaged())
        using (SHA256Managed h = new SHA256Managed())
        {
            m.KeySize = 256;
            m.BlockSize = 256;
            byte[] hash = h.ComputeHash(data);
            byte[] salt = new byte[32];
            new RNGCryptoServiceProvider().GetBytes(salt);
            m.IV = h.ComputeHash(Encoding.UTF8.GetBytes(iv));
            m.Key = new Rfc2898DeriveBytes(password, salt) { IterationCount = 10000 }.GetBytes(32);

            using (MemoryStream ms = new MemoryStream())
            {
                ms.Write(hash, 0, hash.Length);
                ms.Write(salt, 0, salt.Length);
                using (CryptoStream cs = new CryptoStream(ms, m.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(data, 0, data.Length);
                    cs.FlushFinalBlock();
                    return ms.ToArray();
                }
            }
        }
    }

    public static byte[] Decrypt(byte[] data, string iv, string password)
    {
        using (MemoryStream ms = new MemoryStream(data, false))
        using (RijndaelManaged m = new RijndaelManaged())
        using (SHA256Managed h = new SHA256Managed())
        {
            try
            {
                m.KeySize = 256;
                m.BlockSize = 256;

                byte[] hash = new byte[32];
                ms.Read(hash, 0, 32);
                byte[] salt = new byte[32];
                ms.Read(salt, 0, 32);

                m.IV = h.ComputeHash(Encoding.UTF8.GetBytes(iv));
                m.Key = new Rfc2898DeriveBytes(password, salt) { IterationCount = 10000 }.GetBytes(32);
                using (MemoryStream result = new MemoryStream())
                {
                    using (CryptoStream cs = new CryptoStream(ms, m.CreateDecryptor(), CryptoStreamMode.Read))
                    {
                        byte[] buffer = new byte[1024];
                        int len;
                        while ((len = cs.Read(buffer, 0, buffer.Length)) > 0)
                            result.Write(buffer, 0, len);
                    }

                    byte[] final = result.ToArray();
                    if (Convert.ToBase64String(hash) != Convert.ToBase64String(h.ComputeHash(final)))
                        throw new UnauthorizedAccessException();

                    return final;
                }
            }
            catch
            {
                //never leak the exception type...
                throw new UnauthorizedAccessException();
            }
        }
    }
csharptest.net
  • 53,926
  • 10
  • 66
  • 86
  • Sadly there is no encryption authentication, only the plaintext. Why not HMAC the encrypted data (encrypt then MAC)? – zaph Aug 25 '17 at 16:20
-1
 Public Sub decryptFile(ByVal input As String, ByVal output As String)

        inputFile = New FileStream(input, FileMode.Open, FileAccess.Read)
        outputFile = New FileStream(output, FileMode.OpenOrCreate, FileAccess.Write)
        outputFile.SetLength(0)

        Dim buffer(4096) As Byte
        Dim bytesProcessed As Long = 0
        Dim fileLength As Long = inputFile.Length
        Dim bytesInCurrentBlock As Integer
        Dim rijandael As New RijndaelManaged
        Dim cryptoStream As CryptoStream = New CryptoStream(outputFile, rijandael.CreateDecryptor(encryptionKey, encryptionIV), CryptoStreamMode.Write)

        While bytesProcessed < fileLength
            bytesInCurrentBlock = inputFile.Read(buffer, 0, 4096)
            cryptoStream.Write(buffer, 0, bytesInCurrentBlock)
            bytesProcessed = bytesProcessed + CLng(bytesInCurrentBlock)
        End While
        Try
            cryptoStream.Close() 'this will raise error if wrong password used
            inputFile.Close()
            outputFile.Close()
            File.Delete(input)
            success += 1
        Catch ex As Exception
            fail += 1
            inputFile.Close()
            outputFile.Close()
            outputFile = Nothing
            File.Delete(output)
        End Try

I use that code to decrypt any file. Wrong password detected on cryptostream.close(). Catch this line as error when a wrong key is used to decrypt file. When error happens, just close the output stream and release it (set outputFile to Nothing), then delete output file. It's working for me.

Yoh Deadfall
  • 2,623
  • 7
  • 28
  • 32
  • 1
    1. You do not describe how this detects an incorrect key. 2. it is probably detecting incorrect padding which will not detect all incorrect keys. 3. Using padding to detect an incorrect key can result in creating a Padding Oracle which can be used to decrypt the data without the key. – zaph Aug 27 '17 at 13:39
-1

I like Can Gencer's answer; you cannot really verify a decryption without the HMAC.

But, if you have a very a very large plaintext, then the decrypting can be very expensive. You might do a ton of work just to find out that the password was invalid. It would be nice to be able to do a quick rejection of wrong passwords, without going through all that work. There is a way using the PKCS#5 PBKDF2. (standardized in RFC2898, which is accessible to your c# program in Rfc2898DeriveBytes).

Normally the data protocol calls for generation of the key from a password and salt using PBKDF2, at 1000 cycles or some specified number. Then maybe also (optionally) the initialization vector, via a contniuation of the same algorithm.

To implement the quick password check, generate two more bytes via the PBKDF2. If you don't generate and use an IV, then just generate 32 bytes and keep the last 2. Store or transmit this pair of bytes adjacent to your cryptotext. On the decrypting side, get the password, generate the key and (maybe throwaway) IV, then generate the 2 additional bytes, and check them against the stored data. If the pairs don't match you know you have a wrong password, without any decryption.

If they match, it is not a guarantee that the password is correct. You still need the HMAC of the full plaintext for that. But you can save yourself a ton of work, and maybe wall clock time, in most cases of "wrong password", and without compromising the security of the overall system.


ps: you wrote:

The simplest way I can think of would be to put a "magic number" at the start of the message when encrypting, and check if it's still there after decrypting.

Avoid putting plaintext into the cryptotext. It only exposes another attack vector, makes it easier for an attacker to eliminate wrong turns. The password verification thing I mentioned above is a different animal, does not expose this risk.

Community
  • 1
  • 1
Cheeso
  • 180,104
  • 92
  • 446
  • 681
  • The point of PBKDF2 it to be computationally expensive, generally around 100ms. While a MAC is expensive it may be cheaper for short messages and is proportional to message size. – zaph Aug 25 '17 at 16:08