Java 暗号化アーキテクチャー
(JCA) リファレンスガイド

はじめに

Java プラットフォームは、言語の安全性、暗号化、公開鍵インフラストラクチャー、認証、安全な通信、アクセス制御など、セキュリティーに重点を置いています。

JCA はプラットフォームの主要な部分であり、「プロバイダ」アーキテクチャー、およびデジタル署名、メッセージダイジェスト (ハッシュ)、証明書と証明書検証、暗号化 (対称/非対称ブロック/ストリーム暗号)、鍵の生成と管理、安全な乱数生成などのための一連の API が含まれています。これらの API によって、開発者はアプリケーションコードにセキュリティーを簡単に統合できます。アーキテクチャーは、次の方針に基づいて設計されました。

JDK で使用できるその他の暗号化通信ライブラリは JCA プロバイダアーキテクチャーを使用しますが、これらについては別途説明します。Java Secure Socket Extension (JSSE) は、Secure Socket Layer (SSL) および Transport Layer Security (TLS) の実装へのアクセスを提供します。通信しているアプリケーション間で安全にメッセージを交換するために、Java Generic Security Services (JGSS) (Kerberos による) API、および Simple Authentication and Security Layer (SASL) を使用することもできます。

用語に関する注記


警告: JCA を使用すると、アプリケーションへのセキュリティー機能の組み込みが簡単になります。ただし、このドキュメントでは、API の説明に必要な概念の基本的な紹介以外、セキュリティー/暗号化の理論については説明しません。このドキュメントでは、特定のアルゴリズムの長所と短所やプロトコルの設計についても説明しません。暗号化は高度なトピックであり、これらのツールを最適に使用するには、信頼できる、可能なかぎり最近のリファレンスを参照するようにしてください。

実行している内容とその理由を常に理解している必要があります。ランダムコードを単純にコピーし、それが用途を満たす筋書きを完全に解決してくれるとは考えないでください。間違ったツールまたはアルゴリズムが選択されたため、セキュリティーまたはパフォーマンスに重大な問題があるアプリケーションが数多く配備されています。


設計方針

JCA は、次の方針に基づいて設計されました。

実装の独立性とアルゴリズムの独立性は相補関係にあります。そのため、デジタル署名やメッセージダイジェストなどの暗号化サービスを、実装の詳細やこれらの概念の基になるアルゴリズムを意識しないで使用できます。完全なアルゴリズムの独立性は不可能ですが、JCA は標準化された、アルゴリズム固有の API を提供します。実装の独立性が望ましくない場合、JCA では、開発者が特定の実装を指定できます。

アルゴリズムの独立性は、暗号化「エンジン」(サービス) の型を定義し、これらの暗号化エンジンの機能を提供するクラスを定義することにより実現しています。これらのクラスは、エンジンクラスと呼ばれ、例としては、MessageDigestSignatureKeyFactoryKeyPairGenerator、および Cipher クラスがあります。

実装の独立性は、「プロバイダ」ベースのアーキテクチャーを使って実現されます。暗号化サービスプロバイダ (CSP) (このドキュメントでは「プロバイダ」とも呼ばれる) とは、デジタル署名アルゴリズム、メッセージダイジェストアルゴリズム、鍵の変換サービスなどの 1 つ以上の暗号化を実装するパッケージやパッケージセットを意味します。プログラムは単に、特定のサービス (たとえば DSA 署名アルゴリズム) を実行する特定の型のオブジェクト (たとえば Signature オブジェクト) を要求するだけで、インストールされているプロバイダの 1 つから実装を獲得できます。必要に応じて、プログラムは特定プロバイダから実装を要求することもできます。たとえば、より高速で安全性の高いバージョンが利用できるときは、プロバイダをアプリケーションに対して透過的に更新できます。

実装の相互操作性とは、さまざまな実装が互いに機能でき、互いの鍵を使ったり、互いの署名を検証できることを意味します。つまり、1 つのプロバイダが生成した同じアルゴリズムや鍵を別のプロバイダが使えたり、あるプロバイダが生成した署名を別のプロバイダが検証できるということです。

アルゴリズムの拡張性とは、サポートされるエンジンクラスの 1 つに当てはまる新規アルゴリズムを容易に追加できることを意味します。

アーキテクチャー

暗号化サービスプロバイダ

java.security.Provider は、すべてのセキュリティープロバイダの基底クラスです。各 CSP はこのクラスのインスタンスを含みます。インスタンスはプロバイダの名前を含み、実装するセキュリティーサービス/アルゴリズムをすべて一覧表示します。特定のアルゴリズムのインスタンスが必要な場合、JCA フレームワークはプロバイダのデータベースに問い合わせ、一致するものが見つかった場合はそのインスタンスが作成されます。

プロバイダは、公開されている暗号化アルゴリズムの固定実装を提供するパッケージ (または、パッケージのセット) を含みます。各 JDK のインストールでは、1 つ以上のプロバイダがインストールされ、デフォルトで構成されます。追加プロバイダは静的または動的に追加できます (Provider クラスと Security クラスを参照)。クライアントは実行環境を構成し、プロバイダの優先順位を指定できます。優先順位とは、特定プロバイダの指定がない場合に、要求されたサービスに関して検索されるプロバイダの順位です。

JCA を使用するには、アプリケーションは単に特定の型のオブジェクト (たとえば MessageDigest) と特定のアルゴリズムまたはサービス (たとえば「MD5」アルゴリズム) を要求し、インストールされているプロバイダのうちの 1 つから実装を獲得します。あるいは、特定プロバイダのオブジェクトを要求することもできます。各プロバイダは、それぞれの参照名を持ちます。

    md = MessageDigest.getInstance("MD5");
    md = MessageDigest.getInstance("MD5", "ProviderC");

次の図は、「MD5」メッセージダイジェスト実装の要求を示しています。この図は、さまざまなメッセージダイジェストアルゴリズム (「SHA-1」、「MD5」、「SHA-256」、および「SHA-512」) を実装する 3 つの異なるプロバイダを示しています。プロバイダは、左から右への優先順位 (1 から 3) で並べられています。最初の図では、アプリケーションはプロバイダ名を指定せずに MD5 アルゴリズム実装を要求しています。プロバイダが優先順位に従って検索され、その特定のアルゴリズムを提供する最初のプロバイダである ProviderB から実装が返されます。2 番目の図では、アプリケーションは特定のプロバイダである ProviderC から MD5 アルゴリズム実装を要求しています。この場合は、優先順位の高いプロバイダである ProviderB も MD5 実装を提供しますが、ProviderC から実装が返されます。

図 1: MD5 メッセージダイジェスト実装

図 1 の説明: MD5 メッセージダイジェスト実装

JDK での暗号化実装は、さまざまなプロバイダ (SunSunJSSESunJCESunRsaSign) によって配布されます。これは主に歴史的な理由によりますが、それらが提供する機能およびアルゴリズムの型によるところもあります。その他の Java 実行環境は、必ずしもこれらの Sun プロバイダを含みません。したがって、特定のプロバイダが使用可能になることがわかっている場合を除き、アプリケーションはプロバイダ固有の実装を要求しないようにする必要があります。

JCA には、インストールされているプロバイダおよびそれらがサポートするサービスについて、ユーザーが問い合わせることができる API セットがあります。

このアーキテクチャーを使用すると、エンドユーザーがプロバイダを追加することも簡単になります。多くのサードパーティーのプロバイダ実装がすでに使用可能です。プロバイダの記述、インストール、および登録の方法の詳細は、Provider クラスを参照してください。

プロバイダの実際の実装方法

前述のように、アルゴリズムの独立性は、サービスの型にアクセスするためにすべてのアプリケーションが使用する汎用で高レベルの API (Application Programming Interface) を定義することによって実現されます。実装の独立性は、すべてのプロバイダ実装を、明確に定義されたインタフェースに準拠させることによって実現されます。したがって、エンジンクラスのインスタンスは、同じメソッド署名を持つ実装クラスに基づいています。アプリケーション呼び出しはエンジンクラスによって渡され、基盤となる実装に伝送されます。実装は要求を処理し、正しい結果を返します。

各エンジンクラス内のアプリケーション API メソッドは、対応する SPI (Service Provider Interface) を実装するクラスによってプロバイダの実装に渡されます。つまり、各エンジンクラスに対応する抽象 SPI クラスが存在し、この抽象 SPI クラスによって各暗号化サービスプロバイダのアルゴリズムが実装する必要があるメソッドが定義されます。SPI クラスの名前は、対応するエンジンクラス名のあとに Spi を追加した名前になります。たとえば Signature エンジンクラスは、デジタル署名アルゴリズムの機能へのアクセスを提供します。実際のプロバイダ実装は、SignatureSpi のサブクラス内で提供されます。アプリケーションは各エンジンクラスの API メソッドを呼び出し、API メソッドは実際の実装内の SPI メソッドを呼び出します。

各 SPI クラスは、抽象クラスです。指定したアルゴリズムに対する特定の型のサービスの実装を提供するには、プロバイダは、対応する SPI クラスをサブクラス化して、すべての抽象メソッドの実装を提供する必要があります。

API の各エンジンクラスについては、エンジンクラス特有の getInstance() ファクトリメソッドを呼び出すことによって実装インスタンスが要求され、インスタンスが生成されます。ファクトリメソッドは、クラスのインスタンスを返す static メソッドです。エンジンクラスは、前述のフレームワークプロバイダ選択メカニズムを使用して実際の基盤となる実装 (SPI) を取得し、実際のエンジンオブジェクトを作成します。エンジンクラスの各インスタンスは、SPI オブジェクトと呼ばれる対応する SPI クラスのインスタンスを、private フィールドとしてカプセル化します。API オブジェクトのすべての API メソッドは、final として宣言し、それらを実装することによって、カプセル化される SPI オブジェクトの対応する SPI メソッドが呼び出されます。

これを明確にするために、次のコードと図を確認してください。

    import javax.crypto.*;

    Cipher c = Cipher.getInstance("AES");
    c.init(ENCRYPT_MODE, key);
図 2: アプリケーションが AES Cipher インスタンスを取得する方法の例

図 2 の説明:アプリケーションが「AES」Cipher インスタンスを取得する方法の例

ここで、アプリケーションは「AES」javax.crypto.Cipher インスタンスを必要とし、使用するプロバイダは問題となりません。アプリケーションは Cipher エンジンクラスの getInstance() ファクトリメソッドを呼び出し、ファクトリメソッドは JCA フレームワークに「AES」をサポートする最初のプロバイダインスタンスを見つけることを要求します。フレームワークはインストールされている各プロバイダに問い合わせ、Provider クラスのプロバイダのインスタンスを取得します。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 の実装は、米国 Sun Microsystems, Inc によって提供されます。これは、「jks」という名前の独自のキーストアの型 (形式) を使用するもので、キーストアをファイルとして実装しています。その他のキーストア形式も使用できます。「jks」よりも大幅に強力な暗号化を備えた独自の代替キーストア形式である「jceks」や RSA PKCS12 Personal Information Exchange Syntax Standard に基づいた「pkcs12」などです。

アプリケーションは、前述の同じプロバイダメカニズムを使用して、異なるプロバイダから異なるキーストア実装を選択できます。

詳細は、「鍵管理」のセクションを参照してください。

JCA の概念

このセクションでは、主要な JCA API について説明します。

エンジンクラスとアルゴリズム

エンジンクラスは、特定の暗号化アルゴリズムまたはプロバイダに依存しない、特定の型の暗号化サービスへのインタフェースを提供します。エンジンは、次のいずれかを提供します。

次のエンジンクラスが使用可能です。

注: ジェネレータ (generator) は最新の内容でオブジェクトを作成しますが、ファクトリ (factory) は既存の構成要素 (エンコードなど) からオブジェクトを作成します。

コアクラスとインタフェース

このセクションでは、JCA で提供するコアクラスとインタフェースについて説明します。


注: CertPathBuilderCertPathValidator、および CertStore エンジンクラスの詳細は、「Java PKI プログラマーズガイド」を参照してください。


このガイドでは、もっとも有用な高レベルのクラスについて最初に説明し (ProviderSecuritySecureRandomMessageDigestSignatureCipher、および Mac)、次にさまざまなサポートクラスについて説明します。現時点では、鍵 (公開、非公開、および秘密) はさまざまな JCA クラスによって生成および表現され、高レベルのクラスによってその操作の一部として使用されるという説明だけで十分です。

このセクションでは、各クラスおよびインタフェースのメインメソッドの署名を示します。該当するのセクションには、これらのクラス (MessageDigestSignatureKeyPairGeneratorSecureRandomKeyFactory、および鍵仕様クラス) の例があります。

関連する Security API パッケージの完全なリファレンスドキュメントは、次のパッケージのサマリーにあります。

Provider クラス

「暗号化サービスプロバイダ」(このドキュメントでは「プロバイダ」とも呼ばれる) とは、JDK Security API の暗号化機能のサブセットの固定実装を提供するパッケージまたはパッケージセットです。Provider クラスは、このようなパッケージやパッケージセットへのインタフェースです。プロバイダ名、バージョン番号、その他の情報にアクセスするためのメソッドを備えています。Provider クラスを使用すると、暗号化サービスの実装を登録するだけでなく、その他のセキュリティーサービスの実装を登録することもできます。その他のセキュリティーサービスは、JDK Security API またはその拡張機能の 1 つの一部として定義されている場合があります。

暗号化サービスの実装を提供するには、エンティティー (開発グループなど) は実装コードを作成し、Provider クラスのサブクラスを生成します。Provider サブクラスのコンストラクタは、プロバイダが実装したサービスを検索するために JDK Security API が使用する各種プロパティーの値を設定します。つまり、サブクラスは、サービスを実装するクラス名を指定します。

図 3: プロバイダサブクラスの例

図 3 の説明:プロバイダサブクラスの例

プロバイダパッケージが実装できるサービスには、さまざまな種類があります。詳細については、「エンジンクラスとアルゴリズム」を参照してください。

実装が違うと、特徴も違ってくる場合があります。ソフトウェアベースのものもあれば、ハードウェアベースのものもあります。プラットフォーム独立のものもあれば、プラットフォーム固有のものもあります。また、レビューや評価用に使えるプロバイダコードもあれば、使えないものもあります。JCA では、エンドユーザーと開発者の双方が独自のニーズを決定できます。

このセクションでは、エンドユーザーのニーズに合った暗号実装のインストール方法、および開発者のニーズに合った実装の要求方法を説明します。

注: プロバイダの実装の詳細は、ガイド「Java 暗号化アーキテクチャー用プロバイダの実装方法」を参照してください。

プロバイダ実装の要求および獲得方法

この API の各エンジンクラスについては、実装インスタンスが要求され、そのエンジンクラスの getInstance メソッドの 1 つを呼び出してインスタンスが生成されます。このときに、希望するアルゴリズムの名前と実装するプロバイダ (または Provider クラス) の名前 (オプション) を指定します。
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/その他) です。たとえば、
    MessageDigest md = MessageDigest.getInstance("MD5");
    KeyAgreement ka = KeyAgreement.getInstance("DH", "SunJCE");
それぞれ、「MD5」MessageDigest および「DH」KeyAgreement オブジェクトのインスタンスを返します。

付録 A」には、Java 環境での使用について標準化されている名前のリストがあります。一部のプロバイダには、同じアルゴリズムを参照する別名がある場合もあります。たとえば、「SHA-1」アルゴリズムが「SHA1」として参照される場合があります。必ずしもすべてのプロバイダが同じようにアルゴリズム名の別名を持つわけではないため、アプリケーションは別名ではなく標準名を使用する必要があります。

注: アルゴリズム名の大文字と小文字は区別されません。たとえば、次の呼び出しはすべて等価とみなされます。

MessageDigest.getInstance("SHA-1")
MessageDigest.getInstance("sha-1")
MessageDigest.getInstance("sHa-1")

プロバイダを指定しない場合、getInstance は、指定されたアルゴリズムに関連する、要求された暗号化サービスの実装に対応する登録プロバイダを検索します。どの Java 仮想マシン (JVM) でも、プロバイダは指定の「優先順位」でインストールされます。優先順位とは、特定プロバイダの指定がない場合に、プロバイダリストが検索される順位です。たとえば、JVM に、PROVIDER_1PROVIDER_2 という 2 つのプロバイダがインストールされているとします。次のようになっていると仮定します。

ここで、3 つのシナリオについて見てみます。
  1. MD5 実装を検索するとする。どちらのプロバイダもこの実装を供給する。PROVIDER_1 が最高の優先順位を持ち最初に検索されるので、PROVIDER_1 実装が返される。
  2. MD5withRSA 署名アルゴリズムを検索する場合、最初に PROVIDER_1 が検索される。実装が見つからないので次に PROVIDER_2 が検索される。実装が見つかるので、このプロバイダが返される。
  3. SHA1withRSA 署名アルゴリズムを検索する場合を想定する。これを実装するプロバイダはインストールされていないので、NoSuchAlgorithmException がスローされる。

プロバイダ引数を持つ getInstance メソッドは、必要なアルゴリズムを提供するプロバイダを指定したい開発者のためのものです。たとえば、連邦証明書を受け取ったプロバイダ実装を連邦局が使いたいとします。PROVIDER_1 の SHA1withDSA 実装はこの証明書を受け取っていないのに対し、PROVIDER_2 の DSA 実装がこれを受け取っていると仮定します。

連邦局のプログラムは次の呼び出しを実行します。PROVIDER_2 が認証された実装を持つため、これを指定します。

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

この場合、PROVIDER_2 がインストールされていないと、インストールされている別のプロバイダが要求アルゴリズムを実装している場合であっても、NoSuchProviderException がスローされます。

プログラムは、(Security クラスの getProviders メソッドを使用して) インストールされているプロバイダのリストを獲得し、リストから選択することもできます。

注: 汎用アプリケーションは、特定のプロバイダから暗号化サービスを要求しないようにする必要があります。そうでない場合、アプリケーションは特定のプロバイダに結び付けられることになり、そのプロバイダはほかの Java 実装で使用できない可能性があります。特定の要求されたプロバイダよりも優先順位が高い、使用可能な最適化されたプロバイダ (たとえば、PKCS11 によるハードウェアアクセラレータまたは Microsoft の MSCAPI などのネイティブ OS 実装) を利用できない場合もあります。

プロバイダのインストール

暗号化プロバイダを使用するには、最初にインストールし、次に静的または動的に登録する必要があります。さまざまな Sun プロバイダがこのリリースに同梱されており (SUNSunJCESunJSSESunRsaSign など)、すでにインストールおよび登録されています。以降のセクションでは、追加プロバイダのインストールおよび登録方法について説明します。

プロバイダクラスのインストール

プロバイダクラスのインストールには、次の 2 つの方法があります。

  1. 通常の Java classpath

    クラスが格納されている zip または JAR ファイルを、classpath の任意の場所に置く。一部のアルゴリズムの型 (暗号) では、プロバイダは署名された JAR ファイルである必要があります。

  2. インストール/バンドルされた拡張機能

    プロバイダは、標準の拡張ディレクトリに配置された場合、インストール型拡張機能と見なされます。JDK では、次の場所に配置されます。

    • Unix: <java-home>/lib/ext
    • Windows: <java-home>\lib\ext

    ここで、<java-home> は、実行ソフトウェアのインストール先ディレクトリ (JRE (Java Runtime Environment) のトップレベルディレクトリまたは Java JDK ソフトウェアの jre ディレクトリ) を指します。たとえば、JDK 6 を /home/user1/JDK1.6.0 ディレクトリ (Solaris)、または C:\Java\JDK1.6.0 ディレクトリ (Microsoft Windows) にインストールした場合、JAR ファイルを次のディレクトリにインストールする必要があります。

    • Unix: /home/user1/JDK1.6.0/jre/lib/ext
    • Windows: C:\JDK1.6.0\jre\lib\ext

    同様に、JRE 6 を /home/user1/jre1.6.0 ディレクトリ (Solaris)、または C:\jre1.6.0 ディレクトリ (Microsoft Windows) にインストールした場合、JAR ファイルを次のディレクトリにインストールする必要があります。

    • Unix: /home/user1/jre1.6.0/lib/ext
    • Windows: C:\jre1.6.0\lib\ext
    拡張機能の配置方法の詳細は、「拡張機能の配置」を参照してください。

プロバイダの登録

次の手順では、登録済みプロバイダのリストにプロバイダを追加します。プロバイダは、Java アプリケーションを実行する前にセキュリティープロパティー構成ファイルを編集して静的に登録するか、または実行時にメソッドを呼び出して動的に登録できます。破壊行為を行うプロバイダのインストールが実行環境に追加されるのを防ぐために、プロバイダを動的に登録しようとするアプリケーションは適切な実行特権を保持している必要があります。

静的登録

構成ファイルは、次のディレクトリに格納されています。

登録されたプロバイダごとに、このファイルは次の形式の文を保持します。

    security.provider.n=masterClassName

これはプロバイダを宣言し、その優先順位 n を指定します。優先順位とは、特定プロバイダの指定がない場合に、要求されたアルゴリズムに関して検索されるプロバイダの順位です。順位は 1 から始まります。1 が最優先で次に 2、3 ...と続きます。

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 で、このプロバイダを優先順位が第 8 位のプロバイダとして構成するとします。このためには、次の行を java.security ファイルに追加します。

    security.provider.8=com.companyx.provider.ProviderX
動的登録
プロバイダを動的に登録するには、アプリケーションは Security クラスの addProvider または insertProviderAt のどちらかのメソッドを呼び出します。この登録は VM インスタンス間で持続的ではなく、適切な特権を持つ「信頼できる」プログラムでしか実行できません。「セキュリティー」を参照してください。

プロバイダアクセス権の設定

