PVK file information.

PVK files are used to store private keys for code signing in various Microsoft products. Until now very little was known about the format. It is not officially documented.

Nevertheless I have recently determined sufficient information about the latest format to read and write PVK files in a form that is acceptable to various tools that use PVK files.

This information was not derived using anything other that freely documented structures and the examination of some sample files. There is as always some guesswork involved so this may not be totally accurate just yet.

This description refers to the PVK format used by Xenroll version 5.102.1680.101 and signcode version 5.101.1663.1 it may well be incomplete or inaccurate if other versions are used.

File Description.

All integers are in little endian format. A DWORD is a 4 byte unsigned integer (unsigned long).

The file consists of three sections. A header, a salt (optional) and  the private key in either encrypted or unencrypted form.

Header.

The start of the file is a header which consists of several DWORDs. The precise meaning of these is unknown but here is my interpretation.

DWORD magic;
DWORD reserved;
DWORD keytype;
DWORD encrypted;
DWORD saltlen;
DWORD keylen;

magic is a magic number it appears to be 0xb0b5f11e for the current version.

The meaning of reserved is unknown. It was always zero in the files I tested.

keytype is the type of key. It is 1 for a key exchange key and 2 for a signature key. It must be consistent with the type of key defined in the aiKeyAlg field in the BLOBHEADER structure (CALG_RSA_KEYX and CALG_RSA_SIGN).

encrypted is zero for an unencrypted private key and 1 for an encrypted private key.

saltlen is the salt length. It is zero for an unencrypted key file and 0x10 for an encrypted key file.

keylen is the length of the key data.

Salt.

The salt is just saltlen bytes of data which is used as a salt in encrypted key files. If saltlen is zero then the salt is absent.

Private Key.

The private key is in the format of a PRIVATEKEYBLOB. This is the format used by CryptoAPI for the CryptImportKey() and CryptExportKey() functions. For more information check out the CryptoAPI  KEYBLOB documentation on Microsoft's site.

In the unencrypted file the whole PRIVATEKEYBLOB is unencrypted. For the encrypted case just the BLOBHEADER structure is unencrypted.

Encryption and key derivation.

There are two separate key derivation algorithms in used. If the domestic (128 bit) security patch has been applied and the registry fix made then one form is used: I'll refer to this as strong. If the 128 bit patch has not been applied or the patch has been applied but the registry fix has not then another form is used: I'll refer to this as weak.

In both cases the encrypted private key is encrypted using 128 bit RC4. The key is derived using salted SHA1.

In the strong version the key is derived by taking the first 16 bytes  (128 bits) of SHA1(salt+password) where the + sign denotes concatenation and the password is a null terminated ASCII string (the null character is not included in the derivation).

The weak version is the same except the key is derived by taking the first 5 bytes  (40 bits) of SHA1(salt+password) followed by 11 zero bytes.

Since the first four bytes of the magic field of the encrypted RSAPUBKEY part of the encrypted data are always "RSA2" this can be used to check the password is correct.

There is no apparent indication as to which key derivation algorithm is used. My sample code tries both and looks for the "RSA2" string on the decrypted data.

This behaviour is apparently anomalous because the signing tools just use the default CSP and cannot recognize both formats automatically. However if you have applied the domestic patch and the registry fix then you can still access the weakly encrypted files by using the -p option to specify the CSP. That is you would use: -p "Microsoft Base Cryptographic Provider v1.0". If it doesn't work then double check the spelling!

Conversion Tools.

I have written a conversion tool that will convert a standard RSA key in OpenSSL PEM format into a PVK file and vice versa. You can download the Win32 binary here.

Note: I've had numerous reports of DLL problems with the older Win32 binaries, so I'm now statically linking and the OpenSSL DLLs are longer be needed. The down side to this is that the binaries are considerably larger.

The latest source is here as a gziped tar file. The .bin extension is to avoid server problems.

OpenSSL 0.9.9 will include integrated support for PVK conversion.

If there are any problems with these binaries producing errors on what appear to be valid PVK files then mail me at the address on my contact page.

Security Considerations.

There are several security issues relating to PVK files. If there is any demand I'll expand on them. Points of note include: weak encryption in some cases, digesting of salt first, ease of dictionary attack and large quantities of known plaintext in the encrypted data.