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)と特定のアルゴリズムまたはサービス(たとえばSHA-256アルゴリズム)を要求し、インストールされているプロバイダのうちの1つから実装を獲得します。あるいは、特定プロバイダのオブジェクトを要求することもできます。各プロバイダは、それぞれの参照名を持ちます。

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

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

プロバイダ名を指定せずにSHA-256アルゴリズムを要求しているアプリケーションを示す図 特定のプロバイダからSHA-256アルゴリズムを要求しているアプリケーションを示す図
図1a: プロバイダ検索 図1b: 特定のプロバイダの要求

図1a: プロバイダ検索および図1b: 特定のプロバイダの要求の説明

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);
アプリケーションがAES Cipherインスタンスを取得する方法の例

アプリケーションが「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パッケージ内にあります。推奨されるデフォルト・キーストア・タイプ(形式)はpkcs12であり、これは、RSA PKCS12 Personal Information Exchange Syntax Standardに基づいています。デフォルトのキーストア・タイプは、独自仕様の形式であるjksです。独自の代替キーストア形式であるjceksや、RSA PKCS11 Standardに基づいた、ハードウェア・セキュリティ・モジュールやスマートカードなどの暗号化トークンへのアクセスがサポートされているpkcs11など、その他のキーストア形式も使用できます。

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

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

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が使用する各種プロパティの値を設定します。つまり、サブクラスは、サービスを実装するクラス名を指定します。

プロバイダ・サブクラスの例

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

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

実装が違うと、特徴も違ってくる場合があります。ソフトウェア・ベースのものもあれば、ハードウェア・ベースのものもあります。プラットフォーム独立のものもあれば、プラットフォーム固有のものもあります。また、レビューや評価用に使えるプロバイダ・コードもあれば、使えないものもあります。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("SHA-256");
    KeyAgreement ka = KeyAgreement.getInstance("DH", "SunJCE");
これらはそれぞれ、SHA-256 MessageDigestオブジェクトおよびDH KeyAgreementオブジェクトのインスタンスを返します。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  1. 通常のJava classpath

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

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

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

    • Solaris、LinuxまたはMac OS X: <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ファイルを次のディレクトリにインストールする必要があります。

    • Solaris、LinuxまたはMac OS X: /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ファイルを次のディレクトリにインストールする必要があります。

    • Solaris、LinuxまたはMac OS X: /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)の機能を提供するエンジン・クラスです。暗号として強力な乱数を生成する点がjava.lang.Randomクラスとは異なります。ジェネレータのランダム性が不十分な場合、保護メカニズムが簡単に脅かされることになります。暗号化鍵やアルゴリズムのパラメータの生成など、乱数は暗号化全体で使用されます。

すべてのJava SE実装は、java.security.Securityクラスのsecurerandom.strongAlgorithmsプロパティで提供される、もっとも強力な(もっともランダム性の高い)SecureRandomの実装を示す必要があります。この実装は、特に強力な乱数値が必要な場合に使用できます。

SecureRandomクラス

SecureRandomクラスの説明

SecureRandomオブジェクトの作成

SecureRandomのインスタンスは、いくつかの方法で取得できます。

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-256やSHA-512など)の機能を提供するように設計されています。安全な暗号化メッセージ・ダイジェストは、任意サイズの入力(バイト配列)を取り、固定サイズ出力を生成します。これをダイジェストまたはハッシュと言います。

MessageDigestクラス

MessageDigestクラスの図の説明

たとえば、SHA-256のアルゴリズムは32バイトのダイジェストを生成し、SHA-512のアルゴリズムは64バイトのダイジェストを生成します。

ダイジェストには次の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などの暗号化デジタル署名アルゴリズムの機能を提供するように設計されています。安全な暗号化署名アルゴリズムは、任意サイズの入力と非公開鍵を取り、署名と呼ばれる比較的短い(固定サイズの場合もよくある)バイト文字列を生成します。このプロパティは次のとおりです。

また、指定の署名が、関連データの実際の認証署名かどうかを検証することもできます。

Signatureクラス

Signatureクラスの図の説明

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

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

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

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

Cipherクラス

Cipherクラスの図の説明

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

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

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

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

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

操作モード

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

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

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

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

GCMモードでのAES暗号の使用