暗号化プロバイダが使用され (つまり、Cipher、KeyAgreement、KeyGenerator、Mac、または SecretKeyFactory の実装を提供するプロバイダ)、プロバイダがインストール型拡張機能ではない場合、JCA を使用するアプレットまたはアプリケーションをセキュリティーマネージャーがインストールされている環境で実行するときはアクセス権を付与する必要がある場合があります。通常、アプレットの実行時にはセキュリティーマネージャーが常にインストールされます。アプリケーションの場合でも、アプリケーション自体のコード内またはコマンド行引数で指定することにより、セキュリティーマネージャーをインストールできます。デフォルトシステムのポリシー構成ファイルは、インストール型拡張機能 (つまり、拡張機能ディレクトリにインストールされている) にすべてのアクセス権を付与するため、インストール型拡張機能にアクセス権を設定する必要はありません。

ベンダーの提供する各プロバイダ用ドキュメントには、必須のアクセス権やそれを付与する方法が記載されています。たとえば、拡張機能がインストール型ではなく、セキュリティーマネージャーがインストールされている場合、プロバイダに次のアクセス権を付与する必要があります。

たとえば、名前が「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 クラスインタフェースは、名前 (現時点では大文字小文字を区別する)、バージョン番号、およびプロバイダとそのサービスの文字列記述を持ちます。次のメソッドを呼び出して、この情報について Provider のインスタンスを照会できます。

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

Security クラス

Security クラスは、インストールされているプロバイダおよびセキュリティーに関するプロパティーを管理します。このクラスに含まれるのは、インスタンスが生成されない static メソッドだけです。プロバイダの追加または削除、および Security プロパティーの設定用メソッドは、信頼できるプログラムによってしか実行できません。現時点での「信頼できるプログラム」は、次のどれかです。

あるコードが、実行しようとしているアクション (プロバイダの追加など) を行うことができるような、信頼できるコードであると判断するには、その特定のアクションを行う適切な権限がそのアプレットに与えられている必要があります。インストールした JDK のポリシー構成ファイルでは、指定されたコードソースからのコードに対し、どのようなアクセス権 (システムリソースへのアクセスの種類) を与えるかを指定します。詳細は、以降の説明および「デフォルトの Policy の実装とポリシーファイルの構文」と「Java セキュリティーアーキテクチャー」を参照してください。

実行されるコードは、常に、特定の「コードソース」から来ると考えられます。コードソースには、そのコードの元の場所 (URL) だけでなく、コードの署名に使用された非公開鍵に対応する任意の公開鍵への参照も含まれています。コードソース内の公開鍵は、ユーザーのキーストアの別名 (記号名) で参照します。

ポリシー構成ファイル内では、コードソースは、コードベース (URL)、および (signedBy で始まる) 別名の 2 つのコンポーネントで表されます。別名は、コードの署名の検証に使う必要のある公開鍵を含んだキーストアのエントリを識別します。

このようなファイル内の各「許可」文は、指定されたコードソースにアクセス権のセットを与え、許可されるアクションを指定します。

次に、ポリシー構成ファイルの例を示します。

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 ファイルからロードされたコードが、プロバイダの追加または削除、あるいはプロバイダのプロパティーの設定ができることを指定します。JAR ファイルの署名は、ユーザーのキーストア内の別名 sysadmin で参照する公開鍵を使って検証できます。

コードソースを指定するコンポーネントは、どちらも (または両方を) 省略可能です。次に、codeBase を省略した構成ファイルの例を示します。

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/ ディレクトリ内に置かれたコードは、プロバイダの追加と削除を行えます。コードに署名は必要ありません。

codeBasesignedBy を両方とも省略した例を次に示します。

grant {
    permission java.security.SecurityPermission "insertProvider.*";
    permission java.security.SecurityPermission "removeProvider.*";
};
この例では、コードソースを指定するコンポーネントがどちらも省略されています。したがって、出所がどこか、署名が付いているか、だれの署名が付いているかに関係なく、どのコードでもプロバイダの追加と削除が行えます。この付与はセキュリティーホールを作り出す可能性があるため、絶対にお勧めしません。信頼されていないコードによってプロバイダがインストールされるため、適切に機能する実装に依存する後続のコードに影響する可能性があります。たとえば、破壊行為を行う Cipher オブジェクトが、受信した機密情報を取り込んで格納する場合があります。

プロバイダの管理

次の表に、Security クラス内のメソッドのうち、インストールされている Provider を照会するために使用できるメソッドと、実行時にプロバイダをインストールまたは削除するためのメソッドの要約を示します。

プロバイダの照会
メソッド 説明
static Provider[] getProviders() インストールされているすべてのプロバイダを含む配列 (つまり、各パッケージプロバイダの Provider サブクラス) を返します。配列内の Provider の順序は、優先順位に従います。
static Provider getProvider
(String providerName)
providerName という Provider を返します。この Provider が見つからないと null を返します。

プロバイダの追加
メソッド 説明
static int addProvider(Provider provider) インストールされた Provider リストの末尾に Provider を追加します。Provider が追加された優先順位を返すか、すでにインストールされているために Provider が追加されなかった場合は -1 を返します。
static int insertProviderAt (Provider provider, int position) 指定された位置に新規の Provider を追加します。あるプロバイダが要求された場所にインストールされた場合は、それまでその場所にあったプロバイダ、および position より後の位置のすべてのプロバイダの順位は、1 つ後になり、一覧の最後尾に向かって 1 つ移動します。Provider が追加された優先順位を返すか、すでにインストールされているために Provider が追加されなかった場合は -1 を返します。

プロバイダの削除
メソッド 説明
static void removeProvider(String name) 指定された名前の Provider を削除します。プロバイダがインストールされていない場合は、何もせず復帰します。指定されたプロバイダが削除されると、そのプロバイダよりもあとの位置にあるすべてのプロバイダの位置が 1 つ前になり、インストールされたプロバイダ一覧の先頭に向かって 1 つ移動します。

注: プロバイダの優先順位を変更する場合は、まず目的のプロバイダを削除してから、希望する優先順位の位置に挿入し直す必要があります。

セキュリティーのプロパティー

Security クラスは、システムに関するセキュリティーのプロパティーリストを保持します。これらのプロパティーは System プロパティーに似ていますが、セキュリティー関連です。これらのプロパティーは、静的にも動的にも設定できます。静的セキュリティープロパティーの例 ("security.provider.i" セキュリティープロパティーによるプロバイダの静的な登録) についてはすでに説明しました。プロパティーを動的に設定する場合、信頼できるプログラムは次のメソッドを使用できます。

static String getProperty(String key)
static void setProperty(String key, String datum)
注:セキュリティープロバイダのリストは、VM の起動中に確立されます。したがって、前述のメソッドは、プロバイダリストを変更する場合に使用してください。

構成ファイルは、次のディレクトリに格納されています。

SecureRandom クラス

SecureRandom クラスは、乱数ジェネレータ (RNG) の機能を提供するエンジンクラスです。強力な暗号化による乱数を生成する点が Random クラスとは異なります。ジェネレータのランダム性が不十分な場合、保護メカニズムが簡単に脅かされることになります。暗号化鍵やアルゴリズムのパラメータの生成など、乱数は暗号化全体で使用されます。

図 4: SecureRandom クラス

図 4 の説明:SecureRandom クラス

SecureRandom オブジェクトの作成

ほかのエンジンクラスと同様に、SecureRandom オブジェクトを取得するには、SecureRandom クラスの getInstance() static ファクトリメソッドの 1 つを呼び出します。

SecureRandom オブジェクトのシードまたは再シード

呼び出し側が、setSeed メソッドの 1 つへの呼び出しで、getInstance メソッドへの呼び出しに従う場合以外は、SecureRandom の実装は、ジェネレータの内部状態自体を完全にランダム化しようとします。

synchronized public void setSeed(byte[] seed)
public void setSeed(long seed)
SecureRandom オブジェクトに一度シードが入れられると、これはオリジナルのシードと同じようにランダムにビットを生成します。

SecureRandom オブジェクトは、setSeed メソッドの 1 つを使って常に再シードされる可能性があります。指定されたシードは、既存のシードと置き換えられるのではなく、既存のシードに追加されます。したがって、呼び出しを繰り返しても、ランダム性が減少しないことが保証されます。

SecureRandom オブジェクトの使用

ランダムバイトを得るには、呼び出し側は単純に任意の長さの配列を渡します。すると、この配列にランダムバイトが入ります。

synchronized public void nextBytes(byte[] bytes)

シードバイトの生成

ほかの乱数ジェネレータにシードを入れる場合など、必要な場合は、generateSeed メソッドを呼び出して、与えられた数のシードバイトを生成できます。
byte[] generateSeed(int numBytes)

MessageDigest クラス

MessageDigest クラスはエンジンクラスで、安全な暗号化メッセージダイジェスト (SHA-1 や MD5 など) の機能を提供するように設計されています。安全な暗号化メッセージダイジェストは、任意サイズの入力 (バイト配列) を取り、固定サイズ出力を生成します。これをダイジェストまたはハッシュと言います。

図 5: MessageDigest クラス

図 5 の説明:MessageDigest クラス

たとえば、MD5 のアルゴリズムは 16 バイトのダイジェストを生成し、SHA1 のアルゴリズムは 20 バイトのダイジェストを生成します。

ダイジェストには次の 2 つの特徴があります。

メッセージダイジェストを使い、一意で、信頼できるデータ識別子を生成します。データ識別子をデータの「チェックサム」または「デジタル指紋」と呼ぶこともあります。メッセージのうちの 1 ビットを変更するだけで、異なるダイジェスト値が生成されます。

メッセージダイジェストには多くの用途があり、意図的かどうかにかかわらず、いつデータが変更されたかを判断できます。近年、一般的なアルゴリズムに弱点があるかどうかを判断するために大きな努力が払われていますが、結果はさまざまです。ダイジェストアルゴリズムを選択する場合は、最近のリファレンスを調べて、そのステータスと当面のタスクでの妥当性を判断する必要があります。

MessageDigest オブジェクトの作成

ダイジェスト計算では、最初にメッセージダイジェストのインスタンスを生成します。MessageDigest オブジェクトは、MessageDigest クラスの getInstance() static ファクトリメソッドの 1 つを使用することによって取得されます。ファクトリメソッドは、初期化されたメッセージダイジェストオブジェクトを返します。したがって、このあとで初期化を行う必要はありません。

メッセージダイジェストオブジェクトのアップデート

データのダイジェストを計算するための次の手順は、初期化したメッセージダイジェストオブジェクトにデータを入れることです。すべてを一度に、またはチャンクで入れることができます。次のいずれかの update メソッドを呼び出すことによって、メッセージダイジェストに渡すことができます。

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

ダイジェストの計算

update を呼び出してデータチャンクを入れたあとで、次の digest メソッドのうちのどれか 1 つを使用してダイジェストを計算します。

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

最初のメソッドは、計算されたダイジェストを返します。2 番目のメソッドは、ダイジェストのバイト配列を返す digest() を呼び出す前に、入力バイトの配列で最後の update(input) を行います。最後のメソッドは、offset から開始する提供バッファー buf 内に算出ダイジェストを格納します。len は、ダイジェストに割り当てられた buf 内のバイト数です。このメソッドは、実際に buf 内に格納されたバイト数を返します。バッファー内に十分な領域がない場合、メソッドは例外をスローします。

詳細は、「コード例」セクションの MessageDigest の計算の例を参照してください。

Signature クラス

Signature クラスはエンジンクラスであり、DSA や RSAwithMD5 などの暗号化デジタル署名アルゴリズムの機能を提供するように設計されています。安全な暗号化署名アルゴリズムは、任意サイズの入力と非公開鍵を取り、署名と呼ばれる比較的短い (固定サイズの場合もよくある) バイト文字列を生成します。このプロパティーは次のとおりです。 また、指定の署名が、関連データの実際の認証署名かどうかを検証することもできます。 図 6: Signature クラス

図 6 の説明:Signature クラス

Signature オブジェクトは、非公開鍵での署名に対して初期化され、署名するデータを与えられます。結果の署名バイトは、通常は署名されたデータとともに保持されます。検証が必要な場合、別の Signature オブジェクトが作成され、検証に対して初期化されて対応する公開鍵を与えられます。データおよび署名バイトは Signature (署名) オブジェクトに渡され、データと署名が一致した場合に、Signature オブジェクトは成功を報告します。

署名はメッセージダイジェストに似ていますが、提供する保護の種類の点で目的が大きく異なっています。実際、「SHA1WithRSA」などのアルゴリズムは、メッセージダイジェスト「SHA1」を使用して最初に大きなデータセットを管理しやすい形式に「圧縮」し、次に「RSA」アルゴリズムで結果の 20 バイトのメッセージダイジェストに署名します。

データの署名および検証の例は、のセクションを参照してください。

Signature オブジェクトの状態

Signature オブジェクトはモデルオブジェクトです。つまり、Signature オブジェクトは、常に指定の状態にあり、この状態で 1 つの型の操作だけを実行できます。状態は、個々のクラスで定義した final (ファイナル) 整数の定数で表されます。

Signature オブジェクトに可能な状態は、次の 3 つです。

Signature オブジェクトがはじめて生成されるときは、UNINITIALIZED の状態です。Signature クラスは、状態を SIGN に変更する initSign と、状態を VERIFY に変更する initVerify の 2 つの初期化メソッドを定義します。

Signature オブジェクトの作成

署名を付けたり、検証したりするには、最初に Signature のインスタンスを生成します。 Signature オブジェクトは、Signature getInstance() static ファクトリメソッドの 1 つを使用することによって取得されます。

Signature オブジェクトの初期化

Signature オブジェクトを使うには、最初に必ずこれを初期化します。初期化メソッドは、オブジェクトを署名用に使うか検証用に使うかに応じて異なります。

署名で使う場合、オブジェクトは最初に、署名を生成するエンティティーの非公開鍵を使って初期化する必要があります。この初期化は、次のメソッドを呼び出して実行します。

final void initSign(PrivateKey privateKey)
このメソッドでは、Signature オブジェクトは SIGN 状態になります。

Signature オブジェクトを検証で使う場合は、最初に、署名を検証するエンティティーの公開鍵を使って初期化する必要があります。この初期化は、次のいずれかのメソッドを呼び出して実行します。

    final void initVerify(PublicKey publicKey)

    final void initVerify(Certificate certificate)

このメソッドでは、Signature オブジェクトは VERIFY 状態になります。

署名

Signature オブジェクトを署名用に初期化した場合 (このオブジェクトが SIGN 状態の場合)、署名を付けるデータをオブジェクトに入れることができます。これは、次の update メソッドのどれか 1 つを 1 回または複数回呼び出して実行します。

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

署名を付けるデータがすべて Signature オブジェクトに入るまで、update メソッドを呼び出します。

署名を生成するには、sign メソッドの 1 つを呼び出すだけです。

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

最初のメソッドは、署名の結果をバイト配列で返します。2 番目のメソッドは、オフセット位置 offset から開始する提供バッファー outbuf 内に署名の結果を格納します。len は、署名に割り当てられた outbuf 内のバイト数です。このメソッドは、実際に格納されたバイト数を返します。

署名のエンコードは、アルゴリズムに依存します。Java 暗号化アーキテクチャーでの ASN.1 エンコードの使用方法については、標準名のドキュメントを参照してください。

sign メソッドを呼び出すと、signature (署名) オブジェクトは、initSign を呼び出して最初に署名用に初期化されたときの状態にリセットされます。つまり、オブジェクトをリセットするので、必要であれば updatesign を新たに呼び出して、同じ非公開鍵を使って別の署名を生成できます。

あるいは異なる非公開鍵を指定して、initSign への新規呼び出しを作成したり、(署名を検証するために Signature オブジェクトを初期化する目的で) initVerify への新規呼び出しを作成したりできます。

検証

Signature オブジェクトを検証用に初期化した場合 (VERIFY 状態にある場合) は、指定の署名が、それに関連したデータの実際の認証署名であるかどうかを検証できます。この処理を開始するには、検証データ (署名自体ではない) をオブジェクトに入れます。データは、次のいずれかの update メソッドを呼び出すことによってオブジェクトに渡されます。

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

検証するデータがすべて Signature オブジェクトに入るまで、update メソッドを呼び出します。これで、次のいずれかの verify メソッドを呼び出して署名を検証できます。

final boolean verify(byte[] signature)

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

引数は、署名を含むバイト配列である必要があります。このバイト配列は、前にいずれかの sign メソッドの呼び出しによって返された署名バイトを保持します。

verify メソッドは、エンコードされた署名が update メソッドに入れたデータの認証署名かどうかを示す boolean を返します。

verify メソッドを呼び出すと、signature (署名) オブジェクトは、initVerify を呼び出して検証用に初期化されたときの状態にリセットされます。つまり、オブジェクトをリセットするので、initVerify への呼び出しで指定した公開鍵を持つ識別の別の署名を検証できます。

あるいは異なる非公開鍵を指定し、(異なるエンティティーの署名を検証するために Signature オブジェクトを初期化する目的で) initVerify への新規呼び出しを作成したり、(署名を生成するために Signature オブジェクトを初期化する目的で) initSign への新規呼び出しを作成したりできます。

Cipher クラス

Cipher クラスは、暗号化および復号化で使用される暗号機能を提供します。暗号化とは、データ (「クリアテキスト」と呼ばれる) および「キー」を受け取って、鍵を知らないサードパーティーにとって無意味なデータ (「暗号テキスト」) を生成する処理のことです。復号化はその逆で、暗号テキストおよび鍵を受け取り、クリアテキストを生成する処理です。

図 7: Cipher クラス

図 7 の説明:Cipher クラス

対称暗号化方式と非対称暗号化方式

暗号化には、対称 (秘密鍵とも呼ばれる) と非対称 (または公開鍵暗号化方式) という 2 つの主要な種類があります。対称暗号化方式では、同じ秘密鍵がデータの暗号化と復号化の両方を行います。データの機密性を保持するには、鍵を非公開にすることが重要となります。一方、非対称暗号化方式は、公開鍵と非公開鍵のペアを使用してデータを暗号化します。一方の鍵で暗号化されたデータは、他方の鍵で復号化されます。ユーザーは最初に公開鍵と非公開鍵のペアを生成し、だれでもアクセスできる信頼できるデータベースに公開鍵を発行します。そのユーザーと安全に通信することを希望するユーザーは、取得した公開鍵を使用してデータを暗号化します。非公開鍵の保有者のみが復号化できます。このスキームでは、非公開鍵を機密にすることが重要となります。

RSA などの非対称アルゴリズムは、通常、対称アルゴリズムよりも大幅に遅くなります。これらのアルゴリズムは、大量データを効率的に保護するように設計されていません。実際には、非対称アルゴリズムは、対称アルゴリズムを初期化するために使用される少量の秘密鍵を交換する場合に使用されます。

ストリーム暗号とブロック暗号

ブロックストリームという 2 つの主要な種類の暗号があります。ブロック暗号は、ブロック全体を一度に処理します。通常は長いバイト長になります。完全な入力ブロックを作成するのにデータが不足する場合は、データのパディングが必要です。つまり、暗号化の前に、ダミーバイトを追加して暗号のブロックサイズの倍数にします。これらのバイトは、復号化の段階で削除されます。パディングは、アプリケーションか、または「PKCS5PADDING」などのパディング型を使用するように暗号を初期化することによって実行できます。これに対して、ストリーム暗号は入力データを一度に 1 つの小さい単位 (通常はバイト、またはビットの場合もある) で処理します。これにより、暗号は任意の量のデータをパディングを行わずに処理できます。

操作モード

単純なブロック暗号を使用して暗号化する場合、2 つの同じプレーンテキストのブロックは、常に同じ暗号テキストのブロックを生成します。暗号テキストを破ろうとする暗号解読者が繰り返しのテキストのブロックに気付くと、簡単に解読されてしまいます。テキストに複雑さを加えるために、フィードバックモードは、暗号化アルゴリズムを適用する前に、以前の出力ブロックを使用して入力ブロックを変更します。最初のブロックには初期値が必要です。この値は、初期化ベクトル (IV) と呼ばれます。IV は暗号化の前に単純にデータを変更するため、IV はランダムである必要がありますが、必ずしも秘密である必要はありません。CBC (Cipher Block Chaining)、CFB (Cipher Feedback Mode)、OFB (Output Feedback Mode) など、さまざまなモードがあります。ECB (Electronic Cookbook Mode) は、フィードバックのないモードです。

AES や RSA などの一部のアルゴリズムでは異なる長さの鍵が可能ですが、それ以外 (DES や 3DES など) は固定です。長い鍵を使用する暗号化は、一般にメッセージ復元に対して抵抗力が強いことを意味します。いつものように、セキュリティーと時間という相反するものの間で折り合いを付けるため、適切な鍵の長さを選択します。

ほとんどのアルゴリズムは、バイナリ鍵を使用します。ほとんどの人は、16 進数で表現されていても、長いバイナリの数値を記憶できません。文字のパスワードの方が記憶するのは簡単です。通常、文字のパスワードは少数の文字 ([a-zA-Z0-9] など) から選択されるため、「パスワードベースの暗号化」(PBE) などのプロトコルは、文字のパスワードを取得して強力なバイナリ鍵を生成するように定義されています。パスワードから暗号化鍵を取得しようとする攻撃者のタスク (一般の辞書の「単語->値」のマッピングが事前に計算されている、いわゆる「ディクショナリ攻撃」による) を非常に時間のかかるものにするために、大半の PBE 実装では、鍵のランダム性を高めるために乱数への混入 (salt と呼ばれる) が行われます。

