|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Contents
IntroductionMost encryption tools are big, bulky things that store your password and act like safes - things that I didn't want. Also, I enjoy coding, so my natural inclination was to write my own Oh, and by the way, this is my first article - so play nice The AppCryptor uses the 256 bit Rijndael (otherwise known as AES, or the Advanced Encryption Standard) encryption to secure files. It does this using a The files used in the application are explained below:
All of the encryption routines happen in the aptly named Initialising The Encryption EngineThe Public Sub Initialise(ByVal sPWH As String)
'initialise rijM
rijM = New RijndaelManaged
'derive the key and IV using the
'PasswordDeriveBytes class
Dim pdb As PasswordDeriveBytes = _
New PasswordDeriveBytes(sPWH, _
New MD5CryptoServiceProvider().ComputeHash(ConvertStringToBytes(sPWH)))
'extract the key and IV
bKey = pdb.GetBytes(32)
bIV = pdb.GetBytes(16)
'initialise headerBytes
headerBytes = ConvertStringToBytes(headerString)
With rijM
.Key = bKey '256 bit key
.IV = bIV '128 bit IV
.BlockSize = 128 '128 bit BlockSize
.Padding = PaddingMode.PKCS7
End With
bInitialised = True
End Sub
The Main Encryption RoutineThe encryption process takes place in the function The following routine uses a 4 KB buffer when reading in the input file, and requires that the Public Function TransformFile(ByVal sInFile As String, _
ByVal sOutFile As String, _
Optional ByVal encrypt As Boolean = True) As Boolean
'make sure that all the initialisation has been completed:
If Not bInitialised Then
RaiseEvent Finished(ReturnType.Badly) : Return False
If Not IO.File.Exists(sInFile) Then
RaiseEvent Finished(ReturnType.Badly) : Return False
Dim fsIn As FileStream = Nothing
Dim fsOut As FileStream = Nothing
Dim encStream As CryptoStream = Nothing
Dim retVal As ReturnType = ReturnType.Badly
Try
'create the input and output streams:
fsIn = New FileStream(sInFile, FileMode.Open, _
FileAccess.Read)
fsOut = New FileStream(sOutFile, _
FileMode.Create, FileAccess.Write)
'some helper variables
Dim bBuffer(4096) As Byte '4KB buffer
Dim lBytesRead As Long = 0
Dim lFileSize As Long = fsIn.Length
Dim lBytesToWrite As Integer
If encrypt Then
encStream = New CryptoStream(fsOut, _
rijM.CreateEncryptor(bKey, bIV), _
CryptoStreamMode.Write)
'write the header to the output file
'for use when decrypting it
encStream.Write(headerBytes, 0, headerBytes.Length)
'this is the main encryption routine.
'it loops over the input data in blocks of 4KB,
'and writes the encrypted data to disk
Do
If bCancel Then Exit Try
lBytesToWrite = fsIn.Read(bBuffer, 0, 4096)
If lBytesToWrite = 0 Then Exit Do
encStream.Write(bBuffer, 0, lBytesToWrite)
lBytesRead += lBytesToWrite
RaiseEvent Progress(CInt((lBytesRead / lFileSize) * 100))
Loop
RaiseEvent Progress(100)
retVal = ReturnType.Well
Else
encStream = New CryptoStream(fsIn, _
rijM.CreateDecryptor(bKey, bIV), _
CryptoStreamMode.Read)
'read in the header
Dim test(headerBytes.Length) As Byte
encStream.Read(test, 0, headerBytes.Length)
'check to see if the file header reads correctly.
'if it doesn't, then close the stream & jump out
If ConvertBytesToString(test) <> headerString Then
encStream.Clear()
encStream = Nothing
retVal = ReturnType.IncorrectPassword
Exit Try
End If
'this is the main decryption routine.
'it loops over the input data in blocks of 4KB,
'and writes the decrypted data to disk
Do
If bCancel Then
'if the cancel flag is set,
'then jump out
encStream.Clear()
encStream = Nothing
Exit Try
End If
lBytesToWrite = encStream.Read(bBuffer, 0, 4096)
If lBytesToWrite = 0 Then Exit Do
fsOut.Write(bBuffer, 0, lBytesToWrite)
lBytesRead += lBytesToWrite
RaiseEvent Progress(CInt((lBytesRead / _
lFileSize) * 100))
Loop
RaiseEvent Progress(100)
retVal = ReturnType.Well
End If
Catch ex As Exception
Console.WriteLine("*****************ERROR*****************")
Console.WriteLine(ex.ToString)
Console.WriteLine("****************/ERROR*****************")
Finally
'close all I/O streams (encStream first)
If Not encStream Is Nothing Then
encStream.Close()
End If
If Not fsOut Is Nothing Then
fsOut.Close()
End If
If Not fsIn Is Nothing Then
fsIn.Close()
End If
End Try
'only delete the file if the password was bad, and
'therefore its only an empty file
If retVal = ReturnType.IncorrectPassword Then
IO.File.Delete(sOutFile)
End If
'raise the Finished event, and then reset bCancel
RaiseEvent Finished(retVal)
bCancel = False
End Function
Notes On PaddingThe Rijndael algorithm is a symmetric block cipher. This means that it encrypts input data in small blocks at a time. The size of each block is a variable which can be defined by editing the
Rijndael And AESWhen I say that Rijndael is AES, I'm actually lying. They are the same algorithm, however Rijndael has room for more block and key sizes. Rijndael supports key and block sizes of 128, 160, 192, 224, and 256 bits, whereas AES supports only one block size - 128 bits, and one of 128, 192, or 256 bit for the key size (256 bit is specified for use on documents classified as top secret - so it should be okay to use on your bank statements Quote from Wikipedia:
Using the codeThe encryption methods in the code are all contained in the
So, the easiest way to encrypt a file would be the following: Public Sub EncryptFile(ByVal pass As String)
Dim routines As New EncryptionRoutines()
routines.Initialise(pass)
routines.TransformFile(tbInputFile.Text, _
tbOutputFile.Text,True)
End Sub
Of course, you would probably need to improve on this code to enable threading and taking care of the Adding An Item To The Shell's Context MenuI wanted this application to be as easy to use as possible, so that even my mum could cope There are two options when adding to the shell's context menu:
Although dynamic menu items offer much more functionality, I didn't need any of it, and so I opted for the static route. As an example, the following explains how to set up a static context menu item that will appear whenever a user right clicks any file, that will allow them to open the file in Notepad:
To add "Encrypt File" and "Decrypt File" to the shell's context menu, I needed to add two sets of registry keys (one for encryption, the other for decryption). I created these:
These keys are added during installation using the MSI file linked to at the top of the page, so that the user doesn't need to. The 1 and 0 at the end of the Command key's default values indicate encryption and decryption respectively. An explanation of the necessary command line arguments can be found next... Command Line AccessIf you double click on Cryptor.exe, nothing will happen. It does not have a default form. It will only display a form if the command line arguments passed are of the format described below.
The following code loads the correct form based on the arguments. It is from <STAThread()> _
Sub Main(ByVal args() As String)
Application.EnableVisualStyles()
Application.DoEvents()
Select Case args.Length
Case 1
Dim frm As New frmSimpleFile(True, _
GetLongName(args(0)))
If frm.isOK Then
Application.Run(frm)
End If
Case 2
If Not IsNumeric(args(1)) Then
MessageBox.Show("Invalid input arguments: " & _
args(1) & " cannot be converted to an integer" & _
vbCrLf & vbCrLf & "Required Argument" & _
" format: Cryptor.exe [<file_to_use>" & _
" [<1 -> encrypting>]]", "Error",
MessageBoxButtons.OK, MessageBoxIcon.Error)
Return
End If
Dim frm As New frmSimpleFile(args(1) = 1, _
GetLongName(args(0)))
If frm.isOK Then
Application.Run(frm)
End If
End Select
End Sub
Converting Short To Long File NamesWhen the file name is passed to Cryptor through command line arguments, it is in its short form (DOS 8.3). So something that should be (long form): C:\Documents and Settings\Will\My Documents\Visual Studio Projects\network image.png
would get to Cryptor in the (short) form: C:\DOCUME~1\Will\MYDOCU~1\VISUAL~1\NETWOR~1.PNG
The only problem this causes on Win XP is that the title of In Win 98 however, the file name is not restored to the long form, so encrypting a file name of which is longer than 8 characters will cause it to be changed after encryption/decryption. This is both annoying and much more of a problem than it is in Win XP. For this reason, I needed to come up with a way to convert the file name passed in the command line arguments into its long form before passing it to The conversion uses the Win32 API Private Declare Function GetLongPathName Lib "kernel32" _
Alias "GetLongPathNameA" (ByVal lpszShortPath As String, _
ByVal lpszLongPath As String, _
ByVal cchBuffer As Integer) As Integer
Private Function GetLongName(ByVal sShortFileName _
As String) As String
Dim lRetVal As Long, _
sShortPathName As String, iLen As Integer
'Set up buffer area for API function call return
sShortPathName = Space(255)
iLen = Len(sShortPathName)
'Call the function
lRetVal = GetLongPathName(sShortFileName, _
sShortPathName, iLen)
'Strip away unwanted characters.
Return Left(sShortPathName, lRetVal)
End Function
So now, in the code in Points Of InterestThe application works by encrypting the given input file into a temporary output file (stored in the current user's local settings application data folder). It then deletes the original and moves the now encrypted file from the temporary directory to the location the original was kept. Whilst creating this program, I kept experiencing difficulties when it came to the actual encryption and decryption. I kept getting extra null bytes added onto the end of a decrypted file, and also getting exceptions like "Invalid PKCS7 padding". After an annoyingly long period of debugging and kicking things My original code in the Finally
If Not fsIn Is Nothing Then
fsIn.Flush()
fsIn.Close()
fsIn = Nothing
End If
If Not fsOut Is Nothing Then
fsOut.Flush()
fsOut.Close()
fsOut = Nothing
End If
'close the CryptoStream
If Not encStream Is Nothing Then
encStream.Close()
encStream = Nothing
End If
End Try
After changing that to the following, the padding problem was fixed. I think it was the order that was giving me problems - closing the underlying stream before closing Finally
If Not encStream Is Nothing Then
encStream.Close()
End If
If Not fsOut Is Nothing Then
fsOut.Close()
End If
If Not fsIn Is Nothing Then
fsIn.Close()
End If
End Try
As for the extra null bytes, that was an altogether more obvious problem. I wrote a reasonable amount of the encryption code ages ago, when I was just starting with VB. My routine for encrypting a file consisted of using a 4 KB buffer to read in the input - as it still does, however, when it reads in a file, it puts the data into a (nulled) 4 KB array each time it reads in. It then encrypted the entire array. Foolishly, I forgot to store the number of bytes actually read in, and so the To DoThe application works (in my opinion) pretty well as it is. There are, however, some items I would like to add at a later date:
History
DisclaimerThis program can be used to encrypt files. If you encrypt a file with this program, and then forget your password, there is no way you can get the file back - and it is not my fault! If the program crashes whilst encrypting/decrypting and you lose valuable data, then I apologize, but I will not accept any blame. THIS SOFTWARE IS PROVIDED "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. | ||||||||||||||||||||