2015-12-03 63 views
5

我正在開發一個需要對html文檔進行數字簽名的Android應用程序。 該文檔駐留在數據庫中,採用JSON格式。 我在本地簽署的文件使用bash腳本我一些其他太問題發現:在Android上驗證數字簽名

openssl dgst -sha1 someHTMLDoc.html > hash openssl rsautl -sign -inkey privateKey.pem -keyform PEM -in hash > signature.bin

使用生成私鑰:使用產生

openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:3 -out privateKey.pem 

公鑰:

openssl pkey -in privateKey.pem -out publicKey.pem -pubout 

我想驗證Signature.bin中創建的簽名以及某些HTMLDoc.html中的數據,返回e應用程序。

我發送HTML和簽名JSON對象例如:

{ "data" : "<html><body></body></html>", "signature":"6598 13a9 b12b 21a9 ..... " } 

Android的應用程序保存在公鑰共享首選項如下:

-----BEGIN PUBLIC KEY----- MIIBIDANBgkqhkiG9w0AAAEFAAOCAQ0AvniCAKCAQEAvni/NSEX3Rhx91HkJl85 \nx1noyYET ......

通知的「\ n」(換行符)(在從publicKey.pem複製字符串到Android Gradle配置時自動添加)

好吧,畢竟準備,現在的問題。 我想驗證沒有成功的關鍵。

我使用下面的代碼:

private boolean verifySignature(String data, String signature) { 
    InputStream is = null; 
    try { 
     is = new ByteArrayInputStream(Config.getDogbarPublic().getBytes("UTF-8")); //Read DogBar Public key 

     BufferedReader br = new BufferedReader(new InputStreamReader(is)); 
     List<String> lines = new ArrayList<String>(); 
     String line; 
     while ((line = br.readLine()) != null) 
      lines.add(line); 

     // removes the first and last lines of the file (comments) 
     if (lines.size() > 1 && lines.get(0).startsWith("-----") && lines.get(lines.size() - 1).startsWith("-----")) { 
      lines.remove(0); 
      lines.remove(lines.size() - 1); 
     } 

     // concats the remaining lines to a single String 
     StringBuilder sb = new StringBuilder(); 
     for (String aLine : lines) 
      sb.append(aLine); 
     String key = sb.toString(); 

     byte[] keyBytes = Base64.decode(key.getBytes("utf-8"), Base64.DEFAULT); 
     X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); 
     KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 
     PublicKey publicKey = keyFactory.generatePublic(spec); 

     Signature signCheck = Signature.getInstance("SHA1withRSA"); //Instantiate signature checker object. 
     signCheck.initVerify(publicKey); 
     signCheck.update(data.getBytes()); 
     return signCheck.verify(signature.getBytes()); //verify signature with public key 
    } catch (Exception e) { 
     e.printStackTrace(); 
     return false; 
    } 
} 

誰能幫助?我究竟做錯了什麼 ?

我錯過了一些字節轉換?也許JSON對象影響簽名?

簽名是否應包含原始文件包含的\ n(換行符),還是應在JSON文件中沒有?

在此先感謝所有幫助,其高度讚賞。

+0

我一直嘗試不同的方法沒有成功,試圖從JSON對象中刪除換行符,嘗試過getBytes(「ASCII」)和getBytes(「UTF-8」)。 – Noxymon

+1

你有沒有得到任何異常?哪一個? – Henry

+0

我沒有收到任何異常,它只是返回False'signCheck.verify()' – Noxymon

回答

3

數字簽名是計算摘要數據(C)的(函數H),並將其與非對稱加密算法加密(函數E),以產生CYPHER文本(S)的方法:

S = E(H(C)) 

簽名驗證花費簽名解密給定簽名(函數D) - 只有在解密中使用的公鑰與加密中使用的私鑰配對時纔會導致H(C),並計算數據的摘要以檢查兩個摘要是否匹配:

H(C) == D(E(H(C))) 

