如何在Java中验证给定(R,S)对的DSA签名?

时间:2016-11-14 13:23:57

标签: java cryptography digital-signature dsa

我正在使用标准(内置)DSA类开发Java(JDK 1.8)应用程序,以验证数字签名。

我有数据文件和预期的签名存储在文本文件中,如下所示:

// Signature part R:
4226 3F05 F103 E3BE 59BF 3903 37F8 0375 8802 5D8F.
// Signature part S:
AF21 15B0 16E4 1761 75B8 C7D4 F877 5AB7 26BB AE72.

请注意,签名以(R,S)对的形式表示,如FIPS 186-3 NIST标准中所述。

为了验证签名,我从java.security.Signature调用方法verify(byte [] signature)。此方法需要验证表示签名的字节数组。但是,我不知道如何将(R,S)对转换为字节数组。因此,我无法验证签名。

所以,我想知道:

1)是否有一种方法可以将(R,S)对转换为DSA字节数组签名,如verify()方法所期望的那样?或者,

2)有没有办法从Java Signature实例类中获取R和S值,以便我可以将这些值与我的值进行比较?

提前致谢。

编辑:@ dave_thompson_085提出的解决方案效果很好!请参阅下面的完整源代码:

// read DSA parameters from file or other means
BigInteger r = new BigInteger(...);
BigInteger s = new BigInteger(...);
BigInteger p = new BigInteger(...);
BigInteger q = new BigInteger(...);
BigInteger g = new BigInteger(...);
BigInteger y = new BigInteger(...);

// get the public key
KeySpec publicKeySpec = new DSAPublicKeySpec(y, p, q, g);
KeyFactory keyFactory = KeyFactory.getInstance("DSA");
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);  

// read the input file to be checked and update signature
Signature signature = Signature.getInstance("SHA1withDSA");
signature.initVerify(publicKey);
File inputFile = new File(...);
try (BufferedInputStream is = new BufferedInputStream(new FileInputStream(inputFile))) {
    byte[] buffer = new byte[1024];
    while (is.available() != 0) {
        int len = is.read(buffer);
        signature.update(buffer, 0, len);
    }
}

// convert (r, s) to ASN.1 DER encoding
// assuming you have r and s as !!positive!! BigIntegers
// (if you have unsigned byte[] as shown in your Q, 
// use BigInteger r = new BigInteger (1, bytes) etc.
byte[] rb = r.toByteArray();
byte[] sb = s.toByteArray(); // sign-padded if necessary
// these lines are more verbose than necessary to show the structure
// compiler will fold or you can do so yourself 
int off = (2 + 2) + rb.length;
int tot = off + (2 - 2) + sb.length;
byte[] der = new byte[tot + 2];
der[0] = 0x30;
der[1] = (byte) (tot & 0xff);
der[2 + 0] = 0x02;
der[2 + 1] = (byte) (rb.length & 0xff);
System.arraycopy(rb, 0, der, 2 + 2, rb.length);
der[off + 0] = 0x02;
der[off + 1] = (byte) (sb.length & 0xff);
System.arraycopy(sb, 0, der, off + 2, sb.length);

// verifies if the signature is valid
boolean isValid = signature.verify(des);

1 个答案:

答案 0 :(得分:2)

1A。普通Java加密(以及DSA的大多数但不是所有其他用途,以及ECDSA)所期望的形式是 ASN.1 DER编码https://crypto.stackexchange.com/a/1797/12642解释了困难是什么,但并没有告诉你如何做到这一点。

1B。如果你有或可以安装the BouncyCastle cryptoprovider jar,它包含一整套ASN.1例程。或者更容易它也包含 可以访问org.bouncycastle.crypto.signers.DSASignerinit的低级原始verifySignature(byte[], BigInteger, BigInteger)。 (但是这个原语不会做哈希,所以先自己做。)

1C。如果你必须自己使用标准加密:

// assuming you have r and s as !!positive!! BigIntegers
// (if you have unsigned byte[] as shown in your Q, 
// use BigInteger r = new BigInteger (1, bytes) etc.

byte[] rb = r.toByteArray(), sb = s.toByteArray(); // sign-padded if necessary
// these lines are more verbose than necessary to show the structure
// compiler will fold or you can do so yourself 
int off = (2+2)+rb.length, tot = off+(2-2)+sb.length;
byte[] der = new byte[tot+2];
der[0] = 0x30; der[1] = tot;
der[2+0] = 0x02; der[2+1] = rb.length; System.arraycopy(rb,0, der,2+2, rb.length);
der[off+0] = 0x02; der[off+1] = sb.length; System.arraycopy(sb,0, der,off+2, sb.length);

2.您无法通过比较r和s来验证标准DSA签名。正如您在阅读FIPS186-3时应该知道的那样,4.5和4.6部分的签名是随机的;为同一个消息计算两个(或更多)签名每次都会给出一个不同的(r,s)对 - 除非你重复足够的时间来命中相同的k,对于较旧的1024/160组,平均为2 ^ 159次尝试/键,更多用于较新/较大的键。如果你有一百万台计算机,每台计算机每秒可以进行一百万次尝试,那么这仍需要大约16,000,000,000,000,000,000,000,000,000,000年。