加密实现仅损坏最后几个字节

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

下面是我编写的代码,用于使用用户提供的密码来实现加密任意数据。

Imports System.IO
Imports System.Security.Cryptography

Public Class PasswordCrytoSerializer
    Private Const SaltSizeBytes = 32
    Private Const IVSizeBytes = 16
    Private Const KeySizeBytes = 32
    Private Const HashSizeBytes = 32

    Public Property PBKDF2Iterations As Integer = 100000

    ''' <summary>
    ''' Encrypts and then saves binary data into a file
    ''' </summary>
    ''' <param name="fileContent">Array of bytes containing the file content</param>
    ''' <param name="password">Password to derive the encryption key from</param>
    ''' <param name="filePath">Path to save the encrypted data to</param>
    ''' <param name="cipherMode">Ciphermode to use for encryption. It Is <see cref="CipherMode.CBC"/> by default.</param>
    Public Sub EncryptToFile(fileContent As Byte(), password As String, filePath As String, Optional cipherMode As CipherMode = CipherMode.CBC)
        Using OutputFile As New FileStream(filePath, FileMode.Create)
            EncryptToStream(fileContent, password, OutputFile, cipherMode)
        End Using
    End Sub

    ''' <summary>
    ''' Encrypts and then returns the binary data in an array
    ''' </summary>
    ''' <param name="content">Array of bytes containing the data content to encrypt</param>
    ''' <param name="password">Password to derive the encryption key from</param>
    ''' <param name="cipherMode">Ciphermode to use for encryption. It Is <see cref="CipherMode.CBC"/> by default.</param>
    Public Function EncryptToMemory(content As Byte(), password As String, Optional cipherMode As CipherMode = CipherMode.CBC) As Byte()
        Using output As New MemoryStream
            EncryptToStream(content, password, output, cipherMode)
            Return output.ToArray()
        End Using
    End Function

    ''' <summary>
    ''' Encrypts and then saves binary data into a stream
    ''' </summary>
    ''' <param name="content">Array of bytes containing the data content to encrypt</param>
    ''' <param name="password">Password to derive the encryption key from</param>
    ''' <param name="outputStream">The stream to write the encrypted content into</param>
    ''' <param name="cipherMode">Ciphermode to use for encryption. It Is <see cref="CipherMode.CBC"/> by default.</param>
    Public Sub EncryptToStream(content As Byte(), password As String, outputStream As Stream, Optional cipherMode As CipherMode = CipherMode.CBC)
        Using AES = Security.Cryptography.Aes.Create()
            AES.Mode = cipherMode
            AES.Padding = 

            'Key salt is used to create a unique key from the user-supplied password
            Dim keySalt = GenerateRandomSalt()
            'Rfc2898 creates a key from user password and key salt (first 32 bytes are used)
            AES.Key = (New Rfc2898DeriveBytes(password, keySalt, PBKDF2Iterations, HashAlgorithmName.SHA256)).GetBytes(KeySizeBytes)

            'IV is used to create a unique encryted output even when given the same key
            AES.GenerateIV()

            'Hash salt is used to create a secure hash of the key
            Dim hashSalt = GenerateRandomSalt()
            'Rfc2898 creates a hash from the key using its own salt. These hashes can be compared to see if the key (and thus the base password) is correct without having to decrypt the content.
            Dim keyHash = (New Rfc2898DeriveBytes(AES.Key, hashSalt, PBKDF2Iterations, HashAlgorithmName.SHA256)).GetBytes(HashSizeBytes)


            'Save the key salt, IV, hash salt and hash (in that order) into the output before the encrypted data (they will be needed to unencrypt it)
            outputStream.Write(keySalt, 0, keySalt.Length)
            outputStream.Write(AES.IV, 0, AES.IV.Length)
            outputStream.Write(hashSalt, 0, hashSalt.Length)
            outputStream.Write(keyHash, 0, keyHash.Length)

            Using Encrypter As New CryptoStream(outputStream, AES.CreateEncryptor, CryptoStreamMode.Write)
                Encrypter.Write(content, 0, content.Length)
            End Using
        End Using
    End Sub

    ''' <summary>
    ''' Generates a random salt using <see cref="RandomNumberGenerator"/>
    ''' </summary>
    ''' <returns>An array of random bytes</returns>
    Private Function GenerateRandomSalt() As Byte()
        Dim salt(SaltSizeBytes - 1) As Byte

        Using random = RandomNumberGenerator.Create()
            random.GetBytes(salt)
        End Using

        Return salt
    End Function

    ''' <summary>
    ''' Decrypts a file that was encrypted using <see cref="EncryptToFile(Byte(), String, String, CipherMode)"/> and returns the decrypted content as binary data
    ''' </summary>
    ''' <param name="filePath">Path to the file to decrypt</param>
    ''' <param name="password">Password to derive the encryption key from</param>
    ''' <returns>Null if the given password is found to be incorrect, otherwise an array of bytes containing the decrypted data from the file</returns>
    Public Function DecryptFromFile(filePath As String, password As String) As Byte()
        Using Reader As New FileStream(filePath, FileMode.Open)
            Return DecryptFromStream(Reader, password)
        End Using
    End Function

    ''' <summary>
    ''' Decrypts a file that was encrypted using <see cref="EncryptToFile(Byte(), String, String, CipherMode)"/> and returns the decrypted content as binary data
    ''' </summary>
    ''' <param name="bytes">An array containing the binary data to decrypt</param>
    ''' <param name="password">Password to derive the encryption key from</param>
    ''' <returns>Null if the given password is found to be incorrect, otherwise an array of bytes containing the decrypted data from the file</returns>
    Public Function DecryptFromMemory(ByRef bytes As Byte(), password As String) As Byte()
        Using s As New MemoryStream(bytes)
            Return DecryptFromStream(s, password)
        End Using
    End Function

    ''' <summary>
    ''' Decrypts a file that was encrypted using <see cref="EncryptToStream(Byte(), String, Stream, CipherMode)"/> and returns the decrypted content as binary data
    ''' </summary>
    ''' <param name="encryptedSource">A stream containing the binary data to decrypt</param>
    ''' <param name="password">Password to derive the encryption key from</param>
    ''' <returns>Null if the given password is found to be incorrect, otherwise an array of bytes containing the decrypted data from the source</returns>
    Public Function DecryptFromStream(encryptedSource As Stream, password As String) As Byte()

        'Read key salt, IV, hash salt and key hash from begining of file
        Dim keySalt(SaltSizeBytes - 1) As Byte
        encryptedSource.Read(keySalt, 0, SaltSizeBytes)

        Dim iv(IVSizeBytes - 1) As Byte
        encryptedSource.Read(iv, 0, IVSizeBytes)

        Dim hashSalt(SaltSizeBytes - 1) As Byte
        encryptedSource.Read(hashSalt, 0, SaltSizeBytes)

        Dim keyHash(KeySizeBytes - 1) As Byte
        encryptedSource.Read(keyHash, 0, HashSizeBytes)

        Dim ContentLength = encryptedSource.Length - (SaltSizeBytes + IVSizeBytes + SaltSizeBytes + HashSizeBytes)
        Dim R(ContentLength) As Byte

        Using AES = Security.Cryptography.Aes.Create()
            'Calculate key using user-supplied password and key salt from file
            AES.Key = (New Rfc2898DeriveBytes(password, keySalt, PBKDF2Iterations, HashAlgorithmName.SHA256)).GetBytes(KeySizeBytes)
            AES.IV = iv

            'Generate a hash of the newly-generated key using the hash salt from the file
            Dim keyHashToCompare = (New Rfc2898DeriveBytes(AES.Key, hashSalt, PBKDF2Iterations, HashAlgorithmName.SHA256)).GetBytes(HashSizeBytes)
            'If the above hash does not match the hash from the file, the password given for decryption is not correct
            If Not keyHash.SequenceEqual(keyHashToCompare) Then Return Nothing

            'If we made it this far, the password is correct. Decrypt the data and return it.
            Using Decrypter As New CryptoStream(encryptedSource, AES.CreateDecryptor, CryptoStreamMode.Read)
                Decrypter.Read(R, 0, ContentLength)
                Return R
            End Using
        End Using
    End Function

