简介
加解密现状,编写此系列文章的背景:
- 需要考虑系统环境兼容性问题(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证书的签名
证书操作
证书生成
///
/// 生成证书
///
/// 证书失效时间
/// 密钥长度
/// 证书密码
/// 设置将用于签署此证书的签名算法
/// 设置此证书颁发者的DN
/// 设置此证书使用者的DN
/// 设置证书友好名称(可选)
/// 证书生效时间
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中添加,步骤如下:
- 开始→运行→MMC,打开一个空的MMC控制台。
- 在控制台菜单,文件→添加/删除管理单元→添加按钮→选”证书”→添加→选”我的用户账户”→关闭→确定
展开 证书控制台根节点→证书-当前用户→个人→证书,找到证书,可以看到下图中选中的即为我们创建的证书文件
双击证书,可以看到证书的相关信息
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,如下图:
比对与上文控制台打印出的公钥、私钥一致。
下期预告
下一篇将介绍国密算法,敬请期待。。。