How to create a snk from pfx / cer?
Asked Answered
P

3

43

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,

Patric answered 17/11, 2011 at 21:0 Comment(0)
F
43

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)

Flair answered 12/7, 2012 at 22:9 Comment(4)
I've published a command-line tool based on this in source code form on github. I did it in VB for the heck of it.Declivitous
This is not working for me I get crypto error when trying to use in VS2013.Dexterdexterity
edit queue is full or I'd add above: using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography;Cocainize
Well, it matters now, since 'dotnet build' says it doesn't support pfx files.Zeeba
L
33

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.

Lundell answered 27/3, 2012 at 22:34 Comment(5)
This gives me this error in VS2010: The key file 'key.snk' does not contain a public/private key pair.Thekla
It's possible that the pfk that you are working with is incomplete, I experienced this issue while working with .pfk files provided by Verisign specifically where they provide the .pfk, .cer, and .pfx files separately.Lundell
The -p make it only store the public part of the certificate in the snk. As you don't have the private key you can't use the generated snk to sign, except for delay signing, which doesn't change the end...Yellowknife
You can use sn -k keypair.snk to generate both the public and private keys. I don't have the slightest idea where it takes the private key from, but at least it works for my purposes. (and sorry for necroing this thread)Giffard
@Giffard FYI, from MSDN the -k option "Generates a new … key". This is why you don't have to reference an existing .pfxDilettante
G
22

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;"
Gingras answered 17/9, 2016 at 17:45 Comment(2)
Thanks! For some reason the last line did not output a file for me, but replacing it with: Set-Content -Path "$snkFilePath" -Value $snkBytes -Encoding Byte fixed that issue.Romaineromains
The file seems to put placed in the directory above where I ran the script. I changed the script's last line to: [IO.File]::WriteAllBytes((Join-Path -Path (Get-Location) -ChildPath $snkFileName), $snkBytes);Coupon

© 2022 - 2024 — McMap. All rights reserved.