根据 RFC 测试向量计算 Java 中的 ECDSA 签名



我正在用java为与ikev2协议相关的程序编写一个测试工具。 作为其中的一部分,我需要能够计算ECDSA签名(特别是使用NIST P-256曲线)。

RFC 4754 描述了 ECDSA 在 IKEv2 中的使用,并提供了一组测试向量(包括我需要的 p256 曲线)。

我正在尝试使用以下代码通过 java 的 ECDSA 签名实现运行 ECDSA-256 测试向量值(RFC 中的第 8.1 节):

//"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 917DD72E97566EA1 C066957C 86FA3BB4 E26CAD5B F90B7F81 899256CE 7594BB1E A0C89212748BFF3B 3D5B0315

相反,我得到:

30460221 00DD9131 EdeB5EFD C5E718DF C8A7AB2d 5532B85B 7D4C012A E5A4E90C 3B824AB5 D7022100 9A8A2B12 9E10A2FF 7066FF79 89AA73D5 BA37C868 5EC36517 216E2E43 FFA876D7

我知道长度差异是由于Java ASN.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;
            }
        }
    }
}

唷。好的,现在你有一个 SecureRandom 类,它返回一些已知字节,然后回退到真正的 SecureRandom。我会再说一遍(请原谅大喊大叫)- 不要在生产中使用它!

接下来,您需要使用 ECDSA 实现,该实现允许您指定自己的 SecureRandom。您可以使用BouncyCastle的ECDSASigner来实现此目的。除了这里,你将给它你自己的盗版FixedSecureRandom,这样当它调用secureRandom.getBytes()时,它就会得到你想要的字节。 这使您可以控制临时键以匹配测试向量中指定的键。您可能需要调整实际字节(例如,添加零预填充)以匹配ECDSASigner将要请求的内容。

ECPrivateKeyParameters ecPriv = ...; // this is the user's EC private key (not ephemeral)
FixedSecureRandom fsr_k = new FixedSecureRandom();
fsr_k.setBytes(tempKeyK);
ECDSASigner signer = new ECDSASigner();
ParametersWithRandom ecdsaprivrand = new ParametersWithRandom(ecPriv, fsr_k);
signer.init(true, ecdsaprivrand);

请注意,BC 的 ECDSASigner 仅实现 EC 签名部分,而不是哈希。您仍然需要进行自己的哈希处理(假设您的输入数据是data):

Digest md = new SHA256Digest()
md.reset();
md.update(data, 0, data.length);
byte[] hash = new byte[md.getDigestSize()];
md.doFinal(hash, 0);

在创建 ECDSA 签名之前:

BigInteger[] sig = signer.generateSignature(hash);

最后,这个BigInteger[](应该是 length==2)是 (r,s) 值。您需要对它进行 ASN.1 DER 编码,这应该为您提供所需的机器人字节。

这是我使用

BouncyCastle的tsechin解决方案的完整测试,但坚持使用旧的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));

最新更新