Code for Calculating File Hash in a Standard module
PrivateConst HashTypeMD5 AsString="MD5"' https://msdn.microsoft.com/en-us/library/system.security.cryptography.md5cryptoserviceprovider(v=vs.110).aspxPrivateConst HashTypeSHA1 AsString="SHA1"' https://msdn.microsoft.com/en-us/library/system.security.cryptography.sha1cryptoserviceprovider(v=vs.110).aspxPrivateConst HashTypeSHA256 AsString="SHA256"' https://msdn.microsoft.com/en-us/library/system.security.cryptography.sha256cryptoserviceprovider(v=vs.110).aspxPrivateConst HashTypeSHA384 AsString="SHA384"' https://msdn.microsoft.com/en-us/library/system.security.cryptography.sha384cryptoserviceprovider(v=vs.110).aspxPrivateConst HashTypeSHA512 AsString="SHA512"' https://msdn.microsoft.com/en-us/library/system.security.cryptography.sha512cryptoserviceprovider(v=vs.110).aspxPrivate uFileSize AsDouble' Comment out if not testing performance by FileHashes()Sub FileHashes()Dim tStart AsDate, tFinish AsDate, sHash AsString, aTestFiles AsVariant, oTestFile AsVariant, aBlockSizes AsVariant, oBlockSize AsVariantDim BLOCKSIZE AsDouble' This performs performance testing on different file sizes and block sizes
aBlockSizes = Array("2^12-1","2^13-1","2^14-1","2^15-1","2^16-1","2^17-1","2^18-1","2^19-1","2^20-1","2^21-1","2^22-1","2^23-1","2^24-1","2^25-1","2^26-1")
aTestFiles = Array("C:\ISO\clonezilla-live-2.2.2-37-amd64.iso","C:\ISO\HPIP201.2014_0902.29.iso","C:\ISO\SW_DVD5_Windows_Vista_Business_W32_32BIT_English.ISO","C:\ISO\Win10_1607_English_x64.iso","C:\ISO\SW_DVD9_Windows_Svr_Std_and_DataCtr_2012_R2_64Bit_English.ISO")
Debug.Print "Test files: "& Join(aTestFiles," | ")
Debug.Print "BlockSizes: "& Join(aBlockSizes," | ")ForEach oTestFile In aTestFiles
Debug.Print oTestFile
ForEach oBlockSize In aBlockSizes
BLOCKSIZE = Evaluate(oBlockSize)
tStart = Now
sHash = GetFileHash(CStr(oTestFile), BLOCKSIZE, HashTypeMD5)
tFinish = Now
Debug.Print sHash, uFileSize, Format(tFinish - tStart,"hh:mm:ss"), oBlockSize &" ("& BLOCKSIZE &")"NextNextEndSubPrivateFunction GetFileHash(ByVal sFile AsString,ByVal uBlockSize AsDouble,ByVal sHashType AsString)AsStringDim oFSO AsObject' "Scripting.FileSystemObject"Dim oCSP AsObject' One of the "CryptoServiceProvider"Dim oRnd As Random ' "Random" Class by Microsoft, must be in the same fileDim uBytesRead AsDouble, uBytesToRead AsDouble, bDone AsBooleanDim aBlock()AsByte, aBytes AsVariant' Arrays to store bytesDim aHash()AsByte, sHash AsString, i AsLong'Dim uFileSize As Double ' Un-Comment if GetFileHash() is to be used individuallySet oRnd =New Random ' Class by Microsoft: RandomSet oFSO = CreateObject("Scripting.FileSystemObject")Set oCSP = CreateObject("System.Security.Cryptography."& sHashType &"CryptoServiceProvider")If oFSO IsNothingOr oRnd IsNothingOr oCSP IsNothingThen
MsgBox "One or more required objects cannot be created"GoTo CleanUp
EndIf
uFileSize = oFSO.GetFile(sFile).Size ' FILELEN() has 2GB max!
uBytesRead =0
bDone =False
sHash =String(oCSP.HashSize /4,"0")' Each hexadecimal has 4 bits
Application.ScreenUpdating =False' Process the file in chunks of uBlockSize or lessIf uFileSize =0ThenReDim aBlock(0)
oCSP.TransformFinalBlock aBlock,0,0
bDone =TrueElseWith oRnd
.OpenFile sFile
DoIf uBytesRead + uBlockSize < uFileSize Then
uBytesToRead = uBlockSize
Else
uBytesToRead = uFileSize - uBytesRead
bDone =TrueEndIf' Read in some bytes
aBytes =.ReadBytes(uBytesToRead)
aBlock = aBytes
If bDone Then
oCSP.TransformFinalBlock aBlock,0, uBytesToRead
uBytesRead = uBytesRead + uBytesToRead
Else
uBytesRead = uBytesRead + oCSP.TransformBlock(aBlock,0, uBytesToRead, aBlock,0)EndIf
DoEvents
LoopUntil bDone
.CloseFile
EndWithEndIfIf bDone Then' convert Hash byte array to an hexadecimal string
aHash = oCSP.hash
For i =0To UBound(aHash)
Mid$(sHash, i *2+(aHash(i)>15)+2)= Hex(aHash(i))NextEndIf
Application.ScreenUpdating =True' Clean up
oCSP.Clear
CleanUp:Set oFSO =NothingSet oRnd =NothingSet oCSP =Nothing
GetFileHash = sHash
EndFunction
The output is pretty interesting, my test files indicates that BLOCKSIZE = 131071 (2^17-1) gives overall best performance with 32bit Office 2010 on Windows 7 x64, next best is 2^16-1 (65535). Note 2^27-1 yields Out of memory.
Another variation from the code above gives you more performance when you want to get hash codes of all files from a root folder including all sub folders.
Option Explicit
PrivateConst HashTypeMD5 AsString="MD5"' https://msdn.microsoft.com/en-us/library/system.security.cryptography.md5cryptoserviceprovider(v=vs.110).aspxPrivateConst HashTypeSHA1 AsString="SHA1"' https://msdn.microsoft.com/en-us/library/system.security.cryptography.sha1cryptoserviceprovider(v=vs.110).aspxPrivateConst HashTypeSHA256 AsString="SHA256"' https://msdn.microsoft.com/en-us/library/system.security.cryptography.sha256cryptoserviceprovider(v=vs.110).aspxPrivateConst HashTypeSHA384 AsString="SHA384"' https://msdn.microsoft.com/en-us/library/system.security.cryptography.sha384cryptoserviceprovider(v=vs.110).aspxPrivateConst HashTypeSHA512 AsString="SHA512"' https://msdn.microsoft.com/en-us/library/system.security.cryptography.sha512cryptoserviceprovider(v=vs.110).aspxPrivateConst BLOCKSIZE AsDouble=131071' 2^17-1Private oFSO AsObjectPrivate oCSP AsObjectPrivate oRnd As Random ' Requires the Class from Microsoft https://support.microsoft.com/en-us/kb/189981Private sHashType AsStringPrivate sRootFDR AsStringPrivate oRng As Range
Private uFileCount AsDoubleSub AllFileHashes()' Active-X button calls thisDim oWS As Worksheet
' | A: FileHash | B: FileSize | C: FileName | D: FilaName and Path | E: File Last Modification Time | F: Time required to calculate has code (seconds)With ThisWorkbook
' Clear All old entries on all worksheetsForEach oWS In.Worksheets
Set oRng = Intersect(oWS.UsedRange, oWS.UsedRange.Offset(2))IfNot oRng IsNothingThen oRng.ClearContents
NextWith.Worksheets(1)
sHashType = Trim(.Range("A1").Value)' Range(A1)
sRootFDR = Trim(.Range("C1").Value)' Range(C1) Column B for file sizeIf Len(sHashType)=0Or Len(sRootFDR)=0ThenExitSubSet oRng =.Range("A3")' First entry on First PageEndWithEndWith
uFileCount =0If oRnd IsNothingThenSet oRnd =New Random ' Class by Microsoft: RandomIf oFSO IsNothingThenSet oFSO = CreateObject("Scripting.FileSystemObject")' Just to get correct FileSizeIf oCSP IsNothingThenSet oCSP = CreateObject("System.Security.Cryptography."& sHashType &"CryptoServiceProvider")
ProcessFolder oFSO.GetFolder(sRootFDR)
Application.StatusBar =False
Application.ScreenUpdating =True
oCSP.Clear
Set oCSP =NothingSet oRng =NothingSet oFSO =NothingSet oRnd =Nothing
Debug.Print "Total file count: "& uFileCount
EndSubPrivateSub ProcessFolder(ByRef oFDR AsObject)Dim oFile AsObject, oSubFDR AsObject, sHash AsString, dStart AsDate, dFinish AsDate
Application.ScreenUpdating =FalseForEach oFile In oFDR.Files
uFileCount = uFileCount +1
Application.StatusBar = uFileCount &": "& Right(oFile.Path,255- Len(uFileCount)-2)
oCSP.Initialize ' Reinitialize the CryptoServiceProvider
dStart = Now
sHash = GetFileHash(oFile, BLOCKSIZE, sHashType)
dFinish = Now
With oRng
.Value = sHash
.Offset(0,1).Value = oFile.Size ' File Size in bytes.Offset(0,2).Value = oFile.Name ' File name with extension.Offset(0,3).Value = oFile.Path ' Full File name and Path.Offset(0,4).Value = FileDateTime(oFile.Path)' Last modification timestamp of file.Offset(0,5).Value = dFinish - dStart ' Time required to calculate hash codeEndWithIf oRng.Row = Rows.Count Then' Max rows reached, start on Next sheetIf oRng.Worksheet.Index +1> ThisWorkbook.Worksheets.Count Then
MsgBox "All rows in all worksheets have been used, please create more sheets"EndEndIfSet oRng = ThisWorkbook.Sheets(oRng.Worksheet.Index +1).Range("A3")
oRng.Worksheet.Activate
Else' Move to next row otherwiseSet oRng = oRng.Offset(1)EndIfNext'Application.StatusBar = False
Application.ScreenUpdating =True
oRng.Activate
ForEach oSubFDR In oFDR.SubFolders
ProcessFolder oSubFDR
NextEndSubPrivateFunction GetFileHash(ByVal sFile AsString,ByVal uBlockSize AsDouble,ByVal sHashType AsString)AsStringDim uBytesRead AsDouble, uBytesToRead AsDouble, bDone AsBooleanDim aBlock()AsByte, aBytes AsVariant' Arrays to store bytesDim aHash()AsByte, sHash AsString, i AsLong, oTmp AsVariantDim uFileSize AsDouble' Un-Comment if GetFileHash() is to be used individuallyIf oRnd IsNothingThenSet oRnd =New Random ' Class by Microsoft: RandomIf oFSO IsNothingThenSet oFSO = CreateObject("Scripting.FileSystemObject")' Just to get correct FileSizeIf oCSP IsNothingThenSet oCSP = CreateObject("System.Security.Cryptography."& sHashType &"CryptoServiceProvider")If oFSO IsNothingOr oRnd IsNothingOr oCSP IsNothingThen
MsgBox "One or more required objects cannot be created"ExitFunctionEndIf
uFileSize = oFSO.GetFile(sFile).Size ' FILELEN() has 2GB max
uBytesRead =0
bDone =False
sHash =String(oCSP.HashSize /4,"0")' Each hexadecimal is 4 bits' Process the file in chunks of uBlockSize or lessIf uFileSize =0ThenReDim aBlock(0)
oCSP.TransformFinalBlock aBlock,0,0
bDone =TrueElseWith oRnd
OnErrorGoTo CannotOpenFile
.OpenFile sFile
DoIf uBytesRead + uBlockSize < uFileSize Then
uBytesToRead = uBlockSize
Else
uBytesToRead = uFileSize - uBytesRead
bDone =TrueEndIf' Read in some bytes
aBytes =.ReadBytes(uBytesToRead)
aBlock = aBytes
If bDone Then
oCSP.TransformFinalBlock aBlock,0, uBytesToRead
uBytesRead = uBytesRead + uBytesToRead
Else
uBytesRead = uBytesRead + oCSP.TransformBlock(aBlock,0, uBytesToRead, aBlock,0)EndIf
DoEvents
LoopUntil bDone
.CloseFile
CannotOpenFile:If Err.Number <>0Then' Change the hash code to the Error description
oTmp = Split(Err.Description, vbCrLf)
sHash = oTmp(1)&":"& oTmp(2)EndIfEndWithEndIfIf bDone Then' convert Hash byte array to an hexadecimal string
aHash = oCSP.hash
For i =0To UBound(aHash)
Mid$(sHash, i *2+(aHash(i)>15)+2)= Hex(aHash(i))NextEndIf
GetFileHash = sHash
EndFunction
Remarks
METHODS FOR THE CLASS BY MICROSOFT
Method Name
Description
IsOpen
Returns a boolean to indicate whether the file is open.
OpenFile(sFileName As String)
Opens the file specified by the sFileName argument.
CloseFile
Closes the currently open file.
ReadBytes(ByteCount As Long)
Reads ByteCount bytes and returns them in a Variant byte array and moves the pointer.
WriteBytes(DataBytes() As Byte)
Writes the contents of the byte array to the current position in the file and moves the pointer.
Flush
Forces Windows to flush the write cache.
SeekAbsolute(HighPos As Long, LowPos As Long)
Moves the file pointer to the designated position from the beginning of the file. Though VBA treats the DWORDS as signed values, the API treats them as unsigned. Make the high-order argument non-zero to exceed 4GB. The low-order DWORD will be negative for values between 2GB and 4GB.
SeekRelative(Offset As Long)
Moves the file pointer up to +/- 2GB from the current location. You can rewrite this method to allow for offsets greater than 2GB by converting a 64-bit signed offset into two 32-bit values.
PROPERTIES OF THE CLASS BY MICROSOFT
Property
Description
FileHandle
The file handle for the currently open file. This is not compatible with VBA file handles.
FileName
The name of the currently open file.
AutoFlush
Sets/indicates whether WriteBytes will automatically call the Flush method.
NORMAL MODULE
Function
Notes
GetFileHash(sFile As String, uBlockSize As Double, sHashType As String)
Simply throw in the full path to be hashed, Blocksize to use (number of bytes), and the type of Hash to use - one of the private constants: HashTypeMD5, HashTypeSHA1, HashTypeSHA256, HashTypeSHA384, HashTypeSHA512. This was designed to be as generic as possible.
You should un/comment the uFileSize As Double accordingly. I have tested MD5 and SHA1.