How to create a snk from pfx / cer?

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)


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.


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;"