Get certificate's public key in the PKCS8 format

Dmitrii 0 Reputation points
2025-01-16T07:32:41.87+00:00

Environment:

Windows 11 24H2

.Net Framework 4.8, 4.7.2

 

Problem:

I do not understand how to get server's certificate public key in PKCS8 format using the X509Certificate class. 

 

Explanation:

I'm implementing the custom server's certificate check for SSL connection (aka certificate pinning) in the next way:

  • Create a new SSLStream:
SslStream sslStream = new SslStream(
    client.GetStream(),
    false,
    new RemoteCertificateValidationCallback(ValidateServerCertificate),
    null);

Declare the ValidateServerCertificate() method:

public static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) {}

Get server's certificate public key

certificate.GetPublicKey()

 

The public key which I get is in the PKCS1 format and I can't find any function in .Net Framework to get it in the PKCS8 format or to convert from PCKS1 to PKCS8. As a fallback, I'm using the static prefix for the PKCS8 format which works well but is valid only for 2048 bits long public keys:

private static byte[] pkcs8Prefix = { 0x30, 0x82, 0x01, 0x22, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0F, 0x00 };
byte[] pkcs8PublicKey = pkcs8Prefix.Concat(certificate.GetPublicKey()).ToArray();

 

Which function of .Net Framework can I use to get the public key in the PKCS8 format?

.NET
.NET
Microsoft Technologies based on the .NET software framework.
4,103 questions
{count} votes

1 answer

Sort by: Most helpful
  1. Jiale Xue - MSFT 49,361 Reputation points Microsoft External Staff
    2025-01-16T09:10:22.5566667+00:00

    Hi @Dmitrii , Welcome to Microsoft Q&A,

    In the .NET Framework, the X509Certificate class itself does not directly provide the function of converting the public key from PKCS1 format to PKCS8 format.

    The official documentation currently mainly uses the PKCS7 format, X509Certificate Constructors.

    Perhaps you can use the classes in the System.Security.Cryptography namespace to parse the PKCS1 format and encapsulate it into the PKCS8 format.

    Extract RSAParameters from the certificate and use ASN.1 DER encoding to construct the binary format of PKCS8.

    using System;
    using System.Linq;
    using System.Security.Cryptography;
    using System.Security.Cryptography.X509Certificates;
    
    public class CertificateHelper
    {
        public static byte[] GetPkcs8PublicKey(X509Certificate2 certificate)
        {
            using (RSA rsa = certificate.GetRSAPublicKey())
            {
                if (rsa == null)
                {
                    throw new InvalidOperationException("The certificate does not contain an RSA public key.");
                }
                RSAParameters parameters = rsa.ExportParameters(false);
                return EncodePkcs8PublicKey(parameters);
            }
        }
    
        private static byte[] EncodePkcs8PublicKey(RSAParameters parameters)
        {
            // PKCS#8 Prefix for RSA
            byte[] pkcs8Prefix = new byte[]
            {
                0x30, 0x82, 0x01, 0x22, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86,
                0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0F, 0x00
            };
    
            // ASN.1 DER encoding of modulus and exponent
            byte[] modulus = parameters.Modulus;
            byte[] exponent = parameters.Exponent;
    
            byte[] rsaKey = EncodeRsaPublicKey(modulus, exponent);
    
            // Combine the PKCS#8 prefix and the RSA public key
            return pkcs8Prefix.Concat(rsaKey).ToArray();
        }
    
        private static byte[] EncodeRsaPublicKey(byte[] modulus, byte[] exponent)
        {
            using (var ms = new System.IO.MemoryStream())
            {
                // ASN.1 Sequence
                ms.WriteByte(0x30);
                using (var innerMs = new System.IO.MemoryStream())
                {
                    // Modulus (integer)
                    WriteAsn1Integer(innerMs, modulus);
    
                    // Exponent (integer)
                    WriteAsn1Integer(innerMs, exponent);
    
                    byte[] innerContent = innerMs.ToArray();
                    WriteAsn1Length(ms, innerContent.Length);
                    ms.Write(innerContent, 0, innerContent.Length);
                }
    
                return ms.ToArray();
            }
        }
    
        private static void WriteAsn1Integer(System.IO.MemoryStream ms, byte[] value)
        {
            ms.WriteByte(0x02); // ASN.1 Integer type
            WriteAsn1Length(ms, value.Length);
            ms.Write(value, 0, value.Length);
        }
    
        private static void WriteAsn1Length(System.IO.MemoryStream ms, int length)
        {
            if (length < 128)
            {
                ms.WriteByte((byte)length);
            }
            else
            {
                int lengthOfLength = (int)Math.Ceiling(Math.Log(length, 256));
                ms.WriteByte((byte)(0x80 | lengthOfLength));
                for (int i = lengthOfLength - 1; i >= 0; i--)
                {
                    ms.WriteByte((byte)(length >> (8 * i) & 0xFF));
                }
            }
        }
    }
    
    

    Use like this:

    X509Certificate2 certificate = new X509Certificate2("path_to_certificate.cer");
    byte[] pkcs8PublicKey = CertificateHelper.GetPkcs8PublicKey(certificate);
    
    //Convert to Base64 string for visualization
    string pkcs8PublicKeyBase64 = Convert.ToBase64String(pkcs8PublicKey);
    Console.WriteLine(pkcs8PublicKeyBase64);
    
    

    Best Regards,

    Jiale


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment". 

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.