関連データによる認証暗号化方式 (AEAD) などの新しい暗号化モード (Galois/Counter Mode (GCM) など) では、データを暗号化し、同時に結果のメッセージを認証します。結果の AEAD タグ (Mac) の計算に、AAD (Additional Associated Data) を使用できますが、この AAD データは暗号テキストとして出力されません。たとえば、一部のデータは機密にする必要がありませんが、変更を検出するため、タグ計算に含める必要がある場合があります。タグ計算に AAD を含めるには、Cipher.updateAAD() メソッドを使用できます。

Cipher オブジェクトの作成

Cipher オブジェクトは、Cipher getInstance() static ファクトリメソッドの 1 つを使用することによって取得されます。ここでのアルゴリズム名は、アルゴリズム名だけでなく「変換」を指定する点で、ほかのエンジンクラスの場合と少し異なります。変換は、指定された入力に対して実行し、何らかの出力を生成する操作 (または操作のセット) を説明する文字列です。変換には、暗号化アルゴリズム (DES など) の名前が必ず含まれます。それにモードおよびパディングスキームが続く場合もあります。

変換は、次の書式で記述されます。

たとえば、次は有効な変換です。

    "DES/CBC/PKCS5Padding"

    "DES"

変換名だけを指定すると、要求された変換の実装がその環境で使用可能かどうかをシステムが判別し、複数の実装が存在する場合には優先度の高い実装が存在することを返します。

変換名とパッケージプロバイダの両方を指定すると、システムは要求されたパッケージ内に要求された変換の実装が存在するかどうかを判別し、存在しない場合には例外をスローします。

モードやパディングを指定しない場合、モードおよびパディングスキームのプロバイダ固有のデフォルト値が使用されます。たとえば、SunJCE プロバイダは、ECB をデフォルトモードとして使用し、PKCS5PaddingDESDES-EDE、および Blowfish 暗号のデフォルトパディングスキームとして使用します。このため、SunJCE プロバイダでは、

    Cipher c1 = Cipher.getInstance("DES/ECB/PKCS5Padding");
および
    Cipher c1 = Cipher.getInstance("DES");
の 2 つの文は、等価になります。

CFB や OFB などのモードを使用すると、ブロック暗号は、暗号の実際のブロックサイズよりも小さい単位でデータを暗号化できます。このようなモードを要求する場合、「DES/CFB8/NoPadding」および「DES/OFB32/PKCS5Padding」変換に示されるように、この数値をモード名に追加することにより、一度に処理するビット数をオプションで指定できます。数値を指定しない場合、プロバイダ固有のデフォルトが使用されます。(たとえば、SunJCE プロバイダでは DES にデフォルトの 64 ビットが使用されます。)したがって、CFB8 や OFB8 などの 8 ビットモードを使用することで、ブロック暗号をバイト指向のストリーム暗号に変換できます。

このドキュメントの「付録 A」には、変換のアルゴリズム名、モード、およびパディングスキームコンポーネントの指定に使用可能な標準名のリストが掲載されています。

ファクトリメソッドにより返されるオブジェクトは初期化されていないため、使用する前に初期化する必要があります。

Cipher オブジェクトの初期化

getInstance を介して取得された Cipher オブジェクトは、次の 4 つのモードのいずれかで初期化する必要があります。これらのモードは、Cipher クラスの final (ファイナル) 整数の定数として定義されます。モードは、シンボリック名で参照できます。次に、各モードのシンボリック名および目的を示します。

ENCRYPT_MODE
データの暗号化。
DECRYPT_MODE
データの復号化。
WRAP_MODE
安全に転送するために java.security.Key をラッピングする。
UNWRAP_MODE
ラッピングされた鍵を java.security.Key オブジェクトにアンラッピングする。

Cipher 初期化メソッドは、それぞれ操作モードパラメータ (opmode) を取り、そのモード用の Cipher オブジェクトを初期化します。その他のパラメータには、鍵 (key) または鍵を含む証明書 (certificate)、アルゴリズムパラメータ (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 メソッドにパラメータを何も指定しないと、ランダムパラメータを生成するか、プロバイダ固有のパラメータセット (デフォルト) を使用することにより、基盤となる暗号実装が必須パラメータを提供すると見なされます。

ただし、パラメータを必要とする Cipher オブジェクトを復号化用に初期化する場合、init メソッドにパラメータを何も指定しないと、使用した init メソッドに応じて InvalidKeyException または InvalidAlgorithmParameterException 例外が発生します。

詳細は、「アルゴリズムパラメータの管理」に関するセクションを参照してください。

復号化には、暗号化に使用したのと同じパラメータを使用する必要があります。

Cipher オブジェクトを初期化すると、それまでに獲得した状態がすべて失われることに留意してください。つまり、Cipher を初期化することは、その Cipher の新規インスタンスを作成して初期化することと等価です。たとえば、指定された鍵で復号化を行うために Cipher を初期化してから、暗号化を行うために初期化すると、復号化モードで獲得した状態はすべて失われます。

データの暗号化および復号化

データの暗号化または復号化は、1 つのステップで実行 (「単一部分操作」) することも、複数のステップで実行 (「複数部分操作」) することもできます。データの長さが不明な場合、またはデータが長すぎて一度にメモリーに格納できない場合に、複数部分操作は有用です。

1 つのステップでデータの暗号化または復号化を行う場合、次のいずれかの 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)

複数のステップでデータの暗号化または復号化を行うには、次のいずれかの update メソッドを呼び出します。

    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 は鍵オブジェクトに対して直接機能するため、この API を使用するとコードの記述が容易になります。次のメソッドを使用すると、ハードウェアベースの鍵の安全な転送も可能になります。

Key を wrap する場合、まず WRAP_MODE の Cipher オブジェクトを初期化し、次のメソッドを呼び出します。

    public final byte[] wrap(Key key);

ラップされた鍵のバイト (wrap を呼び出した結果) を、そのラップを解除するほかのユーザーに提供する場合、受信者が unwrap を実行するのに必要な、次の追加情報も送信する必要があります。

  1. 鍵アルゴリズムの名前
  2. ラップされた鍵の型。Cipher.SECRET_KEYCipher.PRIVATE_KEY、または Cipher.PUBLIC_KEY のいずれか。

鍵アルゴリズム名は、次に示すように Key インタフェースから getAlgorithm メソッドを呼び出すことにより確認できます。

    public String getAlgorithm();

wrap への呼び出しにより返されたバイトのラップを解除するには、UNWRAP_MODE の Cipher オブジェクトを初期化してから、次を呼び出します。

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

ここで、wrappedKey はラップへの呼び出しにより返されたバイトを、wrappedKeyAlgorithm はラップされた鍵に関連付けられたアルゴリズムを、wrappedKeyType はラップされた鍵の型をそれぞれ指します。これは、Cipher.SECRET_KEYCipher.PRIVATE_KEYCipher.PUBLIC_KEY のいずれかである必要があります。

アルゴリズムパラメータの管理

基盤となる Cipher 実装により使用されるパラメータ (アプリケーションにより init メソッドに明示的に渡されたか、基盤となる実装自体により生成された) は、getParameters メソッドを呼び出すことにより Cipher オブジェクトから取得できます。このメソッドは、パラメータを java.security.AlgorithmParameters オブジェクト (パラメータが使用されない場合は null) として返します。パラメータが初期化ベクトル (IV) の場合、getIV メソッドを呼び出すことによりパラメータを取得できます。

次の例では、パスワードベース暗号化 (PBE) を実装する Cipher オブジェクトを、パラメータを使用せずに鍵のみを使用して初期化します。ただし、選択されたパスワードベース暗号化用に選択されたアルゴリズムは、salt および iteration count という 2 つのパラメータを必要とします。これらは、基盤となるアルゴリズム実装自体により生成されます。アプリケーションは、生成されたパラメータを次の方法で Cipher オブジェクトから取得できます。

    import javax.crypto.*;
    import java.security.AlgorithmParameters;

    // get cipher object for password-based encryption
    Cipher c = Cipher.getInstance("PBEWithMD5AndDES");

    // initialize cipher for encryption, without supplying
    // any parameters. Here, "myKey" is assumed to refer
    // to an already-generated key.
    c.init(Cipher.ENCRYPT_MODE, myKey);

    // encrypt some data and store away ciphertext
    // for later decryption
    byte[] cipherText = c.doFinal("This is just an example".getBytes());

    // retrieve parameters generated by underlying cipher
    // implementation
    AlgorithmParameters algParams = c.getParameters();

    // get parameter encoding and store it away
    byte[] encodedAlgParams = algParams.getEncoded();

復号化には、暗号化に使用したのと同じパラメータを使用する必要があります。これらは、エンコーディングからインスタンス化することが可能であり、次に示すように、対応する Cipher オブジェクトを復号化用に初期化する際に使用できます。

    import javax.crypto.*;
    import java.security.AlgorithmParameters;

    // get parameter object for password-based encryption
    AlgorithmParameters algParams;
    algParams = AlgorithmParameters.getInstance("PBEWithMD5AndDES");

    // initialize with parameter encoding from above
    algParams.init(encodedAlgParams);

    // get cipher object for password-based encryption
    Cipher c = Cipher.getInstance("PBEWithMD5AndDES");

    // initialize cipher for decryption, using one of the
    // init() methods that takes an AlgorithmParameters
    // object, and pass it the algParams object from above
    c.init(Cipher.DECRYPT_MODE, myKey, algParams);

Cipher オブジェクトの初期化時にパラメータを一切指定せず、基盤となる実装がいずれかのパラメータを使用するかどうか不明な場合、Cipher オブジェクトの getParameters メソッドを呼び出して、返される値をチェックするだけで確認できます。戻り値が null の場合、パラメータが使用されなかったことを示します。

SunJCE プロバイダにより実装される次の暗号アルゴリズムは、パラメータを使用します。

SealedObject クラスを使用する場合、復号化操作に使用するアルゴリズムパラメータの格納または転送について心配する必要はありません。このクラスは、シール (暗号化) に使用されるパラメータを暗号化されたオブジェクトコンテンツに添付します。また、アンシール (復号化) でも同じパラメータを使用します。

暗号出力時の考慮事項

Cipher の update および doFinal の中には、呼び出し側による出力バッファーの指定が可能なものがあります。暗号化または復号化されたデータは、このバッファー内に出力されます。この場合、暗号化または復号化操作の結果を保持できるだけの大きさのバッファーを渡すことは重要です。

Cipher 内の次のメソッドを使用して、設定すべき出力バッファーのサイズを確認できます。

    public int getOutputSize(int inputLen)

その他の Cipher ベースのクラス

Cipher を内部的に使用して共通の暗号使用への簡単なアクセスを提供する、いくつかのヘルパークラスがあります。

Cipher Stream クラス

CipherInputStream クラス

このクラスは、通過するデータの暗号化または復号化を行う FilterInputStream です。これは、InputStream またはそのサブクラスのいずれか、および Cipher で構成されます。CipherInputStream は、Cipher オブジェクトの挿入先の、セキュリティー保護された入力ストリームを表します。CipherInputStream の read メソッドは、基盤となる InputStream から読み取られ、埋め込まれた Cipher オブジェクトにより追加処理されたデータを返します。Cipher オブジェクトは、CipherInputStream で使用する前に完全に初期化する必要があります。

たとえば、埋め込まれた Cipher が復号化用に初期化されている場合、CipherInputStream は基盤となる InputStream から読み込んだデータの復号化を試みてから、データをアプリケーションに返します。

このクラスは、上位クラス java.io.FilterInputStream および java.io.InputStream のセマンティクス (特にエラーに関するセマンティクス) に厳密に準拠します。このクラスは、上位クラスで指定されたメソッドを正確に保持し、それらすべてをオーバーライドします。このため、埋め込まれた暗号によるデータの追加処理が可能になります。さらに、このクラスは、上位クラスがスローしない例外をすべてキャッチします。特に、skip(long) メソッドは、Cipher により処理されたデータだけを無視します。

このクラスを使用するプログラマにとって、このクラスで定義またはオーバーライドされていないメソッド (上位クラスのいずれかにあとで追加された新規メソッドまたはコンストラクタ) を使用しないようにすることは重要です。これらのメソッド実装は、CipherInputStream へのセキュリティー面の影響を考慮に入れていないためです。

使用方法の一例として、cipher1 が暗号化用に初期化されている場合を考えてみましょう。次のコードは、暗号および FileInputStream を含む CipherInputStream を使用して、入力ストリームデータを暗号化する方法を示します。

    FileInputStream fis;
    FileOutputStream fos;
    CipherInputStream cis;

    fis = new FileInputStream("/tmp/a.txt");
    cis = new CipherInputStream(fis, cipher1);
    fos = new FileOutputStream("/tmp/b.txt");
    byte[] b = new byte[8];
    int i = cis.read(b);
    while (i != -1) {
        fos.write(b, 0, i);
        i = cis.read(b);
    }
    fos.close();

前述のプログラムは、ファイル /tmp/a.txt からコンテンツを読み取って暗号化し、結果 (暗号化されたバイト) を /tmp/b.txt に格納します。

次の例は、CipherInputStream および FileInputStream の複数インスタンスを簡単に接続する方法を示します。この例では、cipher1 および cipher2 が、それぞれ暗号化および復号化用に (対応する鍵を使用して) 初期化されているものとします。

    FileInputStream fis;
    FileOutputStream fos;
    CipherInputStream cis1, cis2;

    fis = new FileInputStream("/tmp/a.txt");
    cis1 = new CipherInputStream(fis, cipher1);
    cis2 = new CipherInputStream(cis1, cipher2);
    fos = new FileOutputStream("/tmp/b.txt");
    byte[] b = new byte[8];
    int i = cis2.read(b);
    while (i != -1) {
        fos.write(b, 0, i);
        i = cis2.read(b);
    }
    fos.close();

上記のプログラムは、ファイル /tmp/a.txt の内容を /tmp/b.txt にコピーします。ただし、/tmp/a.txt からの読み取り時に、最初に内容の暗号化、次に復号化が行われます。実際のところ、このプログラムはテキストを暗号化したあと、すぐに復号化を行うため、CipherInputStreams のチェーンをわかりやすく示す以外は特に有用なものではありません。

CipherInputStream の読み取りメソッドは、基盤となる暗号からデータが返されるまでブロックします。ブロック暗号が使用される場合、基盤となる InputStream から暗号テキストの完全なブロックが取得される必要があります。

CipherOutputStream クラス

このクラスは、通過するデータの暗号化または復号化を行う FilterOutputStream です。これは、OutputStream またはそのサブクラスのいずれか、および Cipher で構成されます。CipherOutputStream は、Cipher オブジェクトの挿入先の、セキュリティー保護された出力ストリームを表します。CipherOutputStream の write メソッドは、埋め込まれた Cipher オブジェクトを使ってデータを処理してから、基盤となる OutputStream にデータを書き出します。Cipher オブジェクトは、CipherOutputStream で使用する前に完全に初期化する必要があります。

たとえば、埋め込まれた Cipher が暗号化用に初期化されている場合、CipherOutputStream はデータを暗号化してから、基盤となる出力ストリームに書き出します。

このクラスは、上位クラス java.io.OutputStream および java.io.FilterOutputStream のセマンティクス (特にエラーに関するセマンティクス) に厳密に準拠します。このクラスは、上位クラスで指定されたメソッドを正確に保持し、それらすべてをオーバーライドします。このため、埋め込まれた暗号によるすべてのデータの追加処理が可能になります。さらに、このクラスは、上位クラスがスローしない例外をすべてキャッチします。

このクラスを使用するプログラマにとって、このクラスで定義またはオーバーライドされていないメソッド (上位クラスのいずれかにあとで追加された新規メソッドまたはコンストラクタ) を使用しないようにすることは重要です。これらのメソッド実装は、CipherOutputStream へのセキュリティー面の影響を考慮に入れていないためです。

使用方法の一例として、cipher1 が暗号化用に初期化されている場合を考えてみましょう。次のコードは、暗号および FileOutputStream を含む CipherOutputStream を使用して、暗号化されたデータを出力ストリームに書き出す方法を示します。

    FileInputStream fis;
    FileOutputStream fos;
    CipherOutputStream cos;

    fis = new FileInputStream("/tmp/a.txt");
    fos = new FileOutputStream("/tmp/b.txt");
    cos = new CipherOutputStream(fos, cipher1);
    byte[] b = new byte[8];
    int i = fis.read(b);
    while (i != -1) {
        cos.write(b, 0, i);
        i = fis.read(b);
    }
    cos.flush();

前述のプログラムは、ファイル /tmp/a.txt からコンテンツを読み取って暗号化し、結果 (暗号化されたバイト) を /tmp/b.txt に格納します。

次の例は、CipherOutputStream および FileOutputStream の複数インスタンスを簡単に接続する方法を示します。この例では、cipher1 および cipher2 が、それぞれ復号化および暗号化用に (対応する鍵を使用して) 初期化されているものとします。

    FileInputStream fis;
    FileOutputStream fos;
    CipherOutputStream cos1, cos2;

    fis = new FileInputStream("/tmp/a.txt");
    fos = new FileOutputStream("/tmp/b.txt");
    cos1 = new CipherOutputStream(fos, cipher1);
    cos2 = new CipherOutputStream(cos1, cipher2);
    byte[] b = new byte[8];
    int i = fis.read(b);
    while (i != -1) {
        cos2.write(b, 0, i);
        i = fis.read(b);
    }
    cos2.flush();

上記のプログラムは、ファイル /tmp/a.txt の内容を /tmp/b.txt にコピーします。ただし、/tmp/b.txt に書き込む前に、内容の暗号化および復号化が行われます。

ブロック暗号アルゴリズムを使用する場合は、データが暗号化されて基盤となる出力ストリームに送信される前に、プレーンテキストデータの完全なブロックを CipherOutputStream に与える必要があります。

このクラスの flushclose メソッドには、ほかに 1 つの重要な相違点があります。カプセル化された Cipher オブジェクトがパディングを有効にしてブロック暗号アルゴリズムを実装する場合、この相違点に特に留意する必要があります。

SealedObject クラス

プログラマは、このクラスを使用してオブジェクトを生成し、暗号化アルゴリズムを利用してその機密性を保護することができます。

java.io.Serializable インタフェースを実装するオブジェクトが指定された場合、元のオブジェクトを直列化形式 (「ディープコピー」) でカプセル化する SealedObject を作成し、DES などの暗号化アルゴリズムを使用して直列化された内容をシール (暗号化) することにより、機密性を保護できます。そのあと、暗号化された内容の暗号解読 (適正な暗号解読鍵を使用)、および直列化解除を行うことにより、元のオブジェクトを復元できます。

一般的な使用法を、次のコード例に示します。オブジェクトをシールする場合、シール対象のオブジェクトから SealedObject を作成し、直列化されたオブジェクト内容を暗号化する、完全に初期化された Cipher オブジェクトを作成します。この例では、文字列「This is a secret」が DES アルゴリズムを使用してシールされます。シール操作に使用されるすべてのアルゴリズムパラメータは、SealedObject の内部に格納されることに留意してください。

    // create Cipher object
    // NOTE: sKey is assumed to refer to an already-generated
    // secret DES key.
    Cipher c = Cipher.getInstance("DES");
    c.init(Cipher.ENCRYPT_MODE, sKey);

    // do the sealing
    SealedObject so = new SealedObject("This is a secret", c);

シールされた元のオブジェクトは、次の異なる 2 つの方法で復元可能です。

Mac クラス

MessageDigest と同様に、メッセージ認証コード (MAC) は、送信された情報や信頼できないメディアに保存されている情報の整合性をチェックする方法を提供しますが、秘密鍵が計算に含まれます。適切な鍵を持つ人だけが、受信したメッセージを検証できます。一般に、メッセージ認証コードは、秘密鍵を共有する 2 つのパーティー間で送信される情報の有効性を検証する場合に使用されます。

図 8: Mac クラス

図 8 の説明:Mac クラス

暗号化ハッシュ機能に基づく MAC メカニズムは、HMAC と呼ばれます。HMAC は、秘密共有鍵と組み合わせて、MD5 や SHA-1 などの任意の暗号化ハッシュ機能で使用できます。

Mac クラスは、メッセージ認証コード (MAC) の機能を提供します。「コード例」を参照してください。

Mac オブジェクトの作成

Mac オブジェクトは、Mac getInstance() static ファクトリメソッドの 1 つを使用することによって取得されます。

Mac オブジェクトの初期化

Mac オブジェクトは、常に (秘密) 鍵を使って初期化されます。またオプションとして、基盤となる MAC アルゴリズムによっては、パラメータセットを使って初期化することもできます。

Mac オブジェクトを初期化する場合、次のいずれかの init メソッドを呼び出します。

    public void init(Key key);

    public void init(Key key, AlgorithmParameterSpec params);

javax.crypto.SecretKey インタフェースを実装する任意の (秘密) 鍵オブジェクトを使って、Mac オブジェクトを初期化できます。これは、javax.crypto.KeyGenerator.generateKey() が返すオブジェクト、または javax.crypto.KeyAgreement.generateSecret() などが返す鍵協定プロトコルの結果生成されるオブジェクト、または javax.crypto.spec.SecretKeySpec のインスタンスです。

MAC アルゴリズムの中には、Mac オブジェクトの初期化に使用される (秘密) 鍵オブジェクトに関連付けられた (秘密) 鍵アルゴリズムが重要ではないものがあります (SunJCE プロバイダの HMAC-MD5 および HMAC-SHA1 実装の場合)。ただし、それ以外の場合、(秘密) 鍵アルゴリズムは重要であり、(秘密) 鍵オブジェクトが不適切な (秘密) 鍵アルゴリズムで使用されると、InvalidKeyException がスローされます。

MAC の計算

