歡迎光臨
每天分享高質量文章

Java 結合 keytool 實現非對稱簽名與驗證

(點選上方公眾號,可快速關註)


來源:王潔 ,

imaidata.github.io/blog/2017/07/29/java結合keytool實現非對稱簽名與驗證/

keytool的使用

keytool 是 JDK 自帶的一個金鑰庫管理工具。這裡只用到了 keytool 的部分功能,包括生成金鑰對、匯出公鑰等。keytool 生成的公鑰/私鑰對存放到一個到了一個檔案中,這個檔案有密碼保護,通稱為 keystore。

生成金鑰對

$ keytool -genkey -alias signLegal -keystore examplestanstore -validity 1800

生成別名為 signLegal 的金鑰對,存放在金鑰庫 examplestanstore 中,證書的有效期是1800天(預設是90天)。

輸入一系列的引數。輸入的引數遵循了 LDAP 的風格和標準。可以想象,生成的金鑰對可以看成 LDAP 的一個條目。

命令執行成功後會在當前目錄下建立一個叫 examplestanstore 的檔案。

檢視金鑰對

$ keytool -list -keystore examplestanstore -v

列出了examplestanstore金鑰庫的中所有金鑰對。-v引數表示詳細資訊,詳細資訊中有證書的失效時間。

匯出公鑰證書

$ keytool -export -keystore examplestanstore -alias signLegal -file StanSmith.cer

匯出的公鑰存放在當前目錄的 StanSmith.cer 檔案中,是個二進位制檔案。

Java簽名和驗證

參考了Java安全官方教程

http://docs.oracle.com/javase/tutorial/security/apisign/index.html

在該官方教程中,GenSig.java 類生成金鑰對,對輸入的檔案進行簽名,輸出了一個簽名結果檔案 sig 和公鑰 suepk。

VerSig.java 類接受三個引數:公鑰檔案名(suepk)、簽名檔案(sig)、被簽名的源檔案名(hello.txt)。

該教程解釋了兩個類的原理,並附加有原始碼。將原始碼下載並編譯。建立一個 hello.txt 的檔案作為被簽名的標的檔案,裡面隨便放點字串。然後執行:

$ java GenSig hello.txt                        (生成檔案sig和suepk)

$ java VerSig suepk sig hello.txt

signature verifies: true

在實際使用時,金鑰對不可能每次在程式中重新生成。而 keytool 恰好可以生成並相對安全儲存金鑰對。所以下麵結合了 keytool 和 Java 實現的功能。

結合keytool與Java簽名/驗證

參考”Oracle–The Java Tutorials: Weaknesses and Alternatives”

http://docs.oracle.com/javase/tutorial/security/apisign/enhancements.html

金鑰對由 keytool 生成並儲存到 keystore 中保護起來(keystore有密碼)。公鑰也從 keystore 中匯出。GenSig.java 類只需要從 keystore 中取得私鑰進行簽名即可。

VerSig.java 也要做適當的修改。貌似因為從 keystore 中匯出的是證書而不是公鑰,兩者的封裝格式估計有差異。

具體步驟

  1. 利用 keytool -genkey 生成金鑰對儲存在 keystore 中(庫檔案是examplestanstore);

  2. 利用 `keytool -export` 從 keystore 中匯出公鑰證書(StanSmith.cer);

  3. 利用新類 GenSig2.java 生成簽名(檔案名是sig),GenSig2.java 會從 keystore 中取私鑰;

  4. 將公鑰(StanSmith.cer)、簽名(sig)、被簽名檔案(hello.txt)發給驗證方;

  5. 驗證方利用 VerSig2.java 進行驗證。

下麵是 GenSig2.java 和 VerSig2.java 的原始碼和執行方式。

GenSig2.java

import java.io.*;

import java.security.*;

 

class GenSig2 {

 

