我在 PHP 中有一个函数可以解密我的 MP3 文件,这个函数运行得很好,但是当我将它翻译成 VB.NET 时,我没有得到相同的结果。
密钥、IV、密文均正常生成,与PHP生成的结果一致,但明文结果不同。
这是我的 PHP 代码
HIDDEN
这是我在 VB.net 中翻译的代码
HIDDEN
这是 PHP 第一个结果的一部分
fff320c4000818361800798209025d
这是我从代码中得到的结果的一部分
E39457922BB49015E76133D391D6CFB9C5
这是根据 Topaco 的建议我在 VB.Net 中的新代码
HIDDEN
返回什么:两种情况(PHP 和 VB.Net)中的密钥、IV 和密文都是正确且相等的,除了密文的最后一个块多了 16 位(块末尾的零)
另一个问题是,使用 PKCS7 时,我收到错误:填充无效且无法删除。
VB代码中存在以下错误:
Read()
调用填充了整个缓冲区。然而,这是不能保证的(s.Returns部分),因此可以读入缓冲区的数据太少。因此,必须以循环调用 Read()
的方式稳定实现,直到缓冲区完全填满或到达文件末尾。退出循环后读入缓冲区的数据数量被存储(numBytesRead
)。该修复在代码中标记为 Fix 2a。Read()
调用完全填满,最后一个块通常总是小于缓冲区大小,因此在读取整个缓冲区时会写入太多数据。因此,实现必须稳定,以便从缓冲区中读取的数据量与 (numBytesRead
) 中读取的数据量相同。该修复在代码中标记为 Fix 2b。完整代码:
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 标准(换句话说,一次加密整个明文会提供不同的密文)。这种逻辑有一个明显的缺点,即解密时必须知道块大小,以便可以识别和解密每个加密块。
要摆脱这个缺点,需要进行以下改变:
通过此更改,可以以独立于加密所用块大小的方式实现解密。
在这篇文章及其评论中,您将找到有关如何相应修改 PHP 代码的信息。如果您对此方法还有其他疑问,您应该提出一个新问题,因为这超出了旧问题的范围。
安全性:你的逻辑有一些漏洞。使用像 SHA-1 这样的快速摘要作为密钥派生函数是不好的。相反,应该应用可靠的密钥导出函数,例如 Argon2 或至少 PBKDF2。
此外,AES-256 应使用 32 字节密钥,其中每个单独的字节都是从 0 到 255 之间的值范围中随机确定的,并且最后 16 字节不包含 0x00 值。这最终将安全性从 256 位降低到 128 位。