【问题标题】:ASP.NET AES Padding is invalid and cannot be removedASP.NET AES 填充无效且无法删除
【发布时间】:2016-07-18 14:05:58
【问题描述】:

这是一个常见问题,但建议的解决方案 herehere 对我不起作用,而且它们不涉及数据库。

我希望代码示例“失败代码(WITH 数据库)”能够正常工作。

这是我的加密/解密代码(从这里复制:https://msdn.microsoft.com/en-us/library/system.security.cryptography.aes.aspx?cs-save-lang=1&cs-lang=vb&f=255&MSPPError=-2147217396#code-snippet-2):

Shared Function EncryptStringToBytes_Aes(ByVal plainText As String, ByVal Key() As Byte, ByVal IV() As Byte) As Byte()
    ' Check arguments.
    If plainText Is Nothing OrElse plainText.Length <= 0 Then
        Throw New ArgumentNullException("plainText")
    End If
    If Key Is Nothing OrElse Key.Length <= 0 Then
        Throw New ArgumentNullException("Key")
    End If
    If IV Is Nothing OrElse IV.Length <= 0 Then
        Throw New ArgumentNullException("Key")
    End If
    Dim encrypted() As Byte
    ' Create an Aes object
    ' with the specified key and IV.
    Using aesAlg As Aes = Aes.Create()

        aesAlg.Key = Key
        aesAlg.IV = IV

        ' Create a decrytor to perform the stream transform.
        Dim encryptor As ICryptoTransform = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV)
        ' Create the streams used for encryption.
        Using msEncrypt As New MemoryStream()
            Using csEncrypt As New CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)
                Using swEncrypt As New StreamWriter(csEncrypt)

                    'Write all data to the stream.
                    swEncrypt.Write(plainText)
                End Using
                encrypted = msEncrypt.ToArray()
            End Using
        End Using
    End Using

    ' Return the encrypted bytes from the memory stream.
    Return encrypted

End Function 'EncryptStringToBytes_Aes


Shared Function DecryptStringFromBytes_Aes(ByVal cipherText() As Byte, ByVal Key() As Byte, ByVal IV() As Byte) As String
    ' Check arguments.
    If cipherText Is Nothing OrElse cipherText.Length <= 0 Then
        Throw New ArgumentNullException("cipherText")
    End If
    If Key Is Nothing OrElse Key.Length <= 0 Then
        Throw New ArgumentNullException("Key")
    End If
    If IV Is Nothing OrElse IV.Length <= 0 Then
        Throw New ArgumentNullException("Key")
    End If
    ' Declare the string used to hold
    ' the decrypted text.
    Dim plaintext As String = Nothing

    ' Create an Aes object
    ' with the specified key and IV.
    Using aesAlg As Aes = Aes.Create()
        aesAlg.Key = Key
        aesAlg.IV = IV

        ' Create a decrytor to perform the stream transform.
        Dim decryptor As ICryptoTransform = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV)

        ' Create the streams used for decryption.
        Using msDecrypt As New MemoryStream(cipherText)

            Using csDecrypt As New CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)

                Using srDecrypt As New StreamReader(csDecrypt)
                    ' Read the decrypted bytes from the decrypting stream
                    ' and place them in a string.
                    plaintext = srDecrypt.ReadToEnd()
                End Using   <= PADDING ERROR THROWN HERE
            End Using

            'cipherText = msDecrypt.ToArray() 'added by me

        End Using
    End Using

    Return plaintext

End Function 'DecryptStringFromBytes_Aes 

工作示例(无数据库)

    Dim original As String = "Here is some data to encrypt!"

    Dim key As Rfc2898DeriveBytes = New Rfc2898DeriveBytes(_sharedSecret, _salt)

    Try

        ' Create a new instance of the Aes
        ' class.  This generates a new key and initialization 
        ' vector (IV).
        Using myAes As Aes = Aes.Create()

            myAes.Key = key.GetBytes(myAes.KeySize / 8)
            myAes.IV = key.GetBytes(myAes.BlockSize / 8)

            ' Encrypt the string to an array of bytes.
            Dim encrypted As Byte() = EncryptStringToBytes_Aes(original, myAes.Key, myAes.IV)

            ' Decrypt the bytes to a string.
            Dim roundtrip As String = DecryptStringFromBytes_Aes(encrypted, myAes.Key, myAes.IV)

            'Display the original data and the decrypted data.
            ltStatus.Text = String.Format("Original:   {0}", original)
            ltStatus.Text += String.Format("Round Trip: {0}", roundtrip)
        End Using
    Catch ex As Exception
        Console.WriteLine("Error: {0}", ex.Message)
    End Try



    

