
Contents
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.
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.
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!
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
{
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();
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.
|
|
 |
 | Good work SplashBrazil | 13:21 7 Jan '10 |
|
 |
Good work men! 
|
|
|
|
 |
 | WIN+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.
|
|
|
|
 |
 | Perhaps 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
|
|
|
|
 |
|
 |
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
|
|
|
|
 |
|
 |
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
|
|
|
|
 |
|
 |
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
|
|
|
|
 |
|
 |
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
|
|
|
|
 |
|
 |
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
|
|
|
|
 |
|
 |
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
|
|
|
|
 |
|
 |
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
|
|
|
|
 |
|
 |
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
|
|
|
|
 |
|
 |
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
|
|
|
|
 |
|
|