1. 程式人生 > >翻譯:通過Java程式設計建立X.509格式的數字簽名證書

翻譯:通過Java程式設計建立X.509格式的數字簽名證書

本文翻譯自此篇文章,如有餘力可直接閱讀原文。

我所需要解決的問題很簡單:建立一個只需要配置很少欄位的X.509協議的證書,再使用已有的CA私鑰/證書進行簽名,最後匯出為PKCS12格式的簽名證書。把這個過程變得複雜化的原因是:我需要在一臺小型裝置(PDA)上,通過Java程式設計實現。

這個看似簡單的工作花費了我兩天的時間。我想我該分享我的成果,以期能夠幫助比人解決類似的問題。

更新:似乎通過這篇部落格,我找到了一些同道中人,我經常收到一些郵件來諮詢相關細節。請各位不要感到不好意思,但是我確實需要一段時間來回復給你,因為我有太多的郵件需要一一回復。

為什麼通過Java程式設計建立數字簽名證書

有很多命令列工具可以建立X.509協議的證書,例如Sun JDK釋出版中自帶的keytool和可以適配多種平臺的openssl。那麼我為什麼還要通過java程式設計實現建立數字簽名證書的過程呢?我深入地考慮過使用Runtime.Exec去呼叫比較成熟的、系統中自帶的openssl工具包,只需要傳入合適的引數和標準輸入即可。但是這種方法在不使用J2SE的平臺上,例如J2ME JVMs在小型裝置(PDA,例如CDC)和手機(例如CLDC),就會出現很大的問題。有些情況下,CDC支援執行系統命令,但是CLDC卻並非如此。而且,openssl還需要一個配置檔案,這個配置檔案需要滿足一下條件:
1.

裝置中需要已經有可用的配置檔案(Java中用到的openssl操作指令必須要恰當,更何況如果裝置中如果沒有安裝openssl,還需要安裝openssl以及編寫合適的配置檔案。)
2. 通過java程式,有許可權去管理配置檔案,包括建立、修改和移除。這就會帶來額外的複雜性(譯者注:主要是許可權方面)。
因此,openssl可以穩定地執行在類桌面的平臺(J2SE)中,但是要想執行在更多的嵌入式平臺中就會困難重重。我決定大膽冒險,直接使用Java程式設計實現建立數字簽名證書的過程。

調研有哪些可用的Java加密介面

很明顯,如果使用Java進行一些加密性的工作,首選使用近期版本的J2SE中自帶的JCE/JSSE框架。由於歷史上以及當下的美國出口法的原因,JCE被分割成了基本架構和provider

兩部分。J2SE的1.4版本開始預設裝載SunJCE作為provider。但是,類似於使用Runtime.Exec的情況,在以嵌入式形式使用的JRE上很難提供JCE,所以我們在應用設計之初,不能夠樂觀地假定在小型裝置(PDA)上可以使用JCE。更糟糕的是,JCE再也不能夠被第三方庫替換了,所以應用本身不大可能裝載JCE庫。
另一個進行加密操作的方案時使用Bouncycastle庫,它也可以作為一個JCE的provider使用,但是它同時也提供了一個輕量級API可以用於加密。這個介面是可以獨立於JCE框架而獨立使用的。Bouncycastle類庫使用了非常自由的許可協議,被允許在任何應用中使用。因此它提供了一個獨立於平臺自身擁有功能的加密支援。所以我決定使用Bouncycastle輕量級API(下文將簡稱為BC)來完成數字簽名證書的生成任務。
注意:很不幸的是,org.bouncycastle.x509.X509Util類在官方的JAR中並不是public型別的,在JAR外部無法直接使用這個類。但是生成數字簽名證書的過程中必須使用這個類,那麼必須想辦法將這個類變成public型別。可以使用修改原始碼並重新打包成JAR檔案的方法。我將Buncycastle類中的一部分程式碼拷貝到我的程式碼中,保證他們他們能在J2ME中執行,同時精簡程式碼的依賴關係以節省應用所佔用的空間。另一個方法就是使用最新的OpenUAT原始碼,使用BC的類使用它。最後一個方法就是下載BC的原始碼,包含在自己的工程中,做適當的修改(譯者注:我是用的是最後這種方法)。