    public static void main(String[] args) {

 

        if (args.length != 1) {

            System.out.println(“Usage: java GenSig2 “);

            }

        else try{

 

                /*create key paire use keytool:

                $ keytool -genkey -alias signLegal -keystore examplestanstore -validity 1800*/

                // read keystore file

                KeyStore ks = KeyStore.getInstance(“JKS”);

                FileInputStream ksfis = new FileInputStream(“examplestanstore”);

                BufferedInputStream ksbufin = new BufferedInputStream(ksfis);

 

                // open keystore and get private key

                // alias is ‘signLeal’, kpasswd/spasswd is ‘vagrant’

                ks.load(ksbufin, “vagrant”.toCharArray());

                PrivateKey priv = (PrivateKey) ks.getKey(“signLegal”, “vagrant”.toCharArray());

 

            /* Create a Signature object and initialize it with the private key */

 

            Signature dsa = Signature.getInstance(“SHA1withDSA”, “SUN”);

 

            dsa.initSign(priv);

            /* Update and sign the data */

 

            FileInputStream fis = new FileInputStream(args[0]);

            BufferedInputStream bufin = new BufferedInputStream(fis);

            byte[] buffer = new byte[1024];

            int len;

            while (bufin.available() != 0) {

                len = bufin.read(buffer);

                dsa.update(buffer, 0, len);

                };

 

            bufin.close();

 

            /* Now that all the data to be signed has been read in,

                    generate a signature for it */

            byte[] realSig = dsa.sign();

 

            /* Save the signature in a file */

            FileOutputStream sigfos = new FileOutputStream(“sig”);

            sigfos.write(realSig);

 

            sigfos.close();

 

            /* public key file can export from keystore use keytool:

            $  keytool -export -keystore examplestanstore -alias signLegal -file StanSmith.cer */

 

        } catch (Exception e) {

            System.err.println(“Caught exception ” + e.toString());

        }

    };

編譯後,這樣執行:

$ java GenSig2 hello.txt

會生成簽名檔案sig。

VerSig2.java

import java.io.*;

import java.security.*;

import java.security.spec.*;

 

class VerSig2 {

 

