JavaTM PKCS#11 リファレンスガイド


1.0 はじめに

2.0 Sun PKCS#11 プロバイダ

2.1 要件
2.2 構成
2.3 Network Security Services (NSS) へのアクセス
3.0 アプリケーションの開発
3.1 トークンログイン
3.2 トークン鍵
3.3 プロバイダの遅延選択
3.4 JAAS KeyStoreLoginModule
3.5 JSSE キーストアおよびトラストストアとしてのトークン
4.0 ツール
4.1 KeyTool および JarSigner
4.2 Policy ツール
5.0 プロバイダの開発
5.1 プロバイダサービス
5.1.1 エンジンクラスのインスタンス化
5.1.2 パラメータのサポート

付録 A Sun PKCS#11 プロバイダでサポートされるアルゴリズム

付録 B Sun PKCS#11 プロバイダの KeyStore 要件

付録 C プロバイダの例


1.0 はじめに

Java プラットフォームでは、暗号化操作を実行するための一連のプログラミングインタフェースを定義しています。これらのインタフェースは、総称して Java 暗号化アーキテクチャー (JCA) および Java 暗号化拡張機能 (JCE) と呼ばれ、http://java.sun.com/j2se/1.5.0/ja/docs/ja/guide/security/ で指定されています。

暗号化インタフェースはプロバイダベースです。具体的には、アプリケーションはアプリケーションプログラミングインタフェース (API) とやり取りをし、実際の暗号化操作は、一連のサービスプロバイダインタフェース (SPI) に従った構成済みプロバイダ内で実行されます。このアーキテクチャーでは、プロバイダのさまざまな実装をサポートしています。ソフトウェアで暗号化操作を行うプロバイダもあれば、スマートカードデバイスやハードウェア暗号化アクセラレータなどのハードウェアトークン上で暗号化操作を行うプロバイダもあります。

暗号化トークンインタフェース標準である PKCS#11 は、RSA Security が策定し、ハードウェア暗号化アクセラレータやスマートカードなどの暗号化トークンに対するネイティブプログラミングインタフェースを定義しています。ネイティブ PKCS#11 トークンを Java プラットフォームに簡単に統合するために、新しい暗号化プロバイダである Sun PKCS#11 プロバイダが J2SE 5.0 リリースに導入されました。この新しいプロバイダでは、既存の JCA および JCE API 対応アプリケーションがネイティブ PKCS#11 トークンにアクセスできます。アプリケーションへの変更は必要ありません。プロバイダの適切な設定を Java Runtime に行うだけで済みます。

アプリケーションは既存の API を使用して PKCS#11 機能のほとんどを利用できますが、柔軟性や機能性をさらに必要とするアプリケーションもあります。たとえば動的に抜き差しするスマートカードをアプリケーションでもっと簡単に扱えるようにしたい場合があります。また、PKCS#11 トークンで鍵とは関係ない一部の操作を認証する必要があるため、アプリケーションはキーストアを使用せずにトークンにログインできるようにする必要があります。J2SE 5.0 では JCA が拡張され、アプリケーションがさまざまなプロバイダを扱う際の柔軟性が向上しました。

このドキュメントでは、ネイティブ PKCS#11 トークンを Java アプリケーションで使用できるように、Java プラットフォームに構成する方法を説明します。また、PKCS#11 プロバイダを始めとする各種のプロバイダをアプリケーションで扱いやすくするために、JCA に加えられた機能の向上点についても説明します。

2.0 Sun PKCS#11 プロバイダ

その他の多くのプロバイダとは異なり、Sun PKCS#11 プロバイダ自身は暗号化アルゴリズムを実装していません。その代わりに、Java JCA および JCE API とネイティブ PKCS#11 暗号化 API との間のブリッジとして動作し、これらの間の呼び出しと規則を変換します。つまり、標準 JCA および JCE API を呼び出す Java アプリケーションなら、アプリケーションを変更しなくても、基盤となる次のような PKCS#11 実装で提供されるアルゴリズムを利用できるということです。

Java SE はネイティブ PKCS#11 実装にアクセスできるだけですが、それ自身にはネイティブ PKCS#11 実装が含まれていません。ただし、スマートカードやハードウェアアクセラレータなどの暗号化デバイスには、PKCS#11 実装を含んだソフトウェアが付属していることが一般的で、製造元の指示に従ってインストールし、構成する必要があります。

2.1 要件

Sun PKCS#11 プロバイダは、Solaris (SPARC と x86) および Linux (x86) の 32 ビットと 64 ビットの両方の Java プロセスでサポートされています。また、32 ビットの Windows (x86) でもサポートされていますが、適切な PKCS#11 ライブラリが提供されていないため 64 ビットの Windows プラットフォームでは現在サポートされていません。

