首頁 > 軟體

.NET Core加解密實戰系列之——使用BouncyCastle製作p12(.pfx)數字證書

2020-09-23 12:00:23

簡介

加解密現狀,編寫此係列文章的背景:

  • 需要考慮系統環境相容性問題(Linux、Windows)
  • 語言互通問題(如C#、Java等)(加解密本質上沒有語言之分,所以原則上不存在互通性問題)
  • 網上資料版本不一、或不全面
  • .NET官方庫密碼演算法提供不全面,很難針對其他語言(Java)進行適配

本系列文章主要介紹如何在 .NET Core 中使用非對稱加密演算法、編碼演算法、訊息摘要演算法、簽名演算法、對稱加密演算法、國密演算法等一系列演算法,如有錯誤之處,還請大家批評指正。

本系列文章旨在引導大家能快速、輕鬆的瞭解接入加解密,乃至自主組合搭配使用BouncyCastle密碼術包中提供的演算法。

本系列程式碼項目地址:https://github.com/fuluteam/ICH.BouncyCastle.git

上一篇文章《.NET Core加解密實戰系列之——對稱加密演算法》:https://www.cnblogs.com/fulu/p/13650079.html

功能依賴

BouncyCastle(https://www.bouncycastle.org/csharp) 是一個開放源碼的輕量級密碼術包;它支援大量的密碼術演算法,它提供了很多 .NET Core標準庫沒有的演算法。

支援 .NET 4,.NET Standard 1.0-2.0,WP,Silverlight,MonoAndroid,Xamarin.iOS,.NET Core

功能 依賴
Portable.BouncyCastle Portable.BouncyCastle • 1.8.6

前言

在工作中我們難免會接觸對接外部系統(如銀行、支付寶、微信等),對接過程中又無可避免會對資料的加解密和加簽驗籤。一般第三方會提供一個授權證書,讓我們自行解密提取祕鑰。為了讓你拿到證書後不會像我當初一樣一臉懵逼,咱們來看看如何使用C#程式碼製作使用p12證書。

當然,比較常見的,還是推薦大家使用OpenSSL。

OpenSSL是目前最流行的 SSL密碼庫工具,其提供了一個通用、健壯、功能完備的工具套件,用以支援SSL/TLS 協議的實現。
官網:https://www.openssl.org/source/

什麼是p12證書

公鑰加密技術12號標準(Public Key Cryptography Standards #12,PKCS#12)為儲存和傳輸使用者或伺服器私鑰、公鑰和證書指定了一個可移植的格式。它是一種二進位制格式,這些檔案也稱為PFX檔案。

P12證書包含了私鑰、公鑰並且有口令保護,在證書洩露後還有最後一道保障。沒有證書口令無法提取祕鑰。
對PKCS標準感興趣的小夥伴可以參考百度百科PKCS介紹

什麼是X.509格式

在密碼學中,X.509是定義公鑰證書格式的標準。X.509證書用於許多Internet協議,包括TLS/SSL,它是HTTPS(用於瀏覽web的安全協議)的基礎。它們也用於離線應用程式,比如電子簽名。一個X.509證書包含一個公鑰和一個標識(主機名、組織或個人),由證書頒發機構簽名或自簽名。當證書由受信任的證書頒發機構簽名時,或者通過其他方法進行驗證時,持有該證書的人可以依賴於它包含的公鑰來與另一方建立安全通訊,或者驗證由相應私鑰數字簽名的文件。

X.509還定義了證書撤銷列表,這是一種分發被簽名機構認為無效的證書資訊的方法,以及認證路徑驗證演算法,該演算法允許證書由中間CA證書籤名,而中間CA證書又由其他證書籤名,最終到達信任錨。

X.509由國際電信聯盟標準化部門(ITU-T)定義,並基於ITU-T的另一個標準ASN.1。

SSL Certificate (編碼)格式

SSL Certificate實際上就是X.509 Certificate。X.509是一個定義了certificate結構的標準。它在SSL certificate中定義了一個數據域。X.509使用名為 Abstract Syntax Notation One (ASN.1)的通用語言來描述certificate的資料結構。

X.509 certificate 有幾種不同的格式,例如 PEM,DER,PKCS#7 和 PKCS#12。 PEM和PKCS#7格式使用Base64 ASCII編碼,而DER和PKCS#12使用二進位制編碼。certificate檔案基於不同的編碼格式有不同的副檔名。

如下圖就展示了X.509證書的編碼方式和副檔名。

X.509 證書結構

X.509證書的結構是用ASN.1(Abstract Syntax Notation One:抽象語法標記)來描述其資料結構,並使用ASN1語法進行編碼。

  • X.509 v3數字證書的結構如下:
  • certificate 證書
  • Version Number版本號
  • Serial Number序列號
  • ID Signature Algorithm ID簽名演算法
  • Issuer Name頒發者名稱
  • Validity period 有效期
  • Not before起始日期
  • Not after截至日期
  • Subject Name主題名稱
  • Subject pbulic Key Info 主題公鑰資訊
  • Public Key Algorithm公鑰演算法
  • Subject Public Key主題公鑰
  • Issuer Unique Identifier (optional)頒發者唯一標識符(可選)
  • Subject Unique Identifier (optional)主題唯一標識符(可選)
  • Extensions (optional) 證書的擴展項(可選)
  • Certificate Sigature Algorithm證書籤名演算法
  • Certificate Signature證書的簽名

證書操作

證書生成

/// <summary>
/// 生成證書
/// </summary>
/// <param name="notAfter">證書失效時間</param>
/// <param name="keyStrength">金鑰長度</param>
/// <param name="password">證書密碼</param>
/// <param name="signatureAlgorithm">設定將用於簽署此證書的簽名演算法</param>
/// <param name="issuer">設定此證書頒發者的DN</param>
/// <param name="subject">設定此證書使用者的DN</param>
/// <param name="friendlyName">設定證書友好名稱(可選)</param>
/// <param name="notBefore">證書生效時間</param>
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金鑰對生成器
    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);

    //設定一些擴展欄位
    //允許作為一個CA證書(可以頒發下級證書或進行簽名)
    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));

    //證書序列號
    BigInteger serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), random);
    certificateGenerator.SetSerialNumber(serialNumber);

    certificateGenerator.SetIssuerDN(issuer);   //頒發者資訊
    certificateGenerator.SetSubjectDN(subject); //使用者資訊
    certificateGenerator.SetNotBefore(notBefore);   //證書生效時間
    certificateGenerator.SetNotAfter(notAfter); //證書失效時間
    certificateGenerator.SetPublicKey(subjectKeyPair.Public);

    Org.BouncyCastle.X509.X509Certificate certificate = certificateGenerator.Generate(signatureFactory);

    //生成cer證書,公鑰證書
    //var certificate2 = new X509Certificate2(DotNetUtilities.ToX509Certificate(certificate))
    //{
    //    FriendlyName = friendlyName, //設定友好名稱
    //};
    ////cer公鑰檔案
    //var bytes = certificate2.Export(X509ContentType.Cert);
    //using (var fs = new FileStream(certPath, FileMode.Create))
    //{
    //    fs.Write(bytes, 0, bytes.Length);
    //}

    //另一種程式碼生成p12證書的方式(要求使用.net standard 2.1)
    //certificate2 =
    //             certificate2.CopyWithPrivateKey(DotNetUtilities.ToRSA((RsaPrivateCrtKeyParameters)keyPair.Private));

    //var bytes2 = certificate2.Export(X509ContentType.Pfx, password);
    //using (var fs = new FileStream(pfxPath, FileMode.Create))
    //{
    //    fs.Write(bytes2, 0, bytes2.Length);
    //}

    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); //儲存  
    };

}