End Class

我正在尝试加密一个 2,677 字节长的文件。生成的(加密的)文件大小为 2,800 字节。解密后,最终输出文件为 2,689,比初始文件长 12 个字节。

当对原始文件和解密文件进行二进制比较时,它们直到最后都是完全相同的。原始文件的最后五个字节似乎被替换为 17 个字节的

00

我怀疑存在某种填充问题,但我还无法弄清楚这一点。知道为什么会发生这种情况吗?

vb.net encryption cryptography aes rfc2898
1个回答
0
投票

在确定

ContentLength
中的
DecryptFromStream()
时,没有考虑PKCS#7的填充,因此缓冲区
R
比实际的明文要大。
要确定实际数据的大小(即没有填充字节),必须使用
Read()
的返回值(当前代码中未考虑)。然后可以使用该值,例如将数据从太大的缓冲区
R
复制到所需大小的缓冲区中。

由于这个重大变化,还必须考虑到

Read()
不能保证所请求的字节将被读取(仅保证至少读取1个字节,0标记流的末尾,s.here)。这可能是丢失 5 个字节的原因。
要从流中读取所有数据,必须循环填充
R
,直到到达流的末尾。或者(更方便),可以使用
MemoryStream
将数据读入
CopyTo()
(在这种情况下,不需要
R
ContentLength
),如以下代码片段所示:

...
Using MemStream As New MemoryStream
    Using Decrypter As New CryptoStream(encryptedSource, AES.CreateDecryptor, CryptoStreamMode.Read)
        Decrypter.CopyTo(MemStream)
    End Using
    Return MemStream.ToArray()
End Using
...
© www.soinside.com 2019 - 2024. All rights reserved.