第一步:建立新證書所需的公私鑰對

建立證書所需的公私鑰對,下面分別是使用JCE和BC建立公私鑰對的方法:

KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(1024, sr);
KeyPair keypair = keyGen.generateKeyPair();
privKey = keypair.getPrivate();
pubKey = keypair.getPublic();

以上是使用JCE建立公私鑰對。

RSAKeyPairGenerator gen = new RSAKeyPairGenerator();
gen.init(new RSAKeyGenerationParameters(BigInteger.valueOf(3), sr, 1024, 80));
AsymmetricCipherKeyPair keypair = gen.generateKeyPair();
RSAKeyParameters publicKey = (RSAKeyParameters) keypair.getPublic();
RSAPrivateCrtKeyParameters privateKey = (RSAPrivateCrtKeyParameters) keypair.getPrivate();
// used to get proper encoding for the certificate
RSAPublicKeyStructure pkStruct = new RSAPublicKeyStructure(publicKey.getModulus(), publicKey.getExponent());
// JCE format needed for the certificate - because getEncoded() is necessary...
pubKey = KeyFactory.getInstance("RSA").generatePublic(
        new RSAPublicKeySpec(publicKey.getModulus(), publicKey.getExponent()));
// and this one for the KeyStore
privKey = KeyFactory.getInstance("RSA").generatePrivate(
        new RSAPrivateCrtKeySpec(publicKey.getModulus(), publicKey.getExponent(),
                privateKey.getExponent(), privateKey.getP(), privateKey.getQ(), 
                privateKey.getDP(), privateKey.getDQ(), privateKey.getQInv()));

以上是使用BC建立公私鑰對。需要注意的是,BC和JCE生成的公私鑰對的資料結構是不同的,這其中有一些額外的程式碼是將BC的資料結構轉成JCE的資料結構,主要為了便於輸出成為PKCS12格式。我希望能夠不借助JCE實現這個過程,這樣就減少了資料結構轉換的過程。

第二步:建立一個新證書結構

上一步已經有了一個公鑰,可以將它填入到X.509協議的證書結構體中,BC提供了X509v3CertificateGenerator類來實現這個過程,但是它非常依賴於JCE框架,所以最終沒有使用這個類。我下面貼上了一些程式碼。在這段程式碼中僅僅使用BC的API完成建立證書結構的過程,這樣為了逐漸擺脫對JCE框架的依賴。

Calendar expiry = Calendar.getInstance();
expiry.add(Calendar.DAY_OF_YEAR, validityDays);

X509Name x509Name = new X509Name("CN=" + dn);

V3TBSCertificateGenerator certGen = new V3TBSCertificateGenerator();
certGen.setSerialNumber(new DERInteger(BigInteger.valueOf(System.currentTimeMillis())));
certGen.setIssuer(PrincipalUtil.getSubjectX509Principal(caCert));
certGen.setSubject(x509Name);
DERObjectIdentifier sigOID = X509Util.getAlgorithmOID("SHA1WithRSAEncryption");
AlgorithmIdentifier sigAlgId = new AlgorithmIdentifier(sigOID, new DERNull());
certGen.setSignature(sigAlgId);
certGen.setSubjectPublicKeyInfo(new SubjectPublicKeyInfo((ASN1Sequence)new ASN1InputStream(
        new ByteArrayInputStream(pubKey.getEncoded())).readObject()));
certGen.setStartDate(new Time(new Date(System.currentTimeMillis())));
certGen.setEndDate(new Time(expiry.getTime()));
TBSCertificateStructure tbsCert = certGen.generateTBSCertificate();