private static void Certificate_Sample()
{
    //頒發者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",
    });
    //使用者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";    //證書密碼
    var signatureAlgorithm = "SHA256WITHRSA"; //簽名演算法

    //生成證書
    CertificateUtilities.GenerateCertificate("fuluca.pfx", password, signatureAlgorithm, issuer, subject, DateTime.UtcNow.AddDays(-1), DateTime.UtcNow.AddYears(2), "fulu passport");

    //載入證書
    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證書私鑰:");
    Console.WriteLine(privateKey);

    Console.WriteLine("Pfx證書公鑰:");
    Console.WriteLine(publicKey);

    var data = "hello rsa";

    Console.WriteLine($"加密原文:{data}");

    var pkcs1data = RSA.EncryptToBase64(data, AsymmetricKeyUtilities.GetAsymmetricKeyParameterFormPublicKey(publicKey), Algorithms.RSA_ECB_PKCS1Padding);

    Console.WriteLine("加密結果:");
    Console.WriteLine(pkcs1data);

    Console.WriteLine("解密結果:");
    var datares = RSA.DecryptFromBase64(pkcs1data,
        AsymmetricKeyUtilities.GetAsymmetricKeyParameterFormPrivateKey(privateKey), Algorithms.RSA_ECB_PKCS1Padding);

    Console.WriteLine(datares);
}

生成的證書檔案:

證書安裝

雙擊證書檔案進行安裝,儲存位置選擇當前使用者。

證書儲存選擇個人

檢視安裝的證書

可以在MMC的證書管理單元中對證書儲存區進行管理。Windows沒有給我們準備好直接的管理證書的入口。自己在MMC中新增,步驟如下:

  1. 開始→運行→MMC,開啟一個空的MMC控制檯。
  2. 在控制檯選單,檔案→新增/刪除管理單元→新增按鈕→選”證書”→新增→選”我的使用者賬戶”→關閉→確定

展開 證書控制檯根節點→證書-當前使用者→個人→證書,找到證書,可以看到下圖中選中的即為我們創建的證書檔案

雙擊證書,可以看到證書的相關資訊

OpenSSL安裝

工具:openssl

安裝軟體:Win64 OpenSSL v1.1.1g Light

下載地址:http://slproweb.com/products/Win32OpenSSL.html

PFX檔案提取公鑰私鑰

openssl pkcs12 -in fulusso.pfx -nocerts -nodes -out private.key

輸入密碼

openssl rsa -in private.key -out pfx_pri.pem

openssl rsa -in private.key -pubout -out pfx_pub.pem

安裝好OpenSSL後,開啟Win64 OpenSSL Command Prompt,讀取到證書檔案所在目錄,按上述命令執行

開啟證書所在目錄,可以看到檔案 private.key、pfx_pri.pem、pfx_pub.pem 已經生成好了。

用文字工具開啟私鑰檔案pfx_pri.pem,如下圖:

開啟公約檔案pfx_pub.pem,如下圖:

比對與上文控制檯打印出的公鑰、私鑰一致。

下期預告

下一篇將介紹國密演算法,敬請期待。。。

福祿ICH·架構組 福祿娃

IT145.com E-mail:sddin#qq.com