Introduction
A File Checksum is used to verify the integrity of a file. Checksums can detect
both transmission errors and tampering. To detect transmission errors, we would
use a
CRC
(cyclic redundancy check) [1]. To detect tampering, we generally choose a cryptographic
Hash due
to the desirable property of Collision Resistance [2].
This article presents two Shell Extension DLLs which can be used to create file
checksums and verify file checksums. The article is based on Michael Dunn's
Guide to Writing Shell Extensions,
Part I [4], Tom Archer's
Using the Clipboard, Part I: Transferring Simple Text [5], and
Crypto++. This article will
discuss the following topics:
Microsoft offers a command line file checksum tool published under Knowledge
Base article
Q841290, Availability and
Description of the File Checksum Integrity Verifier Utility [6]. The Microsoft
tool supports MD5 and SHA1.
Shell Extension
DLL Usage
Should the reader desire to use the Extension DLLs, he or she should download
and install as described below. Once the DLLs are registered, they are available
as Context Menus when one right clicks a file (or multiple files) in Windows Explorer.
- Download the DLL
- CtxCreateHashDll.zip
- CtxVerifyHashDll.zip
- Unpack to C:\Windows\System\
- Register the DLL
regsvr32.exe C:\Windows\System\CtxCreateHash.Dll
regsvr32.exe C:\Windows\System\CtxVerifyHash.Dll
Windows Vista and User Account Control
To manually run regsvr32.exe under Vista with User Account Control enabled, navigate
to the Command Prompt from the Start menu. Right click the Command Prompt and chose
Run As Administrator as shown in Figure 1.
|
Figure 1: Run As Administrator
|
Neglecting to run regsvr32 with elevated privileges will usually result in the
error, "DllRegisterServer failed with error code 0x80070005," while success is the
familiar succeeded shown below.
|
Figure 2: DllRegisterServer Success
|
Visual C++ Runtime
The latest versions of the compiled DLLs are built and linked with Visual Studio
2008. If required, download the
Microsoft Visual C++ 2008 Redistributable Package. A classic example of requiring
the package is regsvr32 failing to register the DLLs with the error, "This application
has failed to start because the application configuration is incorrect. Reinstalling
the application may fix this problem"
Checksum Creation
After selecting a files or files, if the user selects Create Checksum,
the following hashes are created and placed on the clipboard:
- MD5
- RIPE MD-128, RIPE MD-160, RIPE MD-256, RIPE MD-320
- SHA-1, SHA-224, SHA-256, SHA-384, SHA-512
- Whirlpool
A Message Box is then displayed with the digest of the hashed file or files.
A truncated version is displayed to keep the message box size manageable.
|
Figure 3: Truncated Message Box
|
The full checksums are placed on the Windows Clipboard for pasting. Pasting into
Notepad from the clipboard reveals the full text.
|
Figure 4: Clipboard Text
|
Checksum Verification
Verifying a file's checksum is equally trivial. Navigate to the web page or document
where the checksum resides, highlight, and copy to the clipboard.
|
Figure 5: Copy Checksums to the
Clipboard
|
Navigate to the files, select, right click, and chose Verify Checksum.
|
Figure 6: Verify Checksums
|
Supported verification algorithms are:
- CRC32
- MD2, MD4, MD5
- RIPE MD-128, RIPE MD-160, RIPE MD-256, RIPE MD-320
- SHA-1, SHA-224, SHA-256, SHA-384, SHA-512
- Whirlpool
A message box will be presented similar to below. The message box will group
files in two categories: verified and unverified. A verified file will display the
message "Verified Checksum" with a digest of the checksum. An unverified file will
under the heading "Unverified Checksum"
|
Figure 7: Verify Checksum Message
Box
|
When verifying, the DLL searches for matching hash values in order from strongest
to weakest (SHA-512 to CRC32). The match algorithm terminates on a first match,
so only the strongest hash is displayed.
Compiling and Integrating Crypto++
The samples provided
use various Crypto++ Symmetric Ciphers. Crypto++ can be downloaded from Wei Dai's
Crypto++ page. For compilation and integration issues, visit
Integrating Crypto++ into the Microsoft Visual C++ Environment. This article
is based upon basic assumptions presented in the previously mentioned article. For
those who are interested in other C++ Cryptographic libraries, please see Peter
Gutmann's Cryptlib
or Victor Shoup's NTL.
Windows Clipboard APIs
Please see Tom Archer's
Using the Clipboard, Part I: Transferring Simple Text for an in depth discussion
on the subject. Sample 1 demonstrates enumerating the Clipboard's data formats.
Sample 2 demonstrates reading text from the Clipboard. Finally, sample 3 demonstrates
writing Unicode text to the Clipboard.
|
Figure 8: Writing Clipboard Text
|
Creating a Shell Context Menu Extension DLL
Creating a shell context menu is based extensively on Michael Dunn's
Guide to Writing Shell Extensions,
Part I. Please refer to Michael's article for the discussion. Michael's
techniques works well for inserting a single menu item, or multiple menu items for
Windows 2000.
Composition
Unlike Michael's article, we will have to use a slightly different technique
when we call InsertMenu
on Windows XP, Server 2003, and Vista. This is
due to Composition. Composition is a technique that Windows XP and above use to
group context menu items together. In Figure 9 we see two examples of composition.
Adobe is providing two DLLs which are grouped together, and the checksum DLLs are
being grouped together.
|
Figure 9: Composition
|
For a discussion of Composition, see Raymond Chen's series on
The Old New Thing.
The two chapters of interest are
Chapter 10: Composite Extensions - Groundwork and
Chapter 11: Composite Extensions - Composition. Keep in mind that Chen's
sample code is presented from the shell's perspective rather than our perspective.
Due to the side effects of composition in Windows XP, Server 2003, and Vista, we
must insert menu items as follows:
InsertMenu( hmenu, uMenuIndex, MF_STRING | MF_BYPOSITION,
uidFirstCmd+1, _T("Create Checksums") );
return MAKE_HRESULT( SEVERITY_SUCCESS, FACILITY_NULL, 2 );
If we added two menu items, our routine would be as follows:
InsertMenu( hmenu, uMenuIndex, MF_STRING | MF_BYPOSITION,
uidFirstCmd+1, _T("Item 1") );
InsertMenu( hmenu, uMenuIndex, MF_STRING | MF_BYPOSITION,
uidFirstCmd+2, _T("Item 2") );
return MAKE_HRESULT( SEVERITY_SUCCESS, FACILITY_NULL, 3 );
The shell's algorithm which determines what items should be composted (and how
they are arranged) is not available. But it appears that it depends on:
- Registry string name of the DLL
- Number of leading characters which match in a string compare
It appears the algorithm resides somewhere in the Shell's Shell32!_imp__DSA_InsertItem
function. The rule of thumb observed is if two or more characters match in the registry
string, the items will be composted.
Crypto++ ChannelSwitch Class
Retrieving from the disk multiple times can become a bottleneck for performance.
This is especially true if we are reading the same file continuously. To overcome
the multiple disk read issue, one can use the Crypto++ ChannelSwitch
class. The ChannelSwitch
class allows a user to stream data to multiple
HashFilter
based objects after a single disk read. More correctly,
one can push data to any BufferedTransformation
derived object. This
is trivial when performing string (in memory) based operations; however disk based
operations tax response times. One would not want to read a 1 MB file 6 times to
send it to 6 different consumer objects. Though one could author code to perform
the buffering and delegation, the simplest solution is the ChannelSwitch
.
|
Figure 10: ChannelSwitch Overview
|
ChannelSwitch
offers
AddDefaultRoute(BufferedTransformation&
destination)
function to add a destination.
HashFilter
derives
from
BufferedTransformation:
|
Figure 11: HashFilter Inheritance
|
The abbreviated use of the ChannelSwitch
object in the Create Checksum
Shell Extension DLL is depicted below.
|
Figure 12: CreateChecksum DLL ChannelSwitch
|
An abridged use of the ChannelSwitch
object in the Verify Checksum
Shell Extension DLL is depicted below.
|
Figure 13: VerifyChecksum DLL ChannelSwitch
|
The code to accomplish multiple hashing based on a ChannelSwitch
is shown below. Two hashes are created: MD5 and SHA-1. Wei provides the sample code
in test.cpp of the Crypto++ library.
#include "channels.h" // ChannelSwitch
#include "filters.h" // HashFilter
#include "hex.h" // HexEncoder
#include "md5.h" // MD5
#include "sha.h" // SHA-1, SHA-256, SHA-512
int main( )
{
MD5 hashMD5;
SHA1 hashSHA1;
HashFilter filterMD5(hashMD5);
HashFilter filterSHA1(hashSHA1);
std::auto_ptr<ChannelSwitch> channel(new ChannelSwitch );
channel->AddDefaultRoute(filterMD5);
channel->AddDefaultRoute(filterSHA1);
...
}
Earlier, fciv.exe (KB
841290) was mentioned which creates and verifies MD5 and SHA-1 checksums. By
changing the StringSource
to a FileSource
, the reader
could have a new and improved fciv.exe less the XML encoding.
The output of the Sample 4 (using MD5, SHA-1, RIPEMD-160, and SHA-256) is shown
below.
|
Figure 14: Sample 4
|
Command Line Debugging
If debugging using Visual Studio is too difficult or inconvenient, cdb.exe can
be used. It is the command line equivalent of WinDbg. CDB can be downloaded from
Microsoft in the
Debugging
Tools for Windows package. Some of the more useful commands for this exercise
are shown below.
Attaching to Explorer
To attach to Windows Explorer, issue cdb -pid < pid>
, where
pid
is the process identification number of Explorer. Once attached, Explorer
will be suspended since the debugger is awaiting input. Enter
g
to
run.
|
Figure 15: Attach To Explorer
|
When Explorer is running, enter CTRL-C to break back into the debugger.
Once finished in the debugger, press g
again. Finally, ALT-Tab
does work with Explorer suspended. It will be useful in getting the debugger window
forward when Explorer cannot repaint (and is hiding a view).
Frozen Explorer or Debugger
This section is reiterated from above since it is so important. If Explorer is
frozen when attempting to work on the desktop, type g
in the debugger.
If the debugger is not the windows with focus, use ALT-Tab to bring the
debugger forward. Finally, if you cannot enter commands into the debugger, enter
CTRL-C to break.
Loaded Modules
Once attached to Explorer, we need to know if our DLL is loaded so we can determine
the breakpoint location and set the breakpoint. We can issue a load (ld
)
specify a full path name, of right click a file to coax Explorer into loading it.
Breakpoints
Issuing x ctx*!*hash*
will display symbol locations the two DLLs.
The final expression (*hash*
) refines the results. This is too much
information, since Crypto++ is full of symbols such as these.
To further refine the location search, issue x ctx*!*querycontextmenu
.
Now that the location is known, issue the breakpoint. Exercise Explorer. bl
lists breakpoints. When the breakpoint fires, enter p
once to step out
of DebugBreak()
and into the function. To step into a function, enter
t
.
|
Figure 16: Locating Functions of
Interest
|
Managing an Unstable Windows Explorer
This portion of the article is again based on Michael Dunn's
Guide to Writing Shell Extensions, Part I. Should the reader find the treatment
too superficial, please refer the Michael's article. Michael details topics such
as preparing the Windows environment for debugging Windows Explorer to allowing
the Context Menu DLL to execute in light of Group Policy.
Before debugging, kill the Explorer process. When prompted for a Debug Executable,
specify C:\Windows\Explorer.exe. When debugging begins, Visual Studio will
start an instance of Explorer.
|
Figure 17: explorer.exe
|
Save your work frequently. Most problems only required restarting Explorer, or
an occasional soft reboot. To restart Explorer, open Task Manager and End the Explorer
process.
|
Figure 18: End Explorer Process
|
Next, switch to the Task tab, and click New Task.
|
Figure 19: New explorer.exe Task
|
If the reader builds the DLL, but the DLL cannot be opened to be written during
linking, verify no instances of Explorer.exe are running. If no instances are present,
restart Explorer.
|
Figure 20: Explorer Instances
|
Character Set Considerations
The Context Menu DLLs are written using wide characters since the NT family is
dominating the landscape. The Crypto++ library is narrow, and the Windows API can
be either.
|
Figure 21: Checksum DLL Design Overview
|
Data transfer with respect to Crypto++ is generally from Crypto++ to the DLL.
To facilitate the transfer, the DLL calls StringWiden()
on the narrow
data from Crypto++.
The same is not true for the Windows API. If _UNICODE
and
UNICODE
are defined, no conversion takes place. When using
SBCS
and
MBCS
,
StringWiden()
and
StringNarrow()
are employed when moving strings between Windows and the DLL.
The conversions will be handled by way of the standard c++ library's widen()
and narrow()
. Those using Visual C++ 7.0 and above have more flexibility
in use of standard library conversion routines.
std::wstring StringWiden( const std::string& narrow ) {
std::wstring wide;
wide.resize( narrow.length() );
typedef std::ctype<wchar_t> CT;
CT const& ct = std::_USE(std::locale(), CT);
ct.widen(narrow.data(), narrow.data() + narrow.size(), wide.data());
return wide;
}
Create and Verify Routines
Sample 4 (Channel Switch) and Sample 5 (CtxTest
) provides most of
the code required for the DLL. This section will discuss the remaining items of
interest. The create and verify routines share common code. This would include populating
the list of files and the Fly By help.
Initialize
Initialize()
has been expanded as follows. Notes that files
is a vector of wide string
. Once all of the files have been extracted,
the vector is sorted based on name.
STDMETHODIMP CCreateHash::Initialize( LPCITEMIDLIST pidlFolder,
LPDATAOBJECT pDataObj, HKEY hProgID )
{
pidlFolder; hProgID; FORMATETC fmt = { CF_HDROP,NULL,DVASPECT_CONTENT,-1,TYMED_HGLOBAL };
STGMEDIUM stg = { TYMED_HGLOBAL };
HDROP hDrop;
...
UINT uNumFiles = DragQueryFile( hDrop,
static_cast<UINT>(-1), NULL, 0 );
if( 0 == uNumFiles )
{ return E_INVALIDARG; }
HRESULT hr = S_OK;
TCHAR file[ MAX_PATH * 2 + 1 ];
for(UINT i = 0; i < uNumFiles; i++)
{
DragQueryFile( static_cast<HDROP>( stg.hGlobal ),
i, file, MAX_PATH * 2 );
if (::GetFileAttributes( file ) & FILE_ATTRIBUTE_DIRECTORY)
{ continue; }
#ifdef UNICODE
files.push_back( file );
#else
files.push_back( StringWiden( file ) );
#endif
}
std::sort( files.begin(), files.end() );
return hr;
}
Create InvokeCommand
HRESULT CCreateHash::InvokeCommand ( LPCMINVOKECOMMANDINFO pCmdInfo )
{
if ( 0 != HIWORD( pCmdInfo->lpVerb ) ) { return E_INVALIDARG; }
unsigned int i = 0, j = 0;
std::vector< std::wstring > hashnames;
std::vector< std::wstring > hashvalues;
std::wstring ClipboardText;
std::wstring MsgBoxMessage;
MsgBoxMessage = L"The following was placed on the Windows Clipboard:\r\n\r\n";
switch( LOWORD( pCmdInfo->lpVerb ) )
{
case 1:
{
for( i = 0; i < files.size(); i++ )
{
CalculateFileHashes( files[ i ], hashnames, hashvalues );
ClipboardText += FileName( files[ i ] ) + L"\r\n";
MsgBoxMessage += FileName( files[ i ] ) + L"\r\n";
for( j = 0; j < hashvalues.size(); j++ )
{
ClipboardText += hashnames[ j ] + L": " + hashvalues[ j ] + L"\r\n";
...
} } if( i + 1 < files.size() )
{
ClipboardText += L"\r\n";
if( files.size() <= 2 )
{
MsgBoxMessage += L"\r\n";
} SetClipboardText( pCmdInfo->hwnd, ClipboardText );
#ifdef _UNICODE
MessageBox( pCmdInfo->hwnd, MsgBoxMessage.c_str(),
_T("File Checksum Results"), MB_ICONINFORMATION );
#else
MessageBox( pCmdInfo->hwnd, StringNarrow( MsgBoxMessage ).c_str(),
_T("File Checksum Results"), MB_ICONINFORMATION );
#endif
return S_OK;
break;
}
default:
break;
}
return E_INVALIDARG;
}
Verify InvokeCommand
Verify Checksum InvokeCommand()
uses the same basic code and logic
as Create Checksum. The difference in this code is the introduction of two additional
vectors: vector< wstring > verifiedfiles
and
vector< wstring
> unverifiedfiles
for book keeping.
HRESULT CVerifyHash::InvokeCommand ( LPCMINVOKECOMMANDINFO pCmdInfo )
{
unsigned int i = 0, j = 0;
UINT style = MB_ICONINFORMATION;
bool found = false;
wstring ClipboardText;
wstring MsgBoxMessage;
vector< wstring > hashnames;
vector< wstring > hashvalues;
vector< wstring > verifiedfiles;
vector< wstring > verifiedhashes;
vector< wstring > unverifiedfiles;
if ( 0 != HIWORD( pCmdInfo->lpVerb ) ) { return E_INVALIDARG; }
HWND hwnd = pCmdInfo->hwnd;
if( false == GetClipboardText( ClipboardText ) )
{
MessageBox( hwnd, _T("There is not text on the Clipboard."),
_T("File Checksum Verifier"), MB_ICONWARNING );
return S_OK;
}
switch( LOWORD( pCmdInfo->lpVerb ) )
{
case 1:
{
for( i = 0; i < files.size(); i++ )
{
found = false;
CalculateFileHashes( files[ i ], hashnames, hashvalues );
for( j = 0; j < hashvalues.size() ; j++ )
{
if( true == Find( hashvalues[ j ], ClipboardText ) )
{
found = true;
verifiedfiles.push_back( files[ i ] );
verifiedhashes.push_back( hashnames[ j ] + L": " +
Snip( hashvalues[ j ] ) );
j = hashvalues.size(); }
} if( false == found )
{
unverifiedfiles.push_back( files[ i ] );
}
}
...
return S_OK;
break;
}
default:
break;
}
return E_INVALIDARG;
}
This version of InvokeCommand()
maintains parallel arrays for simplicity.
The arrays are created for each file in the files
vector. The arrays
can be visualized as follows.
|
Figure 22: Hash Name - Hash Value
Parallel Array
|
The arrays are created with CreateFileHashes()
. The function prototype
is below. Both Create and Verify use the function. Create simply dumps it to the
Clipboard, while Verify walks the hashvalues vector (from strongest to weakest),
searching for the hash value on the Clipboard.
bool CalculateFileHashes( const std::wstring& filename,
std::vector< std::wstring >& hashnames,
std::vector< std::wstring >& hashvalues )
InvokeCommand()
is now presented below. Most of the logic is dominated
by maintaining two strings - one for the Message Box, and the other for the Clipboard.
Recall that the extra code for the Message Box is required to keep the size of the
Message Box manageable.
Miscellaneous
When choosing a hash, one should choose at least a 160 bit hash. Note that hash
length does not necessarily equate to strength. For example, RIPEMD-128 is as cryptographically
strong as RIPEMD-256; RIPEMD-160 is as cryptographically strong as RIPEMD-320. RIPEMD-256
and RIPEMD-320 simply generate more entropy for a given Message M. The reader should
refer to
Optional Extensions to 256 and 320 Hash Results: RIPEMD-256 and RIPEMD-320 for
details.
Taking from the NIST
Website:
There are five (5) FIPS-approved algorithms for generating a condensed
representation of a message (message digest): SHA-1, SHA-224, SHA-256,
SHA-384, and SHA-512
March 15, 2006: The SHA-2 family of hash functions (i.e., SHA-224,
SHA-256, SHA-384 and SHA-512) may be used by Federal agencies for
all applications using secure hash algorithms. Federal agencies
should stop using SHA-1 for digital signatures, digital time stamping
and other applications that require collision resistance as soon
as practical, and must use the SHA-2 family of hash functions for
these applications after 2010. After 2010, Federal agencies may
use SHA-1 only for the following applications: hash-based message
authentication codes (HMACs); key derivation functions (KDFs); and
random number generators (RNGs). Regardless of use, NIST encourages
application and protocol designers to use the SHA-2 family of hash
functions for all new applications and protocols.
Finally, taking from the
RIPE MD website:
A 128-bit hash result does not offer sufficient protection anymore.
A brute force collision search attack on a 128-bit hash result requires
264 or about 2 x 1019 evaluations of the function.
In 1994 Paul van Oorschot and Mike Wiener showed that this brute-force
job can be done in less than a month with a $10 million investment
("Parallel Collision Search with Applications to Hash Functions
and Discrete Logarithms," 2nd ACM Conference on Computer and Communications
Security, ACM Press, 1994, pp. 210-218). This cost is expected to
halve every 18 months.
Visual Sudio 2005 and Platform SDK
Should the reader encounter the following issue during compilation:
error C2787: 'IContextMenu' : no GUID has been associated with this
object
The reader can add the following to stdafx.h:
#ifndef IContextMenu
struct __declspec(uuid("000214e4-0000-0000-c000-000000000046")) IContextMenu;
#endif
Doug Harrison, Microsoft MVP, has additional fixes outlined in
Missing
IContextMenu.
Summary
File checksums are an often overlooked Cryptographic tool. With these DLLs, one
can easily incorporate the Checksum functionality into his or her documents or website.
Downloads
Acknowledgments
- Wei Dai for Crypto++ and his invaluable help on the Crypto++
mailing list
- Dr. Brooke Stephens who laid my Cryptographic foundations
Revisions
- 05.24.2008 Added Vista regsvr32.exe Error
- 05.24.2008 Tested Upgrade to Visual C++ 9.0 (VS2008)
- 03.07.2008 Added VS2008 regsvr32.exe Error
- 12.01.2007 Added Windows XP and Above Composting Issue
- 08.03.2007 Added note on IContextMenu and Visual Studio 2005
- 08.03.2007 Upgraded from Visual C++ 7.1 to Visual C++ 8.0
- 05.31.2007 Upgraded from Visual C++ 6.0 to Visual C++ 7.1
- 05.31.2007 Verified Compatibility with Crypto++ 5.5.1
- 05.31.2007 Removed HAVAL in CtxVerifyHash.dll
- 05.31.2007 Added Additional SHA-2 Hashes in CtxVerifyHash.dll
- 05.31.2007 Added Whirlpool (512) Hash in CtxVerifyHash.dll
- 01.09.2007 Added Reference to NIST Hash Choices
- 12.19.2006 Added Section Crypto++ String and File Hashing
- 12.17.2006 Added CRC32 to Verify Shell Extension DLL
- 12.16.2006 Update Article Graphics
- 12.15.2006 Updated ChannelSwitch
- 12.14.2006 Initial Release
Checksums
- CtxCreateHash.zip
- MD5: D967CF24BEC8BF403B0F274B7908876E
- RIPEMD-128: CF073CB397C912EE3395C63F5CCF93FE
- RIPEMD-160: 324123B69596608381147F0F60C343B3F5C4B007
- RIPEMD-256: 5A5A18771EAAF7A2E60C83D2FC8EC507389532A1E7F80FD2C38D5E949899A07F
- RIPEMD-320: C8A01A5A8BA0323CEB25DA4FC45E155E47ACC5BFF7481AECF0C1E4250E5CF07DF7885A9CFD3D8E23
- SHA-1: 53CFD2F493846CC49EC5209CA7E4C9939D371172
- SHA-224: 70A8F1066BEAE431E4462E226A4293DB323A096EDE5163A47B83E64C
- SHA-256: 75DA22C742752DBC73AB421F8A48BCCEC4BDF5486DFF16EB9395C6F499DF9221
- CtxCreateHashDll.zip
- RIPEMD-128: ABEF419E395A7A21CE9D65A5419842D4
- RIPEMD-160: 22EF853EAE308F7B07CA674CC50BE6D9929F3649
- RIPEMD-256:
- 19CC4D1BFD0B92B9E7D48CB7CE14C0C3F094E8E15EC8EB61C89632C5032D474C
- RIPEMD-320: D06012A5A9EA47436AFD247B90D179081B64C62CEF3BA20DF35DB85021FFBB386BA7962A289BB224
- CtxVerifyHash.zip
- SHA-1: B54562A1EC047F31C75027C396CE44DFE6F2020D
- SHA-224: CB413BAD801E4C9F8043BAF294FA29F0616F449B42E453B9EE1E5941
- SHA-256: D10E2C892487CC6A17D5C673DE52974F7F0DB701342DD46B1821FD479DBCEC4F
- CtxVerifyHashDll.zip
- RIPEMD-256: 82BC7E086B8EDA7C1A8C5310F82F31DDEA13BF192482A6D1F362831E8FC2F23A
- SHA-256: 1602BD0FF62E05F4B77428C5943FE37CB7CCE6367C5D79CCC004E125A2C6266A
- Sample1.zip
- Sample2.zip
- MD5: 0CAE59412D3272C12A4985930F313DC1
- Sample3.zip
- SHA-1: 68274EDD7A70792057A379048F1FBA6E58048F7E
- Sample4.zip
- RIPEMD-160: 7434F4A3498A642A53FCC3AF6081718BE18FCE28
- Sample5.zip
- SHA-256: 3DA6D8C6D29DE726B96838273C5E9D7C558F725BF6574345C054E7FD1325DF42