1. 程式人生 > >Java加密體系結構(JCA)參考指南

Java加密體系結構(JCA)參考指南

介紹

Java平臺強調安全性,包括語言安全,密碼學,公鑰基礎設施,認證,安全通訊和訪問控制。

JCA是平臺的一個主要部分,包含一個“Provider”體系結構和一組用於數字簽名,訊息摘要(雜湊),證書和證書驗證,加密(對稱/非對稱塊/流密碼),金鑰生成 管理和安全隨機數生成等等。 這些API允許開發人員將安全性輕鬆整合到應用程式程式碼中。 這個架構是圍繞以下原則設計的:

實現獨立性

應用程式不需要自己實現安全性。 相反,他們可以從Java平臺請求安全服務。安全服務在Provider(見下文)中實現,通過標準介面接入Java平臺。 應用程式可能依靠多個獨立的提供者來提供安全功能。

實現互操作性

Provider可以跨應用程式進行互操作。 具體而言,應用程式不繫結到特定的提供者,而提供者也不繫結到特定的應用程式。

演算法可擴充套件性

Java平臺包含許多內建的Provider,這些Provider實現了當今廣泛使用的一組基本的安全服務。但是,一些應用程式可能依賴尚未實施的新興標準或專有服務。 Java平臺支援安裝實現這些服務的定製提供程式。

JDK中提供的其他密碼通訊庫使用JCA提供程式體系結構,但在別處進行了介紹。 Java安全套接字擴充套件(JSSE)提供對安全套接字層(SSL)和傳輸層安全性(TLS)實現的訪問。 Java通用安全服務(JGSS)(通過Kerberos)API以及簡單身份驗證和安全層(

SASL)也可用於在通訊應用程式之間安全地交換訊息。

術語註釋

  • 在JDK 1.4之前,JCE是一個非捆綁產品,因此,JCA和JCE被定期稱為獨立的,獨特的元件。 但是JCE現在被捆綁在JDK中,所以區別變得不那麼明顯了。 由於JCE使用與JCA相同的體系結構,所以JCE應該更適合作為JCA的一部分。
  • JDK中的JCA包含兩個軟體元件:
    • 定義和支援Provider為其提供實現的加密服務的框架。 這個框架包含了諸如java.security,javax.crypto,javax.crypto.spec和javax.crypto.interfaces等軟體包。
    • Sun,SunRsaSign,SunJCE等實際包含了實際的加密實現的提供商。

無論何時提及特定的JCA提供者,都將由提供者的名稱明確提及。

警告:JCA可以輕鬆地將安全功能整合到您的應用程式中。 然而,除了基本介紹討論API所需的概念之外,本文件不包括安全/密碼學理論。 本文件也沒有涵蓋特定演算法的優缺點,也不涵蓋協議設計。 密碼學是一個高階課題,為了充分利用這些工具,應該參考一個可靠的,最近最新的參考文獻。

你應該總是明白你在做什麼,為什麼:不要簡單地複製隨機程式碼,並期望它完全解決你的使用場景。 由於選擇了錯誤的工具或演算法,許多已經部署了的應用程式包含重大安全或效能問題。

設計原則

JCA是圍繞這些原則設計的:

  • 實現獨立性和互操作性
  • 演算法獨立性和可擴充套件性

實現獨立性和演算法獨立性是互補的; 您可以使用加密服務(如數字簽名和訊息摘要),而無需擔心實現細節,甚至是構成這些概念基礎的演算法。 儘管完全的演算法獨立性是不可能的,但JCA提供了標準化的,演算法特定的API。 當實現獨立性不可取時,JCA可以讓開發人員指出具體的實現。

實現獨立性是使用基於“Provider”的體系結構實現的。 術語 Cryptographic Service Provider (CSP)(在本文件中與“Provider”可互換使用)是指實現一個或多個密碼服務(如數字簽名演算法,訊息摘要演算法和金鑰轉換服務)的包或一組包。 程式可以簡單地請求實現特定服務(例如DSA簽名演算法)的特定型別的物件(例如簽名物件),並從一個安裝的提供者獲得實現。 如果需要的話,程式可以改為請求來自特定提供者的實現。 提供商可能會更新透明的應用程式,例如,當一個更快或更安全的版本可用的時候。

實現互操作性意味著各種實現可以相互協作,使用彼此的金鑰,或者驗證彼此的簽名。 這就意味著,對於相同的演算法,由一個提供者生成的金鑰可以被另一個提供者使用,並且由一個提供者生成的簽名可以被另一個提供者驗證。

演算法可擴充套件性意味著可以容易地新增適合於所支援的引擎類之一的新演算法。

架構

加密服務提供者(CSP)

java.security.Provider是所有安全提供程式的基類。 每個CSP都包含這個類的一個例項,它包含了提供者的名字,並列出了它實現的所有安全服務/演算法。 當需要特定演算法的例項時,JCA框架會諮詢提供者的資料庫,如果找到合適的匹配項,則建立該例項。

Provider包含一個包(或一組包),為宣告的加密演算法提供具體的實現。 每個JDK安裝都預設安裝並配置了一個或多個提供程式。 其他提供者可以靜態或動態新增(參見ProviderSecurity類)。 客戶端可以配置其執行時環境來指定提供程式的首選順序。 首選順序是在沒有請求特定提供者時提供者搜尋請求的服務的順序。

要使用JCA,應用程式只需要請求特定型別的物件(如MessageDigest)和特定的演算法或服務(如“SHA-256”演算法),並從一個已安裝的提供者獲取實現。 或者,程式可以請求來自特定提供者的物件。 每個提供者都有一個名字來引用它。

    md = MessageDigest.getInstance("SHA-256");
    md = MessageDigest.getInstance("SHA-256", "ProviderC");

下圖說明了請求“SHA-256”訊息摘要實現。 這些圖顯示了實現各種訊息摘要演算法(“SHA-256”,“SHA-384”和“SHA-512”)的三個不同的提供者。 供應商按照優先順序從左至右排列(1-3)。 在第一個示例中,應用程式請求SHA-256演算法實現而不指定提供者名稱。 提供程式按優先順序搜尋,並返回提供該特定演算法ProviderB的第一個提供程式的實現。 在第二個圖中,應用程式請求來自特定提供者ProviderC的SHA-256演算法實現。 這次ProviderC的實現被返回,即使具有更高優先順序的提供者ProviderB也提供SHA-256實現。圖1 圖1 自動搜尋Provider

圖2圖2 指定特定的Provider