失败代码(WITH 数据库)

    Dim _salt As Byte() = Encoding.ASCII.GetBytes("o6806642kbM7c5")
    Dim _sharedSecret As String = "abcd"
    Dim original As String = "Here is some data to encrypt!"
    Dim key As Rfc2898DeriveBytes = New Rfc2898DeriveBytes(_sharedSecret, _salt)
    Dim encrypted As Byte()
    Try
        Using myAes As Aes = Aes.Create()
            myAes.Key = key.GetBytes(myAes.KeySize / 8)
            myAes.IV = key.GetBytes(myAes.BlockSize / 8)
            myAes.Padding = PaddingMode.PKCS7
            encrypted = EncryptStringToBytes_Aes(original, myAes.Key, myAes.IV)
        End Using
    Catch ex As Exception
        Console.WriteLine("Error: {0}", ex.Message)
    End Try
    'save to DB
    Dim myConnection As SqlConnection = GetConnection()
    Dim cmd As New SqlCommand("UPDATE banks set bank_name=@bankname WHERE id=1", myConnection)
    cmd.Parameters.Add(New SqlParameter("@bankname", encrypted))
    Try
        myConnection.Open()
        cmd.ExecuteScalar()
    Catch ex As Exception
        GlobalFunctions.LogError("banks:INSERT encrypted", ex.Message, LogLevel.Normal)
    Finally
        myConnection.Close()
    End Try

    'retreive from db
    Dim decrypted As String = ""
    myConnection = GetConnection()
    cmd = New SqlCommand("SELECT bank_name FROM banks where id=1", myConnection)
    Dim reader As SqlDataReader
    Try
        myConnection.Open()
        reader = cmd.ExecuteReader
        If reader.Read Then
            Using myAes As Aes = Aes.Create()
                myAes.Key = key.GetBytes(myAes.KeySize / 8)
                myAes.IV = key.GetBytes(myAes.BlockSize / 8)
                myAes.Padding = PaddingMode.PKCS7
                decrypted = DecryptStringFromBytes_Aes(reader("bank_name"), myAes.Key, myAes.IV)
            End Using
        Else
            GlobalFunctions.LogError("banks:nothing to be read?!?", LogLevel.Normal)
        End If

    Catch ex As Exception
        GlobalFunctions.LogError("banks:SELECT encrypted.", ex.Message, LogLevel.Normal)
    Finally
        myConnection.Close()
    End Try



    

但是这里出了点问题:

