A File Checksum Shell Menu Extension

Introduction

A file Checksum is used to verify the integrity of a file. Checksumming detects both transmission errors and tampering. To detect transmission errors, one could use a CRC (cyclic redundancy check). To detect tampering (and transmission errors), one generally chooses a cryptographic Hash due to the desireable property of Collision Resistance.

This article presents two Shell Extension DLLs that 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" and Tom Archer's "Using the Clipboard, Part I: Transferring Simple Text" and "Crypto++."

This article will discuss the following topics:

  • Shell Extension DLL Usage
  • Compiling and Integrating Crypto++ into the Visual C++ Environment
  • Windows Clipboard APIs
  • Crypto++ ChannelSwitch Class
  • Creating a Shell Context Menu Extension DLL
  • Debugging and Managing an Unstable Explorer.exe
  • Creating CreateHash and VerifyHash Shell Context Menu Extension Dlls

Microsoft offers a command line file checksum tool published under Knowledge Base article Q841290,"Availability and Description of the File Checksum Integrity Verifier Utility." The Microsoft tool supports MD5 or SHA1.

Shell Extension DLL Usage

Should the reader desire to use the Extension DLLs, he or she should:

  • Download the DLL (CtxCreateHashDll.zip or CtxVerifyHashDll.zip)
  • Place in C:\Winnt\System32\
  • regsvr32.exe CtxCreateHash.Dll
  • regsvr32.exe CtxVerifyHash.Dll

Once the DLLs are registered, they are avialable as Context Menus when one right-clicks a file (or multiple files) in Windows Explorer.

If the user selects Create Checksum, MD5, SHA-1, RIPEMD-160, and SHA-256, hashes are created of the selected file or files and a Message Box is displayed with a digest of the hashed file or files. A digest (truncated version) is displayed to keep the Message Box size manageable. The full Checksums are placed on the Windows Clipboard for pasting.

Truncated Message Box

Pasting into Notepad from the Clipboard reveals the full text.

Clipboard Text

Verifying a file's checkum is equally trivial. Navigate to the web page or document where the checksum resides, highlight, and Copy to the Clipboard.

Copy Checksums to the Clipboard

Navigate to the files, right-click, and select Verify Checksum.

Verify Checksums

A Message Box will be presented similar to that shown below. The message box will group files in two catagories: verified and unverified. A verified file will display the message "Verified Checksum" with a digest of the checksum. An unverified file will diplay the message "Unverified Checksum".

Verify Checksum Message Box

When verifying, the extension DLL uses MD4, MD5, RIPEMD-128, SHA-1, HAVAL, RIPEMD-160, and SHA-256. Internally, the DLL searches for matching hash values in order from strongest to weakest (SHA-256 to MD4). The match algorithm terminates on a first match, so only the strongest hash is displayed.

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.

A File Checksum Shell Menu Extension

Compiling and Integrating Crypto++ into the Microsoft Visual C++ Environment

Please see the related article, "Compiling and Integrating Crypto++ into the Microsoft Visual C++ Environment." This article is based upon basic assumptions presented in the previously mentioned article. It also addresses most problems encountered with projects from Command Line to MFC (Errors C1083, C1189, LNK1104, LNK2001, and LNK2005). Additionally, it provides some tips and other nicities for using the Crypto++ Library.

For those who are interested in other C++ Number Theoretic 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.

Enumerating Clipboard Formats

Sample 2 demonstrates reading text from the Clipboard.

Retrieving Clipboard Text

Sample 3 demonstrates writing Unicode text to the Clipboard.

Writing Clipboard Text

A File Checksum Shell Menu Extension

Crypto++ ChannelSwitch Class

The Crypto++ ChannelSwitch class allows a user to pump data to multiple HashFilter-based objects. 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 six times to send it to six different consumer objects. Although one could author code to perform the buffering and delegation code, the simplest solution is the ChannelSwitch.

ChannelSwitch Overview

ChannelSwitch offers the AddDefaultRoute(BufferedTransformation &destination) function, and HashFilter derives from BufferedTransformation.

HashFilter Inheritance Diagram

The use of the ChannelSwitch object in the Verify Checksum Shell Extension DLL is depicted below. Note that Create Checksum Shell Extension DLL only uses MD5, SHA-1, RIPE MD-160, and SHA-256.