在這段程式碼片段中,有一些小技巧。
1. issuer name是否正確非常重要。如果它的值有一個位元組的差別,在認證證書的證書鏈時就會失敗。我之前使用new X509Name(caCert.getSubjectDN().getName())的方法設定這個值,但是得到的確實一個錯誤值。PrincipalUtil類可以提供正確值。
2. identifier物件描述的是使用的簽名演算法型別,是硬編碼的。需要與後邊的簽名過程中保持一致。
3. 正確的編碼證書中的公鑰也是很難的,開始的時候我使用如下的方法:

certGen.setSubjectPublicKeyInfo(new SubjectPublicKeyInfo(sigAlgId, pkStruct.toASN1Object()));

但是這種方法產生的值,openssl無法讀取。我還沒有搞明白為什麼只有JCE框架提供的getEncoded()方法可以得到正確的編碼值,換句話說,正確的編碼方法是什麼?
4. Subject Name可以設定成完成的X.509協議中規定的名稱,(例如:O=My organization, OE=My organizational unit, C=AT, CN=Rene Mayrhofer/[email protected]),但是我選擇僅僅使用一部分內容,使用Common Name(CN)。僅僅使用Commone Name標識證書就已經可以滿足我的需求了。

第三步:匯入簽名根證書的私鑰和數字證書

以下程式碼實現的前提是,一個完整的CA中,包括一個自簽名數字證書和對應的私鑰,並且是PKCS12格式的檔案。這樣的根證書可以使用JCE中的KeyStore類來讀取。

KeyStore caKs = KeyStore.getInstance("PKCS12");
caKs.load(new FileInputStream(new File(caFile)), caPassword.toCharArray());

// load the key entry from the keystore
Key key = caKs.getKey(caAlias, caPassword.toCharArray());
if (key == null) {
    throw new RuntimeException("Got null key from keystore!"); 
}
RSAPrivateCrtKey privKey = (RSAPrivateCrtKey) key;
caPrivateKey = new RSAPrivateCrtKeyParameters(privKey.getModulus(), privKey.getPublicExponent(), privKey.getPrivateExponent(),
        privKey.getPrimeP(), privKey.getPrimeQ(), privKey.getPrimeExponentP(), privKey.getPrimeExponentQ(), privKey.getCrtCoefficient());
// and get the certificate
caCert = (X509Certificate) caKs.getCertificate(caAlias);
if (caCert == null) {
    throw new RuntimeException("Got null cert from keystore!"); 
}
caCert.verify(caCert.getPublicKey());

我還沒有找到不通過JCE類而實現這個過程的方法。

第四步:為新證書籤名

使用CA的私鑰為剛剛建立的證書結構體簽名。這個過程因為引入了塊狀內容編碼而變得非常複雜。

SHA1Digest digester = new SHA1Digest();
AsymmetricBlockCipher rsa = new PKCS1Encoding(new RSAEngine());
ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
DEROutputStream         dOut = new DEROutputStream(bOut);
dOut.writeObject(tbsCert);
byte[] signature;
byte[] certBlock = bOut.toByteArray();
// first create digest
digester.update(certBlock, 0, certBlock.length);
byte[] hash = new byte[digester.getDigestSize()];
digester.doFinal(hash, 0);
// and sign that
rsa.init(true, caPrivateKey);
DigestInfo dInfo = new DigestInfo( new AlgorithmIdentifier(X509ObjectIdentifiers.id_SHA1, null), hash);
byte[] digest = dInfo.getEncoded(ASN1Encodable.DER);
signature = rsa.processBlock(digest, 0, digest.length);

v.add(tbsCert);
v.add(sigAlgId);
v.add(new DERBitString(signature));