MAC は、1 つのステップで計算 (「単一部分操作」) することも、複数のステップで計算 (「複数部分操作」) することもできます。データの長さが不明な場合、またはデータが長すぎて一度にメモリーに格納できない場合に、複数部分操作は有用です。

あるデータの MAC を 1 回のステップで計算するには、次の doFinal メソッドを呼び出します。

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

複数のステップでデータの MAC を計算するには、次のいずれかの update メソッドを呼び出します。

    public void update(byte input);

    public void update(byte[] input);

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

複数部分操作は、上記の doFinal メソッド (最後のステップで入力データが残される場合)、または次の doFinal メソッドのいずれか (最後のステップで入力データが残らない場合) を使って終了させます。

    public byte[] doFinal();

    public void doFinal(byte[] output, int outOffset);

Key インタフェース

ここまでは、鍵とは何か、鍵はどのように生成および表現されるかについて詳細は説明せずに、JCA の高レベルの使用に重点を置いてきました。ここでは鍵に注意を向けます。

java.security.Key インタフェースは、すべての不透明な鍵に関するトップレベルのインタフェースです。すべての不透明な鍵オブジェクトが共有する機能を定義します。

不透明な」鍵の表現では、鍵を構成する鍵データに直接アクセスできません。つまり、「不透明」さにより、鍵へのアクセスが、Key インタフェースによって定義される getAlgorithmgetFormat、および getEncoded

これと対照的なのが透明な表現で、この場合は、対応する仕様クラス内に定義された get メソッドの 1 つを使用して、各鍵データの値に個々にアクセスできます。

不透明な鍵はすべて、次の 3 つの特性を持ちます。

アルゴリズム
その鍵の鍵アルゴリズムです。通常、鍵アルゴリズムは、暗号化または非対称操作アルゴリズム (AESDSARSA など) で、これらのアルゴリズムや関連アルゴリズム (MD5withRSASHA1withRSA など) と連携して機能します。鍵アルゴリズムの名前は、次のメソッドを使用して獲得します。
String getAlgorithm()
コード化形式
鍵の外部エンコード形式は、鍵を別の組織に転送する場合など、鍵の標準表示が Java 仮想マシンの外部で必要なときに使います。鍵は標準形式 (X.509 や PKCS8 など) に従ってエンコードされ、次のメソッドを使用して返されます。
byte[] getEncoded()
フォーマット
エンコードされた鍵のフォーマット名です。次のメソッドから返されます。
String getFormat()
鍵は一般的に、KeyGeneratorKeyPairGenerator などの鍵ジェネレータ、証明書、鍵仕様 (KeyFactory を使用)、または鍵管理で使用するキーストアデータベースにアクセスする KeyStore の実装から獲得します。KeyFactory を使って、アルゴリズム依存型の方法でエンコードされた鍵を解析することが可能です。

また、CertificateFactory を使って、認証を解析することも可能です。

次に、java.security.interfaces および javax.crypto.interfaces パッケージ内の Key インタフェースを拡張するインタフェースのリストを示します。

PublicKey インタフェースと PrivateKey インタフェース

PublicKey および PrivateKey インタフェースはどちらも Key インタフェースを拡張しますが、これらはメソッドを使わないインタフェースで、型の安全性および型の識別に使用します。

KeyPair クラス

KeyPair クラスは鍵のペア (公開鍵と非公開鍵) の簡単なホルダーです。これには 2 つの public メソッドがあります。1 つは非公開鍵を返し、もう 1 つは公開鍵を返します。

PrivateKey getPrivate()
PublicKey getPublic()

鍵仕様のインタフェースおよびクラス

Key オブジェクトと鍵仕様 (KeySpec) は、鍵データの異なる 2 つの表現です。CipherKey オブジェクトを使用して暗号化アルゴリズムを初期化しますが、伝送またはストレージのために鍵を移植性の高い形式に変換する必要がある場合があります。

鍵の透明な表現とは、対応する仕様クラスで定義された get メソッドの 1 つを使って、各鍵データ値に個々にアクセスできるということです。たとえば、DSAPrivateKeySpec は、getXgetPgetQ、および getG メソッドを定義し、非公開鍵 x および鍵の計算に使用する DSA アルゴリズムのパラメータ (素数の p、サブ素数の q、およびベースの g) にアクセスします。鍵がハードウェアデバイス上に格納されている場合は、その鍵仕様には、デバイス上の鍵の識別を助ける情報が含まれていることがあります。

この表現と対照的なのが、Key インタフェースによって定義されるような、不透明な表現です。「不透明な」鍵の表現では、鍵要素フィールドに直接アクセスできません。つまり、「不透明」さにより、鍵へのアクセスが、Key インタフェースによって定義される getAlgorithmgetFormat、および getEncoded

鍵は、アルゴリズムに固有の方法か、またはアルゴリズムに依存しない符号化形式 (ASN.1 など) で指定できます。たとえば、DSA 非公開鍵は、非公開鍵のコンポーネント xpq、および g によって指定するか (DSAPrivateKeySpec を参照)、または、非公開鍵の DER エンコーディングを使って指定することが可能です ( PKCS8EncodedKeySpec を参照)。

KeyFactory および SecretKeyFactory クラスは、不透明な鍵の表現と透明な鍵の表現の間 (つまり、KeyKeySpec の間。操作が可能であると想定) で変換を行うために使用できます。たとえば、スマートカード上の非公開鍵は、カードから取り出せない場合があります。そのような Key は変換不可能です。

次のセクションでは、java.security.spec パッケージ内に含まれる鍵仕様のインタフェースおよびクラスについて説明します。

KeySpec インタフェース

このインタフェースには、メソッドまたは定数が含まれていません。このインタフェースの唯一の目的は、すべての鍵仕様をグループ化することおよびそれらのグループに安全な型を提供することです。すべての鍵仕様で、このインタフェースを実装する必要があります。

KeySpec サブインタフェース

Key インタフェースと同じように、KeySpec インタフェースの同様のセットがあります。

EncodedKeySpec クラス

この抽象クラス (KeySpec インタフェースを実装する) は、エンコードされた形式の公開鍵または非公開鍵を表します。その getEncoded メソッドは、次のエンコードされた鍵を返します。
abstract byte[] getEncoded();
このクラスの getFormat メソッドは、次のエンコード形式の名前を返します。
abstract String getFormat();

固定実装 PKCS8EncodedKeySpec および X509EncodedKeySpec については、次のセクションを参照してください。

PKCS8EncodedKeySpec クラス

このクラスは、EncodedKeySpec のサブクラスで、PKCS8 標準で指定された形式に従って、非公開鍵の DER エンコードを表現します。その getEncoded メソッドは、PKCS8 標準に従ってエンコードされた鍵のバイトを返します。その getFormat メソッドは、文字列「PKCS#8」を返します。

X509EncodedKeySpec クラス

このクラスは、EncodedKeySpec のサブクラスで、X.509 標準で指定された形式に従って、公開鍵の DER エンコードを表現します。その getEncoded メソッドは、X.509 標準に従ってエンコードされた鍵のバイトを返します。その getFormat メソッドは、文字列「X.509」を返します。

ジェネレータおよびファクトリ

Java および JCA API をはじめて使用する人は、ジェネレータとファクトリの区別がつかない場合があります。

図 9: ジェネレータとファクトリの違い

図 9 の説明:ジェネレータとファクトリの違い

ジェネレータは、新しいオブジェクトを生成する場合に使用されます。ジェネレータは、アルゴリズム依存またはアルゴリズム非依存で初期化できます。たとえば、Diffie-Hellman (DH) 鍵ペアを作成するために、アプリケーションは必要な P および G 値を指定できます。または、ジェネレータを適切な鍵の長さで単純に初期化できます。ジェネレータは適切な P および G 値を選択します。どちらの場合も、ジェネレータはパラメータに基づいて新しい鍵を生成します。

一方、ファクトリは、既存のオブジェクト型から別のオブジェクト型へデータを変換する場合に使用されます。たとえば、アプリケーションに DH 非公開鍵の使用可能なコンポーネントがある場合があります。アプリケーションは、それらを KeySpec としてパッケージ化できますが、KeyAgreement オブジェクトが使用できるよう PrivateKey オブジェクトに変換する必要があります。または逆の変換が必要な場合もあります。または、それらに証明書のバイト配列があるが、CertificateFactory を使用して、X509Certificate オブジェクトに変換する必要がある場合があります。アプリケーションは、変換を行うためにファクトリオブジェクトを使用します。

KeyFactory クラス

KeyFactory クラスは、不透明な暗号化 Key鍵仕様 (背後の鍵データの透明な表現) 間の変換を実行するために設計されたエンジンクラスです。

図 10: KeyFactory クラス<

図 10 の説明:KeyFactory クラス

鍵ファクトリは、双方向性があります。つまり、これによって、与えられた鍵仕様 (鍵のデータ) から不透明な鍵オブジェクトを構築することも、鍵オブジェクトの背後の鍵データを適切な形式で取得することもできます。

同一の鍵に対して、複数の互換性のある鍵仕様を存在させることもできます。たとえば、DSA 公開鍵は、コンポーネント ypq、および g によって指定することも (java.security.spec.DSAPublicKeySpec を参照)、X.509 標準に従って DER エンコードを使用して指定することも (X509EncodedKeySpec を参照) できます。

鍵ファクトリは、互換性のある鍵仕様間の変換に使用できます。互換性のある鍵仕様間の変換では、鍵の構文解析が行われます。たとえば、X509EncodedKeySpecDSAPublicKeySpec に変換する場合は、基本的にエンコードされた鍵をコンポーネント単位で解釈処理します。例については、「鍵仕様および KeyFactory を使った署名の生成と検証」のセクションの最後を参照してください。

KeyFactory オブジェクトの作成

KeyFactory オブジェクトは、KeyFactory getInstance() static ファクトリメソッドの 1 つを使用することによって取得されます。

鍵仕様と Key オブジェクト間の変換

公開鍵用の鍵仕様がある場合は、generatePublic メソッドを使って、その仕様から不透明な PublicKey オブジェクトを取得できます。

PublicKey generatePublic(KeySpec keySpec)

同様に、非公開鍵用の鍵仕様がある場合は、generatePrivate メソッドを使って、その仕様から不透明な PrivateKey オブジェクトを取得できます。

PrivateKey generatePrivate(KeySpec keySpec)

Key オブジェクトと鍵仕様間の変換

Key オブジェクトがある場合は、getKeySpec メソッドの呼び出しによって、対応する鍵仕様オブジェクトを取得できます。

KeySpec getKeySpec(Key key, Class keySpec)
keySpec は、鍵のデータが返されるべき仕様クラスを識別します。たとえば、DSAPublicKeySpec.class は、鍵のデータが DSAPublicKeySpec クラスのインスタンスに返されるべきであることを指示します。

詳細は、のセクションを参照してください。

SecretKeyFactory クラス

このクラスは、秘密鍵のファクトリを表します。KeyFactory とは異なり、javax.crypto.SecretKeyFactory オブジェクトは秘密 (対称) 鍵のみを処理し、一方、java.security.KeyFactory オブジェクトは鍵ペアの公開鍵および非公開鍵コンポーネントを処理します。

図 11: SecretKeyFactory クラス

図 11 の説明:SecretKeyFactory クラス

鍵ファクトリは、java.security.Key 型の不透明な暗号鍵である Key と、適切な形式の基本の鍵データの透明な表現である鍵仕様との間の変換を行うために使用します。

java.security.Key 型のオブジェクト (java.security.PublicKeyjava.security.PrivateKey、および javax.crypto.SecretKey はそのサブクラス) は、その実装方法が不明であるため、不透明な鍵オブジェクトになります。基盤となる実装はプロバイダ依存であるため、ソフトウェアベースにもハードウェアベースにもできます。鍵ファクトリを使用すると、プロバイダは独自の暗号化鍵実装を提供できるようになります。

たとえば、公開値 y、素数モジュラス p、ベース g で構成される Diffie Hellman 公開鍵の鍵仕様を保持しており、同じ仕様を別のプロバイダの Diffie-Hellman 鍵ファクトリに送る場合、生成される PublicKey オブジェクトは大概、異なる基盤実装を保持するようになります。

プロバイダは、秘密鍵ファクトリがサポートする鍵仕様をドキュメント化する必要があります。たとえば、SunJCE プロバイダにより提供される DES 鍵の SecretKeyFactory は、DESKeySpec を DES 鍵の透明表現としてサポートします。また、DES-EDE 鍵の SecretKeyFactoryDESedeKeySpec を DES-EDE 鍵の透明表現として、PBE の SecretKeyFactoryPBEKeySpec を基盤となるパスワードの透明表現として、それぞれサポートします。

次の例は、SecretKeyFactory を使用して秘密鍵データを SecretKey オブジェクトに変換する方法を示します。これは、以降の Cipher 操作で使用できます。

    // Note the following bytes are not realistic secret key data
    // bytes but are simply supplied as an illustration of using data
    // bytes (key material) you already have to build a DESKeySpec.
    byte[] desKeyData = { (byte)0x01, (byte)0x02, (byte)0x03,
    (byte)0x04, (byte)0x05, (byte)0x06, (byte)0x07, (byte)0x08 };
    DESKeySpec desKeySpec = new DESKeySpec(desKeyData);
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
    SecretKey secretKey = keyFactory.generateSecret(desKeySpec);

この場合、SecretKey の基盤実装は、KeyFactory のプロバイダに基づきます。

別の方法として、プロバイダに依存せずに、同じ鍵データから等価な機能を持つ SecretKey オブジェクトを作成することも可能です。その場合、javax.crypto.SecretKey インタフェースを実装する javax.crypto.spec.SecretKeySpec クラスを使用します。

    byte[] desKeyData = { (byte)0x01, (byte)0x02, ...};
    SecretKeySpec secretKey = new SecretKeySpec(desKeyData, "DES");

SecretKeyFactory オブジェクトの作成

SecretKeyFactory オブジェクトは、SecretKeyFactory getInstance() static ファクトリメソッドの 1 つを使用することによって取得されます。

鍵仕様と Secret Key オブジェクト間の変換

秘密鍵用の鍵仕様がある場合は、generateSecret メソッドを使って、その仕様から不透明な SecretKey オブジェクトを取得できます。

SecretKey generateSecret(KeySpec keySpec)

Secret Key オブジェクトと鍵仕様間の変換

Secret Key オブジェクトがある場合は、getKeySpec メソッドの呼び出しによって、対応する鍵仕様オブジェクトを取得できます。

KeySpec getKeySpec(Key key, Class keySpec)
keySpec は、鍵のデータが返されるべき仕様クラスを識別します。たとえば、DESKeySpec.class は、鍵のデータが DESKeySpec クラスのインスタンスに返されるべきであることを指示します。

KeyPairGenerator クラス

KeyPairGenerator クラスはエンジンクラスで、公開鍵と非公開鍵のペアの生成に使います。

図 12: KeyPairGenerator クラス

図 12 の説明:KeyPairGenerator クラス

鍵のペアの生成方法には、アルゴリズム独立型とアルゴリズム固有型の 2 つがあります。この 2 つの唯一の相違点は、オブジェクトの初期化にあります。

次に記述するメソッドの呼び出し例は、のセクションを参照してください。

KeyPairGenerator の作成

すべての鍵のペアは、最初に KeyPairGenerator を使って生成します。KeyPairGenerator オブジェクトは、KeyPairGenerator getInstance() static ファクトリメソッドの 1 つを使用することによって取得されます。

KeyPairGenerator の初期化

特定のアルゴリズム用の鍵のペアのジェネレータは、そのアルゴリズムで使うことができる公開鍵または非公開鍵を作成します。また、アルゴリズム固有型のパラメータを生成された各鍵に関連付けます。

まず、鍵のペアを初期化してからでなければ、鍵のペアは鍵を生成できません。ほとんどの場合、アルゴリズム独立型の初期化で十分です。ただし、その他の場合は、アルゴリズム固有型の初期化を使用できます。

アルゴリズムに依存しない初期化

すべての鍵ジェネレータは、キーサイズおよび乱数発生の元の概念を共有します。キーサイズは、アルゴリズムごとに解釈が異なります。たとえば、DSA アルゴリズムの場合、キーサイズはモジュラスの長さと一致します。特定のアルゴリズムのキーサイズについては、標準名のドキュメントを参照してください。

普遍的に共有されるこれら 2 つの引数の型をとる initialize メソッドがあります。

void initialize(int keysize, SecureRandom random)
また、keysize 引数だけをとり、システムが提供する乱数の発生源を使用する initialize メソッドもあります。
void initialize(int keysize)

上記のアルゴリズムに依存しない initialize メソッドを呼び出した場合、パラメータが指定されないため、それぞれの鍵に関連したアルゴリズム固有のパラメータが存在する場合、これをどのように扱うかはプロバイダに任されます。

アルゴリズムが DSA アルゴリズムで、モジュラスのサイズ (キーサイズ) が 512、768、または 1024 の場合は、SUN プロバイダは pq、および g パラメータ用に事前に計算した値を使用します。モジュラスのサイズが前述の値の 1 つでない場合は、SUN プロバイダは、新しいパラメータのセットを作成します。これらの 3 つのモジュラスサイズ以外の、事前に計算されたパラメータセットを持つプロバイダが存在する可能性もあります。また、事前に計算されたパラメータがなく、常に新しいパラメータセットを作成するプロバイダが存在する可能性もあります。

アルゴリズム固有の初期化

アルゴリズム固有のパラメータのセットがすでに存在する状況では (DSA の「コミュニティーパラメータ」など)、AlgorithmParameterSpec 引数を取る initialize メソッドが 2 つあります。このうちの一方は SecureRandom 引数も取りますが、他方では、乱数の発生源はシステムによって提供されます。

void initialize(AlgorithmParameterSpec params,
                SecureRandom random)

void initialize(AlgorithmParameterSpec params)
詳細はのセクションを参照してください。

鍵のペアの生成

鍵のペアの生成手順は、初期化 (およびアルゴリズム) に関係なく、常に同じです。必ず KeyPairGenerator から次のメソッドを呼び出します。

KeyPair generateKeyPair()
generateKeyPair を呼び出すたびに、異なる鍵のペアが作られます。

KeyGenerator クラス

鍵ジェネレータは、対称アルゴリズム用の秘密鍵を生成します。

図 13: KeyGenerator クラス

図 13 の説明:KeyGenerator クラス

KeyGenerator の作成

KeyGenerator オブジェクトは、KeyGenerator getInstance() static ファクトリメソッドの 1 つを使用することによって取得されます。

KeyGenerator オブジェクトの初期化

特定の対称鍵アルゴリズムの鍵ジェレネータは、そのアルゴリズムで使用可能な対称鍵を作成します。また、生成された鍵に、アルゴリズム固有のパラメータ (存在する場合) を関連付けます。

鍵の生成方法には、アルゴリズム独立型とアルゴリズム固有型の 2 つがあります。この 2 つの唯一の相違点は、オブジェクトの初期化にあります。

クライアントが (init メソッドの呼び出しによって) KeyGenerator を明示的に初期化しない場合は、各プロバイダがデフォルトの初期化を提供 (および文書化) する必要があります。

鍵の作成

次のメソッドにより、秘密鍵が生成されます。
    public SecretKey generateKey();

KeyAgreement クラス

鍵協定とは、複数のパーティーが秘密情報を交換しなくても同じ暗号化鍵を確立可能なプロトコルを指します。

図 14: KeyAgreement クラス

図 14 の説明:KeyAgreement クラス

各パーティーは、非公開鍵によって鍵協定オブジェクトを初期化し、通信に参加する各パーティーの公開鍵を入力します。ほとんどの場合は 2 つのパーティーのみですが、Diffie-Hellman などのアルゴリズムでは 3 つ以上のパーティーの参加が可能です。公開鍵がすべて入力されると、各 KeyAgreement オブジェクトは同じ鍵を生成 (合意) します。

KeyAgreement クラスは、鍵協定プロトコルの機能を提供します。共有の秘密の確立に関係する鍵は、鍵ジェネレータのいずれか (KeyPairGenerator または KeyGenerator) か KeyFactory によって、または鍵協定プロトコルの中間フェーズの結果として作成されます。

KeyAgreement オブジェクトの作成

鍵協定に関係する各パーティーは、KeyAgreement オブジェクトを作成する必要があります。KeyAgreement オブジェクトは、KeyAgreement getInstance() static ファクトリメソッドの 1 つを使用することによって取得されます。

KeyAgreement オブジェクトの初期化

非公開情報を使用して KeyAgreement オブジェクトを初期化できます。Diffie-Hellman の場合、Diffie-Hellman 非公開鍵を使用して初期化します。補足的な初期化情報には、乱数の発生源、アルゴリズムパラメータのセットが含まれます。要求された鍵協定アルゴリズムで、アルゴリズムパラメータを指定する必要があり、また KeyAgreement オブジェクトの初期化にパラメータではなく鍵だけが提供される場合、必須のアルゴリズムパラメータを鍵に含める必要があります。たとえば、Diffie-Hellman アルゴリズムは、素数モジュラス p およびベースジェネレータ g をパラメータとして使用します。

KeyAgreement オブジェクトを初期化する場合、次のいずれかの init メソッドを呼び出します。

    public void init(Key key);

    public void init(Key key, SecureRandom random);

    public void init(Key key, AlgorithmParameterSpec params);

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

KeyAgreement フェーズの実行

各協定プロトコルは、鍵協定に関係する各パーティーが実行する必要のある多数のフェーズで構成されます。

鍵協定の次のフェーズを実行するには、doPhase メソッドを呼び出します。

    public Key doPhase(Key key, boolean lastPhase);

key パラメータには、そのフェーズで処理する鍵が含まれます。ほとんどの場合、これは、鍵協定に関係するほかのパーティーのいずれかの公開鍵、または前のフェーズで生成された中間鍵です。doPhase は、この鍵協定のほかのパーティーに送信する必要のある中間鍵を返すため、続くフェーズでその鍵を処理できます。