Verify Checksum DLL ChannelSwitch Usage

The code to accomplish multiple hashing based on a ChannelSwitch is shown below.

// Sample 4

#include "channels.h"    // ChannelSwitch
#include "filters.h"     // HashFilter
#include "hex.h"         // HexEncoder
#include "md5.h"         // MD5
#include "sha.h"         // SHA-1

int main( )
{
   MD5  hashMD5;
   HashFilter filterMD5(hashMD5);

   SHA1 hashSHA1;
   HashFilter filterSHA1(hashSHA1);

   std::auto_ptr<ChannelSwitch> channelSwitch(new ChannelSwitch);
   channelSwitch->AddDefaultRoute(filterMD5);
   channelSwitch->AddDefaultRoute(filterSHA1);

   StringSource( "abcdefghijklmnopqrstuvwxyz", true,
                 channelSwitch.release());

   string digest;
   HexEncoder encoder( new StringSink( digest ),
                       true /* uppercase */ );

   filterMD5.TransferTo( encoder );
   cout << filterMD5.AlgorithmName() << ": " << digest << endl;
   digest.erase();

   filterSHA1.TransferTo( encoder );
   cout << filterSHA1.AlgorithmName() << ": " << digest << endl;
   digest.erase();

   return 0;
}

Earlier, fciv.exe (Q841290) was mentioned; it 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 Sample 4 (using MD5, SHA-1, RIPEMD-160, and SHA-256) is shown below.

Sample 4

A File Checksum Shell Menu Extension

Creating a Shell Context Menu Extension DLL

This portion of the article is exclusively based on Michael Dunn's "Guide to Writing Shell Extensions, Part I." Should the reader find the treatment too superficial, please refer to Michael's article.

In Visual C++ 6.0, select new ATL COM Wizard.

ATL COM Wizard

Select DLL as the Server Type.

DLL COM Server

In Class View, right-click and add a New ATL Object.

Add ATL Object

Choose a Simple Object.

Simple Object

Choose a Name on the Names Tab. MenuItem will suffice for the Sample.

Names Tab

The Attributes should be Custom and No Aggregation.

Attributes Tab

Once the Project is created, enable Exception handling.

Enable Exception Handling

Remove references to _ATL_MIN_CRT (which causes link errors).

Remove _ATL_MIN_CRT

Open MenuItem.h and perform the following:

  • #include <shlobj.h>
  • #include <comdef.h>
  • Remove IMenuItem
  • Add IShellExtInit
  • Add IContextMenu
#include <shlobj.h>
#include <comdef.h>

class ATL_NO_VTABLE CMenuItem :
   public CComObjectRootEx<CComSingleThreadModel>,
   public CComCoClass<CMenuItem, &CLSID_MenuItem>,
   // public IMenuItem
   public IShellExtInit,
   public IContextMenu
{
   ...

BEGIN_COM_MAP(CMenuItem)
   // COM_INTERFACE_ENTRY(IMenuItem)
   COM_INTERFACE_ENTRY(IShellExtInit)
   COM_INTERFACE_ENTRY(IContextMenu)
END_COM_MAP()
}

Finally, add the follwing to MenuItem.h (required due to IShellExtInit and IContextMenu).

public:
   // IShellExtInit
   STDMETHODIMP Initialize(LPCITEMIDLIST, LPDATAOBJECT, HKEY);

public:
   // IContextMenu
   STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO);
   STDMETHODIMP GetCommandString(UINT, UINT, UINT*, LPSTR, UINT);
   STDMETHODIMP QueryContextMenu(HMENU, UINT, UINT, UINT, UINT);

Compiling should produce four errors:

Compile Errors

A File Checksum Shell Menu Extension

Creating a Shell Context Menu Extension DLL (continued)

Add the following to set the string that is displayed when the Test Menu is displayed:

HRESULT CMenuItem::QueryContextMenu( HMENU hmenu, UINT uMenuIndex,
                                     UINT uidFirstCmd,
                                     UINT uidLastCmd, UINT uFlags )
{
   uidFirstCmd;    // Suppress Warning C4100
   uidLastCmd;     // Suppress Warning C4100

   // If the flags include CMF_DEFAULTONLY, we shouldn't do anything.
   if( uFlags & CMF_DEFAULTONLY )
      { return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, 0 ); }

   InsertMenu( hmenu, uMenuIndex, MF_BYPOSITION,
               uidFirstCmd, _T("Context Menu Test") );

   return MAKE_HRESULT( SEVERITY_SUCCESS, FACILITY_NULL, 1 );
}