正如你看到的,使用SHA1和RSA時,需要硬編碼到塊資料中,以匹配在證書結構體中定義的簽名方法。比較複雜的部分是在簽名之前使用PKCS1編碼RSA簽名,使用ASN1 DER編碼摘要。通常可以通過返回值判斷操作是否成功,這會很簡單,但是BC和JCE的provider實現並沒有設定返回值。

第五步:將新建立的數字簽名證書儲存為PKCS12格式的檔案

目前的數字簽名證書還是程式中儲存在記憶體中的Java物件而已,最後一步,需要將資料簽名證書儲存為檔案。這樣就可以將證書匯入到其他的機器或者僅僅是匯入到其他的軟體包中了。

X509CertificateObject clientCert = new X509CertificateObject(new X509CertificateStructure(new DERSequence(v))); 
clientCert.verify(caCert.getPublicKey());

PKCS12BagAttributeCarrier bagCert = clientCert;
bagCert.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName,
        new DERBMPString("My frindly name for the new certificate"));
bagCert.setBagAttribute(
        PKCSObjectIdentifiers.pkcs_9_at_localKeyId,
        new SubjectKeyIdentifierStructure(pubKey));

KeyStore store = KeyStore.getInstance("PKCS12");
store.load(null, null);

X509Certificate[] chain = new X509Certificate[2];
// first the client, then the CA certificate
chain[0] = clientCert;
chain[1] = caCert;

store.setKeyEntry("My friendly name for the new private key", privKey, exportPassword.toCharArray(), chain);

FileOutputStream fOut = new FileOutputStream(exportFile);
store.store(fOut, exportPassword.toCharArray());

這段程式碼嚴重依賴於JCE框架,我需要能夠使用BC替換掉它。這看起來並不難,唯一的難點在於設定localKeyId引數,以便匯入程式碼可以分配證書和私鑰的空間。

如果誰知道如何不通過JCE可以讀寫PKCS12格式的檔案,以及如何使用BC來正確的編碼證書中的公鑰,請聯絡告訴我。

完成程式碼

實現建立X.509格式的數字簽名證書的Java實現類可以在點選這裡檢視。程式碼中同時實現了JCE和BC的方法,因為JCE的提供者方法要比BC的方法更快,因為JCE使用了Java中更加底層的程式碼(native code)。程式碼的應用規則遵循GPL協議,同時如果你對程式碼進行了擴充套件、提升和bug修復,我非常希望你能夠通知我,我將會非常感激。我會更新我的實現程式碼,以便別人更好的使用它。

本文最後修訂於2012年12月18日。

譯者注:
JCE(Java Cryptography Extension)是一組包,它們提供用於加密、金鑰生成和協商以及 Message Authentication Code(MAC)演算法的框架和實現。
它提供對對稱、不對稱、塊和流密碼的加密支援,它還支援安全流和密封的物件。它不對外出口,用它開發完成封裝後將無法呼叫。
JCE,Java Cryptography Extension,在早期JDK版本中,由於受美國的密碼出口條例約束,Java中涉及加解密功能的API被限制出口,所以Java中安全元件被分成了兩部分: 不含加密功能的JCA(Java Cryptography Architecture )和含加密功能的JCE(Java Cryptography Extension)。在JDK1.1-1.3版本期間,JCE屬於擴充套件包,僅供美國和加拿大的使用者下載,JDK1.4+版本後,隨JDK核心包一起分發。

根據文章顯示,這篇文章寫於2012年12月18日之前,通過推測和實驗發現,文中使用的bcprov-jdk15on為1.46版本。該版本的原始碼可以在下文提供的網站中下載,為了防止該網站年久失修,無法下載1.46版本的原始碼,我下載下來,儲存到了CSDN的下載上,讀者也可以從點選此處下載。

程式碼執行需要另外兩個包:log4j和commons-codec,如果使用Maven管理程式碼,可以下pom.xml中新增:

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.16</version>
</dependency>
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.8</version>
</dependency>

相關推薦

翻譯通過Java程式設計建立X.509格式數字簽名證書