Sun PKCS#11 プロバイダでは、PKCS#11 v2.0 以降の実装がシステムにインストールされていなければなりません。この実装は、共有オブジェクトライブラリ (Solaris および Linux での .so) またはダイナミックリンクライブラリ (Windows での .dll) の形態である必要があります。使用する暗号化デバイスにこのような PKCS#11 実装が含まれているかどうかを調べる方法、実装を構成する方法、およびライブラリファイルのファイル名については、ベンダーが提供するマニュアルを参照してください。

Sun PKCS#11 プロバイダでは、基盤となる PKCS#11 実装で提供されるかぎり、多くのアルゴリズムをサポートしています。アルゴリズムとそれらに対応する PKCS#11 機構を、付録 A の表にリストします。

2.2 構成

Sun PKCS#11 プロバイダは、メインクラス sun.security.pkcs11.SunPKCS11 で実装されており、構成ファイルのフルパス名を引数として使用できます。プロバイダを使用するには、あらかじめ Java 暗号化アーキテクチャー (JCA) を使用してインストールしておく必要があります。あらゆる JCA プロバイダと同様に、Sun PKCS#11 プロバイダのインストールは静的またはプログラミングで実行できます。Sun PKCS#11 プロバイダを静的にインストールするには、Java セキュリティーのプロパティーファイル ($JAVA_HOME/lib/security/java.security) にプロバイダを追加します。たとえば以下は、Sun PKCS#11 プロバイダを構成ファイル /opt/bar/cfg/pkcs11.cfg とともにインストールする java.security ファイルの一部です。
# configuration for security providers 1-6 ommitted
security.provider.7=sun.security.pkcs11.SunPKCS11 /opt/bar/cfg/pkcs11.cfg
Sun PKCS#11 プロバイダを動的にインストールするには、適切な構成ファイル名を使用してプロバイダのインスタンスを作成し、インストールします。次に例を示します。
String configName = "/opt/bar/cfg/pkcs11.cfg";
Provider p = new sun.security.pkcs11.SunPKCS11(configName);
Security.addProvider(p);

PKCS#11 実装あたり複数のスロットを使用する場合や、複数の PKCS#11 実装を使用する場合は、適切な構成ファイルでそれぞれのインストールを繰り返すだけです。これにより、それぞれの PKCS#11 実装の各スロットに対して Sun PKCS#11 プロバイダのインスタンスが 1 つ作成されることになります。

構成ファイルはテキストファイルで、以下の形式のエントリが含まれます。

attribute = value
attributevalue の有効な値については、このセクションの表で説明しています。2 つある必須属性は、name および library です。次に、構成ファイルの例を示します。
name = FooAccelerator
library = /opt/foo/lib/libpkcs11.so
コメントは、# (シャープ) 記号で始まる行に記述します。

属性説明
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 になる。 slotslotListIndex のどちらか 1 つを指定すればよい。どちらも指定しない場合のデフォルトは、値 0slotListIndex
slotListIndex スロットのインデックス このプロバイダインスタンスが関連付けされているスロットのインデックス。PKCS#11 の関数 C_GetSlotList で返されるすべてのスロットのリストにおけるインデックスである。たとえば 0 は、リストの先頭のスロットを表す。slotslotListIndex のどちらか 1 つを指定すればよい。どちらも指定しない場合のデフォルトは、値 0slotListIndex
enabledMechanisms 有効にする PKCS#11 機構のリスト。 空白文字で区切り、全体を中かっこ ( ) で囲む このプロバイダインスタンスが使用する PKCS#11 機構のリスト。 これらの機構は、Sun PKCS#11 プロバイダと PKCS#11 トークンの両方でサポートされている必要がある。その他の機構はすべて無視される。リスト内の各エントリは、PKCS#11 機構の名前になる。2 つの PKCS#11 機構から成るリストの例を次に示す。
enabledMechanisms = {
  CKM_RSA_PKCS
  CKM_RSA_PKCS_KEY_PAIR_GEN
}
enabledMechanisms か disabledMechanisms のどちらか 1 つを指定すればよい。どちらも指定しない場合、有効な機構は、Sun PKCS#11 プロバイダと PKCS#11 トークンの両方でサポートされている機構になる
disabledMechanisms 無効にする PKCS#11 機構のリスト。 空白文字で区切り、全体を中かっこ ( ) で囲む このプロバイダインスタンスが無視する PKCS#11 機構のリスト。リストされたあらゆる機構は、トークンと Sun PKCS#11 プロバイダでサポートされていても、プロバイダによって無視される。これらのサービスを無効にするために文字列 SecureRandom および KeyStore を指定できる。

enabledMechanismsdisabledMechanisms のどちらか 1 つを指定すればよい。どちらも指定しない場合、有効な機構は、Sun PKCS#11 プロバイダと PKCS#11 トークンの両方でサポートされている機構になる

attributes 下記参照 attributes オプションは、PKCS#11 鍵オブジェクトの作成時に設定される追加の PKCS#11 属性を指定するために使用する。これにより、特定の属性を必要とするトークンを使用できるようになる。詳細は、以下のセクションを参照

