2 Java暗号化アーキテクチャ(JCA)リファレンス・ガイド
Java暗号化アーキテクチャ(JCA)はプラットフォームの主要な部分であり、プロバイダ・アーキテクチャ、およびデジタル署名、メッセージ・ダイジェスト(ハッシュ)、証明書と証明書検証、暗号化(対称/非対称ブロック/ストリーム暗号)、キーの生成と管理、安全な乱数生成などのための一連のAPIを含んでいます。
Java暗号化アーキテクチャの概要
Javaプラットフォームは、言語の安全性、暗号化、公開キー・インフラストラクチャ、認証、安全な通信、アクセス制御など、セキュリティに重点を置いています。
JCAはプラットフォームの主要な部分であり、「プロバイダ」アーキテクチャ、およびデジタル署名、メッセージ・ダイジェスト(ハッシュ)、証明書と証明書検証、暗号化(対称/非対称ブロック/ストリーム暗号)、キーの生成と管理、安全な乱数生成などのための一連のAPIが含まれています。これらのAPIによって、開発者はアプリケーション・コードにセキュリティを簡単に統合できます。アーキテクチャは、次の方針に基づいて設計されました。
-
実装の独立性: アプリケーションでセキュリティ・アルゴリズムを実装する必要はありません。Javaプラットフォームからセキュリティ・サービスを要求できます。セキュリティ・サービスはプロバイダに実装されています(暗号化サービス・プロバイダを参照)。プロバイダは、標準インタフェースによってJavaプラットフォームにプラグインされます。アプリケーションは、複数の独立したプロバイダにセキュリティ機能を依存する場合があります。
-
実装の相互運用性: プロバイダは、アプリケーション間で相互運用できます。具体的には、アプリケーションは特定のプロバイダにバインドされず、プロバイダは特定のアプリケーションにバインドされません。
-
アルゴリズムの拡張性: Javaプラットフォームには、現在広く使用されている基本的なセキュリティ・サービスを実装する多数の組込みプロバイダが含まれています。ただし、一部のアプリケーションは、まだ実装されていない普及しつつある規格や独自のサービスに依存している場合があります。Javaプラットフォームは、そのようなサービスを実装するカスタム・プロバイダのインストールをサポートします。
JDKで使用できるその他の暗号化通信ライブラリはJCAプロバイダ・アーキテクチャを使用しますが、これらについては別途説明します。JSSEコンポーネントは、Secure Socket Layer (SSL)、Transport Layer Security (TLS)およびDatagram Transport Layer Security (DTLS)実装へのアクセスを提供します。Java Secure Socket Extension (JSSE)リファレンス・ガイドを参照してください。Java Generic Security Services (JGSS) (Kerberosを使用)のAPI、およびSimple Authentication and Security Layer (SASL)を使用して、通信するアプリケーション間でメッセージを安全に交換できます。JAASおよびJava GSS-APIチュートリアルの紹介およびJava SASL APIプログラミングおよび配備ガイドを参照してください。
用語に関するノート
-
JDK 1.4より前は、JCEはバンドルされていない製品であり、そのため、JCAとJCEは常に別個のコンポーネントとして参照されていました。現在JCEはJDKにバンドルされているため、区別は明確でなくなっています。JCEはJCAと同じアーキテクチャを使用するため、JCEはJCAの一部として考えることができます。
-
JDKのJCAには、次の2つのソフトウェア・コンポーネントが含まれます。
- プロバイダが実装を提供可能な暗号化サービスを定義およびサポートするフレームワーク。このフレームワークには、
java.security、javax.crypto、javax.crypto.spec、javax.crypto.interfacesなどのパッケージが含まれます。 Sun、SunRsaSign、SunJCEなどの実際のプロバイダ。実際の暗号化実装が含まれます。
特定のJCAプロバイダを取り上げる場合は、常にプロバイダの名前で明示的に指定されます。
- プロバイダが実装を提供可能な暗号化サービスを定義およびサポートするフレームワーク。このフレームワークには、
警告:
JCAを使用すると、アプリケーションへのセキュリティ機能の組込みが簡単になります。ただし、このドキュメントでは、APIの説明に必要な概念の基本的な紹介以外、セキュリティ/暗号化の理論については説明しません。このドキュメントでは、特定のアルゴリズムの長所と短所やプロトコルの設計についても説明しません。暗号化は高度なトピックであり、これらのツールを最適に使用するには、信頼できる、可能なかぎり最近のリファレンスを参照するようにしてください。実行している内容とその理由を常に理解している必要があります。ランダム・コードを単純にコピーし、それが用途を満たす筋書きを完全に解決してくれるとは考えないでください。間違ったツールまたはアルゴリズムが選択されたため、セキュリティまたはパフォーマンスに重大な問題があるアプリケーションが数多く配備されています。
JCAの設計の原則
JCAは、次の方針に基づいて設計されました。
- 実装の独立性と相互操作性
- アルゴリズムの独立性と拡張性
実装の独立性とアルゴリズムの独立性は相補関係にあります。そのため、デジタル署名やメッセージ・ダイジェストなどの暗号化サービスを、実装の詳細やこれらの概念の基になるアルゴリズムを意識しないで使用できます。完全なアルゴリズムの独立性は不可能ですが、JCAは標準化された、アルゴリズム固有のAPIを提供します。実装の独立性が望ましくない場合、JCAでは、開発者が特定の実装を指定できます。
アルゴリズムの独立性は、暗号化「エンジン」(サービス)の型を定義し、これらの暗号化エンジンの機能を提供するクラスを定義することにより実現しています。これらのクラスは、エンジン・クラスと呼ばれ、例としては、MessageDigest、Signature、KeyFactory、KeyPairGenerator、およびCipherクラスがあります。
実装の独立性は、「プロバイダ」ベースのアーキテクチャを使って実現されます。暗号化サービス・プロバイダ(CSP) (プロバイダとも呼ばれる)とは(暗号化サービス・プロバイダを参照)、デジタル署名アルゴリズム、メッセージ・ダイジェスト・アルゴリズム、キー変換サービスなどの1つ以上の暗号化サービスを実装するパッケージやパッケージ・セットを意味します。プログラムは単に、特定のサービス(たとえばDSA署名アルゴリズム)を実行する特定の型のオブジェクト(たとえばSignatureオブジェクト)を要求するだけで、インストールされているプロバイダの1つから実装を獲得できます。必要に応じて、プログラムは特定プロバイダから実装を要求することもできます。たとえば、より高速で安全性の高いバージョンが利用できるときは、プロバイダをアプリケーションに対して透過的に更新できます。
実装の相互操作性とは、さまざまな実装が互いに機能でき、互いのキーを使ったり、互いの署名を検証できることを意味します。つまり、1つのプロバイダが生成した同じアルゴリズムやキーを別のプロバイダが使えたり、あるプロバイダが生成した署名を別のプロバイダが検証できるということです。
アルゴリズムの拡張性とは、サポートされるエンジン・クラスの1つに当てはまる新規アルゴリズムを容易に追加できることを意味します。
プロバイダ・アーキテクチャ
プロバイダは、通知されている暗号化アルゴリズムの固定実装を提供するパッケージ(または、パッケージのセット)を含みます。
暗号化サービス・プロバイダ
java.security.Providerは、すべてのセキュリティ・プロバイダの基底クラスです。各CSPはこのクラスのインスタンスを含みます。インスタンスはプロバイダの名前を含み、実装するセキュリティ・サービス/アルゴリズムをすべて一覧表示します。特定のアルゴリズムのインスタンスが必要な場合、JCAフレームワークはプロバイダのデータベースに問い合わせ、一致するものが見つかった場合はそのインスタンスが作成されます。
プロバイダは、通知されている暗号化アルゴリズムの固定実装を提供するパッケージ(または、パッケージのセット)を含みます。各JDKのインストールでは、1つ以上のプロバイダがインストールされ、デフォルトで構成されます。追加のプロバイダは、静的または動的に追加できます。クライアントは実行環境を構成し、プロバイダの優先順位を指定できます。優先順位とは、特定プロバイダの指定がない場合に、要求されたサービスに関して検索されるプロバイダの順位です。
JCAを使用するには、アプリケーションは単に特定の型のオブジェクト(たとえばMessageDigest)と特定のアルゴリズムまたはサービス(たとえばSHA-256アルゴリズム)を要求し、インストールされているプロバイダのうちの1つから実装を獲得します。たとえば、次の文は、インストールされているプロバイダからのSHA-256メッセージ・ダイジェストを要求します。
md = MessageDigest.getInstance("SHA-256");あるいは、特定プロバイダのオブジェクトを要求することもできます。各プロバイダは、それぞれの参照名を持ちます。たとえば、次の文は、ProviderCという名前のプロバイダからのSHA-256メッセージ・ダイジェストを要求します。
md = MessageDigest.getInstance("SHA-256", "ProviderC");次の図は、SHA-256メッセージ・ダイジェスト実装の要求を示しています。それらは、様々なメッセージ・ダイジェスト・アルゴリズム(SHA-256、SHA-384およびSHA-512)を実装する3つの異なるプロバイダを示しています。プロバイダは、左から右への優先順位(1から3)で並べられています。図2-1では、アプリケーションはプロバイダ名を指定せずにSHA-256アルゴリズム実装を要求しています。プロバイダが優先順位に従って検索され、その特定のアルゴリズムを提供する最初のプロバイダであるProviderBから実装が返されます。図2-2では、アプリケーションは特定のプロバイダであるProviderCからのSHA-256アルゴリズム実装を要求しています。この場合は、優先順位の高いプロバイダであるProviderBもSHA-256実装を提供しますが、ProviderCから実装が返されます。
JDKでの暗号化実装は、様々なプロバイダ(Sun、SunJSSE、SunJCE、SunRsaSign)によって配布されます。これは主に歴史的な理由によりますが、それらが提供する機能およびアルゴリズムの型によるところもあります。その他のJava実行環境は、必ずしもこれらのプロバイダを含みません。したがって、特定のプロバイダが使用可能になることがわかっている場合を除き、アプリケーションはプロバイダ固有の実装を要求しないようにする必要があります。
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メソッドが呼び出されます。
例2-1 エンジン・クラスのインスタンスの取得のサンプル・コード
Cipher c = Cipher.getInstance("AES");
c.init(ENCRYPT_MODE, key);ここで、アプリケーションは「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()バッキング・メソッドに渡します。
キーストア
「キーストア」と呼ばれるデータベースは、キーおよび証明書のリポジトリを管理するために使用できます。キーストアは、認証、暗号化、または署名のためにデータが必要なアプリケーションで利用できます。
アプリケーションは、KeyStoreクラスの実装を経由してキーストアにアクセスできます。キーストア・クラスの実装は、java.securityパッケージ内にあります。JDK 9の時点では、推奨されるデフォルト・キーストア・タイプ(形式)はpkcs12であり、これは、RSA PKCS12 Personal Information Exchange Syntax Standardに基づいています。以前は、デフォルトのキーストア・タイプは、独自仕様の形式であるjksでした。独自の代替キーストア形式であるjceksや、RSA PKCS11 Standardに基づいた、ハードウェア・セキュリティ・モジュールやスマートカードなどの暗号化トークンへのアクセスがサポートされているpkcs11など、その他のキーストア形式も使用できます。
アプリケーションは、前述の同じプロバイダ・メカニズムを使用して、様々なプロバイダから様々なキーストア実装を選択できます。キーの管理を参照してください。
エンジン・クラスとアルゴリズム
エンジン・クラスは、特定の暗号化アルゴリズムまたはプロバイダに依存しない、特定の型の暗号化サービスへのインタフェースを提供します。
エンジン・クラスでは、次のいずれかを提供します。
- 暗号化操作(たとえば、暗号化、デジタル署名およびメッセージ・ダイジェスト)
- 暗号化データ(キーおよびアルゴリズム・パラメータ)のジェネレータまたはコンバータ
- 暗号化データをカプセル化し、上位の抽象レイヤーで使用できるオブジェクト(キーストアまたは証明書)。
次のエンジン・クラスが使用可能です。
SecureRandom: 乱数または擬似乱数の生成に使用します。MessageDigest: 指定データのメッセージ・ダイジェスト(ハッシュ)の計算に使います。Signature: キーによって初期化されます。データの署名およびデジタル署名の検証に使用します。Cipher: キーによって初期化されます。データの暗号化/復号化に使用します。対称バルク暗号化(例: AES)、非対称暗号化(例: RSA)、パスワードベース暗号化(例: PBE)など、様々なタイプのアルゴリズムがあります。-
Mac:MessageDigestと同様にメッセージ認証コード(MAC)もハッシュ値を生成しますが、メッセージの整合性を保護するために最初にキーによって初期化されます。 - KEM: 秘密キーと公開キーのペアから共有秘密キーを導出するために2つのパーティによって使用されます。
KeyFactory:Key型の既存の不透明な暗号化キーを「キー仕様」(基本のキー・データの透明な表現)に変換したり、その逆の変換を行うために使用します。SecretKeyFactory:SecretKey型の既存の不透明な暗号化キーを「キー仕様」(基本のキー・データの透明な表現)に変換したり、その逆の変換を行うために使用します。SecretKeyFactoryは、秘密(対称)キーのみを作成する特殊なKeyFactoryです。KeyPairGenerator: 指定のアルゴリズムでの使用に適合した、新しい公開キー、秘密キー・ペアの生成に使用します。KeyGenerator: 指定のアルゴリズムで使用するための、新しい秘密キーの生成に使用します。KeyAgreement: 特定の暗号化操作に使用する特定のキーの合意および確立のために、複数のパーティによって使用されます。AlgorithmParameters: パラメータのエンコードおよびデコードなど、特定のアルゴリズムのパラメータの格納に使用します。AlgorithmParameterGenerator: 特定のアルゴリズムに適したアルゴリズム・パラメータ・セットの生成に使用します。KeyStore: キーストアの作成および管理に使用します。キーストアは、キーのデータベースです。キーストア内の秘密キーには、それらのキーに関連付けられた証明書チェーンがあります。証明書チェーンは、対応する公開キーを認証します。また、キーストアには、信頼できるエンティティからの証明書も格納されています。CertificateFactory: 公開キーの証明書および証明書失効リスト(CRL)の作成に使用します。CertPathBuilder: 証明書チェーン(証明書パスとも呼ばれる)の構築に使用します。CertPathValidator: 証明書チェーンの検証に使います。CertStore: リポジトリからCertificateとCRLを取得するために使用します。
ノート:
ジェネレータ(generator)は最新の内容でオブジェクトを作成しますが、ファクトリ(factory)は既存の構成要素(エンコードなど)からオブジェクトを作成します。コア・クラスとインタフェース
次に、JCAで提供されるコア・クラスとインタフェースを示します。
ProviderおよびSecuritySecureRandom、MessageDigest、Signature、Cipher、Mac、KEM、KeyFactory、SecretKeyFactory、KeyPairGenerator、KeyGenerator、KeyAgreement、AlgorithmParameter、AlgorithmParameterGenerator、KeyStoreおよびCertificateFactoryエンジン・クラスKeyインタフェースおよびクラス、KeyPairAlgorithmParameterSpec Interface、AlgorithmParameters、AlgorithmParameterGenerator、およびjava.security.specパッケージとjavax.crypto.specパッケージ内のアルゴリズム・パラメータ仕様インタフェースおよびクラスKeySpec Interface、EncodedKeySpec、PKCS8EncodedKeySpecおよびX509EncodedKeySpecSecretKeyFactory、KeyFactory、KeyPairGenerator、KeyGenerator、KeyAgreementおよびKeyStore
このガイドでは、もっとも有用な高レベルのクラスについて最初に説明し(Provider、Security、SecureRandom、MessageDigest、Signature、Cipher、MacおよびKEM)、次に様々なサポート・クラスについて説明します。現時点では、キー(公開、非公開、および秘密)はさまざまなJCAクラスによって生成および表現され、高レベルのクラスによってその操作の一部として使用されるという説明だけで十分です。
このセクションでは、各クラスおよびインタフェースのメイン・メソッドの署名を示します。対応するコード例の項に、これらのクラス(MessageDigest、Signature、KeyPairGenerator、SecureRandom、KeyFactoryおよびキー仕様クラス)の例があります。
関連するSecurity APIパッケージの完全なリファレンス・ドキュメントは、次のパッケージのサマリーにあります。
Providerクラス
「暗号化サービス・プロバイダ」(このドキュメントでは「プロバイダ」とも呼ばれる)とは、JDK Security APIの暗号化機能のサブセットの固定実装を提供するパッケージまたはパッケージ・セットです。Providerクラスは、このようなパッケージやパッケージ・セットへのインタフェースです。プロバイダ名、バージョン番号、その他の情報にアクセスするためのメソッドを備えています。Providerクラスを使用すると、暗号化サービスの実装を登録するだけでなく、その他のセキュリティ・サービスの実装を登録することもできます。その他のセキュリティ・サービスは、JDK Security APIまたはその拡張機能の1つの一部として定義されている場合があります。
暗号化サービスの実装を提供するには、エンティティ(開発グループなど)は実装コードを作成し、Providerクラスのサブクラスを生成します。Providerサブクラスのコンストラクタは、プロバイダが実装したサービスを検索するためにJDK Security APIが使用する各種プロパティの値を設定します。つまり、サブクラスは、サービスを実装するクラス名を指定します。
プロバイダ・パッケージが実装できるサービスには、様々な種類があります。エンジン・クラスとアルゴリズムを参照してください。
実装が違うと、特徴も違ってくる場合があります。ソフトウェア・ベースのものもあれば、ハードウェア・ベースのものもあります。プラットフォーム独立のものもあれば、プラットフォーム固有のものもあります。また、レビューや評価用に使えるプロバイダ・コードもあれば、使えないものもあります。JCAでは、エンド・ユーザーと開発者の双方が独自のニーズを決定できます。
エンド・ユーザーのニーズに合った暗号化実装のインストール方法、および開発者のニーズに合った実装の要求方法について説明されています。
ノート:
プロバイダを実装するには、プロバイダの実装および統合までのステップを参照してください。プロバイダ実装の要求および獲得方法
この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
は、希望するエンジン・タイプ(たとえば、Signature、MessageDigest,またはCipher)です。たとえば:
Signature sig = Signature.getInstance("SHA256withRSA");
KeyAgreement ka = KeyAgreement.getInstance("DH");これらはそれぞれ、"SHA256withRSA" Signatureおよび"DH" KeyAgreementオブジェクトのインスタンスを返します。
Javaセキュリティ標準アルゴリズム名には、Java環境で使用するために標準化されている名前のリストがあります。一部のプロバイダには、同じアルゴリズムを参照する別名がある場合もあります。たとえば、SHA256アルゴリズムは、SHA-256と呼ばれる場合があります。必ずしもすべてのプロバイダが同じようにアルゴリズム名の別名を持つわけではないため、アプリケーションは別名ではなく標準名を使用する必要があります。
ノート:
アルゴリズム名の大文字と小文字は区別されません。たとえば、次の呼出しはすべて等価とみなされます。Signature.getInstance("SHA256withRSA")
Signature.getInstance("sha256withrsa")
Signature.getInstance("Sha256WithRsa")プロバイダを指定しない場合、getInstanceは、指定されたアルゴリズムに関連する、要求された暗号化サービスの実装に対応する登録プロバイダを検索します。どのJava仮想マシン(JVM)でも、プロバイダは指定の「優先順位」でインストールされます。優先順位とは、特定のプロバイダの指定がない場合に、プロバイダ・リストが検索される順位です。(プロバイダのインストールを参照。)たとえば、JVMに、PROVIDER_1とPROVIDER_2という2つのプロバイダがインストールされているとします。次のようになっていると仮定します。
PROVIDER_1はSHA256withRSAおよびAESを実装。PROVIDER_1の優先順位が1 (最高の優先順位)。PROVIDER_2はSHA256withRSA、SHA256withDSAおよびRC5を実装。PROVIDER_2の優先順位は2。
ここで、3つのシナリオについて見てみます。
- SHA256withRSA実装を検索する: どちらのプロバイダもこの実装を提供する。
PROVIDER_1が最高の優先順位を持ち最初に検索されるので、PROVIDER_1実装が返される。 - SHA256withDSA実装を検索する: 最初に
PROVIDER_1が検索される。実装が見つからないので次にPROVIDER_2が検索される。実装が見つかるので、このプロバイダが返される。 - SHA256withECDSA実装を検索する: これを実装するプロバイダはインストールされていないので、
NoSuchAlgorithmExceptionがスローされる。
プロバイダ引数を持つgetInstanceメソッドは、必要なアルゴリズムを提供するプロバイダを指定したい開発者のためのものです。たとえば、連邦証明書を受け取ったプロバイダ実装を連邦局が使いたいとします。PROVIDER_1はこの証明書を受け取っていないのに対し、PROVIDER_2がこれを受け取っているとします。
連邦局のプログラムは次の呼出しを実行します。PROVIDER_2が認証された実装を持つため、これを指定します。
Signature s = Signature.getInstance("SHA256withRSA", "PROVIDER_2");この場合、PROVIDER_2がインストールされていないと、インストールされている別のプロバイダが要求アルゴリズムを実装している場合であっても、NoSuchProviderExceptionがスローされます。
プログラムは、(SecurityクラスのクラスのgetProvidersメソッドを使用して)インストールされているすべてのプロバイダのリストを獲得し、リストからいずれかを選択することもできます。
ノート:
汎用アプリケーションは、特定のプロバイダから暗号化サービスを要求しないようにする必要があります。そうでない場合、アプリケーションは特定のプロバイダに結び付けられることになり、そのプロバイダはほかのJava実装で使用できない可能性があります。特定の要求されたプロバイダよりも優先順位が高い、使用可能な最適化されたプロバイダ(たとえば、PKCS11によるハードウェア・アクセラレータまたはMicrosoftのMSCAPIなどのネイティブOS実装)を利用できない場合もあります。プロバイダのインストール
暗号化プロバイダを使用するには、最初にインストールし、次に静的または動的に登録する必要があります。さまざまなSunプロバイダがこのリリースに同梱されており(SUN、SunJCE、SunJSSE、SunRsaSignなど)、すでにインストールおよび登録されています。以降のセクションでは、追加プロバイダのインストールおよび登録方法について説明します。
すべてのJDKプロバイダは、すでにインストールされ登録されています。ただし、サードパーティ製プロバイダが必要な場合は、クラス・パスまたはモジュール・パスへのプロバイダの追加、プロバイダの登録(静的または動的)、必要なアクセス権の追加の方法の詳細を、プロバイダの実装および統合までのステップのステップ8: テストの準備で参照してください。
Securityクラス
Securityクラスは、インストールされているプロバイダおよびセキュリティに関するプロパティを管理します。このクラスに含まれるのは、インスタンスが生成されないstaticメソッドだけです。プロバイダの追加または削除、およびSecurityプロパティの設定用メソッドは、信頼できるプログラムによってしか実行できません。現在の信頼できるプログラムは、次のいずかです:
- セキュリティ・マネージャの管理下で実行されていないローカル・アプリケーション
- 指定されたメソッドの実行権限があるアプレットまたはアプリケーション
警告:
セキュリティ・マネージャおよびそれに関連するAPIは非推奨であり、今後のリリースでは削除されます。セキュリティ・マネージャの代わりとなるものはありません。詳細および代替手段については、JEP 411を参照してください。あるコードが、実行しようとしているアクション(プロバイダの追加など)を行うことができるような、信頼できるコードであると判断するには、その特定のアクションを行う適切な権限がそのアプレットに与えられている必要があります。インストールしたJDKのポリシー構成ファイルでは、指定されたコード・ソースからのコードに対し、どのようなアクセス権(システム・リソースへのアクセスの種類)を与えるかを指定します。「デフォルトのPolicyの実装とポリシー・ファイルの構文」と「Java SEプラットフォームのセキュリティ・アーキテクチャ」を参照してください。
実行されるコードは、常に、特定の「コード・ソース」から来ると考えられます。コード・ソースには、そのコードの元の場所(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.*";
}; このポリシーが有効になっている場合は、ローカル・ファイル・システム上の/home/sysadmin/ディレクトリによって署名されたJARファイル内のコードで、プロバイダを追加または削除できます。コードに署名は必要ありません。
codeBaseとsignedByを両方とも省略した例を次に示します。
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プロパティに似ていますが、セキュリティ関連です。これらのプロパティは、静的(<java-home>/conf/security/java.securityファイルを使用)または動的(APIを使用)に設定できます。security.provider.nセキュリティ・プロパティで静的にプロバイダを登録する例は、プロバイダの実装および統合までのステップのステップ8.1: プロバイダの構成を参照してください。プロパティを動的に設定する場合、信頼できるプログラムは次のメソッドを使用できます。
static String getProperty(String key)
static void setProperty(String key, String datum)ノート:
セキュリティ・プロバイダのリストは、VMの起動中に確定します。そのため、プロバイダ・リストを変更する場合は、前述のメソッドを使用してください。SecureRandomクラス
SecureRandomクラスは、初期シード値から疑似乱数列を生成する決定性アルゴリズムである、疑似乱数ジェネレータ(PRNG)にアクセスするか、ランダム性の発生源(たとえば、/dev/randomまたは実際の乱数ジェネレータ)を読み取ることで、暗号として強力な乱数を提供するエンジン・クラス(エンジン・クラスとアルゴリズムを参照)です。PRNGの一例は、NIST SP 800-90Ar1で示されているDeterministic Random Bits Generator (DRBG)です。実装の中には、真の乱数を生成するものもあれば、両方の技術の組み合わせを使用するものもあります。暗号として強力な乱数は、少なくとも、FIPS 140-2、暗号化モジュールのセキュリティ要件の第4.9.1項で示されている統計的乱数生成テストに適合しています。
すべてのJava SE実装は、java.security.Securityクラスのsecurerandom.strongAlgorithmsプロパティで提供される、もっとも強力な(もっともランダム性の高い)SecureRandomの実装を示す必要があります。この実装は、特に強力な乱数値が必要な場合に使用できます。
securerandom.drbg.configプロパティは、SUNプロバイダでDRBGのSecureRandom構成および実装を指定するために使用されます。securerandom.drbg.configは、java.security.Securityクラスのプロパティです。その他のDRBG実装でも、securerandom.drbg.configプロパティを使用できます。
SecureRandomオブジェクトの作成
SecureRandomのインスタンスは、いくつかの方法で取得できます。
-
すべてのJava SE実装は、引数なしのコンストラクタ
new SecureRandom()を使用してデフォルトのSecureRandomを提供します。このコンストラクタは、登録されているセキュリティ・プロバイダのリスト内を、最も推奨されるプロバイダから順に確認し、その後、SecureRandom乱数ジェネレータ(RNG)アルゴリズムをサポートする最初のプロバイダから新しいSecureRandomオブジェクトを返します。どのプロバイダもRNGアルゴリズムをサポートしていない場合は、SUNプロバイダからSHA1PRNGを使用するSecureRandomオブジェクトを返します。 -
SecureRandomの特定の実装を獲得するには、プロバイダ実装の要求および獲得方法のいずれかを使用します。 -
java.security.Securityクラスのsecurerandom.strongAlgorithmsプロパティで定義される強力なSecureRandom実装を取得するには、getInstanceStrong()メソッドを使用します。このプロパティは、重要な値を生成するのに適したプラットフォーム実装をリストします。
SecureRandomオブジェクトのシードまたは再シード
SecureRandomオブジェクトは、getInstance()の呼出し後に次のsetSeedメソッドのいずれかを呼び出さないかぎり、ランダム・シードで初期化されます。
void setSeed(byte[] seed)
void setSeed(long seed)
最初のnextBytes呼出しの前にsetSeedを呼び出して、環境のランダム性を防ぐ必要があります。
SecureRandomオブジェクトによってもたらされるビットのランダム性は、シード・ビットのランダム性によって異なります。
SecureRandomオブジェクトは、setSeedメソッドまたはreseedメソッドのいずれかを使用していつでも再シードされる可能性があります。setSeedで指定されたシードは、既存のシードと置き換えられるのではなく、既存のシードに追加されます。したがって、呼出しを繰り返しても、ランダム性が減少しないことが保証されます。
MessageDigestクラス
MessageDigestクラスは、暗号として安全なメッセージ・ダイジェスト(SHA-256やSHA-512など)の機能を提供するように設計されているエンジン・クラス(エンジン・クラスとアルゴリズムを参照)です。安全な暗号化メッセージ・ダイジェストは、任意サイズの入力(バイト配列)を取り、固定サイズ出力を生成します。これをダイジェストまたはハッシュと言います。
たとえば、SHA-256のアルゴリズムは32バイトのダイジェストを生成し、SHA-512のアルゴリズムは64バイトのダイジェストを生成します。
ダイジェストには次の2つの特徴があります。
- 計算によって、同じ値にハッシュされた2つのメッセージを検索できない。
- ダイジェストの生成に使った入力情報を明らかにしない。
メッセージ・ダイジェストを使い、一意で、信頼できるデータ識別子を生成します。データ識別子をデータの「チェックサム」または「デジタル指紋」と呼ぶこともあります。メッセージのうちの1ビットを変更するだけで、異なるダイジェスト値が生成されます。
メッセージ・ダイジェストには多くの用途があり、意図的かどうかにかかわらず、いつデータが変更されたかを判断できます。ダイジェスト・アルゴリズムを選択する場合は、最近のリファレンスを調べて、そのステータスと当面のタスクでの妥当性を判断する必要があります。
ダイジェストの計算
様々なタイプのdigest()メソッドを使用してダイジェストを計算する手順を示します。
updateメソッドを呼び出して提供する必要があります。メッセージ・ダイジェスト・オブジェクトのアップデートを参照してください。
Signatureクラス
Signatureクラスは、SHA256withDSAやSHA512withRSAなどの暗号化デジタル署名アルゴリズムの機能を提供するように設計されているエンジン・クラス(エンジン・クラスとアルゴリズムを参照)です。安全な暗号化署名アルゴリズムは、任意サイズの入力と秘密キーを取り、署名と呼ばれる比較的短い(固定サイズの場合もよくある)バイト文字列を生成します。このプロパティは次のとおりです。
- 公開キーと非公開キーのペアの所有者のみが署名を作成できる。計算によって、公開キーおよびいくつかの署名のみを保持するユーザーが秘密キーを回復することはできない。
- 署名の生成に使う秘密キーに対応する公開キーが与えられる。これは入力の認証性および整合性を検証できる。
Signatureオブジェクトは、秘密キーでの署名に対して初期化され、署名するデータを与えられます。結果の署名バイトは、通常は署名されたデータとともに保持されます。検証が必要な場合、別のSignatureオブジェクトが作成され、検証に対して初期化されて対応する公開キーを与えられます。データおよび署名バイトはSignature (署名)オブジェクトに渡され、データと署名が一致した場合に、Signatureオブジェクトは成功を報告します。
署名はメッセージ・ダイジェストに似ていますが、提供する保護の種類の点で目的が大きく異なっています。実際、SHA256WithRSAなどのアルゴリズムは、メッセージ・ダイジェストSHA256を使用して最初に大きなデータ・セットを管理しやすい形式に圧縮し、次にRSAアルゴリズムで結果の32バイトのメッセージ・ダイジェストに署名します。
データの署名と検証の例は、生成されたキーを使った署名の生成および検証を参照してください。
Signatureオブジェクトの状態
Signatureオブジェクトはモデル・オブジェクトです。つまり、Signatureオブジェクトは、常に指定の状態にあり、この状態で1つの型の操作だけを実行できます。
状態は、個々のクラスで定義したfinal (ファイナル)整数の定数で表されます。
Signatureオブジェクトに可能な状態は、次の3つです。
UNINITIALIZEDSIGNVERIFY
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オブジェクトによる署名
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エンコードの使用の詳細は、Javaセキュリティ標準アルゴリズム名を参照してください。
signメソッドを呼び出すと、signature (署名)オブジェクトは、initSignを呼び出して最初に署名用に初期化されたときの状態にリセットされます。つまり、オブジェクトをリセットするので、必要であればupdateとsignを新たに呼び出して、同じ秘密キーを使って別の署名を生成できます。
あるいは異なる秘密キーを指定して、initSignへの新規呼出しを作成したり、(署名を検証するためにSignatureオブジェクトを初期化する目的で) initVerifyへの新規呼出しを作成したりできます。
Signatureオブジェクトによる検証
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クラスは、暗号化および復号化で使用される暗号機能を提供します。暗号化とは、データ(クリアテキストと呼ばれる)およびキーを受け取って、キーを知らないサードパーティにとって無意味なデータ(暗号テキスト)を生成する処理のことです。復号化はその逆で、暗号テキストおよびキーを受け取り、クリアテキストを生成する処理です。
対称暗号化方式と非対称暗号化方式
暗号化には、対称(秘密キーとも呼ばれる)と非対称(または公開キー暗号化方式)という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の組合せを再利用して暗号化を行わないでください。つまり、Cipherオブジェクトは、暗号化操作のたびに異なるパラメータ・セットで明示的に初期化しなおす必要があります。
例2-2 GCMモードでAES Cipherを使用するサンプル・コード
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)];
// conclusion of encryption operation
int actualOutputLen = c.doFinal(plainText, 0, plainText.length, cipherText);
// 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, 0, actualOutputLen);
// 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)の名前が必ず含まれます。これにモードおよびパディング・スキームが続く場合もあります。
変換は、次の書式で記述されます。
- "algorithm/mode/padding"または
- "algorithm"
たとえば、次は有効な変換です。
"AES/CBC/PKCS5Padding"
"AES"変換名だけを指定すると、要求された変換の実装がその環境で使用可能かどうかをシステムが判別し、複数の実装が存在する場合には優先度の高い実装が存在するかどうかを返します。
変換名とパッケージ・プロバイダの両方を指定すると、システムは要求されたパッケージ内に要求された変換の実装が存在するかどうかを判別し、存在しない場合には例外をスローします。
アルゴリズム、モードおよびパディングを完全に指定した変換を使用することをお薦めします。そうしないと、プロバイダはデフォルトを使用します。たとえば、SunJCEプロバイダとSunPKCS11プロバイダは、多くの対称暗号でECBをデフォルト・モードとして、PKCS5Paddingをデフォルト・パディングとして使用します。
このため、SunJCEプロバイダでは、
Cipher c1 = Cipher.getInstance("AES/ECB/PKCS5Padding");および
Cipher c1 = Cipher.getInstance("AES");という文は、等価になります。
ノート:
ECBモードは最も簡単に使用できるブロック暗号モードであり、デフォルトの暗号モードになっています。ECBは、単一のデータ・ブロックに対しては正常に機能し、パラレル化可能ですが、モードの特性により、通常は複数のデータ・ブロックの暗号化には使用しないでください。これにより、機密データが簡単かつ完全に開示される可能性があります。このモードは使用可能ですが、関連する暗号化リスクを理解している場合にのみ使用してください。CFBやOFBなどのモードを使用すると、ブロック暗号は、暗号の実際のブロック・サイズよりも小さい単位でデータを暗号化できます。このようなモードを要求する場合、AES/CFB8/NoPaddingおよびAES/OFB32/PKCS5Padding変換で示されるように、一度に処理するビット数を、モード名に追加することで指定することもできます。数値を指定しない場合、プロバイダ固有のデフォルトが使用されます。(たとえば、SunJCEプロバイダではAESにデフォルトの256ビットが使用されます。)したがって、CFB8やOFB8などの8ビット・モードを使用することで、ブロック暗号をバイト指向のストリーム暗号に変換できます。
Javaセキュリティ標準アルゴリズム名には、変換のアルゴリズム名、モードおよびパディング・スキーム・コンポーネントの指定に使用可能な標準名のリストが掲載されています。
ファクトリ・メソッドにより返されるオブジェクトは初期化されていないため、使用する前に初期化する必要があります。
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メソッドはキー・オブジェクトに対して直接機能するため、コードの記述が容易になります。次のメソッドを使用すると、ハードウェア・ベースのキーの安全な転送も可能になります。
Keyをwrapする場合、まずWRAP_MODEのCipherオブジェクトを初期化し、次のメソッドを呼び出します。
public final byte[] wrap(Key key);ラップされたキーのバイト(wrapを呼び出した結果)を、そのラップを解除する他のユーザーに提供する場合、受信者がunwrapを実行するのに必要な、次の追加情報も送信する必要があります。
- キー・アルゴリズムの名前
- ラップされたキーの型(
Cipher.SECRET_KEY、Cipher.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_KEY、Cipher.PRIVATE_KEY、Cipher.PUBLIC_KEYのいずれかである必要があります。
アルゴリズム・パラメータの管理
基盤となるCipher実装により使用されるパラメータ(アプリケーションによりinitメソッドに明示的に渡されたか、基盤となる実装自体により生成されたパラメータ)は、getParametersメソッドを呼び出すことによりCipherオブジェクトから取得できます。このメソッドは、パラメータをjava.security.AlgorithmParametersオブジェクト(または、パラメータが使用されない場合はnull)として返します。パラメータが初期化ベクトル(IV)の場合には、getIVメソッドを呼び出すことによってもパラメータを取得できます。
次の例では、パスワードベース暗号化(PBE)を実装するCipherオブジェクトを、パラメータを使用せずにキーのみを使用して初期化します。ただし、選択されたパスワードベース暗号化用に選択されたアルゴリズムは、saltおよびiteration countという2つのパラメータを必要とします。これらは、基盤となるアルゴリズム実装自体により生成されます。アプリケーションは、生成されたパラメータをCipherオブジェクトから取得できます。例2-3を参照してください。
復号化には、暗号化に使用したのと同じパラメータを使用する必要があります。これらは、それらのエンコーディングからインスタンス化でき、対応するCipherオブジェクトを復号化用に初期化する際に使用できます。例2-4を参照してください。
Cipherオブジェクトの初期化時にパラメータを一切指定せず、基盤となる実装がいずれかのパラメータを使用するかどうか不明な場合、CipherオブジェクトのgetParametersメソッドを呼び出して、返される値をチェックするだけで確認できます。戻り値がnullの場合、パラメータが使用されなかったことを示します。
SunJCEプロバイダにより実装される次の暗号アルゴリズムは、パラメータを使用します。
- AES、DES-EDEおよびBlowfishは、フィードバック(つまりCBC、CFB、OFBまたはPCBC)モードでの使用時に、初期化ベクトル(IV)を使用します。
javax.crypto.spec.IvParameterSpecクラスは、指定されたIVでのCipherオブジェクトの初期化に使用できます。また、CTRモードとGCMモードでは、IVが必要になります。 - PBE Cipherアルゴリズムは、saltと繰返し処理の回数を含む、一連のパラメータを使用します。
javax.crypto.spec.PBEParameterSpecクラスを使用して、saltおよび繰返し処理回数を指定してPBEアルゴリズムを実装するCipherオブジェクトを初期化できます(たとえば: PBEWithHmacSHA256AndAES_256)。
SealedObjectクラスを使用する場合、復号化操作に使用するアルゴリズム・パラメータの格納または転送について心配する必要はありません。このクラスは、シール(暗号化)に使用されるパラメータを暗号化されたオブジェクト・コンテンツに添付します。また、アンシール(復号化)でも同じパラメータを使用します。
例2-3 Cipherオブジェクトからパラメータを取得するサンプル・コード
アプリケーションは、暗号化用の生成されたパラメータを次の方法でCipherオブジェクトから取得できます。
// 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();例2-4 複合化用にCipherオブジェクトを初期化するサンプル・コード
復号化には、暗号化に使用したのと同じパラメータを使用する必要があります。これらは、それらのエンコーディングからインスタンス化でき、次に示すように、対応するCipherオブジェクトを復号化用に初期化する際に使用できます。
// get parameter object for password-based encryption
AlgorithmParameters algParams;
algParams = AlgorithmParameters.getInstance("PBEWithHmacSHA256AndAES_256");
// initialize with parameter encoding from the previous example
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 the previous example
c.init(Cipher.DECRYPT_MODE, myKey, algParams);暗号出力時の考慮事項
CipherのupdateおよびdoFinalメソッドの中には、呼出し側による出力バッファの指定が可能なものがあります。暗号化または復号化されたデータは、このバッファ内に出力されます。この場合、暗号化または復号化操作の結果を保持できるだけの大きさのバッファを渡すことは重要です。
Cipher内の次のメソッドを使用して、設定すべき出力バッファのサイズを確認できます。
public int getOutputSize(int inputLen)Cipherベースのその他のクラス
Cipherを内部的に使用して共通の暗号使用への簡単なアクセスを提供する、いくつかのヘルパー・クラスがあります。
Cipher Streamクラス
CipherInputStreamクラスとCipherOutputStreamクラスは、Cipherストリーム・クラスです。
CipherInputStreamクラス
このクラスは、通過するデータの暗号化または復号化を行うFilterInputStreamです。これは、InputStreamで構成されています。CipherInputStreamは、Cipherオブジェクトの挿入先の、セキュリティ保護された入力ストリームを表します。CipherInputStreamのreadメソッドは、基盤となるInputStreamから読み取られ、埋め込まれたCipherオブジェクトにより追加処理されたデータを返します。Cipherオブジェクトは、CipherInputStreamで使用する前に完全に初期化する必要があります。
たとえば、埋め込まれたCipherが復号化用に初期化されている場合、CipherInputStreamは基盤となるInputStreamから読み込んだデータの復号化を試みてから、データをアプリケーションに返します。
このクラスは、上位クラスjava.io.FilterInputStreamおよびjava.io.InputStreamのセマンティックス(特にエラーに関するセマンティックス)に厳密に準拠します。このクラスは、上位クラスで指定されたメソッドを正確に保持し、それらすべてをオーバーライドします。このため、埋め込まれた暗号によるデータの追加処理が可能になります。さらに、このクラスは、上位クラスがスローしない例外をすべてキャッチします。特に、skip(long)メソッドは、Cipherに処理されたデータのみを無視します。
このクラスを使用するプログラマにとって、このクラスで定義またはオーバーライドされていないメソッド(上位クラスのいずれかに後で追加された新規メソッドまたはコンストラクタ)を使用しないようにすることは重要です。これらのメソッドの設計と実装は、CipherInputStreamへのセキュリティ面の影響を考慮に入れていないためです。例2-5でその使用方法を参照してください。cipher1が暗号化用に初期化されている場合を考えてみましょう。このプログラムは、ファイル/tmp/a.txtから内容を読み取って暗号化し、結果(暗号化されたバイト)を/tmp/b.txtに格納します。
例2-6では、CipherInputStreamおよびFileInputStreamの複数のインスタンスを簡単に接続する方法を示します。この例では、cipher1およびcipher2が、それぞれ暗号化および復号化用に(対応するキーを使用して)初期化されているものとします。このプログラムは、ファイル/tmp/a.txtの内容を/tmp/b.txtにコピーします。ただし、/tmp/a.txtからの読取り時に、最初に内容の暗号化、次に復号化が行われます。実際のところ、このプログラムはテキストを暗号化した後、すぐに復号化を行うため、CipherInputStreamsのチェーンをわかりやすく示す以外は特に有用なものではありません。
CipherInputStreamの読取りメソッドは、基盤となる暗号からデータが返されるまでブロックします。ブロック暗号が使用される場合、基盤となるInputStreamから暗号テキストの完全なブロックが取得される必要があります。
例2-5 CipherInputStreamとFileInputStreamを使用するサンプル・コード
次のコードは、暗号を含むCipherInputStream、およびFileInputStreamを使用して、入力ストリーム・データを暗号化する方法を示しています:
try (FileInputStream fis = new FileInputStream("/tmp/a.txt");
CipherInputStream cis = new CipherInputStream(fis, cipher1);
FileOutputStream 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);
}
}例2-6 CipherInputStreamとFileInputStreamを接続するサンプル・コード
次の例では、CipherInputStreamおよびFileInputStreamの複数のインスタンスを簡単に接続する方法を示します。この例では、cipher1およびcipher2が、それぞれ暗号化および復号化用に(対応するキーを使用して)初期化されているものとします。
try (FileInputStream fis = new FileInputStream("/tmp/a.txt");
CipherInputStream cis1 = new CipherInputStream(fis, cipher1);
CipherInputStream cis2 = new CipherInputStream(cis1, cipher2);
FileOutputStream 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);
}
} CipherOutputStreamクラス
このクラスは、通過するデータの暗号化または復号化を行うFilterOutputStreamです。これは、OutputStreamまたはそのサブクラスのいずれか、およびCipherで構成されます。CipherOutputStreamは、Cipherオブジェクトの挿入先の、セキュリティ保護された出力ストリームを表します。CipherOutputStreamのwriteメソッドは、埋め込まれたCipherオブジェクトを使用してデータを処理してから、基盤となるOutputStreamにデータを書き出します。Cipherオブジェクトは、CipherOutputStreamで使用する前に完全に初期化する必要があります。
たとえば、埋め込まれたCipherが暗号化用に初期化されている場合、CipherOutputStreamはデータを暗号化してから、基盤となる出力ストリームに書き出します。
このクラスは、上位クラスjava.io.OutputStreamおよびjava.io.FilterOutputStreamのセマンティックス(特にエラーに関するセマンティックス)に厳密に準拠します。このクラスは、上位クラスで指定されたメソッドを正確に保持し、それらすべてをオーバーライドします。このため、埋め込まれた暗号によるすべてのデータの追加処理が可能になります。さらに、このクラスは、上位クラスがスローしない例外をすべてキャッチします。
このクラスを使用するプログラマにとって、このクラスで定義またはオーバーライドされていないメソッド(上位クラスのいずれかに後で追加された新規メソッドまたはコンストラクタ)を使用しないようにすることは重要です。これらのメソッドの設計と実装は、CipherOutputStreamへのセキュリティ面の影響を考慮に入れていないためです。
例2-7でその使用方法を参照してください。cipher1が暗号化用に初期化されている場合を考えてみましょう。このプログラムは、ファイル/tmp/a.txtから内容を読み取って暗号化し、結果(暗号化されたバイト)を/tmp/b.txtに格納します。
例2-7では、CipherOutputStreamおよびFileOutputStreamの複数のインスタンスを簡単に接続する方法を示します。この例では、cipher1およびcipher2が、それぞれ復号化および暗号化用に(対応するキーを使用して)初期化されているものとします。このプログラムは、ファイル/tmp/a.txtの内容を/tmp/b.txtにコピーします。ただし、/tmp/b.txtに書き込む前に、内容の暗号化および復号化が行われます。
ブロック暗号アルゴリズムを使用する場合は、データが暗号化されて基盤となる出力ストリームに送信される前に、プレーン・テキスト・データの完全なブロックをCipherOutputStreamに与える必要があります。
このクラスのflushメソッドとcloseメソッドには、他に1つの重要な相違点があります。カプセル化されたCipherオブジェクトがパディングを有効にしてブロック暗号アルゴリズムを実装する場合、この相違点に特に留意する必要があります。
flushは、カプセル化されたCipherオブジェクトにより処理済のバッファリングされた出力バイトを、すべて強制的に書き出すことにより、基盤となるOutputStreamをフラッシュします。カプセル化されたCipherオブジェクトによりバッファリングされ、処理待ち状態にあるバイトは、書き出されません。closeは、基盤となるOutputStreamを閉じて、関連付けられたすべてのシステム・リソースを解放します。カプセル化されたCipherオブジェクトのdoFinalメソッドを呼び出して、このオブジェクトによりバッファリングされたすべてのバイトを処理します。さらにflushメソッドを呼び出して、処理したバイトを基盤となるストリームに書き込みます。
例2-7 CipherOutputStreamとFileOutputStreamを使用するサンプル・コード
このコードは、暗号およびFileOutputStreamを含むCipherOutputStreamを使用して、暗号化されたデータを出力ストリームに書き出す方法を示します。try (FileInputStream fis = new FileInputStream("/tmp/a.txt");
FileOutputStream fos = new FileOutputStream("/tmp/b.txt");
CipherOutputStream 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();
}例2-8 CipherOutputStreamとFileOutputStreamを接続するサンプル・コード
このコードでは、CipherOutputStreamおよびFileOutputStreamの複数のインスタンスを簡単に接続する方法を示します。この例では、cipher1およびcipher2が、それぞれ復号化および暗号化用に(対応するキーを使用して)初期化されているものとします。try (FileInputStream fis = new FileInputStream("/tmp/a.txt");
FileOutputStream fos = new FileOutputStream("/tmp/b.txt");
CipherOutputStream cos1 = new CipherOutputStream(fos, cipher1);
CipherOutputStream 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();
}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つの方法で復元可能です。
- 厳密に同一のアルゴリズム、キー、パディング・スキームなどで初期化され、オブジェクトのシールに使用された
Cipherオブジェクトを使用する方法。c.init(Cipher.DECRYPT_MODE, sKey); try { String s = (String)so.getObject(c); } catch (Exception e) { // do something };この方法には、暗号解読キーに関する知識がなくても、シールされたオブジェクトのアンシールを実行できるという利点があります。たとえば、あるパーティが暗号オブジェクトを必須の暗号解読キーを使って初期化したあとで、別のパーティに渡すと、そのパーティはシールされたオブジェクトをアンシールできます。
- 適切な復号化キーを使用する方法(AESは対称暗号化アルゴリズムであるため、シールとアンシールに同じキーを使用できる)
try { String s = (String)so.getObject(sKey); } catch (Exception e) { // do something };この方法では、
getObjectメソッドは、適切な暗号解読アルゴリズム用の暗号オブジェクトを作成し、シール済みのオブジェクトに格納された特定の暗号解読キーおよびアルゴリズム・パラメータ(存在する場合)を使用してそれを初期化します。この方法の利点は、オブジェクトをアンシールするパーティが、オブジェクトのシールに使用したパラメータ(IVなど)を追跡する必要がないことです。
Macクラス
MessageDigestと同様に、メッセージ認証コード(MAC)は、送信された情報や信頼できないメディアに保存されている情報の整合性をチェックする方法を提供しますが、秘密キーが計算に含まれます。
適切なキーを持つ人だけが、受信したメッセージを検証できます。一般に、メッセージ認証コードは、秘密キーを共有する2つのパーティ間で送信される情報の有効性を検証する場合に使用されます。
暗号化ハッシュ機能に基づくMACメカニズムは、HMACと呼ばれます。HMACは、秘密共有キーと組み合せて、SHA-256などの任意の暗号化ハッシュ機能で使用できます。
Macクラスは、メッセージ認証コード(MAC)の機能を提供します。HMAC-SHA256の例を参照してください。
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);KEMクラス
KEMクラスは、キー・カプセル化メカニズム(KEM)の機能を提供するエンジン・クラス(エンジン・クラスとアルゴリズムを参照)です。
KEMを使用すると、二者間で非対称キーまたは公開キーの暗号化を使用して、対称キーを保護できます。送信者はencapsulateメソッドをコールして、秘密キーとキー・カプセル化メッセージを生成し、受信者はdecapsulateメソッドをコールして、同じ秘密キーをキー・カプセル化メッセージから復元します。
準備
受信者は、KeyPairGeneratorを使用して、キーのペアを作成する必要があります。公開キーは公開され、送信者が使用可能になり、秘密キーは秘密に保持されます。
KEMオブジェクトの作成
各パーティは、KEMオブジェクトを作成する必要があります。KEMオブジェクトは、KEM getInstance() staticファクトリ・メソッドの1つを使用することによって作成されます。プロバイダ実装の要求および獲得方法を参照してください。
エンカプスレータおよびデカプスレータの作成
送信者側で、KEMオブジェクトのnewEncapsulatorメソッドのいずれかをコールして、エンカプスレータを作成します。受信者の公開キーがプロセスで使用されます。受信者側で、KEMオブジェクトのnewDecapsulatorメソッドのいずれかをコールして、デカプスレータを作成します。受信者の秘密キーがプロセスで使用されます。
カプセル化およびカプセル化解除
送信者は、新しく作成されたKEM.Encapsulatorオブジェクトのencapsulateメソッドのいずれかをコールし、KEM.Encapsulatedオブジェクトを返します。KEM.Encapsulatedオブジェクト内の秘密キーは秘密に保持され、内部にあるキー・カプセル化メッセージは受信者に送信されます。
受信者は、送信者からのキー・カプセル化メッセージを、新しく作成されたKEM.Decapsulatorオブジェクトのdecapsulateメソッドのいずれかに渡し、SecretKeyオブジェクトを返します。この秘密キーは、送信者側の秘密キーと同じです。
送信者は、受信者との将来のセキュアな通信にキーを使用できます。
コード例については、キーのカプセル化およびカプセル化解除を参照してください。
Keyインタフェース
java.security.Keyインタフェースは、すべての不透明なキーに関するトップ・レベルのインタフェースです。すべての不透明なキー・オブジェクトが共有する機能を定義します。
ここまでは、キーとは何か、キーはどのように生成および表現されるかについて詳細は説明せずに、JCAの高レベルの使用に重点を置いてきました。ここではキーに注意を向けます。
「不透明な」キーの表現では、キーを構成するキー・データに直接アクセスできません。つまり、不透明な場合、キーへのアクセスが、Keyインタフェースで定義されているgetAlgorithm、getFormatおよびgetEncodedの3つのメソッドに制限されます。
これは、対応するKeySpecインタフェース(KeySpecインタフェースを参照)で定義されているgetメソッドのいずれかによって各キー・データ値に個別にアクセスできる、透明な表現とは対照的です。
不透明なキーはすべて、次の3つの特性を持ちます。
- アルゴリズム
- そのキーのキー・アルゴリズムです。キー・アルゴリズムとは、通常の場合、暗号化アルゴリズムか非対称操作アルゴリズム(
AES、DSAまたはRSAなど)のことであり、そのようなアルゴリズムや関連するアルゴリズム(SHA256withRSAなど)と協調動作します。キーのアルゴリズムの名前は、次のメソッドを使用して取得されます。String getAlgorithm() - エンコードされた形式
- キーの外部エンコード形式は、キーを別の組織に転送する場合など、キーの標準表示がJava仮想マシンの外部で必要なときに使います。キーは標準形式(X.509やPKCS8など)に従ってエンコードされ、次のメソッドを使用して返されます。
byte[] getEncoded() - フォーマット
- エンコードされたキーのフォーマット名です。次のメソッドから返されます。
String getFormat()
キーは一般的に、KeyGeneratorクラスやKeyPairGeneratorクラスなどのキー・ジェネレータ、証明書、KeyFactoryを使用するキー仕様(KeySpecインタフェースを参照)、またはキー管理で使用するキーストア・データベースにアクセスする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つの表現です。CipherはKeyオブジェクトを使用して暗号化アルゴリズムを初期化しますが、伝送またはストレージのためにキーを移植性の高い形式に変換する必要がある場合があります。
キーの透明な表現とは、対応する仕様クラスで定義されたgetメソッドの1つを使って、各キー・データ値に個々にアクセスできるということです。たとえば、DSAPrivateKeySpecは、getX、getP、getQ、およびgetGメソッドを定義し、秘密キーxおよびキーの計算に使用するDSAアルゴリズムのパラメータ(素数のp、サブ素数のq、およびベースのg)にアクセスします。キーがハードウェア・デバイス上に格納されている場合は、そのキー仕様には、デバイス上のキーの識別を助ける情報が含まれていることがあります。
この表現と対照的なのが、Keyインタフェースのインタフェースによって定義されるような、不透明な表現です。不透明なキーの表現では、キー要素フィールドに直接アクセスできません。つまり、不透明な表現では、キーへのアクセスが、Keyインタフェースによって定義されるgetAlgorithm、getFormatおよびgetEncodedの3つのメソッドに制限されます。
キーは、アルゴリズムに固有の方法か、またはアルゴリズムに依存しないエンコーディング形式(ASN.1など)で指定できます。たとえば、DSA秘密キーは、秘密キーのコンポーネントx、p、q、およびgによって指定するか(DSAPrivateKeySpecを参照)、または、秘密キーのDERエンコーディングを使って指定することが可能です(PKCS8EncodedKeySpecを参照)。
KeyFactoryおよびSecretKeyFactoryクラスは、不透明なキーの表現と透明なキーの表現の間(つまり、KeyとKeySpecの間。操作が可能であると想定)で変換を行うために使用できます。(たとえば、スマート・カード上の秘密キーは、カードから取り出せない場合があります。そのようなKeyは変換不可能です。)
次のセクションでは、java.security.specパッケージ内に含まれるキー仕様のインタフェースおよびクラスについて説明します。
KeySpecインタフェース
このインタフェースには、メソッドまたは定数が含まれていません。このインタフェースの唯一の目的は、すべてのキー仕様をグループ化することおよびそれらのグループに安全な型を提供することです。すべてのキー仕様で、このインタフェースを実装する必要があります。
EncodedKeySpecクラス
この抽象クラス(KeySpecインタフェースのインタフェースを実装する)は、エンコードされた形式の公開キーまたは秘密キーを表します。そのgetEncodedメソッドは、次のエンコードされたキーを返します。
abstract byte[] getEncoded();
このクラスのgetFormatメソッドは、次のエンコード形式の名前を返します。
abstract String getFormat();
固定実装PKCS8EncodedKeySpecおよびX509EncodedKeySpecについては、次のセクションを参照してください。
ジェネレータとファクトリ
JavaおよびJCA APIをはじめて使用する人は、ジェネレータとファクトリの区別がつかない場合があります。
ジェネレータは、新しいオブジェクトを生成する場合に使用されます。ジェネレータは、アルゴリズム依存またはアルゴリズム非依存で初期化できます。たとえば、Diffie-Hellman (DH)キー・ペアを作成するために、アプリケーションは必要なPおよびG値を指定できます。または、ジェネレータを適切なキーの長さで単純に初期化できます。ジェネレータは適切なPおよびG値を選択します。どちらの場合も、ジェネレータはパラメータに基づいて新しいキーを生成します。
一方、ファクトリは、既存のオブジェクト型から別のオブジェクト型へデータを変換する場合に使用されます。たとえば、アプリケーションで、DH秘密キーのコンポーネントが使用可能になっている場合があります。アプリケーションは、それらをKeySpecインタフェースとしてパッケージ化できますが、KeyAgreementオブジェクトで使用できるようPrivateKeyオブジェクトに変換する必要があります。または逆の変換が必要な場合もあります。または、それらに証明書のバイト配列があるが、CertificateFactoryを使用して、X509Certificateオブジェクトに変換する必要がある場合があります。アプリケーションは、変換を行うためにファクトリ・オブジェクトを使用します。
KeyFactoryクラス
KeyFactoryクラスは、不透明な暗号化Keyインタフェースとキー仕様のインタフェースおよびクラス(背後のキー・データの透明な表現)との間の変換を実行するために設計されたエンジン・クラスとアルゴリズムです。
キー・ファクトリは、双方向性があります。つまり、これによって、与えられたキー仕様(キーのデータ)から不透明なキー・オブジェクトを構築することも、キー・オブジェクトの背後のキー・データを適切な形式で取得することもできます。
同一のキーに対して、複数の互換性のあるキー仕様を存在させることもできます。たとえば、DSA公開キーは、コンポーネントy、p、qおよびgによって指定することも(java.security.spec.DSAPublicKeySpecを参照)、X.509標準に従ってDERエンコードを使用して指定することも(X509EncodedKeySpecクラスを参照)できます。
キー・ファクトリは、互換性のあるキー仕様間の変換に使用できます。互換性のあるキー仕様間の変換では、キーの構文解析が行われます。たとえば、X509EncodedKeySpecをDSAPublicKeySpecに変換する場合は、基本的にエンコードされたキーをコンポーネント単位に解析します。例については、「キー仕様および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クラスのインスタンスに返される必要があることを指示します。キー仕様およびKeyFactoryを使った署名の生成と検証を参照してください。
SecretKeyFactoryクラス
SecretKeyFactoryクラスは、秘密キーのファクトリを表します。KeyFactoryクラス(KeyFactoryクラスを参照)とは異なり、javax.crypto.SecretKeyFactoryオブジェクトは秘密(対称)キーのみを処理し、一方、java.security.KeyFactoryオブジェクトはキー・ペアの公開キーおよび秘密キー・コンポーネントを処理します。
キー・ファクトリは、java.security.Key型の不透明な暗号キーであるKeyインタフェースと、適切な形式の基本のキー・データの透明な表現であるキー仕様のインタフェースおよびクラスとの間の変換を行うために使用します。
java.security.Key型のオブジェクト(java.security.PublicKey、java.security.PrivateKey、およびjavax.crypto.SecretKeyはそのサブクラス)は、その実装方法が不明であるため、不透明なキー・オブジェクトになります。基盤となる実装はプロバイダ依存であるため、ソフトウェア・ベースにもハードウェア・ベースにもできます。キー・ファクトリを使用すると、プロバイダは独自の暗号化キー実装を提供できるようになります。
たとえば、公開値y、素数モジュラスp、ベースgで構成されるDiffie-Hellman公開キーのキー仕様を保持しており、同じ仕様を別のプロバイダのDiffie-Hellmanキー・ファクトリに送る場合、生成されるPublicKeyオブジェクトは大概、異なる基盤実装を保持するようになります。
プロバイダは、秘密キー・ファクトリがサポートするキー仕様をドキュメント化する必要があります。たとえば、SunJCEプロバイダにより提供されるDESキーのSecretKeyFactoryは、DESKeySpecをDESキーの透明表現としてサポートします。また、DES-EDEキーのSecretKeyFactoryはDESedeKeySpecをDES-EDEキーの透明表現として、PBEのSecretKeyFactoryはPBEKeySpecを基盤となるパスワードの透明表現として、それぞれサポートします。
次の例は、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オブジェクトとキー仕様間の変換
SecretKeyオブジェクトがある場合は、getKeySpecメソッドの呼出しによって、対応するキー仕様オブジェクトを取得できます。
KeySpec getKeySpec(Key key, Class keySpec)keySpecは、キーのデータが返されるべき仕様クラスを識別します。たとえば、DESKeySpec.classは、キーのデータがDESKeySpecクラスのインスタンスに返される必要があることを指示します。
KeyPairGeneratorクラス
KeyPairGeneratorクラスは、エンジン・クラス(エンジン・クラスとアルゴリズムを参照)であり、公開キーと秘密キーのペアの生成に使用します。
キーのペアの生成方法には、アルゴリズム独立型とアルゴリズム固有型の2つがあります。この2つの唯一の相違点は、オブジェクトの初期化にあります。
KeyPairGeneratorのメソッドの呼出し例は、「キーのペアの生成」を参照してください。
KeyPairGeneratorの作成
すべてのキーのペアは、最初にKeyPairGeneratorを使って生成します。KeyPairGeneratorオブジェクトは、KeyPairGeneratorのgetInstance() staticファクトリ・メソッドの1つを使用することによって取得されます。プロバイダ実装の要求および獲得方法を参照してください。
KeyPairGeneratorの初期化
特定のアルゴリズム用のキーのペアのジェネレータは、そのアルゴリズムで使うことができる公開キーまたは秘密キーを作成します。また、アルゴリズム固有型のパラメータを生成された各キーに関連付けます。
まず、キーのペアを初期化してからでなければ、キーのペアはキーを生成できません。ほとんどの場合、アルゴリズム独立型の初期化で十分です。ただし、その他の場合は、アルゴリズム固有型の初期化を使用できます。
アルゴリズムに依存しない初期化
すべてのキー・ジェネレータは、キー・サイズおよび乱数発生の元の概念を共有します。キー・サイズは、アルゴリズムごとに解釈が異なります。たとえば、DSAアルゴリズムの場合、キー・サイズはモジュラスの長さと一致します。(特定のアルゴリズムのキー・サイズについては、Javaセキュリティ標準アルゴリズム名を参照。)
普遍的に共有されるこれら2つの引数の型をとるinitializeメソッドがあります。
void initialize(int keysize, SecureRandom random) また、keysize引数だけをとり、システムが提供する乱数の発生源を使用するinitializeメソッドもあります。
void initialize(int keysize)アルゴリズムに依存しないこれらのinitializeメソッドを呼び出した場合、その他のパラメータは指定されないため、それぞれのキーに関連したアルゴリズム固有のパラメータが存在する場合、そのパラメータの処理はプロバイダによって異なります。
アルゴリズムがDSAアルゴリズムで、モジュラスのサイズ(キー・サイズ)が512、768、1024、2048または3072の場合は、SUNプロバイダはp、q、およびgパラメータに事前計算済の一連の値を使用します。モジュラスのサイズがこれらの値のいずれでもない場合、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のgetInstance() staticファクトリ・メソッドの1つを使用することによって取得されます。プロバイダ実装の要求および獲得方法を参照してください。
KeyGeneratorオブジェクトの初期化
特定の対称キー・アルゴリズムのキー・ジェネレータは、そのアルゴリズムで使用可能な対称キーを作成します。また、生成されたキーに、アルゴリズム固有のパラメータ(存在する場合)を関連付けます。
キーの生成方法には、アルゴリズム独立型とアルゴリズム固有型の2つがあります。この2つの唯一の相違点は、オブジェクトの初期化にあります。
- アルゴリズムに依存しない初期化
すべてのキー・ジェネレータが、キー・サイズおよび乱数発生の元の概念を共有します。普遍的に共有されるこれら2つの引数の型をとる
initメソッドが存在します。また、keysize引数のみをとり、システムにより提供される乱数の発生源を使用するものや、乱数の発生源だけをとるものも存在します。public void init(SecureRandom random); public void init(int keysize); public void init(int keysize, SecureRandom random);アルゴリズムに依存しないこれらの
initメソッドを呼び出した場合、その他のパラメータは指定されないため、生成されたキーに関連したアルゴリズム固有のパラメータが存在する場合、そのパラメータの処理はプロバイダによって異なります。 - アルゴリズム固有の初期化
アルゴリズム固有のパラメータのセットがすでに存在する状況では、
AlgorithmParameterSpec引数を取る2つのinitメソッドが存在します。このうちの一方はSecureRandom引数も取りますが、他方では、乱数の発生源はシステムによって提供されます。public void init(AlgorithmParameterSpec params); public void init(AlgorithmParameterSpec params, SecureRandom random);
クライアントが(initメソッドの呼出しによって) KeyGeneratorを明示的に初期化しない場合は、各プロバイダがデフォルトの初期化を提供(および文書化)する必要があります。
キーの作成
次のメソッドにより、秘密キーが生成されます。
public SecretKey generateKey();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キー交換の例では、lastPhaseをTRUEに設定してdoPhaseを1回呼び出します。Diffie-Hellmanで3つのパーティが存在する場合、doPhaseを2度呼び出します。最初はlastPhaseをFALSEに設定し、2度目にはlastPhaseをTRUEに設定します。
共有される秘密の生成
各パーティがすべての必須キー協定フェーズを実行したあとで、generateSecretメソッドのいずれかを呼び出して共有される秘密を計算できます。
public byte[] generateSecret();
public int generateSecret(byte[] sharedSecret, int offset);
public SecretKey generateSecret(String algorithm);
キーの管理
「キーストア」と呼ばれるデータベースは、キーおよび証明書のリポジトリを管理するために使用できます。証明書とは、あるエンティティが発行したデジタル署名付きの文書で、別なエンティティの公開キーが特定の値であることを証明しています。
キーストアの場所
ユーザー・キーストアは、デフォルトではユーザーのホーム・ディレクトリの.keystoreという名前のファイルに格納されます。ユーザーのホーム・ディレクトリは、user.homeシステム・プロパティによって決まります。このデフォルト値は、オペレーティング・システムによって異なります。
- LinuxおよびmacOS:
/home/username/ - Windows:
C:\Users\username\
キーストア・ファイルは必要な場所に配置できます。環境によっては、複数のキーストアが存在することに意味がある場合があります。たとえば、1つのキーストアがユーザーの秘密キーを保持し、別のキーストアが、信頼関係を確立するために使用される証明書を保持する場合があります。
ユーザーのキーストア以外に、JDKは、さまざまな証明書発行局(CA)からの信頼できる証明書を格納するために使用されるシステム全体のキーストアも保持します。これらのCA証明書は、信頼性の判定を行う場合に使用できます。たとえば、SSL/TLS/DTLSでSunJSSEプロバイダがリモート・ピアから証明書を提示された場合、デフォルトのトラスト・マネージャは、次のいずれかのファイルを調べて、その接続を信頼できるかどうかを判断します。
- LinuxおよびmacOS:
<java-home>/lib/security/cacerts - Windows:
<java-home>\lib\security\cacerts
システム全体のcacertsキーストアを使用するかわりに、アプリケーションに独自のキーストアを設定して使用することも、前述のユーザー・キーストアを使用することも可能です。
キーストアの実装
KeyStoreクラスは、キーストア内の情報へのアクセスおよび情報の変更を行うための、明確に定義されたインタフェースを提供します。複数の異なる固定実装を生成できます(それぞれ特定の型のキーストアを実装)。
現在、KeyStoreを使用する2つのコマンド行ツール(keytoolおよびjarsigner)があります。様々なソースのコードに対して(システム・リソースへの)アクセス権を指定するポリシー・ファイルを処理する場合、このツールはPolicyのリファレンス実装によっても使われます。KeyStoreはpublicとして使用可能なので、JDKユーザーはKeyStoreを使った他のセキュリティ・アプリケーションも作成できます。
アプリケーションでは、KeyStoreクラスのgetInstanceファクトリ・メソッドを使うことで、さまざまなプロバイダから異なる型のキーストア実装を選択できます。キーストアの型は、キーストア情報のストレージ形式とデータ形式を定義するとともに、キーストア内の秘密キーとキーストア自体の整合性を保護するために使われるアルゴリズムを定義します。異なる型のキーストアの実装には、互換性はありません。
デフォルトのキーストア実装はpkcs12です。これは、RSA PKCS12 Personal Information Exchange Syntax Standardに基づくクロス・プラットフォーム・キーストアです。この規格は、ユーザーの秘密キー、証明書、その他の秘密を格納および転送することを主な目的としています。PKCS12キーストア内の個々のエントリに任意の属性を関連付けることができます。
keystore.type=pkcs12ツールおよび他のアプリケーションで異なるデフォルト・キーストア実装を使用するには、この行を変更して様々な型を指定します。
keytoolなどの一部のアプリケーションでも、-storetypeコマンド行パラメータによって、デフォルトのキーストアの型をオーバーライドできます。
ノート:
キーストアの型の指定では、大文字と小文字は区別されません。たとえば、「jks」と「JKS」は同じものとして扱われます。PKCS12は、推奨されている、デフォルトのキーストア型です。ただし、他に3つの型のキーストアが、JDK実装に付属しています。
- jceksは、jksに対する独自の代替キーストア形式であり、トリプルDESによるパスワードベース暗号化を使用します。
jceks実装は、jksキーストア・ファイルを解析してjceks形式に変換できます。キーストア内の非公開キーエントリのパスワードを変更し、キーストアの型として
"-storetype jceks"を指定することによって、「jks」型のキーストアを「jceks」型のキーストアにアップグレードできます。提供される(より)強力な暗号化によるキー保護を、デフォルト・キーストア内の「signkey」という名前の秘密キーに適用する場合は、次のコマンドを入力します。このコマンドにより、旧パスワードおよび新規パスワードの指定が求められます。
Java Development Kitツール仕様のkeytool -keypass -alias signkey -storetype jcekskeytoolに関する項を参照してください。 - jksがもうひとつのオプションです。これは、独自のキーストアの型(形式)を利用するもので、キーストアをファイルとして実装しています。この実装では、個々の秘密キーは独自の個別のパスワードによって保護され、キーストア全体の整合性も(秘密キーとは別の)パスワードによって保護されます。
- "dks"は、ドメイン・キーストアです。これは、単一の論理的なキーストアとして表されるキーストアの集合です。特定のドメインを構成するキーストアは、
DomainLoadStoreParameterで説明されている構文を持つ構成データによって指定されます。
キーストアの実装は、プロバイダ・ベースです。独自のKeyStore実装を記述する必要がある場合は、Java暗号化アーキテクチャでのプロバイダの実装方法を参照してください。
KeyStoreクラス
KeyStoreクラスは、キーストア内の情報へのアクセスおよび情報の変更を行うための、明確に定義されたインタフェースを提供します。
KeyStoreクラスは、エンジン・クラスおよびアルゴリズムです。
このクラスは、メモリー内のキーおよび証明書のコレクションを表します。KeyStoreは次の2種類のエントリを管理します。
-
キーのエントリ: このタイプのキーストア・エントリには、非常に重要な暗号化キー情報が保持されます。この情報は、権限のないアクセスから保護する必要があります。通常、この種類のエントリに格納されるキーは秘密キーで、対応する公開キーを証明する証明書チェーンが伴います。
秘密キーおよび証明書チェーンは、デジタル署名を使った自己認証用に特定のエンティティが使用します。たとえば、ソフトウェア配布団体は、リリースまたはライセンスするソフトウェアの一部としてJARファイルにデジタル署名を付けます。
-
信頼できる証明書のエントリ: このタイプのエントリには、別の相手に属する単一の公開キー証明書が含まれます。これは、信頼できる証明書と呼ばれます。それは、証明書内の公開キーが、証明書のsubject(所有者)によって特定されるアイデンティティに由来するものであることを、キーストアの所有者が信頼するからです。
この種類のエントリは、ほかの組織の認証に使うことができます。
キーストア内の各エントリは、「別名」文字列によって識別されます。秘密キーとそれに関連付けられた証明書チェーンの場合は、これらの文字列はエンティティ自体が認証するというように、方法別に区別されます。たとえば、エンティティが異なる証明書発行局を使ったり、異なる公開キー・アルゴリズムを使ったりして、エンティティ自体を認証することも可能です。
キーストアが持続性があるかどうか、および持続性がある場合に使われるメカニズムは、ここでは指定されません。この規則により、重要な(秘密または非公開)キーを保護するための様々な技術を使うことができます。スマート・カードまたはその他の統合暗号化エンジン(SafeKeyper)を使うことも1つの方法です。また、ファイルなどのより単純なメカニズムを様々な形式で使うこともできます。
これ以降、主なKeyStoreメソッドについて説明します。
KeyStoreオブジェクトの作成
KeyStoreオブジェクトは、KeyStoreのgetInstance()メソッドのいずれかを使用して取得されます。プロバイダ実装の要求および獲得方法を参照してください。
特定キーストアのメモリーへのロード
KeyStoreオブジェクトを使う前に、loadメソッドによってメモリー内に実際のキーストア・データをロードする必要があります。
final void load(InputStream stream, char[] password)オプションのパスワードを使って、キーストア・データの整合性をチェックします。パスワードが提供されない場合は、整合性のチェックは行われません。
空のキーストアを作成するには、InputStream引数としてnullをloadメソッドに渡します。
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キーストアは、任意の属性を含むエントリをサポートします。属性を作成するには、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では、getP、getQ、およびgetGメソッドを定義して、それぞれp、q、およびgにアクセスするなど)。
これに対して、AlgorithmParametersクラスは不透明な表現を提供します。「不透明な」表現では、パラメータ・フィールドに直接アクセスできません。パラメータ・セットに関連付けられたアルゴリズム名の取得(getAlgorithmによる)、およびそのパラメータ・セット用のある種のエンコードの取得(getEncodedによる)しかできません。
AlgorithmParameterSpecインタフェース
AlgorithmParameterSpecは、暗号化パラメータの透明な仕様へのインタフェースです。このインタフェースには、メソッドまたは定数が含まれていません。このインタフェースの唯一の目的は、すべてのパラメータの仕様をグループ化すること(およびそれらのパラメータに安全な型を提供すること)です。すべてのパラメータの仕様で、このインタフェースを実装する必要があります。
次に、java.security.specパッケージとjavax.crypto.specパッケージ内のアルゴリズム・パラメータ仕様インタフェースおよびクラスを示します。
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オブジェクトで表現されるパラメータのバイト・エンコードは、getEncodedへの呼出しによって取得できます。
byte[] getEncoded()このメソッドは、パラメータをプライマリ・エンコード形式で返します。この種のパラメータのASN.1仕様が存在する場合は、プライマリ復号化形式は、ASN.1です。
特定のエンコード形式でパラメータが返されるようにするには、次のように記述します。
byte[] getEncoded(String format) formatがnullの場合は、他のgetEncodedメソッドと同様に、プライマリ・エンコード形式が使用されます。
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アルゴリズムのパラメータの場合、「サイズ」は素数モジュラスのビット数のサイズに一致します。(特定のアルゴリズムのサイズの詳細は、Javaセキュリティ標準アルゴリズム名を参照。)この方法を使うと、アルゴリズム固有型のパラメータの生成値が(ある場合は)、デフォルト基準値になります。普遍的に共有されるこれら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)のオブジェクトを生成するために使用されます。
CertificateFactoryクラスは、エンジン・クラスおよびアルゴリズムです。
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 5280「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()
初めにデフォルトのエンコーディングが一覧表示されます。
標準名
「標準名」ドキュメントには、アルゴリズム仕様に関する情報が含まれています。
Javaセキュリティ標準アルゴリズム名では、JDK Security APIで必要となり使用されるアルゴリズム、証明書およびキーストア・タイプの標準名について説明します。これには、アルゴリズムの仕様に関する詳細も含まれています。特定のプロバイダの情報は、JDKプロバイダ・ドキュメントにあります。
JDKでの暗号化実装は、主に歴史的な理由により、さまざまなプロバイダによって配布されます(Sun、SunJSSE、SunJCE、SunRsaSign)。これらのプロバイダは、すべてのJDK実装で使用可能ではない場合があるため、真に移殖可能なアプリケーションは、特定のプロバイダを指定せずにgetInstance()を呼び出す必要があります。特定のプロバイダを指定するアプリケーションは、基盤となるオペレーティング環境(PKCSやMicrosoftのCAPIなど)用に調整されたネイティブ・プロバイダを使用できない場合があります。
SunPKCS11プロバイダ自体には暗号化アルゴリズムは含まれていませんが、代わりに要求を基盤となるPKCS11実装に送ります。 PKCS#11リファレンス・ガイドおよび基盤となるPKCS11実装を参照して、必要なアルゴリズムがPKCS11プロバイダで使用可能かどうかを判断するようにしてください。同様に、Windowsシステムでは、SunMSCAPIプロバイダは暗号化機能を提供しませんが、代わりに基盤となるオペレーティング・システムに要求を処理するよう渡します。
SSL/TLS実装でのJCAの使用方法
JCAのクラスについて理解したため、次に、これらのクラスがどのように結合されてSSL/TLSのような高度なネットワーク・プロトコルを実装するかについて考えます。
「TLSおよびDTLSプロトコル」のSSL/TLSの概要の項に、プロトコルの機能のおおまかな説明があります。非対称(公開キー)暗号操作は対称操作(秘密キー)よりも大幅に遅いため、公開キー暗号方式は、実際のアプリケーション・データを保護するために使用される秘密キーを確立する場合に使用されます。非常に単純に言うと、SSL/TLSハンドシェークには、初期化データの交換、いくつかの公開キー操作の実行による秘密キーへの到達、およびそのキーを使用したそのあとのトラフィックの暗号化が含まれます。
ノート:
ここで説明する内容では、一部のクラスを取り上げて、どのように使用できるかを示しています。このセクションでは、SSL/TLS実装を構築するための十分な情報は提供しません。詳細は、Java Secure Socket Extension (JSSE)リファレンス・ガイドおよびRFC 5246: Transport Layer Security (TLS)プロトコル、バージョン1.2を参照してください。この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オブジェクトの作成を開始します。次の例では、サーバーのみの認証を使用します。
サーバーのみの認証を、次の例で説明します。この例は非常に単純化されていますが、より高レベルのプロトコルを作成するためにJSSEクラスをどのように組み合せることができるかを示しています。
例2-9 SSL/TLSサーバーでTLS_RSA_WITH_AES_128_CBC_SHAなどのRSAベースの暗号群を使用する
サーバーのKeyManagerがクエリーされ、適切なRSAエントリを返します。サーバーの資格(つまり、証明書/公開キー)がサーバーのCertificateメッセージ内で送信されます。クライアントのTrustManagerはサーバーの証明書を検証し、受け入れた場合、クライアントはSecureRandomオブジェクトを使用していくつかのランダム・バイトを生成します。次に、サーバーの証明書内で見つかったPublicKeyによって初期化された暗号化非対称RSA Cipherオブジェクトを使用して暗号化されます。この暗号化されたデータは、Client Key Exchangeメッセージ内で送信されます。サーバーは、対応するPrivateKeyを使用し、同様のCipherを復号モードで使用してバイトを回復します。これらのバイトは、実際の暗号化キーを確立する場合に使用されます。
例2-10 一時的な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などのブロック暗号が使用される場合は、データにパディングを行なって完全なブロックにします。リモート側では、ステップは単純に逆になります。
暗号強度の構成
管轄ポリシー・ファイル(管轄ポリシー・ファイル形式を参照)とセキュリティ・プロパティ・ファイルを使用してJava Cryptography Extension (JCE)アーキテクチャの暗号強度を構成できます。
Oracle Java JDK 9より前は、Oracle実装によって許可されているデフォルト暗号強度は、強力だが制限付きでした(たとえば、128ビットに制限されたAESキー)。この制限をなくすには、管理者が、無制限強度の管轄ポリシー・ファイルのバンドルを別にダウンロードしてインストールします。管轄ポリシー・ファイルのメカニズムは、JDK 9用に改変されました。現在は、より柔軟な構成が可能になっています。Oracle JDKでは、デフォルト値が、limitedではなくunlimitedになりました。これまでどおり、管理者およびユーザーは、自分の地理的な場所に応じたすべての輸入/輸出ガイドラインに従う必要があります。アクティブな暗号強度は、構成ディレクトリにある管轄ポリシー・ファイルと組み合せてセキュリティ・プロパティ(通常は、java.securityプロパティ・ファイル内で設定される)を使用して決定されるようになっています。
無制限の暗号強度または強力だが制限付きの暗号強度を提供するために必要なJCEポリシー・ファイルはすべてJDKに含まれています。
暗号強度の設定
<java_home>/conf/security/policyの下の各ディレクトリは、それらに含まれている管轄ポリシー・ファイルで定義された一連のポリシー構成を表します。ディレクトリ内のポリシー・ファイルによって表される特定の暗号強度設定をアクティブ化するには、crypto.policyセキュリティ・プロパティ(ファイル<java_home>/conf/security/java.security内で構成される)をそのディレクトリを指すように設定します。
ノート:
通常、java.securityファイル内のプロパティは1回のみ解析されます。このファイル内のプロパティを変更した場合は、アプリケーションを再起動して、変更が正しく反映されていることを確認します。limitedとunlimitedという2つのディレクトリがセットになっており、それぞれにいくつかのポリシー・ファイルが含まれています。デフォルトでは、crypto.policyセキュリティ・プロパティは、次のように設定されています。 crypto.policy = unlimitedこの値全体は、このディレクトリに含まれているファイル間で共通の部分です。これらのポリシー・ファイル設定は、VM規模で適用され、このVM上で実行されるすべてのアプリケーションに影響します。アプリケーション・レベルで暗号強度をオーバーライドする必要がある場合は、「暗号化制限からアプリケーションを除外する方法」を参照してください。
Unlimitedディレクトリの内容
unlimitedディレクトリには、次のポリシー・ファイルが含まれています。
-
<java_home>/conf/security/unlimited/default_US_export.policy// Default US Export policy file. grant { // There is no restriction to any algorithms. permission javax.crypto.CryptoAllPermission; };ノート:
現在は米国からの暗号化の輸出に関する制限はないため、default_US_export.policyファイルは制限なしで設定されます。 -
<java_home>/conf/security/unlimited/default_local.policy// Country specific policy file for countries with no limits on crypto strength. grant { // There is no restriction to any algorithms. permission javax.crypto.CryptoAllPermission; };ノート:
国によっては、現地の制限事項がある場合がありますが、このポリシー・ファイルはunlimitedディレクトリにあるため、ここでは制限は示されません。
これら2つのファイルで定義されているように無制限暗号強度を選択するには、ファイル<java_home>/conf/security/java.security内でcrypto.policy = unlimitedと設定します。
Limitedディレクトリの内容
limitedディレクトリには、現在、次のポリシー・ファイルが含まれています。
-
<java_home>/conf/security/limited/default_US_export.policy// Default US Export policy file. grant { // There is no restriction to any algorithms. permission javax.crypto.CryptoAllPermission; };ノート:
たとえこれがlimitedディレクトリ内にあるとしても、現在は米国からの暗号化の輸出に関する制限はないため、default_US_export.policyファイルは制限なしで設定されます。 -
<java_home>/conf/security/limited/default_local.policy// Some countries have import limits on crypto strength. This policy file // is worldwide importable. grant { permission javax.crypto.CryptoPermission "DES", 64; permission javax.crypto.CryptoPermission "DESede", *; permission javax.crypto.CryptoPermission "RC2", 128, "javax.crypto.spec.RC2ParameterSpec", 128; permission javax.crypto.CryptoPermission "RC4", 128; permission javax.crypto.CryptoPermission "RC5", 128, "javax.crypto.spec.RC5ParameterSpec", *, 12, *; permission javax.crypto.CryptoPermission "RSA", *; permission javax.crypto.CryptoPermission *, 128; };ノート:
このローカル・ポリシー・ファイルでは、デフォルトの制限が示されます。輸入制限があるものを含め、これは国によって許可される必要があります。ただし、法的ガイダンスを入手してください。 -
<java_home>/conf/security/limited/exempt_local.policy// Some countries have import limits on crypto strength, but may allow for // these exemptions if the exemption mechanism is used. grant { // There is no restriction to any algorithms if KeyRecovery is enforced. permission javax.crypto.CryptoPermission *, "KeyRecovery"; // There is no restriction to any algorithms if KeyEscrow is enforced. permission javax.crypto.CryptoPermission *, "KeyEscrow"; // There is no restriction to any algorithms if KeyWeakening is enforced. permission javax.crypto.CryptoPermission *, "KeyWeakening"; };ノート:
輸入制限がある国は、limitedを使用する必要がありますが、除外メカニズムを採用できる場合は、これらの制限を緩和できます。「暗号化制限からアプリケーションを除外する方法」を参照してください。ご自分の状況に応じた法的ガイダンスを入手してください。
カスタム暗号強度の設定
制限をlimitedまたはunlimitedディレクトリにあるポリシー・ファイル内の設定とは異なる暗号強度に設定するには、limitedおよびunlimitedと同列に新しいディレクトリを作成し、そこにポリシー・ファイルを配置します。たとえば、customというディレクトリを作成するとします。このcustomディレクトリに、ファイルdefault_*export.policyまたはexempt_*local.policy、あるいはその両方を含めます。
customディレクトリ内のファイルで定義されているように暗号強度を選択するには、ファイル<java_home>/conf/security/java.security内でcrypto.policy = customと設定します。
管轄ポリシー・ファイル形式
JCAの管轄ポリシー・ファイルは、Java形式のポリシー・ファイル(対応するアクセス権を指定する文を含む)で表されます。暗号強度の構成で説明したように、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>
]
]
]
];
次に、AESアルゴリズムを128ビットの最大キー・サイズに制限する、管轄ポリシー・ファイルのサンプルを示します。
grant {
permission javax.crypto.CryptoPermission "AES", 128;
// ...
};
アクセス権エントリは、単語permissionで始まります。アクセス権エントリの各項目は、指定された順序で記述する必要があります。各エントリはセミコロンで終わります。識別子(grant、permission)では大文字と小文字は区別されませんが、<crypto permission class name>と、値として引き渡される文字列については大文字と小文字が区別されます。アスタリスク(*)は、すべてのアクセス権エントリ・オプションでワイルドカードとして使用できます。たとえば、<alg_name>オプションにアスタリスクを指定すると、すべてのアルゴリズムという意味になります。
次の表に、アクセス権エントリのオプションを示します。
表2-1 アクセス権エントリのオプション
| オプション | 説明 |
|---|---|
<crypto permission class name> |
暗号化アクセス権クラスは、特定の環境下で特定のキー・サイズで、特定のアルゴリズムを使用するアプリケーションの機能に対応します。暗号化アクセス権クラスには、 |
<alg_name> |
"AES"や"RSA"など、暗号化アルゴリズムの標準名を指定する引用符付き文字列。オプションです。 |
<exemption mechanism name> |
除外メカニズムを示す引用符付き文字列。除外メカニズムを実施すると、暗号化制限が緩和されます。オプションです。 使用可能な除外メカニズム名には、「KeyRecovery」、「KeyEscrow」および「KeyWeakening」が含まれます。 |
<maxKeySize> |
指定したアルゴリズムに許可する最大キー・サイズ(ビット)を示す整数値。オプションです。 |
<AlgorithmParameterSpec class name> |
アルゴリズムの強度を指定するクラス名。オプションです。 アルゴリズムによっては、キー・サイズでアルゴリズムの強度を指定するだけでは不十分な場合があります。たとえば、「RC5」アルゴリズムの場合には、ラウンド数も考慮する必要があります。アルゴリズムの強度をキー・サイズだけで表現するのでは不十分な場合、このオプションを使用して、これを行う |
<parameters for constructing an AlgorithmParameterSpec object> |
指定されたAlgorithmParameterSpecオブジェクトを構成するためのパラメータのリスト。<AlgorithmParameterSpec class name>が指定されており、パラメータが必要な場合は必須です。
|
暗号化制限からアプリケーションを除外する方法
注意:
大半のアプリケーション開発者には、この項で説明する内容は関係ありません。関係があるのは、作成するアプリケーションが、政府により暗号化制限の課された国に輸出される可能性があり、アプリケーションをその制限に適合させる必要がある場合だけです。デフォルトでは、アプリケーションは、どの強度の暗号化アルゴリズムでも使用できます。ただし、一部の国の政府による輸入管理制限のため、それらのアルゴリズムの強度を制限する必要がある場合があります。JCAフレームワークには、様々な管轄コンテキスト(場所)でアプリケーションに使用可能な暗号化アルゴリズムの最大強度に関して、制限を強制する機能が含まれます。管轄ポリシー・ファイルでこれらの制限を指定します。管轄ポリシー・ファイルとそれらを作成および構成する方法の詳細は、暗号強度の構成を参照してください。
これらの国の一部またはすべてで、特定のアプリケーションに対し、暗号化制限の一部またはすべての除外が許可されています。たとえば、特定の種類のアプリケーションは特別とみなされ、除外されます。また、キー復元などの除外メカニズムを利用するアプリケーションは、除外可能です。この種の国では、除外されたとみなされるアプリケーションは、除外されていないアプリケーションに許可されるよりも強力な暗号化にアクセスできます。
実行時にアプリケーションが除外されていると認識されるようにするには、次の条件を満たす必要があります。
- JARファイル内にアクセス権ポリシー・ファイルをバンドルする必要があります。アクセス権ポリシー・ファイルには、アプリケーションが保持する暗号化関連のアクセス権、およびそれを保持する条件(存在する場合)を指定します。
- プリケーションおよびアクセス権ポリシー・ファイルを含むJARファイルは、アプリケーションの除外が受け入れられた後に発行されたコード署名証明書で署名されている必要があります。
次に、一部の暗号化制限からアプリケーションを除外するために必要なステップのサンプルを示します。これは、除外されたものとしてアプリケーションを認識および処理するため、JCAにより要求される情報を含む、基本情報です。実際には、アプリケーションを実行可能にする(政府が暗号化制限を課している)特定の国の除外要件を知る必要があります。また、除外されたアプリケーションの処理プロセスを保持するJCAフレームワーク・ベンダーの要件も理解しておく必要があります。詳細は、ベンダーにお尋ね下さい。
ノート:
SunJCEプロバイダは、ExemptionMechanismSpiクラスの実装を提供しません
- アプリケーション・コードの記述およびコンパイル
- 適切な暗号化アクセス権を付与するアクセス権ポリシー・ファイルの作成
- テストの準備
- 政府の課す制限に関する政府承認の申請
- コード署名証明書の取得
- JARファイルへのアプリケーションおよびアクセス権ポリシー・ファイルのバンドル
- ステップ7.1: コード署名証明書の取得
- 制限国のユーザーと同じ環境の設定
- アクセス権ポリシー・ファイルのエントリによって指定された除外メカニズムを実装するプロバイダのインストール(除外メカニズムを使用するアプリケーションのみ)
- アプリケーションのテスト
- 米国政府による輸出承認の申請(必要な場合)
- アプリケーションのデプロイ
除外メカニズムを使用するアプリケーションに対する特殊コード要件
アプリケーションに、関連付けられたアクセス権ポリシー・ファイルがあり(同じJARファイル内)、そのアクセス権ポリシー・ファイルで除外メカニズムが指定されている場合、CipherのgetInstanceメソッドが呼び出されてCipherがインスタンス化されると、JCAコードは、インストール済プロバイダの中から、指定された除外メカニズムを実装するプロバイダを検索します。目的のプロバイダが見つかると、JCAは、そのプロバイダの実装に関連付けられたExemptionMechanism APIオブジェクトをインスタンス化してから、ExemptionMechanismオブジェクトを、getInstanceによって返されたCipherと関連付けます。
Cipherをインスタンス化した後、初期化する前に(Cipherのinitメソッドを呼び出して)、コードから次のCipherメソッドを呼び出す必要があります。
public ExemptionMechanism getExemptionMechanism()
この呼出しにより、Cipherに関連付けられたExemptionMechanismオブジェクトが返されます。次に、返されたExemptionMechanismに対して次のメソッドを実行して、除外メカニズムの実装を初期化する必要があります。
public final void init(Key key)
ここで指定する引数の型は、このあとでCipher initメソッドに指定する引数の型と同じにする必要があります。
ExemptionMechanismの初期化が完了したら、通常と同じ方法でCipherを初期化して使用できます。
アクセス権ポリシー・ファイル
実行時にアプリケーションが暗号化制限の一部またはすべてから除外されていると認識されるには、JARファイル内にアクセス権ポリシー・ファイルをバンドルする必要があります。アクセス権ポリシー・ファイルには、アプリケーションが保持する暗号化関連のアクセス権、およびそれを保持する条件(存在する場合)を指定します。
除外されるアプリケーションにバンドルされるアクセス権ポリシー・ファイル内のアクセス権エントリの書式は、JDKとともにダウンロードされる管轄ポリシー・ファイルの書式と同じです。次にその書式を示します。
permission <crypto permission class name>
[<alg_name>
[
[, <exemption mechanism name>]
[, <maxKeySize>
[, <AlgorithmParameterSpec class name>,
<parameters for constructing an AlgorithmParameterSpec object>
]
]
]
];
管轄ポリシー・ファイル形式を参照してください。
除外されるアプリケーションのアクセス権ポリシー・ファイル
アプリケーションの中には、制限を完全に解除可能なものもあります。通常、その種のアプリケーションにバンドルするアクセス権ポリシー・ファイルには、次を含めるだけで十分です。
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>には除外メカニズムの名前を指定します。次に、除外メカニズムの名前に使用可能なものをリストします:
KeyRecoveryKeyEscrowKeyWeakening
例として、キー復元またはキー・エスクローのいずれかが機能すると、アプリケーションが除外される場合を考えましょう。その場合、アクセス権ポリシー・ファイルには、次が含まれます。
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の最大キー・サイズおよびアルゴリズム・パラメータ値は、除外アクセス権エントリにより決定されます。
アプリケーションにバンドルされるアクセス権ポリシー・ファイル内の関連するエントリに、暗黙的に設定された除外アクセス権が存在しない場合、またはいずれかの登録済プロバイダから利用可能な、指定された除外メカニズムの実装が存在しない場合、デフォルトの標準暗号化アクセス権のみがアプリケーションに付与されます。
アプリケーションのパッケージ化
アプリケーションは、次の3種類のモジュールにパッケージ化できます。
-
名前付きまたは明示的モジュール: モジュール・パスに存在し、
module-info.classファイルにモジュール構成情報が含まれているモジュール。 -
自動モジュール: モジュール・パスに存在するが、
module-info.classファイル(基本的に標準JARファイル)にモジュール構成情報が含まれていないモジュール。 -
名前のないモジュール: クラス・パスに存在するモジュール。
module-info.classファイルがある場合もない場合もあります。このファイルは無視されます。
名前付きモジュールは他よりも優れたパフォーマンス、強力なカプセル化、および簡単な構成を提供するため、アプリケーションを名前付きモジュールにパッケージ化することをお薦めします。それらは、高い柔軟性も提供します。モジュラJDKのクラス・パスにあるそれらを指定することで、非モジュラJDKで、または名前のないモジュールとしてでも、それらを使用できます。
モジュールの詳細は、モジュール・システムの状態とJEP 261: モジュール・システムを参照してください。
その他のJCAコード・サンプル
これらの例では、いくつかのJCAメカニズムの使用を説明します。Diffie-Hellmanキー交換、AES/GCMおよびHMAC-SHA256のサンプル・プログラムも参照してください。
MessageDigestオブジェクトの計算
次のステップでは、MessageDigestオブジェクトの計算手順を説明します。
例2-11 複製によるハッシュ実装
ハッシュ実装によっては、複製(コピー)を介して中間ハッシュをサポートするものもあります。次について、別々のハッシュを計算すると仮定します。
i1i1およびi2i1、i2およびi3
次に、これらのハッシュを計算する1つの方法を示します。ただし、このコードは、SHA-256実装が複製可能である場合のみ機能します。
/* 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();例2-12 ハッシュ実装が複製可能か複製不可能かを判断する
メッセージ・ダイジェストの実装の中には、複製可能なものもあれば、不可能なものもあります。複製(コピー)が可能かどうかを判断するには、次のように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 in the section
// "Compute Intermediate Digests if the Hash Implementation is not Cloneable"
}例2-13 ハッシュ実装が複製可能ではない場合に中間ダイジェストを計算する
メッセージ・ダイジェストが複製不可能な場合は、別の多少複雑な方法を使います。つまり、複数のダイジェストを生成して中間ダイジェストを計算します。この場合、計算する中間ダイジェストの数をあらかじめ知っておく必要があります。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固有のパラメータ・セットp、qおよびgがあり、これを使ってキーのペアを生成するとします。次のコードを実行してキー・ペア・ジェネレータを初期化できます。DSAParameterSpecはAlgorithmParameterSpecです。
DSAParameterSpec dsaSpec = new DSAParameterSpec(p, q, g);
keyGen.initialize(dsaSpec);キーのペアの生成
最終ステップは、キーのペアの実際の生成です。使用した初期化の型(アルゴリズム独立型またはアルゴリズム固有型)に関係なく、同じコードを使用してKeyPairを生成します。
KeyPair pair = keyGen.generateKeyPair();生成されたキーを使った署名の生成および検証
生成されたキーの使用による署名の生成と検証の例。
KeyPairを利用しています。
署名の生成
最初にSignature Classオブジェクトを作成します。
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つのものを送る必要があります。
- データ
- 署名
- データの署名に使用した秘密キーに対応する公開キー
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);
ノート:
前の例では、initVerifyがPublicKeyを必要とするため、Aliceはエンコードされたキー・ビットからPublicKeyを生成する必要がありました。PublicKeyを取得したら、KeyFactorygetKeySpecメソッドを使って、PublicKeyをDSAPublicKeySpecに変換して、必要な場合にコンポーネントにアクセスすることが可能です。
DSAPublicKeySpec dsaPubKeySpec =
(DSAPublicKeySpec)keyFactory.getKeySpec(pubKey, DSAPublicKeySpec.class);これで、DSA公開キー・コンポーネントy、p、q、およびgに、DSAPublicKeySpecクラスの対応するgetメソッド(getY、getP、getQ、およびgetG)を使用してアクセスできます。
乱数の生成
次のコード例では、SecureRandomクラスのDRBG実装の使用による、様々なセキュリティ強度で構成された乱数の生成を説明します。
SecureRandom drbg;
byte[] buffer = new byte[32];
// Any DRBG can be provided
drbg = SecureRandom.getInstance("DRBG");
drbg.nextBytes(buffer);
SecureRandomParameters params = drbg.getParameters();
if (params instanceof DrbgParameters.Instantiation) {
DrbgParameters.Instantiation ins = (DrbgParameters.Instantiation) params;
if (ins.getCapability().supportsReseeding()) {
drbg.reseed();
}
}
// The following call requests a weak DRBG instance. It is only
// guaranteed to support 112 bits of security strength.
drbg = SecureRandom.getInstance("DRBG",
DrbgParameters.instantiation(112, NONE, null));
// Both the next two calls will likely fail, because drbg could be
// instantiated with a smaller strength with no prediction resistance
// support.
drbg.nextBytes(buffer,
DrbgParameters.nextBytes(256, false, "more".getBytes()));
drbg.nextBytes(buffer,
DrbgParameters.nextBytes(112, true, "more".getBytes()));
// The following call requests a strong DRBG instance, with a
// personalization string. If it successfully returns an instance,
// that instance is guaranteed to support 256 bits of security strength
// with prediction resistance available.
drbg = SecureRandom.getInstance("DRBG", DrbgParameters.instantiation(
256, PR_AND_RESEED, "hello".getBytes()));
// Prediction resistance is not requested in this single call,
// but an additional input is used.
drbg.nextBytes(buffer,
DrbgParameters.nextBytes(-1, false, "more".getBytes()));
// Same for this call.
drbg.reseed(DrbgParameters.reseed(false, "extra".getBytes()));2つのキーの同一性の判定
2つのキーが等しいかどうかを判断するサンプル・コードです。
しばしば、2つのキーの同一性の判定が必要になることがありますが、デフォルトのjava.lang.Object.equalsメソッドでは、必要な結果が得られない場合があります。プロバイダに対する依存度がもっとも少ない方法は、エンコードされたキーを比較する方法です。この方法で適切に比較できない場合(たとえば、RSAPrivateKeyとRSAPrivateCrtKeyを比較する場合)、各コンポーネントを比較する必要があります。
その方法を示すコードを次に示します。
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 (これらのメソッドをサポート)に変換します。
try (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形式の認証応答を解析し、そこから証明書をすべて抽出します。
try (FileInputStream fis = new FileInputStream(filename)) {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Collection<? extends Certificate> c = cf.generateCertificates(fis);
for (Certificate cert : c) {
System.out.println(cert);
}
// Alternatively, use this aggregate operation instead of a for-loop:
// c.stream().forEach(e -> System.out.println(e));
}暗号化の使用
このセクションでは、キーの生成、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/CBC/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);
// Retrieve the parameters used during encryption to properly
// initialize the cipher for decryption
AlgorithmParameters params = aesCipher.getParameters();
// Initialize the same cipher for decryption
aesCipher.init(Cipher.DECRYPT_MODE, aesKey, params);
// Decrypt the ciphertext
byte[] cleartext1 = aesCipher.doFinal(ciphertext);cleartextとcleartext1は、同一です。
パスワードベース暗号化の使用
この例では、ユーザーにパスワードを要求し、暗号化キーをそのパスワードから導き出します。
パスワードを収集し、java.lang.String型のオブジェクトに格納するのは、適切と考えられます。ただし、注意が必要な点があります。それは、String型のオブジェクトは不変であるということです。このため、使用後にStringの内容を変更(上書き)またはゼロにするようなメソッドは存在しません。この機能のために、Stringオブジェクトは、ユーザー・パスワードなどセキュリティ上重要な情報の格納には適しません。セキュリティ関連の情報は、常にchar型の配列に収集および格納するようにしてください。この理由で、javax.crypto.spec.PBEKeySpecクラスは、パスワードをchar型の配列として受け取り、返します。
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);
キーのカプセル化およびカプセル化解除
キーのカプセル化およびカプセル化解除の詳細は、KEMクラスを参照してください。
// Receiver side
var kpg = KeyPairGenerator.getInstance("X25519");
var kp = kpg.generateKeyPair();
// Sender side
var kem1 = KEM.getInstance("DHKEM");
var sender = kem1.newEncapsulator(kp.getPublic());
var encapsulated = sender.encapsulate();
var k1 = encapsulated.key();
// Receiver side
var kem2 = KEM.getInstance("DHKEM");
var receiver = kem2.newDecapsulator(kp.getPrivate());
var k2 = receiver.decapsulate(encapsulated.encapsulation());
assert Arrays.equals(k1.getEncoded(), k2.getEncoded());Diffie-Hellmanキー交換、AES/GCMおよびHMAC-SHA256のサンプル・プログラム
次に、Diffie-Hellmanキー交換、AES/GCMおよびHMAC-SHA256のサンプル・プログラムを示します。
2つのパーティ間のDiffie-Hellmanキー交換
このプログラムでは、2つのパーティ間でDiffie-Hellmanキー協定プロトコルが実行されます。
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キー交換
このプログラムでは、3つのパーティ間でDiffie-Hellmanキー協定プロトコルが実行されます。
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, cb
aliceKeyAgree.doPhase(cb, true);
// Bob uses Alice's result, ac
bobKeyAgree.doPhase(ac, true);
// Carol uses Bob's result, ba
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();
}
}
AES/GCMの例
次に、AES/GCMを使用してデータを暗号化/複合化するサンプル・プログラムを示します。
import java.security.AlgorithmParameters;
import java.util.Arrays;
import javax.crypto.*;
public class AESGCMTest {
public static void main(String[] args) throws Exception {
// Slightly longer than 1 AES block (128 bits) to show PADDING
// is "handled" by GCM.
byte[] data = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10};
// Create a 128-bit AES key.
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(128);
SecretKey key = kg.generateKey();
// Obtain a AES/GCM cipher to do the enciphering. Must obtain
// and use the Parameters for successful decryption.
Cipher encCipher = Cipher.getInstance("AES/GCM/NOPADDING");
encCipher.init(Cipher.ENCRYPT_MODE, key);
byte[] enc = encCipher.doFinal(data);
AlgorithmParameters ap = encCipher.getParameters();
// Obtain a similar cipher, and use the parameters.
Cipher decCipher = Cipher.getInstance("AES/GCM/NOPADDING");
decCipher.init(Cipher.DECRYPT_MODE, key, ap);
byte[] dec = decCipher.doFinal(enc);
if (Arrays.compare(data, dec) != 0) {
throw new Exception("Original data != decrypted data");
}
}
}HMAC-SHA256の例
次のサンプル・プログラムでは、HMAC-SHA256の秘密キーオブジェクトを生成し、それを使用してHMAC-SHA256オブジェクトを初期化する方法を示します。
例2-14 HMAC-SHA256の秘密キーオブジェクトを生成する
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 secret key, sk
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(sk);
byte[] result = mac.doFinal("Hi There".getBytes());
}
}
