JDK中的加密實現主要是出於歷史原因通過幾個不同的提供者(Sun,SunJSSE,SunJCE,SunRsaSign)分發的,但在較小的程度上由它們提供的功能和演算法的型別來分發。 其他Java執行時環境可能不一定包含這些Sun提供程式,因此除非知道特定的提供程式可用,否則應用程式不應請求提供程式特定的實現。

JCA提供了一組API,允許使用者查詢安裝哪些提供程式以及支援哪些服務。

此架構還使終端使用者可以輕鬆新增其他提供商。 許多第三方提供者實現已經可用。 有關如何編寫,安裝和註冊提供程式的更多資訊,請參閱Provider類。

Provider如何實際實現

如前所述,通過定義所有應用程式用於訪問服務型別的通用高階應用程式程式設計介面(API)來實現演算法獨立性。 實現獨立性是通過讓所有提供者實現符合定義良好的介面來實現的。 引擎類的例項因此被具有相同方法簽名的實現類“支援”。 應用程式呼叫通過引擎類路由,並傳遞到底層的後臺實現。 該實現處理請求並返回正確的結果。

每個引擎類中的應用程式API方法都通過實現相應服務提供程式介面(SPI)的類路由到提供程式的實現。 也就是說,對於每個引擎類,都有一個對應的抽象SPI類,它定義了每個加密服務提供者演算法必須實現的方法。 每個SPI類的名稱與相應的引擎類相同,接著是Spi。 例如,Signature類提供對數字簽名演算法的功能的訪問。 實際的提供者實現是在SignatureSpi的子類中提供的。 應用程式呼叫引擎類的API方法,在實際的實現中又呼叫SPI方法。

每個SPI類都是抽象的。 為了為特定演算法提供特定型別的服務,提供者必須繼承相應的SPI類,並提供所有抽象方法的實現。

對於API中的每個引擎類,通過呼叫引擎類中的getInstance()工廠方法來請求和例項化實現例項。 工廠方法是一個返回一個類的例項的靜態方法。 引擎類使用上述框架Provider選擇機制來獲取實際的後臺實現(SPI),然後建立實際的引擎物件。 引擎類的每個例項都封裝(作為專用欄位)相應SPI類的例項,稱為SPI物件。 API物件的所有API方法都宣告為final,並且它們的實現呼叫封裝SPI物件的相應SPI方法。

為了使這個更清楚,請檢視下面的程式碼和插圖:

import javax.crypto.*; 
Cipher c = Cipher.getInstance("AES"); 
c.init(ENCRYPT_MODE, key);

獲取Cipher

這裡的應用程式需要一個“AES”演算法的javax.crypto.Cipher例項,並不關心使用哪個提供者。應用程式呼叫Cipher引擎類的getInstance()工廠方法,然後請求JCA框架查詢支援“AES”的第一個提供程式例項。該框架會諮詢每個已安裝的提供者,並獲取提供者類的提供者例項。 (回想一下,Provider類是可用演算法的資料庫。)框架搜尋每個提供者,最終在CSP3中找到合適的條目。這個資料庫入口指向擴充套件CipherSpi的實現類com.foo.AESCipher,因此適用於Cipher引擎類。建立一個com.foo.AESCipher的例項,並將其封裝在一個新建立的javax.crypto.Cipher例項中,該例項返回給應用程式。當應用程式現在對Cipher例項執行init()操作時,Cipher引擎類將請求路由到com.foo.AESCipher類中相應的engineInit()支援方法中。

附錄A列出了為Java環境定義的標準名稱。 其他第三方提供商可以定義他們自己的這些服務的實現,甚至是額外的服務。

金鑰管理

稱為“KeyStore”的資料庫可用於管理金鑰和證書的儲存庫。 金鑰庫可用於需要用於身份驗證,加密或簽名的資料的應用程式。

應用程式可以通過java.security包中的KeyStore類的實現來訪問金鑰庫。 建議的金鑰庫型別(格式)是“pkcs12”,它基於RSA PKCS12個人資訊交換語法標準。 預設的金鑰庫型別是“jks”,這是一種專有格式。 其他金鑰庫格式也是可用的,例如作為替代專有金鑰庫格式的“jceks”,以及基於RSA PKCS11標準的“pkcs11”,並且支援對硬體安全模組和智慧卡等加密令牌的訪問。

應用程式可以使用上述相同的提供程式機制,從不同的提供程式中選擇不同的金鑰庫實現。

請參閱金鑰管理部分了解更多資訊。

JCA概念

本節介紹主要的JCA API。

Engine類和演算法

引擎類為特定型別的密碼服務提供介面,而不依賴於特定的密碼演算法或提供者。 引擎需要提供:

  • 密碼操作(加密,數字簽名,訊息摘要等),
  • 發生器或密碼材料的轉換器(金鑰和演算法引數),或
  • 物件(金鑰庫或證書)封裝了密碼資料,可以在更高的抽象層使用。

以下引擎類是可用的:

  • Signature:使用金鑰初始化,這些簽名用於簽署資料並驗證數字籤
  • Cipher:用金鑰初始化,用於加密/解密資料。存在各種型別的演算法:對稱批量加密(例如AES),非對稱加密(例如RSA)和基於密碼的加密(例如PBE)。
  • Message Authentication Codes(MAC):與MessageDigests一樣,它們也會生成雜湊值,但是首先使用金鑰初始化以保護訊息的完整性。KeyFactory:用於將Key型別的現有不透明金鑰轉換為金鑰規範(底層金鑰材料的透明表示),反之亦然。SecretKeyFactory:用於將SecretKey型別的現有不透明加密金鑰轉換為金鑰規範(底層金鑰材料的透明表示),反之亦然。 SecretKeyFactorys是專門的KeyFactorys,只能建立金鑰(對稱)。KeyPairGenerator:用於生成一對適用於指定演算法的公鑰和私鑰。KeyGenerator:用於生成與指定演算法一起使用的新金鑰。KeyAgreement:由兩方或多方使用,商定和建立一個特定的金鑰,用於特定的密碼操作。AlgorithmParameters:用於儲存特定演算法的引數,包括引數編碼和解碼。AlgorithmParameterGenerator:用於生成適合於指定演算法的一組AlgorithmParameters。KeyStore:用於建立和管理金鑰庫。金鑰庫是金鑰的資料庫。金鑰庫中的私鑰具有與其關聯的證書鏈,用於驗證相應的公鑰。金鑰庫還包含來自可信實體的證書。CertificateFactory:用於建立公鑰證書和證書吊銷列表(CRL)。CertPathBuilder:用於構建證書鏈(也稱為證書路徑)。CertPathValidator:用於驗證證書鏈。CertStore:用於從儲存庫中檢索證書和CRL。