二进制值已成功添加到我的 MSSQL 数据库中 varbinary(MAX) 类型的字段 bank_name 中。 (我也尝试了较小的字段,例如varbinary(50)

但是,当我在从数据库检索后尝试解密此字段时,我收到错误 Padding is invalid and cannot be removed. 请参阅上面代码“失败代码(带有数据库)”中带有注释“

我检查了herehere。而且我没有传递一个空字符串 我也尝试添加cipherText = msDecrypt.ToArray(),但在命中此行之前已经发生错误。

更新 2

我的转储值是:

ReportError 将值存储在数据库中的文本字段中,报告的值是:

myAes.键入
00000000 95 0C 95 EA 1D 40 0C FB 1D 3F B7 FB 73 FB 3F EA ���������������� 00000010 40 62 51 62 51 EA 62 73 B7 2E 1D C8 1D 51 51 95 ����������������

myAes.IV 在
00000000 51 A6 84 73 95 C8 2E 62 84 C8 0C 62 C8 2E 1D 84 ����������������

加密
00000000 FB FB B7 73 D9 51 A6 2E 95 73 62 73 3F 84 A6 40 ���������������� 00000010 B7 62 84 2E 51 95 EA 1D 51 A6 EA 2E 51 A6 51 95 ����������������

myAes.Key out
00000000 51 1D 73 40 EA A6 73 EA FB 73 73 A6 0C A6 D9 1D ���������������� 00000010 2E 3F FB 2E 73 A6 A6 0C A6 C8 95 0C D9 1D B7 73 ����������������

myAes.IV 输出
00000000 B7 95 51 73 B7 D9 95 EA 0C C8 95 95 0C 84 40 62 ����������������

加密
00000000 FB FB B7 73 D9 51 A6 2E 95 73 62 73 3F 84 A6 40 ���������������� 00000010 B7 62 84 2E 51 95 EA 1D 51 A6 EA 2E 51 A6 51 95 ����������������

银行:SELECT 已加密。内边距无效,无法移除。

这是我现在使用的完整代码:

Imports System.Security.Cryptography
Imports System.Data.SqlClient
Imports System.Text

Namespace HexDump
    Class Utils
        Public Shared Function HexDump(bytes As Byte(), Optional bytesPerLine As Integer = 16) As String
            If bytes Is Nothing Then
                Return "<null>"
            End If
            Dim bytesLength As Integer = bytes.Length

            Dim HexChars As Char() = "0123456789ABCDEF".ToCharArray()

            ' 8 characters for the address
            Dim firstHexColumn As Integer = 8 + 3
            ' 3 spaces
            ' - 2 digit for the hexadecimal value and 1 space
            ' - 1 extra space every 8 characters from the 9th
            Dim firstCharColumn As Integer = firstHexColumn + bytesPerLine * 3 + (bytesPerLine - 1) / 8 + 2
            ' 2 spaces 
            ' - characters to show the ascii value
            Dim lineLength As Integer = firstCharColumn + bytesPerLine + Environment.NewLine.Length
            ' Carriage return and line feed (should normally be 2)
            Dim line As Char() = (New [String](" "c, lineLength - Environment.NewLine.Length) + Environment.NewLine).ToCharArray()
            Dim expectedLines As Integer = (bytesLength + bytesPerLine - 1) / bytesPerLine
            Dim result As New StringBuilder(expectedLines * lineLength)

            Dim i As Integer = 0
            While i < bytesLength
                line(0) = HexChars((i >> 28) And &HF)
                line(1) = HexChars((i >> 24) And &HF)
                line(2) = HexChars((i >> 20) And &HF)
                line(3) = HexChars((i >> 16) And &HF)
                line(4) = HexChars((i >> 12) And &HF)
                line(5) = HexChars((i >> 8) And &HF)
                line(6) = HexChars((i >> 4) And &HF)
                line(7) = HexChars((i >> 0) And &HF)

                Dim hexColumn As Integer = firstHexColumn
                Dim charColumn As Integer = firstCharColumn

                For j As Integer = 0 To bytesPerLine - 1
                    If j > 0 AndAlso (j And 7) = 0 Then
                        hexColumn += 1
                    End If
                    If i + j >= bytesLength Then
                        line(hexColumn) = " "c
                        line(hexColumn + 1) = " "c
                        line(charColumn) = " "c
                    Else
                        'Dim b As Byte = bytes(i + j)
                        'line(hexColumn) = HexChars((b >> 4) And &HF)
                        'line(hexColumn + 1) = HexChars(b And &HF)
                        'line(charColumn) = (If(b < 32, "·"c, CChar(b)))
                        Dim b As Byte = bytes((i + j))
                        line(hexColumn) = HexChars(((b + 4) _
             And 15))
                        line((hexColumn + 1)) = HexChars((b And 15))
                        line(charColumn) = Microsoft.VisualBasic.ChrW(65533)

                    End If
                    hexColumn += 3
                    charColumn += 1
                Next
                result.Append(line)
                i += bytesPerLine
            End While
            Return result.ToString()
        End Function
    End Class
End Namespace




Public Class banks_financial
    Inherits System.Web.UI.Page

    Private _lang As String
    Private _registryId As Integer

    Private _salt As Byte() = Encoding.ASCII.GetBytes("o6806642kbM7c5")
    Private _sharedSecret As String = "abcd"

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        Dim original As String = "Here is some data to encrypt!"
        Dim key As Rfc2898DeriveBytes = New Rfc2898DeriveBytes(_sharedSecret, _salt)
        Dim encrypted As Byte()
        Try
            Using myAes As Aes = Aes.Create()
                myAes.Key = key.GetBytes(myAes.KeySize / 8)
                myAes.IV = key.GetBytes(myAes.BlockSize / 8)
                myAes.Padding = PaddingMode.PKCS7
                encrypted = EncryptStringToBytes_Aes(original, myAes.Key, myAes.IV)

                ReportError("myAes.Key in", HexDump.Utils.HexDump(myAes.Key))
                ReportError("myAes.IV in", HexDump.Utils.HexDump(myAes.IV))
                ReportError("encrypted in", HexDump.Utils.HexDump(encrypted))

            End Using
        Catch ex As Exception
            Console.WriteLine("Error: {0}", ex.Message)
        End Try
        'save to DB
        Dim myConnection As SqlConnection = GetConnection()
        Dim cmd As New SqlCommand("UPDATE banks set bank_name=@bankname WHERE id=1", myConnection)
        cmd.Parameters.Add(New SqlParameter("@bankname", encrypted))
        Try
            myConnection.Open()
            cmd.ExecuteScalar()
        Catch ex As Exception
            GlobalFunctions.ReportError("banks:INSERT encrypted", ex.Message, LogLevel.Normal)
        Finally
            myConnection.Close()
        End Try

        'retreive from db
        Dim decrypted As String = ""
        myConnection = GetConnection()
        cmd = New SqlCommand("SELECT bank_name FROM banks where id=1", myConnection)
        Dim reader As SqlDataReader
        Try
            myConnection.Open()
            reader = cmd.ExecuteReader
            If reader.Read Then
                Using myAes As Aes = Aes.Create()
                    myAes.Key = key.GetBytes(myAes.KeySize / 8)
                    myAes.IV = key.GetBytes(myAes.BlockSize / 8)
                    myAes.Padding = PaddingMode.PKCS7

                    ReportError("myAes.Key out", HexDump.Utils.HexDump(myAes.Key))
                    ReportError("myAes.IV out", HexDump.Utils.HexDump(myAes.IV))
                    ReportError("encrypted out", HexDump.Utils.HexDump(reader("bank_name")))
                    decrypted = DecryptStringFromBytes_Aes(reader("bank_name"), myAes.Key, myAes.IV)
                    ReportError("decrypted", decrypted)
                End Using
            Else
                GlobalFunctions.ReportError("banks:nothing to be read?!?", LogLevel.Normal)
            End If

        Catch ex As Exception
            GlobalFunctions.ReportError("banks:SELECT encrypted.", ex.Message, LogLevel.Normal)
        Finally
            myConnection.Close()
        End Try

        ltStatus.Text = GetMessageStatus(decrypted, MsgType.ok)

   



End Class

【问题讨论】:

  • 您将PBKDF2 用于PW 有什么原因吗?那些的盐应该不同,每种加密的 IV 也应该不同。一遍又一遍地使用相同的东西会破坏目的(破解一个,它们都被破解了)。使用不同的意味着将它们保存在某个地方。
  • 嗨,谢谢。我假设你的意思是PKCS7?不是真的,我在另一篇文章中看到了它的推荐。我可以完全省略填充还是你会推荐什么?此外,在现实生活场景中,每个用户的盐值会有所不同(可能是他们的用户 ID 或帐户创建日期)。
  • 不,Rfc2898DeriveBytes 用于基于密码的密钥派生功能 或 PBKDF。您构建它的方式。正确地做这意味着还为每个项目保存一个唯一的 IV 和盐。我试图弄清楚有多少试验和错误残余与实际需要。
  • 只有一个应用程序读取和写入数据库,还是其他位置的其他应用程序实例读取信息?
  • 确实只有一个应用程序读写数据库。

标签: asp.net vb.net encryption aes sql-server-2014-express


【解决方案1】:

直接的问题是您的测试代码误用了Rfc2898DeriveBytes/ PBKDF。它与数据库无关。每次在给定实例上调用 GetBytes() 时,它都会返回一组不同的字节(按设计!)。使用如下代码就可以明显看出这一点:

    Dim salt = CryptoTools.GetRandomBytes(16)
    Dim PBKDF = New Rfc2898DeriveBytes("secret", salt, 10000)

    Dim a = PBKDF.GetBytes(32)
    Dim b = PBKDF.GetBytes(32)
    Dim c = PBKDF.GetBytes(32)

    If a.SequenceEqual(b) = False Then
        Console.WriteLine("A != B")
    End If
    ...

Intellisense 非常清晰:

这些数组根本不一样。来自MSDN for GetBytes的备注部分:

Rfc2898DeriveBytes 类接受密码、盐和迭代计数,然后通过调用 GetBytes 方法生成 密钥重复调用此方法不会生成相同的密钥; ...

重点是我的,但旨在指出它是一个关键的生成器,而不仅仅是一个哈希器。因此,在您的“数据库失败”代码中,您有这个块:

Dim key As Rfc2898DeriveBytes = New Rfc2898DeriveBytes(_sharedSecret, _salt)
...
Using myAes As Aes = Aes.Create()
    ...
    myAes.Key = key.GetBytes(myAes.KeySize / 8)
    myAes.IV = key.GetBytes(myAes.BlockSize / 8)
    ...
End Using

生成的 Key 和 IV 不会相同。这很好,但可能不是您想要的。稍后使用相同的块来解密从数据库读回的内容。用于解密的 Key 和 IV 也会有所不同,但更重要的是它们不会是用于加密的相同 Key 和 IV!

这是一个致命的缺陷,会导致错误。这似乎是一个误导性的错误消息,但它很容易重现/修复。您需要一个新的 PBKDF 来在解密部分“重新开始”,或者 Reset 现有的:

' after this line...
Dim decrypted As String = ""
' ...add this:
key As Rfc2898DeriveBytes = New Rfc2898DeriveBytes(_sharedSecret, _salt)
' or
'key.Reset()

真正的代码更有可能不会以相同的方法进行往返,而是会创建一个新的Rfc2898DeriveBytes

最后,请注意 IV 和 Salt 应该是唯一且随机的,以便有效加密。除了安全的错觉之外,用相同的密码、密钥、盐和 IV 加密每一行确实没有多大意义。有人只需破解一行的 PW 即可获取所有行的数据。

因为Rfc2898DeriveBytes 是确定性的,它可以在生成IV 和Salt(用于要加密的数据)中发挥作用,而无需保存它们或使用静态的。

【讨论】:

  • @Flo 这就是答案。您不能在加密和解密操作中重复使用 Rfc2898DeriveBytes 的实例。已经指出了其他问题(例如生成 IV 的方式),但这解决了解密问题。
  • @Flo 在更新 2 的示例代码中,如果在解密操作之前添加 key.Reset() 会发生什么情况(可能在“从数据库中检索”注释之后)?
  • 是的,Reset 也可以工作,即使您没有使用默认的迭代次数。但是,通常情况下,读取代码的方法与写入方法不同,并且可能会创建一个新实例。我个人不会使用全局实例。
  • 如果解决了您的问题,请点击对勾并点赞。这是否安全取决于您要保护的内容。 Rfc2898DeriveBytes 的单个密码、固定的 Salt 和默认的迭代次数意味着您为每一行创建相同的 Key、IV 对。有些东西需要改变才能有效。并且通过密码/秘密和盐硬编码,如果被盗,答案就在代码中。
  • 假设有多个用户,这意味着UserA的每一行都会使用相同的加密Key和IV,这是不安全的。这可以通过使用Rfc2898DeriveBytes 的默认迭代以外的方法来解决;默认值为 1000,但更常见的是 10,000。如果您使用随机数,即使使用相同的用户 ID,您也可以为每一行设置不同的 Key/IV。 非常很难通过 cmets 定义正确的方法,因为我不知道应用程序的功能或您要保护的内容。
【解决方案2】:

通常,填充错误实际上意味着密钥、加密数据或选项不正确,而不是填充本身。

确保密钥的长度完全正确,否则实现将使用未指定的字节。使密钥完全正确的密钥大小:128、192 或 256 位(16、24 或 32 字节)。

确保密钥、数据和 IV 没有错误编码,加密基于 8 位字节,没有编码。有些库会接受并生成其他编码,例如 Base64 和 16 进制,请仔细查看文档。

十六进制转储密钥、IV、加密数据和解密数据,并将这些样本添加到问题中。

加密函数调用实际上很简单,如果您提供正确(和完整)的输入,输出将是正确的。如果结果仔细检查输入。

更新:
加密和解密的 IV 和密钥不相同,它们必须相同。

确保 IV 相同的一种方法是在加密和解密时将其预先附加到加密数据中,从加密数据中恢复它并在解密加密数据时跳过它。

密钥必须在解密之前以某种方式在加密和解密代码之间预先共享。

【讨论】:

  • 我在这个问题上加了赏金,也许你能帮忙?我看到你已经更新了你的答案。但我仍然不知道如何实现您的解决方案。我是加密新手,所以你可能有你建议的代码示例吗?我怎么知道密钥的长度是否正确?谢谢!
  • 哪部分不明白?
  • 为什么我的代码在涉及数据库的那一刻不起作用。
  • 最好的猜测是没有正确指定 db 字段。十六进制转储密钥、IV、原始数据、进入数据库的加密数据、来自数据库的加密数据和解密数据。十六进制转储的原因是原始数据可能不是您认为的那样,它可能是 UTF-16 编码或其他一些编码。 db 之前和之后的原因是为了确保 db 不会破坏它,可能是因为不适当的字段类型。关键是进入数据库的数据是否与出来的数据相同。你必须调试你的代码,上面是一个好的开始。
  • 谢谢!我用转储更新了我的代码(请参阅update 2 以获得完整的工作代码示例)(我希望我做对了)。它看起来确实好像值与输出值不同。您会推荐哪些后续步骤?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-12-26
  • 1970-01-01
  • 2016-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多