在哈希 SHA256 之后和调用签名 API 之前添加填充 RSA PKCS#1

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

为了使用存储在远程 HSM 中的证书(使用云签名),我实际上是在尝试使用 API,它在给定 SHA256 哈希的情况下生成 PKCS#1.5 签名。问题是我需要实现的协议规范指出填充过程必须在 PKCS#1 中。

API支持的加密算法是这个OID:RSAES-PKCS1-v1_5加密方案

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

这里是协议的规范,规定填充过程必须是 PKCS#1 :

API 的开发人员提到,如果我能够在将哈希发送到 API 之前填充生成的哈希值,我将能够生成正确的签名。

这里是过程的解释:

但是我找不到任何方法在哈希生成之后和发送到 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

...

当我在哈希签名后使用以下代码亲自检查签名时,结果是可以的。我用来检查签名的方法如上所述。

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

是否有一种方法允许在哈希生成后向其添加填充?

根据这个网站:

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

如果在hash值的开头加上一些额外的字节,就可以得到一个PKCS#1签名的结果

SHA256 哈希的十六进制字节如下:

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

在 C# 中,我只是这样添加这些十六进制字节:

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