-1

I have used this guide to encrypt the value of txtCode.Text into a text file, which is then hidden - The value of the encrypted data is going to be used as the password for the database, so I need to read this value, decrypt it, then pass it into the connection string.

However, in this guide, the decryption is done by typing in a value, creating a bytKey and bytIV and then comparing the result of encryption of the newl typed string with the value in the text file. Obviously I can't ask the user to enter the password every time the database needs to be opened, so how can I achieve decryption using this code?

The code which decrypts and encrypts the data is

Public Sub EncryptOrDecryptFile(ByVal strInputFile As String, ByVal strOutputFile As String, _
ByVal bytKey() As Byte, ByVal bytIV() As Byte, ByVal Direction As CryptoAction)

    Try
        fsInput = New System.IO.FileStream(strInputFile, FileMode.Open, FileAccess.Read)
        fsOutput = New System.IO.FileStream(strOutputFile, FileMode.OpenOrCreate, FileAccess.Write)
        fsOutput.SetLength(0)

        Dim bytBuffer(4096) As Byte
        Dim lngBytesProcessed As Long = 0
        Dim lngFileLength As Long = fsInput.Length
        Dim intBytesInCurrentBlock As Integer
        Dim csCryptoStream As CryptoStream

        Dim cspRijndael As New System.Security.Cryptography.RijndaelManaged

        Select Case Direction
            Case CryptoAction.ActionEncrypt
                csCryptoStream = New CryptoStream(fsOutput, _
                cspRijndael.CreateEncryptor(bytKey, bytIV), _
                CryptoStreamMode.Write)

            Case CryptoAction.ActionDecrypt
                csCryptoStream = New CryptoStream(fsOutput, _
                cspRijndael.CreateDecryptor(bytKey, bytIV), _
                CryptoStreamMode.Write)
        End Select

        While lngBytesProcessed < lngFileLength
            intBytesInCurrentBlock = fsInput.Read(bytBuffer, 0, 4096)

            csCryptoStream.Write(bytBuffer, 0, intBytesInCurrentBlock)

            lngBytesProcessed = lngBytesProcessed + _
                                    CLng(intBytesInCurrentBlock)
        End While

        csCryptoStream.Close()
        fsInput.Close()
        fsOutput.Close()

    Catch ex As Exception
        errorLog(ex)

    End Try
End Sub

I need to use this, or another, subroutine to read the file, decrypt the data and then store it in a variable to pass into the connection string - Can this be done? If so, how?

Harambe
  • 423
  • 3
  • 29
  • Thats not a very good crypto article/code. First, you can [encrypt/protect connection strings](https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/protecting-connection-information) thru the NET framework; if you want to do it yourself, you could encrypt the credentials, and save them as Base64 text to Settings; or still simpler than what you have, just use a cryptostream with the filestream you use. – Ňɏssa Pøngjǣrdenlarp Sep 04 '17 at 13:46
  • @Plutonix Do you have any examples of this? The user has requested that they can set the password themselves, so needs to encrypt the value in the `TextBox`. – Harambe Sep 04 '17 at 13:59
  • What DB is it? Little children like to play with fire, but that is not a good idea either. – Ňɏssa Pøngjǣrdenlarp Sep 04 '17 at 14:22
  • @Plutonix It's an MS-Access DB. I need some way of encrypting the database password, but it's proving trickier than I imagined. – Harambe Sep 04 '17 at 14:24
  • Giving users the DB PW is a bad idea. Giving *all* the users the PW is even worse. What happens when one changes the PW as they leave for vacation or is fired? What happens when your app crashes because they "fixed" something directly thru Access? – Ňɏssa Pøngjǣrdenlarp Sep 04 '17 at 15:08
  • @Plutonix The application is being developed for another company who are selling it on, moreorless as the distributor. They don't want a set password, they want each client to be able to set their own password when installing the system - What happens after that isn't really down to me, this is a specific request that they asked for, so I'm just keeping them happy. – Harambe Sep 04 '17 at 15:14

1 Answers1