注意:生成器可以建立具有全新內容的物件,而工廠只能從現有材料(例如編碼)中建立物件。

核心類和介面

本節討論JCA中提供的核心類和介面:

本指南將首先介紹最有用的高階類(Provider,Security,SecureRandom,MessageDigest,Signature,Cipher和Mac),然後研究各種支援類。 現在,簡單地說,金鑰(公鑰,私鑰和加密)由各種JCA類生成和表示,並被高階類用作其操作的一部分。

本部分顯示每個類和介面中主要方法的簽名。 其中一些類(MessageDigest,Signature,KeyPairGenerator,SecureRandom,KeyFactory和金鑰規範類)的示例在相應的示例部分提供。

相關安全API軟體包的完整參考文件可以在軟體包摘要中找到:

Provider類

術語“加密服務提供者”(在本文件中與“提供者”可互換使用)是指提供JDK安全API加密特徵子集的具體實現的一個或一組包。 Provider類是這種包或一組包的介面。 它具有訪問提供程式名稱,版本號和其他資訊的方法。 請注意,除了註冊加密服務的實現外,Provider類還可以用於註冊可能被定義為JDK安全API或其擴充套件之一的其他安全服務的實現。

為了提供密碼服務的實現,一個實體(例如開發組)編寫實現程式碼並建立Provider類的子類。 Provider子類的建構函式設定各種屬性的值; JDK安全API使用這些值來查詢提供者實現的服務。 換句話說,子類指定實現服務的類的名稱。

Provider實現

不同的實現可能具有不同的特性。 有些可能是基於軟體的,有些可能是基於硬體的。 有些可能是平臺無關的,有些可能是平臺特定的。 一些供應商的原始碼可能可用於審查和評估,而有些則可能不可用。 JCA讓終端使用者和開發者決定他們的需求。

在本節中,我們解釋終端使用者如何安裝符合他們需求的加密實現,以及開發人員如何請求適合他們的實現。

Provider的請求和服務如何實現

對於API中的每個引擎類,通過呼叫引擎類的getInstance方法之一來請求和例項化實現例項,指定想要實現的所需演算法的名稱以及可選的提供者(或提供者類)的名稱。

static EngineClassName getInstance(String algorithm) throws NoSuchAlgorithmException 

static EngineClassName getInstance(String algorithm, String provider) throws NoSuchAlgorithmException, NoSuchProviderException 

static EngineClassName getInstance(String algorithm, Provider provider) throws NoSuchAlgorithmException

其中EngineClassName是所需的引擎型別(MessageDigest / Cipher / etc)。 例如:

MessageDigest md = MessageDigest.getInstance("SHA-256"); 
KeyAgreement ka = KeyAgreement.getInstance("DH", "SunJCE");

分別返回“SHA-256”MessageDigest和“DH”KeyAgreement物件的例項。

附錄A包含已經標準化的用於Java環境的名稱列表。 有些提供商可能會選擇也包含也指向相同演算法的別名。 例如,“SHA256”演算法可能被稱為“SHA-256”。 應用程式應該使用標準名稱而不是別名,因為不是所有的提供者都可以用同樣的方法來命名演算法名稱。

注意:演算法名稱不區分大小寫。 例如,以下所有呼叫都是等同的:

MessageDigest.getInstance("SHA256") 
MessageDigest.getInstance("sha256") 
MessageDigest.getInstance("sHa256")

如果沒有指定提供程式,getInstance將搜尋已註冊的提供程式,以獲得與指定演算法關聯的請求的加密服務的實現。 在任何給定的Java虛擬機器(JVM)中,提供程式都是以給定的優先順序安裝的,如果沒有請求特定的提供程式,搜尋提供程式列表的順序就會被搜尋到。 例如,假設在JVM,PROVIDER_1和PROVIDER_2中安裝了兩個提供程式。 假設:

  • PROVIDER_1實現SHA-256和DESede。
  • PROVIDER_1擁有優先順序1(最高優先順序)。
  • PROVIDER_2使用DSA,SHA-256,RC5和RSA實現SHA256。
  • PROVIDER_2具有優先順序2。

現在讓我們看看三種情況:

  1. 如果我們正在尋找一個SHA-256實現,那麼這兩個提供者都提供這樣的實現。 由於PROVIDER_1具有最高優先順序並且首先被搜尋,所以PROVIDER_1實現被返回。
  2. 如果我們正在尋找SHA256withDSA簽名演算法,則首先搜尋PROVIDER_1。 沒有找到實現,因此搜尋了PROVIDER_2。 由於實現被發現,它被返回。
  3. 假設我們正在尋找一個SHA256withRSA簽名演算法。 由於沒有安裝的提供者實現它,丟擲了NoSuchAlgorithmException。

包含提供者引數的getInstance方法適用於想要指定哪個提供者需要演算法的開發人員。 例如,聯邦機構將希望使用已獲得聯邦認證的提供商實現方案。 假設來自PROVIDER_1的SHA256withDSA實施尚未獲得此認證,而PROVIDER_2的DSA實現已獲得此認證。

然後聯邦機構計劃將會有以下呼叫,並指定PROVIDER_2,因為它具有經過認證的實施:

Signature dsa = Signature.getInstance("SHA256withDSA", "PROVIDER_2");

在這種情況下,如果PROVIDER_2未安裝,即使另一個已安裝的提供程式實現了所請求的演算法,也會引發NoSuchProviderException。

程式還可以選擇獲取所有已安裝提供程式的列表(使用Security類中的getProviders方法),並從列表中選擇一個。

注:通用應用程式不應該請求來自特定提供商的加密服務。 否則,應用程式被繫結到在其他Java實現上可能不可用的特定提供程式。 他們也可能無法利用可用的優化提供程式(例如,通過PKCS11的硬體加速器或Microsoft的MSCAPI等本機作業系統實現),這些提供程式的優先順序高於特定的請求提供程式。

安裝Providers

為了使用Provider,必須首先安裝加密提供程式,然後靜態或動態註冊。 Sun發行版提供了多種Sun提供程式(SUN,SunJCE,SunJSSE,SunRsaSign等),已經安裝並註冊。 以下各節介紹如何安裝和註冊其他提供程式。

安裝Provider類

有兩種可能的方法來安裝Provider類:

  1. 在普通的Java classpath中 在classpath的任何位置放置一個包含Provider類的zip或JAR檔案。 一些演算法型別(Cipher)要求提供者是一個簽名的Jar檔案。
  2. 作為已安裝/捆綁的擴充套件 如果將提供程式置於標準擴充套件目錄中,則該提供程式將被視為已安裝的擴充套件程式。 在JDK中,這將位於:

    1.Solaris, Linux, or Mac OS X: JAVA_HOME/lib/ext 2.Windows:JAVA_HOME\lib\ext

