Richard Griffiths Richard Griffiths - 17 days ago 94
Vb.net Question

Padding is invalid and cannot be removed - AES yet another

The code below is the latest iteration in an attempt to build a simple encryption/decryption utility for an in-house project. I'm hoping it's something very silly that I'm simply too far into a trench to see.

It's in .Net 4.7 and I've tried to organise it to make it impossible to accidentally have different keys or options occurring during the process. The code should on Linqpad.

Some things I've tried are listed in the TODOs though I've not document all the daft variations I found today.

If anyone else can make it run on Linqpad or Visual studio 2017, I'd be very keen to learn what I am missing here.

Thanks for any advice!

'Linqpad version - should offer to import System.Security.Cryptography
'I'm on .net 4.7 in my main project hence the Tuple setup below.

Dim Testphrase = "Whatever is happening, nothing I google works!"

Sub Main
Dim encrypted = EncryptString(Testphrase, "Password")
Dim Decrypted = DecryptString(encrypted, "Password")
Decrypted.Dump
End Sub

Private Shared AesManaged As AesManaged
Private Shared password As Rfc2898DeriveBytes

'''Makes sure that I use the same parameters both sides of the equation

Private Shared Function GetCryptBits(passphrase As String) As (passwords As Rfc2898DeriveBytes, AesManaged As AesManaged)
If AesManaged Isnot Nothing Then Return (password, AesManaged) 'TODO: Don't rebuild this if you've already got it.
password = New Rfc2898DeriveBytes(passphrase, Encoding.UTF8.GetBytes("SaltBytes"))
AesManaged = New AesManaged() With {.Mode = CipherMode.CBC,
.Padding = PaddingMode.PKCS7,
.BlockSize = 128, .KeySize = 256} 'TODO: I've tried all the padding choices here.TODO: Have tried RijndaelManaged
AesManaged.IV = password.GetBytes(AesManaged.BlockSize / 8)
AesManaged.Key = password.GetBytes(AesManaged.KeySize / 8)
Return (password, AesManaged)
End Function

'Encrypt
Public Shared Function EncryptString(plainText As String, passPhrase As String) As String
Dim B = GetCryptBits(passPhrase)
With B
Dim plainTextBytes As Byte() = Encoding.UTF8.GetBytes(plainText) 'TODO: Where you see UTF8 I've also tried with Unicode
Dim encryptor As ICryptoTransform = B.AesManaged.CreateEncryptor(B.AesManaged.Key, B.AesManaged.IV)
Using MemoryStream As New MemoryStream()
Using CryptoStream As New CryptoStream(MemoryStream, encryptor, CryptoStreamMode.Write)
CryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length)
CryptoStream.FlushFinalBlock()
Dim cipherTextBytes As Byte() = MemoryStream.ToArray()
AesManaged.clear
Return Convert.ToBase64String(cipherTextBytes)
MemoryStream.Close()
CryptoStream.Close()
End Using
End Using
End With
End Function

'Decrypt
Public Shared Function DecryptString(cipherText As String, passPhrase As String) As String
Dim B = GetCryptBits(passPhrase)
With B
Dim cipherTextBytes As Byte() = Convert.FromBase64String(cipherText)
Dim decryptor As ICryptoTransform = B.AesManaged.CreateDecryptor(B.AesManaged.Key, B.AesManaged.IV)
Using MemoryStream As New MemoryStream

Using CryptoStream As New CryptoStream(MemoryStream, decryptor, CryptoStreamMode.Read)
Dim plainTextBytes As Byte() = New Byte(cipherTextBytes.Length - 1) {}
Dim decryptedByteCount As Integer = CryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length) 'TODO: Here I'm getting "Padding is invalid and cannot be removed."
Return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount)
End Using
End Using
End With
End Function

Answer Source

Below is a corrected version of your code. I am not a VB.Net programmer and so I am not saying this is pretty or best style, etc, but it is working. Your issue was related to two things. One, you are clearing the AesManaged object in your encrypt function and then trying to use it again in your Decrypt function. The other issue was how you did the decrypt. If you look back at my answer in which you lead me here :) you will see the difference between your decrypt and mine. In yours, you were assuming the decrypted bytes would be equal to the length of the encrypted bytes (this line of code here Dim plainTextBytes As Byte() = New Byte(cipherTextBytes.Length - 1) {}), but what you need to do is decrypt in a loop, reading a block of bytes and appending it to your output until there are no more bytes to read.

Edit And as Hans Passant pointed out (and I forgot to mention) you need to initialize the decryption stream with the actual data.

Dim Testphrase = "Whatever is happening, nothing I google works!"

Sub Main
    Dim encrypted = EncryptString(Testphrase, "Password")
    encrypted.Dump
    Dim Decrypted = DecryptString(encrypted, "Password")
    Decrypted.Dump
End Sub

Private Shared AesManaged As AesManaged
Private Shared password As Rfc2898DeriveBytes

'''Makes sure that I use the same parameters both sides of the equation