To save state between Initialize() and InvokeCommand(), add the following to MenuItem.h. Note that there may be multiple files available, but Sample 4 is going to extract only one.

#include <string>
...
protected:
   std::basic_string<TCHAR> file;

Initialize() should be as follows:

STDMETHODIMP CCreateHash::Initialize( LPCITEMIDLIST pidlFolder,
                                      LPDATAOBJECT pDataObj,
                                      HKEY hProgID )
{
   pidlFolder;    // Warning C4100 suppression
   hProgID;       // Warning C4100 suppression

   FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1,
                     TYMED_HGLOBAL };
   STGMEDIUM stg = { TYMED_HGLOBAL };
   HDROP     hDrop;

   // Look for CF_HDROP data in the data object. If there is no
   // such data, return an error back to Explorer.
   if( FAILED( pDataObj->GetData ( &fmt, &stg ) ))
      { return E_INVALIDARG; }

   // Get a pointer to the actual data.
   hDrop = static_cast<HDROP>( GlobalLock ( stg.hGlobal ) );

   // Make sure it worked.
   if( NULL == hDrop )
      { return E_INVALIDARG; }

   // Sanity check -- make sure there is at least one filename.
   UINT uNumFiles = DragQueryFile( hDrop, static_cast<UINT>(-1),
                                   NULL, 0 );
   if( 0 == uNumFiles )
   {
      GlobalUnlock ( stg.hGlobal );
      ReleaseStgMedium ( &stg );

      return E_INVALIDARG;
   }

   HRESULT hr = S_OK;

   //   MAX_PATH can be a bit too small...
   TCHAR szFile[ MAX_PATH * 2 + 1 ];

   DragQueryFile( static_cast<HDROP>( stg.hGlobal ),
      0 /*first file*/, file, MAX_PATH * 2 );

   // Save the first file name in the list
   file = szFile;

   GlobalUnlock ( stg.hGlobal );
   ReleaseStgMedium ( &stg );

   return hr;
}

InvokeCommand() displays a Message Box of the file name the Sample received.

HRESULT CMenuItem::InvokeCommand ( LPCMINVOKECOMMANDINFO pCmdInfo )
{
   // If lpVerb really points to a std::string, ignore this
   // function call and bail out.
   if ( 0 != HIWORD( pCmdInfo->lpVerb ) ) { return E_INVALIDARG; }

   // Get the command index -- the only valid one is 0.
   switch( LOWORD( pCmdInfo->lpVerb ) )
   {
      case 0:
      {
         MessageBox( pCmdInfo->hwnd, file.c_str(),
                     _T("Context Menu Test"), MB_ICONINFORMATION );

         return S_OK;
         break;
      }

      default:
         assert( false );
         break;
   }

   return E_INVALIDARG;
}

Finally, add the following for fly-by help:

HRESULT CMenuItem::GetCommandString( UINT idCmd, UINT uFlags,
                                     UINT* pwReserved, LPSTR pszName,
                                     UINT cchMax )
{

   pwReserved;    // Warning C4100 suppression

   // Check idCmd; it must be 0 since we have only one menu item.
   if ( 0 != idCmd ) { return E_INVALIDARG; }

   // If Explorer is asking for a help std::string, copy our
   // std::string into the supplied buffer.
   if( uFlags & GCS_HELPTEXT )
   {
      if ( uFlags & GCS_UNICODE )
      {
         LPWSTR szText = L"Context Menu Test Description";
         // We need to cast pszName to a Unicode std::string,
         // and then use the Unicode std::string copy API.
         lstrcpynW ( reinterpret_cast<LPWSTR>( pszName ), szText,
                     cchMax );
      }
      else
      {
         LPSTR szText = "Context Menu Test Description";
         // Use the ANSI std::string copy API to return the help
         // std::string.
         lstrcpynA ( pszName, szText, cchMax );
      }

      return S_OK;
   }

   return E_INVALIDARG;
}

A File Checksum Shell Menu Extension

Creating a Shell Context Menu Extension DLL (continued)

The final step in creating the DLL is the Registry entries. Remove the following from MenuItem.rgs:

HKCR
{
   //CtxTest.MenuItem.1 = s 'MenuItem Class'
   //{
   //   CLSID = s '{43C89B83-B0CC-479B-AD1C-6497B54BEFA3}'
   //}
   //CtxTest.MenuItem = s 'MenuItem Class'
   //{
   //   CLSID = s '{43C89B83-B0CC-479B-AD1C-6497B54BEFA3}'
   //   CurVer = s 'CtxTest.MenuItem.1'
   //}
   NoRemove CLSID
   {
      ForceRemove {43C89B83-B0CC-479B-AD1C-6497B54BEFA3} =
         s 'MenuItem Class'
      {
         //ProgID = s 'CtxTest.MenuItem.1'
         //VersionIndependentProgID = s 'CtxTest.MenuItem'
         InprocServer32 = s '%MODULE%'
         {
            val ThreadingModel = s 'Apartment'
         }
         //'TypeLib' = s '{BF5CF4E7-E8CE-4AED-A28F-DE0389FDA1CE}'
      }
   }
}

Add the following to MenuItem.rgs to register for all file types:

HKCR
{
   NoRemove CLSID
   {
      ForceRemove {43C89B83-B0CC-479B-AD1C-6497B54BEFA3} =
         s 'MenuItem Class'
      {
         InprocServer32 = s '%MODULE%'
         {
            val ThreadingModel = s 'Apartment'
         }
      }
   }
   NoRemove *
   {
      NoRemove ShellEx
      {
         NoRemove ContextMenuHandlers
         {
            ForceRemove MenuItem =
               s '{43C89B83-B0CC-479B-AD1C-6497B54BEFA3}'
         }
      }
   }
}

The type library can be safely removed from the project.

Type Library Removal

// CtxTest.cpp

STDAPI DllRegisterServer(void)
{
   // return _Module.RegisterServer(TRUE);
   return _Module.RegisterServer( FALSE );
}

STDAPI DllUnregisterServer(void)
{
   // return _Module.UnregisterServer(TRUE);
   return _Module.UnregisterServer( FALSE );
}

A successful build should create the proper Registry entries as shown below.

Context Menu Handler Registration

Once built and properly registered, the Context Menu Extension DLL will be available for Explorer.

Context Menu Handler

A File Checksum Shell Menu Extension

Debugging and Managing an Unstable Explorer.exe

This portion of the article is exclusively based on Michael Dunn's "Guide to Writing Shell Extensions, Part I." Should the reader find the treatment too superficial, please refer to Michael's article. Michael details topics such as preparing the Windows environment for debugging Explorer.exe to allowing the Context Menu DLL to execute in light of Group Policy.

When prompted for a Debug Executable, the reader should specify C:\WinNT\Explorer.exe. Note that the author was never able to get VC++ 6.0 to snap or honor breakpoints when the DLL was being tested.

Explorer.exe

The reader should save his or her work frequently. Twice, the author had to perform a hard reboot. Luckily, most times only require restarting Explorer, or a soft reboot. To restart Explorer, open Task Manager and End the Explorer process.

End Explorer.exe Process

Next, switch to the Task tab, and click New Task.

New Explorer.exe Task

If the reader builds the DLL, but the DLL can be opened (to be written), verify no instances of Explorer.exe are running. If no instances are present, restart Explorer.

Explorer.exe Instances

A File Checksum Shell Menu Extension

Creating CreateHash and VerifyHash Shell Context Menu Extension DLLs

Sample 5 (CtxTest) and Sample 4 (ChannelSwitch) provide most of the code required for the DLL. This section will discuss the remaining items of interest.

The Context Menu DLLs are written using wide characters because the NT family dominates the landscape. The Crypto++ library is narrow, and the Windows API can be either.

Data transfer with respect to Crypto++ is generally from Crypto++ to the DLL. The same is not true for the Windows API. 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 conversion routines.

// Courtesy of Tom Widmer (VC++ MVP)
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);

   // Non Portable
   // ct.widen( narrow.begin(), narrow.end(), wide.begin() );

   // Portable
   ct.widen(&narrow[0], &narrow[0] + narrow.size(), &wide[0]);

   return wide;
}

Initialize() has been expanded as follows. Note 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;     // Warning C4100 suppression
   hProgID;        // Warning C4100 suppression

   FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1,
                     TYMED_HGLOBAL };
   STGMEDIUM stg = { TYMED_HGLOBAL };
   HDROP     hDrop;

   ...

   // Get a pointer to the actual data.
   hDrop = static_cast<HDROP>( GlobalLock ( stg.hGlobal ) );

   // Sanity check - make sure there is at least one filename.
   UINT uNumFiles =
      DragQueryFile( hDrop, static_cast<UINT>(-1), NULL, 0 );
   if( 0 == uNumFiles )
   {
      ...

      return E_INVALIDARG;
   }

   HRESULT hr = S_OK;

   // The author has encountered situations where MAX_PATH*2 was
   // a bit too small...
   TCHAR file[ MAX_PATH * 4 + 1 ];
   // Loop through all the files that were selected.
   for(UINT i = 0; i < uNumFiles; i++)
   {
      DragQueryFile( static_cast<HDROP>( stg.hGlobal ), i, file,
                     MAX_PATH * 4 );

      // If the file name is a directory, silently skip
      // We should not encounter this...
      if (::GetFileAttributes( file ) & FILE_ATTRIBUTE_DIRECTORY)
         { continue; }

      // Add the file name to the end of the list.
      #ifdef UNICODE
         files.push_back( file );
      #else
         files.push_back( StringWiden( file ) );
      #endif
   }

   std::sort( files.begin(), files.end() );

   ...

   return hr;
}

A File Checksum Shell Menu Extension

Creating CreateHash and VerifyHash Shell Context Menu Extension DLLs (continued)

The next major divergence occurs in InvokeCommand(). InvokeCommand() maintains parallel arrays for simplicity. The arrays are created for each file in the files vector. The arrays can be visualized as follows.

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; 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 maintaing to 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.

HRESULT CCreateHash::InvokeCommand ( LPCMINVOKECOMMANDINFO pCmdInfo )
{
   unsigned int i = 0, j = 0;

   // Friendly Name: 'MD5'
   std::vector< std::wstring > hashnames;

   // Hash: 47FD4214F5775826FB20FEC1091987A1 
   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";

   // If lpVerb really points to a std::string, ignore this
   // function call and bail out.
   if ( 0 != HIWORD( pCmdInfo->lpVerb ) ) { return E_INVALIDARG; }

   // Get the command index -- the only valid one is 0.
   switch( LOWORD( pCmdInfo->lpVerb ) )
   {
      case 0:
      {
         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++ )
            {
               // Clipboard Text is easy...
               // Just dump the information
               ClipboardText += hashnames[ j ] + L": " +
                  hashvalues[ j ] + L"\r\n";

               // Keep the Message Box reasonable
               if( files.size() <= 2 )
               {
                  // Less than 2 files - Dump everything
                  // inot the MessageBox
                  MsgBoxMessage += L"    ";
                  MsgBoxMessage += hashnames[ j ] + L": ";
                  MsgBoxMessage += Snip( hashvalues[ j ] ) + L"\r\n";
               }
               else
               {
                  if( 0 == i || 1 == i )
                  {
                     // The first two get a full print...
                     MsgBoxMessage += L"    ";
                     MsgBoxMessage += hashnames[ j ] + L": ";
                     sgBoxMessage += Snip( hashvalues[ j ] ) + L"\r\n";

                     }
                     else
                     if( 0 == j )
                     {
                        MsgBoxMessage += L"    ...\r\n";
                  }
               }
               // End - Keep the Message Box reasonable
                    
            }    // for( j = 0; j < hashnames.size(); j++ )

            // Pretty Print
            if( i + 1 < files.size() )
            {
               ClipboardText += L"\r\n";
               if( files.size() <= 2 )
               {
                     MsgBoxMessage += L"\r\n";
               }
            }

         }    // for( i = 0; i < files.size(); i++ )

         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;
}

A File Checksum Shell Menu Extension

Creating CreateHash and VerifyHash Shell Context Menu Extension DLLs (continued)

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 unverifiedfiles for book keeping.

HRESULT CVerifyHash::InvokeCommand ( LPCMINVOKECOMMANDINFO pCmdInfo )
{
   unsigned int i = 0, j = 0;
   UINT style = MB_ICONINFORMATION;
   bool found = false;

   std::wstring ClipboardText;

   std::vector< std::wstring > hashnames;
   std::vector< std::wstring > hashvalues;
   std::vector< std::wstring > verifiedfiles;
   std::vector< std::wstring > verifiedhashes;
   std::vector< std::wstring > unverifiedfiles;

   std::wstring MsgBoxMessage;

   // If lpVerb really points to a std::string, ignore this
   // function call and bail out.
   if ( 0 != HIWORD( pCmdInfo->lpVerb ) ) { return E_INVALIDARG; }

   HWND hwnd = pCmdInfo->hwnd;

   // If there is no text on the Clipboard, inform the user and
   // bail out.
   if( false == GetClipboardText( ClipboardText ) )
   {
      MessageBox( hwnd, _T("There is not text on the Clipboard."),
                  _T("File Checksum Verifier"), MB_ICONWARNING );

      return S_OK;
   }

   // Get the command index - the only valid one is 0.
   switch( LOWORD( pCmdInfo->lpVerb ) )
   {
      case 0:
      {
         for( i = 0; i < files.size(); i++ )
         {
            // Hash found on the Clipboard?
            found = false;

            CalculateFileHashes( files[ i ], hashnames, hashvalues );

            // Start at size(): this has the effect of 
            //   verifying strongest (SHA-256) to weakest (MD4)
            for( j = 0; j < hashvalues.size() ; j++ )
            {
               if( true == Find( hashvalues[ j ], ClipboardText ) )
               {
                  found = true;

                  verifiedfiles.push_back( files[ i ] );

                  // Concatenate 'MD5: ' and
                  // '82A80BE6F1E0E7766FAC3CA661089EE4', etc
                  verifiedhashes.push_back( hashnames[ j ] + L": "
                     + Snip( hashvalues[ j ] ) );

                  j = hashvalues.size();    // Break the loop
               }

            }    // for( j = 0; j < hashvalues.size(); j++ )

            if( false == found )
            {
               unverifiedfiles.push_back( files[ i ] );
            }

         }    // for( i = 0; i < files.size(); i++ )

         for( i = 0; i < verifiedfiles.size(); i++ )
         {
            MsgBoxMessage += L"Verified ";
            MsgBoxMessage += verifiedfiles[ i ];
            MsgBoxMessage += L"\r\n";

            MsgBoxMessage += L"    ";
            // verifiedhashes includes 'MD5', etc
            MsgBoxMessage += verifiedhashes[ i ];
            MsgBoxMessage += L"\r\n";

            if( i + 1 < verifiedfiles.size() )
               { MsgBoxMessage += L"\r\n"; }
         }

         if( unverifiedfiles.size() > 0 )
         {
            if( verifiedfiles.size() > 0 )
            {
               MsgBoxMessage += L"\r\n";
            }

            if( 1 == unverifiedfiles.size() )
            {
               MsgBoxMessage += L"Unverified Checksum:\r\n";
            }
            else
            {
               MsgBoxMessage += L"Unverified Checksums:\r\n";
            }

            for( i = 0; i < unverifiedfiles.size(); i++ )
            {
               MsgBoxMessage += L"     ";
               MsgBoxMessage += unverifiedfiles[ i ];
               MsgBoxMessage += L"\r\n";
            }
         }

         if( verifiedfiles.size()  > 0 &&
             unverifiedfiles.size() == 0 )
            { style = MB_ICONINFORMATION; }
         else
         if( verifiedfiles.size() == 0 &&
             unverifiedfiles.size()  > 0 )
            { style = MB_ICONERROR; }
         else
         if( verifiedfiles.size()  > 0 &&
             unverifiedfiles.size()  > 0 )
            { style = MB_ICONASTERISK; }

         #ifdef _UNICODE
            MessageBox( hwnd, MsgBoxMessage.c_str(),
                        L"File Checksum Results", style );
         #else
            MessageBox( hwnd, StringNarrow( MsgBoxMessage ).c_str(),
                        "File Checksum Results", style );
         #endif

         return S_OK;
         break;
      }

        default:
            break;
   }

   return E_INVALIDARG;
}

A File Checksum Shell Menu Extension

Miscellaneous

So that the reader can exercise the the Verify Checksum Extension DLL, the article's file checksums are presented. One checksum is incorrect (it will become 'unverified'), while others are missing (demonstrate the strongest match logic).

