从 x509certificate2 对象导出 pem 格式的公钥 [英] exporting a public key in pem format from x509certificate2 object

查看:124
本文介绍了从 x509certificate2 对象导出 pem 格式的公钥的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是这个主题的新手,我对 PEM 格式和 CER 格式的公钥之间的区别感到困惑.

I'm new to this subject, and I got confused of the differences between a public key in PEM format vs CER format.

我正在尝试从 C# 代码中 PEM 格式的 x509certificate2 对象导出公钥.

I'm trying to export a public key from a x509certificate2 object in PEM format in c# code.

据我了解,cer 格式的证书与 pem 格式的证书的区别仅在于页眉和页脚(如果我理解正确,base 64 中 .cer 格式的证书应该是 someBase64String 而在 pem 格式中它是相同的字符串,包括开始和结束页眉和页脚.

As far as I understand, the difference between a certificate in cer format vs pem format, is only the header and footer (if I understand correctly, a certificate in .cer format in base 64 should be someBase64String and in pem format it's the same string including the begin and end header and footer).

但我的问题是关于公钥的.让 pubKey 成为从 x509certificate2 对象以 .cer 格式导出的公钥,是这个key的pem格式,将是:

but my question is for the public key. let pubKey be a public key exported in .cer format from an x509certificate2 object, is the pem format of this key, will be:

------BEGIN PUBLIC KEY-----
pubKey...
------END PUBLIC KEY------

以 base 64 编码?

encoded in base 64?

谢谢:)

推荐答案

用于公钥.让 pubKey 成为从 x509certificate2 对象以 .cer 格式导出的公钥

for the public key. let pubKey be a public key exported in .cer format from an x509certificate2 object

谈论.cer 格式"仅适用于您拥有完整证书的情况;这就是 X509Certificate2 将导出的全部内容.(好吧,或者一组证书,或者一组带有关联私钥的证书).

Talking about a ".cer format" only applies when you have the whole certificate; and that's all that an X509Certificate2 will export as. (Well, or a collection of certificates, or a collection of certificates with associated private keys).

.NET 中的任何内容都不会为您提供证书的 DER 编码的 SubjectPublicKeyInfo 块,这就是 PEM 编码下的PUBLIC KEY".

Nothing built in to .NET will give you the DER-encoded SubjectPublicKeyInfo block of the certificate, which is what becomes "PUBLIC KEY" under a PEM encoding.

如果需要,您可以自己构建数据.对于 RSA 来说,这还不算太糟糕,但并不完全令人愉快.数据格式定义在 https://tools.ietf.org/html/rfc3280#第 4.1 节:

You can build the data yourself, if you want. For RSA it's not too bad, though not entirely pleasant. The data format is defined in https://tools.ietf.org/html/rfc3280#section-4.1:

SubjectPublicKeyInfo  ::=  SEQUENCE  {
    algorithm            AlgorithmIdentifier,
    subjectPublicKey     BIT STRING  }

AlgorithmIdentifier  ::=  SEQUENCE  {
    algorithm               OBJECT IDENTIFIER,
    parameters              ANY DEFINED BY algorithm OPTIONAL  }

https://tools.ietf.org/html/rfc3279#section-2.3.1 描述了如何对 RSA 密钥进行编码,尤其是:

https://tools.ietf.org/html/rfc3279#section-2.3.1 describes how RSA keys, in particular are to be encoded:

rsaEncryption OID 旨在用于算法领域AlgorithmIdentifier 类型的值.参数字段必须此算法标识符的 ASN.1 类型为 NULL.

The rsaEncryption OID is intended to be used in the algorithm field of a value of type AlgorithmIdentifier. The parameters field MUST have ASN.1 type NULL for this algorithm identifier.

RSA 公钥必须使用 ASN.1 类型 RSAPublicKey 进行编码:

The RSA public key MUST be encoded using the ASN.1 type RSAPublicKey:

RSAPublicKey ::= SEQUENCE {
    modulus            INTEGER,    -- n
    publicExponent     INTEGER  }  -- e

这些结构背后的语言是 ASN.1,由 ITU X.680,它们被编码为字节的方式包含在 ITU X.690.

The language behind these structures is ASN.1, defined by ITU X.680, and the way they get encoded to bytes is covered by the Distinguished Encoding Rules (DER) ruleset of ITU X.690.

.NET 实际上给了你很多这样的部分,但你必须组装它们:

.NET actually gives you back a lot of these pieces, but you have to assemble them:

private static string BuildPublicKeyPem(X509Certificate2 cert)
{
    byte[] algOid;

    switch (cert.GetKeyAlgorithm())
    {
        case "1.2.840.113549.1.1.1":
            algOid = new byte[] { 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 };
            break;
        default:
            throw new ArgumentOutOfRangeException(nameof(cert), $"Need an OID lookup for {cert.GetKeyAlgorithm()}");
    }

    byte[] algParams = cert.GetKeyAlgorithmParameters();
    byte[] publicKey = WrapAsBitString(cert.GetPublicKey());

    byte[] algId = BuildSimpleDerSequence(algOid, algParams);
    byte[] spki = BuildSimpleDerSequence(algId, publicKey);

    return PemEncode(spki, "PUBLIC KEY");
}

private static string PemEncode(byte[] berData, string pemLabel)
{
    StringBuilder builder = new StringBuilder();
    builder.Append("-----BEGIN ");
    builder.Append(pemLabel);
    builder.AppendLine("-----");
    builder.AppendLine(Convert.ToBase64String(berData, Base64FormattingOptions.InsertLineBreaks));
    builder.Append("-----END ");
    builder.Append(pemLabel);
    builder.AppendLine("-----");

    return builder.ToString();
}

private static byte[] BuildSimpleDerSequence(params byte[][] values)
{
    int totalLength = values.Sum(v => v.Length);
    byte[] len = EncodeDerLength(totalLength);
    int offset = 1;

    byte[] seq = new byte[totalLength + len.Length + 1];
    seq[0] = 0x30;

    Buffer.BlockCopy(len, 0, seq, offset, len.Length);
    offset += len.Length;

    foreach (byte[] value in values)
    {
        Buffer.BlockCopy(value, 0, seq, offset, value.Length);
        offset += value.Length;
    }

    return seq;
}

private static byte[] WrapAsBitString(byte[] value)
{
    byte[] len = EncodeDerLength(value.Length + 1);
    byte[] bitString = new byte[value.Length + len.Length + 2];
    bitString[0] = 0x03;
    Buffer.BlockCopy(len, 0, bitString, 1, len.Length);
    bitString[len.Length + 1] = 0x00;
    Buffer.BlockCopy(value, 0, bitString, len.Length + 2, value.Length);
    return bitString;
}

private static byte[] EncodeDerLength(int length)
{
    if (length <= 0x7F)
    {
        return new byte[] { (byte)length };
    }

    if (length <= 0xFF)
    {
        return new byte[] { 0x81, (byte)length };
    }

    if (length <= 0xFFFF)
    {
        return new byte[]
        {
            0x82,
            (byte)(length >> 8),
            (byte)length,
        };
    }

    if (length <= 0xFFFFFF)
    {
        return new byte[]
        {
            0x83,
            (byte)(length >> 16),
            (byte)(length >> 8),
            (byte)length,
        };
    }

    return new byte[]
    {
        0x84,
        (byte)(length >> 24),
        (byte)(length >> 16),
        (byte)(length >> 8),
        (byte)length,
    };
}

DSA 和 ECDSA 密钥具有更复杂的 AlgorithmIdentifier.parameters 值,但 X509Certificate 的 GetKeyAlgorithmParameters() 恰好将它们返回正确格式,因此您只需要记下它们的 OID(字符串)查找键和它们的 OID(字节[]) switch 语句中的编码值.

DSA and ECDSA keys have more complex values for AlgorithmIdentifier.parameters, but X509Certificate's GetKeyAlgorithmParameters() happens to give them back correctly formatted, so you would just need to write down their OID (string) lookup key and their OID (byte[]) encoded value in the switch statement.

我的 SEQUENCE 和 BIT STRING 构建器肯定可以更高效(哦,看看那些糟糕的数组),但这对于性能不重要的东西就足够了.

My SEQUENCE and BIT STRING builders can definitely be more efficient (oh, look at all those poor arrays), but this would suffice for something that isn't perf-critical.

要检查您的结果,您可以将输出粘贴到 openssl rsa -pubin -text -noout,如果它打印出除错误之外的任何内容,您已制作了合法编码的公钥"RSA 密钥的编码.

To check your results, you can paste the output to openssl rsa -pubin -text -noout, and if it prints anything other than an error you've made a legally encoded "PUBLIC KEY" encoding for an RSA key.

这篇关于从 x509certificate2 对象导出 pem 格式的公钥的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