2

After numerous attempts with that code of yours I decided to ditch it - it is simply old, ineffective and unsafe (as noted by Plutonix).

Here's a much more efficient piece of code where I've modified and improved an old class of mine - called Aes256Stream. It is derived from the CryptoStream and will handle the IV automatically for you.

'+---------------------------------------------------------------------------------+'
'|                               === Aes256Stream ===                              |'
'|                                                                                 |'
'|                  Created by Vincent "Visual Vincent" Bengtsson                  |'
'|                      Website: https://www.mydoomsite.com/                       |'
'|                                                                                 |'
'|                                                                                 |'
'|                            === COPYRIGHT LICENSE ===                            |'
'|                                                                                 |'
'| Copyright (c) 2016-2017, Vincent Bengtsson                                      |'
'| All rights reserved.                                                            |'
'|                                                                                 |'
'| Redistribution and use in source and binary forms, with or without              |'
'| modification, are permitted provided that the following conditions are met:     |'
'|                                                                                 |'
'| 1. Redistributions of source code must retain the above copyright notice, this  |'
'|    list of conditions and the following disclaimer.                             |'
'| 2. Redistributions in binary form must reproduce the above copyright notice,    |'
'|    this list of conditions and the following disclaimer in the documentation    |'
'|    and/or other materials provided with the distribution.                       |'
'|                                                                                 |'
'| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |'
'| ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED   |'
'| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE          |'
'| DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR |'
'| ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES  |'
'| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;    |'
'| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND     |'
'| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT      |'
'| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS   |'
'| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                    |'
'+---------------------------------------------------------------------------------+'

Imports System.IO
Imports System.Security.Cryptography
Imports System.Runtime.InteropServices

Public Class Aes256Stream
    Inherits CryptoStream

#Region "Fields"
    Private _underlyingStream As Stream
    Private AES As RijndaelManaged
    Private Transform As ICryptoTransform
#End Region

#Region "Properties"
    ''' <summary>
    ''' Gets the block size, in bits, used by the stream's AES algorithm.
    ''' </summary>
    ''' <remarks></remarks>
    Public ReadOnly Property BlockSize As Integer
        Get
            Return AES.BlockSize
        End Get
    End Property

    ''' <summary>
    ''' Gets the key size, in bits, used by the stream's AES algorithm.
    ''' </summary>
    ''' <remarks></remarks>
    Public ReadOnly Property KeySize As Integer
        Get
            Return AES.KeySize
        End Get
    End Property

    ''' <summary>
    ''' Gets the length in bytes of the underlying stream.
    ''' </summary>
    ''' <remarks></remarks>
    Public Overrides ReadOnly Property Length As Long
        Get
            Return _underlyingStream.Length
        End Get
    End Property

    ''' <summary>
    ''' Gets or sets the position within the underlying stream.
    ''' </summary>
    ''' <remarks></remarks>
    Public Overrides Property Position As Long
        Get
            If _underlyingStream.CanSeek = False Then Throw New NotSupportedException("The underlying stream doesn't support seeking!")
            Return _underlyingStream.Position
        End Get
        Set(value As Long)
            If _underlyingStream.CanSeek = False Then Throw New NotSupportedException("The underlying stream doesn't support seeking!")
            _underlyingStream.Position = value
        End Set
    End Property

    ''' <summary>
    ''' Gets the underlying stream.
    ''' </summary>
    ''' <remarks></remarks>
    Public ReadOnly Property UnderlyingStream As Stream
        Get
            Return _underlyingStream
        End Get
    End Property
#End Region

#Region "Constructors"
    Private Sub New(ByVal UnderlyingStream As Stream, _
                    ByVal AES As RijndaelManaged, _
                    ByVal CryptoTransform As ICryptoTransform, _
                    ByVal Mode As CryptoStreamMode)
        MyBase.New(UnderlyingStream, CryptoTransform, Mode)
        Me._underlyingStream = UnderlyingStream
        Me.AES = AES
        Me.Transform = CryptoTransform
    End Sub