lastPhase パラメータには、実行するフェーズが鍵協定の最後のフェーズかどうかを指定します。値 FALSE は、これが鍵協定の最後のフェーズではない (このあとにフェーズが続く) ことを示します。値 TRUE は、これが鍵協定の最後のフェーズであり、鍵協定が完了する (次に generateSecret が呼び出される) ことを示します。

2 つのパーティー間の Diffie-Hellman の例では、lastPhaseTRUE に設定して doPhase を呼び出します。Diffie-Hellman で 3 つのパーティーが存在する場合、doPhase を 2 度呼び出します。最初は lastPhaseFALSE に設定し、2 度目には lastPhaseTRUE に設定します。

共有される秘密の生成

各パーティーがすべての必須鍵協定フェーズを実行したあとで、generateSecret メソッドのいずれかを呼び出して共有される秘密を計算できます。

    public byte[] generateSecret();

    public int generateSecret(byte[] sharedSecret, int offset);

    public SecretKey generateSecret(String algorithm);

鍵管理

「キーストア」と呼ばれるデータベースは、鍵および証明書のリポジトリを管理するために使用できます。証明書とは、あるエンティティーが発行したデジタル署名付きの文書で、別なエンティティーの公開鍵が特定の値であることを証明しています。

キーストアの場所

ユーザーキーストアは、デフォルトではユーザーのホームディレクトリの .keystore という名前のファイルに格納されます。ユーザーのホームディレクトリは、"user.home" システムプロパティーによって決まります。Solaris システムの場合、"user.home" がデフォルトでユーザーのホームディレクトリになっています。Win32 システムの場合、指定されたユーザー名が uName のとき、"user.home" のデフォルトは次のように設定されます。

キーストアファイルは必要な場所に配置できます。環境によっては、複数のキーストアが存在することに意味がある場合があります。たとえば、JSSE (SSL/TLS) では、1 つのキーストアがユーザーの非公開鍵を保持し、別のキーストアが信頼関係を確立するために使用される証明書を保持する場合があります。

ユーザーのキーストア以外に、JDK は、さまざまな証明書発行局 (CA) からの信頼できる証明書を格納するために使用されるシステム全体のキーストアも保持します。これらの CA 証明書は、信頼性の判定を行う場合に使用できます。たとえば、SSL/TLS で SunJSSE プロバイダがリモートピアから証明書を提示された場合、デフォルトのトラストマネージャーは、

ファイルに問い合わせて、接続が信頼できるかどうかを判断します。システム全体の cacerts キーストアを使用する代わりに、アプリケーションは独自のキーストアを設定して使用するか、前述のユーザーキーストアを使用できます。

キーストアの実装

KeyStore クラスは、キーストア内の情報へのアクセスおよび情報の変更を行うための、明確に定義されたインタフェースを提供します。複数の異なる固定実装を生成できます (それぞれ特定ののキーストアを実装)。

現在、KeyStore を利用するコマンド行ツールには、keytooljarsigner の 2 つがあります。また、GUI ベースの policytool というツールもあります。さまざまなソースのコードに対して (システムリソースへの) アクセス権を指定するポリシーファイルを処理する場合、このツールは Policy のリファレンス実装によっても使われます。KeyStore は public として使用可能なので、JDK ユーザーは KeyStore を使ったほかのセキュリティーアプリケーションも作成できます。

アプリケーションでは、KeyStore クラスの getInstance ファクトリメソッドを使うことで、さまざまなプロバイダから異なるのキーストア実装を選択できます。キーストアの型は、キーストア情報のストレージ形式とデータ形式を定義するとともに、キーストア内の非公開鍵とキーストア自体の整合性を保護するために使われるアルゴリズムを定義します。異なる型のキーストアの実装には、互換性はありません。

Sun が提供する jks という組み込みのデフォルトのキーストア実装の型があります。これは、独自のキーストアの型 (形式) を利用するもので、キーストアをファイルとして実装しています。この実装では、個々の非公開鍵は独自の個別のパスワードによって保護され、キーストア全体の整合性も (非公開鍵とは別の) パスワードによって保護されます。デフォルトは、セキュリティープロパティーファイル内の次の行によって指定されています。

    keystore.type=jks

ツールおよびほかのアプリケーションで異なるデフォルトのキーストア実装を使用するには、この行を変更してデフォルトの型を指定します。「jceks」という型のキーストア実装を提供するプロバイダパッケージがある場合は、この行を次のように変更します。

    keystore.type=jceks
keytool などの一部のアプリケーションでも、-storetype コマンド行パラメータによって、デフォルトのキーストアの型をオーバーライドできます。
注: キーストアの型の指定では、大文字と小文字は区別されません。たとえば、「JKS」と「jks」は同じものとして扱われます。
ほかに 2 つの型のキーストアが、JDK 実装に付属しています。
  1. 「jceks」は、「jks」に対する独自の代替キーストア形式であり、トリプル DES によるパスワードベースの暗号化形式の、大幅に強力な暗号化を使用します。

    Sun の「jceks」実装は、「jks」キーストアファイルを解析して「jceks」形式に変換できます。キーストア内の非公開鍵エントリのパスワードを変更し、キーストアの型として "-storetype jceks" を指定することによって、「jks」型のキーストアを「jceks」型のキーストアにアップグレードできます。提供される (より) 強力な暗号化による鍵保護を、デフォルトキーストア内の「signkey」という名前の非公開鍵に適用する場合は、次のコマンドを入力します。このコマンドにより、旧パスワードおよび新規パスワードの指定が求められます。

        keytool -keypasswd -alias signkey -storetype jceks
    
    keytool とキーストア、およびその管理方法の詳細は、「セキュリティーツール」を参照してください。
  2. 「pkcs12」がもうひとつのオプションです。これは、RSA PKCS12 Personal Information Exchange Syntax Standard に基づくクロスプラットフォームキーストアです。この規格は、ユーザーの非公開鍵、証明書、その他の秘密を格納および転送することを主な目的としています。JDK 6 では、信頼できる証明書を「pkcs12」に格納する規格はまだ確立されていないため、「jks」または「jceks」を信頼できる証明書に使用する必要があります。

キーストアの実装は、プロバイダベースです。独自のキーストア実装を記述することに関心がある開発者は、「Java 暗号化アーキテクチャー用プロバイダの実装方法」でこのトピックの詳細を参照してください。

KeyStore クラス

KeyStore クラスは、キーストア内の情報へのアクセスおよび情報の変更を行うための明確に定義されたインタフェースを提供するエンジンクラスです。

図 15: KeyStore クラス

図 15 の説明:KeyStore クラス

このクラスは、メモリー内の鍵および証明書のコレクションを表します。KeyStore は次の 2 種類のエントリを管理します。

鍵エントリ

この種類のキーストアエントリには、非常に重要な暗号化鍵の情報が保持されます。情報は、保護された形式で格納され、権限のないアクセスを防ぎます。通常、この種類のエントリで格納される鍵は非公開鍵で、対応する公開鍵を証明する証明書連鎖が伴います。

非公開鍵および証明書連鎖は、デジタル署名を使った自己認証用に特定のエンティティーが使用します。たとえば、ソフトウェア配布団体は、リリースまたはライセンスするソフトウェアの一部として JAR ファイルにデジタル署名を付けます。

信頼できる証明書エントリ

この種類のエントリには、別の組織に属する単一の公開鍵が含まれます。これは信頼できる証明書と呼ばれますが、それは、キーストアの所有者が、証明書内の公開鍵が実際に証明書のサブジェクト (所有者) によって識別されたアイデンティティーに属することを信頼するためです。

この種類のエントリは、ほかの組織の認証に使うことができます。

キーストア内の各エントリは、「別名」文字列によって識別されます。非公開鍵とそれに関連付けられた証明書連鎖の場合は、これらの文字列は、エンティティー自体が認証するというように、方法べつに区別されます。たとえば、エンティティーが異なる証明書発行局を使ったり、異なる公開鍵アルゴリズムを使ったりして、エンティティー自体を認証することも可能です。

キーストアが持続性があるかどうか、および持続性がある場合に使われるメカニズムは、ここでは指定されません。この規則により、重要な (秘密または非公開) 鍵を保護するためのさまざまな技術を使うことができます。スマートカードまたはその他の統合暗号化エンジン (SafeKeyper) を使うことも 1 つの方法です。また、ファイルなどのより単純なメカニズムをさまざまな形式で使うこともできます。

主要な KeyStore メソッドを次で説明します。

KeyStore オブジェクトの作成

KeyStore オブジェクトは、KeyStore getInstance() static ファクトリメソッドの 1 つを使用することによって取得されます。

特定キーストアのメモリーへのロード

KeyStore オブジェクトを使う前に、load メソッドによってメモリー内に実際のキーストアデータをロードする必要があります。
final void load(InputStream stream, char[] password)
オプションのパスワードを使って、キーストアデータの整合性をチェックします。パスワードが提供されない場合は、整合性のチェックは行われません。

空のキーストアを作成するには、InputStream 引数として nullload メソッドに渡します。

キーストアの別名一覧の取得

キーストアのすべてのエントリには、一意の別名を介してアクセスします。aliases メソッドは、キーストア内の別名の列挙を返します。

final Enumeration aliases()

キーストアエントリの種類の決定

KeyStore クラス」で説明したように、キーストアのエントリには、2 種類あります。

次のメソッドは、与えられた別名によって指定されたエントリが、それぞれ鍵または証明書か、信頼できる証明書エントリであることを決定します。

final boolean isKeyEntry(String alias)
final boolean isCertificateEntry(String alias)

キーストアエントリの追加、設定、および削除

setCertificateEntry メソッドは、証明書を指定された別名に割り当てます。
final void setCertificateEntry(String alias, Certificate cert)
alias が存在しない場合は、その別名のついた信頼できる証明書のエントリが作成されます。alias が存在し、信頼できる証明書のエントリが識別された場合は、それに関連付けられた証明書が cert によって置き換えられます。

setKeyEntry メソッドは、alias がまだ存在しない場合に、鍵のエントリの追加または設定を行います。

final void setKeyEntry(String alias,
                       Key key,
                       char[] password,
                       Certificate[] chain)

final void setKeyEntry(String alias,
                       byte[] key,
                       Certificate[] chain)
バイト配列として key を取るメソッドでは、この引数は、保護された形式の鍵のバイトです。たとえば、SUN プロバイダによって提供されるキーストアの実装では、key バイト配列は、PKCS8 標準の定義に従って EncryptedPrivateKeyInfo としてエンコードされた、保護された非公開鍵を格納します。もう一方のメソッドの password は、鍵の保護に使うパスワードです。

deleteEntry メソッドは、エントリを削除します。

final void deleteEntry(String alias)

キーストアからの情報の取得

getKey メソッドは、与えられた別名に関連付けられた鍵を返します。鍵は、与えられたパスワードを使って復元されます。
final Key getKey(String alias, char[] password)
次のメソッドは、与えられた別名に関連付けられた証明書、または証明書連鎖をそれぞれ返します。
final Certificate getCertificate(String alias)
final Certificate[] getCertificateChain(String alias)
次の文を使って、与えられた証明書と一致した最初のエントリの名前 (alias) を決定できます。
final String getCertificateAlias(Certificate cert)

キーストアの保存

メモリー内のキーストアを、store メソッドを使って保存できます。
final void store(OutputStream stream, char[] password)
パスワードは、キーストアデータの統合チェックサムの計算に使われます。統合チェックサムは、キーストアデータのあとに追加されます。

アルゴリズムパラメータのクラス

Key および Keyspec と同様に、アルゴリズムの初期化パラメータは AlgorithmParameter または AlgorithmParameterSpec によって表されます。使用状況に応じて、アルゴリズムはパラメータを直接使用できます。または、伝送またはストレージのためにパラメータを移植性の高い形式に変換する必要がある場合があります。

AlgorithmParameterSpec によるパラメータのセットの透明な表現とは、セットの各パラメータの値に個別にアクセスできることを意味します。これらの値には、対応する仕様クラスに定義された get メソッドの 1 つを使ってアクセスできます (DSAParameterSpec では、getPgetQ、および getG メソッドを定義して、それぞれ pq、および g にアクセスするなど)。

これに対して、AlgorithmParameters クラスは不透明な表現を提供します。「不透明な」表現では、パラメータフィールドに直接アクセスできません。パラメータセットに関連付けられたアルゴリズム名の取得 (getAlgorithm による)、およびそのパラメータセット用のある種のエンコードの取得 (getEncoded による) しかできません。

AlgorithmParameterSpec インタフェース

AlgorithmParameterSpec は、暗号化パラメータの透明な仕様へのインタフェースです。このインタフェースには、メソッドまたは定数が含まれていません。このインタフェースの唯一の目的は、すべてのパラメータの仕様をグループ化すること (およびそれらのパラメータに安全な型を提供すること) です。すべてのパラメータの仕様で、このインタフェースを実装する必要があります。

java.security.spec および javax.crypto.spec パッケージに含まれるアルゴリズムパラメータ仕様のインタフェースおよびクラスについては、JDK javadoc で説明されています。

次のアルゴリズムパラメータ仕様は、JSR 105 の一部として特にデジタル署名に使用されます。

AlgorithmParameters クラス

AlgorithmParameters クラスは、暗号化パラメータの不透明な表現を提供するエンジンクラスです。特定の AlgorithmParameterSpec オブジェクトを使用するか、または認識される書式でパラメータをエンコードすることによって、AlgorithmParameters クラスを初期化できます。結果の仕様を getParameterSpec メソッドで取得できます (次のセクションを参照)。

AlgorithmParameters オブジェクトの作成

AlgorithmParameters オブジェクトは、AlgorithmParameters getInstance() static ファクトリメソッドの 1 つを使用することによって取得されます。

AlgorithmParameters オブジェクトの初期化

AlgorithmParameters オブジェクトのインスタンスが生成されたら、適切なパラメータ仕様またはパラメータのエンコードにより、init を呼び出すことで初期化する必要があります。

void init(AlgorithmParameterSpec paramSpec)
void init(byte[] params)
void init(byte[] params, String format)
これらの init メソッドで、params は、エンコードされたパラメータを含む配列で、format は、デコード形式の名前です。params 引数を指定し、format 引数を指定しない init メソッドでは、パラメータのプライマリデコード形式が使われます。パラメータの ASN.1 仕様が存在する場合は、プライマリデコード形式は、ASN.1 です。
注: AlgorithmParameters オブジェクトは、1 回だけ初期化できます。再利用はできません。

エンコードされたパラメータの取得

AlgorithmParameters オブジェクトで表現されるパラメータのバイトエンコードは、getEncoded への呼び出しによって取得できます。

byte[] getEncoded()
このメソッドは、パラメータをプライマリエンコード形式で返します。この種のパラメータの ASN.1 仕様が存在する場合は、プライマリ復号化形式は、ASN.1 です。

特定のエンコード形式でパラメータが返されるようにするには、次のように記述します。

byte[] getEncoded(String format)
format が null の場合は、ほかの getEncoded メソッドと同様に、プライマリエンコード形式が使われます。
注: SUN プロバイダによって提供されるデフォルトの AlgorithmParameters の実装では、format 引数は、現在のところ無視されます。

AlgorithmParameters オブジェクトから透明な仕様への変換

アルゴリズムパラメータの透明なパラメータの仕様は、getParameterSpec への呼び出しにより、AlgorithmParameters オブジェクトから取得できます。

AlgorithmParameterSpec getParameterSpec(Class paramSpec)
paramSpec は、パラメータが返されるべき仕様クラスを識別します。たとえば、仕様クラス DSAParameterSpec.class が識別された場合、パラメータが DSAParameterSpec クラスのインスタンスに返されるべきであることを示します。このクラスは java.security.spec パッケージにあります。

AlgorithmParameterGenerator クラス

AlgorithmParameterGenerator クラスは、特定のアルゴリズムに適した新しいパラメータのセットの生成に使用されるエンジンクラスです。アルゴリズムは、AlgorithmParameterGenerator のインスタンスの作成時に指定されます。このオブジェクトは、既存のアルゴリズムパラメータのセットがなく、ゼロから生成する場合に使用されます。

AlgorithmParameterGenerator オブジェクトの作成

AlgorithmParameterGenerator オブジェクトは、AlgorithmParameterGenerator getInstance() static ファクトリメソッドの 1 つを使用することによって取得されます。

AlgorithmParameterGenerator オブジェクトの初期化

AlgorithmParameterGenerator オブジェクトは、アルゴリズム独立型、またはアルゴリズム固有型の 2 種類の方法で初期化できます。

アルゴリズム独立型の方法では、すべてのパラメータジェネレータが「サイズ」および乱数発生の元という概念を共有するという特性を利用します。サイズの単位は、すべてアルゴリズムパラメータで普遍的に共通していますが、その解釈はアルゴリズムにより異なります。たとえば、DSA アルゴリズムのパラメータの場合、「サイズ」は素数モジュラスのビット数のサイズに一致します。特定のアルゴリズムのサイズについては、標準名のドキュメントを参照してください。この方法を使うと、アルゴリズム固有型のパラメータの生成値が (ある場合は)、デフォルト基準値になります。普遍的に共有されるこれら 2 つの引数の型をとる init メソッドが 1 つあります。

void init(int size, SecureRandom random);
また、size 引数だけをとり、システムが提供する乱数の発生源を使用する init メソッドもあります。
void init(int size)

3 番目の方法では、パラメータジェネレータオブジェクトの初期化にアルゴリズム固有型のセマンティクスを使います。アルゴリズム固有型のセマンティクスは、AlgorithmParameterSpec オブジェクト内に提供されるアルゴリズム固有型のパラメータ生成値のセットによって表されます。

void init(AlgorithmParameterSpec genParamSpec,
                          SecureRandom random)

void init(AlgorithmParameterSpec genParamSpec)
たとえば、Diffie-Hellman システムパラメータを生成するには、通常、パラメータ生成値は、プライムモジュラスおよびランダム指数のサイズで構成されます。どちらのサイズもビット数で指定します。

アルゴリズムパラメータの生成

AlgorithmParameterGenerator オブジェクトを作成および初期化したら、generateParameters メソッドを使ってアルゴリズムパラメータを生成できます。
AlgorithmParameters generateParameters()

CertificateFactory クラス

CertificateFactory クラスは、証明書ファクトリの機能を定義するエンジンクラスです。証明書ファクトリは、証明書および証明書の取り消しリスト (CRL) オブジェクトをそのエンコードから生成するために使用されます。

X.509 の証明書ファクトリは、java.security.cert.X509Certificate のインスタンスである証明書、および java.security.cert.X509CRL のインスタンスである CRL を返します。

CertificateFactory オブジェクトの作成

CertificateFactory オブジェクトは、getInstance() static ファクトリメソッドの 1 つを使用することによって取得されます。

証明書オブジェクトの生成

証明書オブジェクトを生成し、入力ストリームから読み込まれたデータを使って初期化するには、generateCertificate メソッドを使います。
final Certificate generateCertificate(InputStream inStream)
特定の入力ストリームから読み込まれた証明書のコレクションビュー (空の可能性もある) を返すには、generateCertificates メソッドを使います。
final Collection generateCertificates(InputStream inStream)

CRL オブジェクトの生成

証明書の取り消しリスト (CRL) オブジェクトを生成し、入力ストリームから読み込まれたデータを使って初期化するには、generateCRL メソッドを使います。
final CRL generateCRL(InputStream inStream)
特定の入力ストリームから読み込まれた CRL のコレクションビュー (空の可能性もある) を返すには、generateCRLs メソッドを使います。
final Collection generateCRLs(InputStream inStream)

CertPath オブジェクトの生成

PKIX 用の証明書パスビルダーおよびバリデータは、RFC 3280「Internet X.509 Public Key Infrastructure Certificate and CRL Profile」によって定義されます。

PKIX LDAP V2 スキーマを使用して証明書と CRL を Collection ディレクトリおよび LDAP ディレクトリから取得するための証明書ストアの実装も、RFC 2587 として IETF から使用可能です。

CertPath オブジェクトを生成し、そのオブジェクトを入力ストリームから読み込まれたデータを使って初期化するには、次のいずれかの generateCertPath メソッドを使用します (必要に応じて、データに対して使用するエンコーディングを指定します)。

final CertPath generateCertPath(InputStream inStream)

final CertPath generateCertPath(InputStream inStream,
                                String encoding)
CertPath オブジェクトを生成し、証明書のリストを使用して初期化するには、次のメソッドを使用します。
final CertPath generateCertPath(List certificates)
証明書ファクトリでサポートされている CertPath エンコーディングのリストを取得するには、getCertPathEncodings メソッドを呼び出します。
final Iterator getCertPathEncodings()
初めにデフォルトのエンコーディングが一覧表示されます。

SSL/TLS 実装での JCA の使用方法

JCA のクラスについて理解したため、次に、これらのクラスがどのように結合されて SSL/TLS のような高度なネットワークプロトコルを実装するかについて考えます。「JSSE リファレンスガイド」の SSL/TLS の概要のセクションに、プロトコルの機能の高レベルの説明があります。非対称 (公開鍵) 暗号操作は対称操作 (秘密鍵) よりも大幅に遅いため、公開鍵暗号方式は、実際のアプリケーションデータを保護するために使用される秘密鍵を確立する場合に使用されます。非常に単純に言うと、SSL/TLS ハンドシェークには、初期化データの交換、いくつかの公開鍵操作の実行による秘密鍵への到達、およびその鍵を使用したそのあとのトラフィックの暗号化が含まれます。
注: ここで説明する詳細では、単純に、前述のクラスがどのように使用されるかを示します。このセクションでは、SSL/TLS 実装を構築するための十分な情報は提供しません。詳細は、「JSSE リファレンスガイド」および「RFC 2246: The TLS Protocol」を参照してください。

