2016-11-14 44 views
2

我正在开发一个使用标准(内置)DSA类的Java(JDK 1.8)应用程序来验证数字签名。如何在Java中验证给定(R,S)对的DSA签名?

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

// 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中的签名。该方法需要一个表示待验证签名的字节数组。但是,我不知道如何将(R,S)对转换为字节数组。因此我无法验证签名。

所以,我想知道:

1)是否有转换(R,S)对成预期由所述验证()方法的DSA字节数组签名的方式?;或者,

2)是否有从Java签名实例类获得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); 

回答

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.DSASignerinitverifySignature(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组/键,更新/更大的键。如果你有一百万台计算机,每台计算机每秒可以做一百万次尝试,这仍然需要大约16,000,000,000,000,000,000,000,000年。

+0

感谢戴夫汤普森的回复。 我更喜欢使用内建的java.security类,因为我相信他们可以设法解决这个问题。 对于比较r值和s值来验证签名是不可能的。我忘记提及我知道发行人的公钥(即p,q,g和y值)。 因此,是否有可能验证签名知道p,q,g,y,r和s值? –

+0

当然,如果您不想依赖第三方库,则需要自己构建所需的“DSAPublicKey”和签名格式。 –

+0

当然,我这样做: //获得发行人的公钥 PublicKey pubKey = KeyFactory.getInstance(“DSA”)。generatePublic(new DSAPublicKeySpec(y,p,q,g)); 签名sig = Signature.getInstance(“SHA1withDSA”); sig.initVerify(pubKey); sig.update(...); //读取文件检查并调用update() byte [] sigToVerify = convert(r,s,y,p,q,g,...); //将(r,s,...)转换为预期格式 boolean verify = sig.verify(sigToVerify); 问题是我不知道如何实现如上所示的convert()方法。有任何想法吗? –