程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 將Java加密技術同Windows結合起來

將Java加密技術同Windows結合起來

編輯:關於JAVA

公共鑰匙加密技術需要一個空間來存儲數字證書和私鑰。通過將鑰匙和證書存儲到一個文件中(稱為keystore),Java Security Architecture實現了獨立於平台的加密技術。

Microsoft Windows把鑰匙和證書存儲到Windows注冊表和文件系統中。這就是說,在Windows系統上運行安全的Java程序的用戶必須在Java和Microsoft的鑰匙和證書庫之間輸入和輸出鑰匙和證書。好消息是,你可以“哄騙”Java應用程序通過Microsoft本地函數來運用Microsoft的證書和鑰匙庫。

通過將你的Java應用程序同Windows 鑰匙/證書庫結合起來,你雖然犧牲了平台獨立性,但你得到了四個好處:減少了管理和支持的成本、更方便用戶使用、更好的證書撤消校驗、以及更好的鑰匙和證書管理工具。

一個Java程序必須通過四個不同的類實現與Windows加密術的集成: · TrustManager Provider:用這個類來實現與Windows證書庫的集成並實現安全策略。

· KeyManager Provider:用這個類來實現與Windows私鑰庫的集成。

· RSA Signature Provider:數字簽名需要訪問私鑰庫。如果Java程序不能讀取私鑰(比如,如果私鑰存在一個加密了的智能卡上了),那麼簽名操作就必須在Windows中進行。

· RSA Cipher Provider:解密RSA加密的數據(如加密套接字協議層(SSL)對稱的鑰匙)需要訪問私鑰庫。如果Java程序不能讀取私鑰(比如,如果私鑰存在一個加密了的智能卡上了),那麼RSA解密操作就必須在Windows中進行。

我將講述與Windows平台集成的TrustManager Provider、KeyManager Provider、RSA Signature Provider和RSA Cipher Provider的用法。TrustManager和KeyManager可以讓你構建可運行的Windows支持的Java Secure Socket Extension(JSSE)應用程序。JSSE范例程序——EchoServer和EchoClient可以證明這一點。你不能覆蓋JSSE的內置的RSA Cipher Provider,所以,只有當私鑰可以從Windows鑰匙庫中輸出時,JSSE應用程序才可以運行。

如果你在編寫一個運用RSA簽名或RSA加密的Java應用程序,那麼你可以運用Windows支持的RSA Signature Provider和Cipher Provider。這不需要從Windows鑰匙庫中輸出私鑰。對於其它三個提供者(provider),你可以單獨使用每一個。

該代碼是用 beta版JDK 1.4.0-rc開發的,很穩定。不過,我們打算將該代碼作為一個框架,進行進一步的開發。在將該代碼用於生產環境前,你應該改進異常處理,確信在本地代碼中沒有內存洩露,並使密鑰的暴露降低到最小。為了測試代碼,你需要一個RSA數字證書。你可以從VeriSign網站www.verisign.com/client/enrollment得到一個臨時證書,有效期是60天。具體操作請遵循該站點上的指南。不要選定標為“Protect your Private Key”的框。因為沒有選定這個框,你的私鑰就可以輸出。

下面的代碼初試化了四個提供者:

MSTrustMgrProvider.install();
MSKeyMgrProvider.install();
MSRSASignProvider.install();
MSRSACipherProvider.install();
kmf = KeyManagerFactory
.getInstance("MSKMF");
tmf = TrustManagerFactory.
getInstance("MSTMF");
Cipher cipher =
Cipher.getInstance(
"RSA/ECB/PKCS1Padding");
Signature rsa =
Signature.getInstance(
"SHA1withRSA");

所有的四個提供者都調用了10個本地的Microsoft函數:

· MSgetCACerts()從Microsoft證書庫返回一列認證授權中心(Certificate Authority (CA))簽發的證書。

· 如果一個證書沒有被撤消,MSVerifyCertRevocation()返回true。

· MSgetPrivateKey()為一個特定的別名(alias )返回私鑰。(這裡所說的一個別名就是帶有一個RSA私鑰和證書的一個身份。)鑰匙從Microsoft鑰匙庫中輸出。

· MSgetCert()為一個特定的別名從Microsoft證書庫中返回一個證書。

· MSgetAliases()返回一組別名(帶有私鑰的一個身份的名字)。Microsoft鑰匙庫中的每個私鑰都有一個別名。