この SSL/TLS 実装が JSSE プロバイダとして使用可能になるとします。Provider クラスの固定実装が最初に記述され、最終的にプロバイダの Security クラスのリストに登録されます。このプロバイダは、主に、アルゴリズム名から実際の実装クラスへのマッピングを提供します。たとえば、「SSLContext.TLS」->「com.foo.TLSImpl」です。アプリケーションが SSLContext.getInstance("TLS")) によって「TLS」インスタンスを要求すると、要求されたアルゴリズムがプロバイダのリストに問い合わせされ、適切なインスタンスが作成されます。

実際のハンドシェークの詳細を説明する前に、JSSE のアーキテクチャーのいくつかを簡単に確認する必要があります。JSSE アーキテクチャーの中心は、SSLContext です。コンテキストは、最終的に SSL/TLS プロトコルを実際に実装する最終オブジェクト (SSLSocket および SSLEngine) を作成します。SSLContext は、2 つのコールバッククラス KeyManager および TrustManager によって初期化されます。これらを使用すると、アプリケーションは送信する認証データを最初に選択し、次にピアによって送信された資格を検証できます。

JSSE KeyManager は、ピアに提示する資格の選択を行います。多くのアルゴリズムが可能ですが、一般的な方法は、RSA または DSA 公開鍵と非公開鍵のペアを、ディスクファイルに基づく KeyStore 内の X509Certificate とともに保持することです。KeyStore オブジェクトが初期化されてファイルからロードされると、ファイルの未変換のバイトは KeyFactory を使用して PublicKey および PrivateKey オブジェクトに変換され、証明書連鎖のバイトは CertificateFactory を使用して変換されます。資格が必要な場合、KeyManager は単純にこの KeyStore オブジェクトに問い合わせ、どの資格を提示するかを判断します。

KeyStore の内容は、最初に keytool などのユーティリティーを使用して作成されている場合があります。keytool は、RSA または DSA KeyPairGenerator を作成し、適切なキーサイズで初期化します。このジェネレータは、次に、keytool が新しく作成された証明書とともに KeyStore に格納する KeyPair (最終的にディスクに書き込まれる) を作成する場合に使用されます。

JSSE TrustManager は、ピアから受信した資格の検証を行います。資格の検証には多数の方法があります。そのひとつは、CertPath オブジェクトを作成し、JDK の組み込み公開鍵インフラストラクチャー (PKI) フレームワークで検証を処理する方法です。内部的に、CertPath 実装は Signature オブジェクトを作成し、それを使用して証明書連鎖内のそれぞれの署名を検証する場合があります。

アーキテクチャーの基本について理解することによって、SSL/TLS ハンドシェークの手順のいくつかを調べることができます。クライアントは、ClientHello メッセージをサーバーに送信することによって開始します。サーバーは使用する暗号群を選択し、ServerHello メッセージ内でそれを返信し、暗号群の選択に基づいて JCA オブジェクトの作成を開始します。次の例では、サーバーのみの認証を使用します。

図 16: SSL メッセージ

図 16 の説明:SSL メッセージ

最初の例では、サーバーは TLS_RSA_WITH_AES_128_CBC_SHA などの RSA ベースの暗号群を使用しようとします。サーバーの KeyManager がクエリーされ、適切な RSA エントリを返します。サーバーの資格 (つまり、証明書/公開鍵) がサーバーの Certificate メッセージ内で送信されます。クライアントの TrustManager はサーバーの証明書を検証し、受け入れた場合、クライアントは SecureRandom オブジェクトを使用していくつかのランダムバイトを生成します。次に、サーバーの証明書内で見つかった PublicKey によって初期化された暗号化非対称 RSA Cipher オブジェクトを使用して暗号化されます。この暗号化されたデータは、Client Key Exchange メッセージ内で送信されます。サーバーは、対応する PrivateKey を使用し、同様の Cipher を復号モードで使用してバイトを回復します。これらのバイトは、実際の暗号化鍵を確立する場合に使用されます。

別の例では、一時的な Diffie-Hellman 鍵協定アルゴリズムと TLS_DHE_DSS_WITH_AES_128_CBC_SHA などの DSA 署名アルゴリズムが選択されます。両方の側でそれぞれ、KeyPairGenerator を使用して、新しい一時的な DH 公開鍵と非公開鍵のペアを確立します。各ジェネレータは、KeyFactory および DHPublicKeySpec クラスを使用して、さらに細かく変換できる DH 鍵を作成します。それぞれの側は、次に KeyAgreement オブジェクトを作成し、それぞれの DH PrivateKey によって初期化します。サーバーはその公開鍵を ServerKeyExchange メッセージ内で送信し (DSA 署名アルゴリズムによって保護される)、クライアントはその公開鍵を ClientKeyExchange メッセージ内で送信します。公開鍵が別の KeyFactory を使用して再度組み立てられると、これらは協定オブジェクトに渡されます。KeyAgreement オブジェクトは、実際の暗号化鍵を確立する場合に使用される合意済みのバイトを生成します。

実際の暗号化鍵が確立されると、対称 Cipher オブジェクトを初期化するために秘密鍵が使用され、この暗号が送信中のすべてのデータを保護する場合に使用されます。データが変更されたかどうかを判断できるように MessageDigest が作成され、ネットワークに送信されるデータのコピーを受信します。パケットが完全な場合、ダイジェスト (ハッシュ) がデータのあとに付加され、パケット全体が Cipher によって暗号化されます。AES などのブロック暗号が使用される場合は、データにパディングを行なって完全なブロックにします。リモート側では、手順は単純に逆になります。

これは非常に単純化されていますが、これらのクラスがどのように結合されて高レベルのプロトコルを作成するかを示しています。

アプリケーションの暗号化制限の「免責」を取得する方法

注意 1: 大半のアプリケーション開発者には、このセクションで説明する内容は関係ありません。関係があるのは、作成するアプリケーションが、政府により暗号化制限の課された国に輸出される可能性があり、アプリケーションをその制限に適合させる必要がある場合だけです。注意 2: このセクション全体で、「アプリケーション」という語は、アプリケーションとアプレットの両方を指します。

JCA フレームワークには、暗号化アルゴリズムおよび最大暗号化強度に関する制限を施行する機能が含まれており、この制限は、さまざまな管轄コンテキスト (位置) でアプレット/アプリケーションから利用可能です。これらの制限はすべて、「管轄ポリシーファイル」に指定されます。

輸入管理制限された国が存在するため、Java SE Development Kit 6 に同梱される管轄ポリシーファイルでは、「強力」ではあっても制限付きの暗号化が使用可能になっています。適格国 (大半の国が該当) の在住者は、暗号化機能に制限のない「強度無制限」のバージョンを利用できます。ただし、政府が制限を課しているこれらの国が輸入できるのは「強力な」バージョンだけです。JCA フレームワークでは、インストール済みの管轄ポリシーファイルで指定された制限が施行されます。

これらの国の一部またはすべてで、特定のアプリケーションに対し、暗号化制限の一部またはすべての免責が許可されています。たとえば、特定の種類のアプリケーションは「特別」と見なされ、免責されます。また、鍵復元などの「免責メカニズム」を利用するアプリケーションは、免責可能です。この種の国では、免責されたと見なされるアプリケーションは、免責されていないアプリケーションに許可されるよりも強力な暗号化にアクセスできます。

実行時にアプリケーションが「免責されている」と認識されるようにするには、次の条件を満たす必要があります。

次に、暗号化制限の一部またはすべての免責をアプリケーションに設定するために必要な手順のサンプルを示します。これは、免責されたものとしてアプリケーションを認識および処理するため、JCA により要求される情報を含む、基本情報です。実際には、アプリケーションを実行可能にする (政府が暗号化制限を課している) 特定の国の免責要件を知る必要があります。また、免責されたアプリケーションの処理プロセスを保持する JCA フレームワークベンダーの要件も理解しておく必要があります。詳細は、ベンダーにお尋ね下さい。(注: SunJCE プロバイダは、ExemptionMechanismSpi クラスの実装を提供しません。)


免責メカニズムを使用するアプリケーションに対する特殊コード要件

アプリケーションが関連付けられたアクセス権ポリシーファイルを (同じ JAR ファイル内に) 保持し、アクセス権ポリシーファイルで免責メカニズムが指定されている場合、Cipher getInstance メソッドが呼び出されて Cipher がインスタンス化されると、JCA コードはインストール済みのプロバイダ内で指定された免責メカニズムを実装するものを検索します。目的のプロバイダが見つかると、JCA は、プロバイダの実装に関連付けられた ExemptionMechanism API オブジェクトをインスタンス化してから、ExemptionMechanism オブジェクトを getInstance が返す Cipher と関連付けます。

Cipher をインスタンス化したあと、かつ初期化する前に (Cipher init メソッドを呼び出して)、コードから次の Cipher メソッドを呼び出す必要があります。

    public ExemptionMechanism getExemptionMechanism()

この呼び出しにより、Cipher に関連付けられた ExemptionMechanism オブジェクトが返されます。次に、返された ExemptionMechanism に対して次のメソッドを実行して、免責メカニズムの実装を初期化する必要があります。

    public final void init(Key key)

ここで指定する引数の型は、このあとで Cipher init メソッドに指定する引数の型と同じにする必要があります。

ExemptionMechanism の初期化が完了したら、通常と同じ方法で Cipher を初期化して使用できます。

アクセス権ポリシーファイル

実行時にアプリケーションが暗号化制限の一部またはすべてを「免責」されていると認識されるには、JAR ファイル内にアクセス権ポリシーファイルをバンドルする必要があります。アクセス権ポリシーファイルには、アプリケーションが保持する暗号化関連のアクセス権、およびそれを保持する条件 (存在する場合) を指定します。

注: アプリケーションにバンドルするアクセス権ポリシーファイルの名前は、cryptoPerms にする必要があります。

免責されるアプリケーションにバンドルされるアクセス権ポリシーファイル内のアクセス権エントリの書式は、JDK とともにダウンロードされる管轄ポリシーファイルの書式と同じです。次にその書式を示します。

    permission <crypto permission class name>[ <alg_name>
        [[, <exemption mechanism name>][, <maxKeySize>
        [, <AlgorithmParameterSpec class name>,
        <parameters for constructing an AlgorithmParameterSpec object>
        ]]]];

管轄ポリシーファイルの書式の詳細は、「付録 B」を参照してください。

免責されるアプリケーションのアクセス権ポリシーファイル

アプリケーションの中には、制限を完全に解除可能なものもあります。通常、その種のアプリケーションにバンドルするアクセス権ポリシーファイルには、次を含めるだけで十分です。

    grant {
        // There are no restrictions to any algorithms.
        permission javax.crypto.CryptoAllPermission;
    };

アプリケーションが 1 つの (またはいくつかの特定の) アルゴリズムだけを使用する場合、アクセス権ポリシーファイルには、CryptoAllPermission を付与するのではなく、そのアルゴリズムを明示的に記述します。たとえば、アプリケーションが Blowfish アルゴリズムだけを使用する場合、アクセス権ポリシーファイルですべてのアルゴリズムに CryptoAllPermission を付与する必要はありません。Blowfish アルゴリズムが使用される場合、暗号化制限が存在しないことを指定するだけで十分です。この場合、アクセス権ポリシーファイルは、次のようになります。

    grant {
        permission javax.crypto.CryptoPermission "Blowfish";
    };

免責メカニズムにより免責されるアプリケーションのアクセス権ポリシーファイル

免責メカニズムが導入されるためにアプリケーションが「免責される」と見なされる場合、アプリケーションにバンドルされるアクセス権ポリシーファイルに 1 つ以上の免責メカニズムを指定する必要があります。実行時に、これらの免責メカニズムのいずれかが機能していると、アプリケーションは免責されたものと見なされます。次のようなアクセス権エントリ内に、各免責メカニズムを指定する必要があります。

    // No algorithm restrictions if specified
    // exemption mechanism is enforced.
    permission javax.crypto.CryptoPermission *,
        "<ExemptionMechanismName>";

ここで、<ExemptionMechanismName> には免責メカニズムの名前を指定します。指定可能な免責メカニズムの名前には、次が含まれます。

例として、鍵復元または鍵エスクローのいずれかが機能すると、アプリケーションが免責される場合を考えましょう。その場合、アクセス権ポリシーファイルには、次が含まれます。
    grant {
        // No algorithm restrictions if KeyRecovery is enforced.
        permission javax.crypto.CryptoPermission *,
            "KeyRecovery";
        // No algorithm restrictions if KeyEscrow is enforced.
        permission javax.crypto.CryptoPermission *,
            "KeyEscrow";
    };

注:免責メカニズムを指定するアクセス権エントリには、最大鍵サイズを指定してはいけません。許可される鍵のサイズは、実際にはインストールされた免責管轄ポリシーファイルにより決定されます。詳細は、次のセクションを参照してください。

バンドルされたアクセス権ポリシーファイルによる暗号化アクセス権への影響

実行時にアプリケーションが (getInstance メソッドを呼び出すことで) Cipher をインスタンス化し、かつそのアプリケーションが関連するアクセス権ポリシーファイルを保持する場合、JCA はアクセス権ポリシーファイルに getInstance の呼び出しで指定されたアルゴリズムに適用されるエントリが含まれるかどうかをチェックします。該当するエントリが含まれ、エントリが CryptoAllPermission を付与するか免責メカニズムの実施を指定しない場合、このアルゴリズムには暗号化制限が存在しないことを意味します。

アクセス権ポリシーファイルに getInstance の呼び出しで指定されたアルゴリズムに適用されるエントリが含まれ、かつエントリで免責メカニズムの実施が指定されている場合、免責管轄ポリシーファイルがチェックされます。免責されるアクセス権が関連するアルゴリズムおよび免責メカニズムのエントリを含み、そのエントリがアプリケーションにバンドルされるアクセス権ポリシーファイル内のアクセス権により暗黙的に設定されている場合、および指定された免責メカニズムの実装がいずれかの登録済みプロバイダから利用可能な場合、Cipher の最大鍵サイズおよびアルゴリズムパラメータ値は、免責アクセス権エントリにより決定されます。

アプリケーションにバンドルされるアクセス権ポリシーファイル内の関連するエントリに、暗黙的に設定された免責アクセス権が存在しない場合、またはいずれかの登録済みプロバイダから利用可能な、指定された免責メカニズムの実装が存在しない場合、デフォルトの標準暗号化アクセス権のみがアプリケーションに付与されます。

コード例

次に示すのは、JCA メカニズムのいくつかの使用を説明する短い例です。また、全体を示す実際の例が「付録 D」にあります。

MessageDigest オブジェクトの計算

最初に、次の例のようにしてメッセージダイジェストを作成します。

MessageDigest sha = MessageDigest.getInstance("SHA-1");
この呼び出しは、正しく初期化されたメッセージダイジェストオブジェクトを変数 sha に代入します。この実装は、National Institute for Standards and Technology (NIST) の FIPS 180-2 ドキュメントの定義に従って、Secure Hash アルゴリズム (SHA-1) を実装します。標準名とアルゴリズムに関する詳細は、「付録 A」を参照してください。

次に、3 つのバイト配列 i1i2、および i3 があるとします。計算するメッセージダイジェストを持つ合計入力は、この 3 つの配列から形成されます。このダイジェスト (または「ハッシュ」) は、次の呼び出しを使って計算できます。

sha.update(i1);
sha.update(i2);
sha.update(i3);
byte[] hash = sha.digest();

次の一連の呼び出しを使っても同じです。

sha.update(i1);
sha.update(i2);
byte[] hash = sha.digest(i3);
メッセージダイジェストが計算されると、メッセージダイジェストオブジェクトは自動的にリセットされ、新しいデータを受信してそのダイジェストを計算できる状態になります。以前の状態 (たとえば update 呼び出しで入れたデータ) はすべて失われます。

ハッシュ実装によっては、複製 (コピー) を介して中間ハッシュをサポートするものもあります。次について、別々のハッシュを計算すると仮定します。

次のように処理します。

/* compute the hash for i1 */
sha.update(i1);
byte[] i1Hash = sha.clone().digest();

/* compute the hash for i1 and i2 */
sha.update(i2);
byte[] i12Hash = sha.clone().digest();

/* compute the hash for i1, i2 and i3 */
sha.update(i3);
byte[] i123hash = sha.digest();
このコードは、SHA-1 実装が複製 (コピー) 可能な場合にだけ機能します。メッセージダイジェストの実装の中には、複製 (コピー) 可能なものもあれば、不可能なものもあります。複製 (コピー) が可能かどうかを判断するには、次のように MessageDigest オブジェクトを複製し、発生する可能性のある例外をキャッチします。
try {
    // try and clone it
    /* compute the hash for i1 */
    sha.update(i1);
    byte[] i1Hash = sha.clone().digest();
    // ...
    byte[] i123hash = sha.digest();
} catch (CloneNotSupportedException cnse) {
    // do something else, such as the code shown below
}
メッセージダイジェストが複製 (コピー) 不可能な場合は、別の多少複雑な方法を使います。つまり、複数のダイジェストを生成して中間ダイジェストを計算します。この場合、計算する中間ダイジェストの数をあらかじめ知っておく必要があります。
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
MessageDigest sha12 = MessageDigest.getInstance("SHA-1");
MessageDigest sha123 = MessageDigest.getInstance("SHA-1");

byte[] i1Hash = sha1.digest(i1);

sha12.update(i1);
byte[] i12Hash = sha12.digest(i2);

sha123.update(i1);
sha123.update(i2);
byte[] i123Hash = sha123.digest(i3);

鍵のペアの生成

この例では、「DSA」(デジタル署名アルゴリズム) という名前のアルゴリズムについて、「公開 - 非公開」の鍵のペアを生成し、この鍵のペアをそのあとの例で使用します。鍵は、1024 ビットモジュラスで生成します。ここでは、アルゴリズム実装を提供するプロバイダは考慮しません。

KeyPairGenerator の生成

最初の手順は、DSA アルゴリズムの鍵を生成するための鍵のペアジェネレータオブジェクトの獲得です。

KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA");

KeyPairGenerator の初期化

次の手順は、鍵のペアジェネレータの初期化です。ほとんどの場合、アルゴリズム独立型の初期化で十分ですが、アルゴリズム固有型の初期化を使う必要がある場合もあります。

アルゴリズムに依存しない初期化

すべての鍵ジェネレータは、キーサイズおよび乱数発生の元の概念を共有します。KeyPairGenerator クラス初期化メソッドには、最低限キーサイズが必要です。乱数の発生源が明示的に提供されない場合、もっとも高い優先順位でインストールされているプロバイダの SecureRandom 実装が使用されます。したがって、キーサイズ 1024 で鍵を生成するには、次のコードを呼び出すだけです。

SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");
keyGen.initialize(1024, random);
次のコードは、特定の、追加でシードされる SecureRandom オブジェクトを使用する方法を示しています。
SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");
random.setSeed(userSeed);
keyGen.initialize(1024, random);
前述のアルゴリズム独立型の initialize メソッドを呼び出した場合、パラメータが指定されないため、それぞれの鍵に関連したアルゴリズム固有のパラメータが存在する場合、これをどのように扱うかはプロバイダに任されます。プロバイダは、事前に計算されたパラメータ値を使うことも、新しい値を生成することもできます。

アルゴリズム固有の初期化

アルゴリズム固有のパラメータのセットがすでに存在する状況では (DSA の「コミュニティーパラメータ」など)、AlgorithmParameterSpec 引数を取る initialize メソッドが 2 つあります。鍵のペアのジェネレータが DSA アルゴリズムのジェネレータで、DSA 特定パラメータのセット pq、および g があり、これを使って鍵のペアを生成したいとします。次のコードを実行して鍵のペアのジェネレータを初期化できます。DSAParameterSpec は AlgorithmParameterSpec です。

DSAParameterSpec dsaSpec = new DSAParameterSpec(p, q, g);
SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");
random.setSeed(userSeed);
keyGen.initialize(dsaSpec, random);
注: 名前が p のパラメータは、長さがモジュラスの長さ (「サイズ」) の素数です。したがって、モジュラスの長さを指定するためにほかのメソッドを呼び出す必要はありません。

鍵のペアの生成

最終手順は、鍵のペアの実際の生成です。使用した初期化の型 (アルゴリズム独立型またはアルゴリズム固有型) に関係なく、同じコードを使って鍵のペアを生成します。
KeyPair pair = keyGen.generateKeyPair();

生成された鍵を使った署名の生成および検証

次の署名の生成および検証の例では、前述の鍵のペアの例で生成された KeyPair を利用しています。

署名の生成

最初に署名オブジェクトを生成します。

Signature dsa = Signature.getInstance("SHA1withDSA");
鍵のペアの例で生成した鍵のペアを使い、非公開鍵を指定してオブジェクトを初期化します。このあとで data というバイト配列に署名を付けます。
/* Initializing the object with a private key */
PrivateKey priv = pair.getPrivate();
dsa.initSign(priv);

/* Update and sign the data */
dsa.update(data);
byte[] sig = dsa.sign();

署名の検証

署名の検証は簡単です。ここでも、鍵のペアの例で生成した鍵のペアを使います。
/* Initializing the object with the public key */
PublicKey pub = pair.getPublic();
dsa.initVerify(pub);

/* Update and verify the data */
dsa.update(data);
boolean verifies = dsa.verify(sig);
System.out.println("signature verifies: " + verifies);

鍵仕様および KeyFactory を使った署名の生成と検証

