Click here to Skip to main content
Email Password   helpLost your password?

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.
You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralGood work
SplashBrazil
13:21 7 Jan '10  
Good work men! Smile
GeneralWIN+E Key is ignored after install. This extension should not be used in current state under WinXP.
sternenfaenger77
23:43 13 Aug '09  
WIN+E Key is ignored after install. This extension should not be used in current state under WinXP.
GeneralPerhaps dotNET is not a good choice for writing shell extensions...
Stephen Hewitt
20:43 10 Apr '08  
See here[^]. Of particular interest is the following quote:
Although multiple versions of the CLR may exist on a given machine, only one version may run in a particular process. So once the host chooses which version to load, all managed code that runs in that process will use that version of the CLR.


Another take on this issue can be found here[^]:
Unfortunately unmanaged C++ is really the only way to go here.

Writing in-process shell extensions in managed code is actually a very dangerous thing to do because it has the effect of injecting your managed code (and the .NET Framework) into every application on the machine that has a file open dialog.

The problems occur because only one version of the .NET Framework can be loaded in a process at any given time (other shared components such as java and msxml have the same property and thus the same restriction).

If you write your shell extension using the 2.0 .NET Framework and an application built with the 1.1 .NET Framework uses a file open dialog, your shell extension will fail because it can not run on an earlier version. Things can get even worse if your shell-extension manages to get loaded in a process before another applications managed code does: your extension may force an existing application onto a different runtime version than the one it was expecting and cause it to fail.

Because of these problems we strongly recomend against using any single-instance-per-process runtime or library (such as the .NET Framework, java, or msxml) in an in-process shell extension.



This presents a serious problem with writing shell extensions with dotNET. What happens if the user is using another extention written using a different version of the CLR? Only one of the extensions will work! What happens if, in a future version of windows, one of explorer's components uses dotNET but again uses a different CLR version? Your extension will be unusable!

Steve

GeneralRe: Perhaps dotNET is not a good choice for writing shell extensions...
Lukasz Swiatkowski
23:51 14 Apr '08  
That is right as long the shell extension is using .NET Framework 2.0 or higher, or there are multiple versions of CLR installed.
If the shell extension uses .NET Framework 1.1 (which is forward and backward compatible) or only one version of CLR is installed, then everything should be OK.

Lukasz

~~~~~~~~~~~~~~~~~~~
My website: www.lukesw.net

GeneralRe: Perhaps dotNET is not a good choice for writing shell extensions...
Stephen Hewitt
0:04 15 Apr '08  
The quotes I gave recommend not programming shell extensions using .NET and for good reason. One of the quotes I gave earlier was from Jesse Kaplan, program manager on the .NET CLR team. I’ll repeat some of it:

“Writing in-process shell extensions in managed code is actually a very dangerous thing to do because it has the effect of injecting your managed code (and the .NET Framework) into every application on the machine that has a file open dialog.”

I think this guy probably knows what he’s on about!

Steve

GeneralRe: Perhaps dotNET is not a good choice for writing shell extensions...
Lukasz Swiatkowski
15:14 17 Apr '08  
I see what you mean, and I understand it. However, dependences of every shell extension are injected into any application which uses a file open dialog. Writing a small and fast shell extension in .NET 1.1 wouldn't have worse effects than a small and fast extension created with C++ (which would require C++ 2005/2008 redistributable libraries which would also be injected into other applications). Remember that .NET only loads necessary dependences when they are required.

The only extensions I wouldn't create in .NET are global system hooks which could force an application to load a different CLR version. Any other "simple" shell extensions (like context menu/property sheet/icon/tooltip shell extension) which are loaded after the application has already loaded the appropriate version of CLR, are not that dangerous.

Regards,
Lukasz

~~~~~~~~~~~~~~~~~~~
My website: www.lukesw.net

GeneralRe: Perhaps dotNET is not a good choice for writing shell extensions...
Stephen Hewitt
15:21 17 Apr '08  
Lukasz Swiatkowski wrote:
Writing a small and fast shell extension in .NET 1.1 wouldn't have worse effects than a small and fast extension created with C++ (which would require C++ 2005/2008 redistributable libraries which would also be injected into other applications).


Not true. Firstly you can link statically or dynamically in C++: if you link statically there is no need for a DLL. Secondly multiple versions of the C++ runtimes can coexist in a single progress, in fact this is routine.

Steve

GeneralRe: Perhaps dotNET is not a good choice for writing shell extensions...
Lukasz Swiatkowski
16:23 17 Apr '08  
Yes, you can link statically or dynamically, 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 so it require installing redistributables, and the shell extension would inject them into processes.