· MSrsaSignHash()返回哈希數據(hashed data)的RSA簽名。

· MSrsaDecrypt()用RSA算法來解密一個先前加密了的數據塊。

· MSrsaEncrypt()用Microsoft RSA provider來加密一個數據塊。

· MSrsaGetKeysize()返回Microsoft鑰匙庫中一個鑰匙的RSA鑰匙大小。

· MSgetCRL()將一個證書撤消清單(Certificate Revocation List(CRL))下載到Microsoft Internet緩存中。

一個約500行的用C語言代碼編寫的源文件mscryptofunctions.c中包含了所有這些函數。該代碼可以在Windows 98/NT4/2000/XP上運行。

公共鑰匙加密算法

公共鑰匙加密有兩個目的:加密和數字簽名。公共鑰匙加密運用一個包含兩部分的鑰匙(或一對鑰匙):一個私鑰和一個公鑰。公鑰帶有開始和終止日期、一個序號、一個身份(稱為Subject Distinguished Name)、和一個CA的簽名(見列表1)。RSA是最常用的公共鑰匙加密算法。

公共鑰匙加密運用一個公鑰和一個私鑰。一個數字證書(如下所示)包含公鑰、開始和結束日期、一個序號、一個身份和一個證書授權中心(CA)的簽名。
Serial number:
6822 3C33 7945 3AC8 F8C5 398B 7469 94E1
Signature algorithm: md5RSA
Issuer: CN = VeriSign Class 1 CA Individual
Subscriber-Persona Not Validated,
OU = www.verisign.com/repository/RPA Incorp.
By Ref.,LIAB.LTD(c)98,
OU = VeriSign Trust Network, O = VeriSign, Inc.
Valid from: Wednesday, May 30, 2001 7:00:00 PM
Valid to: Monday, July 30, 2001 6:59:59 PM
Subject: E = [email protected],
CN = Brian Boyter,
OU = Digital ID Class 1 <\? Microsoft,
OU = Persona Not Validated,
OU = www.verisign.com/repository/RPA Incorp.
by ref.,LIAB.LTD(c)98,
OU = VeriSign Trust Network, O = VeriSign, Inc.
Public key: 3081 8902 8181 00BA B459 0F39 156E
C69E C238 BFD0 401D DBB9 D207 DFA4 5DBD 09F3
5CE6 B5E6 C357 88DD 808B 0699 5F68 A2A4 6A8A
3B21 6D3D D0A1 1E5F DAB1 FB8E F835 F84F 849B
29A4 6943 8D59 0669 7C81 1D00 03B7 1A02 4E7A
8596 11BD 7CC4 07A3 D7E5 9FF6 5684 B853 04F0
0938 A11E 5218 F9AB F034 070D C8C4 6652 C19B
4C57 E435 EFDC 85D4 B269 07B7 0102 0301 0001
Basic constraints: Subject Type=End Entity,
Path Length Constraint=None
Certificate policy:
Policy Qualifier Id=CPS Qualifier:
https://www.verisign.com/CPS
Policy Qualifier Info:
Organization=VeriSign, Inc.,
Notice Number=1
CRL Distribution Point Distribution Point Name:
Full Name:
URL=http://crl.verisign.com/class1.crl
Thumbprint algorithm: sha1
Thumbprint: 74A8 9F07 43AA 8FFC C4D5 AB09 3773 3AFF F7E7 DFFC

公共鑰匙加密中的加密是用公鑰來完成的,解密是用私鑰完成的。公共鑰匙加密對於大量的加密來說運算很復雜,但它卻被廣泛用來分配密鑰。密鑰,或對稱加密算法,如DES和RC4,通常用於大量的加密,但是密鑰加密算法需要一些保密的方法來交換用於大量加密的鑰匙。一種技術是生成一個隨機數,用公共鑰匙加密算法來加密那個隨機數,然後將加密了的隨機數發送給遠端的同伴。發送者用遠端同伴的公鑰來加密隨機數。接收者用它自己的私鑰來解密這個隨機數。任何截取了加密的隨機數的第三方都不能解密那個隨機數,因為他沒有私鑰。

在數字簽名中,用私鑰來完成簽名,用公鑰來完成確認。被簽名了的文件通常是經過哈希算法處理過的。哈希算法是一個單向算法,它可以減小文件的大小。運用MD5哈希算法,文件被簡小到16字節。運用SHA1哈希算法,文件被簡小到20字節。然後,就用簽名人的私鑰對經哈希算法處理過的文件進行加密。任何人都可以用簽名人的公鑰來解密哈希文件。

