2016-11-14 48 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()方法。有任何想法嗎? –