本文翻譯自此篇文章,如有餘力可直接閱讀原文。 我所需要解決的問題很簡單:建立一個只需要配置很少欄位的X.509協議的證書,再使用已有的CA私鑰/證書進行簽名,最後匯出為PKCS12格式的簽名證書。把這個過程變得複雜化的原因是:我需要在一臺小型裝置(PDA)上,

hbase程式設計通過Java api操作hbase

轉:http://www.aboutyun.com/thread-7151-1-1.html http://blog.csdn.net/cnweike/article/details/42920547 http://blog.csdn.net/zwx19921215/art

Elastcisearch.Nest 7.x 系列`偽`官方翻譯通過 NEST 來快捷試用 Elasticsearch

本系列已經全部完成,完整版可見 :https://blog.zhuliang.ltd/categories/Elasticsearch/ 本系列博文是“偽”官方文件翻譯(更加本土化),並非完全將官方文件進行翻譯,而是在查閱、測試原始文件並轉換為自己真知灼見後的“準”翻譯。有不同見解 / 說明不周的地方,還

SprinvAction學習一、裝配bean通過Java程式碼裝配bean

具體步驟 (1)普通的介面 (2)實現介面的類,該類為需要裝配到spring bean中 (3)配置類,由該類定義bean 示例程式碼: 介面1:(普通的介面) package com.sp

【福利】BAT架構師分享最全Java架構師學習技能圖譜包含Java程式設計+網路+設計模式+資料庫+分散式等

**【福利】**總結了一份架構圖譜,希望對想成為架構師的朋友有一定的參考和幫助。 我簡短談下目前大家關心的話題:網際網路裁員浪潮裡,大家會發現一般裁員會先從可替代性的業務性程式設計師開始,原因很簡單,由於日常負責專案大部分都是業務性的,真正有技術實力提升機會非常有限,平時工作繁忙,忽略了

Felomeng翻譯通過創立三家科技公司學到的57條經驗

譯者序:應該說,這篇文章還是不錯的。雖然網上有些翻譯版本,但是個人強烈覺得有必要再翻譯一下。以下為本人所翻: 從1999年以來,我一直在建立科技公司並參與運營。我最近創立的一家公司是Fabulis.com。經過這些年的努力,我學到了57條經驗教訓。其實,我可以羅列出超過10

Java中如何進行加密和數字簽名

本文主要談一下密碼學中的加密和數字簽名,以及其在java中如何進行使用。對密碼學有興趣的夥伴,推薦看Bruce Schneier的著作:Applied Crypotography。在jdk1.5的發行版本中安全性方面有了很大的改進,也提供了對RSA演算法的直接支援,現在我們

Java 實現 RSA加密解密及數字簽名

RSA公鑰加密演算法是1977年由羅納德·李維斯特(Ron Rivest)、阿迪·薩莫爾(Adi Shamir)和倫納德·阿德曼(Leonard Adleman)一起提出的。當時他們三人都在麻省理工學院工作。RSA就是他們三人姓氏開頭字母拼在一起組成的。 RSA是目

Java高階架構師(一)第21節通過X-gen生成商品模組