#End Region

#Region "Methods"

#Region "Instance"
    ''' <summary>
    ''' Sets the length of the underlying stream.
    ''' </summary>
    ''' <param name="value">The desired length of  underlying stream in bytes.</param>
    ''' <remarks></remarks>the
    Public Overrides Sub SetLength(value As Long)
        _underlyingStream.SetLength(value)
    End Sub
#End Region

#Region "Shared"
    ''' <summary>
    ''' Creates an AES-256 encryption stream.
    ''' </summary>
    ''' <param name="UnderlyingStream">The underlying stream to write the encrypted data to.</param>
    ''' <param name="Key">The encryption key to use when encrypting the data (automatically padded or truncated to KeySize/8 bytes).</param>
    ''' <remarks></remarks>
    Public Shared Function CreateEncryptionStream(ByVal UnderlyingStream As Stream, ByVal Key As Byte()) As Aes256Stream
        Dim AES As New RijndaelManaged
        AES.KeySize = 256
        AES.BlockSize = 128

        AES.Key = Aes256Stream.PadOrTruncate(Key, AES.KeySize / 8)
        AES.GenerateIV()

        AES.Mode = CipherMode.CBC
        AES.Padding = PaddingMode.PKCS7

        'Write the IV to the underlying stream.
        If UnderlyingStream.CanWrite = True Then
            Dim LengthIV As Byte() = BitConverter.GetBytes(AES.IV.Length) 'Convert the IV.Length Integer to a byte array.
            UnderlyingStream.Write(LengthIV, 0, LengthIV.Length) 'Write the length of the IV.
            UnderlyingStream.Write(AES.IV, 0, AES.IV.Length) 'Write the IV.
        Else
            Throw New IOException("Underlying stream is not writable!")
        End If

        Return New Aes256Stream(UnderlyingStream, AES, AES.CreateEncryptor(), CryptoStreamMode.Write)
    End Function

    ''' <summary>
    ''' Creates an AES-256 decryption stream.
    ''' </summary>
    ''' <param name="UnderlyingStream">The underlying stream to decrypt the data from.</param>
    ''' <param name="Key">The encryption key to use when encrypting the data (automatically padded or truncated to KeySize/8 bytes).</param>
    ''' <remarks></remarks>
    Public Shared Function CreateDecryptionStream(ByVal UnderlyingStream As Stream, ByVal Key As Byte()) As Aes256Stream
        Dim AES As New RijndaelManaged
        AES.KeySize = 256
        AES.BlockSize = 128

        AES.Key = Aes256Stream.PadOrTruncate(Key, AES.KeySize / 8)

        'Read the IV from the underlying stream.
        If UnderlyingStream.CanRead = True Then
            Dim BytesReadIV As Integer = 0
            Dim BufferIV As Byte() = New Byte(Marshal.SizeOf(GetType(Integer)) - 1) {}
            Dim LengthIV As Integer

            'Read the IV's length.
            While BytesReadIV < BufferIV.Length
                Dim BytesRead As Integer = UnderlyingStream.Read(BufferIV, BytesReadIV, BufferIV.Length - BytesReadIV)
                BytesReadIV += BytesRead

                If BytesRead = 0 AndAlso BytesReadIV < BufferIV.Length Then _
                    Throw New IOException("End of stream reached before IV could be parsed!")
            End While

            'Convert the bytes to an Integer.
            LengthIV = BitConverter.ToInt32(BufferIV, 0)

            'Reset the variables.
            BytesReadIV = 0
            BufferIV = New Byte(LengthIV - 1) {}

            'Read the IV.
            While BytesReadIV < BufferIV.Length
                Dim BytesRead As Integer = UnderlyingStream.Read(BufferIV, BytesReadIV, BufferIV.Length - BytesReadIV)
                BytesReadIV += BytesRead

                If BytesRead = 0 AndAlso BytesReadIV < BufferIV.Length Then _
                    Throw New IOException("End of stream reached before IV could be parsed!")
            End While

            'Set the IV.
            AES.IV = BufferIV
        Else
            Throw New IOException("Underlying stream is not readable!")
        End If

        AES.Mode = CipherMode.CBC
        AES.Padding = PaddingMode.PKCS7

        Return New Aes256Stream(UnderlyingStream, AES, AES.CreateDecryptor(), CryptoStreamMode.Read)
    End Function

    Private Shared Function PadOrTruncate(ByVal Input As Byte(), ByVal PreferredLength As Integer) As Byte()
        If Input.Length < PreferredLength Then 'Pad with zeros.
            Dim PreviousLength As Integer = Input.Length

            Array.Resize(Input, Input.Length + (PreferredLength - Input.Length))
            For i = PreviousLength To Input.Length - 1
                Input(i) = 0
            Next

            Return Input

        ElseIf Input.Length > PreferredLength Then 'Truncate.
            Array.Resize(Input, PreferredLength)
            Return Input

        End If

        Return Input 'Do nothing.
    End Function
#End Region

#End Region

#Region "Dispose()"
    Protected Overrides Sub Dispose(disposing As Boolean)
        MyBase.Dispose(disposing)
        If disposing Then
            Try
                If Transform IsNot Nothing Then
                    Transform.Dispose()
                    Transform = Nothing
                End If
            Catch
            End Try

            Try
                If AES IsNot Nothing Then
                    AES.Dispose()
                    AES = Nothing
                End If
            Catch
            End Try
        End If
    End Sub
#End Region

End Class

The two main things to remember are:

  • CreateEncryptionStream() creates a write only stream for performing encryption.

  • CreateDecryptionStream() creates a read only stream for performing decryption.


Performing encryption

The simplest example of performing encryption only requires you to create a new Aes256Stream and write to it:

Using AesStream As Aes256Stream = Aes256Stream.CreateEncryptionStream(<output stream>, <encryption key>)
    AesStream.Write(<buffer>, <offset>, <length>)
End Using

Notes:

  • <output stream> is the stream to encrypt the data to (for instance a FileStream).

  • <encryption key> is the key (as a byte array) to use when encrypting the data.

  • <buffer>, <offset> and <length> behave just like when you write to a regular stream.


Performing decryption

Likewise the most simple example of performing decryption only requires you to create a new Aes256Stream and read from it:

Using AesStream As Aes256Stream = Aes256Stream.CreateDecryptionStream(<input stream>, <decryption key>)

    Dim DecryptedData As Byte() = New Byte(AesStream.Length - 1) {}
    AesStream.Read(DecryptedData, 0, DecryptedData.Length)

End Using

Notes:

  • <input stream> is the stream to decrypt data from (for instance a FileStream).

  • <decryption key> (same as <encryption key> above).

  • AesStream.Length is equal to calling <input stream>.Length (same goes for AesStream.Position and <input stream>.Position).


For your use

Here's how you can adapt your code to work with it...

Methods:

''' <summary>
''' Encrypts data to a file.
''' </summary>
''' <param name="File">The file to encrypt data to.</param>
''' <param name="Data">The data to encrypt.</param>
''' <param name="Key">The key to use to perform the encryption.</param>
''' <remarks></remarks>
Public Sub EncryptFile(ByVal File As String, ByVal Data As Byte(), ByVal Key As Byte())
    Using OutputStream As New FileStream(File, FileMode.Create, FileAccess.Write, FileShare.None)
        Using AesStream As Aes256Stream = Aes256Stream.CreateEncryptionStream(OutputStream, Key)
            AesStream.Write(Data, 0, Data.Length)
        End Using
    End Using