你必須非常小心地保護私鑰。Windows將私鑰以有些令人迷惑的形式存儲在文件系統中。一個惡意的侵犯者可以進入到你的計算機並找到你的私鑰。任何得到了你的私鑰的人都可以化裝成你,在你不知道的情況下做出簽署文件等行為。一種保護私鑰的方法就是用一個加密的智能卡,該卡上存儲了私鑰。運用一個加密的智能卡,用戶仍然可以進行公鑰加密和簽名活動,但沒有人——甚至用戶——可以讀取私鑰。智能卡有一個RSA加密和簽名處理器,只有這個RSA處理器有權使用私鑰。

當一個SSL服務器向一個SSL客戶端確認身份時,客戶端必須根據下面這些標准來確定服務器的證書是否有效:

· 證書必須有一個信任鏈,其根CA必須是客戶端信任的。

· 服務器證書,和信任鏈中所有的CA證書必須有有效的簽名。每個證書都是由下面更高級的CA來簽署的,除了根CA外,它簽名自己的證書。

· 當前的日期和時間必須在服務器證書的有效期內,而且也在信任鏈中所有證書的有效期內。每個證書都有一個有效期(證書可以有效使用的一個開始日期和時間以及結束日期和時間)。

· 每個CA應該管理和公布一個CRL。客戶端必須可以從信任鏈中的每個CA得到CRLs,來查看服務器證書或下屬CA的一個證書是否已被其下面更高級的CA撤消了。

· 證書必須可以有效用於其目的。鑰匙的用途定義在證書中。例如,CA批准的僅用於數字簽名的一個鑰匙就不能用於SSL鑰匙交換。

Java安全實現環境中不進行證書撤消確認,就是說,它不進行CRL處理。我將向你展示如何運用Microsoft Windows的本地加密函數來檢查證書信任鏈中的CRLs,從而為Java實現一個TrustManager和KeyManager。

TrustManager

javax.net.ssl.X509TrustManager有三個方法,你可以在MSTrustManagerlmpl.java中找到: · getAcceptedIssuers()為Microsoft證書庫中的所有CAs返回一組證書。

· checkClientTrusted()執行服務器的安全策略。

· checkServerTrusted()執行客戶端的安全策略。

一個典型的TCP網絡安全策略是:

· 客戶端開始連接。假設客戶端只連接到“安全的”服務器。客戶端應該要求服務器用一個數字證書向客戶端證明身份。通過確認服務器的證書(信任鏈、簽名是有效的,有效期、證書沒有被撤消,而且證書是批准用於RSA鑰匙交換的),客戶端確認服務器的真實性。客戶端通過有效的證書來信任服務器。

· 服務器接收來自所有客戶端的TCP連接,有些客戶端可能是惡意的。服務器可以要求客戶端用一個數字證書向服務器證明身份。那樣的話,客戶端的身份就可以被確認,而且多種信任級別也可以實現了。如果服務器不要求客戶端證明身份,服務器應該假設所有的客戶端都是惡意的。

你可以在checkServerTrusted()中看到,實現客戶端安全策略是很容易的。CheckServerTrusted()檢查簽名、信任鏈中證書的有效日期和CRLs。(我在後面會探討證書撤消處理。)checkClientTrusted()方法與checkServerTrusted()是一樣的。一般來說,這個安全策略對服務器來說並不夠。一種增強服務器安全狀態的方法就是要求客戶端用數字證書來證明身份,只接受由一個特定的CA(如VeriSign CA)發布的證書,並且檢驗證書的Subject Distinguished Name中的特殊字段(如0=sun.com)。只需要幾行Java代碼就可以把這個過程添加到checkClientTrusted()中了。你需要定制checkClientTrusted()來實現你的安全策略(見列表2)。

checkClientTrusted()方法檢查簽名、信任鏈中證書的有效日期和CRLs。但是,對服務器來說,這個安全函數並不夠。你可以通過定制checkClientTrusted()來增強安全策略。
public void checkClientTrusted(
X509Certificate chain[]) {
// DontKnowFlag indicates what to do if we're
// not sure if the certificate is revoked
// int DontKnowFlag=0; // reject the cert
// int DontKnowFlag=1; // accept the cert
int DontKnowFlag=2; // ask the user
   // check for revoked certs in the cert chain
if (com.boyter.mscrypto.MSValidCertificate.
isCertChainValid(chain, DontKnowFlag))
return;
   // client cert is not trusted
System.out.println("Client Certificate is not Trusted - aborting");
System.exit(2);
}