前述の鍵のペアの例で生成したような公開鍵と非公開鍵のペアを使うよりも、単に DSA 非公開鍵のコンポーネントx (非公開鍵)、p (素数)、q (サブ素数)、および g (ベース) を使うとします。

さらに、非公開鍵を使って someData というバイト配列内のあるデータにデジタル署名を付けたいとします。次の手順を実行します。この手順は、鍵仕様の作成、および鍵ファクトリを使って鍵仕様から PrivateKey を取得する方法も示しています。initSign には PrivateKey が必要です。

DSAPrivateKeySpec dsaPrivKeySpec = new DSAPrivateKeySpec(x, p, q, g);

KeyFactory keyFactory = KeyFactory.getInstance("DSA");
PrivateKey privKey = keyFactory.generatePrivate(dsaPrivKeySpec);

Signature sig = Signature.getInstance("SHA1withDSA");
sig.initSign(privKey);
sig.update(someData);
byte[] signature = sig.sign();
Alice が署名したデータを使いたいとします。彼女がそのデータを使うため、および署名を検証するためには、彼女に次の 3 つのものを送る必要があります。
  1. データ
  2. 署名
  3. データの署名に使った非公開鍵に対応する公開鍵。
someData バイトをあるファイルに格納し、signature バイトを別のファイルに格納し、両方を Alice に送ることができます。

公開鍵に関しては、上の署名の例のように、データの署名に使った DSA 非公開鍵に対応する DSA 公開鍵のコンポーネントを持っていると想定します。この場合は、コンポーネントから DSAPublicKeySpec を作成できます。

DSAPublicKeySpec dsaPubKeySpec = new DSAPublicKeySpec(y, p, q, g);
ただし、鍵バイトをファイルに記述できるように、鍵バイトを抽出する必要があります。このためには、まず、前の例ですでに作成した DSA 鍵ファクトリの generatePublic メソッドを呼び出します。
PublicKey pubKey = keyFactory.generatePublic(dsaPubKeySpec);
次に、次のコードを使って (エンコードされた) 鍵バイトを抽出します。
byte[] encKey = pubKey.getEncoded();
この時点でバイトをファイルに保存し、データおよび署名を保存したファイルと一緒に Alice に送ることができます。

ここで、Alice がこれらのファイルを受け取り、データファイルのデータバイトを data というバイト配列に、署名ファイルの署名バイトを signature というバイト配列に、公開鍵ファイルのエンコードされた公開鍵バイトを encodedPubKey というバイト配列にコピーしたとします。

Alice は、次のコードを実行して署名を検証できます。このコードは、エンコードされた状態から DSA 公開鍵のインスタンスを生成するために、鍵ファクトリを使う方法も示しています。initVerify には PublicKey が必要です。

    X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(encodedPubKey);

    KeyFactory keyFactory = KeyFactory.getInstance("DSA");
    PublicKey pubKey = keyFactory.generatePublic(pubKeySpec);

    Signature sig = Signature.getInstance("SHA1withDSA");
    sig.initVerify(pubKey);
    sig.update(data);
    sig.verify(signature);
注:前述のコードでは、initVerifyPublicKey を必要とするため、Alice はエンコードされた鍵ビットから PublicKey を生成する必要がありました。PublicKey を取得したら、KeyFactory getKeySpec メソッドを使って、PublicKey を DSAPublicKeySpec に変換して、必要な場合にコンポーネントにアクセスすることが可能です。
    DSAPublicKeySpec dsaPubKeySpec =
        (DSAPublicKeySpec)keyFactory.getKeySpec(pubKey,
            DSAPublicKeySpec.class)
これで Alice は、DSA 公開鍵コンポーネント ypq、および g に、DSAPublicKeySpec クラスの対応する get メソッド (getYgetPgetQ、および getG) を使ってアクセスできます。

2 つの鍵の同一性の判定

しばしば、2 つの鍵の同一性の判定が必要になることがありますが、デフォルトの java.lang.Object.equals メソッドでは、必要な結果が得られない場合があります。プロバイダに対する依存度がもっとも少ない方法は、エンコードされた鍵を比較する方法です。この方法で適切に比較できない場合 (たとえば、RSAPrivateKeyRSAPrivateCrtKey を比較する場合)、各コンポーネントを比較する必要があります。その方法を示すコードを次に示します。

static boolean keysEqual(Key key1, Key key2) {
    if (key1.equals(key2)) {
        return true;
    }

    if (Arrays.equals(key1.getEncoded(), key2.getEncoded())) {
        return true;
    }

    // More code for different types of keys here.
    // For example, the following code can check if
    // an RSAPrivateKey and an RSAPrivateCrtKey are equal:
    // if ((key1 instanceof RSAPrivateKey) &&
    //     (key2 instanceof RSAPrivateKey)) {
    //     if ((key1.getModulus().equals(key2.getModulus())) &&
    //         (key1.getPrivateExponent().equals(
    //                                      key2.getPrivateExponent()))) {
    //         return true;
    //     }
    // }

    return false;
}

Base64 でエンコードされた証明書の読み取り

次の例は、Base64でエンコードされた証明書を読み取ります。認証の開始は、次のコードで指定されます。

-----BEGIN CERTIFICATE-----
最後は、次の行で終わります。
-----END CERTIFICATE-----
generateCertificate の各呼び出しが 1 つの証明書だけを使用し、入力ストリームの読み出し位置がファイル内の次の証明書に置かれるように、FileInputStream (mark および reset をサポートしていない) を ByteArrayInputStream (これらのメソッドをサポート) に変換します。
FileInputStream fis = new FileInputStream(filename);
BufferedInputStream bis = new BufferedInputStream(fis);

CertificateFactory cf = CertificateFactory.getInstance("X.509");

while (bis.available() > 0) {
    Certificate cert = cf.generateCertificate(bis);
    System.out.println(cert.toString());
}

認証応答の解析

次の例は、ファイル内に保存された PKCS7 形式の認証応答を解析し、そこから証明書をすべて抽出します。

FileInputStream fis = new FileInputStream(filename);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Collection c = cf.generateCertificates(fis);
Iterator i = c.iterator();
while (i.hasNext()) {
   Certificate cert = (Certificate)i.next();
   System.out.println(cert);
}

暗号化の使用

このセクションでは、鍵の生成、Cipher オブジェクトの作成と初期化、およびファイルの暗号化と復号化という一連の処理について説明します。この例全体で、Advanced Encryption Standard (AES) を使用します。

鍵の生成

AES 鍵を作成するには、AES 用の KeyGenerator をインスタンス化する必要があります。特定の AES 鍵生成実装について考慮する必要はないため、プロバイダは指定しません。KeyGenerator を初期化しないため、AES 鍵の作成にはシステム提供の乱数発生源およびデフォルトのキーサイズが使用されます。

    KeyGenerator keygen = KeyGenerator.getInstance("AES");
    SecretKey aesKey = keygen.generateKey();

鍵を生成したあと、同じ KeyGenerator を使用してほかの鍵を再度作成できます。

Cipher の作成

次のステップは、Cipher インスタンスの作成です。これには、Cipher クラスのいずれかの getInstance ファクトリメソッドを使用します。次のコンポーネントを含む必須の変換名を、スラッシュ (/) で区切って指定する必要があります。

この例では、Electronic Codebook モードおよび PKCS5 パディングで AES 暗号を作成します。特定の必須変換の実装について考慮する必要はないため、プロバイダは指定しません。

AES の標準アルゴリズム名は「AES」、Electronic Codebook モードの標準名は「ECB」、PKCS5 パディングの標準名は「PKCS5Padding」です。

    Cipher aesCipher;

    // Create the cipher
    aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");

前で生成された aesKey を使用して、Cipher オブジェクトを暗号化用に初期化します。

    // Initialize the cipher for encryption
    aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);

    // Our cleartext
    byte[] cleartext = "This is just an example".getBytes();

    // Encrypt the cleartext
    byte[] ciphertext = aesCipher.doFinal(cleartext);

    // Initialize the same cipher for decryption
    aesCipher.init(Cipher.DECRYPT_MODE, aesKey);

    // Decrypt the ciphertext
    byte[] cleartext1 = aesCipher.doFinal(ciphertext);

cleartextcleartext1 は、同一です。

パスワードベース暗号化の使用

この例では、ユーザーにパスワードを要求し、暗号化鍵をそのパスワードから導き出します。

java.lang.String 型のオブジェクトにパスワードを収集および格納するのは、適切と考えられます。ただし、注意すべき点があります。それは、String 型のオブジェクトは不変であるということです。このため、使用後に String の内容を変更 (上書き) またはゼロにするようなメソッドは存在しません。この機能のために、String オブジェクトは、ユーザーパスワードなどセキュリティー上重要な情報の格納には適しません。セキュリティー関連の情報は、常に char 型の配列に収集および格納するようにしてください。

この理由で、javax.crypto.spec.PBEKeySpec クラスは、パスワードを char 型の配列として受け取り、返します。入力ストリームから文字配列パスワードを読み込む 1 つの方法として、「付録 D」のサンプルコード内の ReadPassword クラスを参照してください。

PKCS5 で定義されたパスワードベース暗号化 (PBE) を使用するには、salt および iteration count を指定する必要があります。復号化時にも、暗号化時と同じ salt および繰り返し処理の回数を使用する必要があります。

            PBEKeySpec pbeKeySpec;
            PBEParameterSpec pbeParamSpec;
            SecretKeyFactory keyFac;

            // Salt
            byte[] salt = {
                (byte)0xc7, (byte)0x73, (byte)0x21, (byte)0x8c,
                (byte)0x7e, (byte)0xc8, (byte)0xee, (byte)0x99
            };

            // Iteration count
            int count = 20;

            // Create PBE parameter set
            pbeParamSpec = new PBEParameterSpec(salt, count);

            // Prompt user for encryption password.
            // Collect user password as char array (using the
            // "readPassword" method from above), and convert
            // it into a SecretKey object, using a PBE key
            // factory.
            System.out.print("Enter encryption password:  ");
            System.out.flush();
            pbeKeySpec = new PBEKeySpec(readPassword(System.in));
            keyFac = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
            SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);

            // Create PBE Cipher
            Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");

            // Initialize PBE Cipher with key and parameters
            pbeCipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);

            // Our cleartext
            byte[] cleartext = "This is another example".getBytes();

            // Encrypt the cleartext
            byte[] ciphertext = pbeCipher.doFinal(cleartext);

鍵協定の使用

2 つおよび 3 つのパーティー間で Diffie-Hellman 鍵交換を実行するサンプルプログラムに関しては、「付録 D」を参照してください。


付録 A: 標準名

JDK Security API は、さまざまなアルゴリズム、証明書、およびキーストアのタイプの標準名を必要とし、これらを使用します。以前にこの付録 A およびその他のセキュリティー仕様 (JSSE/CertPath/その他) にあった仕様名は、標準名のドキュメントにまとめられました。このドキュメントには、アルゴリズムの仕様に関する詳細も含まれています。特定のプロバイダの情報は、「Sun Provider Documentation」にあります。

JDK での暗号化実装は、主に歴史的な理由により、さまざまなプロバイダによって配布されます (SunSunJSSESunJCESunRsaSign)。これらのプロバイダは、すべての JDK 実装で使用可能ではない場合があるため、真に移殖可能なアプリケーションは、特定のプロバイダを指定せずに getInstance() を呼び出す必要があります。特定のプロバイダを指定するアプリケーションは、基盤となるオペレーティング環境 (PKCS や Microsoft の CAPI など) 用に調整されたネイティブプロバイダを使用できない場合があります。

SunPKCS11 プロバイダ自体には暗号化アルゴリズムは含まれていませんが、代わりに要求を基盤となる PKCS11 実装に送ります。「PKCS#11 リファレンスガイド」および基盤となる PKCS11 実装を参照して、必要なアルゴリズムが PKCS11 プロバイダで使用可能かどうかを判断するようにしてください。同様に、Windows システムでは、SunMSCAPI プロバイダは暗号化機能を提供しませんが、代わりに基盤となるオペレーティングシステムに要求を処理するよう渡します。


付録 B: 管轄ポリシーファイルの形式

JCA の管轄ポリシーファイルは、Java 形式のポリシーファイル (対応するアクセス権を指定する文を含む) で表されます。「デフォルトの Policy の実装とポリシーファイルの構文」に説明されているように、Java ポリシーファイルでは、指定されたコードソースからコードに付与するアクセス権を指定します。アクセス権は、システムリソースへのアクセスを表します。JCA の場合、「リソース」は暗号化アルゴリズムです。また、暗号化制限はすべてのコードに適用されるため、コードソースを指定する必要はありません。

管轄ポリシーファイルは、1 つ以上の「アクセス権エントリ」を含む、非常に基本的な「付与エントリ」で構成されます。

grant {
<permission entries>;
};

次に、管轄ポリシーファイルのアクセス権エントリの書式を示します。

permission <crypto permission class name>[ <alg_name>
    [[, <exemption mechanism name>][, <maxKeySize>
    [, <AlgorithmParameterSpec class name>,
    <parameters for constructing an
        AlgorithmParameterSpec object>]]]];

次に、「Blowfish」アルゴリズムを 64 ビットの最大鍵サイズに制限する、管轄ポリシーファイルのサンプルを示します。

    grant {
        permission javax.crypto.CryptoPermission "Blowfish", 64;
        // ...
    };

アクセス権エントリは、permission で始まります。前述のテンプレート内の <crypto permission class name> には、javax.crypto.CryptoPermission などの具体的なアクセス権クラス名を指定します。暗号化アクセス権クラスは、特定の環境下で特定の鍵サイズを使用するアプリケーション/アプレットの機能に対応します。暗号化アクセス権クラスには、CryptoPermission および CryptoAllPermission の 2 つが存在します。特別な CryptoAllPermission クラスは、暗号化関連のアクセス権すべてを表します。つまり、暗号化関連の制限はないことを示します。

<alg_name> を指定する場合、「DES」や「RSA」など暗号化アルゴリズムの標準名を表す文字列 (「付録 A」を参照) を引用符で囲んで指定します。

<免責メカニズム名> を指定する場合、免責メカニズムを指す文字列を引用符で囲んで指定します。免責メカニズムを実施すると、暗号化制限が緩和されます。使用可能な免責メカニズム名には、「KeyRecovery」、「KeyEscrow」、および「KeyWeakening」が含まれます。

<maxKeySize> には、指定したアルゴリズムに許可する最大鍵サイズ (ビット) を示す整数値を指定します。

アルゴリズムによっては、鍵サイズでアルゴリズムの強度を指定するだけでは不十分な場合があります。たとえば、「RC5」アルゴリズムの場合には、ラウンド数も考慮する必要があります。アルゴリズムの強度を鍵サイズだけで表現するのでは不十分な場合、アクセス権エントリで AlgorithmParameterSpec クラス名 (javax.crypto.spec.RC5ParameterSpec など)、および指定された AlgorithmParameterSpec オブジェクトの構築用パラメータリストも指定する必要があります。

アクセス権エントリの各項目は、指定された順序で記述する必要があります。各エントリはセミコロンで終わります。

識別子 (grantpermission) では大文字と小文字は区別されませんが、<crypto permission class name> と、値として引き渡される文字列については大文字と小文字が区別されます。

注:「*」は、すべてのアクセス権エントリオプションでワイルドカードとして使用できます。たとえば、<alg_name> オプションに「*」を指定すると、「すべてのアルゴリズム」という意味になります。


付録 C: 「強力」な管轄ポリシーファイルにより許可される最大鍵サイズ

輸入管理制限があるため、Java SE Development Kit に同梱された管轄ポリシーファイルは「強固」ですが、暗号化の使用には制限があります。詳細については、「暗号化アルゴリズムの輸入制限」を参照してください。


付録 D: サンプルプログラム

2 つのパーティー間の Diffie-Hellman 鍵交換

/*
 * Copyright (c) 1997, 2001, Oracle and/or its affiliates. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Oracle nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.*;
import java.security.interfaces.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.crypto.interfaces.*;
import com.sun.crypto.provider.SunJCE;

/**
 * This program executes the Diffie-Hellman key agreement protocol
 * between 2 parties: Alice and Bob.
 *
 * By default, preconfigured parameters (1024-bit prime modulus and base
 * generator used by SKIP) are used.
 * If this program is called with the "-gen" option, a new set of
 * parameters is created.
 */

public class DHKeyAgreement2 {

    private DHKeyAgreement2() {}

    public static void main(String argv[]) {
        try {
            String mode = "USE_SKIP_DH_PARAMS";

            DHKeyAgreement2 keyAgree = new DHKeyAgreement2();

            if (argv.length > 1) {
                keyAgree.usage();
                throw new Exception("Wrong number of command options");
            } else if (argv.length == 1) {
                if (!(argv[0].equals("-gen"))) {
                    keyAgree.usage();
                    throw new Exception("Unrecognized flag: " + argv[0]);
                }
                mode = "GENERATE_DH_PARAMS";
            }

            keyAgree.run(mode);
        } catch (Exception e) {
            System.err.println("Error: " + e);
            System.exit(1);
        }
    }

    private void run(String mode) throws Exception {

        DHParameterSpec dhSkipParamSpec;

        if (mode.equals("GENERATE_DH_PARAMS")) {
            // Some central authority creates new DH parameters
            System.out.println
                ("Creating Diffie-Hellman parameters (takes VERY long) ...");
            AlgorithmParameterGenerator paramGen
                = AlgorithmParameterGenerator.getInstance("DH");
            paramGen.init(512);
            AlgorithmParameters params = paramGen.generateParameters();
            dhSkipParamSpec = (DHParameterSpec)params.getParameterSpec
                (DHParameterSpec.class);
        } else {
            // use some pre-generated, default DH parameters
            System.out.println("Using SKIP Diffie-Hellman parameters");
            dhSkipParamSpec = new DHParameterSpec(skip1024Modulus,
                                                  skip1024Base);
        }

        /*
         * Alice creates her own DH key pair, using the DH parameters from
         * above
         */
        System.out.println("ALICE: Generate DH keypair ...");
        KeyPairGenerator aliceKpairGen = KeyPairGenerator.getInstance("DH");
        aliceKpairGen.initialize(dhSkipParamSpec);
        KeyPair aliceKpair = aliceKpairGen.generateKeyPair();

        // Alice creates and initializes her DH KeyAgreement object
        System.out.println("ALICE: Initialization ...");
        KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH");
        aliceKeyAgree.init(aliceKpair.getPrivate());

        // Alice encodes her public key, and sends it over to Bob.
        byte[] alicePubKeyEnc = aliceKpair.getPublic().getEncoded();

        /*
         * Let's turn over to Bob. Bob has received Alice's public key
         * in encoded format.
         * He instantiates a DH public key from the encoded key material.
         */
        KeyFactory bobKeyFac = KeyFactory.getInstance("DH");
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec
            (alicePubKeyEnc);
        PublicKey alicePubKey = bobKeyFac.generatePublic(x509KeySpec);

        /*
         * Bob gets the DH parameters associated with Alice's public key.
         * He must use the same parameters when he generates his own key
         * pair.
         */
        DHParameterSpec dhParamSpec = ((DHPublicKey)alicePubKey).getParams();

        // Bob creates his own DH key pair
        System.out.println("BOB: Generate DH keypair ...");
        KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH");
        bobKpairGen.initialize(dhParamSpec);
        KeyPair bobKpair = bobKpairGen.generateKeyPair();

        // Bob creates and initializes his DH KeyAgreement object
        System.out.println("BOB: Initialization ...");
        KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH");
        bobKeyAgree.init(bobKpair.getPrivate());

        // Bob encodes his public key, and sends it over to Alice.
        byte[] bobPubKeyEnc = bobKpair.getPublic().getEncoded();

        /*
         * Alice uses Bob's public key for the first (and only) phase
         * of her version of the DH
         * protocol.
         * Before she can do so, she has to instantiate a DH public key
         * from Bob's encoded key material.
         */
        KeyFactory aliceKeyFac = KeyFactory.getInstance("DH");
        x509KeySpec = new X509EncodedKeySpec(bobPubKeyEnc);
        PublicKey bobPubKey = aliceKeyFac.generatePublic(x509KeySpec);
        System.out.println("ALICE: Execute PHASE1 ...");
        aliceKeyAgree.doPhase(bobPubKey, true);

        /*
         * Bob uses Alice's public key for the first (and only) phase
         * of his version of the DH
         * protocol.
         */
        System.out.println("BOB: Execute PHASE1 ...");
        bobKeyAgree.doPhase(alicePubKey, true);

        /*
         * At this stage, both Alice and Bob have completed the DH key
         * agreement protocol.
         * Both generate the (same) shared secret.
         */
        byte[] aliceSharedSecret = aliceKeyAgree.generateSecret();
        int aliceLen = aliceSharedSecret.length;

        byte[] bobSharedSecret = new byte[aliceLen];
        int bobLen;
        try {
            // show example of what happens if you
            // provide an output buffer that is too short
            bobLen = bobKeyAgree.generateSecret(bobSharedSecret, 1);
        } catch (ShortBufferException e) {
            System.out.println(e.getMessage());
        }
        // provide output buffer of required size
        bobLen = bobKeyAgree.generateSecret(bobSharedSecret, 0);

        System.out.println("Alice secret: " +
          toHexString(aliceSharedSecret));
        System.out.println("Bob secret: " +
          toHexString(bobSharedSecret));

        if (!java.util.Arrays.equals(aliceSharedSecret, bobSharedSecret))
            throw new Exception("Shared secrets differ");
        System.out.println("Shared secrets are the same");

        /*
         * Now let's return the shared secret as a SecretKey object
         * and use it for encryption. First, we generate SecretKeys for the
         * "DES" algorithm (based on the raw shared secret data) and
         * then we use DES in ECB mode
         * as the encryption algorithm. DES in ECB mode does not require any
         * parameters.
         *
         * Then we use DES in CBC mode, which requires an initialization
         * vector (IV) parameter. In CBC mode, you need to initialize the
         * Cipher object with an IV, which can be supplied using the
         * javax.crypto.spec.IvParameterSpec class. Note that you have to use
         * the same IV for encryption and decryption: If you use a different
         * IV for decryption than you used for encryption, decryption will
         * fail.
         *
         * NOTE: If you do not specify an IV when you initialize the
         * Cipher object for encryption, the underlying implementation
         * will generate a random one, which you have to retrieve using the
         * javax.crypto.Cipher.getParameters() method, which returns an
         * instance of java.security.AlgorithmParameters. You need to transfer
         * the contents of that object (e.g., in encoded format, obtained via
         * the AlgorithmParameters.getEncoded() method) to the party who will
         * do the decryption. When initializing the Cipher for decryption,
         * the (reinstantiated) AlgorithmParameters object must be passed to
         * the Cipher.init() method.
         */
        System.out.println("Return shared secret as SecretKey object ...");
        // Bob
        // NOTE: The call to bobKeyAgree.generateSecret above reset the key
        // agreement object, so we call doPhase again prior to another
        // generateSecret call
        bobKeyAgree.doPhase(alicePubKey, true);
        SecretKey bobDesKey = bobKeyAgree.generateSecret("DES");

        // Alice
        // NOTE: The call to aliceKeyAgree.generateSecret above reset the key
        // agreement object, so we call doPhase again prior to another
        // generateSecret call
        aliceKeyAgree.doPhase(bobPubKey, true);
        SecretKey aliceDesKey = aliceKeyAgree.generateSecret("DES");

        /*
         * Bob encrypts, using DES in ECB mode
         */
        Cipher bobCipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
        bobCipher.init(Cipher.ENCRYPT_MODE, bobDesKey);

        byte[] cleartext = "This is just an example".getBytes();
        byte[] ciphertext = bobCipher.doFinal(cleartext);

        /*
         * Alice decrypts, using DES in ECB mode
         */
        Cipher aliceCipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
        aliceCipher.init(Cipher.DECRYPT_MODE, aliceDesKey);
        byte[] recovered = aliceCipher.doFinal(ciphertext);

        if (!java.util.Arrays.equals(cleartext, recovered))
            throw new Exception("DES in CBC mode recovered text is " +
              "different from cleartext");
        System.out.println("DES in ECB mode recovered text is " +
            "same as cleartext");

        /*
         * Bob encrypts, using DES in CBC mode
         */
        bobCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
        bobCipher.init(Cipher.ENCRYPT_MODE, bobDesKey);

        cleartext = "This is just an example".getBytes();
        ciphertext = bobCipher.doFinal(cleartext);
        // Retrieve the parameter that was used, and transfer it to Alice in
        // encoded format
        byte[] encodedParams = bobCipher.getParameters().getEncoded();

        /*
         * Alice decrypts, using DES in CBC mode
         */
        // Instantiate AlgorithmParameters object from parameter encoding
        // obtained from Bob
        AlgorithmParameters params = AlgorithmParameters.getInstance("DES");
        params.init(encodedParams);
        aliceCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
        aliceCipher.init(Cipher.DECRYPT_MODE, aliceDesKey, params);
        recovered = aliceCipher.doFinal(ciphertext);

        if (!java.util.Arrays.equals(cleartext, recovered))
            throw new Exception("DES in CBC mode recovered text is " +
              "different from cleartext");
        System.out.println("DES in CBC mode recovered text is " +
            "same as cleartext");
    }

