Java暗号化アーキテクチャ(JCA)はプラットフォームの主要な部分であり、プロバイダ・アーキテクチャ、およびデジタル署名、メッセージ・ダイジェスト(ハッシュ)、証明書と証明書検証、暗号化(対称/非対称ブロック/ストリーム暗号)、鍵の生成と管理、安全な乱数生成などのための一連のAPIを含んでいます。
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は標準化された、アルゴリズム固有の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もMD5実装を提供しますが、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 エンジン・クラスのインスタンスの取得のサンプル・コード
import javax.crypto.*;
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)など、様々なタイプのアルゴリズムがあります。MessageDigestと同様にこれらもハッシュ値を生成しますが、メッセージの整合性を保護するために最初に鍵によって初期化されます。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、KeyFactory、SecretKeyFactory、KeyPairGenerator、KeyGenerator、KeyAgreement、AlgorithmParameter、AlgorithmParameterGenerator、KeyStore、CertificateFactoryおよびエンジンKey Interface、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)、次にさまざまなサポート・クラスについて説明します。現時点では、鍵(公開、非公開、および秘密)はさまざまなJCAクラスによって生成および表現され、高レベルのクラスによってその操作の一部として使用されるという説明だけで十分です。
このセクションでは、各クラスおよびインタフェースのメイン・メソッドの署名を示します。対応するコード例の項に、これらのクラス(MessageDigest、Signature、KeyPairGenerator、SecureRandom、KeyFactoryおよび鍵仕様クラス)の例があります。
関連するSecurity APIパッケージの完全なリファレンス・ドキュメントは、次のパッケージのサマリーにあります。
「暗号化サービス・プロバイダ」(このドキュメントでは「プロバイダ」とも呼ばれる)とは、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
は、希望するエンジン・タイプ(MessageDigest/Cipher/など)です。次に例を示します。
MessageDigest md = MessageDigest.getInstance("SHA-256");
KeyAgreement ka = KeyAgreement.getInstance("DH", "SunJCE");
これらはそれぞれ、SHA-256 MessageDigestオブジェクトおよびDH KeyAgreementオブジェクトのインスタンスを返します。
Javaセキュリティ標準アルゴリズム名には、Java環境で使用するために標準化されている名前のリストがあります。一部のプロバイダには、同じアルゴリズムを参照する別名がある場合もあります。たとえば、SHA256アルゴリズムは、SHA-256と呼ばれる場合があります。必ずしもすべてのプロバイダが同じようにアルゴリズム名の別名を持つわけではないため、アプリケーションは別名ではなく標準名を使用する必要があります。
注意:
アルゴリズム名の大文字と小文字は区別されません。たとえば、次の呼出しはすべて等価とみなされます。
MessageDigest.getInstance("SHA256")
MessageDigest.getInstance("sha256")
MessageDigest.getInstance("sHa256")
プロバイダを指定しない場合、getInstanceは、指定されたアルゴリズムに関連する、要求された暗号化サービスの実装に対応する登録プロバイダを検索します。どのJava仮想マシン(JVM)でも、プロバイダは指定の「優先順位」でインストールされます。優先順位とは、特定のプロバイダの指定がない場合に、プロバイダ・リストが検索される順位です。(プロバイダのインストールを参照。)たとえば、JVMに、PROVIDER_1とPROVIDER_2という2つのプロバイダがインストールされているとします。次のようになっていると仮定します。
PROVIDER_1はSHA-256およびDESedeを実装。PROVIDER_1の優先順位が1 (最高の優先順位)。PROVIDER_2はSHA256withDSA、SHA-256、RC5およびRSAを実装。PROVIDER_2の優先順位は2。ここで、3つのシナリオについて見てみます。
PROVIDER_1が最高の優先順位を持ち最初に検索されるので、PROVIDER_1実装が返される。PROVIDER_1が検索される。実装が見つからないので次にPROVIDER_2が検索される。実装が見つかるので、このプロバイダが返される。NoSuchAlgorithmExceptionがスローされる。プロバイダ引数を持つgetInstanceメソッドは、必要なアルゴリズムを提供するプロバイダを指定したい開発者のためのものです。たとえば、連邦証明書を受け取ったプロバイダ実装を連邦局が使いたいとします。PROVIDER_1のSHA256withDSA実装はこの証明書を受け取っていないのに対し、PROVIDER_2のDSA実装がこれを受け取っているとします。
連邦局のプログラムは次の呼出しを実行します。PROVIDER_2が認証された実装を持つため、これを指定します。
Signature dsa = Signature.getInstance("SHA256withDSA", "PROVIDER_2");
この場合、PROVIDER_2がインストールされていないと、インストールされている別のプロバイダが要求アルゴリズムを実装している場合であっても、NoSuchProviderExceptionがスローされます。
プログラムは、(SecurityクラスのクラスのgetProvidersメソッドを使用して)インストールされているすべてのプロバイダのリストを獲得し、リストからいずれかを選択することもできます。
注意:
汎用アプリケーションは、特定のプロバイダから暗号化サービスを要求しないようにする必要があります。そうでない場合、アプリケーションは特定のプロバイダに結び付けられることになり、そのプロバイダはほかのJava実装で使用できない可能性があります。特定の要求されたプロバイダよりも優先順位が高い、使用可能な最適化されたプロバイダ(たとえば、PKCS11によるハードウェア・アクセラレータまたはMicrosoftのMSCAPIなどのネイティブOS実装)を利用できない場合もあります。暗号化プロバイダを使用するには、最初にインストールし、次に静的または動的に登録する必要があります。さまざまなSunプロバイダがこのリリースに同梱されており(SUN、SunJCE、SunJSSE、SunRsaSignなど)、すでにインストールおよび登録されています。以降のセクションでは、追加プロバイダのインストールおよび登録方法について説明します。
すべてのJDKプロバイダは、すでにインストールされ登録されています。ただし、サードパーティ製プロバイダが必要な場合は、クラス・パスまたはモジュール・パスへのプロバイダの追加、プロバイダの登録(静的または動的)、必要なアクセス権の追加の方法の詳細を、プロバイダの実装および統合までのステップのステップ8: テストの準備で参照してください。
Securityクラスは、インストールされているプロバイダおよびセキュリティに関するプロパティを管理します。このクラスに含まれるのは、インスタンスが生成されないstaticメソッドだけです。プロバイダの追加または削除、およびSecurityプロパティの設定用メソッドは、信頼できるプログラムによってしか実行できません。現時点での「信頼できるプログラム」は、次のどれかです。
あるコードが、実行しようとしているアクション(プロバイダの追加など)を行うことができるような、信頼できるコードであると判断するには、その特定のアクションを行う適切な権限がそのアプレットに与えられている必要があります。インストールしたJDKのポリシー構成ファイルでは、指定されたコード・ソースからのコードに対し、どのようなアクセス権(システム・リソースへのアクセスの種類)を与えるかを指定します。(以降の説明、およびデフォルトのPolicyの実装とポリシー・ファイルの構文とJavaセキュリティ・アーキテクチャ仕様を参照。)
実行されるコードは、常に、特定の「コード・ソース」から来ると考えられます。コード・ソースには、そのコードの元の場所(URL)だけでなく、コードの署名に使用された非公開鍵に対応する任意の公開鍵への参照も含まれています。コード・ソース内の公開鍵は、ユーザーのものから別名(記号名)で参照します。
ポリシー構成ファイル内では、コード・ソースは、コード・ベース(URL)、および(signedByで始まる)別名の2つのコンポーネントで表されます。別名は、コードの署名の検証に使う必要のある公開鍵を含んだキーストアのエントリを識別します。
このようなファイル内の各「許可」文は、指定されたコード・ソースにアクセス権のセットを与え、許可されるアクションを指定します。
次に、ポリシー構成ファイルの例を示します。
grant codeBase "file:/home/sysadmin/", signedBy "sysadmin" {
permission java.security.SecurityPermission "insertProvider";
permission java.security.SecurityPermission "removeProvider";
permission java.security.SecurityPermission "putProviderProperty.*";
};
この構成ファイルは、ローカル・ファイル・システム上の/home/sysadmin/ディレクトリ内の署名されたJARファイルからロードされたコードが、プロバイダの追加または削除、あるいはプロバイダのプロパティを設定できることを指定します。(JARファイルの署名は、ユーザーのキーストア内の別名sysadminで参照する公開鍵を使用して検証できます)。
コード・ソースを指定するコンポーネントは、どちらも(または両方を)省略可能です。次に、codeBaseを省略した構成ファイルの例を示します。
grant signedBy "sysadmin" {
permission java.security.SecurityPermission "insertProvider.*";
permission java.security.SecurityPermission "removeProvider.*";
};
このポリシーが有効になっている場合は、ローカル・ファイル・システム上の/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クラスは、初期シード値から疑似乱数列を生成する決定性アルゴリズムである、疑似乱数ジェネレータ(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のインスタンスは、いくつかの方法で取得できます。
すべてのJava SE実装は、引数なしのコンストラクタnew SecureRandom()を使用してデフォルトのSecureRandomを提供します。このコンストラクタは、登録されているセキュリティ・プロバイダのリスト内を、最も推奨されるプロバイダから順に確認し、その後、SecureRandom乱数ジェネレータ(RNG)アルゴリズムをサポートする最初のプロバイダから新しいSecureRandomオブジェクトを返します。どのプロバイダもRNGアルゴリズムをサポートしていない場合は、SUNプロバイダからSHA1PRNGを使用するSecureRandomオブジェクトを返します。
SecureRandomの特定の実装を獲得するには、プロバイダ実装の要求および獲得方法のいずれかを使用します。
java.security.Securityクラスのsecurerandom.strongAlgorithmsプロパティで定義される強力なSecureRandom実装を取得するには、getInstanceStrong()メソッドを使用します。このプロパティは、重要な値を生成するのに適したプラットフォーム実装をリストします。
SecureRandomオブジェクトは、getInstance()の呼出し後に次のsetSeedメソッドのいずれかを呼び出さないかぎり、ランダム・シードで初期化されます。
void setSeed(byte[] seed)
void setSeed(long seed)
最初のnextBytes呼出しの前にsetSeedを呼び出して、環境のランダム性を防ぐ必要があります。
SecureRandomオブジェクトによってもたらされるビットのランダム性は、シード・ビットのランダム性によって異なります。
SecureRandomオブジェクトは、setSeedメソッドまたはreseedメソッドのいずれかを使用していつでも再シードされる可能性があります。setSeedで指定されたシードは、既存のシードと置き換えられるのではなく、既存のシードに追加されます。したがって、呼出しを繰り返しても、ランダム性が減少しないことが保証されます。
MessageDigestクラスは、暗号として安全なメッセージ・ダイジェスト(SHA-256やSHA-512など)の機能を提供するように設計されているエンジン・クラス(エンジン・クラスとアルゴリズムを参照)です。安全な暗号化メッセージ・ダイジェストは、任意サイズの入力(バイト配列)を取り、固定サイズ出力を生成します。これをダイジェストまたはハッシュと言います。
たとえば、SHA-256のアルゴリズムは32バイトのダイジェストを生成し、SHA-512のアルゴリズムは64バイトのダイジェストを生成します。
ダイジェストには次の2つの特徴があります。
メッセージ・ダイジェストを使い、一意で、信頼できるデータ識別子を生成します。データ識別子をデータの「チェックサム」または「デジタル指紋」と呼ぶこともあります。メッセージのうちの1ビットを変更するだけで、異なるダイジェスト値が生成されます。
メッセージ・ダイジェストには多くの用途があり、意図的かどうかにかかわらず、いつデータが変更されたかを判断できます。近年、一般的なアルゴリズムに弱点があるかどうかを判断するために大きな努力が払われていますが、結果はさまざまです。ダイジェスト・アルゴリズムを選択する場合は、最近のリファレンスを調べて、そのステータスと当面のタスクでの妥当性を判断する必要があります。
様々なタイプのdigest()メソッドを使用してダイジェストを計算する手順を示します。
updateメソッドを呼び出して提供する必要があります。メッセージ・ダイジェスト・オブジェクトのアップデートを参照してください。Signatureクラスは、SHA256withDSAやSHA512withRSAなどの暗号化デジタル署名アルゴリズムの機能を提供するように設計されているエンジン・クラス(エンジン・クラスとアルゴリズムを参照)です。安全な暗号化署名アルゴリズムは、任意サイズの入力と非公開鍵を取り、署名と呼ばれる比較的短い(固定サイズの場合もよくある)バイト文字列を生成します。このプロパティは次のとおりです。
Signatureオブジェクトは、非公開鍵での署名に対して初期化され、署名するデータを与えられます。結果の署名バイトは、通常は署名されたデータとともに保持されます。検証が必要な場合、別のSignatureオブジェクトが作成され、検証に対して初期化されて対応する公開鍵を与えられます。データおよび署名バイトはSignature (署名)オブジェクトに渡され、データと署名が一致した場合に、Signatureオブジェクトは成功を報告します。
署名はメッセージ・ダイジェストに似ていますが、提供する保護の種類の点で目的が大きく異なっています。実際、SHA256WithRSAなどのアルゴリズムは、メッセージ・ダイジェストSHA256を使用して最初に大きなデータ・セットを管理しやすい形式に圧縮し、次にRSAアルゴリズムで結果の32バイトのメッセージ・ダイジェストに署名します。
データの署名と検証の例は、生成された鍵を使った署名の生成および検証を参照してください。
Signatureオブジェクトはモデル・オブジェクトです。つまり、Signatureオブジェクトは、常に指定の状態にあり、この状態で1つの型の操作だけを実行できます。
状態は、個々のクラスで定義したfinal (ファイナル)整数の定数で表されます。
Signatureオブジェクトに可能な状態は、次の3つです。
UNINITIALIZEDSIGNVERIFY Signatureオブジェクトがはじめて生成されるときは、UNINITIALIZEDの状態です。Signatureクラスは、状態をSIGNに変更するinitSignと、状態をVERIFYに変更するinitVerifyという2つの初期化メソッドを定義します。
署名を付けたり、検証したりするには、最初にSignatureのインスタンスを生成します。
Signatureオブジェクトは、Signature getInstance() staticファクトリ・メソッドの1つを使用することによって取得されます。プロバイダ実装の要求および獲得方法を参照してください。
Signatureオブジェクトを使うには、最初に必ずこれを初期化します。初期化メソッドは、オブジェクトを署名用に使うか検証用に使うかに応じて異なります。
署名で使う場合、オブジェクトは最初に、署名を生成するエンティティの非公開鍵を使って初期化する必要があります。この初期化は、次のメソッドを呼び出して実行します。
final void initSign(PrivateKey privateKey)
このメソッドでは、SignatureオブジェクトはSIGN状態になります。Signatureオブジェクトを検証で使う場合は、最初に、署名を検証するエンティティの公開鍵を使って初期化する必要があります。この初期化は、次のいずれかのメソッドを呼び出して実行します。
final void initVerify(PublicKey publicKey)
final void initVerify(Certificate certificate)
このメソッドでは、SignatureオブジェクトはVERIFY状態になります。
Signatureオブジェクトを署名用に初期化した場合(このオブジェクトがSIGN状態の場合)、署名を付けるデータをオブジェクトに入れることができます。これは、次のupdateメソッドのどれか1つを1回または複数回呼び出して実行します。
final void update(byte b) final void update(byte[] data) final void update(byte[] data, int off, int len)
署名を付けるデータがすべてSignatureオブジェクトに入るまで、updateメソッドを呼び出します。
署名を生成するには、signメソッドの1つを呼び出すだけです。
final byte[] sign() final int sign(byte[] outbuf, int offset, int len)
最初のメソッドは、署名の結果をバイト配列で返します。2番目のメソッドは、オフセット位置offsetから開始する提供バッファoutbuf内に署名の結果を格納します。lenは、署名に割り当てられたoutbuf内のバイト数です。このメソッドは、実際に格納されたバイト数を返します。
署名のエンコードは、アルゴリズムに依存します。Java暗号化アーキテクチャでのASN.1エンコードの使用の詳細は、Javaセキュリティ標準アルゴリズム名仕様を参照してください。
signメソッドを呼び出すと、signature (署名)オブジェクトは、initSignを呼び出して最初に署名用に初期化されたときの状態にリセットされます。つまり、オブジェクトをリセットするので、必要であればupdateとsignを新たに呼び出して、同じ非公開鍵を使って別の署名を生成できます。
あるいは異なる非公開鍵を指定して、initSignへの新規呼出しを作成したり、(署名を検証するためにSignatureオブジェクトを初期化する目的で) initVerifyへの新規呼出しを作成したりできます。
Signatureオブジェクトを検証用に初期化した場合(VERIFY状態にある場合)は、指定の署名が、それに関連したデータの実際の認証署名であるかどうかを検証できます。この処理を開始するには、検証データ(署名自体ではない)をオブジェクトに入れます。データは、次のいずれかのupdateメソッドを呼び出すことによってオブジェクトに渡されます。
final void update(byte b) final void update(byte[] data) final void update(byte[] data, int off, int len)
検証するデータがすべてSignatureオブジェクトに入るまで、updateメソッドを呼び出します。これで、次のいずれかのverifyメソッドを呼び出して署名を検証できます。
final boolean verify(byte[] signature) final boolean verify(byte[] signature, int offset, int length)
引数は、署名を含むバイト配列である必要があります。このバイト配列は、前にいずれかのsignメソッドの呼出しによって返された署名バイトを保持します。
verifyメソッドは、エンコードされた署名がupdateメソッドに入れたデータの認証署名かどうかを示すbooleanを返します。
verifyメソッドを呼び出すと、signature (署名)オブジェクトは、initVerifyを呼び出して検証用に初期化されたときの状態にリセットされます。つまり、オブジェクトをリセットするので、initVerifyへの呼出しで指定した公開鍵を持つ識別の別の署名を検証できます。
あるいは異なる非公開鍵を指定し、(異なるエンティティの署名を検証するためにSignatureオブジェクトを初期化する目的で)initVerifyへの新規呼出しを作成したり、(署名を生成するためにSignatureオブジェクトを初期化する目的で) initSignへの新規呼出しを作成したりできます。
Cipherクラスは、暗号化および復号化で使用される暗号機能を提供します。暗号化とは、データ(クリアテキストと呼ばれる)および鍵を受け取って、鍵を知らないサードパーティにとって無意味なデータ(暗号テキスト)を生成する処理のことです。復号化はその逆で、暗号テキストおよび鍵を受け取り、クリアテキストを生成する処理です。
対称暗号化方式と非対称暗号化方式
暗号化には、対称(秘密鍵とも呼ばれる)と非対称(または公開鍵暗号化方式)という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の組合せを再利用して暗号化を行わないでください。つまり、暗号オブジェクトは、暗号化操作のたびに異なるパラメータ・セットで明示的に初期化しなおす必要があります。
例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など)の名前が必ず含まれます。これにモードおよびパディング・スキームが続く場合もあります。
変換は、次の書式で記述されます。
たとえば、次は有効な変換です。
"AES/CBC/PKCS5Padding"
"AES"
変換名だけを指定すると、要求された変換の実装がその環境で使用可能かどうかをシステムが判別し、複数の実装が存在する場合には優先度の高い実装が存在することを返します。
変換名とパッケージ・プロバイダの両方を指定すると、システムは要求されたパッケージ内に要求された変換の実装が存在するかどうかを判別し、存在しない場合には例外をスローします。
アルゴリズム、モードおよびパディングを完全に指定した変換を使用することをお薦めします。そうしないと、プロバイダはデフォルトを使用します。たとえば、SunJCEプロバイダとSunPKCS11プロバイダは、多くの対称暗号でECBをデフォルト・モードとして、PKCS5Paddingをデフォルト・パディングとして使用します。
このため、SunJCEプロバイダでは、
Cipher c1 = Cipher.getInstance("AES/ECB/PKCS5Padding");
および
Cipher c1 = Cipher.getInstance("AES");
という文は、等価になります。
注意:
ECBモードは最も簡単に使用できるブロック暗号モードであり、JDKおよびJREではデフォルトになっています。ECBは、異なる鍵を使用する場合の単一のデータ・ブロックに対しては正常に機能しますが、複数のデータ・ブロックには絶対に使用しないでください。Cipher Block Chaining (CBC)やGalois/Counter Mode (GCM)などのその他の暗号モードのほうが適しています。CFBやOFBなどのモードを使用すると、ブロック暗号は、暗号の実際のブロック・サイズよりも小さい単位でデータを暗号化できます。このようなモードを要求する場合、AES/CFB8/NoPaddingおよびAES/OFB32/PKCS5Padding変換で示されるように、一度に処理するビット数を、モード名に追加することで指定することもできます。数値を指定しない場合、プロバイダ固有のデフォルトが使用されます。(たとえば、SunJCEプロバイダではAESにデフォルトの128ビットが使用されます。)したがって、CFB8やOFB8などの8ビット・モードを使用することで、ブロック暗号をバイト指向のストリーム暗号に変換できます。
Javaセキュリティ標準アルゴリズム名には、変換のアルゴリズム名、モードおよびパディング・スキーム・コンポーネントの指定に使用可能な標準名のリストが掲載されています。
ファクトリ・メソッドにより返されるオブジェクトは初期化されていないため、使用する前に初期化する必要があります。
Cipherオブジェクトの初期化
getInstanceを介して取得されたCipherオブジェクトは、次の4つのモードのいずれかで初期化する必要があります。これらのモードはCipherクラスのfinal (ファイナル)整数の定数として定義されます。モードは、シンボリック名で参照できます。次に、各モードのシンボリック名および目的の説明を示します。
java.security.Keyをラッピングする。java.security.Keyオブジェクトにアンラッピングする。Cipher初期化メソッドは、それぞれ操作モード・パラメータ(opmode)を取り、そのモード用のCipherオブジェクトを初期化します。その他のパラメータには、鍵(key)または鍵を含む証明書(certificate)、アルゴリズム・パラメータ(params)、および乱数の発生源(random)などがあります。
Cipherオブジェクトを初期化する場合、次のいずれかのinitメソッドを呼び出します。
public void init(int opmode, Key key);
public void init(int opmode, Certificate certificate);
public void init(int opmode, Key key, SecureRandom random);
public void init(int opmode, Certificate certificate,
SecureRandom random);
public void init(int opmode, Key key,
AlgorithmParameterSpec params);
public void init(int opmode, Key key,
AlgorithmParameterSpec params, SecureRandom random);
public void init(int opmode, Key key,
AlgorithmParameters params);
public void init(int opmode, Key key,
AlgorithmParameters params, SecureRandom random);
初期化ベクトルなどのパラメータを必要とするCipherオブジェクトを暗号化用に初期化する場合、initメソッドにパラメータを何も指定しないと、ランダム・パラメータを生成するか、プロバイダ固有のパラメータ・セット(デフォルト)を使用することにより、基盤となる暗号実装が必須パラメータを提供すると見なされます。
ただし、パラメータを必要とするCipherオブジェクトを復号化用に初期化する場合、initメソッドにパラメータを何も指定しないと、使用したinitメソッドに応じてInvalidKeyExceptionまたはInvalidAlgorithmParameterException例外が発生します。
アルゴリズム・パラメータの管理を参照してください。
復号化には、暗号化に使用したのと同じパラメータを使用する必要があります。
Cipherオブジェクトを初期化すると、それまでに獲得した状態がすべて失われることに留意してください。つまり、Cipherを初期化することは、そのCipherの新規インスタンスを作成して初期化することと等価です。たとえば、指定された鍵で復号化を行うためにCipherを初期化してから、暗号化を行うために初期化すると、復号化モードで獲得した状態はすべて失われます。
データの暗号化および復号化
データの暗号化または復号化は、1つのステップで実行(単一部分操作)することも、複数のステップで実行(複数部分操作)することもできます。データの長さが不明な場合、またはデータが長すぎて一度にメモリーに格納できない場合に、複数部分操作は有用です。
1つのステップでデータの暗号化または復号化を行う場合、次のいずれかのdoFinalメソッドを呼び出します。
public byte[] doFinal(byte[] input);
public byte[] doFinal(byte[] input, int inputOffset, int inputLen);
public int doFinal(byte[] input, int inputOffset,
int inputLen, byte[] output);
public int doFinal(byte[] input, int inputOffset,
int inputLen, byte[] output, int outputOffset)
複数のステップでデータの暗号化または復号化を行うには、次のいずれかのupdateメソッドを呼び出します。
public byte[] update(byte[] input);
public byte[] update(byte[] input, int inputOffset, int inputLen);
public int update(byte[] input, int inputOffset, int inputLen,
byte[] output);
public int update(byte[] input, int inputOffset, int inputLen,
byte[] output, int outputOffset)
複数部分操作は、前述のdoFinalメソッドのいずれか(最後のステップで入力データが残されている場合)、または次のdoFinalメソッドのいずれか(最後のステップで入力データが残っていない場合)を使用して終了させます。
public byte[] doFinal();
public int doFinal(byte[] output, int outputOffset);
指定された変換の一部としてパディング(またはアンパディング)が要求された場合、すべてのdoFinalメソッドで、必要なパディング(またはアンパディング)操作がすべて処理されます。
doFinalを呼び出すと、Cipherオブジェクトはinitを呼び出して初期化した時の状態にリセットされます。つまり、Cipherオブジェクトはリセットされて、さらにデータを暗号化または復号化するために使用できます(initの呼出しで指定された操作モードにより異なる)。
鍵のラッピングとアンラッピング
鍵をラッピングすると、ある場所から別の場所へ安全に転送できます。
wrap/unwrap APIは鍵オブジェクトに対して直接機能するため、このAPIを使用するとコードの記述が容易になります。次のメソッドを使用すると、ハードウェア・ベースの鍵の安全な転送も可能になります。
Keyをwrapする場合、まずWRAP_MODEのCipherオブジェクトを初期化し、次のメソッドを呼び出します。
public final byte[] wrap(Key key);
ラップされた鍵のバイト(wrapを呼び出した結果)を、そのラップを解除する他のユーザーに提供する場合、受信者がunwrapを実行するのに必要な、次の追加情報も送信する必要があります。
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プロバイダにより実装される次の暗号アルゴリズムは、パラメータを使用します。
javax.crypto.spec.IvParameterSpecクラスは、指定されたIVでのCipherオブジェクトの初期化に使用できます。また、CTRモードとGCMモードでは、IVが必要になります。javax.crypto.spec.PBEParameterSpecクラスを使用して、saltおよび繰返し処理回数を指定してPBEアルゴリズムを実装するCipherオブジェクトを初期化できます(例: PBEWithHmacSHA256AndAES_256)。SealedObjectクラスのクラスを使用する場合、復号化操作で使用するアルゴリズム・パラメータの格納または転送について心配する必要はありません。このクラスは、シール(暗号化)に使用されるパラメータを暗号化されたオブジェクト・コンテンツに添付します。また、アンシール(復号化)でも同じパラメータを使用します。
例2-3 Cipherオブジェクトからパラメータを取得するサンプル・コード
アプリケーションは、暗号化用の生成されたパラメータを次の方法でCipherオブジェクトから取得できます。
import javax.crypto.*;
import java.security.AlgorithmParameters;
// get cipher object for password-based encryption
Cipher c = Cipher.getInstance("PBEWithHmacSHA256AndAES_256");
// initialize cipher for encryption, without supplying
// any parameters. Here, "myKey" is assumed to refer
// to an already-generated key.
c.init(Cipher.ENCRYPT_MODE, myKey);
// encrypt some data and store away ciphertext
// for later decryption
byte[] cipherText = c.doFinal("This is just an example".getBytes());
// retrieve parameters generated by underlying cipher
// implementation
AlgorithmParameters algParams = c.getParameters();
// get parameter encoding and store it away
byte[] encodedAlgParams = algParams.getEncoded();
例2-4 複合化用にCipherオブジェクトを初期化するサンプル・コード
復号化には、暗号化に使用したのと同じパラメータを使用する必要があります。これらは、それらのエンコーディングからインスタンス化でき、次に示すように、対応するCipherオブジェクトを復号化用に初期化する際に使用できます。
import javax.crypto.*;
import java.security.AlgorithmParameters;
// get parameter object for password-based encryption
AlgorithmParameters algParams;
algParams = AlgorithmParameters.getInstance("PBEWithHmacSHA256AndAES_256");
// initialize with parameter encoding from above
algParams.init(encodedAlgParams);
// get cipher object for password-based encryption
Cipher c = Cipher.getInstance("PBEWithHmacSHA256AndAES_256");
// initialize cipher for decryption, using one of the
// init() methods that takes an AlgorithmParameters
// object, and pass it the algParams object from above
c.init(Cipher.DECRYPT_MODE, myKey, algParams);
暗号出力時の考慮事項
CipherのupdateおよびdoFinalメソッドの中には、呼出し側による出力バッファの指定が可能なものがあります。暗号化または復号化されたデータは、このバッファ内に出力されます。この場合、暗号化または復号化操作の結果を保持できるだけの大きさのバッファを渡すことは重要です。
Cipher内の次のメソッドを使用して、設定すべき出力バッファのサイズを確認できます。
public int getOutputSize(int inputLen)
Cipherを内部的に使用して共通の暗号使用への簡単なアクセスを提供する、いくつかのヘルパー・クラスがあります。
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を使用するサンプル・コード
次のコードは、暗号およびFileInputStreamを含むCipherInputStreamを使用して、入力ストリーム・データを暗号化する方法を示します。
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();
}
プログラマは、このクラスを使用してオブジェクトを生成し、暗号化アルゴリズムを利用してその機密性を保護することができます。
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
};
この方法には、暗号解読鍵に関する知識がなくても、シールされたオブジェクトのアンシールを実行できるという利点があります。たとえば、あるパーティが暗号オブジェクトを必須の暗号解読鍵を使って初期化したあとで、別のパーティに渡すと、そのパーティはシールされたオブジェクトをアンシールできます。
try {
String s = (String)so.getObject(sKey);
} catch (Exception e) {
// do something
};
この方法では、getObjectメソッドは、適切な暗号解読アルゴリズム用の暗号オブジェクトを作成し、シール済みのオブジェクトに格納された特定の暗号解読鍵およびアルゴリズム・パラメータ(存在する場合)を使用してそれを初期化します。この方法の利点は、オブジェクトをアンシールするパーティが、オブジェクトのシールに使用したパラメータ(IVなど)を追跡する必要がないことです。
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);
java.security.Keyインタフェースは、すべての不透明な鍵に関するトップ・レベルのインタフェースです。すべての不透明な鍵オブジェクトが共有する機能を定義します。
ここまでは、鍵とは何か、鍵はどのように生成および表現されるかについて詳細は説明せずに、JCAの高レベルの使用に重点を置いてきました。ここでは鍵に注意を向けます。
「不透明な」鍵の表現では、鍵を構成する鍵データに直接アクセスできません。つまり、「不透明」さにより、鍵へのアクセスが、Keyインタフェースによって定義されるgetAlgorithm、getFormat、およびgetEncoded。
これは、対応するKeySpecインタフェース(KeySpecインタフェースを参照)で定義されているgetメソッドのいずれかによって各鍵データ値に個別にアクセスできる、透明な表現とは対照的です。
不透明な鍵はすべて、次の3つの特性を持ちます。
AES、DSAまたはRSAなど)のことであり、そのようなアルゴリズムや関連するアルゴリズム(SHA256withRSAなど)と協調動作します。鍵のアルゴリズムの名前は、次のメソッドを使用して取得されます。 String getAlgorithm()
byte[] getEncoded()
String getFormat()
鍵は一般的に、KeyGeneratorクラスやKeyPairGeneratorクラスなどの鍵ジェネレータ、証明書、KeyFactoryを使用する鍵仕様(KeySpecインタフェースを参照)、または鍵管理で使用するキーストア・データベースにアクセスするKeystoreの実装から獲得します。KeyFactoryを使って、アルゴリズム依存型の方法でエンコードされた鍵を解析することが可能です。
また、CertificateFactoryを使って、認証を解析することも可能です。
次に、java.security.interfacesおよびjavax.crypto.interfacesパッケージ内のKeyインタフェースを拡張するインタフェースのリストを示します。
PublicKeyインタフェースとPrivateKeyインタフェース
PublicKeyおよびPrivateKeyインタフェースはどちらもKeyインタフェースを拡張しますが、これらはメソッドを使わないインタフェースで、型の安全性および型の識別に使用します。
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インタフェースのインタフェースを実装する)は、エンコードされた形式の公開鍵または非公開鍵を表します。その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クラスは、不透明な暗号化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クラスは、秘密鍵のファクトリを表します。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クラスは、エンジン・クラス(エンジン・クラスとアルゴリズムを参照)であり、公開鍵と非公開鍵のペアの生成に使用します。
鍵のペアの生成方法には、アルゴリズム独立型とアルゴリズム固有型の2つがあります。この2つの唯一の相違点は、オブジェクトの初期化にあります。
次に記述するメソッドの呼出し例は、鍵のペアの生成を参照してください。
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パラメータに事前計算済の一連の値を使用します。モジュラスのサイズが前述の値の1つでない場合は、SUNプロバイダは、新しいパラメータのセットを作成します。これらの3つのモジュラス・サイズ以外の、事前に計算されたパラメータ・セットを持つプロバイダが存在する可能性もあります。また、事前に計算されたパラメータがなく、常に新しいパラメータ・セットを作成するプロバイダが存在する可能性もあります。
アルゴリズム固有の初期化
アルゴリズム固有のパラメータのセットがすでに存在する状況では(DSAのコミュニティ・パラメータなど)、AlgorithmParameterSpecインタフェース引数を取るinitializeメソッドが2つあります。このうちの一方はSecureRandom引数も取りますが、他方では、乱数の発生源はシステムによって提供されます。
void initialize(AlgorithmParameterSpec params,
SecureRandom random)
void initialize(AlgorithmParameterSpec params)
鍵のペアの生成を参照してください。
鍵のペアの生成
鍵のペアの生成手順は、初期化(およびアルゴリズム)に関係なく、常に同じです。必ずKeyPairGeneratorから次のメソッドを呼び出します。
KeyPair generateKeyPair()
generateKeyPairを呼び出すたびに、異なる鍵のペアが作られます。
鍵ジェネレータは、対称アルゴリズム用の秘密鍵を生成します。
KeyGeneratorの作成
KeyGeneratorオブジェクトは、KeyGeneratorの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();
鍵協定とは、複数のパーティが秘密情報を交換しなくても同じ暗号化鍵を確立可能なプロトコルを指します。
各パーティは、非公開鍵によって鍵協定オブジェクトを初期化し、通信に参加する各パーティの公開鍵を入力します。ほとんどの場合は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を呼び出します。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システム・プロパティによって決まります。このデフォルト値は、オペレーティング・システムによって異なります。
/home/username/C:\Users\username\キーストア・ファイルは必要な場所に配置できます。環境によっては、複数のキーストアが存在することに意味がある場合があります。たとえば、1つのキーストアがユーザーの非公開鍵を保持し、別のキーストアが、信頼関係を確立するために使用される証明書を保持する場合があります。
ユーザーのキーストア以外に、JDKは、さまざまな証明書発行局(CA)からの信頼できる証明書を格納するために使用されるシステム全体のキーストアも保持します。これらのCA証明書は、信頼性の判定を行う場合に使用できます。たとえば、SSL/TLS/DTLSでSunJSSEプロバイダがリモート・ピアから証明書を提示された場合、デフォルトのトラスト・マネージャは、次のいずれかのファイルを調べて、その接続を信頼できるかどうかを判断します。
<java-home>/lib/security/cacerts<java-home>\lib\security\cacertsシステム全体のcacertsキーストアを使用する代わりに、アプリケーションは独自のキーストアを設定して使用するか、前述のユーザー・キーストアを使用できます。
キーストアの実装
KeyStoreクラスは、キーストア内の情報へのアクセスおよび情報の変更を行うための、明確に定義されたインタフェースを提供します。複数の異なる固定実装を生成できます(それぞれ特定の型のキーストアを実装)。
現在、KeyStoreを利用するコマンド行ツールには、keytoolとjarsignerの2つがあります。また、GUIベースのpolicytoolというツールもあります。様々なソースのコードに対して(システム・リソースへの)アクセス権を指定するポリシー・ファイルを処理する場合、このツールは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キーストア・ファイルを解析してjceks形式に変換できます。キーストア内の非公開鍵エントリのパスワードを変更し、キーストアの型として"-storetype jceks"を指定することによって、「jks」型のキーストアを「jceks」型のキーストアにアップグレードできます。提供される(より)強力な暗号化による鍵保護を、デフォルト・キーストア内の「signkey」という名前の非公開鍵に適用する場合は、次のコマンドを入力します。このコマンドにより、旧パスワードおよび新規パスワードの指定が求められます。
keytool -keypass -alias signkey -storetype jceksJava Platform, Standard Editionツール・リファレンスでkeytoolを参照してください。
DomainLoadStoreParameterで説明されている構文を持つ構成データによって指定されます。キーストアの実装は、プロバイダ・ベースです。独自のKeyStore実装を記述する必要がある場合は、Java暗号化アーキテクチャでのプロバイダの実装方法を参照してください。
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は、暗号化パラメータの透明な仕様へのインタフェースです。このインタフェースには、メソッドまたは定数が含まれていません。このインタフェースの唯一の目的は、すべてのパラメータの仕様をグループ化すること(およびそれらのパラメータに安全な型を提供すること)です。すべてのパラメータの仕様で、このインタフェースを実装する必要があります。
次に、java.security.specパッケージとjavax.crypto.specパッケージ内のアルゴリズム・パラメータ仕様インタフェースおよびクラスを示します。
DHParameterSpecDHGenParameterSpecDSAParameterSpecECGenParameterSpecECParameterSpecGCMParameterSpecIvParameterSpecMGF1ParameterSpecOAEPParameterSpecPBEParameterSpecPSSParameterSpecRC2ParameterSpecRC5ParameterSpecRSAKeyGenParameterSpec次のアルゴリズム・パラメータ仕様は、特にXMLデジタル署名用に使用されます。
AlgorithmParametersクラスは、暗号化パラメータの不透明な表現を提供します。
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の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クラスは、証明書ファクトリの機能を定義します。証明書ファクトリは、エンコーディングから証明書のオブジェクトおよび証明書失効リスト(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()
初めにデフォルトのエンコーディングが一覧表示されます。
JCAのクラスについて理解したため、次に、これらのクラスがどのように結合されてSSL/TLSのような高度なネットワーク・プロトコルを実装するかについて考えます。
SSL、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内で構成される)をそのディレクトリを指すように設定します。
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フレームワークには、様々な管轄コンテキスト(場所)でアプリケーションに使用可能な暗号化アルゴリズムの最大強度に関して、制限を強制する機能が含まれます。管轄ポリシー・ファイルでこれらの制限を指定します。管轄ポリシー・ファイルとそれらを作成および構成する方法の詳細は、暗号強度の構成を参照してください。
これらの国の一部またはすべてで、特定のアプリケーションに対し、暗号化制限の一部またはすべての免責が許可されています。たとえば、特定の種類のアプリケーションは「特別」と見なされ、免責されます。また、鍵復元などの「免責メカニズム」を利用するアプリケーションは、免責可能です。この種の国では、免責されたと見なされるアプリケーションは、免責されていないアプリケーションに許可されるよりも強力な暗号化にアクセスできます。
実行時にアプリケーションが「免責されている」と認識されるようにするには、次の条件を満たす必要があります。
次に、アプリケーションにいくつかの暗号化制限が適用されないようにするために必要な手順のサンプルを示します。これは、免責されたものとしてアプリケーションを認識および処理するため、JCAにより要求される情報を含む、基本情報です。実際には、アプリケーションを実行可能にする(政府が暗号化制限を課している)特定の国の免責要件を知る必要があります。また、免責されたアプリケーションの処理プロセスを保持するJCAフレームワーク・ベンダーの要件も理解しておく必要があります。詳細は、ベンダーにお尋ね下さい。
注意:
SunJCEプロバイダは、ExemptionMechanismSpiクラスの実装を提供しません免責メカニズムを使用するアプリケーションに対する特殊コード要件
アプリケーションに、関連付けられたアクセス権ポリシー・ファイルがあり(同じJARファイル内)、そのアクセス権ポリシー・ファイルで免責メカニズムが指定されている場合、CipherのgetInstanceメソッドが呼び出されてCipherがインスタンス化されると、JCAコードは、インストール済プロバイダの中から、指定された免責メカニズムを実装するプロバイダを検索します。目的のプロバイダが見つかると、JCAは、そのプロバイダの実装に関連付けられたExemptionMechanism APIオブジェクトをインスタンス化してから、ExemptionMechanismオブジェクトを、getInstanceによって返されたCipherと関連付けます。
Cipherをインスタンス化した後、初期化する前に(Cipherのinitメソッドを呼び出して)、コードから次のCipherメソッドを呼び出す必要があります。
public ExemptionMechanism getExemptionMechanism()
この呼出しにより、Cipherに関連付けられたExemptionMechanismオブジェクトが返されます。次に、返されたExemptionMechanismに対して次のメソッドを実行して、免責メカニズムの実装を初期化する必要があります。
public final void init(Key key)
ここで指定する引数の型は、このあとでCipher initメソッドに指定する引数の型と同じにする必要があります。
ExemptionMechanismの初期化が完了したら、通常と同じ方法でCipherを初期化して使用できます。
アクセス権ポリシー・ファイル
実行時にアプリケーションが暗号化制限の一部またはすべてを「免責」されていると認識されるには、JARファイル内にアクセス権ポリシー・ファイルをバンドルする必要があります。アクセス権ポリシー・ファイルには、アプリケーションが保持する暗号化関連のアクセス権、およびそれを保持する条件(存在する場合)を指定します。
免責されるアプリケーションにバンドルされるアクセス権ポリシー・ファイル内のアクセス権エントリの書式は、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の最大鍵サイズおよびアルゴリズム・パラメータ値は、免責アクセス権エントリにより決定されます。
アプリケーションにバンドルされるアクセス権ポリシー・ファイル内の関連するエントリに、暗黙的に設定された免責アクセス権が存在しない場合、またはいずれかの登録済みプロバイダから利用可能な、指定された免責メカニズムの実装が存在しない場合、デフォルトの標準暗号化アクセス権のみがアプリケーションに付与されます。
「標準名」ドキュメントには、アルゴリズム仕様に関する情報が含まれています。
Javaセキュリティ標準アルゴリズム名仕様では、JDK Security APIで必要となり使用されるアルゴリズム、証明書およびキーストア・タイプの標準名について説明します。これには、アルゴリズムの仕様に関する詳細も含まれています。特定のプロバイダの情報は、JDKプロバイダ・ドキュメントにあります。
JDKでの暗号化実装は、主に歴史的な理由により、さまざまなプロバイダによって配布されます(Sun、SunJSSE、SunJCE、SunRsaSign)。これらのプロバイダは、すべてのJDK実装で使用可能ではない場合があるため、真に移殖可能なアプリケーションは、特定のプロバイダを指定せずにgetInstance()を呼び出す必要があります。特定のプロバイダを指定するアプリケーションは、基盤となるオペレーティング環境(PKCSやMicrosoftのCAPIなど)用に調整されたネイティブ・プロバイダを使用できない場合があります。
SunPKCS11プロバイダ自体には暗号化アルゴリズムは含まれていませんが、代わりに要求を基盤となるPKCS11実装に送ります。PKCS#11リファレンス・ガイドおよび基盤となるPKCS11実装を参照して、必要なアルゴリズムがPKCS11プロバイダで使用可能かどうかを判断するようにしてください。同様に、Windowsシステムでは、SunMSCAPIプロバイダは暗号化機能を提供しませんが、代わりに基盤となるオペレーティング・システムに要求を処理するよう渡します。
アプリケーションは、次の3種類のモジュールにパッケージ化できます。
名前付きまたは明示的モジュール: モジュール・パスに存在し、module-info.classファイルにモジュール構成情報が含まれているモジュール。
自動モジュール: モジュール・パスに存在するが、module-info.classファイル(基本的に標準JARファイル)にモジュール構成情報が含まれていないモジュール。
名前のないモジュール: クラス・パスに存在するモジュール。module-info.classファイルがある場合もない場合もあります。このファイルは無視されます。
名前付きモジュールは他よりも優れたパフォーマンス、強力なカプセル化、および簡単な構成を提供するため、アプリケーションを名前付きモジュールにパッケージ化することをお薦めします。それらは、高い柔軟性も提供します。モジュラJDKのクラス・パスにあるそれらを指定することで、非モジュラJDKで、または名前のないモジュールとしてでも、それらを使用できます。
モジュールの詳細は、モジュール・システムの状態とJEP 261: モジュール・システムを参照してください。
これらの例では、いくつかのJCAメカニズムの使用を説明します。Diffie-Hellman鍵交換、AES/GCMおよびHMAC-SHA256のサンプル・プログラムも参照してください。
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 shown below
}
例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つの鍵の同一性の判定が必要になることがありますが、デフォルトの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でエンコードされた証明書を読み取ります。認証の開始は、次のコードで指定されます。
-----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);
}
// Or use the aggregate operations below for the above 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");
keygen.init(128);
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);
次に、Diffie-Hellman鍵交換、AES/GCMおよびHMAC-SHA256のサンプル・プログラムを示します。
このプログラムでは、2つのパーティ間でDiffie-Hellman鍵協定プロトコルが実行されます。
/*
* Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.*;
import java.security.interfaces.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.crypto.interfaces.*;
import com.sun.crypto.provider.SunJCE;
public class DHKeyAgreement2 {
private DHKeyAgreement2() {}
public static void main(String argv[]) throws Exception {
/*
* Alice creates her own DH key pair with 2048-bit key size
*/
System.out.println("ALICE: Generate DH keypair ...");
KeyPairGenerator aliceKpairGen = KeyPairGenerator.getInstance("DH");
aliceKpairGen.initialize(2048);
KeyPair aliceKpair = aliceKpairGen.generateKeyPair();
// Alice creates and initializes her DH KeyAgreement object
System.out.println("ALICE: Initialization ...");
KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH");
aliceKeyAgree.init(aliceKpair.getPrivate());
// Alice encodes her public key, and sends it over to Bob.
byte[] alicePubKeyEnc = aliceKpair.getPublic().getEncoded();
/*
* Let's turn over to Bob. Bob has received Alice's public key
* in encoded format.
* He instantiates a DH public key from the encoded key material.
*/
KeyFactory bobKeyFac = KeyFactory.getInstance("DH");
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(alicePubKeyEnc);
PublicKey alicePubKey = bobKeyFac.generatePublic(x509KeySpec);
/*
* Bob gets the DH parameters associated with Alice's public key.
* He must use the same parameters when he generates his own key
* pair.
*/
DHParameterSpec dhParamFromAlicePubKey = ((DHPublicKey)alicePubKey).getParams();
// Bob creates his own DH key pair
System.out.println("BOB: Generate DH keypair ...");
KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH");
bobKpairGen.initialize(dhParamFromAlicePubKey);
KeyPair bobKpair = bobKpairGen.generateKeyPair();
// Bob creates and initializes his DH KeyAgreement object
System.out.println("BOB: Initialization ...");
KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH");
bobKeyAgree.init(bobKpair.getPrivate());
// Bob encodes his public key, and sends it over to Alice.
byte[] bobPubKeyEnc = bobKpair.getPublic().getEncoded();
/*
* Alice uses Bob's public key for the first (and only) phase
* of her version of the DH
* protocol.
* Before she can do so, she has to instantiate a DH public key
* from Bob's encoded key material.
*/
KeyFactory aliceKeyFac = KeyFactory.getInstance("DH");
x509KeySpec = new X509EncodedKeySpec(bobPubKeyEnc);
PublicKey bobPubKey = aliceKeyFac.generatePublic(x509KeySpec);
System.out.println("ALICE: Execute PHASE1 ...");
aliceKeyAgree.doPhase(bobPubKey, true);
/*
* Bob uses Alice's public key for the first (and only) phase
* of his version of the DH
* protocol.
*/
System.out.println("BOB: Execute PHASE1 ...");
bobKeyAgree.doPhase(alicePubKey, true);
/*
* At this stage, both Alice and Bob have completed the DH key
* agreement protocol.
* Both generate the (same) shared secret.
*/
try {
byte[] aliceSharedSecret = aliceKeyAgree.generateSecret();
int aliceLen = aliceSharedSecret.length;
byte[] bobSharedSecret = new byte[aliceLen];
int bobLen;
} catch (ShortBufferException e) {
System.out.println(e.getMessage());
} // provide output buffer of required size
bobLen = bobKeyAgree.generateSecret(bobSharedSecret, 0);
System.out.println("Alice secret: " +
toHexString(aliceSharedSecret));
System.out.println("Bob secret: " +
toHexString(bobSharedSecret));
if (!java.util.Arrays.equals(aliceSharedSecret, bobSharedSecret))
throw new Exception("Shared secrets differ");
System.out.println("Shared secrets are the same");
/*
* Now let's create a SecretKey object using the shared secret
* and use it for encryption. First, we generate SecretKeys for the
* "AES" algorithm (based on the raw shared secret data) and
* Then we use AES in CBC mode, which requires an initialization
* vector (IV) parameter. Note that you have to use the same IV
* for encryption and decryption: If you use a different IV for
* decryption than you used for encryption, decryption will fail.
*
* If you do not specify an IV when you initialize the Cipher
* object for encryption, the underlying implementation will generate
* a random one, which you have to retrieve using the
* javax.crypto.Cipher.getParameters() method, which returns an
* instance of java.security.AlgorithmParameters. You need to transfer
* the contents of that object (e.g., in encoded format, obtained via
* the AlgorithmParameters.getEncoded() method) to the party who will
* do the decryption. When initializing the Cipher for decryption,
* the (reinstantiated) AlgorithmParameters object must be explicitly
* passed to the Cipher.init() method.
*/
System.out.println("Use shared secret as SecretKey object ...");
SecretKeySpec bobAesKey = new SecretKeySpec(bobSharedSecret, 0, 16, "AES");
SecretKeySpec aliceAesKey = new SecretKeySpec(aliceSharedSecret, 0, 16, "AES");
/*
* Bob encrypts, using AES in CBC mode
*/
Cipher bobCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
bobCipher.init(Cipher.ENCRYPT_MODE, bobAesKey);
byte[] cleartext = "This is just an example".getBytes();
byte[] ciphertext = bobCipher.doFinal(cleartext);
// Retrieve the parameter that was used, and transfer it to Alice in
// encoded format
byte[] encodedParams = bobCipher.getParameters().getEncoded();
/*
* Alice decrypts, using AES in CBC mode
*/
// Instantiate AlgorithmParameters object from parameter encoding
// obtained from Bob
AlgorithmParameters aesParams = AlgorithmParameters.getInstance("AES");
aesParams.init(encodedParams);
Cipher aliceCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
aliceCipher.init(Cipher.DECRYPT_MODE, aliceAesKey, aesParams);
byte[] recovered = aliceCipher.doFinal(ciphertext);
if (!java.util.Arrays.equals(cleartext, recovered))
throw new Exception("AES in CBC mode recovered text is " +
"different from cleartext");
System.out.println("AES in CBC mode recovered text is "
"same as cleartext");
}
/*
* Converts a byte to hex digit and writes to the supplied buffer
*/
private static void byte2hex(byte b, StringBuffer buf) {
char[] hexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'A', 'B', 'C', 'D', 'E', 'F' };
int high = ((b & 0xf0) >> 4);
int low = (b & 0x0f);
buf.append(hexChars[high]);
buf.append(hexChars[low]);
}
/*
* Converts a byte array to hex string
*/
private static String toHexString(byte[] block) {
StringBuffer buf = new StringBuffer();
int len = block.length;
for (int i = 0; i < len; i++) {
byte2hex(block[i], buf);
if (i < len-1) {
buf.append(":");
}
}
return buf.toString();
}
}
このプログラムでは、3つのパーティ間でDiffie-Hellman鍵協定プロトコルが実行されます。
/*
* Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.crypto.interfaces.*;
/*
* This program executes the Diffie-Hellman key agreement protocol between
* 3 parties: Alice, Bob, and Carol using a shared 2048-bit DH parameter.
*/
public class DHKeyAgreement3 {
private DHKeyAgreement3() {}
public static void main(String argv[]) throws Exception {
// Alice creates her own DH key pair with 2048-bit key size
System.out.println("ALICE: Generate DH keypair ...");
KeyPairGenerator aliceKpairGen = KeyPairGenerator.getInstance("DH");
aliceKpairGen.initialize(2048);
KeyPair aliceKpair = aliceKpairGen.generateKeyPair();
// This DH parameters can also be constructed by creating a
// DHParameterSpec object using agreed-upon values
DHParameterSpec dhParamShared = ((DHPublicKey)aliceKpair.getPublic()).getParams();
// Bob creates his own DH key pair using the same params
System.out.println("BOB: Generate DH keypair ...");
KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH");
bobKpairGen.initialize(dhParamShared);
KeyPair bobKpair = bobKpairGen.generateKeyPair();
// Carol creates her own DH key pair using the same params
System.out.println("CAROL: Generate DH keypair ...");
KeyPairGenerator carolKpairGen = KeyPairGenerator.getInstance("DH");
carolKpairGen.initialize(dhParamShared);
KeyPair carolKpair = carolKpairGen.generateKeyPair();
// Alice initialize
System.out.println("ALICE: Initialize ...");
KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH");
aliceKeyAgree.init(aliceKpair.getPrivate());
// Bob initialize
System.out.println("BOB: Initialize ...");
KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH");
bobKeyAgree.init(bobKpair.getPrivate());
// Carol initialize
System.out.println("CAROL: Initialize ...");
KeyAgreement carolKeyAgree = KeyAgreement.getInstance("DH");
carolKeyAgree.init(carolKpair.getPrivate());
// Alice uses Carol's public key
Key ac = aliceKeyAgree.doPhase(carolKpair.getPublic(), false);
// Bob uses Alice's public key
Key ba = bobKeyAgree.doPhase(aliceKpair.getPublic(), false);
// Carol uses Bob's public key
Key cb = carolKeyAgree.doPhase(bobKpair.getPublic(), false);
// Alice uses Carol's result from above
aliceKeyAgree.doPhase(cb, true);
// Bob uses Alice's result from above
bobKeyAgree.doPhase(ac, true);
// Carol uses Bob's result from above
carolKeyAgree.doPhase(ba, true);
// Alice, Bob and Carol compute their secrets
byte[] aliceSharedSecret = aliceKeyAgree.generateSecret();
System.out.println("Alice secret: " + toHexString(aliceSharedSecret));
byte[] bobSharedSecret = bobKeyAgree.generateSecret();
System.out.println("Bob secret: " + toHexString(bobSharedSecret));
byte[] carolSharedSecret = carolKeyAgree.generateSecret();
System.out.println("Carol secret: " + toHexString(carolSharedSecret));
// Compare Alice and Bob
if (!java.util.Arrays.equals(aliceSharedSecret, bobSharedSecret))
throw new Exception("Alice and Bob differ");
System.out.println("Alice and Bob are the same");
// Compare Bob and Carol
if (!java.util.Arrays.equals(bobSharedSecret, carolSharedSecret))
throw new Exception("Bob and Carol differ");
System.out.println("Bob and Carol are the same");
}
/*
* Converts a byte to hex digit and writes to the supplied buffer
*/
private static void byte2hex(byte b, StringBuffer buf) {
char[] hexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'A', 'B', 'C', 'D', 'E', 'F' };
int high = ((b & 0xf0) >> 4);
int low = (b & 0x0f);
buf.append(hexChars[high]);
buf.append(hexChars[low]);
}
/*
* Converts a byte array to hex string
*/
private static String toHexString(byte[] block) {
StringBuffer buf = new StringBuffer();
int len = block.length;
for (int i = 0; i < len; i++) {
byte2hex(block[i], buf);
if (i < len-1) {
buf.append(":");
}
}
return buf.toString();
}
}
次に、AES/GCMを使用してデータを暗号化/複合化するサンプル・プログラムを示します。
/*
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import java.security.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オブジェクトを初期化する方法を示します。
例2-14 HMAC-SHA256の秘密鍵オブジェクトを生成する
/*
* Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import java.security.*;
import javax.crypto.*;
/**
* This program demonstrates how to generate a secret-key object for
* HMACSHA256, and initialize an HMACSHA256 object with it.
*/
public class initMac {
public static void main(String[] args) throws Exception {
// Generate secret key for HmacSHA256
KeyGenerator kg = KeyGenerator.getInstance("HmacSHA256");
SecretKey sk = kg.generateKey();
// Get instance of Mac object implementing HmacSHA256, and
// initialize it with the above secret key
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(sk);
byte[] result = mac.doFinal("Hi There".getBytes());
}
}