Java提供了幾個與證書鏈處理相關的類,在Java Certification Path API Programmer's Guide中有進一步說明。我對它們做過實驗,最後決定不用它們,因為我認為它們太復雜了。

TrustManager有第三個方法getAcceptedIssuers()。該方法為Microsoft證書庫中所有CAs返回一組證書。Microsoft將這些證書存儲在Registry中;你可以通過啟動REGEDIT程序並查看HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\Root\Certificates找到它們。GetAcceptedIssuers()方法執行了Microsoft的本地函數CertEnumCertificateslnStore()。CA證書被作為一組base64位編碼的字符串傳回到Java方法。Java.security.cert.CertificateFactory將base64編碼的證書轉換成Java證書。

證書撤消

有兩種撤消證書的方法:Online Certificate Status Protocol(OCSP)和CRL。OCSP(見RFC2560)並沒有得到廣泛的支持,所以我只探討用來確認一個證書的撤消狀況的CRLs。CAs定期公布一個CRL(見RFC2459)。CRL是一列證書序號,由CA簽署。如果一個證書的序號列在CRL中,那個證書就被撤消了,不再有效了。大多數證書在發布時,都有一個稱為CRL Distribution Point(CDP)的擴展名。CDP通常是一個HTTP或Lightweight Directory Access Protocol(LDAP)URL,它指明該CRL存儲在哪裡。我的VeriSign證書的CDP是http://crl.verisign.com/class1.crl。該CRL有500多K,所以需要花些時間來下載。

Java的確提供了類java.security.cert.LDAPCertStoreParameters,它可以用來從一個LDAP目錄獲取一個CRL,但這並不適用於VeriSign CRL(因為那個CRL存在一個Web服務器上,而不是一個LDAP服務器上)。Microsoft有一個比Java更豐富的API用來管理證書撤消。如果你浏覽一個安全的網站(HTTPS),IE將會檢驗服務器的證書是否已被撤消(假設服務器的證書有一個CDP)。IE將從證書鏈中的CAs獲取CRLs並把它們緩存到IE的Temporary Internet Files(臨時Internet文件)目錄中。在下一次需要來自那個CA的CRL時,IE將首先檢查緩存。如果它在緩存中找到該CRL,IE會檢查該CRL是否過期。(CRLs也有有效的from/to期限。VeriSign運用一個10天的有效期限。)如果CRL已經過期,IE將獲取一個新的CRL。

我考慮開發一個只用於Java的CRL獲取、緩存和撤消確認過程——但是那樣會需要大量的Java代碼,需要支持幾個URL協議(HTTP、LDAP、FTP、文件),需要用一個文件來存儲CRL緩存,而且需要復制Microsoft提供的底層框架。目的是運用IE的證書撤消底層框架。

Microsoft提供了一個函數CertVerifyRevocation(),它與IE緩存結合起來了並按需要下載和處理CRLs。我琢磨了好幾周撤消過程,最後得到了一個可靠的算法。當CertVerifyRecocation()下載一個CRL時,下載有時會中斷。這就使CertVerifyRevocation()返回假的、很難恢復的錯誤狀況。我實現的過程顯示在圖1中。

圖1. 證書撤消流程表

其訣竅就是調用函數CryptRetrieveObjectByUrl()來從LDAP、HTTP或FTP服務器預取CRL到IE緩存中,然後調用CertVerifyRevocation()來檢查這個證書是否已被撤消。雖然CryptRetrieveObjectByUrl()下載仍然要受到網絡的影響,造成連接中斷,但運用CryptRetrieveObjectByUrl()的好處就是你可以識別問題,報告一個有意義的錯誤信息,並且(如果你需要)可以提示用戶接受或拒絕該證書。不幸的是,只有Windows 2000或更高版本中有CryptRetrieveObjectByUrl()。如果程序要在Windows 98或Windows NT4上運行,你就不能預取CRL。

我也嘗試運用Microsoft函數來下載CRLs,然後在Java中處理CRL。對於一個大的CRL(比如VeriSign CRL),這種方式很慢。