Private Shared Function GetCryptBits(passphrase As String) As (passwords As Rfc2898DeriveBytes, AesManaged As AesManaged)
    If AesManaged Isnot Nothing Then Return (password, AesManaged) 'TODO: Don't rebuild this if you've already got it.
    password = New Rfc2898DeriveBytes(passphrase, Encoding.UTF8.GetBytes("SaltBytes"))
    AesManaged = New AesManaged() With {.Mode = CipherMode.CBC,
                                                           .Padding = PaddingMode.PKCS7,
                                                           .KeySize = 256} 'TODO: I've tried all the padding choices here.TODO: Have tried RijndaelManaged
    Dim iv As Byte() = password.GetBytes(AesManaged.BlockSize / 8)
    Dim key As Byte() = password.GetBytes(AesManaged.KeySize / 8)
    AesManaged.IV = iv
    AesManaged.Key = key
    Return (password, AesManaged)
End Function

'Encrypt
Public Shared Function EncryptString(plainText As String, passPhrase As String) As String
    Dim B = GetCryptBits(passPhrase)
    With B
        Dim plainTextBytes As Byte() = Encoding.UTF8.GetBytes(plainText) 'TODO: Where you see UTF8 I've also tried with Unicode
        Dim encryptor As ICryptoTransform = B.AesManaged.CreateEncryptor(B.AesManaged.Key, B.AesManaged.IV)
        Using MemoryStream As New MemoryStream()
            Using CryptoStream As New CryptoStream(MemoryStream, encryptor, CryptoStreamMode.Write)
                CryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length)
                CryptoStream.FlushFinalBlock()
                Dim cipherTextBytes As Byte() = MemoryStream.ToArray()
                Return Convert.ToBase64String(cipherTextBytes)
                MemoryStream.Close()
                CryptoStream.Close()
            End Using
        End Using
    End With
End Function

'Decrypt
Public Shared Function DecryptString(cipherText As String, passPhrase As String) As String
    Dim B = GetCryptBits(passPhrase)
    With B
        Dim cipherTextBytes As Byte() = Convert.FromBase64String(cipherText)
        Dim decryptor As ICryptoTransform = B.AesManaged.CreateDecryptor(B.AesManaged.Key, B.AesManaged.IV)
        Using MemoryStream As New MemoryStream(cipherTextBytes)
            Using OutputStream As New MemoryStream()
                Using CryptoStream As New CryptoStream(MemoryStream, decryptor, CryptoStreamMode.Read)
                    Dim plainTextBytes As Byte() = New Byte(1024) {}
                    Dim read As Integer = CryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length)
                    While read > 0
                        OutputStream.Write(plainTextBytes,0, read)
                        read = CryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length)
                    End While
                    Return Encoding.UTF8.GetString(OutputStream.ToArray())
                End Using                
            End Using
        End Using
    End With
End Function