attributes の構成

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 の有効な値は以下のとおりです。 keytype の有効な値は、CKO_PUBLIC_KEYCKO_PRIVATE_KEY、および CKO_SECRET_KEY で、それぞれ公開鍵、非公開鍵、および秘密鍵に対応しています。 また、任意の鍵タイプに一致する * もあります。

keyalgorithm の有効な値は、PKCS#11 仕様に定義された CKK_xxx 定数のどれか 1 つ、または任意の鍵アルゴリズムに一致する * です。Sun PKCS#11 プロバイダで現在サポートしているアルゴリズムは、CKK_RSA、CKK_DSA、CKK_DH、CKK_AES、CKK_DES、CKK_DES3、CKK_RC4、CKK_BLOWFISH、および CKK_GENERIC です。

属性の名前と値は、1 つまたは複数の名前 - 値ペアのリストとして指定されます。 name は、CKA_SENSITIVE など、PKCS#11 仕様での CKA_xxx 定数でなければなりません。 value は、以下のいずれかになります。

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 = trueCKA_DECRYPT = true 両方のセットがあることになります。

attributes オプションには特殊な形式もあります。構成ファイルに attributes = compatibility と書くことができます。これは、属性ステートメントの全体的なセットに対するショートカットです。これはプロバイダが既存の Java アプリケーションとの互換性を最大限に保つことを目的としています。 これにより、Java アプリケーションでは、たとえばすべての鍵コンポーネントにアクセス可能で、秘密鍵を暗号化および復号化の両方に使用可能であることが期待されます。compatibility 属性の行は、ほかの attributes の行とともに使用できます。 この場合は先に説明したような、同様の編成およびオーバーライドの規則が適用されます。

2.3 Network Security Services (NSS) へのアクセス

Network Security Services (NSS) は、Mozilla/Firefox ブラウザ、Sun の Java Enterprise System サーバーソフトウェア、およびその他の多くの製品で使用されるオープンソースの一連のセキュリティーライブラリです。暗号化 API は PKCS#11 に基づいていますが、PKCS#11 標準ではない特別な機能が含まれています。Sun PKCS#11 プロバイダには、NSS 固有の機能 (複数の NSS 固有の構成指示など) と相互に作用するためのコードが含まれています。これらについては次で説明します。

最適な結果を得るために、使用可能な最新バージョンの NSS を使用することをお勧めします。少なくともバージョン 3.11.1 になります。

次に説明されている nss 構成指示が使用されている場合、Sun PKCS#11 プロバイダは NSS 固有のコードを使用します。この場合、通常の構成コマンド libraryslot、および slotListIndex は使用できません。

属性説明
nssLibraryDirectory NSS および NSPR ライブラリを含むディレクトリ NSS および NSPR ライブラリを含むディレクトリのフルパス名。Java VM として同じプロセス内で実行されている別のコンポーネントによって NSS がすでにロードおよび初期化されていないかぎり、この属性を指定する必要があります。

プラットフォームに応じて、このディレクトリを含めるために LD_LIBRARY_PATH または PATH (Windows) を設定して、オペレーティングシステムで依存ライブラリを検索できるようにする必要がある場合があります。

nssSecmodDirectory NSS DB ファイルを含むディレクトリ NSS 構成および鍵情報 (secmod.dbkey3.db、および cert8.db) を含むディレクトリのフルパス名。別のコンポーネントによって NSS がすでに初期化されていないか (上記参照)、または次に説明されているようにデータベースファイルなしで NSS が使用されないかぎり、この指示を指定する必要があります。
nssDbMode readWritereadOnly、および noDb のうちのいずれか この指示は、NSS データベースへのアクセス方法を決定します。読み書きモードではフルアクセスが可能ですが、データベースには一度に 1 つのプロセスのみがアクセスする必要があります。読み取り専用モードでは、このファイルの変更は許可されません。

noDb モードを使用すると、データベースファイルなしで純粋に暗号化プロバイダとして NSS を使用できます。PKCS11 KeyStore を使用して持続的な鍵を作成することはできません。NSS には、バンドルされている Sun の Java ベース暗号化プロバイダ (Elliptic Curve Cryptography (ECC) など) では現在使用できない高度に最適化された実装およびアルゴリズムが含まれているため、このモードは有用です。

nssModule keystorecryptofips、および trustanchors のうちのいずれか さまざまなライブラリやスロットを使用して NSS の機能が使用できるようになっています。この指示は、SunPKCS11 のインスタンスがアクセスするモジュールを決定します。

crypto モジュールは、noDb モードでのデフォルトです。このモジュールはログインを必要としない暗号化操作をサポートしますが、持続的な鍵はサポートしません。

FIPS-140 準拠モードに対して NSS secmod.db が設定されている場合、fips モジュールがデフォルトになります。このモードでは、NSS によって、使用可能なアルゴリズムおよび鍵を作成する場合に使用する PKCS#11 属性が制限されます。