GCMでのAES暗号はAEAD暗号で、非AEAD暗号とは使用パターンが異なります。通常のデータ以外にAADも取ります(AADは暗号化/復号化用のオプションですが、暗号化/復号化を行うデータの前に必ず指定する必要があります)。また、GCMを安全に使用するため、呼出し側では鍵とIVの組合せを再利用して暗号化を行わないでください。つまり、暗号オブジェクトは、暗号化操作のたびに異なるパラメータ・セットで明示的に初期化しなおす必要があります。

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

	GCMParameterSpec myParams = new GCMParameterSpec(myTLen, myIv);
	Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
	c.init(Cipher.ENCRYPT_MODE, myKey, myParams);

	// AAD is optional, if present, it must be supplied before any update/doFinal calls.
	c.updateAAD(myAAD);  // if AAD is non-null
	byte[] cipherText = new byte[c.getOutputSize(plainText.length)];
	c.doFinal(plainText, 0, plainText.length, cipherText);    // conclusion of encryption operation

	// To decrypt, same AAD and GCM parameters must be supplied
	c.init(Cipher.DECRYPT_MODE, myKey, myParams);
	c.updateAAD(myAAD);
	byte[] recoveredText = c.doFinal(cipherText);

	// MUST CHANGE IV VALUE if the same key were to be used again for encryption
     	byte[] newIv = ...;
	myParams = new GCMParameterSpec(myTLen, newIv);

Cipherオブジェクトの作成

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

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

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

    "AES/CBC/PKCS5Padding"

    "AES"

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

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

アルゴリズム、モードおよびパディングを完全に指定した変換を使用することをお薦めします。そうしないと、プロバイダはデフォルトを使用します。たとえば、SunJCEプロバイダとSunPKCS11プロバイダは、多くの対称暗号でECBをデフォルト・モードとして、PKCS5Paddingをデフォルト・パディングとして使用します。

このため、SunJCEプロバイダでは、

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

の2つの文は、等価になります。


注意: ECBモードは最も簡単に使用できるブロック暗号モードで、JDK/JREではデフォルトになっています。ECBは、単一のデータ・ブロックに対しては正常に機能しますが、複数のデータ・ブロックには絶対に使用しないでください。


CFBやOFBなどのモードを使用すると、ブロック暗号は、暗号の実際のブロック・サイズよりも小さい単位でデータを暗号化できます。このようなモードを要求する場合、AES/CFB8/NoPaddingおよびAES/OFB32/PKCS5Padding変換で示されるように、一度に処理するビット数を、モード名に追加することで指定することもできます。数値を指定しない場合、プロバイダ固有のデフォルトが使用されます。(たとえば、SunJCEプロバイダではAESにデフォルトの128ビットが使用されます。)したがって、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("PBEWithHmacSHA256AndAES_256");

    // 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("PBEWithHmacSHA256AndAES_256");

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

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

    // 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を作成し、AESなどの暗号化アルゴリズムを使用して、直列化された内容をシール(暗号化)することにより、機密性を保護できます。その後、暗号化された内容の復号化(適正な復号化鍵を使用)、および直列化解除を行うことにより、元のオブジェクトを復元できます。

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

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

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

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

Macクラス

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

Macクラス

図8の説明: Macクラス

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

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など)で、これらのアルゴリズムや関連アルゴリズム(SHA256withRSAなど)と連携して機能します。鍵アルゴリズムの名前は、次のメソッドを使用して取得します。
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の3つのメソッドに制限されます。