這裡JAVA_HOME是指安裝執行時軟體的目錄,它是Java執行時環境(JRE)的頂層目錄或Java JDK軟體中的jre目錄。 例如,如果在Solaris上將JDK 6安裝在名為/home/user1/JDK1.6.0的目錄中或在Microsoft Windows上的名為C:\ Java \ JDK1.6.0的目錄中,則需要將JAR檔案安裝到 以下目錄:

  • Solaris, Linux, or Mac OS X: /home/user1/JDK1.6.0/jre/lib/ext
  • Windows: C:\JDK1.6.0\jre\lib\ext

同樣,如果在Solaris上將JRE 6安裝在名為/home/user1/jre1.6.0的目錄中或在Microsoft Windows上的名為C:\ jre1.6.0的目錄中,則需要將JAR檔案安裝在以下目錄中:

  • Solaris, Linux, or Mac OS X: /home/user1/jre1.6.0/lib/ext
  • Windows: C:\jre1.6.0\lib\ext

有關如何部署擴充套件的更多資訊,請參閱如何擴充套件部署

註冊Provider

下一步是將Provider新增到註冊提供者列表中。 通過在執行Java應用程式之前編輯安全屬性配置檔案,或通過在執行時呼叫方法來動態地註冊提供程式,可以靜態註冊提供程式。 為了防止將惡意提供程式的安裝新增到執行時環境中,試圖動態註冊提供程式的應用程式必須擁有適當的執行時許可權。

靜態註冊

配置檔案位於以下位置:

  • Solaris, Linux, or Mac OS X: JAVA_HOME/lib/security/java.security
  • Windows: JAVA_HOME\lib\security\java.security

對於每個註冊的提供者,這個檔案應該有以下形式的表單:

security.provider.n=masterClassName

這聲明瞭一個提供者,並且指定了它的首選順序n。 首選順序是提供者搜尋請求的演算法的順序(當沒有請求特定的提供者時)。 順序以1為基礎:1是最優先的,然後是2,依此類推。

masterClassName必須指定提供者的主類的完全限定名稱。 提供者的文件將指定它的主類。 這個類總是Provider類的一個子類。 子類建構函式設定Java加密API所需的各種屬性的值,以查詢提供者實現的演算法或其他工具。

JDK標配自動安裝和配置的提供程式,如“SUN”和“SunJCE”。 “SUN”提供者的主類是sun.security.provider包中的SUN類,相應的java.security檔案條目如下:

security.provider.5=sun.security.provider.Sun

要利用另一個JCA提供者,請新增一條引用備用提供者的行,指定首選順序(如果需要,對其他提供者的訂單進行相應的調整)。

假設CompanyX的提供者的主類是com.companyx.provider.ProviderX,並且你想把這個提供者配置成第八個最優先的。 為此,您可以將以下行新增到java.security檔案中:

security.provider.8=com.companyx.provider.ProviderX

動態註冊

要動態註冊提供者,應用程式呼叫Security類中的addProvider或insertProviderAt方法。 這種型別的註冊在VM例項中不是永久性的,只能通過具有適當許可權的“可信任”程式完成。 參見安全性

設定Provider許可權

每當使用加密提供者(即提供Cipher,KeyAgreement,KeyGenerator,Mac或SecretKeyFactory的實現的提供者),並且提供者不是已安裝的擴充套件時可能需要授予使用JCA的applet或應用程式時的許可權 安裝了一個安全管理器。 每當一個applet執行的時候,通常都會安裝一個安全管理器,並且可以通過應用程式本身的程式碼或通過命令列引數為應用程式安裝安全管理器。 由於預設系統策略配置檔案授予安裝的擴充套件的所有許可權(即安裝在擴充套件目錄中),因此不需要授予安裝的擴充套件的許可權。

您將要使用的每個提供商的供應商的文件應該包括所需的許可權以及如何授予這些許可權的資訊。 例如,如果提供程式不是已安裝的擴充套件程式並安裝了安全管理器,則可能需要以下許可權:

  • java.lang.RuntimePermission“getProtectionDomain”獲取類保護域。 提供者可能需要在進行自我完整性檢查的過程中獲得自己的保護域
  • java.security.SecurityPermission“putProviderProperty.{name}”設定提供程式屬性,其中{name}由實際提供程式名稱替換。

例如,下面給出了一個樣例語句,該語句授予名稱為“MyJCE”且程式碼位於myjce_provider.jar中的提供程式的許可權。 這樣的陳述可以出現在政策檔案中。 在這個例子中,假設myjce_provider.jar檔案位於/ localWork目錄中。

    grant codeBase "file:/localWork/myjce_provider.jar" {
        permission java.lang.RuntimePermission "getProtectionDomain";
        permission java.security.SecurityPermission
            "putProviderProperty.MyJCE";
     };

Provider類方法

每個Provider類例項都有一個(當前區分大小寫的)名稱,一個版本號以及提供者及其服務的字串描述。 您可以通過呼叫以下方法來查詢提供程式例項以獲取此資訊:

public String getName()
public double getVersion()
public String getInfo()

Security類

Security類管理已安裝的提供程式和安全性屬性。 它只包含靜態方法,永遠不會例項化。 用於新增或刪除提供者以及設定安全屬性的方法只能由受信任的程式執行。 目前,“可信程式”也是

  • 一個不在安全管理器下執行的本地應用程式,或者
  • 一個小程式或具有執行指定方法許可權的應用程式(見下文)。

正在執行的程式碼總是被認為來自特定的“程式碼源”。 程式碼源不僅包括原始碼的位置(URL),還包括對可能用於簽名程式碼的私鑰對應的任何公鑰的引用。 程式碼源中的公鑰由使用者金鑰庫中的(符號)別名引用。

在策略配置檔案中,程式碼源由兩個元件表示:程式碼庫(URL)和別名(以signedBy開頭),其中別名名稱標識包含必須用於驗證的公鑰的金鑰庫條目 程式碼的簽名。

以下是一個示例策略配置檔案:

grant codeBase "file:/home/sysadmin/", signedBy "sysadmin" {
    permission java.security.SecurityPermission "insertProvider.*";
    permission java.security.SecurityPermission "removeProvider.*";
    permission java.security.SecurityPermission "putProviderProperty.*";
};

此配置檔案指定從本地檔案系統上/ home / sysadmin /目錄下的已簽名JAR檔案載入的程式碼可以新增或刪除提供程式或設定提供程式屬性。 (請注意,可以使用使用者金鑰庫中的別名sysadmin引用的公鑰來驗證JAR檔案的簽名。)

