正如 Benny 提到的,这是一个相当老的线程,但我处于同样的情况,我找不到一个真正令人满意的解决方案,首先将 .cer 文件转换为 .pem 文件,然后提取(在我的情况)旧的主题散列,这是 android 所必需的。最后,我能找到的最佳解决方案是使用 BouncyCastle nuget 包。因此,以下代码基于该库。
using Org.BouncyCastle.X509;
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
namespace CertificateTest
{
public class CertificateService
{
private readonly X509Certificate certificate;
public string AsPem
{
get
{
var base64Pem = Convert.ToBase64String(certificate.GetEncoded());
var wrappedPem = string.Join("\n", SplitByLength(base64Pem, 64));
return $"-----BEGIN CERTIFICATE-----\n{wrappedPem}\n-----END CERTIFICATE-----";
}
}
public string SubjectHash => CreateHash(certificate.SubjectDN.GetDerEncoded(), SHA1.Create());
public string SubjectHashOld => CreateHash(certificate.SubjectDN.GetDerEncoded(), MD5.Create());
public CertificateService(string certificateFilePath)
{
certificate = new X509CertificateParser().ReadCertificate(File.ReadAllBytes(certificateFilePath));
}
// hashing based on https://github.com/openssl/openssl/blob/master/crypto/x509/x509_cmp.c
private string CreateHash(byte[] subjectName, HashAlgorithm hashAlgorithm)
{
var hashBytes = hashAlgorithm.ComputeHash(subjectName);
ulong ret = (
((ulong)hashBytes[0]) |
((ulong)hashBytes[1] << 8) |
((ulong)hashBytes[2] << 16) |
((ulong)hashBytes[3] << 24)
) & 0xffffffff;
return ret.ToString("X2").PadLeft(8, '0').ToLower();
}
// Taken from: https://stackoverflow.com/a/3008775/773339
private IEnumerable<string> SplitByLength(string str, int maxLength)
{
for (var index = 0; index < str.Length; index += maxLength)
{
yield return str.Substring(index, Math.Min(maxLength, str.Length - index));
}
}
}
}
用法很简单:
private static void Main(string[] args)
{
var service = new CertificateService("my-certificate.pem"); // new CertificateService("my-certificate.cer");
Debug.WriteLine(service.AsPem);
Debug.WriteLine(service.SubjectHash);
Debug.WriteLine(service.SubjectHashOld);
}
如您所见,我已经可以在这样的线程中找到一些代码(例如散列)。对我来说,真正的突破是使用 BouncyCastle 库而不是内置类,并使用它完全加载证书并从中提取所有信息。手动解析和以其他方式编码 SubjectDN 让我无处可去。
由于Convert.ToBase64String 本身不能在 64 个字符后分割行,因此这里通过使用堆栈溢出中的引用代码手动完成。