Click here to Skip to main content
15,887,683 members
Articles / Desktop Programming / Windows Forms
Article

File Hash Generator Shell Extension

Rate me:
Please Sign up or sign in to vote.
3.55/5 (11 votes)
10 Apr 2008CPOL4 min read 85.6K   1.8K   48   14
Describes a shell extension option that calculates the file hash (SHA-1, MD5 etc.) of a file for use in verifying authenticity.

FileHash.png

Contents

Introduction

File hashes are used to verify the authenticity and integrity of files - especially those transmitted over the internet. When downloading a file from MSDN for instance, you are presented with the SHA-1 Hash - a list of seemingly random characters and numbers that are generated using the SHA-1 encryption algorithm to uniquely identify that file. If even a single bit of that file is changed, the hash itself will change. Most importantly, it is nearly impossible to create a different file that will produce the same hash, making spoofing unfeasable. But the question remains, how can I easily verify that hash against the file that I actually downloaded? This article will present a window shell extension that gives you easy access to the hash of any file quickly and easily.

Background

There are two coding concepts covered in this article. The first is cryptographic hash and the second is windows shell extensions. The cryptographic hash, of which there are many different algorithms, produces the digital fingerprint of a file. The .NET Framework provides 6 System.Security.Cryptography.HashAlgorithm implementations that can be used for generating the digital fingerprint: MD5, SHA-1,SHA-256,SHA-384,SHA-512 and RIPEMD-160. The two most commonly used when downloading a file are MD5 and SHA-1. Windows shell extensions take us beyond the comfortable realm of managed code and force us to implement COM to integrate into the unmanaged world of Win32. For this element of the utility,code is pulled directly from Dino Esposito's article Manage with the Windows Shell: Write Shell Extensions with C#. Dino's explanation of the COM interfaces and sample code were invaluable to this project. To really understand the implemention of a shell extension, I highly recommend his article.

Using the Utility

With the integration into the Windows Explorer context menu, generating the hash as simple as selecting one or more files then right-clicking to display the context menu. From this menu, you can select which type of file hash you want to generate by selecting the sub-menu option. You will then be presented with a window containing the file name, the hash that you selected, plus the full path to the file(s) that you selected. This DataGridView table allows for selecting of the desired values and copying to the clipboard if you need to publish the hash value. It's that easy!

Reviewing the Code

Generating a file hash is a very straighforward task. In the code sample below, the specific implementation of the HashAlgorithm abstract type is created based on a user selected HashType enumeration value. Next, using a System.IO.FileStream object, the file is opened and streamed into the HashAlgorithm object to let it do its magic. The resulting Byte array is then put into a StringBuilder so that the hash string can be returned to the calling object.

public static string GetFileHash(string filePath, HashType type)
    {
        if (!File.Exists(filePath))
            return string.Empty;

        System.Security.Cryptography.HashAlgorithm hasher;
        switch(type)
        {
            case HashType.SHA1:
            default:
                hasher = new SHA1CryptoServiceProvider();
                break;
            case HashType.SHA256:
                hasher = new SHA256Managed();
                break;
            case HashType.SHA384:
                hasher = new SHA384Managed();
                break;
            case HashType.SHA512:
                hasher = new SHA512Managed();
                break;
            case HashType.MD5:
                hasher = new MD5CryptoServiceProvider();
                break;
            case HashType.RIPEMD160:
                hasher = new RIPEMD160Managed();
                break;
        }
        StringBuilder buff = new StringBuilder();
        try
        {
            using (FileStream f = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 8192))
            {
                hasher.ComputeHash(f);
                Byte[] hash = hasher.Hash;
                foreach (Byte hashByte in hash)
                {
                    buff.Append(string.Format("{0:x2}", hashByte));
                }
            }
        }
        catch
        {
            return "Error reading file." + new System.Random(DateTime.Now.Second * DateTime.Now.Millisecond).Next().ToString();
        }
        return buff.ToString();
}
 public enum HashType
    {
        [Description("SHA-1")]
        SHA1,
        [Description("SHA-256")]
        SHA256,
        [Description("SHA-384")]
        SHA384,
        [Description("SHA-512")]
        SHA512,
        [Description("MD5")]
        MD5,
        [Description("RIPEMD-160")]
        RIPEMD160

    }