鍵は、アルゴリズムに固有の方法か、またはアルゴリズムに依存しないエンコーディング形式(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をはじめて使用する人は、ジェネレータとファクトリの区別がつかない場合があります。

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

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

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

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

KeyFactoryクラス

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

KeyFactoryクラス<

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オブジェクトは鍵ペアの公開鍵および非公開鍵コンポーネントを処理します。

SecretKeyFactoryクラス

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 DESedeKeySpec.

    byte[] desEdeKeyData = getKeyData();
    DESedeKeySpec desEdeKeySpec = new DESedeKeySpec(desEdeKeyData);
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
    SecretKey secretKey = keyFactory.generateSecret(desEdeKeySpec);

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

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

    byte[] aesKeyData = getKeyData();
    SecretKeySpec secretKey = new SecretKeySpec(aesKeyData, "AES");

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クラスはエンジン・クラスで、公開鍵と非公開鍵のペアの生成に使います。

KeyPairGeneratorクラス

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クラス

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

KeyGeneratorクラス

KeyGeneratorクラスの図の説明

KeyGeneratorの作成

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

KeyGeneratorオブジェクトの初期化

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

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

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

鍵の作成

次のメソッドにより、秘密鍵が生成されます。

    public SecretKey generateKey();

KeyAgreementクラス

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

KeyAgreementクラス

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

推奨のキーストア実装は「pkcs12」です。これは、RSA PKCS12 Personal Information Exchange Syntax Standardに基づくクロス・プラットフォーム・キーストアです。この規格は、ユーザーの非公開鍵、証明書、その他の秘密を格納および転送することを主な目的としています。PKCS12キーストア内の個々のエントリに任意の属性を関連付けることができます。

デフォルトのキーストア実装の型は「jks」で、java.securityファイル内の次の行に指定されています。

    keystore.type=jks

ツールおよび他のアプリケーションで異なるデフォルトのキーストア実装を使用するには、この行を変更して別のデフォルトの型を指定します。たとえば、デフォルトのキーストア実装として「pkcs12」を使用するには、この行を次のように変更します。

    keystore.type=pkcs12

keytoolなどの一部のアプリケーションでも、-storetypeコマンド行パラメータによって、デフォルトのキーストアの型をオーバーライドできます。

注: キーストアの型の指定では、大文字と小文字は区別されません。たとえば、「jks」と「JKS」は同じものとして扱われます。

他に2つの型のキーストアが、JDK実装に付属しています。

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

KeyStoreクラス

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

KeyStoreクラス

KeyStoreクラスの図の説明

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

鍵エントリ

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

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

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

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

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

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

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

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

KeyStoreオブジェクトの作成

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

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

KeyStoreオブジェクトを使う前に、loadメソッドによってメモリー内に実際のキーストア・データをロードする必要があります。

final void load(InputStream stream, char[] password)

オプションのパスワードを使って、キーストア・データの整合性をチェックします。パスワードが提供されない場合は、整合性のチェックは行われません。

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

DKSキーストアは、DomainLoadStoreParameterを代替のloadメソッドに渡すことによってロードされます。

final void load(KeyStore.LoadStoreParameter param)

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

キーストアのすべてのエントリには、一意の別名を介してアクセスします。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)

PKCS #12キーストアは、任意の属性を含むエントリをサポートします。属性を作成するには、java.security.PKCS12Attributeクラスを使用します。新しいキーストア・エントリを作成するときは、属性を受け入れるコンストラクタを使用します。最後に、次のメソッドを使用してエントリをキーストアに追加します。

final void setEntry(String alias, Entry entry, 
                    ProtectionParameter protParam)

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

getKeyメソッドは、与えられた別名に関連付けられた鍵を返します。鍵は、与えられたパスワードを使って復元されます。

final Key getKey(String alias, char[] password)

次のメソッドは、与えられた別名に関連付けられた証明書、または証明書チェーンをそれぞれ返します。

final Certificate getCertificate(String alias)
final Certificate[] getCertificateChain(String alias)

次の文を使って、与えられた証明書と一致した最初のエントリの名前(alias)を決定できます。

final String getCertificateAlias(Certificate cert)

PKCS #12キーストアは、任意の属性を含むエントリをサポートします。次のメソッドを使用して、属性を含む可能性があるエントリを取得します。

final Entry getEntry(String alias, ProtectionParameter protParam)

次に、KeyStore.Entry.getAttributesメソッドを使用してそのような属性を抽出し、KeyStore.Entry.Attributeインタフェースのメソッドを使用してそれらを調べます。

キーストアの保存

メモリー内のキーストアを、storeメソッドを使って保存できます。

final void store(OutputStream stream, char[] password)

パスワードは、キーストア・データの統合チェックサムの計算に使われます。統合チェックサムは、キーストア・データのあとに追加されます。

DKSキーストアは、DomainLoadStoreParameterを代替のstoreメソッドに渡すことによって格納されます。

final void store(KeyStore.LoadStoreParameter param)

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

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 APIドキュメントで説明されています。

