Thursday, September 23, 2004

Public keys and public key tokens

spent some time this morning doing some "dotting the i's work" on my metadata engine. One of the things that I've been holding out implementing for a while because it wasn't particularly important in my test cases was adding some code that would generate efficient assembly references to strongly-named assemblies.

Strongly-named assemblies contain the signer's public key embedded in the assembly. You can see this by opening up the assembly using ILDASM and viewing its manifest. Inside the manifest, there must be a .publickey section if that assembly has a strong name. If you look at one of these sections, you'll see that it is rather large. For example, System.Web.dll's public key is 160 bytes in length.

When one assembly references a strongly-named assembly, it must embed a reference to the strongly-named assembly's public key to ensure that an attacker cannot substitute another assembly for the one that it was expecting to find. The simplest solution would be to simply embed the strongly-named assembly's public key in the referencing assembly. As you can see, however, this is not a very efficient solution due to the large size of the public keys.

Microsoft solves the "public key bloat" problem by using a hash of the strongly-named assembly's public key. These hashes are referred to as public key tokens, and are the low 8 bytes of the SHA1 hash of the strongly-named assembly's public key. SHA1 hashes are 160 bit (20 byte) hashes, and the top 12 bytes of the hash are simply discarded in this algorithm.

Calculating the SHA1 hash of an assembly's public key turns out to be really straightforward using the CryptoAPI hashing functions. Here's a simple piece of code that will generate an SHA1 hash:

bool CalculatePublicKeyToken( const unsigned char *publicKey, ULONG publicKeyLen )
if( 0 == publicKeyLen )
return true;

bool result = false;
unsigned char buffer[ 20 ];
DWORD bufferLen = 20;

if( CryptAcquireContext( &hProv, 0, 0, PROV_RSA_FULL, 0 ) )
if( CryptCreateHash( hProv, CALG_SHA1, 0, 0, &hHash ) )
if( CryptHashData( hHash, publicKey, publicKeyLen, 0 ) )
if( CryptGetHashParam( hHash, HP_HASHVAL, buffer, &bufferLen, 0 ) )
DumpPublicKeyToken( buffer, bufferLen );
result = true;

if( hHash ) CryptDestroyHash( hHash );
if( hProv ) CryptReleaseContext( hProv, 0 );

return result;

It is also possible to use the StrongNameTokenFromAssembly API defined in the StrongName.h header file in the Framework SDK. However, this API assumes that the assembly isn't loaded already, which certainly isn't the case in my code.


Post a Comment

<< Home