0
votes

Okay, so I am attempting to create a system whereby I serialize a dictionary, then encrypt it and then can decrypt it and then restore the dictionary.

It also includes some selection statements based on a setting whereby the user sets whether to always, never or prompt for encryption.

I have been attempting to follow along with the CryptoStream Class documentation, but for some reason this isn't working. I think the encryption might be working, but the file is a lot smaller than the none encrypted .ser equivalent so I don't know. The decryption generates an "Attempting to deserialize an empty stream." error, which is pretty self explanatory, but I can't work out how to fix it.

The backup and restore works fine without encryption.

Here are the relevant subroutines (and the GIT link if it's easier) and any help would be hugely appreciated! This is for an A Level computing project, so I'm not too fussed about the actual strength of the encryption (really don't want to start faffing around with hashing AND salting), just that it works.

Encrypt Backup:

Private Sub encryptBackup()
    Dim key As Byte()
    Dim IV As Byte() = New Byte() {}

    Using MD5 As New MD5CryptoServiceProvider
        Dim tmp = System.Text.Encoding.UTF8.GetBytes(InputBox("Please insert password:", "Password Input") & "This is most definitely not an obtuse amount of salt")
        key = MD5.ComputeHash(tmp)
        IV = MD5.ComputeHash(key)
    End Using
    Using cryptoRijndael = Rijndael.Create()

        Dim cryptoCryptor As ICryptoTransform = cryptoRijndael.CreateEncryptor(key, IV)
        Using fStream As New FileStream(fldBackupJobs & "\Backup Files\" & Strings.Replace(Strings.Replace(Now, ":", "_"), "/", ".") & ".bin", FileMode.OpenOrCreate), cStream As New CryptoStream(fStream, cryptoCryptor, CryptoStreamMode.Write)
            Dim Formatter As New BinaryFormatter
            Formatter.Serialize(cStream, photoJobs)
            MsgBox("Written to file")
        End Using
    End Using
End Sub

Decrypt Backup:

Private Sub decryptBackup(pathsStr As String)
    photoJobs = Nothing
    Dim key As Byte()
    Dim IV As Byte() = New Byte() {}
    Using MD5 As New MD5CryptoServiceProvider
        Dim tmp = System.Text.Encoding.UTF8.GetBytes(InputBox("Please insert password:", "Password Input") & "This is most definitely not an obtuse amount of salt")
        key = MD5.ComputeHash(tmp)
        IV = MD5.ComputeHash(key)
    End Using
    Using cryptoRijndael = Rijndael.Create()
        Dim cryptoCryptor As ICryptoTransform = cryptoRijndael.CreateEncryptor(key, IV)
        pathstr = OpenFileDialog.FileName
        Using fStream As New FileStream(pathstr, FileMode.Open), cStream As New CryptoStream(fStream, cryptoCryptor, CryptoStreamMode.Read)
            Dim Formatter As New BinaryFormatter
            photoJobs = CType(Formatter.Deserialize(cStream), Dictionary(Of String, PhotoJob))
            MsgBox("Backup Restored")
        End Using
    End Using
End Sub

And the GIT Link: https://github.com/hughesjs/Photo-Gift-Manager

Thanks In Advance!!

1

1 Answers

1
votes

The first part of the code gives me pause because (beyond GoTo) it looks like the crypto method is dependent upon the file extension. Since the user could change this via Explorer, it is very brittle. And dont let the user choose: if it is needs to be encrypted, do it; if not, don't. Certainly, the encryption method ought not be up to them (we get the big bucks to make those decisions for them).

Encrypting using a a BindingList(of Animal) which I happen to have handy:

Dim key As Byte()
Dim iv As Byte() = New Byte() {}

' see notes below
Using MD5 As New MD5CryptoServiceProvider
    ' UTF8 not unicode; convert password to Byte()
    Dim tmp = Encoding.UTF8.GetBytes(password & "$*^!#" & password)
    ' hash the PW to get the crypto Key
    key = MD5.ComputeHash(tmp)
    ' hash the Key to get the IV
    iv = MD5.ComputeHash(key)
End Using
    
Using rijAlg = Rijndael.Create()
      
    ' Create cryptor using the Key and IV
    Dim cryptor As ICryptoTransform = rijAlg.CreateEncryptor(key, IV)

    ' Open a filestream for the output file, wrap it with
    ' a CryptoStream created with the cryptor in WRITE (output) mode
    Using fs As New FileStream("C:\Temp\crypto.bin", FileMode.OpenOrCreate),
       cs As New CryptoStream(fs, cryptor, CryptoStreamMode.Write)

        ' serialize collection to CryptoStream (to disk)
        Dim bf As New BinaryFormatter
        bf.Serialize(cs, mcol)
    End Using

End Using

To decrypt, use the same Key and IV:

mcol = Nothing

' the comments above pertain, just in reverse
Using rijAlg = Rijndael.Create()
    Dim cryptor As ICryptoTransform = rijAlg.CreateDecryptor(key, iv)
    Using fs As New FileStream("C:\Temp\crypto.bin", FileMode.Open),
       cs As New CryptoStream(fs, cryptor, CryptoStreamMode.Read)

        Dim bf As New BinaryFormatter
        ' Convert object to type
        mcol = CType(bf.Deserialize(cs), BindingList(Of Animal))
    End Using
End Using

' test:
For Each a As Animal In mcol
    Console.WriteLine(a.Name)
Next

All my animals survived the trip:

Rover
Gizmo
Ziggy

The main thing appears to be that you have too many streams in use. In your decrypter you are trying to deserialize from a memstream which has nothing to do with the cryptostream that reads the file. In fact it was just created the line before. A cryptostream basically just wraps whatever "real" stream you are using.

Also, this line shows you are not using Option Strict:

photoJobs = formatter.Deserialize(memStreamSerial)

Deserialize returns an Object and photoJobs is some sort of collection IIRC from past posts. Note that my code uses CType to convert to BindingList(Of Animal).

Use Option Strict for anything more complex than 'Hello, World`. Always.


Crypto Notes / Cautions

Deriving (hashing) the IV from the password is a bad idea: these should be independent pieces of data. The IV should be unique for each piece of data (which will not be the case when a PW is reused) and unique. I added some arbitrary text to the PW so that the MD5 hash is not directly derived from teh PW, but it is still suboptimal.

Secondly, MD5 is outdated.

To create a random IV

Private Const MinSize = 7
Public Shared Function GetRandomBytes(size As Integer) As Byte()
    ' dont allow less than a sensible min
    Dim data(If(size < MinSize, MinSize, size)) As Byte

    Using rng As New RNGCryptoServiceProvider
        ' fill the array 
        rng.GetNonZeroBytes(data)
    End Using
    Return data

End Function

Put this in a crypto tools lib because you will use it for IV's as well as Salt for hashes. Example:

 myIV = CryptoUtils.GetRandomBytes(15)

If the IV is unique each time, the trick becomes how to save it so the same values can be used in decryption. The IV need not be secret, so it can be saved to the FileStream before you pass it to the CryptoStream constructor. The Decrypt method does the reverse.

These too can be made into methods so the same process is used each time.


Finally, your questions will get a better reception if they were more concise. The error clearly indicates a crypto problem so those first 2 blocks are more or less noise.