package com.sishuok.architecture1.goodsmgr.vo; import com.sishuok.architecture1.common.vo.BaseModel; public class GoodsModel extends BaseModel{

Java程式設計思想 第十二章通過異常處理錯誤

發現錯誤的理想時機是在編譯階段,也就是程式在編碼過程中發現錯誤,然而一些業務邏輯錯誤,編譯器並不能一定會找到錯誤,餘下的問題需要在程式執行期間解決,這就需要發生錯誤的地方能夠準確的將錯誤資訊傳遞給某個接收者,以便接收者知道如何正確的處理這個錯誤資訊。 改進錯誤的機制在Java中尤為重要,

番外 01Spring IoC 實現原理簡析,Java的反射機制,通過類名建立物件

轉載請註明來源 賴賴的部落格 前景概要 在 01 走進Spring,Context、Bean和IoC 中,我們看到了強大的Spring通過ApplicationContext實現了bean工廠(也就是物件工廠),那究竟是怎麼實現的呢,本次給大家寫一個小D

Java併發程式設計(01)執行緒的建立方式,狀態週期管理

> 本文原始碼:[GitHub·點這裡](https://github.com/cicadasmile/java-base-parent) || [GitEE·點這裡](https://gitee.com/cicadasmile/java-base-parent) # 一、併發程式設計簡介 ##

《Effective Java 中文版 第2版》學習筆記 第4條通過私有構造器強化不可實例化的能力

缺省 ive ont sof family 其他 筆記 cnblogs ror   只有當類不包含顯式的構造器時,編譯器才會生成一個公有的、無參的缺省構造器。只要讓一個類包含私有構造器,這個類就不能被實例化了。示例: 1 // 工具類 2 public class Uti

javasocket 網路程式設計

socket的通俗解釋: 套接字=主機+埠號。兩個東西配在一起,叫做“配套”。 另外“套”也有對應的意思,它可以把網路上的兩個應用對應起來,所以用“套”。 它是用來與另一個應用連線的,所以用“接”。 又因為它是一小段資料,很小一小段,所以叫“字”。 “套接字",就是一小段用來將網路個兩個應用

JAVA程式設計題解與上機指導 第四版 第8章 Java的圖形使用者介面設計 8.2 建立“My JFrame”

一、簡介:程式設計實現建立並顯示一個標題為“My Frame”,Frame背景為黑色,Panel背景為白色,其中,Panel中加入“開啟”“關閉”“返回”三個按鈕,並一行排開。 二、程式碼如下: import java.awt.*; import javax.swing.*; import ja

Core Java(一)Java程式設計概述

Java “白皮書”的關鍵術語 簡單性 面向物件 分散式 健壯性 安全性 體系結構中立 可移植性 解釋型 高效能 多執行緒 動態性 簡單性 Java語法是C++語法的一個“純淨”版本。這裡沒有標頭檔案、

JAVA小白系列之第二個分支面向物件程式設計

想必剛入門的小白總是搞不清楚面向物件和麵向過程這兩個概念,雖然我在前面有所提及,但是都是泛泛而談,也有各種語言融入進去,不理解的會越來越暈,那麼,這節我就專門來講講什麼是JAVA的面向物件。 面向過程和麵向物件回顧 不記得概念的可以具體看看之前的文章,這裡僅做簡單的回顧: 面向過程 關注於流

Java程式設計思想】12.通過異常處理錯誤

Java 的基本理念是“結構不佳的程式碼不能執行”。 異常處理是 Java 中唯一正式的錯誤報告機制,並且通過編譯器強制執行。 12.1 概念 異常機制會保證能夠捕獲錯誤,並且只需在一個地方(即異常處理程式中)處理錯即可。 12.2 基本異常 異常情形(exceptional conditio

Java入門推薦Java程式設計快速高效的入門學習方法

看到有不少的小夥伴詢問快速高效學習Java程式設計的方法有哪些?對Java程式設計感興趣的小夥伴比較在意Java程式設計的學習方法,想要找到好的學習方法快速高效的學習,早日學成高薪就業。小編就和讀者分享一下快速高效學習Java程式設計的方法,希望可以幫到喜歡Java程式設計想要學習的小夥伴們。

java程式設計思想——第十二章(通過異常處理錯誤)》

12.1 概念## 發現錯誤的理想時機是編譯時期,然而,編譯期間並不能找出所有的錯誤,餘下的問題必須在執行時期解決。 12.2 基本異常## 異常是指阻止當前方法或作用域繼續執行的問題。 當丟擲異常後,首先在堆上建立異常物件,當前的執行路徑被終止,並從當前環境中彈