根据RFC测试向量在Java中计算ECDSA签名
我正在用java为一个与ikev2协议相关的程序编写一个测试工具。作为其中的一部分,我需要能够计算ECDSA特征(特别是使用NIST P-256曲线) 描述了ECDSA在IKEv2中的使用,并提供了一组测试向量(包括我需要的p256曲线) 我正在尝试使用以下代码通过java的ECDSA签名实现运行ECDSA-256测试向量值(在RFC中):根据RFC测试向量在Java中计算ECDSA签名,java,cryptography,elliptic-curve,ecdsa,Java,Cryptography,Elliptic Curve,Ecdsa,我正在用java为一个与ikev2协议相关的程序编写一个测试工具。作为其中的一部分,我需要能够计算ECDSA特征(特别是使用NIST P-256曲线) 描述了ECDSA在IKEv2中的使用,并提供了一组测试向量(包括我需要的p256曲线) 我正在尝试使用以下代码通过java的ECDSA签名实现运行ECDSA-256测试向量值(在RFC中): //"abc" for the input byte[] input = { 0x61, 0x62, 0x63 }; //Ugly w
//"abc" for the input
byte[] input = { 0x61, 0x62, 0x63 };
//Ugly way of getting the ECParameterSpec for the P-256 curve by name as opposed to specifying all the parameters manually.
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec kpgparams = new ECGenParameterSpec("secp256r1");
kpg.initialize(kpgparams);
ECParameterSpec params = ((ECPublicKey) kpg.generateKeyPair().getPublic()).getParams();
//Create the static private key W from the Test Vector
ECPrivateKeySpec static_privates = new ECPrivateKeySpec(new BigInteger("DC51D3866A15BACDE33D96F992FCA99DA7E6EF0934E7097559C27F1614C88A7F", 16), params);
KeyFactory kf = KeyFactory.getInstance("EC");
ECPrivateKey spriv = (ECPrivateKey) kf.generatePrivate(static_privates);
//Perfrom ECDSA signature of the data with SHA-256 as the hash algorithm
Signature dsa = Signature.getInstance("SHA256withECDSA");
dsa.initSign(spriv);
dsa.update(input);
byte[] output = dsa.sign();
System.out.println("Result: " + new BigInteger(1, output).toString(16));
结果应该是:
CB28E099 9B9C7715 FD0A80D8 E47A7707 9716CBBF 917DD72E
97566EA1 C066957C 86FA3B4 E26CAD5B F90B7F81 899256CE 7594BB1E A0C89212
748BFF3B 3D5B0315
相反,我得到:
30460221 00dd9131 edeb5efd c5e718df c8a7ab2d 5532b85b 7d4c012a e5a4e90c 3b824ab5 d7022100 9A2B12 9e10a2ff 7066ff79 89aa73d5 ba37c868 5ec36517 216e2e43 ffa876d7
我知道长度差异是由于JavaASN.1对签名进行编码造成的。然而,其余的都是完全错误的,我不明白为什么
任何帮助或建议都将不胜感激
另外,我不是ECDSA或Java加密专家,所以我犯的可能是一个愚蠢的错误,我猜每次运行程序时,相同的明文(待签名)输入都会得到不同的签名值 ECDSA指定为每个签名生成随机临时ECDSA私钥。为此,
Signature.getInstance(“SHA256withECDSA”)
不允许您指定临时密钥(这是一件好事,可以防止许多人在脚上自拍!)。相反,它会得到自己的SecureRandom实例,这将使您的输出不确定性
这可能意味着您不能使用JCE(Signature.getInstance()
)进行测试向量验证
您可以做的是扩展SecureRandom
,使其返回确定性数据。显然,您不应该在实际部署中使用此选项:
public class FixedSecureRandom extends SecureRandom {
private static boolean debug = false;
private static final long serialVersionUID = 1L;
public FixedSecureRandom() { }
private int nextBytesIndex = 0;
private byte[] nextBytesValues = null;
public void setBytes(byte[] values) {
this.nextBytesValues = values;
}
public void nextBytes(byte[] b) {
if (nextBytesValues==null) {
super.nextBytes(b);
} else if (nextBytesValues.length==0) {
super.nextBytes(b);
} else {
for (int i=0; i<b.length; i++) {
b[i] = nextBytesValues[nextBytesIndex];
nextBytesIndex = (nextBytesIndex + 1) % nextBytesValues.length;
}
}
}
}
请注意,BC的ECDSASigner
仅实现EC签名部分,而不是哈希。您仍然需要自己进行哈希运算(假设您的输入数据位于data
):
在创建ECDSA签名之前:
BigInteger[] sig = signer.generateSignature(hash);
最后,这个
biginger[]
(应该是length==2)是(r,s)值。您需要ASN.1对其进行编码,这将为您提供所需的droids字节。以下是我的完整测试,使用tsechin的解决方案,使用BouncyCastle,但坚持使用好的旧JCA API:
byte[] input = { 0x61, 0x62, 0x63 };
//Create the static private key W from the Test Vector
ECNamedCurveParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec("secp256r1");
org.bouncycastle.jce.spec.ECPrivateKeySpec privateKeySpec = new org.bouncycastle.jce.spec.ECPrivateKeySpec(new BigInteger("DC51D3866A15BACDE33D96F992FCA99DA7E6EF0934E7097559C27F1614C88A7F", 16), parameterSpec);
KeyFactory kf = KeyFactory.getInstance("EC");
ECPrivateKey spriv = (ECPrivateKey) kf.generatePrivate(privateKeySpec);
//Perfrom ECDSA signature of the data with SHA-256 as the hash algorithm
Signature dsa = Signature.getInstance("SHA256withECDSA", "BC");
FixedSecureRandom random = new FixedSecureRandom();
random.setBytes(Hex.decode("9E56F509196784D963D1C0A401510EE7ADA3DCC5DEE04B154BF61AF1D5A6DECE"));
dsa.initSign(spriv, random);
dsa.update(input);
byte[] output = dsa.sign();
// compare the signature with the expected reference values
ASN1Sequence sequence = ASN1Sequence.getInstance(output);
DERInteger r = (DERInteger) sequence.getObjectAt(0);
DERInteger s = (DERInteger) sequence.getObjectAt(1);
Assert.assertEquals(r.getValue(), new BigInteger("CB28E0999B9C7715FD0A80D8E47A77079716CBBF917DD72E97566EA1C066957C", 16));
Assert.assertEquals(s.getValue(), new BigInteger("86FA3BB4E26CAD5BF90B7F81899256CE7594BB1EA0C89212748BFF3B3D5B0315", 16));
成功!谢谢你快速而详细的回答。我一读它就明白了,它现在对我来说都很有用。非常感谢您的帮助:-)其他人请看这个。最初,我犯了一个新手错误,做了fsr_k.setBytes(新的BigInteger(“9E56F509196784D963D1C0A401510EE7ADA3DCC5DEE04B154BF61AF1D5A6DECE”),toByteArray()),差点哭了。在执行此操作时,字节数组前面会附加一个0字节,因此您仍然会得到错误的K值:-(@PinkyNoBrain)您需要一个名为I2OS(整数到八位字节字符串)的函数。此方法需要两个输入,一个整数和(键)长度。它返回整数的无符号大端编码,并将
00
字节前置到密钥大小。现在有了无符号部分,请确保还有填充部分…非常感谢:ECDSASigner只实现EC签名部分,而不是哈希。帮助,我。我正在执行ECPrivateKey spriv=(ECPrivateKey)kf.generatePrivate(privateKeySpec);
使应用程序忽略其余代码
BigInteger[] sig = signer.generateSignature(hash);
byte[] input = { 0x61, 0x62, 0x63 };
//Create the static private key W from the Test Vector
ECNamedCurveParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec("secp256r1");
org.bouncycastle.jce.spec.ECPrivateKeySpec privateKeySpec = new org.bouncycastle.jce.spec.ECPrivateKeySpec(new BigInteger("DC51D3866A15BACDE33D96F992FCA99DA7E6EF0934E7097559C27F1614C88A7F", 16), parameterSpec);
KeyFactory kf = KeyFactory.getInstance("EC");
ECPrivateKey spriv = (ECPrivateKey) kf.generatePrivate(privateKeySpec);
//Perfrom ECDSA signature of the data with SHA-256 as the hash algorithm
Signature dsa = Signature.getInstance("SHA256withECDSA", "BC");
FixedSecureRandom random = new FixedSecureRandom();
random.setBytes(Hex.decode("9E56F509196784D963D1C0A401510EE7ADA3DCC5DEE04B154BF61AF1D5A6DECE"));
dsa.initSign(spriv, random);
dsa.update(input);
byte[] output = dsa.sign();
// compare the signature with the expected reference values
ASN1Sequence sequence = ASN1Sequence.getInstance(output);
DERInteger r = (DERInteger) sequence.getObjectAt(0);
DERInteger s = (DERInteger) sequence.getObjectAt(1);
Assert.assertEquals(r.getValue(), new BigInteger("CB28E0999B9C7715FD0A80D8E47A77079716CBBF917DD72E97566EA1C066957C", 16));
Assert.assertEquals(s.getValue(), new BigInteger("86FA3BB4E26CAD5BF90B7F81899256CE7594BB1EA0C89212748BFF3B3D5B0315", 16));