keystore モジュールは、その他の構成でのデフォルトです。このモジュールは、PKCS11 KeyStore を使用して持続的な鍵をサポートします。この鍵は NSS DB ファイルに保存されます。このモジュールではログインが必要です。

信頼できるアンカーライブラリを含むように secmod.db が構成されている場合、trustanchors モジュールを使用すると、PKCS11 KeyStore を使用して NSS の信頼できるアンカー証明書にアクセスできます。

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

3.0 アプリケーションの開発

Java アプリケーションは、既存の JCA および JCE API を使用して、Sun PKCS#11 プロバイダ経由で PKCS#11 トークンにアクセスできます。多くのアプリケーションではこれで十分ですが、抽出不可能な鍵や動的に変更されるスマートカードといった一部の PKCS#11 機能を扱うには難しい場合があります。そのため、一部の PKCS#11 機能を使用するアプリケーションのサポートを向上できるように、多くの拡張機能が API に加えられました。このセクションでは、そのような拡張機能について説明します。

3.1 トークンログイン

非公開鍵へのアクセスなど一部の PKCS#11 操作では、その操作の実行前に、PIN (個人識別番号) を使用してログインする必要があります。ログインが必要となる操作でもっとも多いのはトークン上の鍵を扱う操作です。Java アプリケーションではそのような操作で、最初にキーストアをロードするのが一般的です。java.security.KeyStore クラス経由でキーストアとして PKCS#11 トークンにアクセスするときは、load メソッドに対してパスワード入力パラメータで PIN を指定できます。これは、J2SE 5.0 より前でアプリケーションがキーストアを初期化する方法に似ています。PIN は、トークンにログインするために、Sun PKCS#11 プロバイダが使用します。次に例を示します。

char[] pin = ...; 
KeyStore ks = KeyStore.getInstance("PKCS11");
ks.load(null, pin); 

この例は、静的なキーストアとして PKCS#11 トークンを扱うアプリケーションに適しています。抜き差しされるスマートカードのように PKCS#11 トークンをより動的に利用したいアプリケーションの場合は、新しい KeyStore.Builder クラスを使用できます。コールバックハンドラで PKCS#11 キーストアのビルダーを初期化する方法を次の例に示します。

KeyStore.Builder builder = new KeyStore.Builder("PKCS11");
builder.setCallbackHandler(new MyGuiCallbackHandler());
Sun PKCS#11 プロバイダの場合、コールバックハンドラは PasswordCallback を満たすことができなければなりません。 PasswordCallback は、ユーザーに PIN を要求する場合に使用されます。アプリケーションがキーストアにアクセスしなければならない場合は、ビルダーを以下のように使用します。
KeyStore ks = builder.getKeyStore();
Key key = ks.get(alias, null);
ビルダーは、先に構成されたコールバックハンドラで使用するパスワードを必要に応じてユーザーに求めます。ビルダーがパスワードを要求するのは、初回のアクセス時のみです。アプリケーションのユーザーが同じスマートカードを使用し続ける場合は、もう一度パスワードを要求されることはありません。ユーザーがスマートカードを抜いて、別のスマートカードを差した場合は、新しいカードに対するパスワードを要求されます。

PKCS#11 トークンによっては、キー関連でない操作でもトークンログインを必要とする場合があります。そのような操作を使用するアプリケーションでは、新しく導入された java.security.AuthProvider クラスを使用できます。AuthProvider クラスは、java.security.Provider を拡張し、プロバイダでログインおよびログアウト操作を行ったり、プロバイダが使用するコールバックハンドラを設定したりするためのメソッドを定義しています。Sun PKCS#11 プロバイダの場合、コールバックハンドラは PasswordCallback を満たすことができなければなりません。 PasswordCallback は、ユーザーに PIN を確認する場合に使用されます。

アプリケーションで AuthProvider を使用してトークンにログインする方法を、次の例に示します。

AuthProvider aprov = Security.getProvider("SunPKCS11");
aprov.login(subject, new MyGuiCallbackHandler());

3.2 トークン鍵

Java の Key オブジェクトには、実際の鍵データが含まれる場合も含まれない場合もあります。

アプリケーションおよびプロバイダでは、各種の Key オブジェクトを表すのに適切なインタフェースを使用する必要があります。ソフトウェア Key オブジェクト (または実際の鍵データにアクセスできる任意の Key オブジェクト) では、java.security.interfaces および javax.crypto.interfaces パッケージにインタフェース (DSAPrivateKey など) を実装する必要があります。抽出不可能なトークン鍵を表す Key オブジェクトでは、java.security および javax.crypto パッケージに、関連する総称インタフェース (PrivateKeyPublicKey、または SecretKey) だけを実装する必要があります。鍵アルゴリズムの特定は、Key.getAlgorithm() メソッドを使用して実行されます。

アプリケーションでは、抽出不可能なトークン鍵の Key オブジェクトは、そのトークンに関連付けされたプロバイダだけが使用できることに注意しなければなりません。