程式碼源(或兩者)的元件可能會省略。 下面是一個程式碼庫被省略的配置檔案的例子:

grant signedBy "sysadmin" {
    permission java.security.SecurityPermission "insertProvider.*";
    permission java.security.SecurityPermission "removeProvider.*";
};

如果此策略有效,那麼由sysadmin簽名的JAR檔案中的程式碼可以新增/刪除提供程式,而不管JAR檔案的來源在哪裡。

這是一個沒有簽名者的例子:

grant codeBase "file:/home/sysadmin/" {
    permission java.security.SecurityPermission "insertProvider.*";
    permission java.security.SecurityPermission "removeProvider.*";
};

在這種情況下,來自本地檔案系統/ home / sysadmin /目錄中任何地方的程式碼都可以新增/刪除提供程式。 程式碼不需要簽名。

不包括codeBase和signedBy的例子是:

grant { permission java.security.SecurityPermission "insertProvider.*"; permission java.security.SecurityPermission "removeProvider.*"; };

在這裡,在缺少兩個程式碼源元件的情況下,任何程式碼(無論它來自何處,是否簽名,誰簽名)都可以新增/刪除提供程式。 顯然,這是絕對不推薦,因為這可能會開啟一個安全漏洞。 不受信任的程式碼可能會安裝提供程式,從而影響後來取決於正常執行的實現的程式碼。 (例如,流氓Cipher物件可能會捕獲並存儲它收到的敏感資訊。)

管理Providers

下表總結了Security類中的方法,您可以使用它來查詢安裝了哪些提供程式,以及在執行時安裝或刪除提供程式。

查詢Provider

方法 描述
static Provider[] getProviders() 返回一個包含所有已安裝提供程式的陣列(技術上,每個包提供程式的提供程式子類)。 陣列中提供者的順序是他們的偏好順序。
static Provider getProvider(String providerName) 返回提供程式名providerName。 如果找不到Provider,它將返回null。

增加Provider

方法 描述
static int addProvider(Provider provider) 將提供程式新增到已安裝提供程式列表的末尾。 它將返回提供者新增的偏好位置,如果提供者因為已經安裝而未被新增,則返回-1。
static int insertProviderAt (Provider provider, int position) 在指定的位置新增一個新的提供程式。 如果給定的供應商安裝在要求的位置,則以前在該位置的供應商和位置大於位置的所有供應商向上移動一個位置(靠近清單的末尾)。 此方法返回提供程式新增的優先順序位置,如果提供程式因為已經安裝而未新增,則返回-1。

刪除Provider

方法 描述
static void removeProvider(String name) 刪除具有指定名稱的提供程式。 如果沒有安裝提供程式,它將靜默地返回。 當指定的提供者被移除時,位於比指定提供者所在位置更大的位置的所有提供者被向下移動一個位置(朝向已安裝提供者列表的頭部)。

注意:如果要更改提供者的偏好位置,則必須先將其刪除,然後將其重新插入到新的偏好位置。

安全屬性

Security類維護一個系統範圍的安全屬性的列表。 這些屬性與系統屬性類似,但與安全性相關。 這些屬性可以靜態或動態設定。 我們已經看到了一個靜態安全屬性的例子(即,通過“security.provider.i”安全屬性靜態註冊一個提供者)。 如果要動態設定屬性,可信程式可以使用以下方法:

static String getProperty(String key) static void setProperty(String key, String datum)

注意:安全提供程式的列表是在VM啟動過程中建立的,因此必須使用上述方法來更改提供程式列表。

提醒一下,配置檔案位於以下位置:

  • Solaris, Linux, or Mac OS X: JAVA_HOME/lib/security/java.security
  • Windows: JAVA_HOME\lib\security\java.security

SecureRandom類

SecureRandom類是提供隨機數生成器(RNG)功能的引擎類。 它不同於java.lang.Random類,因為它產生密碼強的隨機數。 如果生成器中的隨機性不足,則會使保護機制變得更加容易。 在密碼學中使用隨機數字,例如生成加密金鑰,演算法引數等等。

所有Java SE實現都必須指出它們在java.security.Security類的securerandom.strongAlgorithms屬性中提供的最強(最隨機)的SecureRandom實現。 當需要特別強的隨機值時,可以使用此實現。偽隨機數

建立SecureRandom物件

有幾種方法可以獲得SecureRandom的例項:

  • 所有Java SE實現都使用無引數建構函式提供預設的SecureRandom:new SecureRandom()。
  • 要獲得SecureRandom的特定實現,請使用getInstance()靜態工廠方法之一。

設定或者重設種子

SecureRandom實現嘗試完全隨機化發生器本身的內部狀態,除非呼叫者通過呼叫其中一個setSeed方法來呼叫getInstance方法:

synchronized public void setSeed(byte[] seed)
public void setSeed(long seed)

一旦SecureRandom物件被設定了種子,它將產生與原始種子一樣隨機的位元。

在任何時候,SecureRandom物件都可以使用setSeed方法之一重新設定種子。 此時會對原來的種子進行補充,而不是取代現有的種子; 因此,保證重複的呼叫永遠不會降低隨機性。

使用SecureRandom物件

要獲得隨機位元組,呼叫者只需傳遞任意長度的陣列,然後用隨機位元組填充:

synchronized public void nextBytes(byte[] bytes)

生成種子位元組

如果需要,可以呼叫generateSeed方法來生成給定數量的種子位元組(例如,為其他隨機數生成器生成種子):

byte[] generateSeed(int numBytes)

MessageDigest類

MessageDigest類是一個引擎類,用於提供密碼安全的訊息摘要(如SHA-256或SHA-512)的功能。 加密安全的訊息摘要採用任意大小的輸入(一個位元組陣列),並生成一個固定大小的輸出,稱為摘要或雜湊。摘要

例如,SHA-256演算法產生一個32位元組的摘要,而SHA-512的是64位元組。

摘要有兩個屬性:

  • 找到兩個雜湊值相同的訊息在計算上應該是不可行的。
  • 摘要不應該透露任何關於用於生成它的輸入。

訊息摘要用於生成唯一且可靠的資料識別符號。 它們有時被稱為“校驗和”或資料的“數字指紋”。 只改變訊息的一位應該產生不同的摘要值。

訊息摘要有許多用途,可以確定資料何時被修改,有意或無意。 最近,已經有相當大的努力來確定流行演算法是否存在任何缺陷,結果不盡相同。 在選擇一個摘要演算法時,應該總是查閱最近的參考文獻,以確定其當前任務的狀態和適當性。