次のアルゴリズム・パラメータ仕様は、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を作成し、適切なキー・サイズで初期化します。このジェネレータは、次に、KeyPair (最終的にディスクに書き込まれる)を作成する場合に使用されます。これはkeytoolにより、新しく作成された証明書とともにKeyStoreに格納されます。

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

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

SSLメッセージ

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などのブロック暗号が使用される場合は、データにパディングを行なって完全なブロックにします。リモート側では、手順は単純に逆になります。

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

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

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

デフォルトでは、アプリケーションは、どの強度の暗号化アルゴリズムでも使用できます。ただし、一部の国の政府による輸入管理制限のため、それらのアルゴリズムの強度を制限する必要がある場合があります。JCAフレームワークには、様々な管轄コンテキスト(場所)でアプリケーションに使用可能な暗号化アルゴリズムの最大強度に関して、制限を強制する機能が含まれます。管轄ポリシー・ファイルでこれらの制限を指定します。管轄ポリシー・ファイルおよびその作成方法と構成方法の詳細は、付録B: 管轄ポリシー・ファイル形式および付録C: 暗号強度の構成を参照してください。

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

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

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

注意: SunJCEプロバイダは、ExemptionMechanismSpiクラスの実装を提供しません。

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

アプリケーションに、関連付けられたアクセス権ポリシー・ファイルがあり(同じJARファイル内)、そのアクセス権ポリシー・ファイルで免責メカニズムが指定されている場合、CipherのgetInstanceメソッドが呼び出されてCipherがインスタンス化されると、JCAコードは、インストール済プロバイダの中から、指定された免責メカニズムを実装するプロバイダを検索します。目的のプロバイダが見つかると、JCAは、そのプロバイダの実装に関連付けられたExemptionMechanismオブジェクトをインスタンス化してから、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-256");
この呼出しは、正しく初期化されたメッセージ・ダイジェスト・オブジェクトを変数shaに代入します。この実装は、National Institute for Standards and Technology (NIST)のFIPS 180-2ドキュメントの定義に従って、Secure Hashアルゴリズム(SHA-256)を実装します。標準名とアルゴリズムに関する詳細は、「付録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-256実装が複製(コピー)可能な場合にのみ機能します。メッセージ・ダイジェストの実装の中には、複製(コピー)可能なものもあれば、不可能なものもあります。複製(コピー)が可能かどうかを判断するには、次のように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 md1 = MessageDigest.getInstance("SHA-256");
   MessageDigest md2 = MessageDigest.getInstance("SHA-256");
   MessageDigest md3 = MessageDigest.getInstance("SHA-256");

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

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

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

鍵のペアの生成

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

鍵ペア・ジェネレータの生成

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

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

鍵ペア・ジェネレータの初期化

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

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

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

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

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

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

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

鍵のペアの生成

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

KeyPair pair = keyGen.generateKeyPair();

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

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

署名の生成

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

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

鍵のペアの例で生成した鍵のペアを使い、非公開鍵を指定してオブジェクトを初期化します。このあとで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("SHA256withDSA");
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("SHA256withDSA");
    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)

これで、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ファクトリ・メソッドを使用します。次のコンポーネントを含む必須の変換名を、スラッシュ(/)で区切って指定する必要があります。

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

AESの標準アルゴリズム名はAES、Cipher Block Chainingモードの標準名はCBC、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および繰返し処理の回数を使用する必要があります。より新しいPBEアルゴリズムは、1000以上の繰返し処理回数を使用します。

    PBEKeySpec pbeKeySpec;
    PBEParameterSpec pbeParamSpec;
    SecretKeyFactory keyFac;

    // Salt
    byte[] salt = new SecureRandom().nextBytes(salt);

    // Iteration count
    int count = 1000;

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

    // Prompt user for encryption password.
    // Collect user password as char array, and convert
    // it into a SecretKey object, using a PBE key
    // factory.
    char[] password = System.console.readPassword("Enter encryption password: ");
    pbeKeySpec = new PBEKeySpec(password);
    keyFac = SecretKeyFactory.getInstance("PBEWithHmacSHA256AndAES_256");
    SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);

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

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

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

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

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

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

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

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


付録C: 暗号強度の構成