3.3 プロバイダの遅延選択

J2SE 5.0 より前では、Cipher.getInstance("DES") などの Java 暗号化 getInstance() メソッドは、要求されたアルゴリズムを実装している最初のプロバイダからの実装を返していました。これは、ソフトウェア Key オブジェクトだけを使用できるプロバイダで、抽出不可能なトークン鍵の Key オブジェクトをアプリケーションが使用しようとする場合に問題がありました。このような場合、プロバイダは InvalidKeyException をスローします。これは、CipherKeyAgreementMacSignature の各クラスの問題です。

J2SE 5.0 では、関連する初期化メソッドが呼び出されるまでプロバイダの選択を遅らせることで、この問題に対応しています。初期化メソッドでは Key オブジェクトを受け入れ、指定した Key オブジェクトを受け入れられるプロバイダをその時点で判断できます。これにより、選択したプロバイダで、指定した Key オブジェクトを使用できることが保証されます。次に、影響のある初期化メソッドを示します。

また、アプリケーションが初期化メソッドを複数回呼び出すと (毎回異なる鍵を使用するなど)、その鍵に適切なプロバイダが毎回選択されます。つまり、初期化の呼び出しごとに異なるプロバイダが選択される可能性があります。

このプロバイダ遅延選択は、アプリケーションからは認識されませんが、CipherKeyAgreementMac、および SignaturegetProvider() メソッドの動作に影響します。getProvider() が初期化操作の発生する前 (そのためプロバイダ選択が起こる前) に呼び出された場合は、要求されるアルゴリズムがサポートされている最初のプロバイダが返されます。このプロバイダは、初期化メソッドが呼び出されたあとに選択されたプロバイダと同じにならない場合があります。getProvider() が初期化操作のあとに呼び出された場合は、実際に選択されたプロバイダが返されます。アプリケーションでは関連する初期化メソッドを呼び出したあとにのみ、getProvider() を呼び出すようにしてください。

getProvider() だけでなく、以下のメソッドにも同様の影響があります。

3.4 JAAS KeyStoreLoginModule

Java SE には、JAAS キーストアログインモジュールである KeyStoreLoginModule が付属しています。このモジュールを使用すると、アプリケーションで特定のキーストア内の ID を使用して認証を行うことができます。アプリケーションは認証後に、自身の主体および資格の情報 (証明書および非公開鍵) をキーストアから取得します。このログインモジュールを使用し、PKCS#11 トークンをキーストアとして使用するように構成すると、アプリケーションはこれらの情報を PKCS#11 トークンから取得できるようになります。

以下のオプションを使用して KeyStoreLoginModule を構成すると、PKCS#11 トークンをキーストアとして使用できます。

ここで「some_pin_url」は PIN の場所です。keyStorePasswordURL オプションを省略すると、ログインモジュールは PIN をアプリケーションのコールバックハンドラから取得し、PasswordCallback でその PIN を指定します。PKCS#11 トークンをキーストアとして使用する構成ファイルの例を次に示します。
other {
    com.sun.security.auth.module.KeyStoreLoginModule required
        keyStoreURL="NONE"
  keyStoreType="PKCS11"
  keyStorePasswordURL=file:/home/joe/scpin;
};

複数の Sun PKCS#11 プロバイダを動的に構成した場合、または java.security セキュリティープロパティーファイル内で構成した場合は、keyStoreProvider オプションを使用して、特定のプロバイダインスタンスを対象にします。このオプションの引数は、プロバイダの名前です。Sun PKCS#11 プロバイダの場合、プロバイダ名は SunPKCS11-TokenName という形式になります。 ここで「TokenName」は、プロバイダインスタンスが構成された名前の接尾辞です。 詳細は構成属性の表を参照してください。たとえば、以下の構成ファイルでは、PKCS#11 プロバイダインスタンスに名前接尾辞 SmartCard で名前を付けています。

other {
    com.sun.security.auth.module.KeyStoreLoginModule required
        keyStoreURL="NONE"
  keyStoreType="PKCS11"
  keyStorePasswordURL=file:/home/joe/scpin
  keyStoreProvider="SunPKCS11-SmartCard";
};

protected authentication path を介してのログインをサポートしている PKCS#11 トークンもあります。たとえばスマートカードには、PIN を入力するための専用の PIN パッドがある場合があります。生体測定機器にも、認証情報を取得する専用の方法が用意されています。PKCS#11 トークンに保護された認証パスがある場合は、protected=true オプションを使用し、keyStorePasswordURL オプションは省略します。そのようなトークン用の構成ファイルの例を次に示します。

other {
    com.sun.security.auth.module.KeyStoreLoginModule required
        keyStoreURL="NONE"
  keyStoreType="PKCS11"
  protected=true;
};

3.5 JSSE キーストアおよびトラストストアとしてのトークン