列表3顯示關於isCertRevoked()方法的Java代碼片段。IsCertRevoked()方法調用了兩個本地函數,MSgetCRL()和MSVerifyCertRevocation()。MSgetCRL()調用Microsoft函數CryptRetrieveObjectByURL(),如果CRL不在緩存中,它可以使CRL被下載。你不需要把CRL傳遞到Java中,因為它從來不用在Java中。函數MSCertVerifyRevocation()調用Microsoft函數CertVerifyRevocatoin()來確定一個證書是否被撤消了。列表4顯示關於MSgetCRL()和MSVerifyCertRevocation()的代碼片段。警告:當我第一次在Windows 2000 Server PC上測試CertVerifyRevocation()時,它沒有運行。在我將PC升級到Service Pack 2後,重新測試就很成功。

我們的確認程序中的isCertRevoked()方法調用了兩個本地函數:MSgetCRL()和MSVerifyCertRevocation() (如列表4所示)。
boolean isCertRevoked(X509Certificate cert,
int DontKnowFlag) {
byte[] certblob = cert.getEncoded();
   // Does the cert have a CDP (
// CRL distribution point)???
byte[] CDPblob = cert.getExtensionValue(
"2.5.29.31");
   // yes there is a CDP - ASN parse the CDP
String[] URLarray = MSF.MSparseCDP(CDPblob);
for (int i=0; i<URLarray.length; i++) {
String URL = URLarray[i];
// go fetch that CRL
if (MSF.MSgetCRL(URL)) {
// url was fetched correctly
break;
}
   // is the cert revoked???
int revocationStatus =
MSF.MSVerifyCertRevocation(certblob);
switch (revocationStatus) {
case 0: // cert is revoked
return AskUserWhatHeWantsToDo(DontKnowFlag);
case 1: // cert is not revoked
return false;
default:
}
   // processing error - cannot determine
// if cert is revoked
return AskUserWhatHeWantsToDo(DontKnowFlag);
}

函數MSgetCRL()和MSVerifyCertRevocation()由方法isCertRevoked()調用(如列表3所示)。MSgetCRL()調用Microsoft函數CryptRetrieveObjectByURL(),如果在緩存中沒有CRL,可以用該函數來下載它。函數MSVerifyCertRevocation()調用Microsoft函數CertVerifyRevocation()來查看一個證書是否已被撤消。

MSgetCRL(jstring jurl)
{
if (!CryptRetrieveObjectByUrl(
url, CONTEXT_OID_CRL, 0, timeout*1000,
(LPVOID)&crl, NULL, NULL, NULL, NULL)) {
printf("CryptRetrieveObjectByUrl failed\n");
// cached url is corrupted
DeleteUrlCacheEntry(url);
return JNI_FALSE;
}
return JNI_TRUE;
}
MSVerifyCertRevocation (jbyteArray jCert)
{
rgpvContext[0] = (PVOID)pCertContext;
if (CertVerifyRevocation(X509_ASN_ENCODING,
CERT_CONTEXT_REVOCATION_TYPE, 1, rgpvContext,
0, NULL, &status)) {
return 1; // cert is not revoked
}
   if (status.dwError == CRYPT_E_REVOKED)
return 0; // cert is revoked
   return -2; // processing error
}

KeyManager

javax.net.ssl.X509KeyManager有六個方法:

· getClientAliases()返回一組客戶端別名。(這裡的一個別名就是帶有一個RSA私鑰和證書的一個身份。)

· getServerAliases()返回一組服務器別名。

· chooseClientAlias()從一組別名中選擇一個客戶端別名。

· chooseServerAlias()從一組別名中選擇一個服務器別名。

· getCertificateChain()為一個證書返回有序的證書鏈。

· getPrivateKey()為一個別名返回私鑰。

Microsoft將私鑰和它們相關的證書存儲在文件系統中。我的私鑰和證書存儲在目錄C:\Documents和Settings\Administrator\Application Data\Microsoft中。你不需要知道鑰匙存儲在哪裡,因為Microsoft提供了一個API用來訪問鑰匙和證書庫。

KeyManager的getClientAliases()和getServerAliases()方法執行Microsoft本地的函數CertEnumCertificateslnStore(),查看Microsoft的“My”證書庫中的所有證書。“My”證書庫中的證書應該有一個與它們相關連的私鑰。每個證書/私鑰組合都有一個特殊的標識符,稱為CONTAINER;這就相當於Java中的“別名”(見列表5)。

CertEnumCertificatesInStore()方法查看Microsoft中“My”證書庫中所有的證書;每個證書都有一個相關的私鑰。每個證書/私鑰組合都有一個標識符,稱為CONTAINER,這就相當於Java中的別名。
JobjectArray MSgetAliases (jstring jcertStore) {
   // open Microsoft certificate store
hSystemStore = CertOpenStore(
CERT_STORE_PROV_SYSTEM,
0, 0, CERT_SYSTEM_STORE_CURRENT_USER,
certStore);
   // read all the certificates
while(pCertContext=
CertEnumCertificatesInStore(
hSystemStore, pCertContext)) {
   // get the cert key container name
CertGetCertificateContextProperty(
pCertContext, CERT_KEY_PROV_INFO_PROP_ID,
alias, &propLen);
   // add to list of aliases
AddDataToList(&list, alias, strlen(alias)+1);
}
return jaliases;
}

列表5

方法chooseClientAlias()和chooseServerAlias()從一列別名中返回一個客戶端(或服務器)別名。如果只有一個別名,在選擇別名時就不會有歧義。因為Java的創始人對於選擇運用哪個別名並沒有提供任何特殊的指導,我就選擇了清單中第一個別名。另一個選擇合適的客戶端別名的方法就是提示用戶從一列可能的別名中選擇一個別名。(在SSL客戶端,你通常可以這麼做,但在服務器上不行。)

KeyManager的getCertificateChain()方法為一個證書返回有序的證書鏈。該方法通過調用getAcceptedIssuers()方法得到一列可信任的證書簽發者。首先我們找到證書簽發者的Distinguished Name(DN),然後我們查看是否有哪個可信任的簽發者有那個DN。幾個簽發者可以有同一個DN。對於具有簽發者DN的每個證書,我們提取公鑰並嘗試在原始證書上確認簽名。如果沒有一個簽發者有正確的DN和正確的公鑰,證書鏈就被破壞了,出現一個異常。如果我們找到了正確的簽發者簽發的證書,我們就重復上述過程來查找和確認那個證書的簽發者。重復該過程,直到我們達到根CA。對於一個根CA,Subject DN和簽發者DN是一樣的(見列表6)。

方法getCertChain()為一個證書返回有序的證書鏈。
MSCryptoFunctions MSF = new MSCryptoFunctions();
X509Certificate[] getCertChain(
X509Certificate cert) {
   try {
getCACerts();
   Principal subject = cert.getSubjectDN();
Principal issuer = cert.getIssuerDN();
CertChainList.add(cert);
   // stop if issuer==subject (root CA)
while (!(issuer.equals(subject))) {
   match = false;
X509CertSelector xcs =
new X509CertSelector();
xcs.setCertificateValid(new Date());
   Collection certcollection =
CACerts.getCertificates(xcs);
   //
// the next 7 lines are inserted to work
// around a problem with X509CertSelector.
// we should be able to do this with
// xcs.setSubject(issuer.toString());
//
Iterator iter = certcollection.iterator();
while ( iter.hasNext() ) {
X509Certificate cacert =
(X509Certificate) (iter.next());
if (!cacert.getSubjectDN().equals(issuer))
iter.remove();
}
   issuerArray =
new X509Certificate[
certcollection.size()];
issuerArray = (X509Certificate[])
certcollection.toArray(issuerArray);
   for (int i=0; i<\<>issuerArray.length; i++)
if (verifySignature(issuerArray[i], cert)){
match = true;
cert = issuerArray[i];
subject = cert.getSubjectDN();
issuer = cert.getIssuerDN();
CertChainList.add(cert);
break;
}
if (!match) {
return null; // cert chain broken
}
}
} catch (Exception e) {
e.printStackTrace();
}
   X509Certificate[] CertChain =
new X509Certificate[CertChainList.size()];
CertChainList.toArray(CertChain);
   return CertChain;
}

getPrivateKey()方法為一個別名返回私鑰,假設私鑰可以從Microsoft鑰匙庫中輸出。記住,有時私鑰是不能輸出的。(例如,如果你用了一個加密了的智能卡,那麼就沒人可以從智能卡上讀取私鑰了。)如果不能輸出私鑰,getPrivateKey()就返回一個虛擬的私鑰。所以,如果getPrivateKey()不能得到私鑰,我們就騙Java,讓它認為得到了私鑰。getPrivateKey()也緩存別名,所以,當一個Java程序試圖執行一個RSA數字簽名函數時,我們就會知道運用哪個私鑰了(緩存的別名),而且Microsoft加密提供者就可以執行我們想要的RSA簽名或解密函數了(見列表7)。

方法getPrivateKey()為一個別名返回私鑰,假設私鑰可以從Windows鑰匙庫中輸出。
MSCryptoFunctions MSF = new MSCryptoFunctions();
public PrivateKey getPrivateKey(String alias) {
   // get the private key from MS Windows for
// this alias
byte[] keyblob = MSF.MSgetPrivateKey(alias);
   if (keyblob == null) { // generate a dummy key
byte[] modblob = new byte[128];
for(i=0; i<128; i++)
modblob[i] = 127;
mod = new BigInteger(modblob);
exp = mod;
   } else { // use the key that got exported
for(i=0; i<keysize/8; i++) {
modblob[i] = keyblob[19-i+(keysize/16)*2];
expblob[i] = keyblob[19-i+(keysize/16)*9];
}
mod = new BigInteger(1, modblob);
exp = new BigInteger(1, expblob);
}
RSAPrivateKeySpec privKeySpec =
new RSAPrivateKeySpec(mod, exp);
KeyFactory kf = KeyFactory.getInstance("RSA");
privkey = kf.generatePrivate(privKeySpec);
return privkey;
}

RSA Signature Provider

java.security.SignatureSpi類有五個方法:

· engineInitSign()為簽名初試化RSA簽名引擎。

· engineInitVerify()為確認一個簽名初試化RSA簽名引擎。

· engineUpdate()增加數據到簽名或確認操作。

· engineSign()完成簽名操作並返回數字簽名。

· engineVerify()完成簽名-確認過程,如果簽名是正確的,返回true。

記住,數字簽名需要私鑰,確認一個數字簽名需要公鑰。如果我們有權使用Microsoft的私鑰——即,私鑰是可輸出的——就沒必要在Microsoft本地代碼中執行RSA簽名操作了。但是在有些情況下(例如,如果我們運用一個加密了的智能卡),我們無權使用私鑰。如果私鑰是不能輸出的,我們必須用Microsoft本地代碼進行數字簽名。

如果Java程序運用KeyManager的方法getPrivateKey()來獲取私鑰,私鑰的別名就被緩存起來。當RSA Signature Provider進行簽名時,我們就重用緩存的別名,並調用Microsoft本地函數來執行簽名操作而不用暴露私鑰。(這聽起來有些虛假,但確實可行。)注意,在engineInitSign()中沒有用私鑰。我用Java JCE哈希函數來進行運算,然後用Microsoft Cryptographic Provider從哈希文件中生成RSA簽名。

通過添加一個engineInitSign(字符串別名)方法,可以改進Java Signature類:

MSCryptoFunctions MSF =
new MSCryptoFunctions();
protected void engineInitSign(
PrivateKey privateKey) {
MSF.MSrsaSignInit((byte[])null,
"MD5");
}
   protected byte[] engineSign() {
byte[] hash = MD5.digest();
byte[] mssig =
MSF.MSrsaSignHash(hash,
(byte[])null, "MD5");
return mssig;
}

我們可以在Microsoft本地代碼中實現簽名-確認,但這麼做沒有優勢。在實現過程中,我們運用了JSSE提供者在Java中執行確認:

protected void engineInitVerify(
PublicKey publicKey) {
jsse = Signature.getInstance(
"MD5withRSA", "SunJSSE");
jsse.initVerify(publicKey);
}
   protected boolean engineVerify(
byte[] sigBytes) {
boolean verifyresult=false;
verifyresult =
jsse.verify(sigBytes);
return verifyresult;
}

RSA Cipher Provider

javax.Crypto.CipherSpi類有12個方法:

· engineInit()初試化密碼提供者(cipher provider)。

· engineUpdate()繼續一個由多個部分組成的加密或解密操作。

· engineDoFinal()加密或解密一個單一操作中的數據,或完成一個由多個部分組成的操作。

· engineGetBlockSize()返回字區大小(以字節形式)。

· engineGetIV()返回初試化向量。它不用於RSA密碼。

· engineGetKeySize()返回一個特定的鑰匙對象的鑰匙大小。

· engineGetOutputSize()以字節形式返回輸出長度,輸出緩沖器需要這個長度來保存下一個update或doFinal操作的結果,輸入長度已假定。

· engineGetParameters()返回這個密碼運用的參數。

· engineSetMode()設置密碼的模式(加密或解密)。

· engineSetPadding()設置這個密碼的填充機制(當前只支持PKCS1填充)。

· engineWrap()封裝一個鑰匙(未實現)。

· engineUnwrap()解開一個鑰匙(未實現)。

RSA加密過程需要公鑰;RSA解密需要私鑰。如果我們有權使用Microsoft鑰匙庫中的RSA私鑰——即,私鑰是可以輸出的——那麼就沒必要在Microsoft本地代碼中執行RSA密碼操作。但在有些情況下(例如,如果我們運用一個加密了的智能卡),我們就無權使用私鑰。如果私鑰是不能輸出的,我們必須用Microsoft本地代碼進行RSA解密。

RSA密碼在Windows本地代碼中的實現很簡單。本質的解密過程如下:

MSrsaDecrypt (jstring jpadalg,
jbyteArray jdata) {
CryptAcquireContext(
&hDecryptProv, alias, NULL,
PROV_RSA_FULL,0);
CryptGetUserKey(hDecryptProv,
AT_KEYEXCHANGE, &hDecryptKey);
CryptDecrypt(hDecryptKey, 0,
TRUE, 0, encryptblob, &ndata);
return decryptblob;
}

加密幾乎是一樣的。因為RSA加密只需要公鑰,加密模式可以在Java中執行。我選擇在Windows本地代碼中實現RSA加密和解密,因為這很容易。

RSA密碼是一個美國政府控制的加密算法。如果你想將RSA cipher provider同Sun JCE結合起來運用,你必須創建一個JAR文件,用一個DSA鑰匙簽署它,然後添加一個由Sun Microsystems簽發的證書。為了方便,我已經把所有的mscrypto-class文件放在了一個單一的包中(com.boyter.mscrypto)。在簽署的JAR文件中,只需要有MSRSACipherProvider.class和MSRSACipherFactoryImpl.class。用來創建和簽名JAR文件的Windows命令是:

jar cvf mscrypto.jar com
jarsigner -keystore keystore
-storepass foobar mscrypto.jar
jcesigner

你可以從“How to Implement a Provider for the Java Cryptography Extension 1.2.1.”(見資源)的第五步找到關於從Sun獲取一個JCE代碼簽署的證書的說明。如果你想避免JCE的局限性(如美國政府的輸出控制),你可以用一個clean-room式的實現環境(如BeeJCE)來代替JCE。我提供了一個稱為msrsatest.java的程序,可以用來測試Microsoft 加密的RSA簽名和RSA密碼提供者。為此,你必須在Microsoft Windows中安裝一個RSA私鑰和證書。

有根據的運用理由

將Java安全特征同本地Microsoft Windows安全平台結合起來有許多好的理由,包括減少了的管理費用、CRL確認和與智能卡的兼容。將Java JCE同Windows證書和鑰匙庫結合起來的另一個理由是用來管理Java證書和鑰匙庫的Java工具很麻煩。Microsoft平台有一個更好的圖形用戶界面(GUI)用來管理Windows鑰匙和證書庫。你可以通過運行CERTMGR.EXT程序(與Windows平台SDK在一起)來啟動GUI,或者你可以從IE窗口來啟動:運用下拉菜單Tools | Internet Option,選擇Content鍵,然後選擇Certificates。

在將Microsoft 加密支持的KeyManager和TrustManager用於JSSE時,你不會有什麼問題。Microsoft 加密支持的RSA Signature Provider和RSA Cipher Provider也可以運行,但不能用於JSSE。

資源

The Factory design pattern:

The SSL specification-JSSE (Java Secure Sockets Extension):

Instructions for installing JSSE:

"Build secure network applications with SSL and the JSSE API"

Clean-room Java implementation of RSA signing and encryption:

"How to Implement a Provider for the Java Cryptography Extension 1.2.1"

BeeJCE, Virtual Unlimited's cleanroom implementation of JCE:

Microsoft SDK Crypto API Reference:

Java Certification Path API Programmer's Guide:

Internet X.509 Public Key Infrastructure Certificate and CRL Profile:

關於作者:

Brian Boyter做過20年的軍隊情報軍官,還曾是Air Force Information Warfare Center、Cisco Systems和Digital Defense的安全顧問。現在,他是Avaya Inc.的高級安全顧問。你可以通過[email protected]聯系他。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved