为什么我在 bouncycastle 中生成的 SHA512-ECDSA 密钥在 jsrsasign 中不匹配?

Why do my SHA512-ECDSA keys, generated in bouncycastle, mismatch in jsrsasign?

我有一个应用程序和一个服务器,它们都使用签名消息(使用 bouncycastle)进行通信。对于一个新的应用程序,我想使用 JS 并使用 jsrsasign 对消息进行签名,然后再次使用 bouncycastle 在服务器端对其进行验证。密钥也在 bouncycastle 中生成,然后传输到 JS 应用程序。

JS->Server通信签名总是无效的,于是尝试完全在JS中对消息进行签名验证,发现这样也不行。我假设我没有使用正确的配置,但我没有找出问题的根源。

我正在使用 ECDSA 和 prime256v1 曲线。

我的密钥是这样生成的:

val generator = KeyPairGenerator.getInstance("ECDSA")
val spec = ECNamedCurveTable.getParameterSpec("prime256v1")
generator.initialize(spec, SecureRandom())
val keypair = generator.generateKeyPair()

val privateKey = Base64.toBase64String(keypair.private.encoded)
// X962
val publicKey = Base64.toBase64String(Arrays.copyOfRange(keypair.public.encoded, 26, keypair.public.encoded.size))

生成的密钥如下所示:

privateKey = MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg6xrOGWvvTTh5EfxhDco0xqppyFrSRbFaKUKbZutlynugCgYIKoZIzj0DAQehRANCAAQ6EhELaQWv3z7tBgJXYMGspw93Ni+LvvmY3f0MSBtu84ldBZi3boz+OV3hiiyO+mBx+jCg4s2TDF+nw0Vi3lS7

publicKey = BDoSEQtpBa/fPu0GAldgwaynD3c2L4u++Zjd/QxIG27ziV0FmLdujP45XeGKLI76YHH6MKDizZMMX6fDRWLeVLs=

为了测试,我使用这些键和 运行 来查看它们在 JS 端是否有效:

const message = "Testing";
// Sign
const sig = new KJUR.crypto.Signature({ alg: "SHA512withECDSA" });
sig.init({ d: keyPair.private, curve: "prime256v1" });
sig.updateString(message);
const signature = sig.sign();

// Verify
let sigVerification = new r.Signature({ alg: "SHA512withECDSA" });
sigVerification.init({ xy: keyPair.public, curve: "prime256v1" });
sigVerification.updateString(message);
let isValid = sigVerification.verify(signature);
if(isValid) {
  console.log("Signature is valid, keys are matching");
} else {
  throw "The signature is invalid. Please make sure the keys match."
}

这对于从服务器获取的密钥失败,但是当我使用 jsrsasign 生成密钥时它起作用了(不过我需要让服务器密钥起作用)。

我已经尝试将密钥编码为 HEX 而不是 BASE64,但这没有帮助。由于我对整个签名主题还很陌生,而且我已经无计可施,非常感谢您的帮助。

键可以用不同的格式和编码指定。发布的私钥具有 PKCS#8 格式,public 密钥以未压缩格式指定。两者都是 Base64 编码。
关于导入,十六进制编码在这里更合适(正如已经完成的那样,在 Java/BouncyCastle 代码中,或者在 JavaScript 代码中,例如 )。那么用jsrsasign导入是可行的,如下:

// Import as hex encoded PKCS#8 key
var privKeyPKCS8Hex = '308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420eb1ace196bef4d387911fc610dca34c6aa69c85ad245b15a29429b66eb65ca7ba00a06082a8648ce3d030107a144034200043a12110b6905afdf3eed06025760c1aca70f77362f8bbef998ddfd0c481b6ef3895d0598b76e8cfe395de18a2c8efa6071fa30a0e2cd930c5fa7c34562de54bb';
var privKey = KEYUTIL.getKeyFromPlainPrivatePKCS8Hex(privKeyPKCS8Hex);
var signature = new KJUR.crypto.Signature({"alg": "SHA512withECDSA"});
signature.init(privKey);
signature.updateString("The quick brown fox jumps over the lazy dog");
var signatureHex = signature.sign();
document.getElementById("signatureHex").innerHTML = signatureHex;
 
var publicKeyUncompressed = '043a12110b6905afdf3eed06025760c1aca70f77362f8bbef998ddfd0c481b6ef3895d0598b76e8cfe395de18a2c8efa6071fa30a0e2cd930c5fa7c34562de54bb';
var pubKey = new KJUR.crypto.ECDSA({'pub': publicKeyUncompressed, 'curve': 'prime256v1'});
var signature = new KJUR.crypto.Signature({"alg": "SHA512withECDSA"});
signature.init(pubKey);
signature.updateString("The quick brown fox jumps over the lazy dog");
var verified = signature.verify(signatureHex); 
document.getElementById("verified").innerHTML = verified;
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsrsasign/10.4.0/jsrsasign-all-min.js"></script>
<p style="font-family:'Courier New', monospace;" id="signatureHex"></p>
<p style="font-family:'Courier New', monospace;" id="verified"></p>

还有其他导入方式。例如,私钥可以是 PEM 编码(不需要每 64 字节后的换行符):

var privKeyPPCS8PEM = `-----BEGIN PRIVATE KEY-----
                       MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg6xrOGWvvTTh5EfxhDco0xqppyFrSRbFaKUKbZutlynugCgYIKoZIzj0DAQehRANCAAQ6EhELaQWv3z7tBgJXYMGspw93Ni+LvvmY3f0MSBtu84ldBZi3boz+OV3hiiyO+mBx+jCg4s2TDF+nw0Vi3lS7
                       -----END PRIVATE KEY-----`;
var privKey = KEYUTIL.getKeyFromPlainPrivatePKCS8PEM(privKeyPPCS8PEM);

有关不同导入选项的更多信息,请参阅 jsrsasign 的文档,例如KEYUTIL.

一个小提示:prime256v1对应的密钥大小(基点顺序)为256位(32字节)。但是对于 SHA512,您使用的是两倍大的摘要(64 字节)。在这种情况下,根据 NIST FIPS 186-4,使用哈希的最左边的 n 位,here.