End Sub

''' <summary>
''' Decrypts a file and returns the decrypted data.
''' </summary>
''' <param name="File">The file to decrypt.</param>
''' <param name="Key">The key to use to perform the decryption.</param>
''' <remarks></remarks>
Public Function DecryptFile(ByVal File As String, ByVal Key As Byte()) As Byte()
    Using InputStream As New FileStream(File, FileMode.Open, FileAccess.Read, FileShare.Read)
        Using DecryptedStream As New MemoryStream
            Using AesStream As Aes256Stream = Aes256Stream.CreateDecryptionStream(InputStream, Key)

                Dim Buffer As Byte() = New Byte(4096 - 1) {}
                While AesStream.Position < AesStream.Length
                    Dim BytesRead As Integer = AesStream.Read(Buffer, 0, Buffer.Length)
                    DecryptedStream.Write(Buffer, 0, BytesRead)
                End While

                Return DecryptedStream.ToArray()

            End Using
        End Using
    End Using
End Function

Usage example:

Dim Key As Byte() = System.Text.Encoding.UTF8.GetBytes("verysecretpassword") 'If you actually use a password here do hash it first!

Dim TextToEncrypt As String = "Hello World! How are you?"
Dim DataToEncrypt As Byte() = System.Text.Encoding.UTF8.GetBytes(TextToEncrypt)