建立MessageDigest物件

計算摘要的第一步是建立一個訊息摘要例項。 MessageDigest物件是通過使用MessageDigest類中的getInstance()靜態工廠方法之一獲得的。 工廠方法返回一個初始化的訊息摘要物件。 因此不需要進一步的初始化。

更新Message Digest物件

計算某些資料的摘要的下一步是將資料提供給初始化的訊息摘要物件。 它可以一次或一塊地提供。 可以通過呼叫其中一種更新方法將訊息提供給訊息摘要:

void update(byte input)
void update(byte[] input)
void update(byte[] input, int offset, int len)

計算摘要

在通過呼叫更新資料塊之後,使用對其中一個摘要方法的呼叫來計算摘要:

byte[] digest()
byte[] digest(byte[] input)
int digest(byte[] buf, int offset, int len)

第一種方法返回計算的摘要。 第二種方法在呼叫digest()之前用輸入位元組陣列進行最終更新(輸入),digest()返回摘要位元組陣列。 最後一個方法將計算的摘要儲存在提供的緩衝區buf中,從偏移量開始。 len是分配給摘要的buf中的位元組數,該方法返回實際儲存在buf中的位元組數。 如果緩衝區中沒有足夠的空間,該方法將丟擲異常。

請參閱程式碼示例部分中的計算MessageDigest示例以獲取更多詳細資訊。

Signature類

Signature類是一個引擎類,旨在提供加密數字簽名演算法(如DSA或RSAwithMD5)的功能。 密碼安全簽名演算法採用任意大小的輸入和私鑰,並生成一個相對較短(通常是固定大小)的位元組串,稱為簽名,具有以下屬性:

  • 只有私鑰/公鑰對的所有者才能建立簽名。 任何擁有公鑰的人都可以在計算上不可能恢復私鑰
  • 鑑於與用於生成簽名的私鑰相對應的公鑰,應該有可能驗證輸入的真實性和完整性。
  • 簽名和公鑰沒有透露有關私鑰的任何資訊。

它也可以用來驗證所謂的簽名是否實際上是與其相關的資料的真實簽名。

簽名

一個簽名物件被初始化為用一個私鑰進行簽名,並被賦予待簽名的資料。 結果簽名位元組通常與簽名資料保持一致。 當需要驗證時,另一個簽名物件被建立和初始化以進行驗證並給出相應的公鑰。 資料和簽名位元組被饋送到簽名物件,並且如果資料和簽名匹配,則簽名物件報告成功。

儘管簽名看起來與訊息摘要相似,但它們在提供的保護型別上卻有著非常不同的目的。 事實上,諸如“SHA256withRSA”的演算法使用訊息摘要“SHA256”將大資料集初始“壓縮”為更易於管理的形式,然後用“RSA”演算法對得到的32位元組訊息摘要進行簽名。

請參閱示例部分以獲取簽名和驗證資料的示例。

Signature物件狀態

簽名物件是模態物件。 這意味著一個Signature物件總是處於一個給定的狀態,它只能執行一種操作。 狀態被表示為在其各自的類中定義的最終整數常量。

簽名物件可能具有的三種狀態是:

  • UNINITIALIZED
  • SIGN
  • VERIFY

第一次建立時,Signature物件處於UNINITIALIZED狀態。 Signature類定義了兩個初始化方法initSign和initVerify,它們分別將狀態更改為SIGN和VERIFY。

建立Signature物件

簽名或驗證簽名的第一步是建立一個簽名例項。 簽名物件是通過使用Signature getInstance()靜態工廠方法之一獲得的。

初始化Signature物件

Signature物件在使用之前必須被初始化。 初始化方法取決於物件是要用於簽名還是驗證。

如果要用於簽名,則必須首先使用要生成簽名的實體的私鑰來初始化該物件。 這個初始化通過呼叫方法完成:

final void initSign(PrivateKey privateKey)

此方法將Signature物件置於SIGN狀態。

如果相反,Signature物件將用於驗證,則必須首先使用將要驗證簽名的實體的公鑰來初始化。 這個初始化是通過呼叫以下任一方法來完成的:

    final void initVerify(PublicKey publicKey)

    final void initVerify(Certificate certificate)

此方法將Signature物件置於VERIFY狀態。

簽名

如果簽名物件已經被初始化為簽名(如果它處於SIGN狀態),則待簽名的資料可以被提供給該物件。 這是通過對其中一個更新方法進行一個或多個呼叫完成的:

final void update(byte b)
final void update(byte[] data)
final void update(byte[] data, int off, int len)

應該呼叫update方法,直到所有要簽名的資料都被提供給簽名物件。

要生成簽名,只需呼叫其中一個簽名方法:

final byte[] sign()
final int sign(byte[] outbuf, int offset, int len)

第一個方法以位元組陣列的形式返回簽名結果。 第二個將簽名結果儲存在提供的緩衝區outbuf中,從offset開始。 len是分配給簽名的outbuf中的位元組數。 該方法返回實際儲存的位元組數。

簽名編碼是特定於演算法的。 有關在Java加密體系結構中使用ASN.1編碼的更多資訊,請參閱標準名稱文件。

對sign方法的呼叫會將簽名物件重置為之前通過呼叫initSign進行初始化以進行簽名的狀態。 也就是說,如果需要,該物件將被重置並可用於生成具有相同私鑰的另一個簽名,通過新的呼叫來更新和簽名。或者,可以對指定不同私鑰的initSign或initVerify(初始化Signature物件以驗證簽名)進行新的呼叫。

驗證

如果簽名物件已被初始化以進行驗證(如果它處於VERIFY狀態),則它可以驗證所謂的簽名實際上是與其相關聯的資料的真實簽名。 為了開始這個過程,要被驗證的資料(而不是簽名本身)被提供給物件。 通過呼叫其中一種更新方法將資料傳遞給物件:

final void update(byte b)
final void update(byte[] data)
final void update(byte[] data, int off, int len)

應該呼叫更新方法,直到所有要驗證的資料都被提供給Signature物件。 現在可以通過呼叫其中一種驗證方法來驗證簽名:

final boolean verify(byte[] signature)

final boolean verify(byte[] signature, int offset, int length)

引數必須是包含簽名的位元組陣列。 這個位元組陣列將儲存以前呼叫返回的簽名位元組。

verify方法返回一個布林值,指示編碼簽名是否是提供給更新方法的資料的真實簽名。

對verify方法的呼叫通過呼叫initVerify將初始化後的簽名物件重置為其狀態。 也就是說,該物件被重置並可用於驗證在initVerify呼叫中指定了公鑰的身份的另一個簽名。