The trick comes in when you want to be able to right-click on the file in Windows explorer and generate the hash. This is where Dino Esposito's expertise comes in. By modifying the code found in his article, I was able to hook into the shell context menu and add my menu option along with the sub-menu selections for the various hash types. This code can be found in the FileHashShellExt.cs file in the sample project.The key element for this integration are the IContextMenu interface. The first method, QueryContextMenu, in conjunction with a shell32 DragQueryFile method allows you to interrogate the number and type of files that were selected to determine if the custom menu option should be added. The second method, InvokeCommand provides back the information on the sub-menu item selected so that you can execute the proper hash type.

Now that you have your code ready to go, you need to make Windows aware of your utility. This is handled via the registry. It's easy enough to register an assembly for COM interop, but how do you let Windows know what exactly it's supposed to do with it? By implementing the System.Runtime.InteropServices.ComRegisterFunctionAttribute attribute along with the RegisterServer method. This method will get executed when you register the assembly with regasm.exe to add the needed registry entries that tell the Windows Shell to accept the new extension and present the context menu option for all files.
[System.Runtime.InteropServices.ComRegisterFunctionAttribute()]
static void RegisterServer(String str1)
{
    try
    {
        // For Winnt set me as an approved shellex
        RegistryKey root;
        RegistryKey rk;
        root = Registry.LocalMachine;
        rk = root.OpenSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved", true);
        rk.SetValue(guid.ToString(), "FileHash shell extension");
        rk.Flush();
        rk.Close();

        // Set "*\\shellex\\ContextMenuHandlers\\BatchResults" regkey to my guid
        root = Registry.ClassesRoot;
        rk = root.CreateSubKey("*\\shellex\\ContextMenuHandlers\\FileHash");
        rk.SetValue("", guid.ToString());
        rk.Flush();
        rk.Close();
    }
    catch(Exception e)
    {
        System.Console.WriteLine(e.ToString());
    }
}
There is an associated method UnregisterServer that is called when you unregister the assemply with regasm.exe and the "/u" flag to remove the registry entries. If you install the Shell extension via the demo project installer, all of this is taken care of for you. If you choose to download the code and install it manually, you can use the register.bat and unregister.bat files respectively (you'll want to check that the paths to regasm.exe and gacutil.exe are correct for your environment.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralWARNING: Dangerous Bug Could Wreck User's Computer Title is required Pin
Bill SerGio, The Infomercial King14-Mar-10 8:21
Bill SerGio, The Infomercial King14-Mar-10 8:21 
GeneralPossible Bug in the code Pin
Andrea mariniello3-Mar-10 4:10
Andrea mariniello3-Mar-10 4:10 
GeneralGood work Pin
SplashBrazil7-Jan-10 12:21
SplashBrazil7-Jan-10 12:21 
GeneralWIN+E Key is ignored after install. This extension should not be used in current state under WinXP. Pin
sternenfaenger7713-Aug-09 22:43
sternenfaenger7713-Aug-09 22:43 
GeneralPerhaps dotNET is not a good choice for writing shell extensions... Pin
Stephen Hewitt10-Apr-08 19:43
Stephen Hewitt10-Apr-08 19:43 
GeneralRe: Perhaps dotNET is not a good choice for writing shell extensions... Pin
Lukasz Sw.14-Apr-08 22:51
Lukasz Sw.14-Apr-08 22:51 
GeneralRe: Perhaps dotNET is not a good choice for writing shell extensions... Pin
Stephen Hewitt14-Apr-08 23:04
Stephen Hewitt14-Apr-08 23:04 
GeneralRe: Perhaps dotNET is not a good choice for writing shell extensions... Pin
Lukasz Sw.17-Apr-08 14:14
Lukasz Sw.17-Apr-08 14:14 
GeneralRe: Perhaps dotNET is not a good choice for writing shell extensions... Pin
Stephen Hewitt17-Apr-08 14:21
Stephen Hewitt17-Apr-08 14:21 
GeneralRe: Perhaps dotNET is not a good choice for writing shell extensions... Pin
Lukasz Sw.17-Apr-08 15:23
Lukasz Sw.17-Apr-08 15:23 
GeneralRe: Perhaps dotNET is not a good choice for writing shell extensions... Pin
Stephen Hewitt17-Apr-08 15:45
Stephen Hewitt17-Apr-08 15:45 
Lukasz Swiatkowski wrote:
but every dll library written in C++ 2003/5/8 which uses standard windows libraries is dynamically linked to at least an msvcr70/80/90.dll file


Not so. See here[^], specifically the /MT switch.

In fact I built an app which requires no runtime just then. Here's a dump using "PEDump":
Dump of file CONSOLE.EXE
 
File Header
  Machine:                      014C (i386)
  Number of Sections:           0005
  TimeDateStamp:                4807FB8B -> Fri Apr 18 11:38:19 2008
  PointerToSymbolTable:         00000000
  NumberOfSymbols:              00000000
  SizeOfOptionalHeader:         00E0
  Characteristics:              0102
    EXECUTABLE_IMAGE
    32BIT_MACHINE
 
Optional Header
  Magic                         010B
  linker version                9.00
  size of code                  12000
  size of initialized data      7400
  size of uninitialized data    0
  entrypoint RVA                453C
  base of code                  1000
  base of data                  13000
  image base                    400000
  section align                 1000
  file align                    200
  required OS version           5.00
  image version                 0.00
  subsystem version             5.00
  Win32 Version                 0
  size of image                 1F000
  size of headers               400
  checksum                      27DA8
  Subsystem                     0003 (Windows character)
  DLL flags                     8140
 
  stack reserve size            100000
  stack commit size             1000
  heap reserve size             100000
  heap commit size              1000
  RVAs & sizes                  10
 
Data Directory
  EXPORT       rva: 00000000  size: 00000000
  IMPORT       rva: 00016ACC  size: 00000028
  RESOURCE     rva: 0001C000  size: 000001B4
  EXCEPTION    rva: 00000000  size: 00000000
  SECURITY     rva: 00000000  size: 00000000
  BASERELOC    rva: 0001D000  size: 000010D8
  DEBUG        rva: 00013190  size: 0000001C
  COPYRIGHT    rva: 00000000  size: 00000000
  GLOBALPTR    rva: 00000000  size: 00000000
  TLS          rva: 00000000  size: 00000000
  LOAD_CONFIG  rva: 00015510  size: 00000040
  BOUND_IMPORT rva: 00000000  size: 00000000
  IAT          rva: 00013000  size: 0000012C
  DELAY_IMPORT rva: 00000000  size: 00000000
  unused       rva: 00000000  size: 00000000
  unused       rva: 00000000  size: 00000000
 
Section Table
  01 .text     VirtSize: 00011F21  VirtAddr:  00001000
    raw data offs:   00000400  raw data size: 00012000
    relocation offs: 00000000  relocations:   00000000
    line # offs:     00000000  line #'s:      00000000
    characteristics: 60000020
      CODE  EXECUTE  READ  ALIGN_DEFAULT(16)

  02 .rdata    VirtSize: 00004180  VirtAddr:  00013000
    raw data offs:   00012400  raw data size: 00004200
    relocation offs: 00000000  relocations:   00000000
    line # offs:     00000000  line #'s:      00000000
    characteristics: 40000040
      INITIALIZED_DATA  READ  ALIGN_DEFAULT(16)
 
  03 .data     VirtSize: 00003168  VirtAddr:  00018000
    raw data offs:   00016600  raw data size: 00001400
    relocation offs: 00000000  relocations:   00000000
    line # offs:     00000000  line #'s:      00000000
    characteristics: C0000040
      INITIALIZED_DATA  READ  WRITE  ALIGN_DEFAULT(16)
 
  04 .rsrc     VirtSize: 000001B4  VirtAddr:  0001C000
    raw data offs:   00017A00  raw data size: 00000200
    relocation offs: 00000000  relocations:   00000000
    line # offs:     00000000  line #'s:      00000000
    characteristics: 40000040
      INITIALIZED_DATA  READ  ALIGN_DEFAULT(16)
 
  05 .reloc    VirtSize: 00001AFE  VirtAddr:  0001D000
    raw data offs:   00017C00  raw data size: 00001C00
    relocation offs: 00000000  relocations:   00000000
    line # offs:     00000000  line #'s:      00000000
    characteristics: 42000040
      INITIALIZED_DATA  DISCARDABLE  READ  ALIGN_DEFAULT(16)
 
Debug Formats in File
  Type            Size     Address  FilePtr  Charactr TimeDate Version
  --------------- -------- -------- -------- -------- -------- --------
  CODEVIEW        00000086 00015558 00014958 00000000 4807FB8B 0.00
 
Resources (RVA: 1C000)
ResDir (0) Entries:01 (Named:00, ID:01) TimeDate:00000000 Vers:4.00
    --------------------------------------------------------------
    ResDir (18) Entries:01 (Named:00, ID:01) TimeDate:00000000 Vers:4.00
        ResDir (1) Entries:01 (Named:00, ID:01) TimeDate:00000000 Vers:4.00
            ID: 00000409  DataEntryOffs: 00000048
            DataRVA: 1C058  DataSize: 0015A  CodePage: 4E4
 
Imports Table:
  KERNEL32.dll
  OrigFirstThunk:  00016AF4 (Unbound IAT)
  TimeDateStamp:   00000000 -> Thu Jan 01 11:00:00 1970
  ForwarderChain:  00000000
  First thunk RVA: 00013000
  Ordn  Name
   704  InterlockedIncrement
   700  InterlockedDecrement
  1057  Sleep
   692  InitializeCriticalSection
   190  DeleteCriticalSection
   217  EnterCriticalSection
   751  LeaveCriticalSection
   367  GetCommandLineA
  1069  TerminateProcess
   425  GetCurrentProcess
  1086  UnhandledExceptionFilter
  1045  SetUnhandledExceptionFilter
   721  IsDebuggerPresent
   858  RaiseException
   486  GetLastError
   673  HeapFree
   914  RtlUnwind
   737  LCMapStringA
  1146  WideCharToMultiByte
   794  MultiByteToWideChar
   739  LCMapStringW
   347  GetCPInfo
   669  HeapAlloc
   505  GetModuleHandleW
   544  GetProcAddress
   260  ExitProcess
  1165  WriteFile
   571  GetStdHandle
   500  GetModuleFileNameA
   330  FreeEnvironmentStringsA
   447  GetEnvironmentStrings
   331  FreeEnvironmentStringsW
   449  GetEnvironmentStringsW
  1000  SetHandleCount
   471  GetFileType
   569  GetStartupInfoA
  1076  TlsGetValue
  1074  TlsAlloc
  1077  TlsSetValue
  1075  TlsFree
  1004  SetLastError
   429  GetCurrentThreadId
   671  HeapCreate
  1111  VirtualFree
   852  QueryPerformanceCounter
   614  GetTickCount
   426  GetCurrentProcessId
   591  GetSystemTimeAsFileTime
  1108  VirtualAlloc
   676  HeapReAlloc
   387  GetConsoleCP
   405  GetConsoleMode
   321  FlushFileBuffers
   872  ReadFile
   991  SetFilePointer
    67  CloseHandle
   678  HeapSize
   338  GetACP
   531  GetOEMCP
   731  IsValidCodePage
   488  GetLocaleInfoA
   573  GetStringTypeA
   576  GetStringTypeW
   621  GetUserDefaultLCID
   248  EnumSystemLocalesA
   733  IsValidLocale
   753  LoadLibraryA
   693  InitializeCriticalSectionAndSpinCount
  1154  WriteConsoleA
   409  GetConsoleOutputCP
  1164  WriteConsoleW
  1020  SetStdHandle
   490  GetLocaleInfoW
   120  CreateFileA


Note the import table! Only kernel32.dll is needed!

But you're missing the main point: it doesn't matter if the DLL runtimes are injected into another process as multiple versions of the C-runtime libraries can exist in a single process. This is not the case with the CLR.

Steve

GeneralRe: Perhaps dotNET is not a good choice for writing shell extensions... Pin
Lukasz Sw.18-Apr-08 2:30
Lukasz Sw.18-Apr-08 2:30 
GeneralRe: Perhaps dotNET is not a good choice for writing shell extensions... Pin
Stephen Hewitt20-Apr-08 13:29
Stephen Hewitt20-Apr-08 13:29 
GeneralRe: Perhaps dotNET is not a good choice for writing shell extensions... Pin
Lukasz Sw.21-Apr-08 6:15
Lukasz Sw.21-Apr-08 6:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.