PKCS#11 トークンを JSSE キーストアまたはトラストストアとして使用できるように、JSSE アプリケーションでは先に説明した API を使用して KeyStore をインスタンス化します。 この KeyStore は、PKCS#11 トークンによって戻され、キーマネージャーおよびトラストマネージャーに渡されます。こうして JSSE アプリケーションでは、トークン上のキーにアクセスできるようになります。

JSSE では、システムプロパティー経由でキーストアおよびトラストストアを使用するように構成できます (『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」)。

4.0 ツール

J2SE 5.0 では、セキュリティーツールが更新され、新しい Sun PKCS#11 プロバイダを使用した操作がサポートされました。変更内容を次に説明します。

4.1 KeyTool および JarSigner

Sun PKCS#11 プロバイダが Java ランタイムの $JAVA_HOME/lib/security ディレクトリ内にある java.security セキュリティープロパティーファイルで構成されている場合、以下のオプションを使用することで、PKCS#11 トークンの操作に keytool および jarsigner を使用できます。

構成済み PKCS#11 トークンの内容をリストするコマンドの例を次に示します。
keytool -keystore NONE -storetype PKCS11 -list
PIN は -storepass オプションで指定できます。指定されない場合、keytool および jarsigner では、トークン PIN を要求します。トークンに保護された認証パス (専用の PIN パッドや生体読み取り機など) がある場合、-protected オプションを指定する必要がありますが、パスワードオプションを指定する必要はありません。

複数の Sun PKCS#11 プロバイダを java.security セキュリティープロパティーファイル内で構成した場合は、-providerName オプションを使用して、特定のプロバイダインスタンスを対象にします。このオプションの引数は、プロバイダの名前です。

Sun PKCS#11 プロバイダの場合、providerNameSunPKCS11-TokenName という形式になります。 ここで「TokenName」は、プロバイダインスタンスが構成された名前の接尾辞です。 詳細は構成属性の表を参照してください。たとえば、以下のコマンドでは、名前接尾辞 SmartCard の PKCS#11 キーストアプロバイダインスタンスの内容をリストします。
keytool -keystore NONE -storetype PKCS11 \
        -providerName SunPKCS11-SmartCard \
        -list

Sun PKCS#11 プロバイダを java.security セキュリティープロパティーファイル内で構成していない場合は、以下のオプションを使用して、プロバイダを動的にインストールするように keytool および jarsigner を設定します。

ここで「ConfigFilePath」は、トークン構成ファイルのパスです。Sun PKCS#11 プロバイダが java.security ファイルで構成されていないときに PKCS#11 キーストアをリストするコマンドを以下の例に示します。
keytool -keystore NONE -storetype PKCS11 \
        -providerClass sun.security.pkcs11.SunPKCS11 \
        -providerArg /foo/bar/token.config \
        -list

4.2 Policy ツール

J2SE 5.0 より前では、デフォルトの Policy の実装における keystore エントリは以下の構文を使用していました。

keystore "some_keystore_url", "keystore_type";
この構文は、PKCS#11 キーストアにアクセスするには不十分でした。 そのようなアクセスでは通常は PIN が必要となり、また PKCS#11 プロバイダインスタンスが複数ある場合があったためです。これらの要件を満たすために、keystore エントリの構文は J2SE 5.0 で以下のように更新されました。
keystore "some_keystore_url", "keystore_type", "keystore_provider";
keystorePasswordURL "some_password_url";
ここで「keystore_provider」はキーストアプロバイダ名 (SunPKCS11-SmartCard など)、「some_password_url」は、トークン PIN の場所を指す URL です。keystore_provider」と「keystorePasswordURL」の行はどちらも省略可能です。keystore_provider」が指定されていない場合、特定のキーストアタイプをサポートする最初の構成済みプロバイダが使用されます。keystorePasswordURL」の行が指定されていない場合、パスワードは使用されません。

PKCS#11 トークンのキーストア Policy エントリの例を次に示します。

keystore "NONE", "PKCS11", "SunPKCS11-SmartCard";
keystorePasswordURL "file:/foo/bar/passwordFile";

5.0 プロバイダの開発

J2SE 5.0 では、java.security.Provider クラスに新しい機能が導入され、プロバイダ実装で PKCS#11 トークンや暗号化サービス全般を簡単にサポートできるようになりました。これらの新機能を次に説明します。

新しい機能を例示した簡単なプロバイダについては、付録 C を参照してください。

5.1 プロバイダサービス

前述のプロバイダのマニュアルで説明しているとおり、J2SE 5.0 より前では、サポートされているサービスを記述する java.util.Property エントリを作成するためにプロバイダが必要でした。プロバイダによるサービス実装ごとに、サービスの型 (CipherSignature など)、ピリオド、およびサービスが適用されるアルゴリズム名で構成される名前のプロパティーが必要です。プロパティーの値には、サービスを実装するクラスの完全修飾名を指定する必要があります。値 com.sun.crypto.provider.DHKeyAgreement を持つように KeyAgreement.DiffieHellman プロパティーを設定するプロバイダの例を次に示します。