或者,可以對initVerify進行一次新的呼叫,指定一個不同的公鑰(初始化Signature物件以驗證來自不同實體的簽名)或initSign(初始化Signature物件以生成簽名)。

Cipher類

Cipher類提供用於加密和解密的加密密碼的功能。 加密是取資料(稱為明文)和金鑰的過程,並且產生對不知道金鑰的第三方毫無意義的資料(密文)。 解密是相反的過程:取密文和金鑰併產生明文。加密

對稱與非對稱密碼學

有兩種主要型別的加密:對稱(也稱為金鑰)和非對稱(或公鑰密碼)。 在對稱密碼學中,同一個金鑰既能加密也能解密資料。 保持金鑰私密對保持資料保密至關重要。 另一方面,非對稱加密使用公鑰/私鑰對來加密資料。 用一個金鑰加密的資料與另一個金鑰解密。 使用者首先生成公鑰/私鑰對,然後將公鑰釋出在任何人都可以訪問的可信資料庫中。 希望與該使用者安全通訊的使用者使用檢索到的公鑰來加密資料。 只有私鑰的持有者才能解密。 保密私鑰對此方案至關重要。

不對稱演算法(如RSA)通常比對稱演算法慢得多。 這些演算法不能有效地保護大量的資料。 實際上,不對稱演算法被用來交換較小的祕密金鑰,用來初始化對稱演算法。

流與分組密碼

密碼有兩種主要型別:塊和流。 分組密碼一次處理整個塊,通常是多個位元組的長度。 如果沒有足夠的資料來建立完整的輸入塊,則必須填充資料:也就是說,在加密之前,必須新增虛擬位元組以使密碼塊大小成倍數。 這些位元組在解密階段被剝離。 填充既可以由應用程式完成,也可以通過初始化密碼來使用填充型別,例如“PKCS5PADDING”。 相比之下,流密碼一次只能處理一個小單元(通常是一個位元組,甚至一點點)的傳入資料。 這允許密碼處理任意數量的資料而不用填充。

操作模式

當使用簡單的分組密碼進行加密時,兩個相同的明文塊將總是產生相同的密文塊。如果他們注意到重複文字塊,那麼試圖破解密文的密碼分析者將會有更容易的工作。為了增加文字的複雜性,反饋模式使用前面的輸出塊在應用加密演算法之前改變輸入塊。第一個塊需要一個初始值,這個值被稱為初始化向量(IV)。由於IV在加密之前只是簡單地改變資料,所以IV應該是隨機的,但不一定需要保密。有多種模式,例如CBC(密碼塊連結),CFB(密碼反饋模式)和OFB(輸出反饋模式)。 ECB(電子碼本模式)是一種不受塊位置或其他密文塊影響的模式。因為如果ECB密文使用相同的明文/金鑰,ECB密文是相同的,這種模式通常不適合加密應用,不應該使用。

一些演算法如AES和RSA允許不同長度的金鑰,但其他演算法則是固定的,如3DES。 使用更長的金鑰進行加密通常意味著對訊息恢復的更強的抵抗力。 像往常一樣,安全和時間之間有一個折衷,所以選擇適當的金鑰長度。

大多數演算法使用二進位制金鑰。 即使以十六進位制表示,大多數人類也無法記憶長序列的二進位制數字。 字元密碼更容易記憶。 由於字元密碼通常是從少量字元中選擇的(例如[a-zA-Z0-9]),因此已經定義了諸如“基於密碼的加密”(PBE)等協議,這些協議使用字元密碼並生成強二進位制金鑰。 為了使攻擊者從口令到金鑰的任務非常耗時(通過所謂的“字典式攻擊”,其中常用字典字 - 值對映是預先計算的),大多數PBE實現將以隨機數混合, 被稱為鹽,以增加關鍵的隨機性。

更新的密碼模式,例如帶有關聯資料的認證加密(AEAD)(例如,伽羅瓦/計數器模式(GCM)),可以對資料進行加密並同時驗證結果資訊。 在計算生成的AEAD標記(Mac)期間可以使用附加關聯資料(AAD),但是這個AAD資料不會以密文的形式輸出。 (例如,某些資料可能不需要保密,但應該計算標籤計算以檢測修改。)Cipher.updateAAD()方法可用於在標籤計算中包含AAD。

使用GCM模式的AES密碼

使用GCM的AES密碼是一種AEAD密碼,與非AEAD密碼具有不同的使用模式。 除了常規資料外,還需要AAD,這是可選的加密/解密,但AAD必須在資料加密/解密之前提供。 另外,為了安全地使用GCM,呼叫者不應該重複使用金鑰和IV組合來進行加密。 這意味著每次加密操作時,密碼物件應該用不同的一組引數顯式地重新初始化。

SecretKey myKey = ... ;
byte[] myAAD = ... ;
byte[] plainText = ... ;
int myTLen = ... ;
byte[] myIv = ... ;

GCMParameterSpec myParams = new GCMParameterSpec(myTLen, myIv); 
Cipher c = Cipher.getInstance("AES/GCM/NoPadding"); 
c.init(Cipher.ENCRYPT_MODE, myKey, myParams); 
// AAD is optional, if present, it must be supplied before any update/doFinal calls. c.updateAAD(myAAD); // if AAD is non-null 
byte[] cipherText = new byte[c.getOutputSize(plainText.length)]; 
c.doFinal(plainText, 0, plainText.length, cipherText); // conclusion of encryption operation 
// To decrypt, same AAD and GCM parameters must be supplied c.init(Cipher.DECRYPT_MODE, myKey, myParams); 
c.updateAAD(myAAD); 
byte[] recoveredText = c.doFinal(cipherText); 
// MUST CHANGE IV VALUE if the same key were to be used again for encryption byte[] newIv = ...; 
myParams = new GCMParameterSpec(myTLen, newIv);

建立Cipher物件

密碼物件是通過使用密碼getInstance()靜態工廠方法之一獲得的。 在這裡,演算法名稱與其他引擎類稍有不同,因為它不僅指定演算法名稱,而且指定“變換”。 轉換是一個字串,它描述了在給定輸入上執行的操作(或操作集)以產生一些輸出。 變換總是包括密碼演算法的名稱(例如,AES),並且可以跟隨模式和填充方案。

轉型的形式是:

  • “algorithm/mode/padding” or
  • “algorithm”

例如,以下是有效的轉換:

    "AES/CBC/PKCS5Padding"

    "AES"

如果只指定了一個轉換名稱,系統將確定在環境中是否有所需的轉換實現,如果有多個轉換名稱,則返回一個首選項。

