Javaプラットフォームでは、暗号化操作を実行するための一連のプログラミング・インタフェースを定義しています。これらのインタフェースは、総称してJava暗号化アーキテクチャ(JCA)およびJava暗号化拡張機能(JCE)と呼ばれています。Java暗号化アーキテクチャ(JCA)リファレンス・ガイドを参照してください。
暗号化インタフェースはプロバイダ・ベースです。具体的には、アプリケーションはアプリケーション・プログラミング・インタフェース(API)とやり取りをし、実際の暗号化操作は、一連のサービス・プロバイダ・インタフェース(SPI)に従った構成済みプロバイダ内で実行されます。このアーキテクチャでは、プロバイダのさまざまな実装をサポートしています。ソフトウェアで暗号化操作を行うプロバイダもあれば、スマートカード・デバイスやハードウェア暗号化アクセラレータなどのハードウェア・トークン上で暗号化操作を行うプロバイダもあります。
暗号化トークン・インタフェース標準であるPKCS#11は、RSA Securityが策定し、ハードウェア暗号化アクセラレータやスマートカードなどの暗号化トークンに対するネイティブ・プログラミング・インタフェースを定義しています。JCAおよびJCE APIを使用する既存のアプリケーションでは、PKCS#11プロバイダを使用してネイティブPKCS#11トークンにアクセスできます。アプリケーションへの変更は必要ありません。必要なのは、プロバイダを正しく構成することのみです。
アプリケーションは既存のAPIを使用してPKCS#11機能のほとんどを利用できますが、柔軟性や機能性をさらに必要とするアプリケーションもあります。たとえば、アプリケーションで、動的に抜き差しするスマートカードをより簡単に扱えるようにする必要がある場合があります。また、PKCS#11トークンで鍵とは関係ない一部の操作を認証する必要があるため、アプリケーションはキーストアを使用せずにトークンにログインできるようにする必要があります。JCAにより、アプリケーションが様々なプロバイダを扱う際の柔軟性が向上します。
このドキュメントでは、ネイティブPKCS#11トークンをJavaアプリケーションで使用できるように、Javaプラットフォームに構成する方法を説明します。また、JCAによってPKCS#11プロバイダを始めとする各種プロバイダがアプリケーションでどのように扱いやすくなるかについても説明します。
その他の多くのプロバイダとは異なり、SunPKCS11プロバイダ自体は暗号化アルゴリズムを実装していません。その代わりに、Java JCAおよびJCE APIとネイティブPKCS#11暗号化APIとの間のブリッジとして動作し、これらの間の呼び出しと規則を変換します。
つまり、標準JCAおよびJCE APIを呼び出すJavaアプリケーションなら、アプリケーションを変更しなくても、基盤となる次のようなPKCS#11実装で提供されるアルゴリズムを利用できるということです。
注意:
Java SEは、ネイティブPKCS#11実装へのアクセスを容易にするのみであり、それ自体にはネイティブPKCS#11実装は含まれていません。ただし、スマート・カードやハードウェア・アクセラレータなどの暗号化デバイスには、PKCS#11実装を含んだソフトウェアが付属していることが一般的で、製造元の指示に従ってインストールし、構成する必要があります。SunPKCS11プロバイダでは、PKCS#11 v2.0以降の実装がシステムにインストールされている必要があります。この実装は、共有オブジェクト・ライブラリ(SolarisおよびLinuxでの.so
)またはダイナミック・リンク・ライブラリ(Windowsでの.dll
)の形態である必要があります。使用する暗号化デバイスにこのようなPKCS#11実装が含まれているかどうかを調べる方法、実装を構成する方法、およびライブラリ・ファイルのファイル名については、ベンダーが提供するマニュアルを参照してください。
SunPKCS11プロバイダでは、基盤となるPKCS#11実装で提供されている場合は、多くのアルゴリズムがサポートされています。それらのアルゴリズムとその対応するPKCS#11メカニズムを、SunPKCS11プロバイダでサポートされているアルゴリズムで表に示します。
SunPKCS11プロバイダは、モジュールjdk.crypto.cryptokiにあります。このプロバイダを使用するには、まずそれを静的にまたはプログラムでインストールする必要があります。
このプロバイダを静的にインストールするには、Javaセキュリティのプロパティ・ファイル(java-home/conf/security/java.security
)にプロバイダを追加します。
たとえば次は、SunPKCS11プロバイダを構成ファイル/opt/bar/cfg/pkcs11.cfg
とともにインストールするjava.security
ファイルの一部です。
# configuration for security providers 1-12 ommitted security.provider.13=SunPKCS11 /opt/bar/cfg/pkcs11.cfg
このプロバイダを動的にインストールするには、適切な構成ファイル名を使用してプロバイダのインスタンスを作成し、インストールします。次に例を示します。
String configName = "/opt/bar/cfg/pkcs11.cfg"; Provider p = Security.getProvider("SunPKCS11"); p = p.configure(configName); Security.addProvider(p);
PKCS#11実装あたり複数のスロットを使用する場合や、複数のPKCS#11実装を使用する場合は、適切な構成ファイルでそれぞれのインストールを繰り返すだけです。これにより、それぞれのPKCS#11実装の各スロットに対してSunPKCS11プロバイダのインスタンスが1つ作成されることになります。
構成ファイルはテキスト・ファイルであり、これには次の形式でエントリが含まれています。
attribute=value
attributeとvalueの有効な値については、この項内の表で説明しています。
2つある必須属性は、name
およびlibrary
です。次に、構成ファイルの例を示します。
name = FooAccelerator library = /opt/foo/lib/libpkcs11.so
コメントは、#
(シャープ)記号で始まる行に記述します。
表5-1 PKCS#11プロバイダの構成ファイル内の属性
属性 | 値 | 説明 |
---|---|---|
library | PKCS#11実装のパス名 | PKCS#11実装のフル・パス名(拡張子を含む)。パス名の書式はプラットフォームに依存します。たとえば、SolarisおよびLinuxではPKCS#11実装のパス名が/opt/foo/lib/libpkcs11.so などになりますが、Windowsでのパス名はC:\foo\mypkcs11.dll などになります。 |
name | このプロバイダ・インスタンスの名前接尾辞 | この文字列は、接頭辞SunPKCS11- と連結して、このプロバイダ・インスタンスの名前(つまり、プロバイダ・インスタンスのProvider.getName() メソッドで返される文字列)を生成します。たとえばname 属性がFooAccelerator の場合、プロバイダ・インスタンスの名前はSunPKCS11-FooAccelerator になります。 |
description | このプロバイダ・インスタンスの説明 | この文字列は、プロバイダ・インスタンスのProvider.getInfo() メソッドで返されます。何も指定されていない場合、デフォルトの説明が返されます。 |
slot | スロットのID | このプロバイダ・インスタンスが関連付けされているスロットのID。たとえば、PKCS#11のID 1 のスロットでは、1 を使用します。指定できるのは、多くともslot かslotListIndex のどちらか1つです。どちらも指定しない場合のデフォルトは、値0 のslotListIndex です。 |
slotListIndex | スロットのインデックス | このプロバイダ・インスタンスが関連付けされているスロットのインデックス。これは、PKCS#11の関数C_GetSlotList で返されるすべてのスロットのリストに対するインデックスです。たとえば、0 はリストの先頭のスロットを表します。指定できるのは、多くともslot かslotListIndex のどちらか1つです。どちらも指定しない場合のデフォルトは、値0 のslotListIndex です。 |
enabledMechanisms | 有効にするPKCS#11メカニズムのリスト。空白文字で区切り、全体を中カッコ()で囲む | これは、SunPKCS11プロバイダとPKCS#11トークンの両方でサポートされている場合に、このプロバイダ・インスタンスで使用する必要があるPKCS#11メカニズムのリストです。その他のメカニズムはすべて無視されます。リスト内の各エントリは、PKCS#11メカニズムの名前になります。2つのPKCS#11メカニズムから成るリストの例を次に示します。 enabledMechanisms = { CKM_RSA_PKCS CKM_RSA_PKCS_KEY_PAIR_GEN }指定できるのは、多くとも enabledMechanisms かdisabledMechanisms のどちらか1つです。どちらも指定しない場合、有効なメカニズムは、SunPKCS11プロバイダ(SunPKCS11プロバイダでサポートされているアルゴリズムを参照)とPKCS#11トークンの両方でサポートされているメカニズムになります。 |
disabledMechanisms | 無効にするPKCS#11メカニズムのリスト。空白文字で区切り、全体を中カッコ()で囲む | このプロバイダ・インスタンスが無視するPKCS#11メカニズムのリスト。リストされたあらゆるメカニズムは、トークンとSunPKCS11プロバイダでサポートされていても、プロバイダによって無視されます。これらのサービスを無効にするために文字列SecureRandom およびKeyStore を指定できます。 指定できるのは、多くとも |
attributes | 下記参照 | attributes オプションは、PKCS#11鍵オブジェクトの作成時に設定される追加のPKCS#11属性を指定するために使用します。これにより、特定の属性を必要とするトークンを使用できるようになります。詳細は、次のセクションを参照してください。 |
属性の構成
attributesオプションは、PKCS#11鍵オブジェクトの作成時に設定される追加のPKCS#11属性を指定するために使用できます。デフォルトでSun PKCS#11プロバイダでは、オブジェクトの作成時に必須のPKCS#11属性を指定するだけです。たとえばRSA公開鍵では、鍵タイプおよびアルゴリズム(CKA_CLASSおよびCKA_KEY_TYPE)と、RSA公開鍵の鍵の値(CKA_MODULUSおよびCKA_PUBLIC_EXPONENT)を指定します。使用しているPKCS#11ライブラリでは、実装固有のデフォルト値をRSA公開鍵のその他の属性に割り当てます。たとえば、メッセージの暗号化と検証に鍵を使用できます(CKA_ENCRYPTおよびCKA_VERIFY = true)。
attributes
オプションは、PKCS#11実装で割り当てたデフォルト値を使用しない場合や、PKCS#11実装でデフォルト値をサポートしないために、明示的に値を指定する必要がある場合に使用します。使用しているPKCS#11実装でサポートしない属性や、該当する鍵タイプで無効な属性を指定すると、実行時に操作が失敗する原因となります。
オプションは0回または複数回指定できます。また、次に説明するように、構成ファイルで指定した順番でオプションが処理されます。attributes
オプションは次の書式です。
attributes(operation, keytype, keyalgorithm) = { name1 = value1 [...] }
operation
の有効な値は次のとおりです。
generate
。KeyPairGeneratorまたはKeyGeneratorによって生成された鍵用import
。KeyFactoryまたはSecretKeyFactoryによって作成された鍵用。暗号化操作の初期化メソッド(Signature.initSign()
など)に渡されるときにPKCS#11鍵オブジェクトに自動的に変換されるJava Software鍵にも適用される。*
。生成操作または作成操作のどちらかで作成された鍵用。 keytype
の有効な値は、CKO_PUBLIC_KEY
、CKO_PRIVATE_KEY
、およびCKO_SECRET_KEY
で、それぞれ公開鍵、非公開鍵、および秘密鍵に対応しています。また、任意の鍵タイプに一致する*
もあります。
keyalgorithm
の有効な値は、PKCS#11仕様に定義されたCKK_xxx
定数のいずれか1つ、または任意の鍵アルゴリズムに一致する*
です。Sun PKCS11プロバイダで現在サポートしているアルゴリズムは、CKK_RSA、CKK_DSA、CKK_DH、CKK_AES、CKK_DES、CKK_DES3、CKK_RC4、CKK_BLOWFISH、およびCKK_GENERICです。
属性の名前と値は、1つまたは複数の名前と値のペアのリストとして指定されます。name
はPKCS#11仕様のCKA_xxx
定数(CKA_SENSITIVE
など)でなければなりません。value
は、次のいずれかになります。
true
またはfalse
0x
で始まる16進数表記。null
。この属性はオブジェクトの作成時に指定してはならないことを示す。 attributes
オプションを複数回指定すると、エントリは、まとめられた属性で指定された順序で、あとの属性が前の属性をオーバーライドして処理されます。たとえば、次の構成ファイルを考えてみます。
attributes(*,CKO_PRIVATE_KEY,*) = { CKA_SIGN = true } attributes(*,CKO_PRIVATE_KEY,CKK_DH) = { CKA_SIGN = null } attributes(*,CKO_PRIVATE_KEY,CKK_RSA) = { CKA_DECRYPT = true }
1番目のエントリでは、すべての非公開鍵に対してCKA_SIGN = true
を指定しています。2番目のオプションでは、Diffie-Hellman非公開鍵についてnull
でオーバーライドするため、CKA_SIGN
属性はDiffie-Hellman非公開鍵に対してまったく指定されません。最後に、3番目のオプションでは、RSA非公開鍵に対してCKA_DECRYPT = true
を指定しています。つまりRSA非公開鍵には、CKA_SIGN = true
とCKA_DECRYPT = true
両方のセットがあることになります。
attributes
オプションには特殊な形式もあります。構成ファイルにattributes = compatibility
と書くことができます。これは、属性ステートメントのセット全体に対するショートカットです。これはプロバイダが既存のJavaアプリケーションとの互換性を最大限に保つことを目的としています。これにより、Javaアプリケーションでは、たとえばすべての鍵コンポーネントにアクセス可能で、秘密鍵を暗号化および復号化の両方に使用可能であることが期待されます。compatibility
属性の行は、他のattributes
行とともに使用できます。この場合は先に説明したような、同様のアグリゲーションおよびオーバーライドの規則が適用されます。
ネットワーク・セキュリティ・サービス(NSS)は一連のオープン・ソース・セキュリティ・ライブラリであり、その暗号化APIは、PKCS#11に基づいていますが、PKCS#11標準ではない特別な機能を含んでいます。SunPKCS11プロバイダには、NSS固有の機能(複数のNSS固有の構成ディレクティブなど)と相互に作用するためのコードが含まれています。これらについては次で説明します。
最適な結果を得るために、使用可能な最新バージョンのNSSを使用することをお薦めします。少なくともバージョン3.12になります。
次に説明されているnss
構成ディレクティブのいずれかが使用されている場合、SunPKCS11プロバイダはNSS固有のコードを使用します。この場合、通常の構成コマンドlibrary
、slot
、およびslotListIndex
は使用できません。
表5-2 NSSの属性と値
属性 | 値 | 説明 |
---|---|---|
nssLibraryDirectory | NSSおよびNSPRライブラリを含むディレクトリ | NSSおよびNSPRライブラリを含むディレクトリのフル・パス名。Java VMとして同じプロセス内で実行されている別のコンポーネントによってNSSがすでにロードおよび初期化されていないかぎり、この属性を指定する必要があります。 プラットフォームに応じて、このディレクトリを含めるために |
nssSecmodDirectory | NSS DBファイルを含むディレクトリ | NSS構成および鍵情報(secmod.db 、key3.db 、およびcert8.db )を含むディレクトリのフル・パス名。別のコンポーネントによってNSSがすでに初期化されていないか(上記参照)、または次に説明されているようにデータベース・ファイルなしでNSSが使用されないかぎり、この指示を指定する必要があります。 |
nssDbMode | readWrite 、readOnly 、noDb のいずれか |
この指示は、NSSデータベースへのアクセス方法を決定します。読書きモードではフル・アクセスが可能ですが、データベースには一度に1つのプロセスのみがアクセスする必要があります。読取り専用モードでは、このファイルの変更は許可されません。 noDbモードを使用すると、データベース・ファイルなしで純粋に暗号化プロバイダとしてNSSを使用できます。PKCS11 KeyStoreを使用して持続的な鍵を作成することはできません。NSSには、Oracleのバンドル済Javaベース暗号化プロバイダ(Elliptic Curve Cryptography (ECC)など)では現在使用できない高度に最適化された実装およびアルゴリズムが含まれているため、このモードは有用です。 |
nssModule | keystore 、crypto 、fips 、trustanchors のいずれか |
さまざまなライブラリやスロットを使用してNSSの機能が使用できるようになっています。この指示は、SunPKCS11のインスタンスがアクセスするモジュールを決定します。
FIPS-140準拠モードに対してNSSの
信頼できるアンカー・ライブラリを含むように |
例5-1 NSS用SunPKCS11構成ファイル
純粋な暗号化プロバイダとしてのNSS
name = NSScrypto nssLibraryDirectory = /opt/tests/nss/lib nssDbMode = noDb attributes = compatibility
FIPS 140準拠の暗号化トークンとしてのNSS
name = NSSfips nssLibraryDirectory = /opt/tests/nss/lib nssSecmodDirectory = /opt/tests/nss/fipsdb nssModule = fips
PKCS#11に問題が発生し、デバッグが必要になることがあります。ライブラリ、スロット、トークンおよびメカニズムのデバッグ情報を表示するには、<java-home>/conf/security/sunpkcs11-solaris.cfg
ファイルにshowInfo=true
を追加します。
その他のデバッグ情報については、次のいずれかのオプションを指定してJavaプロセスを起動または再起動してください。
SunPKCS11プロバイダの一般的なデバッグ情報:
-Djava.security.debug=sunpkcs11
PKCS#11キーストア固有のデバッグ情報の場合:
-Djava.security.debug=pkcs11keystore
トラブルシューティング・プロセスの一環として、PKCS#11プロバイダ、または特定のプロバイダに固有のメカニズムを一時的に無効にすると便利な場合があります。
PKCS#11プロバイダの無効化
PKCS#11プロバイダは、次のいずれかの方法で無効にできます。
単一のJavaプロセスに対してPKCS#11を無効にします。次のJavaコマンド行フラグを使用してJavaプロセスを起動または再起動します。
-Dsun.security.pkcs11.enable-solaris=false
注意:
このステップは、デフォルトのSolaris PKCS11プロバイダ・ファイル(sun.security.pkcs11.SunPKCS11
、/conf/security/sunpkcs11-solaris.cfg
および/conf/security/sunpkcs11-solaris.cfg
)に基づくSunPKCS11プロバイダにのみ適用されます。特定のJavaインストールで実行されるすべてのJavaプロセスについてPKCS#11を無効にします。これは、APIを使用して動的に実行するか(この項には記載されていません)、次に示すように<java_home>/conf/security/java.security
ファイルを編集してSunPKCS11セキュリティ・プロバイダをコメント・アウトすることにより静的に実行できます(必要な場合はプロバイダの順序の番号を変更することを忘れないでください)。
#
# List of providers and their preference orders (see above):
#
security.provider.1=SUN
security.provider.2=SunRsaSign
security.provider.3=SunEC
security.provider.4=SunJSSE
security.provider.5=SunJCE
security.provider.6=SunJGSS
security.provider.7=SunSASL
security.provider.8=XMLDSig
security.provider.9=SunPCSC
security.provider.10=JdkLDAP
security.provider.11=JdkSASL
security.provider.12=SunMSCAPI
#security.provider.13=SunPKCS11
このJavaのインストールで実行されているJavaプロセスを起動または再起動します。
特定のメカニズムの無効化
PKCS#11のメカニズムのいずれかで問題が発生した場合、PKCS#11プロバイダ全体ではなく、その特定のメカニズムのみを無効にすることにより、問題を解決できます(それまでにPKCS#11プロバイダを無効にしていた場合は、再度有効にすることを忘れないでください)。
たとえば、SecureRandomメカニズムのみを無効にするには、<java-home>/conf/security/sunpkcs11-solaris.cfg
ファイル内の無効なメカニズムのリストにSecureRandomを追加します。
name = Solaris
description = SunPKCS11 accessing Solaris Cryptographic Framework
library = /usr/lib/$ISA/libpkcs11.so
handleStartupErrors = ignoreAll
# Use the X9.63 encoding for EC points (do not wrap in an ASN.1 OctetString).
useEcX963Encoding = true
attributes = compatibility
disabledMechanisms = {
CKM_DSA_KEY_PAIR_GEN
SecureRandom
}
Javaアプリケーションは、既存のJCAおよびJCE APIを使用して、SunPKCS11プロバイダ経由でPKCS#11トークンにアクセスできます。
個人識別番号を使用してキーストアにログインし、PKCS#11の操作を実行できます。
秘密鍵へのアクセスなど一部のPKCS#11操作では、その操作の実行前に、PIN (個人識別番号)を使用してログインする必要があります。ログインが必要となる操作でもっとも多いのはトークン上の鍵を扱う操作です。Javaアプリケーションではそのような操作で、最初にキーストアをロードするのが一般的です。java.security.KeyStore
クラス経由でキーストアとしてPKCS#11トークンにアクセスするときは、load
メソッドに対してパスワード入力パラメータでPINを指定できます。PINは、その後、トークンにログインするために、SunPKCS11プロバイダが使用します。次に例を示します。
char[] pin = ...; KeyStore ks = KeyStore.getInstance("PKCS11"); ks.load(null, pin);
この例は、静的なキーストアとしてPKCS#11トークンを扱うアプリケーションに適しています。抜き差しされるスマートカードなど、アプリケーションでPKCS#11トークンをより動的に利用する必要がある場合は、新しいKeyStore.Builder
クラスを使用します。コールバック・ハンドラでPKCS#11キーストアのビルダーを初期化する方法を次の例に示します。
KeyStore.CallbackHandlerProtection chp = new KeyStore.CallbackHandlerProtection(new MyGuiCallbackHandler()); KeyStore.Builder builder = KeyStore.Builder.newInstance("PKCS11", null, chp);
SunPKCS11プロバイダの場合、コールバック・ハンドラがPasswordCallback
に対応できる必要があります。PasswordCallbackは、ユーザーにPINを要求するために使用されます。アプリケーションがキーストアにアクセスしなければならない場合は、ビルダーを次のように使用します。
KeyStore ks = builder.getKeyStore(); Key key = ks.getKey(alias, null);
ビルダーは、先に構成されたコールバック・ハンドラで使用するパスワードを必要に応じてユーザーに求めます。ビルダーがパスワードを要求するのは、初回のアクセス時のみです。アプリケーションのユーザーが同じスマート・カードを使用し続ける場合は、もう一度パスワードを要求されることはありません。ユーザーがスマートカードを抜いて、別のスマートカードを差した場合は、新しいカードに対するパスワードを要求されます。
PKCS#11トークンによっては、鍵とは関係ない操作でもトークン・ログインを必要とする場合があります。そのような操作を使用するアプリケーションでは、java.security.AuthProviderクラスを使用できます。AuthProvider
クラスは、java.security.Provider
を拡張し、プロバイダでログインおよびログアウト操作を行なったり、プロバイダが使用するコールバック・ハンドラを設定したりするためのメソッドを定義しています。
SunPKCS11プロバイダの場合、コールバック・ハンドラがPasswordCallback
に対応できる必要があります。PasswordCallbackは、ユーザーにPINを要求するために使用されます。
次に、アプリケーションでAuthProvider
を使用してトークンにログインする方法の例を示します。
AuthProvider aprov = (AuthProvider)Security.getProvider("SunPKCS11"); aprov.login(subject, new MyGuiCallbackHandler());
JavaのKey
オブジェクトには、実際の鍵データが含まれる場合も含まれない場合もあります。
アプリケーションおよびプロバイダでは、各種のKeyオブジェクトを表すために正しいインタフェースを使用する必要があります。ソフトウェアKeyオブジェクト(または実際の鍵データにアクセスできる任意のKeyオブジェクト)では、java.security.interfacesおよびjavax.crypto.interfacesパッケージにインタフェース(DSAPrivateKey
など)を実装する必要があります。抽出不可能なトークン鍵を表すKeyオブジェクトでは、java.securityおよびjavax.cryptoパッケージに、関連するジェネリック・インタフェース(PrivateKey
、PublicKey
またはSecretKey
)のみを実装する必要があります。鍵アルゴリズムの特定は、Key.getAlgorithm()
メソッドを使用して実行されます。
抽出不可能なトークン鍵のKeyオブジェクトはそのトークンに関連付けられたプロバイダのみが使用できるということに注意してください。
Cipher.getInstance("AES")
などのJava暗号化getInstance()
メソッドは、要求されたアルゴリズムを実装している最初のプロバイダからの実装を返します。ただし、JDKは、関連する初期化メソッドが呼び出されるまでプロバイダの選択を遅らせます。初期化メソッドはKey
オブジェクトを受け入れ、その時点で、指定したKey
オブジェクトを受け入れられるプロバイダを判断できます。これにより、選択したプロバイダで、指定したKey
オブジェクトを使用できることが保証されます。(アプリケーションが、ソフトウェアKeyオブジェクトのみを使用できるプロバイダで、抽出不可能なトークン鍵のKey
オブジェクトを使用しようとした場合、プロバイダはInvalidKeyException
をスローします。これは、Cipher
、KeyAgreement
、Mac
および、Signature
クラスの問題です。次に、影響のある初期化メソッドを示します。
Cipher
.init(..., Key key, ...)
KeyAgreement
.init(Key key, ...)
Mac
.init(Key key, ...)
Signature
.initSign(PrivateKey privateKey)
また、アプリケーションが初期化メソッドを複数回呼び出すと(毎回異なる鍵を使用するなど)、その鍵に適切なプロバイダが毎回選択されます。つまり、初期化の呼び出しごとに異なるプロバイダが選択される可能性があります。
このプロバイダの遅延選択は、アプリケーションからは認識されませんが、Cipher
、KeyAgreement
、Mac
、およびSignature
のgetProvider()
メソッドの動作に影響します。getProvider()
が初期化操作の発生する前 (したがって、プロバイダ選択が起こる前)に呼び出された場合は、要求されるアルゴリズムがサポートされている最初のプロバイダが返されます。このプロバイダは、初期化メソッドが呼び出されたあとに選択されたプロバイダと同じにならない場合があります。getProvider()
が初期化操作のあとに呼び出された場合は、実際に選択されたプロバイダが返されます。アプリケーションでは関連する初期化メソッドを呼び出したあとにのみ、getProvider()
を呼び出すようにしてください。
getProvider()
だけでなく、次のメソッドにも同様の影響があります。
Cipher.getBlockSize
Cipher.getExcemptionMechanism
Cipher.getIV
Cipher.getOutputSize
Cipher.getParameters
Mac.getMacLength
Signature.getParameters
Signature.setParameter
JDKには、JAASキーストア・ログイン・モジュールであるKeyStoreLoginModuleが付属しています。このモジュールを使用すると、アプリケーションで、指定したキーストア内のIDを使用して認証を行うことができます。アプリケーションは認証後に、自身のプリンシパルおよびクレデンシャル情報(証明書および秘密鍵)をキーストアから取得します。このログイン・モジュールを使用し、PKCS#11トークンをキーストアとして使用するように構成すると、アプリケーションはこの情報をPKCS#11トークンから取得できるようになります。
次のオプションを使用してKeyStoreLoginModule
を構成すると、PKCS#11トークンをキーストアとして使用できます。
keyStoreURL="NONE"
keyStoreType="PKCS11"
keyStorePasswordURL=some_pin_url
説明は次のとおりです。
keyStorePasswordURL
オプションを省略すると、ログイン・モジュールはPINをアプリケーションのコールバック・ハンドラから取得し、PasswordCallback
でそのPINを指定します。PKCS#11トークンをキーストアとして使用する構成ファイルの例を次に示します。 other { com.sun.security.auth.module.KeyStoreLoginModule required keyStoreURL="NONE" keyStoreType="PKCS11" keyStorePasswordURL="file:/home/joe/scpin"; };
複数のSunPKCS11プロバイダを動的に構成した場合、またはjava.security
セキュリティ・プロパティ・ファイル内で構成した場合は、keyStoreProvider
オプションを使用して、特定のプロバイダ・インスタンスを対象にします。このオプションの引数は、プロバイダの名前です。SunPKCS11プロバイダの場合、プロバイダ名はSunPKCS11-TokenName
という形式になります。ここで、TokenName
はプロバイダ・インスタンスが構成された名前の接尾辞です。詳細は、表5-1を参照してください。たとえば、次の構成ファイルでは、PKCS#11プロバイダ・インスタンスに名前接尾辞SmartCard
で名前を付けています。
other { com.sun.security.auth.module.KeyStoreLoginModule required keyStoreURL="NONE" keyStoreType="PKCS11" keyStorePasswordURL="file:/home/joe/scpin" keyStoreProvider="SunPKCS11-SmartCard"; };
保護された認証パスを介してのログインをサポートしているPKCS#11トークンもあります。たとえばスマートカードには、PINを入力するための専用のPINパッドがある場合があります。生体測定機器にも、認証情報を取得する専用の方法が用意されています。PKCS#11トークンに保護された認証パスがある場合は、protected=true
オプションを使用し、keyStorePasswordURL
オプションは省略します。そのようなトークン用の構成ファイルの例を次に示します。
other { com.sun.security.auth.module.KeyStoreLoginModule required keyStoreURL="NONE" keyStoreType="PKCS11" protected=true; };
PKCS#11トークンをJSSEキーストアまたはトラスト・ストアとして使用できるように、JSSEアプリケーションでは、トークン・ログインで説明したAPIを使用してKeyStoreをインスタンス化します。このKeyStoreは、PKCS#11トークンによって戻され、キー・マネージャおよびトラスト・マネージャに渡されます。こうしてJSSEアプリケーションでは、トークン上の鍵にアクセスできるようになります。
JSSEでは、システム・プロパティ経由でキーストアおよびトラスト・ストアを使用するように構成できます(Java Secure Socket Extension (JSSE)リファレンス・ガイドを参照)。PKCS#11トークンをキーストアまたはトラスト・ストアとして使用するには、javax.net.ssl.keyStoreType
およびjavax.net.ssl.trustStoreType
システム・プロパティをそれぞれ「PKCS11」に設定し、javax.net.ssl.keyStore
およびjavax.net.ssl.trustStore
システム・プロパティをそれぞれ「NONE
」に設定します。特定のプロバイダ・インスタンスを使用するように指定するには、javax.net.ssl.keyStoreProvider
およびjavax.net.ssl.trustStoreProvider
システム・プロパティを使用します(例: SunPKCS11-SmartCard)。
SunPKCS11プロバイダがjava.security
セキュリティ・プロパティ・ファイル(Javaランタイムの$JAVA_HOME/conf/security
ディレクトリ内にある)で構成されている場合、次のオプションを使用することで、PKCS#11トークンの操作にkeytoolおよびjarsignerを使用できます。
-keystore NONE
-storetype PKCS11
keytool -keystore NONE -storetype PKCS11 -listPINは
-storepass
オプションで指定できます。指定されない場合、keytool
およびjarsigner
は、トークンPINを要求します。トークンに保護された認証パス(専用のPINパッドや生体読取り機など)がある場合、-protected
オプションを指定する必要がありますが、パスワード・オプションを指定する必要はありません。java.security
セキュリティ・プロパティ・ファイル内で複数のSunPKCS11プロバイダが構成されている場合、-providerName
オプションを使用して特定のプロバイダ・インスタンスを選択できます。このオプションの引数は、プロバイダの名前です。
-providerName providerName
SunPKCS11プロバイダの場合、providerName
の形式はSunPKCS11-TokenName
のようになります。この説明を次に示します。
SmartCard
のPKCS#11キーストア・プロバイダ・インスタンスの内容をリストします。 keytool -keystore NONE -storetype PKCS11 \ -providerName SunPKCS11-SmartCard \ -list
SunPKCS11プロバイダをjava.security
セキュリティ・プロパティ・ファイル内で構成していない場合は、次のオプションを使用して、プロバイダを動的にインストールするようにkeytool
およびjarsigner
を設定します。
-providerClass sun.security.pkcs11.SunPKCS11
-providerArg ConfigFilePath
java.security
ファイルで構成されていないときにPKCS#11キーストアをリストするコマンドの例を示します。keytool -keystore NONE -storetype PKCS11 \ -providerClass sun.security.pkcs11.SunPKCS11 \ -providerArg /foo/bar/token.config \ -list
注意:
ポリシー・ツールは、JDK 9では推奨されていません。
デフォルト・ポリシー実装内のkeystore
エントリには、次の構文があります。これには、PINおよび複数のPKCS#11プロバイダ・インスタンスを指定できます。
keystore "some_keystore_url", "keystore_type", "keystore_provider"; keystorePasswordURL "some_password_url";
説明は次のとおりです。
"SunPKCS11-SmartCard"
)。keystorePasswordURL
の行が指定されていない場合、パスワードは使用されません。 例5-2 PKCS#11トークンのためのキーストア・ポリシー・エントリ
PKCS#11トークンのキーストア・ポリシー・エントリの例を次に示します。
keystore "NONE", "PKCS11", "SunPKCS11-SmartCard"; keystorePasswordURL "file:/foo/bar/passwordFile";
java.security.Provider
クラスを使用すると、プロバイダ開発者は、プロバイダ・サービスおよびパラメータ・サポートによって、より簡単にPKCS#11トークンおよび暗号化サービスをサポートできます。
プロバイダ・サービスおよびパラメータ・サポートを示すように設計された単純なプロバイダの例は、プロバイダの例を参照してください。
プロバイダによるサービス実装ごとに、サービスの型(Cipher
、Signature
など)、ピリオド、およびサービスが適用されるアルゴリズム名で構成される名前のプロパティが必要です。プロパティの値には、サービスを実装するクラスの完全修飾名を指定する必要があります。値com.sun.crypto.provider.DHKeyAgreement
を持つようにKeyAgreement.DiffieHellman
プロパティを設定するプロバイダの例を次に示します。
put("KeyAgreement.DiffieHellman", "com.sun.crypto.provider.DHKeyAgreement")
public staticのネストされたクラスProvider.Serviceは、プロバイダ・サービスのプロパティ(型、属性、アルゴリズム名、アルゴリズムの別名など)をカプセル化します。プロバイダはProvider.putService()
メソッドを呼び出すことで、Provider.Service
オブジェクトをインスタンス化して登録できます。これは、Property
エントリを作成してProvider.put()
メソッドを呼び出すことと同じです。Provider.put
経由で登録されたレガシーのProperty
エントリも引き続きサポートされています。
クラスcom.sun.crypto.provider.DHKeyAgreement
によって実装され、DiffieHellman
アルゴリズムを使用する、KeyAgreement
型のService
オブジェクトを作成するプロバイダの例を次に示します。
Service s = new Service(this, "KeyAgreement", "DiffieHellman", "com.sun.crypto.provider.DHKeyAgreement", null, null); putService(s);
旧バージョンのProperty
エントリの代わりにProvider.Servicee
オブジェクトを使用すると、大きな利点が2つあります。1つ目として、エンジン・クラスをインスタンス化するときに、プロバイダの柔軟性が向上します。2つ目として、プロバイダでパラメータ・サポートをテストできます。次に、これらの特徴について説明します。
エンジン・クラスのインスタンス化
デフォルトでは、Java暗号化フレームワークが特定のサービスのプロバイダ・プロパティを検索し、そのプロパティで登録されたエンジン・クラスを直接インスタンス化します。プロバイダはこの動作をオーバーライドし、要求されたサービス自体のためにエンジン・クラスをインスタンス化できます。
デフォルトの動作をオーバーライドするときは、プロバイダはカスタムな動作を追加するようにProvider.Service.newInstance()
メソッドをオーバーライドします。たとえばプロバイダはカスタムのコンストラクタを呼び出したり、プロバイダの外部からはアクセスできない(プロバイダのみが知る)情報を使用して初期化を実行したりすることができます。
Java暗号化フレームワークでは、プロバイダのサービス実装がアプリケーション固有のパラメータを使用できるかどうかを判断するために、すばやいチェックを試みます。このチェックを実行するために、フレームワークではProvider.Service.supportsParameter()
を呼び出します。
フレームワークは、プロバイダの遅延選択中にこのすばやいチェックを利用します(プロバイダの遅延選択を参照)。アプリケーションが初期化メソッドを呼び出してKey
オブジェクトを渡すと、フレームワークではService.supportsParameter()
メソッドを呼び出すことで、配下のプロバイダに対してそのオブジェクトをサポートしているかどうかを確認します。supportsParameter()
がfalse
を返すと、フレームワークはただちにそのプロバイダを対象から取り除きます。supportsParameter()
がtrue
を返すと、フレームワークはKey
オブジェクトをそのプロバイダの初期化エンジン・クラス実装に渡します。ソフトウェアKey
オブジェクトを必要とするプロバイダでは、ソフトウェア以外の鍵を渡されたときにfalse
を返すように、このメソッドをオーバーライドする必要があります。同様に、抽出不可能な鍵が含まれたPKCS#11トークンのプロバイダでは、このプロバイダが作成した、つまりそれぞれのトークン上の鍵に対応するKey
オブジェクトに対してtrue
のみを返すようにする必要があります。
注意:
supportsParameter()
のデフォルト実装ではtrue
を返します。これにより、既存のプロバイダを変更しないで動作させることができます。しかし、この緩やかなデフォルト実装のために、フレームワークでは、初期化エンジン・クラス実装内部のKey
オブジェクトを拒否するプロバイダが例外をスローしたときに、その例外をキャッチできるようにしておく必要があります。フレームワークでは、これらのケースをsupportsParameter()
がfalse
を返すときと同じように扱います。注意:
disabledMechanisms
およびenabledMechanisms
構成ディレクティブを使用すると、メカニズムを無視するようにSunPKCS11に指示できます(SunPKCS11の構成を参照)。Elliptic Curveメカニズムでは、SunPKCS11プロバイダは、パラメータのエンコーディングとしてnamedCurve
の選択を使用する鍵のみを使用し、圧縮されていない形式のみを許可します。SunPKCS11プロバイダでは、標準名が付けられたすべてのドメイン・パラメータをトークンがサポートしていることが前提となります。
表5-3 SunPKCS11プロバイダでサポートされているJavaアルゴリズム
Javaアルゴリズム | PKCS#11メカニズム |
---|---|
Signature.MD2withRSA | CKM_MD2_RSA_PKCS、CKM_RSA_PKCS、CKM_RSA_X_509 |
Signature.MD5withRSA | CKM_MD5_RSA_PKCS、CKM_RSA_PKCS、CKM_RSA_X_509 |
Signature.SHA1withRSA | CKM_SHA1_RSA_PKCS、CKM_RSA_PKCS、CKM_RSA_X_509 |
Signature.SHA224withRSA | CKM_SHA224_RSA_PKCS、CKM_RSA_PKCS、CKM_RSA_X_509 |
Signature.SHA256withRSA | CKM_SHA256_RSA_PKCS、CKM_RSA_PKCS、CKM_RSA_X_509 |
Signature.SHA384withRSA | CKM_SHA384_RSA_PKCS、CKM_RSA_PKCS、CKM_RSA_X_509 |
Signature.SHA512withRSA | CKM_SHA512_RSA_PKCS、CKM_RSA_PKCS、CKM_RSA_X_509 |
Signature.SHA1withDSA | CKM_DSA_SHA1、CKM_DSA |
Signature.NONEwithDSA | CKM_DSA |
Signature.SHA1withECDSA | CKM_ECDSA_SHA1、CKM_ECDSA |
Signature.SHA224withECDSA | CKM_ECDSA |
Signature.SHA256withECDSA | CKM_ECDSA |
Signature.SHA384withECDSA | CKM_ECDSA |
Signature.SHA512withECDSA | CKM_ECDSA |
Signature.NONEwithECDSA | CKM_ECDSA |
Cipher.RSA/ECB/PKCS1Padding | CKM_RSA_PKCS |
Cipher.ARCFOUR | CKM_RC4 |
Cipher.DES/CBC/NoPadding | CKM_DES_CBC |
Cipher.DESede/CBC/NoPadding | CKM_DES3_CBC |
Cipher.AES/CBC/NoPadding | CKM_AES_CBC |
Cipher.Blowfish/CBC/NoPadding | CKM_BLOWFISH_CBC |
Cipher.RSA/ECB/NoPadding | CKM_RSA_X_509 |
Cipher.AES/CTR/NoPadding | CKM_AES_CTR |
KeyAgreement.ECDH | CKM_ECDH1_DERIVE |
KeyAgreement.DiffieHellman | CKM_DH_PKCS_DERIVE |
KeyPairGenerator.RSA | CKM_RSA_PKCS_KEY_PAIR_GEN |
KeyPairGenerator.DSA | CKM_DSA_KEY_PAIR_GEN |
KeyPairGenerator.EC | CKM_EC_KEY_PAIR_GEN |
KeyPairGenerator.DiffieHellman | CKM_DH_PKCS_KEY_PAIR_GEN |
KeyGenerator.ARCFOUR | CKM_RC4_KEY_GEN |
KeyGenerator.DES | CKM_DES_KEY_GEN |
KeyGenerator.DESede | CKM_DES3_KEY_GEN |
KeyGenerator.AES | CKM_AES_KEY_GEN |
KeyGenerator.Blowfish | CKM_BLOWFISH_KEY_GEN |
Mac.HmacMD5 | CKM_MD5_HMAC |
Mac.HmacSHA1 | CKM_SHA_1_HMAC |
Mac.HmacSHA224 | CKM_SHA224_HMAC |
Mac.HmacSHA256 | CKM_SHA256_HMAC |
Mac.HmacSHA384 | CKM_SHA384_HMAC |
Mac.HmacSHA512 | CKM_SHA512_HMAC |
MessageDigest.MD2 | CKM_MD2 |
MessageDigest.MD5 | CKM_MD5 |
MessageDigest.SHA1 | CKM_SHA_1 |
MessageDigest.SHA-224 | CKM_SHA224 |
MessageDigest.SHA-256 | CKM_SHA256 |
MessageDigest.SHA-384 | CKM_SHA384 |
MessageDigest.SHA-512 | CKM_SHA512 |
KeyFactory.RSA | サポートされるRSAメカニズムすべて |
KeyFactory.DSA | サポートされるDSAメカニズムすべて |
KeyFactory.EC | サポートされるECメカニズムすべて |
KeyFactory.DiffieHellman | サポートされるDiffie-Hellmanメカニズムすべて |
SecretKeyFactory.ARCFOUR | CKM_RC4 |
SecretKeyFactory.DES | CKM_DES_CBC |
SecretKeyFactory.DESede | CKM_DES3_CBC |
SecretKeyFactory.AES | CKM_AES_CBC |
SecretKeyFactory.Blowfish | CKM_BLOWFISH_CBC |
SecureRandom.PKCS11 | CK_TOKEN_INFOにはCKF_RNGビット・セットがある |
KeyStore.PKCS11 | 常に使用可能 |
ここでは、SunPKCS11プロバイダのKeyStoreを、ベースとなるネイティブのPKCS#11ライブラリへ実装する場合の要件を説明します。
注意:
より多くの既存のPKCS#11ライブラリとの相互運用性を実現するため、将来のリリースで変更が加えられる可能性があります。読取り専用アクセス
PKCS#11トークンに保存された既存のオブジェクトをKeyStoreエントリにマッピングするため、SunPKCS11プロバイダのKeyStore実装は次の操作を実行します。
C_FindObjects[Init|Final]
を呼び出して、トークン上のすべての非公開鍵オブジェクトを検索します。検索テンプレートには、次の属性があります。 C_FindObjects[Init|Final]
を呼び出して、トークン上のすべての証明書オブジェクトを検索します。検索テンプレートには、次の属性があります。 一致するペアごとに、発行者 ->サブジェクトのパスに従って証明書チェーンが作成されます。エンド・エンティティ証明書から、次の属性を持つ検索テンプレートを使用したC_FindObjects[Init|Final]
が呼び出されます。
この検索は、発行者の証明書が見つからないか、自己署名証明書が見つかるまで継続します。複数の証明書が見つかった場合は、最初の証明書が使用されます。
非公開鍵と証明書が一致して証明書チェーンが作成されると、エンド・エンティティ証明書のCKA_LABELの値をKeyStoreの別名として、非公開鍵エントリに情報が保存されます。
エンド・エンティティ証明書にCKA_LABELがない場合は、別名はCKA_IDから作成されます。CKA_IDが出力可能文字からのみ構成される場合は、CKA_IDのバイトをUTF-8文字セットを使用してデコードして、Stringの別名を作成します。または、16進数のString別名を、CKA_IDバイトから作成します(例: 「0xFFFF」)。
複数の証明書が同一のCKA_LABELを共有する場合、別名はCKA_LABELに加え、エンド・エンティティ証明書の発行者およびシリアル番号(例: "MyCert/CN=foobar/1234"
)から作成されます。
CKA_TRUSTED属性がサポートされていない場合は、信頼できる証明書エントリは作成されません。
C_FindObjects[Init|Final]
を呼び出して、トークン上のすべての秘密鍵オブジェクトを検索します。検索テンプレートには、次の属性があります。 各秘密鍵オブジェクトに対して、CKA_LABEL値をKeyStoreの別名とするKeyStore秘密鍵エントリが作成されます。各秘密鍵オブジェクトには、固有のCKA_LABELが必要です。
書込みアクセス
PKCS#11トークンにKeyStoreエントリに対する新しいKeyStoreエントリを作成するため、SunPKCS11プロバイダのKeyStore実装は次の操作を実行します。
CKA_TOKEN=true
としてC_CreateObjectが呼び出され、エントリの内容それぞれに対してトークン・オブジェクトが作成されます。 非公開鍵オブジェクトは、CKA_PRIVATE=true
で保存されます。KeyStoreの別名(UTF-8エンコード)は、非公開鍵と対応するエンド・エンティティ証明書の両方でCKA_IDとして設定されます。KeyStoreの別名では、エンド・エンティティ証明書オブジェクトにCKA_LABELが設定されます。
非公開鍵エントリのチェーン内の各証明書も保存されます。CKA_LABELは、CA証明書には設定されません。CA証明書がトークン内にある場合、複製は保存されません。
秘密鍵オブジェクトは、CKA_PRIVATE=true
で保存されます。KeyStoreの別名は、CKA_LABELとして設定されます。
CKA_TOKEN=true
として呼び出されます。CKA_TRUSTED=true
と設定することが許可されないため(トークン初期化アプリケーションのみが可能)、信頼できる証明書エントリを作成できません。その他
前述の検索の他、SunPKCS11プロバイダのKeyStore実装で次の検索を使用して内部関数を実行できます。具体的には、次のどの属性テンプレートを使用しても、C_FindObjects[Init|Final]
を呼び出すことができます。
CKA_TOKEN true CKA_CLASS CKO_CERTIFICATE CKA_SUBJECT [subject DN]
CKA_TOKEN true CKA_CLASS CKO_SECRET_KEY CKA_LABEL [label]
CKA_TOKEN true CKA_CLASS CKO_CERTIFICATE or CKO_PRIVATE_KEY CKA_ID [cka_id]
次に、Providerクラスの機能を示す単純なプロバイダの例を示します。
package com.foo; import java.io.*; import java.lang.reflect.*; import java.security.*; import javax.crypto.*; /** * Example provider that demonstrates some Provider class features. * * . implement multiple different algorithms in a single class. * Previously each algorithm needed to be implemented in a separate class * (e.g. one for SHA-256, one for SHA-384, etc.) * * . multiple concurrent instances of the provider frontend class each * associated with a different backend. * * . it uses "unextractable" keys and lets the framework know which key * objects it can and cannot support * * Note that this is only a simple example provider designed to demonstrate * several of the new features. It is not explicitly designed for efficiency. */ public final class ExampleProvider extends Provider { // reference to the crypto backend that implements all the algorithms final CryptoBackend cryptoBackend; public ExampleProvider(String name, CryptoBackend cryptoBackend) { super(name, 1.0, "JCA/JCE provider for " + name); this.cryptoBackend = cryptoBackend; // register the algorithms we support (SHA-256, SHA-384, DESede, and AES) putService(new MyService (this, "MessageDigest", "SHA-256", "com.foo.ExampleProvider$MyMessageDigest")); putService(new MyService (this, "MessageDigest", "SHA-384", "com.foo.ExampleProvider$MyMessageDigest")); putService(new MyCipherService (this, "Cipher", "DES", "com.foo.ExampleProvider$MyCipher")); putService(new MyCipherService (this, "Cipher", "AES", "com.foo.ExampleProvider$MyCipher")); } // the API of our fictitious crypto backend static abstract class CryptoBackend { abstract byte[] digest(String algorithm, byte[] data); abstract byte[] encrypt(String algorithm, KeyHandle key, byte[] data); abstract byte[] decrypt(String algorithm, KeyHandle key, byte[] data); abstract KeyHandle createKey(String algorithm, byte[] keyData); } // the shell of the representation the crypto backend uses for keys private static final class KeyHandle { // fill in code } // we have our own ServiceDescription implementation that overrides newInstance() // that calls the (Provider, String) constructor instead of the no-args constructor private static class MyService extends Service { private static final Class[] paramTypes = {Provider.class, String.class}; MyService(Provider provider, String type, String algorithm, String className) { super(provider, type, algorithm, className, null, null); } public Object newInstance(Object param) throws NoSuchAlgorithmException { try { // get the Class object for the implementation class Class clazz; Provider provider = getProvider(); ClassLoader loader = provider.getClass().getClassLoader(); if (loader == null) { clazz = Class.forName(getClassName()); } else { clazz = loader.loadClass(getClassName()); } // fetch the (Provider, String) constructor Constructor cons = clazz.getConstructor(paramTypes); // invoke constructor and return the SPI object Object obj = cons.newInstance(new Object[] {provider, getAlgorithm()}); return obj; } catch (Exception e) { throw new NoSuchAlgorithmException("Could not instantiate service", e); } } } // custom ServiceDescription class for Cipher objects. See supportsParameter() below private static class MyCipherService extends MyService { MyCipherService(Provider provider, String type, String algorithm, String className) { super(provider, type, algorithm, className); } // we override supportsParameter() to let the framework know which // keys we can support. We support instances of MySecretKey, if they // are stored in our provider backend, plus SecretKeys with a RAW encoding. public boolean supportsParameter(Object obj) { if (obj instanceof SecretKey == false) { return false; } SecretKey key = (SecretKey)obj; if (key.getAlgorithm().equals(getAlgorithm()) == false) { return false; } if (key instanceof MySecretKey) { MySecretKey myKey = (MySecretKey)key; return myKey.provider == getProvider(); } else { return "RAW".equals(key.getFormat()); } } } // our generic MessageDigest implementation. It implements all digest // algorithms in a single class. We only implement the bare minimum // of MessageDigestSpi methods private static final class MyMessageDigest extends MessageDigestSpi { private final ExampleProvider provider; private final String algorithm; private ByteArrayOutputStream buffer; MyMessageDigest(Provider provider, String algorithm) { super(); this.provider = (ExampleProvider)provider; this.algorithm = algorithm; engineReset(); } protected void engineReset() { buffer = new ByteArrayOutputStream(); } protected void engineUpdate(byte b) { buffer.write(b); } protected void engineUpdate(byte[] b, int ofs, int len) { buffer.write(b, ofs, len); } protected byte[] engineDigest() { byte[] data = buffer.toByteArray(); byte[] digest = provider.cryptoBackend.digest(algorithm, data); engineReset(); return digest; } } // our generic Cipher implementation, only partially complete. It implements // all cipher algorithms in a single class. We implement only as many of the // CipherSpi methods as required to show how it could work private static abstract class MyCipher extends CipherSpi { private final ExampleProvider provider; private final String algorithm; private int opmode; private MySecretKey myKey; private ByteArrayOutputStream buffer; MyCipher(Provider provider, String algorithm) { super(); this.provider = (ExampleProvider)provider; this.algorithm = algorithm; } protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException { this.opmode = opmode; myKey = MySecretKey.getKey(provider, algorithm, key); if (myKey == null) { throw new InvalidKeyException(); } buffer = new ByteArrayOutputStream(); } protected byte[] engineUpdate(byte[] b, int ofs, int len) { buffer.write(b, ofs, len); return new byte[0]; } protected int engineUpdate(byte[] b, int ofs, int len, byte[] out, int outOfs) { buffer.write(b, ofs, len); return 0; } protected byte[] engineDoFinal(byte[] b, int ofs, int len) { buffer.write(b, ofs, len); byte[] in = buffer.toByteArray(); byte[] out; if (opmode == Cipher.ENCRYPT_MODE) { out = provider.cryptoBackend.encrypt(algorithm, myKey.handle, in); } else { out = provider.cryptoBackend.decrypt(algorithm, myKey.handle, in); } buffer = new ByteArrayOutputStream(); return out; } // code for remaining CipherSpi methods goes here } // our SecretKey implementation. All our keys are stored in our crypto // backend, we only have an opaque handle available. There is no // encoded form of these keys. private static final class MySecretKey implements SecretKey { final String algorithm; final Provider provider; final KeyHandle handle; MySecretKey(Provider provider, String algorithm, KeyHandle handle) { super(); this.provider = provider; this.algorithm = algorithm; this.handle = handle; } public String getAlgorithm() { return algorithm; } public String getFormat() { return null; // this key has no encoded form } public byte[] getEncoded() { return null; // this key has no encoded form } // Convert the given key to a key of the specified provider, if possible static MySecretKey getKey(ExampleProvider provider, String algorithm, Key key) { if (key instanceof SecretKey == false) { return null; } // algorithm name must match if (!key.getAlgorithm().equals(algorithm)) { return null; } // if key is already an instance of MySecretKey and is stored // on this provider, return it right away if (key instanceof MySecretKey) { MySecretKey myKey = (MySecretKey)key; if (myKey.provider == provider) { return myKey; } } // otherwise, if the input key has a RAW encoding, convert it if (!"RAW".equals(key.getFormat())) { return null; } byte[] encoded = key.getEncoded(); KeyHandle handle = provider.cryptoBackend.createKey(algorithm, encoded); return new MySecretKey(provider, algorithm, handle); } } }