EncryptFile("encrypted.txt", DataToEncrypt, Key)

Dim DecryptedData As Byte() = DecryptFile("encrypted.txt", Key)
Dim DecryptedText As String = System.Text.Encoding.UTF8.GetString(DecryptedData)


Online test

An online testable version of this code can be found here: https://dotnetfiddle.net/PXaJF8

Visual Vincent
  • 18,045
  • 5
  • 28
  • 75
  • 1
    The general practice is to write the IV to the (raw) filestream so that the Decryptor can use the same one without having to recreate it, store it or use a static one. It is one of the crypto-related failings of that article. It would also be simpler to serialize a class using the cryptostream. – Ňɏssa Pøngjǣrdenlarp Sep 04 '17 at 14:48
  • @Plutonix : Based on the [**OP's previous question**](https://stackoverflow.com/q/45997750) he creates the IV from the same string as the password. – Visual Vincent Sep 04 '17 at 14:53
  • 3
    Yeah, thats even worse. The IV should be a set of random bytes not derived from the PW, thats why you'd want to save it to the stream. – Ňɏssa Pøngjǣrdenlarp Sep 04 '17 at 14:54
  • @Plutonix : I'm no cryptography expert... Could you please explain how storing a random IV in the file is safer than deriving one from the password and not storing it at all? (Of course I understand that the latter isn't very safe, but I want to know how the former is) – Visual Vincent Sep 04 '17 at 14:58
  • 1
    If it is based on something known, it is determinative not random and the IV is supposed to be random. When written to the file, it also means the file must be read a certain way and thus a little harder to decipher even if you have the PW (super simplified - I am not a cryptographer). I used to have a cute little file encryptor class but of course I cannot find it now – Ňɏssa Pøngjǣrdenlarp Sep 04 '17 at 15:02
  • @Plutonix : Thanks for explaining! I cannot test the code since I have no access to a computer, but I believe I can implement the in-the-file-IV if you can answer me this: Does the `CryptoStream` always start at position 0, or at `fsInput.Position`? – Visual Vincent Sep 04 '17 at 15:11
  • @Plutonix : The rest I was aware of, I was wondering so that the code doesn't try to decrypt the IV as well after reading it with `fsInput`. Thanks! :) – Visual Vincent Sep 04 '17 at 15:15
  • So, before opening the connection to the database on the log on screen, I've put the following: `Dim password As String = DecryptFile(myPath/myFile.txt, encryptionKey, encryptionIV)` - But the latter two variables have not been set or assigned a value - Where would this be done? – Harambe Sep 04 '17 at 15:18
  • @Harambe : At this point those would be your `bytKey` and `bytIV` variables, though I'm working on updating the answer to adapt to [**Plutonix's comment**](https://stackoverflow.com/questions/46036071/decrypting-a-text-file-using-rjindael/46039488?noredirect=1#comment79039335_46039488). – Visual Vincent Sep 04 '17 at 15:24
  • Those don't get saved anywhere, which is the issue that I'm having - Perhaps I should be storing these in the text file along with the password string? – Harambe Sep 04 '17 at 15:26
  • @Harambe : You must store them at least once, whether you need to require the password the first time you start the program, every time you want to access the DB or just by hardcoding it (unsafe). Otherwise you have nothing to decrypt with. The IV can be stored in the file and read using Plutonix's code or the one I'll be posting any minute now. – Visual Vincent Sep 04 '17 at 15:41
  • So when I'm encrypting the password, how do I append the Key and IV bytes to the file? It currently writes using `csCryptoStream = New CryptoStream(fsOutput, cspRijndael.CreateEncryptor(bytKey, bytIV), CryptoStreamMode.Write)`, but I'm not sure where in there I can modify it to append the variables - Which would then need to be read using `File.ReadAllBytes()`? – Harambe Sep 04 '17 at 16:03
  • You **do not** append the key. If you are doing that you might as well not encrypt it. The 2nd code block stores the IV for you. You also do not use `ReadAllBytes`. If it is encrypted, you need to read it back thru another CryptoStream – Ňɏssa Pøngjǣrdenlarp Sep 04 '17 at 16:04
  • @Harambe : Updated my answer. I decided to write a better encryption code (which also handles the IV for you!). – Visual Vincent Sep 04 '17 at 22:26
  • @Plutonix : When you specify a block size of 128 I think the algorithms are pretty much the same. Also, I added an online test. -- **EDIT:** There, now it is _**definitely**_ AES. ;) – Visual Vincent Sep 04 '17 at 23:00
  • @Plutonix : Lol, I was blinded by trying to get this to work at all. Fixed by a rollback. -- Can you point me somewhere where I can see the actual differences? Because I haven't been able to find anything saying that Rij would be the better one (where the block size is 128). – Visual Vincent Sep 04 '17 at 23:06
  • @Plutonix : Turns out it doesn't make any difference in this case. The `AesManaged` class uses [**`RijndaelManaged` as its base**](http://referencesource.microsoft.com/#System.Core/System/Security/Cryptography/AesManaged.cs,5168289df5ba28cf), so my code is using both Rijndael and AES because in this case Rijndael == AES. :) – Visual Vincent Sep 04 '17 at 23:11
  • In your example usage for my code, would the chosen password replace `"yourverysecretpassword"`, or would it replace `"Hello World! How are you?"`If it's the first one, what would then replace the second one, or vice versa? – Harambe Sep 05 '17 at 08:44
  • @Harambe : `verysecretpassword` is the key **to encrypt the data with**. `Hello World! ...` is _**what**_ you want to encrypt (what you want to store in the file that no one should see). – Visual Vincent Sep 05 '17 at 09:12
  • @VisualVincent I see, and then to decrypt it, I'd use the same key? I'll give it a go now, but seems like it should all work. – Harambe Sep 05 '17 at 09:18
  • @Harambe : Exactly. You can check out my online test in the bottom of my answer. – Visual Vincent Sep 05 '17 at 09:22
  • I'll need to hard-code the password for the key, right? Unless I generated a random string and stored it in a file? – Harambe Sep 05 '17 at 09:26
  • 1
    @Harambe : Hard coding is bad - but yes, unless asking the user for the password (key) when your program starts is an option for you. – Visual Vincent Sep 05 '17 at 09:37
  • @VisualVincent One last thing - After I've decrypted the file to get the password, when trying to overwrite it I get `Access to the path 'xyz' is denied`? This is after `If File.Exists(directorypath & "dbpw.txt") Then Dim DecryptedData As Byte() = DecryptFile(directorypath & "dbpw.txt", Key) DecryptedText = System.Text.Encoding.UTF8.GetString(DecryptedData) End If` – Harambe Sep 05 '17 at 11:19
  • @Harambe : Are you opening any additional streams to the file, or have you modified anything in my `Encrypt-/DecryptFile` methods? You can use [**Process Explorer**](https://serverfault.com/a/1980) to view all open handles to your file to see if anything is locking it. -- Also try running your app with administrator privileges. – Visual Vincent Sep 05 '17 at 11:29