put("KeyAgreement.DiffieHellman", 
      "com.sun.crypto.provider.DHKeyAgreement")

J2SE 5.0 では、public static の入れ子のクラス Provider.Service が新たに導入されました。このクラスを使用すると、プロバイダサービスのプロパティー (型、属性、アルゴリズム名、アルゴリズムの別名など) をカプセル化しやすくなります。プロバイダは Provider.putService() メソッドを呼び出すことで、Provider.Service オブジェクトをインスタンス化して登録します。これは、J2SE 5.0 より前で行われていた、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 つ目として、プロバイダでパラメータの妥当性をテストすることができます。次に、これらの特徴について説明します。

5.1.1 エンジンクラスのインスタンス化

J2SE 5.0 より前では、Java 暗号化フレームワークが特定のサービスのプロバイダプロパティーを検索し、そのプロパティーで登録されたエンジンクラスを直接インスタンス化していました。J2SE 5.0 では、デフォルトで同じ動作をしますが、プロバイダはこの動作をオーバーライドし、要求されたサービス自身のエンジンクラスをインスタンス化できます。

デフォルトの動作をオーバーライドするときは、プロバイダはカスタムな動作を追加するように Provider.Service.newInstance() メソッドをオーバーライドします。たとえばプロバイダはカスタムのコンストラクタを呼び出したり、プロバイダの外部からはアクセスできない (プロバイダのみが知る) 情報を使用して初期化を実行したりすることができます。

5.1.2 パラメータのサポート

Java 暗号化フレームワークでは、プロバイダのサービス実装がアプリケーション固有のパラメータを使用できるかどうかを判断するために、すばやいチェックを試みます。このチェックを実行するために、フレームワークでは Provider.Service.supportsParameter() を呼び出します。

J2SE 5.0 では、フレームワークはプロバイダの遅延選択中にこのすばやいチェックを利用します。アプリケーションが初期化メソッドを呼び出して Key オブジェクトを渡すと、フレームワークでは Service.supportsParameter() メソッドを呼び出すことで、配下のプロバイダに対してその Key オブジェクトをサポートしているかどうかを確認します。supportsParameter()false を返すと、フレームワークはただちにそのプロバイダを対象から取り除きます。supportsParameter()true を返すと、フレームワークは Key オブジェクトをそのプロバイダの初期化エンジンクラス実装に渡します。ソフトウェア Key オブジェクトを必要とするプロバイダでは、ソフトウェア以外の鍵を渡されたときに false を返すように、このメソッドをオーバーライドする必要があります。同様に、抽出不可能な鍵が含まれた PKCS#11 トークンのプロバイダでは、このプロバイダが作成した、つまりそれぞれのトークン上の Key に対応する Key オブジェクトに対して true だけを返すようにしなければなりません。

supportsParameter() のデフォルト実装では true を返します。これにより、既存のプロバイダを変更しないで動作させることができます。しかし、この緩やかなデフォルト実装のために、フレームワークでは、初期化エンジンクラス実装内部の Key オブジェクトを拒否するプロバイダが例外をスローしたときに、その例外をキャッチできるようにしておく必要があります。フレームワークでは、これらのケースを supportsParameter()false を返すときと同じように扱います。

付録 A:Sun PKCS#11 プロバイダでサポートされるアルゴリズム

以下の表では、Sun PKCS#11 プロバイダでサポートされる Java アルゴリズムと、サポートに必要となる対応する PKCS#11 機構についてリストします。複数の機構がリストされている場合は、設定されている順番に記載しており、いずれか 1 つを指定すれば十分です。disabledMechanisms および enabledMechanisms 構成指示を使用すると、機構を無視するように SunPKCS11 に指示できます。

Elliptic Curve 機構では、SunPKCS11 は、パラメータのエンコーディングとして namedCurve 選択を使用する鍵のみを使用します。また、圧縮されていない形式のみが許可されます。Sun PKCS#11 プロバイダでは、標準的な名前が付けられたすべてのドメインパラメータをトークンがサポートしていることが前提となります。

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.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.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
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.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-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 常に使用可能

付録 B:Sun PKCS#11 プロバイダの KeyStore 要件

ここでは、Sun PKCS# プロバイダの KeyStore を、基本となるネイティブの PKCS#11 ライブラリへ実装する場合の要件を説明します。より多くの既存の PKCS#11 ライブラリとの相互運用性を実現するため、将来のリリースで変更が加えられる可能性があります。

読み取り専用アクセス

