Generating and Using P12 Certificates with BouncyCastle in .NET Core
This article explains how to generate PKCS#12 (p12) certificates using BouncyCastle in .NET Core, extract keys with OpenSSL, install the certificates on Windows, and demonstrates encryption and decryption with the generated keys, providing complete code samples and step‑by‑step instructions.
Introduction
The current situation of encryption and decryption motivates this series of articles: compatibility across Linux and Windows, language interoperability (C#, Java, etc.), inconsistent online documentation, and incomplete .NET built‑in cryptographic algorithms.
This series introduces how to use asymmetric encryption, encoding, message digest, signature, symmetric encryption, and national cryptographic algorithms in .NET Core, and welcomes corrections.
The goal is to help readers quickly and easily integrate encryption, even combining algorithms from the BouncyCastle cryptographic library.
Project code: https://github.com/fuluteam/ICH.BouncyCastle.git
Previous article: .NET Core Encryption Series – Symmetric Encryption
Dependencies
BouncyCastle ( https://www.bouncycastle.org/csharp ) is an open‑source lightweight cryptographic library that provides many algorithms not available in the .NET Core standard library.
Supported platforms: .NET 4, .NET Standard 1.0‑2.0, WP, Silverlight, MonoAndroid, Xamarin.iOS, .NET Core.
Function
Dependency
Portable.BouncyCastle
Portable.BouncyCastle • 1.8.6
Preface
When integrating external systems such as banks, Alipay, or WeChat, data encryption, decryption, signing, and verification are unavoidable. Third parties usually provide an authorized certificate for us to extract the private key. This guide shows how to handle a p12 certificate with C# code.
In practice, using OpenSSL is also common.
OpenSSL is the most popular SSL cryptographic library, offering a robust toolkit for implementing SSL/TLS protocols. Official site: https://www.openssl.org/source/
What is a p12 Certificate?
PKCS#12 (Public Key Cryptography Standards #12) defines a portable binary format for storing and transporting private keys, public keys, and certificates. Files in this format are also called PFX files.
A p12 certificate contains a private key, a public key, and is protected by a password; without the password the key cannot be extracted.
What is X.509 Format?
X.509 defines the standard format for public‑key certificates used in many Internet protocols, including TLS/SSL, which underpins HTTPS. An X.509 certificate contains a public key and an identifier (hostname, organization, or individual) signed by a Certificate Authority (CA) or self‑signed.
X.509 also defines Certificate Revocation Lists (CRL) and path validation algorithms, allowing certificates to be chained to trusted roots.
The standard is defined by the ITU‑T and based on ASN.1.
SSL Certificate (Encoding) Formats
SSL certificates are essentially X.509 certificates. X.509 uses ASN.1 to describe the certificate structure.
Common encodings include PEM, DER, PKCS#7, and PKCS#12. PEM and PKCS#7 use Base64 ASCII, while DER and PKCS#12 are binary.
X.509 Certificate Structure
The structure of an X.509 v3 certificate is described using ASN.1 and includes fields such as version, serial number, signature algorithm, issuer name, validity period, subject name, subject public key info, extensions, and the certificate signature.
Version Number
Serial Number
Signature Algorithm ID
Issuer Name
Validity Period (Not Before / Not After)
Subject Name
Subject Public Key Info
Public Key Algorithm
Subject Public Key
Issuer Unique Identifier (optional)
Subject Unique Identifier (optional)
Extensions (optional)
Certificate Signature Algorithm
Certificate Signature
Certificate Operations
Certificate Generation
///
/// Generate certificate
///
///
Certificate expiration time
///
Key length
///
Certificate password
///
Signature algorithm used for signing the certificate
///
Issuer DN
///
Subject DN
///
Friendly name (optional)
///
Certificate effective time
public static void GenerateCertificate(string filename, string password, string signatureAlgorithm, X509Name issuer, X509Name subject, DateTime notBefore, DateTime notAfter, string friendlyName, int keyStrength = 2048)
{
SecureRandom random = new SecureRandom(new CryptoApiRandomGenerator());
var keyGenerationParameters = new KeyGenerationParameters(random, keyStrength);
var keyPairGenerator = new RsaKeyPairGenerator(); // RSA key pair generator
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
ISignatureFactory signatureFactory = new Asn1SignatureFactory(signatureAlgorithm, subjectKeyPair.Private, random);
// the certificate generator
X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator();
var spki = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(subjectKeyPair.Public);
// Add extensions
certificateGenerator.AddExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(true));
certificateGenerator.AddExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifier(spki));
certificateGenerator.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifier(spki));
certificateGenerator.AddExtension(X509Extensions.ExtendedKeyUsage.Id, true, new ExtendedKeyUsage(KeyPurposeID.IdKPServerAuth));
// Serial number
BigInteger serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), random);
certificateGenerator.SetSerialNumber(serialNumber);
certificateGenerator.SetIssuerDN(issuer); // Issuer DN
certificateGenerator.SetSubjectDN(subject); // Subject DN
certificateGenerator.SetNotBefore(notBefore); // Valid from
certificateGenerator.SetNotAfter(notAfter); // Valid to
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
Org.BouncyCastle.X509.X509Certificate certificate = certificateGenerator.Generate(signatureFactory);
var certEntry = new X509CertificateEntry(certificate);
var store = new Pkcs12StoreBuilder().Build();
store.SetCertificateEntry(friendlyName, certEntry);
var chain = new X509CertificateEntry[1];
chain[0] = certEntry;
store.SetKeyEntry(friendlyName, new AsymmetricKeyEntry(subjectKeyPair.Private), chain);
using (var fs = File.Create(filename))
{
store.Save(fs, password.ToCharArray(), random); // Save
}
}
private static void Certificate_Sample()
{
// Issuer DN
var issuer = new X509Name(new ArrayList { X509Name.C, X509Name.O, X509Name.OU, X509Name.L, X509Name.ST },
new Hashtable {
[X509Name.C] = "CN",
[X509Name.O] = "Fulu Newwork",
[X509Name.OU] = "Fulu RSA CA 2020",
[X509Name.L] = "Wuhan",
[X509Name.ST] = "Hubei"
});
// Subject DN
var subject = new X509Name(new ArrayList { X509Name.C, X509Name.O, X509Name.CN },
new Hashtable {
[X509Name.C] = "CN",
[X509Name.O] = "ICH",
[X509Name.CN] = "*.fulu.com"
});
var password = "123456"; // certificate password
var signatureAlgorithm = "SHA256WITHRSA"; // signature algorithm
// Generate certificate
CertificateUtilities.GenerateCertificate("fuluca.pfx", password, signatureAlgorithm, issuer, subject,
DateTime.UtcNow.AddDays(-1), DateTime.UtcNow.AddYears(2), "fulu passport");
// Load certificate
X509Certificate2 pfx = new X509Certificate2("fuluca.pfx", password, X509KeyStorageFlags.Exportable);
var keyPair = DotNetUtilities.GetKeyPair(pfx.PrivateKey);
var subjectPublicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public);
var privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private);
var privateKey = Base64.ToBase64String(privateKeyInfo.ParsePrivateKey().GetEncoded());
var publicKey = Base64.ToBase64String(subjectPublicKeyInfo.GetEncoded());
Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.WriteLine("Pfx certificate private key:");
Console.WriteLine(privateKey);
Console.WriteLine("Pfx certificate public key:");
Console.WriteLine(publicKey);
var data = "hello rsa";
Console.WriteLine($"Plain text: {data}");
var pkcs1data = RSA.EncryptToBase64(data, AsymmetricKeyUtilities.GetAsymmetricKeyParameterFormPublicKey(publicKey), Algorithms.RSA_ECB_PKCS1Padding);
Console.WriteLine("Encryption result:");
Console.WriteLine(pkcs1data);
Console.WriteLine("Decryption result:");
var datares = RSA.DecryptFromBase64(pkcs1data, AsymmetricKeyUtilities.GetAsymmetricKeyParameterFormPrivateKey(privateKey), Algorithms.RSA_ECB_PKCS1Padding);
Console.WriteLine(datares);
}Certificate Installation
Double‑click the certificate file to install it, choosing "Current User" as the store location.
Store location: Personal.
View the installed certificate in the MMC Certificate snap‑in under Current User → Personal → Certificates.
Double‑click the certificate to see its details.
OpenSSL Installation
Tool: OpenSSL.
Install: Win64 OpenSSL v1.1.1g Light.
Download: http://slproweb.com/products/Win32OpenSSL.html
Extract Public/Private Keys from PFX
openssl pkcs12 -in fulusso.pfx -nocerts -nodes -out private.key
# Enter password
openssl rsa -in private.key -out pfx_pri.pem
openssl rsa -in private.key -pubout -out pfx_pub.pemAfter installing OpenSSL, open the "Win64 OpenSSL Command Prompt", navigate to the directory containing the certificate, and run the commands above.
The generated files private.key, pfx_pri.pem, and pfx_pub.pem can be opened with a text editor to verify that the keys match those printed by the console earlier.
Next Issue Preview
The next article will introduce national cryptographic algorithms. Stay tuned.
Fulu Network R&D Team
Providing technical literature sharing for Fulu Holdings' tech elite, promoting its technologies through experience summaries, technology consolidation, and innovation sharing.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.