How to create a snk from pfx / cer?

27.6k views Asked by At

Microsoft seems to have created a certification jungle, that is hard to understand.

  • Microsoft X.509 certificate (.cer)
  • Personal Information Exchange (.pfx)
  • Assembly Signature Key Attribute (.snk)

    1. Would it be advisable to create an snk file based on pfx or cer? (Not sure if its even possible, and if its possible how is it done?)

    2. While an assembly can be signed with a password protected pfx, it doesn't seem to be strong named though, unless it is signed with snk instead. But the snk has no password protection. Which one is safer to use? As I am the only developer in my project, I don't have the multi-developer saftey environment issue, but still would like to know what is best approach.

Many Thanks,

3

There are 3 answers

4
Sir Kill A Lot On BEST ANSWER

A little clarification about your mentioned file types:

  • .cer-files are X.509 Certificates
  • .pfx-files are encrypted X.509 Certificates using a password-based symmetric key, also see PKCS #12 (Wikipedia)
  • .snk-files only contain the RSA key (public/private or public only)

It doesn't matter if you sign an assembly using .pfx-files or .snk-files, it will get strong named either way. Storing the RSA key as a encrypted certificate (.pfx) is of course more secure than storing just the unencrypted key (.snk).

You can easily extract the key from those files in code using class System.Security.Cryptography.X509Certificates.X509Certificate2.

To extract key from .pfx:

/// <summary>
/// Converts .pfx file to .snk file.
/// </summary>
/// <param name="pfxData">.pfx file data.</param>
/// <param name="pfxPassword">.pfx file password.</param>
/// <returns>.snk file data.</returns>
public static byte[] Pfx2Snk(byte[] pfxData, string pfxPassword)
{
    // load .pfx
    var cert = new X509Certificate2(pfxData, pfxPassword, X509KeyStorageFlags.Exportable);

    // create .snk
    var privateKey = (RSACryptoServiceProvider)cert.PrivateKey;
    return privateKey.ExportCspBlob(true);
}

Use privateKey.ExportCspBlob(false) to extract public key only! (e.g. for delay-signing of assemblies)

5
punkcoder On

To generate a snk file with solely the public key from a pfx:

sn -p keypair.pfx key.snk

I have always been a fan of using snk files over .pfx files they just seem less buggy.

2
David Woodward On

Here's the same method provided by @Sir Kill A Lot in his answer, but converted to a PowerShell script (pfx2snk.ps1).

Param(
    [Parameter(Mandatory=$True,Position=1)]
    [string] $pfxFilePath,
    [string] $pfxPassword
)

# The path to the snk file we're creating
[string] $snkFilePath = [IO.Path]::GetFileNameWithoutExtension($pfxFilePath) + ".snk";

# Read in the bytes of the pfx file
[byte[]] $pfxBytes = Get-Content $pfxFilePath -Encoding Byte;

# Get a cert object from the pfx bytes with the private key marked as exportable
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(
    $pfxBytes,
    $pfxPassword,
    [Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable);

# Export a CSP blob from the cert (which is the same format as an SNK file)
[byte[]] $snkBytes = ([Security.Cryptography.RSACryptoServiceProvider]$cert.PrivateKey).ExportCspBlob($true);

# Write the CSP blob/SNK bytes to the snk file
[IO.File]::WriteAllBytes($snkFilePath, $snkBytes);

Just run that script providing the pfx file path and password and it will make an snk file in the same directory as the pfx file (with the same name other than the extension).

powershell.exe -File pfx2snk.ps1 -pfxFilePath cert.pfx -pfxPassword "pfx password"

Or, if your pfx doesn't have a password (shame, shame):

powershell.exe -File pfx2snk.ps1 cert.pfx

And, if you're unfortunate enough to be working in an environment where they don't allow PowerShell scripts to execute (ie. interactive PowerShell sessions only), then you can execute this ugly one liner from a standard cmd.exe command line (replacing file paths and pfx password as needed).

powershell.exe -Command "[IO.File]::WriteAllBytes('SnkFilePath.snk', ([Security.Cryptography.RSACryptoServiceProvider](New-Object System.Security.Cryptography.X509Certificates.X509Certificate2((Get-Content 'PfxFilePath.pfx' -Encoding Byte), 'PfxPassword', [Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)).PrivateKey).ExportCspBlob($true));"

I actually use that one-liner as a standard part of my Visual Studio pre-build process to automate the process of using the same keys from our authenticode signature certs (pfx file) for strong name signing. That's not a requirement, but it just seems to make sense to me that they should be the same and it feeds my OCD tendencies.

(I use an snk file rather than the original pfx because I've had the "buggy" experience using pfx files for strong name signing that @punkcoder mentioned in his answer)

And, if you're interested, I have something like the following as a part of my post-build process in Visual Studio to add the authenticode signature to the project output (in "Release" project configurations anyway).

powershell.exe -Command "Set-AuthenticodeSignature -FilePath '$(TargetPath)' -Certificate '$(SolutionDir)MyCert.pfx' -TimestampServer http://timestamp.verisign.com/scripts/timstamp.dll -HashAlgorithm sha256;"