对于我正在进行的一个项目,我需要验证数据记录的签名。我们有一个遗留项目,它使用了源代码不再可用的导入 DLL。我们现在正在更新该项目,并希望使签名验证现代化。
支持的签名方法有 RSA 1024、ECDSA 192、ECDSA 224 和 ECDSA 256。
作为示例数据,我有以下公钥:
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwhDZNphi4J1EiBCQwZ1WBCYbnf9Ne6UqPuLLGJPTj5++io9pxT3McYgFFFPb7HdfJCrPsKmTVwlw9Z1RpQru6g==
,以下签名:
BEgwRgIhAPqcBPjRROiwTBUFTF50iDXSBn+e71c3fIj9kgfxUbAZAiEAR6KM+u8D6TWtaKVdH8EjLxWJf4Vm/u2Z5YptKEjTgHs=
,以及应验证以下数据:;"4399901879126";"302.1302.006";"4005249001296";"20240627151654328";
我已经使用旧工具验证这些值应该给出积极的结果。
我尝试了以下一些方法,但没有成功:
private static void Main(string[] args)
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var publicKey = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwhDZNphi4J1EiBCQwZ1WBCYbnf9Ne6UqPuLLGJPTj5++io9pxT3McYgFFFPb7HdfJCrPsKmTVwlw9Z1RpQru6g==";
var signature = "BEgwRgIhAPqcBPjRROiwTBUFTF50iDXSBn+e71c3fIj9kgfxUbAZAiEAR6KM+u8D6TWtaKVdH8EjLxWJf4Vm/u2Z5YptKEjTgHs=";
var data = ";\"4399901879126\";\"302.1302.006\";\"4005249001296\";\"20240627151654328\";";
var value = VerifyEcdsaSignature(Convert.FromBase64String(publicKey), Encoding.Latin1.GetBytes(data), Convert.FromBase64String(signature));
Console.WriteLine(value);
Console.ReadLine();
}
public static bool VerifySignature(byte[] key, byte[] data, byte[] signature)
{
var publicKey = PublicKey.CreateFromSubjectPublicKeyInfo(key, out _);
var ecdsa = publicKey.GetECDsaPublicKey() ?? ECDsa.Create();
return ecdsa.VerifyData(data, signature, HashAlgorithmName.SHA256);
}
public static byte[] GetSha224Bytes(byte[] data)
{
return Sha224.Create().ComputeHash(data);
}
public static byte[] GetSha256Bytes(byte[] data)
{
return SHA256.HashData(data);
}
private static bool VerifyEcdsaSignature(byte[] key, byte[] bytesData, byte[] bytesSignature)
{
var publicKey = PublicKey.CreateFromSubjectPublicKeyInfo(key, out _);
var ecdsa = publicKey.GetECDsaPublicKey();
var pubKey = DotNetUtilities.GetECDsaPublicKey(ecdsa);
ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA");
signer.Init(false, pubKey);
signer.BlockUpdate(bytesData);
return signer.VerifySignature(bytesSignature);
}
验证失败的原因是签名格式不兼容以及(可能)您的遗留工具中的错误!
这可以通过十六进制编码签名最容易地解释:
此签名采用 ASN.1/DER 编码。大多数库支持 ASN.1/DER 编码签名,但没有与
OCTET STRING
关联的 ID/长度,即两个前导字节 0x0448。如果删除这些,结果是:
如果指定了正确的签名格式,则该签名应该现在实际上已成功验证。
由于 C# 默认使用 IEEE P1363 格式,因此必须使用
DSASignatureFormat.Rfc3279DerSequence
作为 VerifyData()
调用中的第三个参数或(如已使用的)BC 变体中的 SHA-256withECDSA
显式指定 ASN.1/DER 格式。
尽管如此,这个验证还是失败了!原因是遗留工具没有正确实现 ASN.1/DER 编码。
正确编码的解释可以在here找到:r和s的编码是最小尺寸的有符号大端。这意味着如果前导字节大于 0x7f,则前导 0x00 只能出现在 r 之前。对于 s 同样如此。
对于发布的签名,r (
0xFA9C...B019
) 属于这种情况,但 s (0x47A2...807B
) 则不然。尽管如此,0x00字节也被设置为s。这是不正确的,必须修复:必须删除 0x00 字节并调整长度信息:
有了这个签名,验证就成功了:
...
var publicKey = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwhDZNphi4J1EiBCQwZ1WBCYbnf9Ne6UqPuLLGJPTj5++io9pxT3McYgFFFPb7HdfJCrPsKmTVwlw9Z1RpQru6g==";
var signature = "MEUCIQD6nAT40UTosEwVBUxedIg10gZ/nu9XN3yI/ZIH8VGwGQIgR6KM+u8D6TWtaKVdH8EjLxWJf4Vm/u2Z5YptKEjTgHs=";
var data = ";\"4399901879126\";\"302.1302.006\";\"4005249001296\";\"20240627151654328\";";
var value = VerifySignature(Convert.FromBase64String(publicKey), Encoding.UTF8.GetBytes(data), Convert.FromBase64String(signature));
Console.WriteLine(value); // true
...
public static bool VerifySignature(byte[] key, byte[] data, byte[] signature)
{
...
return ecdsa.VerifyData(data, signature, HashAlgorithmName.SHA256, DSASignatureFormat.Rfc3279DerSequence); // apply ASN.1/DER format
}
...