如果同時指定了轉換名稱和包提供者,系統將確定所請求的包中是否存在所請求轉換的實現,如果沒有,則丟擲異常。

建議使用完全指定演算法,模式和填充的轉換。 如果不這樣做,提供者將使用預設值。 例如,SunJCE和SunPKCS11提供程式將ECB用作預設模式,將PKCS5Padding用作許多對稱密碼的預設填充。

這意味著在SunJCE提供商的情況下:

Cipher c1 = Cipher.getInstance("AES/ECB/PKCS5Padding");
Cipher c1 = Cipher.getInstance("AES");

是等同的語句。

**注意:**ECB模式是最簡單的塊密碼模式,並且是JDK / JRE中的預設模式。 ECB適用於單個數據塊,但絕對不應該用於多個數據塊。

使用CFB和OFB等模式,分組密碼可以以小於密碼實際塊大小的單位加密資料。 在請求這種模式時,可以通過在“AES / CFB8 / NoPadding”和“AES / OFB32 / PKCS5Padding”轉換中將模式名稱附加到模式名稱來一次指定要處理的位數。 如果沒有指定這樣的號碼,則使用提供者特定的預設值。 (例如,SunJCE提供程式使用預設值為128位的AES)。因此,可以使用8位模式(如CFB8或OFB8)將塊密碼轉換為面向位元組的流密碼。

本文件的附錄A包含一個標準名稱列表,可用於指定轉換的演算法名稱,模式和填充方案元件。

工廠方法返回的物件是未初始化的,必須在它們變為可用之前進行初始化。

初始化Cipher物件

通過getInstance獲得的Cipher物件必須初始化為四種模式之一,在Cipher類中定義為最終整數常量。 這些模式可以通過它們的符號名稱來引用,這些符號名稱將在下面顯示,同時還會描述每種模式的用途:

ENCRYPT_MODE

資料加密.

DECRYPT_MODE

資料解密.

WRAP_MODE

將java.security.Key包裝為位元組,以便可以安全地傳輸金鑰.

UNWRAP_MODE

將之前包裝的金鑰解包到java.security.Key物件中.

每個密碼初始化方法都採用操作模式引數(opmode),並初始化該模式的密碼物件。 其他引數包括包含金鑰(certificate)的金鑰(key)或證書,演算法引數(params)以及隨機源(random)。

要初始化Cipher物件,請呼叫以下init方法之一:

    public void init(int opmode, Key key);

    public void init(int opmode, Certificate certificate);

    public void init(int opmode, Key key, SecureRandom random);

    public void init(int opmode, Certificate certificate,
                     SecureRandom random);

    public void init(int opmode, Key key,
                     AlgorithmParameterSpec params);

    public void init(int opmode, Key key,
                     AlgorithmParameterSpec params, SecureRandom random);

    public void init(int opmode, Key key,
                     AlgorithmParameters params);

    public void init(int opmode, Key key,
                     AlgorithmParameters params, SecureRandom random);

如果需要引數的密碼物件(例如,初始化向量)被初始化為加密,並且沒有引數提供給初始化方法,則底層密碼實現本身應該提供所需的引數,或者通過生成隨機引數或者通過使用 預設的,特定於提供者的引數集合。

但是,如果需要引數的Cipher物件被初始化為解密,並且沒有引數提供給init方法,則將引發InvalidKeyException或InvalidAlgorithmParameterException異常,具體取決於所使用的init方法。

有關更多詳細資訊,請參閱關於管理演算法引數一節.

必須使用與加密相同的引數進行解密。

請注意,當一個Cipher物件被初始化時,它將失去所有先前獲得的狀態。 換句話說,初始化一個Cipher就相當於建立一個新的Cipher例項,並初始化它。 例如,如果密碼首先被初始化為用給定金鑰進行解密,然後初始化用於加密,則在解密模式下將失去獲得的任何狀態。

資料加解密

資料可以在一個步驟(單部分操作)或多個步驟(多部分操作)中加密或解密。 如果事先不知道資料將要執行多長時間,或者資料太長而無法一次儲存在記憶體中,則多部分操作非常有用。

要在一個步驟中加密或解密資料,請呼叫其中一個doFinal方法:

    public byte[] doFinal(byte[] input);

    public byte[] doFinal(byte[] input, int inputOffset, int inputLen);

    public int doFinal(byte[] input, int inputOffset,
                       int inputLen, byte[] output);

    public int doFinal(byte[] input, int inputOffset,
                       int inputLen, byte[] output, int outputOffset)

要以多個步驟加密或解密資料,請呼叫其中一種更新方法:

    public byte[] update(byte[] input);

    public byte[] update(byte[] input, int inputOffset, int inputLen);

    public int update(byte[] input, int inputOffset, int inputLen,
                      byte[] output);

    public int update(byte[] input, int inputOffset, int inputLen,
                      byte[] output, int outputOffset)

多部分操作必須由上述doFinal方法之一終止(如果最後一步仍有一些輸入資料),或者通過以下doFinal方法之一(如果沒有輸入資料留給最後一步):

    public byte[] doFinal();

    public int doFinal(byte[] output, int outputOffset);

如果填充(或非填充)已被請求作為指定轉換的一部分,則所有doFinal方法都將處理任何必要的填充(或非填充)。

呼叫doFinal會將Cipher物件重置為通過呼叫init初始化時的狀態。 也就是說,Cipher物件被重置並且可用於加密或解密(取決於在對init的呼叫中指定的操作模式)更多的資料。

封裝和解包金鑰

包裝鑰匙可以將鑰匙從一個地方安全地轉移到另一個地方。

wrap / unwrap API使得編寫程式碼更方便,因為它直接處理關鍵物件。 這些方法還可以安全地傳輸基於硬體的金鑰。

要包裝一個Key,首先要為WRAP_MODE初始化Cipher物件,然後呼叫以下內容:

public final byte[] wrap(Key key);

如果您要將打包的金鑰位元組(呼叫wrap的結果)提供給開啟它們的其他人,請務必傳送收件人需要的附加資訊,以便進行解包:

  • 金鑰演算法的名稱和
  • 包裝金鑰的型別(Cipher.SECRET_KEY,Cipher.PRIVATE_KEY或Cipher.PUBLIC_KEY之一)。

金鑰演算法名稱可以通過從Key介面呼叫getAlgorithm方法來確定:

    public String getAlgorithm();

要開啟先前呼叫的包裝返回的位元組,請首先初始化UNWRAP_MODE的Cipher物件,然後呼叫以下內容:

    public final Key unwrap(byte[] wrappedKey,
                            String wrappedKeyAlgorithm,
                            int wrappedKeyType));

這裡,wrappedKey是從前一個包裝呼