In my humble opinion maybe it is better to write a shell extensions in C++, but it is not that bad to write properly some kinds of them using .NET 1.1. Either C++ dll's would inject one or more C++ runtimes or .NET dll's would inject the CLR (but always only one).

Lukasz

~~~~~~~~~~~~~~~~~~~
My website: www.lukesw.net

GeneralRe: Perhaps dotNET is not a good choice for writing shell extensions...
Stephen Hewitt
16:45 17 Apr '08  
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...
Lukasz Swiatkowski
3:30 18 Apr '08  
I have written that an application which is using standard libraries would require msvcrXX.dll file. I know that using compiler and linker switches you can create an app which need only kernel32.dll. But more libraries are needed to create a shell extension.

And a shell extension like from this article wouldn't cause anything dangerous on machine with only one CLR installed. Don't demonize .NET shell extension only because one of CLR creators said that they can be dangerous in certain situations.

This is exactly like the Office .NET extensions. Loading a plugin which uses CLR 2.0 into e.g. Word would prevent from loading a plugin which would use future CLR's. The only differene is that Office extensions are supported within VS by default and shell extensions are not.

Both, Office and shell .NET extensions written properly and used with due caution won't do anything dangerous. You cannot deny it.

Lukasz

~~~~~~~~~~~~~~~~~~~
My website: www.lukesw.net

GeneralRe: Perhaps dotNET is not a good choice for writing shell extensions...
Stephen Hewitt
14:29 20 Apr '08  
Lukasz Swiatkowski wrote:
I have written that an application which is using standard libraries would require msvcrXX.dll file. I know that using compiler and linker switches you can create an app which need only kernel32.dll. But more libraries are needed to create a shell extension.


Like I've said a few times now, the fact that a C++ program depends on the "msvcrXX.dll" DLLs is not a problem: the use of multiple versions of a shared library in a process is only a problem if only one version can exist per process and this simply isn't the case with these libraries as it in with the CLR.

Lukasz Swiatkowski wrote:
And a shell extension like from this article wouldn't cause anything dangerous on machine with only one CLR installed.


True, but you can't control what other software the user has installed on his computer and which version of the CLR they use.

Lukasz Swiatkowski wrote:
Don't demonize .NET shell extension only because one of CLR creators said that they can be dangerous in certain situations.


Many experts agree, and Microsoft itself doesn't support the use of dotNET in this context. I'm not demonising dotNET, I'm just saying use the right tool for the right job.

Lukasz Swiatkowski wrote:
You cannot deny it.


What I deny is that using dotNET to write shell extensions is NOT dangerous.

I'm sick of arguing however. I've pointed out the problems and provided references.

Steve

GeneralRe: Perhaps dotNET is not a good choice for writing shell extensions...
Lukasz Swiatkowski
7:15 21 Apr '08  
Stephen Hewitt wrote:
Like I've said a few times now, the fact that a C++ program depends on the "msvcrXX.dll" DLLs is not a problem: the use of multiple versions of a shared library in a process is only a problem if only one version can exist per process and this simply isn't the case with these libraries as it in with the CLR.

I didn't say it's a problem. I only meant that these libraries are injected the same way CLR is. The only difference is that the CLR is injected only once.

Stephen Hewitt wrote:
True, but you can't control what other software the user has installed on his computer and which version of the CLR they use.
[...]
I'm just saying use the right tool for the right job.

You are right. But almost all .NET shell extensions I have ever seen were created for other developers or advanced users.
Of course I wouldn't use .NET if I had to create e.g. commercial shell extension which would be used by all people.
But if I created free shell extension and post it e.g. to the CodeProject I would attach a warning clause saying that there is recommended to have only one CLR installed in order to safely use this extension.

Stephen Hewitt wrote:
Many experts agree, and Microsoft itself doesn't support the use of dotNET in this context.

So unless the next version of CLR will support loading multiple CLR's into a process (like Silverlight CLR does), or the same problems will be occuring with Office .NET extensions. I assume that if Microsoft supports .NET extensions for Office, shell extensions which use CRL 2.0 also will be supported in future (or at least .NET 1.0/1.1 won't be anymore supported and available for download).

Stephen Hewitt wrote:
I'm sick of arguing however. I've pointed out the problems and provived references.

I also don't want to argue. I appreciate that you try to warn of improper usage of .NET Framework.

I only want to point that .NET shell extensions are not always dangerous, and when created properly and used with due caution, they can be safe.

Lukasz

~~~~~~~~~~~~~~~~~~~
My website: www.lukesw.net


Last Updated 10 Apr 2008 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010