    public static void main(String[] args) {

 

        /* Verify a DSA signature */

 

        if (args.length != 3) {

            System.out.println(“Usage: VerSig publickeyfile signaturefile datafile”);

            }

        else try{

 

            /* import encoded public cert */

            FileInputStream certfis = new FileInputStream(args[0]);

            java.security.cert.CertificateFactory cf =

                java.security.cert.CertificateFactory.getInstance(“X.509”);

            java.security.cert.Certificate cert =  cf.generateCertificate(certfis);

            PublicKey pubKey = cert.getPublicKey();

 

            /* input the signature bytes */

            FileInputStream sigfis = new FileInputStream(args[1]);

            byte[] sigToVerify = new byte[sigfis.available()];

            sigfis.read(sigToVerify );

 

            sigfis.close();

 

            /* create a Signature object and initialize it with the public key */

            Signature sig = Signature.getInstance(“SHA1withDSA”, “SUN”);

            sig.initVerify(pubKey);

 

            /* Update and verify the data */

 

            FileInputStream datafis = new FileInputStream(args[2]);

            BufferedInputStream bufin = new BufferedInputStream(datafis);

            byte[] buffer = new byte[1024];

            int len;

            while (bufin.available() != 0) {

                len = bufin.read(buffer);

                sig.update(buffer, 0, len);

                };

 

            bufin.close();

 

            boolean verifies = sig.verify(sigToVerify);

 

            System.out.println(“signature verifies: ” + verifies);

 

        } catch (Exception e) {

            System.err.println(“Caught exception ” + e.toString());

        };

    }

}

編譯後,這樣執行(StanSmith.cer 是利用 keytool 匯出的公鑰證書,見前文):

$ java VerSig2 StanSmith.cer sig hello.txt

signature verifies: true

OpenSSL

雖然也研究了一下 OpenSSL,但發現與 Java 難以結合,難度也很大。例如它的教程中採用的是 RSA,而上面的 Java 使用的是 DSA。所以只是貼在這裡備忘,可以忽略。

參考”An Introduction to the OpenSSL command line tool”

http://users.dcc.uchile.cl/~pcamacho/tutorial/crypto/openssl/openssl_intro.html

生成私鑰

$ openssl genrsa -out key.pem 1024

$ cat key.pem

—–BEGIN RSA PRIVATE KEY—–

MIICXQIBAAKBgQCzVDmu6Cf2QF7cERCGYU3B8Epm6pkkpMZFgotphXMgAmBBNJbh

Si7qPH4R5JlEm1ZXPr5DZH/pyJBWQhiiHGeUAOve+GOgvt9Rk25r7OEWYvn/GCr/

JBfLBGqwtlzn/t2s2x04IooshsGkOd6YpZoztkEDtu2gKHedFczF607IvwIDAQAB

AoGAMdbIqUmwQYomUvcTJqXIXIwRwYSVx09cI1lisZL7Kfw/ECAzhq19WHAzgXmM

9zpMxraTXluCCVFKfA6mlfda+ZoBlKSYdOecwNB+TSAumf9XK8uHW/g8C+Ykq9OG

g9Uiy8rKnl12Zaiu9H8L82ud0CkTFW2636/PuKgtp+4YbXECQQDhKdh8lwgumg7H

YIw5476QOHnPL7c3OFPGtaOZMZJkjMPfRzgR4B5PjcGnOLDoTlkATcBPmXtLwwJJ

SzaBdaRjAkEAy+NwdOzC1yQrTrkZQx1brNjO3iytfkl3t1xAWyz5Sy1IB7+4fsod

Eh3br5E1o5YRipY2GJZvp2OAAt3tz6iS9QJASvIYwu+qo4hX3vk9847gwTRrJxFk

1JaFHCEdgUJEzf8ku08DVL/alvRCPxzZlZluenFmz5fwuDkCq87DJ7g2rQJBAMDM

+SnIPdMeA8n0pRvfJjLD7pMP4pu6M3fzx3Owiqj5T9TsCjXzQBxCmdxizzs7DKll

tA/6Kek64PFVFa25tgUCQQCTM1VwfNKjFbd+0HuF6WAs3Odjuo0gKk/QIjdn7M5/

I0kxEApKxTto3oiuCQGeYL/sqy3WjM0476w48+xUsQeF

—–END RSA PRIVATE KEY—–

匯出公鑰

$ openssl rsa -in key.pem -pubout -out pub-key.pem

$ cat pub-key.pem

—–BEGIN PUBLIC KEY—–

MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCzVDmu6Cf2QF7cERCGYU3B8Epm

6pkkpMZFgotphXMgAmBBNJbhSi7qPH4R5JlEm1ZXPr5DZH/pyJBWQhiiHGeUAOve

+GOgvt9Rk25r7OEWYvn/GCr/JBfLBGqwtlzn/t2s2x04IooshsGkOd6YpZoztkED

tu2gKHedFczF607IvwIDAQAB

—–END PUBLIC KEY—–

摘要計算

建立一個內容是 1234 的文字檔案 hello.txt。用 OpenSSL 計算它的 SHA256 摘要(SHA256 是 jarsigner 的預設摘要演演算法):

$ cat hello.txt

1234

$ openssl dgst -SHA256 -out hello.sha256 hello.txt

$ cat hello.sha256

SHA256(hello.txt)= a883dafc480d466ee04e0d6da986bd78eb1fdd2178d04693723da3a8f95d42f4

簽名和驗證

對摘要檔案 hello.sha256 進行簽名:

$ openssl rsautl -sign -in hello.sha256 -out hello.sign -inkey key.pem

用公鑰對簽名進行驗證:

$ openssl rsautl -verify -in hello.sign -inkey pub-key.pem -pubin

SHA256(hello.txt)= a883dafc480d466ee04e0d6da986bd78eb1fdd2178d04693723da3a8f95d42f4

用公鑰驗證必須加上 -pubin 引數。 用私鑰對簽名進行驗證:

$ openssl rsautl -verify -in hello.sign -inkey key.pem

SHA256(hello.txt)= a883dafc480d466ee04e0d6da986bd78eb1fdd2178d04693723da3a8f95d42f4

驗證的STD輸出與摘要檔案 hello.sha256 的內容一樣,說明驗證可以透過。

【關於投稿】


如果大家有原創好文投稿,請直接給公號傳送留言。


① 留言格式:
【投稿】+《 文章標題》+ 文章連結

② 示例:
【投稿】《不要自稱是程式員,我十多年的 IT 職場總結》:http://blog.jobbole.com/94148/

③ 最後請附上您的個人簡介哈~



看完本文有收穫?請轉發分享給更多人

關註「ImportNew」,提升Java技能

贊(0)

分享創造快樂