
Introduction
This application shows how to parse Assembly "strong name keyfiles" generated with sn.exe.
Background
System.Security.Cryptography
provides functions to work with RSA keypairs. These functions are wrappers on the Win32 Crypto API. However, the methods provided in the 1.1 Framework do not easily parse SNK keyfiles created by sn.exe. The reason is that none of the methods will accept a PRIVATEKEYBLOB
or PUBLICKEYBLOB
structure directly. Therefore, it is necessary to write some extra code to parse those structures. Furthermore, there are additional (some would say obscure) tweaks on CpParameters
required to get an SNK keyfile to import correctly.
Using the code
You can make a new assembly keypair by running sn.exe which is available from the Framework SDK. To make a new keypair run "sn.exe -k keypair.snk". To export just the public key from your keypair, use "sn.exe -p keypair.snk pubkey.pub".
The app I wrote works pretty much as you would expect. You can create new keys, or open an SNK/PUB keypair files by pressing the buttons. If you are using a public key file, the "sign it" button will be disabled. You can also open an XML file with an encoded RSA keypair.
After your keypair is loaded, type some text on the left, hit "sign it", and then hit "verify it". Test that the signature verification really works by twiddling one character in the signature or in the original plaintext, then hitting "verify it" again - it should fail.
The guts of the app lie in RSA1024Util.cs. I hardcoded the class to use 1024 bit encryption because currently sn.exe only generates 1024 bit keyfiles. (As an aside, I wonder why 2048 bits aren't supported for assembly keys? Cryptographers have lately been warning about the eventual fall of 1024 bit keys..)
Here is the function that converts the SNK byte buffer to RSAParameters
. If the buffer is "public length" - 160 bytes - then it will only fill in the public key fields, "exponent" and "modulus". Otherwise it will read the rest of the private key fields.
The byte offsets were discovered by digging around the Win32 CryptoAPI documentation and helpful articles by Dr. Michel I. Gallant, of http://jensign.com . Note that each byte array must be reversed due to a big endian vs. little endian ordering issue.
public const int _magic_priv_idx = 0x08;
public const int _magic_pub_idx = 0x14;
public const int _magic_size = 4;
public static RSAParameters FigureParams(
byte[] keypair)
{
RSAParameters ret = new RSAParameters();
if ((keypair == null) || (keypair.Length < 1))
return ret;
bool pubonly = SnkBufIsPubLength(keypair);
if ((pubonly) && (!CheckRSA1(keypair)))
return ret;
if ((!pubonly) && (!CheckRSA2(keypair)))
return ret;
int magic_idx = pubonly ? _magic_pub_idx : _magic_priv_idx;
int bitlen_idx = magic_idx + _magic_size;
int bitlen_size = 4;
int exp_idx = bitlen_idx + bitlen_size;
int exp_size = 4;
int mod_idx = exp_idx + exp_size;
int mod_size = 128;
int p_idx = mod_idx + mod_size;
int p_size = 64;
int q_idx = p_idx + p_size;
int q_size = 64;
int dp_idx = q_idx + q_size;
int dp_size = 64;
int dq_idx = dp_idx + dp_size;
int dq_size = 64;
int invq_idx = dq_idx + dq_size;
int invq_size = 64;
int d_idx = invq_idx + invq_size;
int d_size = 128;
ret.Exponent = BlockCopy(keypair, exp_idx, exp_size);
Array.Reverse(ret.Exponent);
ret.Modulus = BlockCopy(keypair, mod_idx, mod_size);
Array.Reverse(ret.Modulus);
if (pubonly) return ret;
ret.P = BlockCopy(keypair, p_idx, p_size);
Array.Reverse(ret.P);
ret.Q = BlockCopy(keypair, q_idx, q_size);
Array.Reverse(ret.Q);
ret.DP = BlockCopy(keypair, dp_idx, dp_size);
Array.Reverse(ret.DP);
ret.DQ = BlockCopy(keypair, dq_idx, dq_size);
Array.Reverse(ret.DQ);
ret.InverseQ = BlockCopy(keypair, invq_idx, invq_size);
Array.Reverse(ret.InverseQ);
ret.D = BlockCopy(keypair, d_idx, d_size);
Array.Reverse(ret.D);
return ret;
}
There is one other not-well-documented switch that needs to be thrown in order to get the SNK file to be imported correctly. Keys generated by sn.exe have the KeyNumber set to 2, which is "AT_SIGNATURE
". For more information see http://www.jensign.com/JavaScience/dotnet/keyinfo/.
protected void CtorFromSnkBuf(byte[] snkbuf)
{
if ((snkbuf == null) || (snkbuf.Length < 1))
{
CtorFromXml(null);
return;
}
RSAParameters param = FigureParams(snkbuf);
CspParameters cp = new CspParameters();
cp.KeyNumber = 2;
_rsa = new RSACryptoServiceProvider(1024, cp);
_rsa.ImportParameters(param);
}
Why did the Framework authors make it inconvenient to work with SNK files generated by sn.exe? I don't know, but in any case there's the code to work around that issue.