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