管轄ポリシー・ファイル(付録B: 管轄ポリシー・ファイル形式を参照)とセキュリティ・プロパティ・ファイルを使用してJava Cryptography Extension (JCE)アーキテクチャの暗号強度を構成できます。

Oracle実装で許可されているデフォルトの暗号強度は、無制限です。ただし、管理者およびユーザーは、これまでどおり、それぞれの地理的な場所に応じたすべての輸入/輸出ガイドラインに従う必要があります。アクティブな暗号強度は、構成ディレクトリにある管轄ポリシー・ファイルと組み合せてセキュリティ・プロパティ(通常は、java.securityプロパティ・ファイル内で設定される)を使用して決定されます。

無制限の暗号強度または強力だが制限付きの暗号強度を提供するために必要なJCEポリシー・ファイルはすべてJDKに含まれています。

暗号強度の設定

JDK 8u161、7u171および6u181以降、<java_home>/jre/lib/security/policyの各サブディレクトリは、その中の管轄ポリシー・ファイルにより定義されたポリシー構成を表しています。このディレクトリには、デフォルトで2つのサブディレクトリ(limitedおよびunlimited)が含まれ、それぞれにJARファイル内にパッケージ化されたポリシー・ファイルの完全セットが含まれています。

<java_home>jre/lib/security/policyに含まれる特定のポリシー構成をアクティブ化するには、(ファイル<java_home>/jre/lib/security/java.security内の) crypto.policyセキュリティ・プロパティの値を、ポリシー構成が含まれるサブディレクトリの名前に設定します。デフォルトでは、crypto.policyセキュリティ・プロパティは定義されていません。

JDKは、次のようにポリシー構成を決定します。

  1. crypto.policyセキュリティ・プロパティが定義されている場合、JDKは、このセキュリティ・プロパティにより指定されたポリシー構成を使用します。
  2. crypto.policyセキュリティ・プロパティが設定されておらず、レガシーの<java_home>/lib/securityディレクトリ内に従来のUS_export_policy.jarファイル(強力だが制限付きの暗号強度)とlocal_policy.jarファイル(無制限の暗号強度)が存在する場合、JDKは、これらのJARファイル内に指定されたポリシー構成を使用します。これにより、ユーザーが古いJDKバージョンからアップグレードする場合に互換性を維持できます。
  3. crypto.policyセキュリティ・プロパティが設定されておらず、<java_home>/lib/securityディレクトリ内にUS_export_policy.jarファイルおよびlocal_policy.jarファイルが存在しない場合、JDKは、無制限の暗号強度(cryto.policy=unlimitedに相当)を使用します。

ポリシー構成の設定はVM全体に適用され、このVM上で実行されるすべてのアプリケーションに影響を及ぼします。アプリケーション・レベルで暗号強度をオーバーライドする必要がある場合は、「アプリケーションの暗号化制限の免責を取得する方法」を参照してください。

unlimitedディレクトリの内容

unlimitedディレクトリには、無制限の暗号強度を提供する次のポリシー・ファイルが含まれています。

limitedディレクトリの内容

limitedディレクトリには、強力だが制限付きの暗号強度を提供する次のポリシー・ファイルが含まれています。

カスタム暗号強度の設定

limitedまたはunlimitedディレクトリにあるポリシー・ファイル内の設定とは異なる暗号強度を構成するには、<java_home>/jre/lib/security内に新しいサブディレクトリを作成し、そこにポリシー・ファイルを配置します。たとえば、<java_home>/jre/lib/security/customというディレクトリを作成できます。このcustomディレクトリに、ファイルdefault_*export.policyまたはexempt_*local.policy、あるいはその両方を含めます。

customディレクトリ内のファイルで定義されている暗号強度を選択するには、<java_home>/jre/lib/security/java.securityファイル内にcrypto.policy=customを設定します。


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

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

