3

We are trying to get the subject name hash algorithm working in C#. You can run this from the openssl command line with the following code.

openssl x509 -inform PEM -hash -noout -in google.pem

This returns an 8 character code that is a truncated hash of the certificate subject. Our code however does not return the same code.

public static String GenerateX509SubjectNameHash(X509Certificate xCert)
{
    byte[] bytes = Encoding.ASCII.GetBytes(xCert.SubjectName.Name);
    HashAlgorithm hash = new SHA1Managed();
    var hashBytes = hash.ComputeHash(bytes);
    ulong hashVal = (((ulong)hashBytes[0]) | ((ulong)hashBytes[1] << 8) | ((ulong)hashBytes[2] << 16) | ((ulong)hashBytes[3] << 24) & 0xffffffff);

    return hashVal.ToString("X");
}

The original code that does this in openssl can be referenced here: https://github.com/openssl/openssl/blob/master/crypto/x509/x509_cmp.c#L226

What are we doing wrong? --Thanks,

  • 1
    Also see [Generate Subject Hash of X509Certificate in Java](http://stackoverflow.com/a/30265791/608639). Though its Java, it details OpenSSL handling of the subject hash. – jww Apr 20 '16 at 23:36

2 Answers2

0

I know this is an old thread. But it could help others. It's not easy to find out. but I think I found a way it works so far for my certificates I tested. You need the nuget packages of bouncycastle and then this piece of code.

 public async Task ExecuteAsync()
    {
        var issuer = GetIssuer(await GetServerCertificateAsync());
        System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);


        var certString = Convert.ToBase64String(issuer.RawData);

        //Alles canonical maken (lowercase certificaat opvragen via bouncyCastle)
        //subject name omzetten naar Lowercase zoals OpenSSL doet.
        //een ASN1Object van maken alle bytes samen nemen en hash berekenen. WHOOPIEeee!!
        Org.BouncyCastle.X509.X509CertificateParser certParser = new Org.BouncyCastle.X509.X509CertificateParser();
        var cert = certParser.ReadCertificate(issuer.RawData);
        var subjectDNCanonical = new Org.BouncyCastle.Asn1.X509.X509Name(cert.SubjectDN.ToString().ToLower());
        var asn1Object = (Asn1Sequence)subjectDNCanonical.ToAsn1Object();
        List<byte[]> terms = new List<byte[]>();
        int finalLen = 0;
        foreach (var asn1Obj in asn1Object.ToArray())
        {
            byte[] term = asn1Obj.GetEncoded();
            term[9] = 0x0c; // change tag from 0x13 (printable string) to 0x0c
            terms.Add(term);
            finalLen += term.Length;
        }
        byte[] finalEncForw = new byte[finalLen];
        int j = 0;
        foreach (byte[] term in terms)
        {
            foreach (byte b in term)
            {
                finalEncForw[j++] = b;
            }
        }
        HashAlgorithm hash;
        hash = System.Security.Cryptography.SHA1.Create();
        var hashBytes = hash.ComputeHash(finalEncForw);
        ulong ret = (((ulong)hashBytes[0]) | ((ulong)hashBytes[1] << 8) | ((ulong)hashBytes[2] << 16) | ((ulong)hashBytes[3] << 24)) & 0xffffffff;
        Result = ret.ToString("X2").PadLeft(8, '0').ToLower();
    }

Mainly .ToLower was the killer. You can't do this with the default X509Certificate2 of .NET. If you do that it will give X509Name with only one part of the subject. So that is why you have to convert it to a BouncyCastle certificate.

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 27 '21 at 10:38
0

As Benny mentioned, this is thread quite old, but I was in the same situation, that I could not find a really satisfying solution to first, convert a .cer file to a .pem file, and secondly, extract (in my case) the old subject hash, which is necessary for android. Finally the best solution I could find was by using the BouncyCastle nuget package. Hence the following code is based on that library.

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));
            }
        }
    }
}

Usage is quite simple:

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);
}

As you can see, some of the code (e.g. the hashing) I could already find in threads like this. For me the real break through was using the BouncyCastle library instead of the built-in classes, and completely loading the certificate with it and extracting all information from there. Manual parsing and otherwise encoding the SubjectDN lead me nowhere.

Since Convert.ToBase64String cannot split lines after 64 characters itself, this is done manually here by using the referenced code from stack overflow.

alex3683
  • 1,460
  • 14
  • 25