    /*
     * Converts a byte to hex digit and writes to the supplied buffer
     */
    private void byte2hex(byte b, StringBuffer buf) {
        char[] hexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
                            '9', 'A', 'B', 'C', 'D', 'E', 'F' };
        int high = ((b & 0xf0) >> 4);
        int low = (b & 0x0f);
        buf.append(hexChars[high]);
        buf.append(hexChars[low]);
    }

    /*
     * Converts a byte array to hex string
     */
    private String toHexString(byte[] block) {
        StringBuffer buf = new StringBuffer();

        int len = block.length;

        for (int i = 0; i < len; i++) {
             byte2hex(block[i], buf);
             if (i < len-1) {
                 buf.append(":");
             }
        }
        return buf.toString();
    }

    /*
     * Prints the usage of this test.
     */
    private void usage() {
        System.err.print("DHKeyAgreement usage: ");
        System.err.println("[-gen]");
    }

    // The 1024 bit Diffie-Hellman modulus values used by SKIP
    private static final byte skip1024ModulusBytes[] = {
        (byte)0xF4, (byte)0x88, (byte)0xFD, (byte)0x58,
        (byte)0x4E, (byte)0x49, (byte)0xDB, (byte)0xCD,
        (byte)0x20, (byte)0xB4, (byte)0x9D, (byte)0xE4,
        (byte)0x91, (byte)0x07, (byte)0x36, (byte)0x6B,
        (byte)0x33, (byte)0x6C, (byte)0x38, (byte)0x0D,
        (byte)0x45, (byte)0x1D, (byte)0x0F, (byte)0x7C,
        (byte)0x88, (byte)0xB3, (byte)0x1C, (byte)0x7C,
        (byte)0x5B, (byte)0x2D, (byte)0x8E, (byte)0xF6,
        (byte)0xF3, (byte)0xC9, (byte)0x23, (byte)0xC0,
        (byte)0x43, (byte)0xF0, (byte)0xA5, (byte)0x5B,
        (byte)0x18, (byte)0x8D, (byte)0x8E, (byte)0xBB,
        (byte)0x55, (byte)0x8C, (byte)0xB8, (byte)0x5D,
        (byte)0x38, (byte)0xD3, (byte)0x34, (byte)0xFD,
        (byte)0x7C, (byte)0x17, (byte)0x57, (byte)0x43,
        (byte)0xA3, (byte)0x1D, (byte)0x18, (byte)0x6C,
        (byte)0xDE, (byte)0x33, (byte)0x21, (byte)0x2C,
        (byte)0xB5, (byte)0x2A, (byte)0xFF, (byte)0x3C,
        (byte)0xE1, (byte)0xB1, (byte)0x29, (byte)0x40,
        (byte)0x18, (byte)0x11, (byte)0x8D, (byte)0x7C,
        (byte)0x84, (byte)0xA7, (byte)0x0A, (byte)0x72,
        (byte)0xD6, (byte)0x86, (byte)0xC4, (byte)0x03,
        (byte)0x19, (byte)0xC8, (byte)0x07, (byte)0x29,
        (byte)0x7A, (byte)0xCA, (byte)0x95, (byte)0x0C,
        (byte)0xD9, (byte)0x96, (byte)0x9F, (byte)0xAB,
        (byte)0xD0, (byte)0x0A, (byte)0x50, (byte)0x9B,
        (byte)0x02, (byte)0x46, (byte)0xD3, (byte)0x08,
        (byte)0x3D, (byte)0x66, (byte)0xA4, (byte)0x5D,
        (byte)0x41, (byte)0x9F, (byte)0x9C, (byte)0x7C,
        (byte)0xBD, (byte)0x89, (byte)0x4B, (byte)0x22,
        (byte)0x19, (byte)0x26, (byte)0xBA, (byte)0xAB,
        (byte)0xA2, (byte)0x5E, (byte)0xC3, (byte)0x55,
        (byte)0xE9, (byte)0x2F, (byte)0x78, (byte)0xC7
    };

    // The SKIP 1024 bit modulus
    private static final BigInteger skip1024Modulus
    = new BigInteger(1, skip1024ModulusBytes);

    // The base used with the SKIP 1024 bit modulus
    private static final BigInteger skip1024Base = BigInteger.valueOf(2);
}

3 つのパーティー間の Diffie-Hellman 鍵交換

/*
 * Copyright (c) 1997, 2001, Oracle and/or its affiliates. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Oracle nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.*;
import java.security.interfaces.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.crypto.interfaces.*;
import com.sun.crypto.provider.SunJCE;

/**
 * This program executes the Diffie-Hellman key agreement protocol
 * between 3 parties: Alice, Bob, and Carol.
 *
 * We use the same 1024-bit prime modulus and base generator that are
 * used by SKIP.
 */

public class DHKeyAgreement3 {

    private DHKeyAgreement3() {}

    public static void main(String argv[]) {
        try {
            DHKeyAgreement3 keyAgree = new DHKeyAgreement3();
            keyAgree.run();
        } catch (Exception e) {
            System.err.println("Error: " + e);
            System.exit(1);
        }
    }

    private void run() throws Exception {

        DHParameterSpec dhSkipParamSpec;

        System.out.println("Using SKIP Diffie-Hellman parameters");
        dhSkipParamSpec = new DHParameterSpec(skip1024Modulus, skip1024Base);

        // Alice creates her own DH key pair
        System.out.println("ALICE: Generate DH keypair ...");
        KeyPairGenerator aliceKpairGen = KeyPairGenerator.getInstance("DH");
        aliceKpairGen.initialize(dhSkipParamSpec);
        KeyPair aliceKpair = aliceKpairGen.generateKeyPair();

        // Bob creates his own DH key pair
        System.out.println("BOB: Generate DH keypair ...");
        KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH");
        bobKpairGen.initialize(dhSkipParamSpec);
        KeyPair bobKpair = bobKpairGen.generateKeyPair();

        // Carol creates her own DH key pair
        System.out.println("CAROL: Generate DH keypair ...");
        KeyPairGenerator carolKpairGen = KeyPairGenerator.getInstance("DH");
        carolKpairGen.initialize(dhSkipParamSpec);
        KeyPair carolKpair = carolKpairGen.generateKeyPair();


        // Alice initialize
        System.out.println("ALICE: Initialize ...");
        KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH");
        aliceKeyAgree.init(aliceKpair.getPrivate());

        // Bob initialize
        System.out.println("BOB: Initialize ...");
        KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH");
        bobKeyAgree.init(bobKpair.getPrivate());

        // Carol initialize
        System.out.println("CAROL: Initialize ...");
        KeyAgreement carolKeyAgree = KeyAgreement.getInstance("DH");
        carolKeyAgree.init(carolKpair.getPrivate());


        // Alice uses Carol's public key
        Key ac = aliceKeyAgree.doPhase(carolKpair.getPublic(), false);

        // Bob uses Alice's public key
        Key ba = bobKeyAgree.doPhase(aliceKpair.getPublic(), false);

        // Carol uses Bob's public key
        Key cb = carolKeyAgree.doPhase(bobKpair.getPublic(), false);


        // Alice uses Carol's result from above
        aliceKeyAgree.doPhase(cb, true);

        // Bob uses Alice's result from above
        bobKeyAgree.doPhase(ac, true);

        // Carol uses Bob's result from above
        carolKeyAgree.doPhase(ba, true);


        // Alice, Bob and Carol compute their secrets
        byte[] aliceSharedSecret = aliceKeyAgree.generateSecret();
        System.out.println("Alice secret: " + toHexString(aliceSharedSecret));

        byte[] bobSharedSecret = bobKeyAgree.generateSecret();
        System.out.println("Bob secret: " + toHexString(bobSharedSecret));

        byte[] carolSharedSecret = carolKeyAgree.generateSecret();
        System.out.println("Carol secret: " + toHexString(carolSharedSecret));


        // Compare Alice and Bob
        if (!java.util.Arrays.equals(aliceSharedSecret, bobSharedSecret))
            throw new Exception("Alice and Bob differ");
        System.out.println("Alice and Bob are the same");

        // Compare Bob and Carol
        if (!java.util.Arrays.equals(bobSharedSecret, carolSharedSecret))
            throw new Exception("Bob and Carol differ");
        System.out.println("Bob and Carol are the same");
    }


    /*
     * Converts a byte to hex digit and writes to the supplied buffer
     */
    private void byte2hex(byte b, StringBuffer buf) {
        char[] hexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
                            '9', 'A', 'B', 'C', 'D', 'E', 'F' };
        int high = ((b & 0xf0) >> 4);
        int low = (b & 0x0f);
        buf.append(hexChars[high]);
        buf.append(hexChars[low]);
    }

    /*
     * Converts a byte array to hex string
     */
    private String toHexString(byte[] block) {
        StringBuffer buf = new StringBuffer();

        int len = block.length;

        for (int i = 0; i < len; i++) {
             byte2hex(block[i], buf);
             if (i < len-1) {
                 buf.append(":");
             }
        }
        return buf.toString();
    }

    /*
     * Prints the usage of this test.
     */
    private void usage() {
        System.err.print("DHKeyAgreement usage: ");
        System.err.println("[-gen]");
    }

    // The 1024 bit Diffie-Hellman modulus values used by SKIP
    private static final byte skip1024ModulusBytes[] = {
        (byte)0xF4, (byte)0x88, (byte)0xFD, (byte)0x58,
        (byte)0x4E, (byte)0x49, (byte)0xDB, (byte)0xCD,
        (byte)0x20, (byte)0xB4, (byte)0x9D, (byte)0xE4,
        (byte)0x91, (byte)0x07, (byte)0x36, (byte)0x6B,
        (byte)0x33, (byte)0x6C, (byte)0x38, (byte)0x0D,
        (byte)0x45, (byte)0x1D, (byte)0x0F, (byte)0x7C,
        (byte)0x88, (byte)0xB3, (byte)0x1C, (byte)0x7C,
        (byte)0x5B, (byte)0x2D, (byte)0x8E, (byte)0xF6,
        (byte)0xF3, (byte)0xC9, (byte)0x23, (byte)0xC0,
        (byte)0x43, (byte)0xF0, (byte)0xA5, (byte)0x5B,
        (byte)0x18, (byte)0x8D, (byte)0x8E, (byte)0xBB,
        (byte)0x55, (byte)0x8C, (byte)0xB8, (byte)0x5D,
        (byte)0x38, (byte)0xD3, (byte)0x34, (byte)0xFD,
        (byte)0x7C, (byte)0x17, (byte)0x57, (byte)0x43,
        (byte)0xA3, (byte)0x1D, (byte)0x18, (byte)0x6C,
        (byte)0xDE, (byte)0x33, (byte)0x21, (byte)0x2C,
        (byte)0xB5, (byte)0x2A, (byte)0xFF, (byte)0x3C,
        (byte)0xE1, (byte)0xB1, (byte)0x29, (byte)0x40,
        (byte)0x18, (byte)0x11, (byte)0x8D, (byte)0x7C,
        (byte)0x84, (byte)0xA7, (byte)0x0A, (byte)0x72,
        (byte)0xD6, (byte)0x86, (byte)0xC4, (byte)0x03,
        (byte)0x19, (byte)0xC8, (byte)0x07, (byte)0x29,
        (byte)0x7A, (byte)0xCA, (byte)0x95, (byte)0x0C,
        (byte)0xD9, (byte)0x96, (byte)0x9F, (byte)0xAB,
        (byte)0xD0, (byte)0x0A, (byte)0x50, (byte)0x9B,
        (byte)0x02, (byte)0x46, (byte)0xD3, (byte)0x08,
        (byte)0x3D, (byte)0x66, (byte)0xA4, (byte)0x5D,
        (byte)0x41, (byte)0x9F, (byte)0x9C, (byte)0x7C,
        (byte)0xBD, (byte)0x89, (byte)0x4B, (byte)0x22,
        (byte)0x19, (byte)0x26, (byte)0xBA, (byte)0xAB,
        (byte)0xA2, (byte)0x5E, (byte)0xC3, (byte)0x55,
        (byte)0xE9, (byte)0x2F, (byte)0x78, (byte)0xC7
    };

    // The SKIP 1024 bit modulus
    private static final BigInteger skip1024Modulus
    = new BigInteger(1, skip1024ModulusBytes);

    // The base used with the SKIP 1024 bit modulus
    private static final BigInteger skip1024Base = BigInteger.valueOf(2);
}

Blowfish 暗号の例

/*
 * Copyright (c) 1997, 2001, Oracle and/or its affiliates. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Oracle nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;

/**
 * This program generates a Blowfish key, retrieves its raw bytes, and
 * then reinstantiates a Blowfish key from the key bytes.
 * The reinstantiated key is used to initialize a Blowfish cipher for
 * encryption.
 */

public class BlowfishKey {

    public static void main(String[] args) throws Exception {

        KeyGenerator kgen = KeyGenerator.getInstance("Blowfish");
        SecretKey skey = kgen.generateKey();
        byte[] raw = skey.getEncoded();
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "Blowfish");

        Cipher cipher = Cipher.getInstance("Blowfish");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
        byte[] encrypted =
            cipher.doFinal("This is just an example".getBytes());
    }
}

HMAC-MD5 の例

/*
 * Copyright (c) 1997, 2001, Oracle and/or its affiliates. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Oracle nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.security.*;
import javax.crypto.*;

/**
 * This program demonstrates how to generate a secret-key object for
 * HMAC-MD5, and initialize an HMAC-MD5 object with it.
 */

public class initMac {

    public static void main(String[] args) throws Exception {

        // Generate secret key for HMAC-MD5
        KeyGenerator kg = KeyGenerator.getInstance("HmacMD5");
        SecretKey sk = kg.generateKey();

        // Get instance of Mac object implementing HMAC-MD5, and
        // initialize it with the above secret key
        Mac mac = Mac.getInstance("HmacMD5");
        mac.init(sk);
        byte[] result = mac.doFinal("Hi There".getBytes());
    }
}

InputStream から ASCII パスワードを読み取る例

/*
 * @(#)ReadPassword.java  1.1 06/06/07
 *
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

import java.util.*;
import java.io.*;
import java.security.*;

public class ReadPassword {
    /**
     * Read a password from the InputStream "in".
     * <p>
     * As Strings are immutable, passwords should be stored as an array
     * of characters, which can be blanked out when no longer needed.
     * <p>
     * If the provided InputStream is the System's Console, this method
     * uses the non-echoing readPassword() method of java.io.Console
     * (new to JDK 6).  If not, a fallback implementation is used.
     * <p>
     * NOTE:  For expository purposes, and because some applications do
     * not understand multi-byte characters, only 8-bit ASCII passwords
     * are handled here.
     * <p>
     * NOTE:  If a SecurityManager is used, the default standard
     * java.policy file found in the JDK (i.e.
     * <java-home>/lib/security/java.policy) allows reading the
     * line.separator property.  If your environment is different, this
     * code will need to be granted the appropriate privilege.
     *
     * @param   in
     *          the InputStream used to obtain the password.
     *
     * @return  A character array containing the password or passphrase,
     *          not including the line-termination characters,
     *          or null if an end of stream has been reached.
     *
     * @throws  IOException
     *          if an I/O problem occurs
     */
    public static final char[] readPassword(InputStream in)
            throws IOException {

        /*
         * If available, directly use the java.io.Console class to
         * avoid character echoing.
         */
        if (in == System.in && System.console() != null) {
            // readPassword returns "" if you just print ENTER,
            return System.console().readPassword();
        }

        /*
         * If a console is not available, read the InputStream
         * directly.  This approach may cause password echoing.
         *
         * Since different operating systems have different End-Of-Line
         * (EOL) sequences, this algorithm should allow for
         * platform-independent implementations.  Typical EOL sequences
         * are a single line feed ('\n'), or a carriage return/linefeed
         * combination ('\r\n').  However, some OS's use a single
         * a carriage return ('\r'), which complicates portability.
         *
         * Since we may not have the ability to push bytes back into the
         * InputStream, another approach is used here.  The javadoc for
         * <code>java.lang.System.getProperties()</code> specifies that
         * the set of system properties will contain a system-specific
         * value for the "line.separator".  Scan for this character
         * sequence instead of hard-coding a particular sequence.
         */

        /*
         * Enclose the getProperty in a doPrivileged block to minimize
         * the call stack permission required.
         */
        char [] EOL = AccessController.doPrivileged(
            new PrivilegedAction<char[]>() {
                public char[] run() {
                    String s = System.getProperty("line.separator");
                    // Shouldn't happen.
                    if (s == null) {
                        throw new RuntimeException(
                            "line.separator not defined");
                    }
                    return s.toCharArray();
                }
            });

        char [] buffer = new char[128];
        try {
            int len = 0;                // len of data in buffer.
            boolean done = false;       // found the EOL sequence
            int b;                      // byte read

            while (!done) {
                /*
                 * realloc if necessary
                 */
                if (len >= buffer.length) {
                    char [] newbuffer = new char[len + 128];
                    System.arraycopy(buffer, 0, newbuffer, 0, len);
                    Arrays.fill(buffer, ' ');
                    buffer = newbuffer;
                }

                /*
                 * End-of-Stream?
                 */
                if ((b = in.read()) == -1) {
                    // Return as much as we have, null otherwise.
                    if (len == 0) {
                        return null;
                    }
                    break;
                } else {
                    /*
                     * NOTE:  In the simple PBE example here,
                     * only 8 bit ASCII characters are handled.
                     */
                    buffer[len++] = (char) b;
                }

                /*
                 * check for the EOL sequence.  Do we have enough bytes?
                 */
                if (len >= EOL.length) {
                    int i = 0;
                    for (i = 0; i < EOL.length; i++) {
                        if (buffer[len - EOL.length + i] != EOL[i]) {
                            break;
                        }
                    }
                    done = (i == EOL.length);
                }
            }

            /*
             * If we found the EOL, strip the EOL chars.
             */
            char [] result = new char[done ? len - EOL.length : len];
            System.arraycopy(buffer, 0, result, 0, result.length);

            return result;
        } finally {
            /*
             * Zero out the buffer.
             */
            if (buffer != null) {
                Arrays.fill(buffer, ' ');
            }
        }
    }
}


Copyright © 1993, 2013, Oracle and/or its affiliates. All rights reserved.