When choosing a hash, one should choose at least a 160 bit hash. 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.

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 web site.

Acknowledgments

  • Wei Dai for Crypto++ and his invaluable help on the Crypto++ mailing list
  • Dr. Brooke Stephens, who laid my Cryptographic foundations

Revisions

  • 12.17.2006 Added CRC32 to VerifyShell Extension Dll
  • 12.16.2006 Update Article Graphics
  • 12.15.2006 Updated ChannelSwitch
  • 12.14.2006 Initial Release

Checksums

CtxCreateHash.zip
  MD5: DD5A31G10EB9D014GGC83CC6E78GE180
  SHA-1: 139ED5D514105G3D761B7C119B61C1D5B6226G73
  RIPEMD-160: 5733B92G233B07CDD893E04CE7A6BB3E04B07G77
  SHA-256: 96GC91G94C8BAA72EBB5791E4C88112B732B8251AG2CCD7DG6DE50A73CD7A0B9

CtxCreateHashDll.zip
  MD5: A7A193B7F067DD9BAD418A98490F2D91
  SHA-1: 68BF7917FE130DB4D96E5A00ADE7A23A67779375
  RIPEMD-160: 9A70FC76285913A01084A442575A2E8CD1EF1023
  SHA-256: B46EA6F57E7236D3ED170E3E6F812D0D763BB2A05D4092F3A76D12CF1284E342

CtxVerifyHash.zip
  MD5: 152DA64B79D0993B4F95918C46838234
  SHA-1: BD3E1F9F6099BC2F3A0D111BFB46DC8432675154
  RIPEMD-160: E07D11CFDFA7D259267F8108026886C000279928
  SHA-256: D816DB278F9A0ED226DB2E174C6DB939AB948EA8461EACD0F0E0BE2B7E94E475

CtxVerifyHashDll.zip
  MD5: 9BF066CDB37E632EEB9FDFF909A3C02E
  SHA-1: 1E0F1EA2A514CD31F3B32220FA5C47701CA759E3
  RIPEMD-160: 5FFC1B21A3ED39A39594CAEB93FF4EDF44148152
  SHA-256: E2E895C3FC4C5A6CE278B23BAB228ABA13B938DBE0321BA19A2846A3D48A8AB8

Sample1.zip
  CRC32: BD50E874

Sample2.zip
  MD5: 0CAE59412D3272C12A4985930F313DC1

Sample3.zip
  SHA-1: 68274EDD7A70792057A379048F1FBA6E58048F7E

Sample4.zip
  RIPEMD-160: 7434F4A3498A642A53FCC3AF6081718BE18FCE28

  Sample5.zip
SHA-256: 3DA6D8C6D29DE726B96838273C5E9D7C558F725BF6574345C054E7FD1325DF42



About the Author

Jeffrey Walton

In the past, I have worked as an IT consultant for County Government (Anne Arundel County), the Nuclear Energy Institute, the Treasury Department, and Social Security Administration as a Network Engineer and System Administrator. Primary Administration experience includes Microsoft Windows and Novell Netware, with additional exposure and familiarity with Mac and Linux OSes. Previous to the US government, I was a programmer for a small business using Microsoft Visual Languages (Basic 5.0, 6.0, and C++ 5.0, 6.0) and Scripting Languages. An undergraduate degree (BS in Computer Science) was obtained from University of Maryland, Baltimore County. Graduate work includes a Masters of Science (Computer Science) from Johns Hopkins University (expected before 2009). Training and Certifications include Microsoft, Checkpoint, and Cisco.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • You probably have several goals for your patient portal of choice. Is "community" one of them? With a bevy of vendors offering portal solutions, it can be challenging for a hospital to know where to start. Fortunately, YourCareCommunity helps ease the decision-making process. Read this white paper to learn more. "3 Ways Clinicians can Leverage a Patient Portal to Craft a Healthcare Community" is a published document owned by www.medhost.com

  • Today's competitive marketplace requires the organization to frequently release and deploy applications at the pace of user demands, with reduced cost, risk, and increased quality. This book defines the basics of application release and deployment, and provides best practices for implementation with resources for a deeper dive. Inside you will find: The business and technical drivers behind automated application release and deployment. Evaluation guides for application release and deployment solutions. …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds