【问题标题】:How to verify a DSA signature given (R,S) pair in Java?如何在Java中验证给定(R,S)对的DSA签名?
【发布时间】:2017-03-28 03:41:55
【问题描述】:

我正在使用标准(内置)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) 对转换为 verify() 方法所期望的 DSA 字节数组签名?或者,

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);

【问题讨论】:

    标签: java cryptography digital-signature dsa


    【解决方案1】:

    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) 可访问。 (但这个原语不做散列,所以先自己做。)

    1℃。如果您必须自己使用标准加密货币:

    // 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 年。

    【讨论】:

    • 感谢戴夫汤普森的回复。我确实更喜欢使用内置的 java.security 类,因为我相信他们可以设法解决这个问题。你说得对,不可能比较 r 和 s 值来验证签名。我忘了提到我知道发行者的公钥(即 p、q、g 和 y 值)。因此,知道p、q、g、y、r和s值是否可以验证签名?
    • 当然可以,但是如果您不想依赖第三方库,您需要自己构建所需的DSAPublicKey 和签名格式。
    • 当然,我正在这样做: // 获取颁发者的公钥 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() 方法。有什么想法吗?
    • 对不起,这是我的第一篇文章。我还没有发现如何格式化代码!
    • @MarceloMedeiros META:cmets 中的格式非常有限,SX 被设计为问答而非讨论;过多的 cmets 经常被删除。使用编辑按钮添加大量附加细节、数据,尤其是代码到您的问题。将此类更改标记为“已添加”或“已编辑”通常很好,尽管系统会自动维护编辑历史记录并可以根据要求显示。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-28
    • 1970-01-01
    • 2018-09-17
    • 1970-01-01
    • 2014-02-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多