PKCS#11 トークンに保存された既存のオブジェクトを KeyStore エントリにマッピングするため、Sun PKCS#11 プロバイダの KeyStore 実装は次の操作を実行します。

  1. C_FindObjects[Init|Final] を呼び出して、トークン上のすべての非公開鍵オブジェクトを検索します。検索テンプレートには、次の属性があります。

    • CKA_TOKEN = true
    • CKA_CLASS = CKO_PRIVATE_KEY

  2. C_FindObjects[Init|Final] を呼び出して、トークン上のすべての証明書オブジェクトを検索します。検索テンプレートには、次の属性があります。

    • CKA_TOKEN = true
    • CKA_CLASS = CKO_CERTIFICATE

  3. それぞれの CKA_ID 属性を取得することで、各非公開鍵オブジェクトを対応する証明書と一致させます。一致するペアは、同じ一意の CKA_ID を共有する必要があります。

    一致するペアごとに、発行者 -> 被認証者パスに従って証明書チェーンが作成されます。エンドエンティティー証明書から、次の属性を持つ検索テンプレートを使用した C_FindObjects[Init|Final] が呼び出されます。

    • CKA_TOKEN = true
    • CKA_CLASS = CKO_CERTIFICATE
    • CKA_SUBJECT = [DN of certificate issuer]

    この検索は、発行者の証明書が見つからないか、自己署名証明書が見つかるまで継続します。複数の証明書が見つかった場合は、最初の証明書が使用されます。

    非公開鍵と証明書が一致して証明書チェーンが作成されると、エンドエンティティー証明書の 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」) から作成されます。

  4. (エンドエンティティー証明書として) 非公開鍵エントリの一部ではない各証明書は、信頼できるかどうかチェックされます。CKA_TRUSTED 属性が true の場合、CKA_LABEL 値を KeyStore の別名とする、KeyStore の信頼できる証明書エントリが作成されます。証明書に CKA_LABEL がない場合、または複数の証明書が同じ CKA_LABEL を共有する場合、別名は上述のように作成されます。

    CKA_TRUSTED 属性がサポートされていない場合は、信頼できる証明書エントリは作成されません。

  5. 非公開鍵エントリまたは信頼できる証明書エントリの一部ではない非公開鍵または証明書オブジェクトは、すべて無視されます。

  6. C_FindObjects[Init|Final] を呼び出して、トークン上のすべての秘密鍵オブジェクトを検索します。検索テンプレートには、次の属性があります。

    • CKA_TOKEN = true
    • CKA_CLASS = CKO_SECRET_KEY

    各秘密鍵オブジェクトに対して、CKA_LABEL 値を KeyStore の別名とする KeyStore 秘密鍵エントリが作成されます。各秘密鍵オブジェクトには、固有の CKA_LABEL が必要です。

書き込みアクセス

PKCS#11 トークンに KeyStore エントリに対する新しい KeyStore エントリを作成するため、Sun PKCS#11 プロバイダの KeyStore 実装は次の操作を実行します。

  1. KeyStore.setEntry などを使用して KeyStore エントリを作成するには、CKA_TOKEN=true として C_CreateObject を呼び出し、エントリの内容それぞれに対してトークンオブジェクトを作成します。

    非公開鍵オブジェクトは、CKA_PRIVATE=true で保存されます。KeyStore の別名 (UTF8 エンコード) は、非公開鍵と対応するエンドエンティティー証明書の両方で CKA_ID として設定されます。KeyStore の別名では、エンドエンティティー証明書オブジェクトに CKA_LABEL が設定されます。

    非公開鍵エントリのチェーン内の各証明書も保存されます。CKA_LABEL は、CA 証明書には設定されません。CA 証明書がトークン内にある場合、複製は保存されません。

    秘密鍵オブジェクトは、CKA_PRIVATE=true で保存されます。KeyStore の別名は、CKA_LABEL として設定されます。

  2. セッションオブジェクトをトークンオブジェクトに変換しようとする場合 (KeyStore.setEntry が呼び出され、指定されたエントリの非公開鍵オブジェクトがセッションオブジェクトである場合など)、C_CopyObject が CKA_TOKEN=true として呼び出されます。

  3. トークン内に複数の証明書が見つかり、同じ CKA_LABEL を共有する場合は、トークンへの書き込み機能は無効になります。

  4. PKCS#11 仕様では一般のアプリケーションで CKA_TRUSTED=true と設定することが許可されないため (トークン初期化アプリケーションのみが可能)、信頼できる証明書エントリを作成できません。

その他

上記の検索のほか、Sun PKCS#11 プロバイダの KeyStore 実装で次の検索を使用して内部関数を実行できます。特に、次の属性テンプレートのどれを使用した場合でも、C_FindObjects[Init|Final] を呼び出すことができます。

付録 C: プロバイダの例

package com.foo;

import java.io.*;
import java.lang.reflect.*;
import java.security.*;
import javax.crypto.*;

/**
 * Example provider that demonstrates some of the new API features.
 *
 *  . implement multiple different algorithms in a single class.
 *    Previously each algorithm needed to be implemented in a separate class
 *    (e.g. one for MD5, one for SHA-1, 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 (MD5, SHA1, DES, and AES)
        putService(new MyService
            (this, "MessageDigest", "MD5", "com.foo.ExampleProvider$MyMessageDigest"));
        putService(new MyService
            (this, "MessageDigest", "SHA1", "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);
        }
    }
}