Search code examples
c#hashpaddingpkcs#1

Add padding RSA PKCS#1 after hash SHA256 and before calling API of signature


In order to use a certificate stored in a distant HSM (using cloud signature), I'm actually trying to use an API that generates a PKCS#1.5 signature given a SHA256 Hash. The problem is that the specification of the protocol that I need to implement states that the padding process must be in PKCS#1.

The encryption algorithm supported by the API is this OID : RSAES-PKCS1-v1_5 encryption scheme

http://oid-info.com/get/1.2.840.113549.1.1.1

Here is the specification of the protocol, it is stated the the padding process must be PKCS#1 :

Padding specification

The developers of the API mentioned that if I'm able to pad the hash value generated before sending the hash to the API, I would be able to generate the right signature.

Here is the explanation of the process :

enter image description here

But I can't find any way of padding the hash after it's generated and before it's sent to the API.

...

string pathFileToBeHashed = "D:\\Temp\\virsct.SCT";

// Gets content of file
// Removes invalid characters before hash

string strToHash = File.ReadAllText(pathFileToBeHashed);
strToHash = strToHash.Replace(Constants.vbCr, "");
strToHash = strToHash.Replace(Constants.vbLf, "");
strToHash = strToHash.Replace(Strings.ChrW(26).ToString(), "");

// Hash SHA256

byte[] btToHash = Encoding.UTF8.GetBytes(strToHash);
string strHashed = string.Empty;

using var sha256 = new SHA256Managed()
{
    strHashed = Convert.ToBase64String(sha256.ComputeHash(btToHash));
}

// Send to the API

...

When I personally check the signature using the following code after the hash is being signed, the result is OK. The method that I'm using to check signature is as above.

private static bool IsSignatureOK(string certB64, string signB64, string strCheminCompletFichier)
{
    X509Certificate2 x509 = null;
    try
    {
        x509 = new X509Certificate2(Convert.FromBase64String(certB64));

        using RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)x509.PublicKey.Key;
        byte[] btSign = Convert.FromBase64String(signB64);

        byte[] btFichier = File.ReadAllBytes(strCheminCompletFichier);
        byte[] btHashFichier = null;
        using (MemoryStream ms = new MemoryStream(btFichier))
        {
            btHashFichier = CalculateHash(ms);
        }

        return rsa.VerifyHash(btHashFichier, CryptoConfig.MapNameToOID("SHA256"), btSign);
    }
    catch (Exception ex)
    {
        Interaction.MsgBox($"KO : {ex.Message}");
        return false;
    }
    finally
    {
        if (x509 != null)
            x509.Reset();
    }
}

public static byte[] CalculateHash(MemoryStream memStream)
{
    byte[] btHashWithContext = null;

    int BLOC_LENGTH = 1000000;

    memStream.Position = 0;

    int Nb_Blocs = Convert.ToInt32(memStream.Length / BLOC_LENGTH) + (memStream.Length % BLOC_LENGTH == 0 ? 0 : 1);

    using (SHA256Managed hashAlgorithm = new SHA256Managed())
    {
        byte[] MyBuffer = null;
        for (int j = 0; j <= Nb_Blocs - 1; j++)
        {
            MyBuffer = new byte[BLOC_LENGTH];

            int Nb_octets_lus = memStream.Read(MyBuffer, 0, BLOC_LENGTH);
            if (Nb_octets_lus != BLOC_LENGTH)
                Array.Resize(ref MyBuffer, Nb_octets_lus);

            string restemp = Encoding.UTF8.GetString(MyBuffer);
            restemp = restemp.Replace(Constants.vbCr, "");
            restemp = restemp.Replace(Constants.vbLf, "");
            restemp = restemp.Replace(Strings.ChrW(26).ToString(), "");

            byte[] MyTempArray = Encoding.UTF8.GetBytes(restemp);

            hashAlgorithm.TransformBlock(MyTempArray, 0, MyTempArray.Length, null, 0);

            MyTempArray = null;
        }
        hashAlgorithm.TransformFinalBlock(new byte[0], 0, 0);
        btHashWithContext = hashAlgorithm.Hash;
    }

    return btHashWithContext;
}

Is there a method allowing to add padding to a hash after it's generated ?


Solution

  • According to this website :

    https://www.ibm.com/docs/en/linux-on-systems?topic=cryptography-pkcs-1-hash-formats

    If you add some extra bytes at the beginning of the hash value, you can obtain a result according to a PKCS#1 signature

    The hex bytes for a SHA256 Hash are as following :

    SHA-256
    X'3031300D 06096086 48016503 04020105 000420'
    

    In C# I just add those hex bytes this way :

    public void Main()
    {
        string hash = "3Z6TXNaRtoPP5kv5l4XBarx4GM+uH1M9rRK9iwRkKfY=";
        byte[] btHash = Convert.FromBase64String(hash);
        
        // Generates padding bytes to add at beginning of hash
        string hexString = "3031300D060960864801650304020105000420";
        byte[] btPadBytes = HexStringToByteArray(hexString);
    
        // Combines both byte arrays
        byte[] btCombined = ByteArrayCombine(bt, btHash);
        
        string hashWithPadding = Convert.ToBase64String(btCombined);
        
    }
    
    public static byte[] HexStringToByteArray(string hex)
    {
        return Enumerable.Range(0, hex.Length)
                         .Where(x => x % 2 == 0)
                         .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
                         .ToArray();
    }
    
    public static byte[] ByteArrayCombine(byte[] first, byte[] second)
    {
        byte[] ret = new byte[first.Length + second.Length];
        Buffer.BlockCopy(first, 0, ret, 0, first.Length);
        Buffer.BlockCopy(second, 0, ret, first.Length, second.Length);
        return ret;
    }