解密 AES 256 CBC

问题描述 投票:0回答:1

我在 PHP 中有一个函数可以解密我的 MP3 文件,这个函数运行得很好,但是当我将它翻译成 VB.NET 时,我没有得到相同的结果。

密钥、IV、密文均正常生成,与PHP生成的结果一致,但明文结果不同。

这是我的 PHP 代码

HIDDEN

这是我在 VB.net 中翻译的代码

HIDDEN

这是 PHP 第一个结果的一部分

fff320c4000818361800798209025d

这是我从代码中得到的结果的一部分

E39457922BB49015E76133D391D6CFB9C5

这是根据 Topaco 的建议我在 VB.Net 中的新代码

HIDDEN

返回什么:两种情况(PHP 和 VB.Net)中的密钥、IV 和密文都是正确且相等的,除了密文的最后一个块多了 16 位(块末尾的零)

另一个问题是,使用 PKCS7 时,我收到错误:填充无效且无法删除。

php vb.net encryption
1个回答
1
投票

VB代码中存在以下错误:

  • PHP/OpenSSL 默认使用 PKCS#7 填充。但是,当前 VB 代码中禁用了填充,这就是没有删除块的填充字节的原因。因此,必须在 VB 代码中启用填充(通过显式指定 PKCS#7 填充,或删除显式填充规范以便使用默认填充,即 PKCS#7)。该修复在代码中标记为 Fix 1
  • 读取密文数据时,当前代码假设
    Read()
    调用填充了整个缓冲区。然而,这是不能保证的(s.Returns部分),因此可以读入缓冲区的数据太少。因此,必须以循环调用
    Read()
    的方式稳定实现,直到缓冲区完全填满或到达文件末尾。退出循环后读入缓冲区的数据数量被存储(
    numBytesRead
    )。该修复在代码中标记为 Fix 2a
    另外,当前代码在写入数据时总是读取整个缓冲区。即使假设缓冲区将被
    Read()
    调用完全填满,最后一个块通常总是小于缓冲区大小,因此在读取整个缓冲区时会写入太多数据。因此,实现必须稳定,以便从缓冲区中读取的数据量与 (
    numBytesRead
    ) 中读取的数据量相同。该修复在代码中标记为 Fix 2b
  • 在 PHP 代码中,AES-256 与太短的 16 字节密钥结合使用。 PHP/OpenSSL 通过填充 0x00 值,从这个无效的 16 字节密钥中静默生成 32 字节密钥。这在 VB 代码中不会自动发生,必须显式实现。该修复在代码中标记为 Fix 3

完整代码:

Imports System.IO
Imports System.Security.Cryptography
Imports System.Text

...

Dim path_source As String = "C:\Teste\61.mp3.enc"
Dim path_dest As String = "C:\Teste\61.mp3"

Dim cryptKey As String = "0d8pkTOROILJBjV1gqxUpSc-Pk67l2Sw2qmooJaGujJ9_iiQ=" 'exemple my cryptkey
Dim key As Byte() = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(cryptKey)).Take(16).ToArray()
key = key.Concat(New Byte(32 - key.Length() - 1) {}).ToArray()  ' Fix 3


Using fpOut As FileStream = New FileStream(path_dest, FileMode.Create)
    Using fpIn As FileStream = New FileStream(path_source, FileMode.Open, FileAccess.Read)
        Dim iv As Byte() = New Byte(15) {}
        fpIn.Read(iv, 0, 16)

        Dim plaintext As Byte()
        While fpIn.Position < fpIn.Length
            Dim numBytesRead As Integer = 0
            Dim numBytesToRead As Integer = 160016
            Dim ciphertext As Byte() = New Byte(numBytesToRead - 1) {}
            While (numBytesToRead > 0)
                Dim n As Integer = fpIn.Read(ciphertext, numBytesRead, numBytesToRead) ' Fix 2a
                If (n = 0) Then
                    Exit While
                End If
                numBytesRead = (numBytesRead + n)
                numBytesToRead = (numBytesToRead - n)
            End While

            Try
                Using AES As RijndaelManaged = New RijndaelManaged()

                    AES.BlockSize = 128
                    AES.KeySize = 256
                    AES.Padding = PaddingMode.PKCS7 ' Fix 1
                    AES.Mode = CipherMode.CBC
                    AES.Key = key
                    AES.IV = iv
                    Console.WriteLine("AES IV:          " & BitConverter.ToString(AES.IV))
                    Dim decipher As ICryptoTransform = AES.CreateDecryptor(AES.Key, AES.IV)
                    Using ms As MemoryStream = New MemoryStream()
                        Using cs As CryptoStream = New CryptoStream(ms, decipher, CryptoStreamMode.Write)
                            cs.Write(ciphertext, 0, numBytesRead) ' Fix 2b
                            cs.FlushFinalBlock()
                        End Using
                        plaintext = ms.ToArray()
                    End Using
                End Using
                iv = ciphertext.Take(16).ToArray()
                fpOut.Write(plaintext, 0, plaintext.Length)
            Catch ex As Exception
                Console.WriteLine("Error: {0}", ex.Message)
            End Try
        End While
        Console.WriteLine("end of file")
    End Using
End Using

通过这些更改,使用 PHP/OpenSSL 加密的 (mp3) 文件可以在我的机器上使用 VB 代码成功解密。


当前的 PHP 代码以 CBC 模式使用 PKCS#7 填充来加密每个块,使用前一个块的第一个密文块作为加密块的 IV(在第一个块的情况下,使用实际的 IV) 。这意味着虽然每个单独的加密块都符合 CBC 标准,但加密块的串联却不符合 CBC 标准(换句话说,一次加密整个明文会提供不同的密文)。这种逻辑有一个明显的缺点,即解密时必须知道块大小,以便可以识别和解密每个加密块。

要摆脱这个缺点,需要进行以下改变:

  • 对除最后一个块之外的所有块禁用 PKCS#7 填充。
  • 前一个块的最后一个(不是第一个!)密文块将用作加密块的 IV(对于第一个块,实际的 IV 与之前一样使用)。

通过此更改,可以以独立于加密所用块大小的方式实现解密。

这篇文章及其评论中,您将找到有关如何相应修改 PHP 代码的信息。如果您对此方法还有其他疑问,您应该提出一个新问题,因为这超出了旧问题的范围。


安全性:你的逻辑有一些漏洞。使用像 SHA-1 这样的快速摘要作为密钥派生函数是不好的。相反,应该应用可靠的密钥导出函数,例如 Argon2 或至少 PBKDF2。
此外,AES-256 应使用 32 字节密钥,其中每个单独的字节都是从 0 到 255 之间的值范围中随机确定的,并且最后 16 字节不包含 0x00 值。这最终将安全性从 256 位降低到 128 位。

© www.soinside.com 2019 - 2024. All rights reserved.