由此可知,爲了使簽名有效,賦予哈希函數(C)的字節必須完全相同。

在你的情況下,他們都沒有,因爲當你使用openssl dgst(右H(C))的輸出計算的摘要是字面上是這樣的:

SHA1(someHTMLDoc.html)= 22596363b3de40b06f981fb85d82312e8c0ed511 

這是輸入到RSA加密。

而當你要驗證的簽名,摘要(左H(C))的輸出是字節,例如在十六進制:

22596363b3de40b06f981fb85d82312e8c0ed511 

所以,你最終加密的字節,以產生(在右側H(C)):

0000000: 5348 4131 2873 6f6d 6548 746d 6c44 6f63 SHA1(someHtmlDoc 
0000010: 2e68 746d 6c29 3d20 3232 3539 3633 3633 .html)= 22596363 
0000020: 6233 6465 3430 6230 3666 3938 3166 6238 b3de40b06f981fb8 
0000030: 3564 3832 3331 3265 3863 3065 6435 3131 5d82312e8c0ed511 
0000040: 0a          . 

和比較針對字節(在左側H(C)):

0000000: 2259 6363 b3de 40b0 6f98 1fb8 5d82 312e "[email protected]].1. 
0000010: 8c0e d511        .... 

此外,您還需要使用-signopenssl dgst以獲得正確的輸出格式(請參閱Difference between openSSL rsautl and dgst)。

所以在OpenSSL的側做:

openssl dgst -sha1 -sign privateKey.pem someHTMLDoc.html > signature.bin 

在Java方面做:

import java.io.FileInputStream; 
import java.io.FileNotFoundException; 
import java.io.InputStream; 
import java.io.InputStreamReader; 
import java.security.KeyFactory; 
import java.security.Signature; 
import java.security.interfaces.RSAPublicKey; 
import java.security.spec.X509EncodedKeySpec; 

import org.spongycastle.util.io.pem.PemObject; 
import org.spongycastle.util.io.pem.PemReader; 

public class VerifySignature { 
    public static void main(final String[] args) throws Exception { 
     try (PemReader reader = publicKeyReader(); InputStream data = data(); InputStream signatureData = signature()) { 
      final PemObject publicKeyPem = reader.readPemObject(); 
      final byte[] publicKeyBytes = publicKeyPem.getContent(); 
      final KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 
      final X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes); 
      final RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(publicKeySpec); 

      final Signature signature = Signature.getInstance("SHA1withRSA"); 
      signature.initVerify(publicKey); 

      final byte[] buffy = new byte[16 * 1024]; 
      int read = -1; 
      while ((read = data.read(buffy)) != -1) { 
       signature.update(buffy, 0, read); 
      } 

      final byte[] signatureBytes = new byte[publicKey.getModulus().bitLength()/8]; 
      signatureData.read(signatureBytes); 

      System.out.println(signature.verify(signatureBytes)); 
     } 
    } 

    private static InputStream data() throws FileNotFoundException { 
     return new FileInputStream("someHTMLDoc.html"); 
    } 

    private static PemReader publicKeyReader() throws FileNotFoundException { 
     return new PemReader(new InputStreamReader(new FileInputStream("publicKey.pem"))); 
    } 

    private static InputStream signature() throws FileNotFoundException { 
     return new FileInputStream("signature.bin"); 
    } 
} 

我用Spongy Castle的公共密鑰的PEM解碼,使事情更易讀一點並且更易於使用。

+0

我不知道我完全理解..你應該如何去加密?我應該只採用dgst函數的'22596363b3de40b06f981fb85d82312e8c0ed511'部分並簽名嗎?你能解釋我應該怎麼做,而不是我犯了什麼錯誤?我很難理解如何去寫所寫的內容。 – Noxymon

+1

請參閱我編輯的答案,您應該使用帶'-sign'選項的'openssl dgst'來生成JCE中Signature實現所需的二進制輸出。 –

+0

給這個人一個餅乾!你先生是個天才!非常感謝。 – Noxymon