/*
 * Copyright (c) 1997, 2017, 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;

public class DHKeyAgreement2 {
    private DHKeyAgreement2() {}
    public static void main(String argv[]) throws Exception {
        
        /*
         * Alice creates her own DH key pair with 2048-bit key size
         */
        System.out.println("ALICE: Generate DH keypair ...");
        KeyPairGenerator aliceKpairGen = KeyPairGenerator.getInstance("DH");
        aliceKpairGen.initialize(2048);
        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 dhParamFromAlicePubKey = ((DHPublicKey)alicePubKey).getParams();

        // Bob creates his own DH key pair
        System.out.println("BOB: Generate DH keypair ...");
        KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH");
        bobKpairGen.initialize(dhParamFromAlicePubKey);
        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.
         */
        try {
            byte[] aliceSharedSecret = aliceKeyAgree.generateSecret();
            int aliceLen = aliceSharedSecret.length;
            byte[] bobSharedSecret = new byte[aliceLen];
            int bobLen;
        } 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 create a SecretKey object using the shared secret
         * and use it for encryption. First, we generate SecretKeys for the
         * "AES" algorithm (based on the raw shared secret data) and
         * Then we use AES in CBC mode, which requires an initialization
         * vector (IV) parameter. 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.
         *
         * 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 explicitly
         * passed to the Cipher.init() method.
         */
        System.out.println("Use shared secret as SecretKey object ...");
        SecretKeySpec bobAesKey = new SecretKeySpec(bobSharedSecret, 0, 16, "AES");
        SecretKeySpec aliceAesKey = new SecretKeySpec(aliceSharedSecret, 0, 16, "AES");

        /*
         * Bob encrypts, using AES in CBC mode
         */
        Cipher bobCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        bobCipher.init(Cipher.ENCRYPT_MODE, bobAesKey);
        byte[] cleartext = "This is just an example".getBytes();
        byte[] 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 AES in CBC mode
         */

        // Instantiate AlgorithmParameters object from parameter encoding
        // obtained from Bob
        AlgorithmParameters aesParams = AlgorithmParameters.getInstance("AES");
        aesParams.init(encodedParams);
        Cipher aliceCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        aliceCipher.init(Cipher.DECRYPT_MODE, aliceAesKey, aesParams);
        byte[] recovered = aliceCipher.doFinal(ciphertext);
        if (!java.util.Arrays.equals(cleartext, recovered))
            throw new Exception("AES in CBC mode recovered text is " +
                    "different from cleartext");
        System.out.println("AES in CBC mode recovered text is "
                "same as cleartext");
    }

    /*
     * Converts a byte to hex digit and writes to the supplied buffer
     */
    private static 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 static 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();
    }
}

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

/*
 * Copyright (c) 1997, 2017, 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 java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.crypto.interfaces.*;
   /*
    * This program executes the Diffie-Hellman key agreement protocol between
    * 3 parties: Alice, Bob, and Carol using a shared 2048-bit DH parameter.
    */
    public class DHKeyAgreement3 {
        private DHKeyAgreement3() {}
        public static void main(String argv[]) throws Exception {
        // Alice creates her own DH key pair with 2048-bit key size
            System.out.println("ALICE: Generate DH keypair ...");
            KeyPairGenerator aliceKpairGen = KeyPairGenerator.getInstance("DH");
            aliceKpairGen.initialize(2048);
            KeyPair aliceKpair = aliceKpairGen.generateKeyPair();
        // This DH parameters can also be constructed by creating a
        // DHParameterSpec object using agreed-upon values
            DHParameterSpec dhParamShared = ((DHPublicKey)aliceKpair.getPublic()).getParams();
        // Bob creates his own DH key pair using the same params
            System.out.println("BOB: Generate DH keypair ...");
            KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH");
            bobKpairGen.initialize(dhParamShared);
            KeyPair bobKpair = bobKpairGen.generateKeyPair();
        // Carol creates her own DH key pair using the same params
            System.out.println("CAROL: Generate DH keypair ...");
            KeyPairGenerator carolKpairGen = KeyPairGenerator.getInstance("DH");
            carolKpairGen.initialize(dhParamShared);
            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 static 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 static 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();
        }
    }

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-SHA256の例

/*
 * Copyright (c) 1997, 2017, 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
     * HMACSHA256, and initialize an HMACSHA256 object with it.
     */

    public class initMac {

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

            // Generate secret key for HmacSHA256
            KeyGenerator kg = KeyGenerator.getInstance("HmacSHA256");
            SecretKey sk = kg.generateKey();

            // Get instance of Mac object implementing HmacSHA256, and
            // initialize it with the above secret key
            Mac mac = Mac.getInstance("HmacSHA256");
            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, 2020, Oracle and/or its affiliates. All rights reserved.