2 Java暗号化アーキテクチャ(JCA)リファレンス・ガイド
Java暗号化アーキテクチャ(JCA)はプラットフォームの主要な部分であり、プロバイダ・アーキテクチャ、およびデジタル署名、メッセージ・ダイジェスト(ハッシュ)、証明書と証明書検証、暗号化(対称/非対称ブロック/ストリーム暗号)、キーの生成と管理、安全な乱数生成などのための一連のAPIを含んでいます。
Java暗号化アーキテクチャの概要
Javaプラットフォームは、言語の安全性、暗号化、公開キーインフラストラクチャ、認証、安全な通信、アクセス制御など、セキュリティに重点を置いています。
JCAはプラットフォームの主要な部分であり、「プロバイダ」アーキテクチャ、およびデジタル署名、メッセージ・ダイジェスト(ハッシュ)、証明書と証明書検証、暗号化(対称/非対称ブロック/ストリーム暗号)、キーの生成と管理、安全な乱数生成などのための一連のAPIが含まれています。これらのAPIによって、開発者はアプリケーション・コードにセキュリティを簡単に統合できます。アーキテクチャは、次の方針に基づいて設計されました。
-
実装の独立性: アプリケーションでセキュリティ・アルゴリズムを実装する必要はありません。Javaプラットフォームからセキュリティ・サービスを要求できます。セキュリティ・サービスはプロバイダに実装されています(暗号化サービス・プロバイダを参照)。プロバイダは、標準インタフェースによってJavaプラットフォームにプラグインされます。アプリケーションは、複数の独立したプロバイダにセキュリティ機能を依存する場合があります。
-
実装の相互運用性: プロバイダは、アプリケーション間で相互運用できます。具体的には、アプリケーションは特定のプロバイダにバインドされず、プロバイダは特定のアプリケーションにバインドされません。
-
アルゴリズムの拡張性: Javaプラットフォームには、現在広く使用されている基本的なセキュリティ・サービスを実装する多数の組込みプロバイダが含まれています。ただし、一部のアプリケーションは、まだ実装されていない普及しつつある規格や独自のサービスに依存している場合があります。Javaプラットフォームは、そのようなサービスを実装するカスタム・プロバイダのインストールをサポートします。
JDKで使用できるその他の暗号化通信ライブラリはJCAプロバイダ・アーキテクチャを使用しますが、これらについては別途説明します。JSSEコンポーネントは、Secure Socket Layer (SSL)、Transport Layer Security (TLS)およびDatagram Transport Layer Security (DTLS)実装へのアクセスを提供します。Java Secure Socket Extension (JSSE)リファレンス・ガイドを参照してください。Java Generic Security Services (JGSS) (Kerberosを使用)のAPI、およびSimple Authentication and Security Layer (SASL)を使用して、通信するアプリケーション間でメッセージを安全に交換できます。JAASおよびJava GSS-APIチュートリアルの紹介およびJava SASL APIプログラミングおよび配備ガイドを参照してください。
用語に関する注記
-
JDK 1.4より前は、JCEはバンドルされていない製品であり、そのため、JCAとJCEは常に別個のコンポーネントとして参照されていました。現在JCEはJDKにバンドルされているため、区別は明確でなくなっています。JCEはJCAと同じアーキテクチャを使用するため、JCEはJCAの一部として考えることができます。
-
JDKのJCAには、次の2つのソフトウェア・コンポーネントが含まれます。
- プロバイダが実装を提供可能な暗号化サービスを定義およびサポートするフレームワーク。このフレームワークには、
java.security
、javax.crypto
、javax.crypto.spec
、javax.crypto.interfaces
などのパッケージが含まれます。 Sun
、SunRsaSign
、SunJCE
などの実際のプロバイダ。実際の暗号化実装が含まれます。
特定のJCAプロバイダを取り上げる場合は、常にプロバイダの名前で明示的に指定されます。
- プロバイダが実装を提供可能な暗号化サービスを定義およびサポートするフレームワーク。このフレームワークには、
警告:
JCAを使用すると、アプリケーションへのセキュリティ機能の組込みが簡単になります。ただし、このドキュメントでは、APIの説明に必要な概念の基本的な紹介以外、セキュリティ/暗号化の理論については説明しません。このドキュメントでは、特定のアルゴリズムの長所と短所やプロトコルの設計についても説明しません。暗号化は高度なトピックであり、これらのツールを最適に使用するには、信頼できる、可能なかぎり最近のリファレンスを参照するようにしてください。実行している内容とその理由を常に理解している必要があります。ランダム・コードを単純にコピーし、それが用途を満たす筋書きを完全に解決してくれるとは考えないでください。間違ったツールまたはアルゴリズムが選択されたため、セキュリティまたはパフォーマンスに重大な問題があるアプリケーションが数多く配備されています。
JCAの設計の原則
JCAは、次の方針に基づいて設計されました。
- 実装の独立性と相互操作性
- アルゴリズムの独立性と拡張性
実装の独立性とアルゴリズムの独立性は相補関係にあります。そのため、デジタル署名やメッセージ・ダイジェストなどの暗号化サービスを、実装の詳細やこれらの概念の基になるアルゴリズムを意識しないで使用できます。完全なアルゴリズムの独立性は不可能ですが、JCAは標準化された、アルゴリズム固有のAPIを提供します。実装の独立性が望ましくない場合、JCAでは、開発者が特定の実装を指定できます。
アルゴリズムの独立性は、暗号化「エンジン」(サービス)の型を定義し、これらの暗号化エンジンの機能を提供するクラスを定義することにより実現しています。これらのクラスは、エンジン・クラスと呼ばれ、例としては、MessageDigest、Signature、KeyFactory、KeyPairGenerator、およびCipherクラスがあります。
実装の独立性は、「プロバイダ」ベースのアーキテクチャを使って実現されます。暗号化サービス・プロバイダ(CSP) (プロバイダとも呼ばれる)とは(暗号化サービス・プロバイダを参照)、デジタル署名アルゴリズム、メッセージ・ダイジェスト・アルゴリズム、キー変換サービスなどの1つ以上の暗号化サービスを実装するパッケージやパッケージ・セットを意味します。プログラムは単に、特定のサービス(たとえばDSA署名アルゴリズム)を実行する特定の型のオブジェクト(たとえばSignature
オブジェクト)を要求するだけで、インストールされているプロバイダの1つから実装を獲得できます。必要に応じて、プログラムは特定プロバイダから実装を要求することもできます。たとえば、より高速で安全性の高いバージョンが利用できるときは、プロバイダをアプリケーションに対して透過的に更新できます。
実装の相互操作性とは、さまざまな実装が互いに機能でき、互いのキーを使ったり、互いの署名を検証できることを意味します。つまり、1つのプロバイダが生成した同じアルゴリズムやキーを別のプロバイダが使えたり、あるプロバイダが生成した署名を別のプロバイダが検証できるということです。
アルゴリズムの拡張性とは、サポートされるエンジン・クラスの1つに当てはまる新規アルゴリズムを容易に追加できることを意味します。
プロバイダ・アーキテクチャ
プロバイダは、通知されている暗号化アルゴリズムの固定実装を提供するパッケージ(または、パッケージのセット)を含みます。
暗号化サービス・プロバイダ
java.security.Provider
は、すべてのセキュリティ・プロバイダの基底クラスです。各CSPはこのクラスのインスタンスを含みます。インスタンスはプロバイダの名前を含み、実装するセキュリティ・サービス/アルゴリズムをすべて一覧表示します。特定のアルゴリズムのインスタンスが必要な場合、JCAフレームワークはプロバイダのデータベースに問い合わせ、一致するものが見つかった場合はそのインスタンスが作成されます。
プロバイダは、通知されている暗号化アルゴリズムの固定実装を提供するパッケージ(または、パッケージのセット)を含みます。各JDKのインストールでは、1つ以上のプロバイダがインストールされ、デフォルトで構成されます。追加のプロバイダは、静的または動的に追加できます。クライアントは実行環境を構成し、プロバイダの優先順位を指定できます。優先順位とは、特定プロバイダの指定がない場合に、要求されたサービスに関して検索されるプロバイダの順位です。
JCAを使用するには、アプリケーションは単に特定の型のオブジェクト(たとえばMessageDigest
)と特定のアルゴリズムまたはサービス(たとえばSHA-256アルゴリズム)を要求し、インストールされているプロバイダのうちの1つから実装を獲得します。たとえば、次の文は、インストールされているプロバイダからのSHA-256メッセージ・ダイジェストを要求します。
md = MessageDigest.getInstance("SHA-256");
あるいは、特定プロバイダのオブジェクトを要求することもできます。各プロバイダは、それぞれの参照名を持ちます。たとえば、次の文は、ProviderCという名前のプロバイダからのSHA-256メッセージ・ダイジェストを要求します。
md = MessageDigest.getInstance("SHA-256", "ProviderC");
次の図は、SHA-256メッセージ・ダイジェスト実装の要求を示しています。それらは、様々なメッセージ・ダイジェスト・アルゴリズム(SHA-256、SHA-384およびSHA-512)を実装する3つの異なるプロバイダを示しています。プロバイダは、左から右への優先順位(1から3)で並べられています。図2-1では、アプリケーションはプロバイダ名を指定せずにSHA-256アルゴリズム実装を要求しています。プロバイダが優先順位に従って検索され、その特定のアルゴリズムを提供する最初のプロバイダであるProviderBから実装が返されます。図2-2では、アプリケーションは特定のプロバイダであるProviderCからのSHA-256アルゴリズム実装を要求しています。この場合は、優先順位の高いプロバイダであるProviderBも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)など、様々なタイプのアルゴリズムがあります。-
Mac
:MessageDigest
と同様にメッセージ認証コード(MAC)もハッシュ値を生成しますが、メッセージの整合性を保護するために最初にキーによって初期化されます。 KeyFactory
:Key
型の既存の不透明な暗号化キーを「キー仕様」(基本のキー・データの透明な表現)に変換したり、その逆の変換を行うために使用します。SecretKeyFactory
:SecretKey
型の既存の不透明な暗号化キーを「キー仕様」(基本のキー・データの透明な表現)に変換したり、その逆の変換を行うために使用します。SecretKeyFactory
は、秘密(対称)キーのみを作成する特殊なKeyFactory
です。KeyPairGenerator
: 指定のアルゴリズムでの使用に適合した、新しい公開キー、秘密キー・ペアの生成に使用します。KeyGenerator
: 指定のアルゴリズムで使用するための、新しい秘密キーの生成に使用します。KeyAgreement
: 特定の暗号化操作に使用する特定のキーの合意および確立のために、複数のパーティによって使用されます。AlgorithmParameters
: パラメータのエンコードおよびデコードなど、特定のアルゴリズムのパラメータの格納に使用します。AlgorithmParameterGenerator
: 特定のアルゴリズムに適したアルゴリズム・パラメータ・セットの生成に使用します。KeyStore
: キーストアの作成および管理に使用します。キーストアは、キーのデータベースです。キーストア内の秘密キーには、それらのキーに関連付けられた証明書チェーンがあります。証明書チェーンは、対応する公開キーを認証します。また、キーストアには、信頼できるエンティティからの証明書も格納されています。CertificateFactory
: 公開キーの証明書および証明書失効リスト(CRL)の作成に使用します。CertPathBuilder
: 証明書チェーン(証明書パスとも呼ばれる)の構築に使用します。CertPathValidator
: 証明書チェーンの検証に使います。CertStore
: リポジトリからCertificate
とCRL
を取得するために使用します。
注意:
ジェネレータ(generator)は最新の内容でオブジェクトを作成しますが、ファクトリ(factory)は既存の構成要素(エンコードなど)からオブジェクトを作成します。コア・クラスとインタフェース
次に、JCAで提供されるコア・クラスとインタフェースを示します。
Provider
およびSecurity
SecureRandom
、MessageDigest
、Signature
、Cipher
、Mac
、KeyFactory
、SecretKeyFactory
、KeyPairGenerator
、KeyGenerator
、KeyAgreement
、AlgorithmParameter
、AlgorithmParameterGenerator
、KeyStore
、CertificateFactory
およびエンジンKey Interface
、KeyPair
AlgorithmParameterSpec Interface
、AlgorithmParameters
、AlgorithmParameterGenerator
、およびjava.security.spec
パッケージとjavax.crypto.spec
パッケージ内のアルゴリズム・パラメータ仕様インタフェースおよびクラスKeySpec Interface
、EncodedKeySpec
、PKCS8EncodedKeySpec
およびX509EncodedKeySpec
SecretKeyFactory
、KeyFactory
、KeyPairGenerator
、KeyGenerator
、KeyAgreement
およびKeyStore
このガイドでは、もっとも有用な高レベルのクラスについて最初に説明し(Provider、Security、SecureRandom、MessageDigest、Signature、Cipher、およびMac)、次にさまざまなサポート・クラスについて説明します。現時点では、キー(公開、非公開、および秘密)はさまざまなJCAクラスによって生成および表現され、高レベルのクラスによってその操作の一部として使用されるという説明だけで十分です。
このセクションでは、各クラスおよびインタフェースのメイン・メソッドの署名を示します。対応するコード例の項に、これらのクラス(MessageDigest、Signature、KeyPairGenerator、SecureRandom、KeyFactoryおよびキー仕様クラス)の例があります。
関連するSecurity APIパッケージの完全なリファレンス・ドキュメントは、次のパッケージのサマリーにあります。
Providerクラス
「暗号化サービス・プロバイダ」(このドキュメントでは「プロバイダ」とも呼ばれる)とは、JDK Security APIの暗号化機能のサブセットの固定実装を提供するパッケージまたはパッケージ・セットです。Provider
クラスは、このようなパッケージやパッケージ・セットへのインタフェースです。プロバイダ名、バージョン番号、その他の情報にアクセスするためのメソッドを備えています。Provider
クラスを使用すると、暗号化サービスの実装を登録するだけでなく、その他のセキュリティ・サービスの実装を登録することもできます。その他のセキュリティ・サービスは、JDK Security APIまたはその拡張機能の1つの一部として定義されている場合があります。
暗号化サービスの実装を提供するには、エンティティ(開発グループなど)は実装コードを作成し、Provider
クラスのサブクラスを生成します。Provider
サブクラスのコンストラクタは、プロバイダが実装したサービスを検索するためにJDK Security APIが使用する各種プロパティの値を設定します。つまり、サブクラスは、サービスを実装するクラス名を指定します。
プロバイダ・パッケージが実装できるサービスには、様々な種類があります。エンジン・クラスとアルゴリズムを参照してください。
実装が違うと、特徴も違ってくる場合があります。ソフトウェア・ベースのものもあれば、ハードウェア・ベースのものもあります。プラットフォーム独立のものもあれば、プラットフォーム固有のものもあります。また、レビューや評価用に使えるプロバイダ・コードもあれば、使えないものもあります。JCAでは、エンド・ユーザーと開発者の双方が独自のニーズを決定できます。
エンド・ユーザーのニーズに合った暗号化実装のインストール方法、および開発者のニーズに合った実装の要求方法について説明されています。
注意:
プロバイダを実装するには、プロバイダの実装および統合までのステップを参照してください。プロバイダ実装の要求および獲得方法
このAPIのエンジン・クラス(エンジン・クラスとアルゴリズムを参照)ごとに、実装インスタンスが要求され、そのエンジン・クラスのgetInstanceメソッドの1つを呼び出してインスタンスが生成されます。このときに、希望するアルゴリズムの名前、および希望する実装を含むプロバイダ(またはProvider
クラス)の名前(オプション)を指定します。
static EngineClassName getInstance(String algorithm)
throws NoSuchAlgorithmException
static EngineClassName getInstance(String algorithm, String provider)
throws NoSuchAlgorithmException, NoSuchProviderException
static EngineClassName getInstance(String algorithm, Provider provider)
throws NoSuchAlgorithmException
説明は次のとおりです。
EngineClassName
は、希望するエンジン・タイプ(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つのシナリオについて見てみます。
- SHA-256実装を検索するとする。どちらのプロバイダもこの実装を供給する。
PROVIDER_1
が最高の優先順位を持ち最初に検索されるので、PROVIDER_1
実装が返される。 - SHA256withDSA署名アルゴリズムを検索する場合、最初に
PROVIDER_1
が検索される。実装が見つからないので次にPROVIDER_2
が検索される。実装が見つかるので、このプロバイダが返される。 - SHA256withRSA署名アルゴリズムを検索するとする。これを実装するプロバイダはインストールされていないので、
NoSuchAlgorithmException
がスローされる。
プロバイダ引数を持つgetInstanceメソッドは、必要なアルゴリズムを提供するプロバイダを指定したい開発者のためのものです。たとえば、連邦証明書を受け取ったプロバイダ実装を連邦局が使いたいとします。PROVIDER_1
のSHA256withDSA実装はこの証明書を受け取っていないのに対し、PROVIDER_2
のDSA実装がこれを受け取っているとします。
連邦局のプログラムは次の呼出しを実行します。PROVIDER_2
が認証された実装を持つため、これを指定します。
Signature dsa = Signature.getInstance("SHA256withDSA", "PROVIDER_2");
この場合、PROVIDER_2
がインストールされていないと、インストールされている別のプロバイダが要求アルゴリズムを実装している場合であっても、NoSuchProviderException
がスローされます。
プログラムは、(SecurityクラスのクラスのgetProvidersメソッドを使用して)インストールされているすべてのプロバイダのリストを獲得し、リストからいずれかを選択することもできます。
注意:
汎用アプリケーションは、特定のプロバイダから暗号化サービスを要求しないようにする必要があります。そうでない場合、アプリケーションは特定のプロバイダに結び付けられることになり、そのプロバイダはほかのJava実装で使用できない可能性があります。特定の要求されたプロバイダよりも優先順位が高い、使用可能な最適化されたプロバイダ(たとえば、PKCS11によるハードウェア・アクセラレータまたはMicrosoftのMSCAPIなどのネイティブOS実装)を利用できない場合もあります。プロバイダのインストール
暗号化プロバイダを使用するには、最初にインストールし、次に静的または動的に登録する必要があります。さまざまなSunプロバイダがこのリリースに同梱されており(SUN
、SunJCE
、SunJSSE
、SunRsaSign
など)、すでにインストールおよび登録されています。以降のセクションでは、追加プロバイダのインストールおよび登録方法について説明します。
すべてのJDKプロバイダは、すでにインストールされ登録されています。ただし、サードパーティ製プロバイダが必要な場合は、クラス・パスまたはモジュール・パスへのプロバイダの追加、プロバイダの登録(静的または動的)、必要なアクセス権の追加の方法の詳細を、プロバイダの実装および統合までのステップのステップ8: テストの準備で参照してください。
Securityクラス
Securityクラスは、インストールされているプロバイダおよびセキュリティに関するプロパティを管理します。このクラスに含まれるのは、インスタンスが生成されないstaticメソッドだけです。プロバイダの追加または削除、およびSecurity
プロパティの設定用メソッドは、信頼できるプログラムによってしか実行できません。現時点での「信頼できるプログラム」は、次のどれかです。
- セキュリティ・マネージャの下で動作していないローカル・アプリケーション。
- 指定されたメソッドを実行する権限のあるアプレットまたはアプリケーション(次を参照)。
あるコードが、実行しようとしているアクション(プロバイダの追加など)を行うことができるような、信頼できるコードであると判断するには、その特定のアクションを行う適切な権限がそのアプレットに与えられている必要があります。インストールしたJDKのポリシー構成ファイルでは、指定されたコード・ソースからのコードに対し、どのようなアクセス権(システム・リソースへのアクセスの種類)を与えるかを指定します。(以降の説明、および「デフォルトのPolicyの実装とポリシー・ファイルの構文」と「Java SEプラットフォームのセキュリティ・アーキテクチャ」を参照。)
実行されるコードは、常に、特定の「コード・ソース」から来ると考えられます。コード・ソースには、そのコードの元の場所(URL)だけでなく、コードの署名に使用された秘密キーに対応する任意の公開キーへの参照も含まれています。コード・ソース内の公開キーは、ユーザーのものから別名(記号名)で参照します。
ポリシー構成ファイル内では、コード・ソースは、コード・ベース(URL)、および(signedBy
で始まる)別名の2つのコンポーネントで表されます。別名は、コードの署名の検証に使う必要のある公開キーを含んだキーストアのエントリを識別します。
このようなファイル内の各「許可」文は、指定されたコード・ソースにアクセス権のセットを与え、許可されるアクションを指定します。
次に、ポリシー構成ファイルの例を示します。
grant codeBase "file:/home/sysadmin/", signedBy "sysadmin" {
permission java.security.SecurityPermission "insertProvider";
permission java.security.SecurityPermission "removeProvider";
permission java.security.SecurityPermission "putProviderProperty.*";
};
この構成ファイルは、ローカル・ファイル・システム上の/home/sysadmin/
ディレクトリ内の署名されたJARファイルからロードされたコードが、プロバイダの追加または削除、あるいはプロバイダのプロパティを設定できることを指定します。(JARファイルの署名は、ユーザーのキーストア内の別名sysadmin
で参照する公開キーを使用して検証できます)。
コード・ソースを指定するコンポーネントは、どちらも(または両方を)省略可能です。次に、codeBase
を省略した構成ファイルの例を示します。
grant signedBy "sysadmin" {
permission java.security.SecurityPermission "insertProvider.*";
permission java.security.SecurityPermission "removeProvider.*";
};
このポリシーが有効になっている場合は、ローカル・ファイル・システム上の/home/sysadmin/
ディレクトリによって署名されたJARファイル内のコードで、プロバイダを追加または削除できます。コードに署名は必要ありません。
codeBase
とsignedBy
を両方とも省略した例を次に示します。
grant {
permission java.security.SecurityPermission "insertProvider.*";
permission java.security.SecurityPermission "removeProvider.*";
};
この例では、コード・ソースを指定するコンポーネントがどちらも省略されています。したがって、出所がどこか、署名が付いているか、だれの署名が付いているかに関係なく、どのコードでもプロバイダの追加と削除が行えます。この付与はセキュリティ・ホールを生む可能性があるため、絶対にお薦めしません。信頼されていないコードによってプロバイダがインストールされ、それにより適切に機能する実装に依存する後続のコードに影響する可能性があります。(たとえば、破壊行為を行うCipher
オブジェクトが、受信した機密情報を取り込んで格納する場合があります。)
プロバイダの管理
次の表に、Security
クラス内のメソッドのうち、インストールされているProvider
を照会するために使用できるメソッドと、実行時にプロバイダをインストールまたは削除するためのメソッドの要約を示します。
プロバイダの照会
メソッド | 説明 |
---|---|
static Provider[] getProviders() |
インストールされているすべてのプロバイダを含む配列(つまり、各パッケージ・プロバイダのProvider サブクラス)を返します。配列内のProvider の順序は、優先順位に従います。
|
static Provider getProvider (String providerName) |
providerName というProvider を返します。このProvider が見つからないとnull を返します。
|
プロバイダの追加
メソッド | 説明 |
---|---|
static int addProvider(Provider provider) |
インストールされたProvider のリストの末尾にProvider を追加します。Provider が追加された優先順位を返すか、すでにインストールされているためにProvider が追加されなかった場合は-1 を返します。
|
static int insertProviderAt (Provider provider, int position) |
指定された位置に新規のProvider を追加します。あるプロバイダが要求された場所にインストールされた場合は、それまでその場所にあったプロバイダ、およびposition より後の位置のすべてのプロバイダの順位は、1つ後になり、一覧の最後尾に向かって1つ移動します。このメソッドは、Provider が追加された優先順位を返すか、すでにインストールされているためにProvider が追加されなかった場合は-1 を返します。
|
プロバイダの削除
メソッド | 説明 |
---|---|
static void removeProvider(String name) |
指定された名前のProvider を削除します。プロバイダがインストールされていない場合は、何もせず復帰します。指定されたプロバイダが削除されると、そのプロバイダよりもあとの位置にあるすべてのプロバイダの位置が1つ前になり、インストールされたプロバイダ一覧の先頭に向かって1つ移動します。
|
注意:
プロバイダの優先順位を変更する場合は、まず目的のプロバイダを削除してから、希望する優先順位の位置に挿入しなおす必要があります。セキュリティ・プロパティ
Security
クラスは、システムに関するセキュリティ・プロパティのリストを保持します。これらのプロパティはSystem
プロパティに似ていますが、セキュリティ関連です。これらのプロパティは、静的(<java-home>/conf/security/java.security
ファイルを使用)または動的(APIを使用)に設定できます。security.provider.n
セキュリティ・プロパティで静的にプロバイダを登録する例は、プロバイダの実装および統合までのステップのステップ8.1: プロバイダの構成を参照してください。プロパティを動的に設定する場合、信頼できるプログラムは次のメソッドを使用できます。
static String getProperty(String key)
static void setProperty(String key, String datum)
注意:
セキュリティ・プロバイダのリストは、VMの起動中に確立されます。したがって、前述のメソッドは、プロバイダ・リストを変更する場合に使用してください。SecureRandomクラス
SecureRandomクラスは、初期シード値から疑似乱数列を生成する決定性アルゴリズムである、疑似乱数ジェネレータ(PRNG)にアクセスするか、ランダム性の発生源(たとえば、/dev/random
または実際の乱数ジェネレータ)を読み取ることで、暗号として強力な乱数を提供するエンジン・クラス(エンジン・クラスとアルゴリズムを参照)です。PRNGの一例は、NIST SP 800-90Ar1で示されているDeterministic Random Bits Generator (DRBG)です。実装の中には、真の乱数を生成するものもあれば、両方の技術の組み合わせを使用するものもあります。暗号として強力な乱数は、少なくとも、FIPS 140-2、暗号化モジュールのセキュリティ要件の第4.9.1項で示されている統計的乱数生成テストに適合しています。
すべてのJava SE実装は、java.security.Security
クラスのsecurerandom.strongAlgorithms
プロパティで提供される、もっとも強力な(もっともランダム性の高い)SecureRandomの実装を示す必要があります。この実装は、特に強力な乱数値が必要な場合に使用できます。
securerandom.drbg.config
プロパティは、SUNプロバイダでDRBGのSecureRandom
構成および実装を指定するために使用されます。securerandom.drbg.config
は、java.security.Security
クラスのプロパティです。その他のDRBG実装でも、securerandom.drbg.config
プロパティを使用できます。
SecureRandomオブジェクトの作成
SecureRandom
のインスタンスは、いくつかの方法で取得できます。
-
すべてのJava SE実装は、引数なしのコンストラクタ
new SecureRandom()
を使用してデフォルトのSecureRandom
を提供します。このコンストラクタは、登録されているセキュリティ・プロバイダのリスト内を、最も推奨されるプロバイダから順に確認し、その後、SecureRandom
乱数ジェネレータ(RNG)アルゴリズムをサポートする最初のプロバイダから新しいSecureRandomオブジェクトを返します。どのプロバイダもRNGアルゴリズムをサポートしていない場合は、SUNプロバイダからSHA1PRNGを使用するSecureRandomオブジェクトを返します。 -
SecureRandom
の特定の実装を獲得するには、プロバイダ実装の要求および獲得方法のいずれかを使用します。 -
java.security.Security
クラスのsecurerandom.strongAlgorithms
プロパティで定義される強力なSecureRandom
実装を取得するには、getInstanceStrong()
メソッドを使用します。このプロパティは、重要な値を生成するのに適したプラットフォーム実装をリストします。
SecureRandomオブジェクトのシードまたは再シード
SecureRandomオブジェクトは、getInstance()の呼出し後に次のsetSeedメソッドのいずれかを呼び出さないかぎり、ランダム・シードで初期化されます。
void setSeed(byte[] seed)
void setSeed(long seed)
最初のnextBytes呼出しの前にsetSeedを呼び出して、環境のランダム性を防ぐ必要があります。
SecureRandomオブジェクトによってもたらされるビットのランダム性は、シード・ビットのランダム性によって異なります。
SecureRandom
オブジェクトは、setSeed
メソッドまたはreseed
メソッドのいずれかを使用していつでも再シードされる可能性があります。setSeed
で指定されたシードは、既存のシードと置き換えられるのではなく、既存のシードに追加されます。したがって、呼出しを繰り返しても、ランダム性が減少しないことが保証されます。
MessageDigestクラス
MessageDigest
クラスは、暗号として安全なメッセージ・ダイジェスト(SHA-256やSHA-512など)の機能を提供するように設計されているエンジン・クラス(エンジン・クラスとアルゴリズムを参照)です。安全な暗号化メッセージ・ダイジェストは、任意サイズの入力(バイト配列)を取り、固定サイズ出力を生成します。これをダイジェストまたはハッシュと言います。
たとえば、SHA-256のアルゴリズムは32バイトのダイジェストを生成し、SHA-512のアルゴリズムは64バイトのダイジェストを生成します。
ダイジェストには次の2つの特徴があります。
- 計算によって、同じ値にハッシュされた2つのメッセージを検索できない。
- ダイジェストの生成に使った入力情報を明らかにしない。
メッセージ・ダイジェストを使い、一意で、信頼できるデータ識別子を生成します。データ識別子をデータの「チェックサム」または「デジタル指紋」と呼ぶこともあります。メッセージのうちの1ビットを変更するだけで、異なるダイジェスト値が生成されます。
メッセージ・ダイジェストには多くの用途があり、意図的かどうかにかかわらず、いつデータが変更されたかを判断できます。近年、一般的なアルゴリズムに弱点があるかどうかを判断するために大きな努力が払われていますが、結果はさまざまです。ダイジェスト・アルゴリズムを選択する場合は、最近のリファレンスを調べて、そのステータスと当面のタスクでの妥当性を判断する必要があります。
ダイジェストの計算
様々なタイプのdigest()メソッドを使用してダイジェストを計算する手順を示します。
update
メソッドを呼び出して提供する必要があります。メッセージ・ダイジェスト・オブジェクトのアップデートを参照してください。
Signatureクラス
Signature
クラスは、SHA256withDSAやSHA512withRSAなどの暗号化デジタル署名アルゴリズムの機能を提供するように設計されているエンジン・クラス(エンジン・クラスとアルゴリズムを参照)です。安全な暗号化署名アルゴリズムは、任意サイズの入力と秘密キーを取り、署名と呼ばれる比較的短い(固定サイズの場合もよくある)バイト文字列を生成します。このプロパティは次のとおりです。
- 公開キーと非公開キーのペアの所有者のみが署名を作成できる。計算によって、公開キーおよびいくつかの署名のみを保持するユーザーが秘密キーを回復することはできない。
- 署名の生成に使う秘密キーに対応する公開キーが与えられる。これは入力の認証性および整合性を検証できる。
Signatureオブジェクトは、秘密キーでの署名に対して初期化され、署名するデータを与えられます。結果の署名バイトは、通常は署名されたデータとともに保持されます。検証が必要な場合、別のSignatureオブジェクトが作成され、検証に対して初期化されて対応する公開キーを与えられます。データおよび署名バイトはSignature (署名)オブジェクトに渡され、データと署名が一致した場合に、Signatureオブジェクトは成功を報告します。
署名はメッセージ・ダイジェストに似ていますが、提供する保護の種類の点で目的が大きく異なっています。実際、SHA256WithRSAなどのアルゴリズムは、メッセージ・ダイジェストSHA256を使用して最初に大きなデータ・セットを管理しやすい形式に圧縮し、次にRSAアルゴリズムで結果の32バイトのメッセージ・ダイジェストに署名します。
データの署名と検証の例は、生成されたキーを使った署名の生成および検証を参照してください。
Signatureオブジェクトの状態
Signature
オブジェクトはモデル・オブジェクトです。つまり、Signature
オブジェクトは、常に指定の状態にあり、この状態で1つの型の操作だけを実行できます。
状態は、個々のクラスで定義したfinal (ファイナル)整数の定数で表されます。
Signature
オブジェクトに可能な状態は、次の3つです。
UNINITIALIZED
SIGN
VERIFY
Signature
オブジェクトがはじめて生成されるときは、UNINITIALIZED
の状態です。Signature
クラスは、状態をSIGN
に変更するinitSign
と、状態をVERIFY
に変更するinitVerify
という2つの初期化メソッドを定義します。
Signatureオブジェクトの作成
署名を付けたり、検証したりするには、最初にSignature
のインスタンスを生成します。
Signature
オブジェクトは、Signature
getInstance() staticファクトリ・メソッドの1つを使用することによって取得されます。プロバイダ実装の要求および獲得方法を参照してください。
Signatureオブジェクトの初期化
Signature
オブジェクトを使うには、最初に必ずこれを初期化します。初期化メソッドは、オブジェクトを署名用に使うか検証用に使うかに応じて異なります。
署名で使う場合、オブジェクトは最初に、署名を生成するエンティティの秘密キーを使って初期化する必要があります。この初期化は、次のメソッドを呼び出して実行します。
final void initSign(PrivateKey privateKey)
このメソッドでは、Signature
オブジェクトはSIGN
状態になります。Signature
オブジェクトを検証で使う場合は、最初に、署名を検証するエンティティの公開キーを使って初期化する必要があります。この初期化は、次のいずれかのメソッドを呼び出して実行します。
final void initVerify(PublicKey publicKey)
final void initVerify(Certificate certificate)
このメソッドでは、Signature
オブジェクトはVERIFY
状態になります。
Signatureオブジェクトによる署名
Signature
オブジェクトを署名用に初期化した場合(このオブジェクトがSIGN
状態の場合)、署名を付けるデータをオブジェクトに入れることができます。これは、次のupdate
メソッドのどれか1つを1回または複数回呼び出して実行します。
final void update(byte b)
final void update(byte[] data)
final void update(byte[] data, int off, int len)
署名を付けるデータがすべてSignature
オブジェクトに入るまで、update
メソッドを呼び出します。
署名を生成するには、sign
メソッドの1つを呼び出すだけです。
final byte[] sign()
final int sign(byte[] outbuf, int offset, int len)
最初のメソッドは、署名の結果をバイト配列で返します。2番目のメソッドは、オフセット位置offsetから開始する提供バッファoutbuf内に署名の結果を格納します。lenは、署名に割り当てられたoutbuf内のバイト数です。このメソッドは、実際に格納されたバイト数を返します。
署名のエンコードは、アルゴリズムに依存します。Java暗号化アーキテクチャでのASN.1エンコードの使用の詳細は、Javaセキュリティ標準アルゴリズム名を参照してください。
sign
メソッドを呼び出すと、signature (署名)オブジェクトは、initSign
を呼び出して最初に署名用に初期化されたときの状態にリセットされます。つまり、オブジェクトをリセットするので、必要であればupdate
とsign
を新たに呼び出して、同じ秘密キーを使って別の署名を生成できます。
あるいは異なる秘密キーを指定して、initSign
への新規呼出しを作成したり、(署名を検証するためにSignature
オブジェクトを初期化する目的で) initVerify
への新規呼出しを作成したりできます。
Signatureオブジェクトによる検証
Signature
オブジェクトを検証用に初期化した場合(VERIFY
状態にある場合)は、指定の署名が、それに関連したデータの実際の認証署名であるかどうかを検証できます。この処理を開始するには、検証データ(署名自体ではない)をオブジェクトに入れます。データは、次のいずれかのupdate
メソッドを呼び出すことによってオブジェクトに渡されます。
final void update(byte b)
final void update(byte[] data)
final void update(byte[] data, int off, int len)
検証するデータがすべてSignature
オブジェクトに入るまで、update
メソッドを呼び出します。これで、次のいずれかのverify
メソッドを呼び出して署名を検証できます。
final boolean verify(byte[] signature)
final boolean verify(byte[] signature, int offset, int length)
引数は、署名を含むバイト配列である必要があります。このバイト配列は、前にいずれかのsign
メソッドの呼出しによって返された署名バイトを保持します。
verify
メソッドは、エンコードされた署名がupdate
メソッドに入れたデータの認証署名かどうかを示すboolean
を返します。
verify
メソッドを呼び出すと、signature (署名)オブジェクトは、initVerify
を呼び出して検証用に初期化されたときの状態にリセットされます。つまり、オブジェクトをリセットするので、initVerify
への呼出しで指定した公開キーを持つ識別の別の署名を検証できます。
あるいは異なる非公開キーを指定し、(異なるエンティティの署名を検証するためにSignature
オブジェクトを初期化する目的で)initVerify
への新規呼出しを作成したり、(署名を生成するためにSignature
オブジェクトを初期化する目的で) initSign
への新規呼出しを作成したりできます。
Cipherクラス
Cipher
クラスは、暗号化および復号化で使用される暗号機能を提供します。暗号化とは、データ(クリアテキストと呼ばれる)およびキーを受け取って、キーを知らないサードパーティにとって無意味なデータ(暗号テキスト)を生成する処理のことです。復号化はその逆で、暗号テキストおよびキーを受け取り、クリアテキストを生成する処理です。
対称暗号化方式と非対称暗号化方式
暗号化には、対称(秘密キーとも呼ばれる)と非対称(または公開キー暗号化方式)という2つの主要な種類があります。対称暗号化方式では、同じ秘密キーがデータの暗号化と復号化の両方を行います。データの機密性を保持するには、キーを非公開にすることが重要となります。一方、非対称暗号化方式は、公開キーと秘密キーのペアを使用してデータを暗号化します。一方のキーで暗号化されたデータは、他方のキーで復号化されます。ユーザーは最初に公開キーと秘密キーのペアを生成し、だれでもアクセスできる信頼できるデータベースに公開キーを発行します。そのユーザーと安全に通信することを希望するユーザーは、取得した公開キーを使用してデータを暗号化します。秘密キーの保有者のみが復号化できます。このスキームでは、秘密キーを機密にすることが重要となります。
RSAなどの非対称アルゴリズムは、通常、対称アルゴリズムよりも大幅に遅くなります。これらのアルゴリズムは、大量データを効率的に保護するように設計されていません。実際には、非対称アルゴリズムは、対称アルゴリズムを初期化するために使用される少量の秘密キーを交換する場合に使用されます。
ストリーム暗号とブロック暗号
ブロックとストリームという2つの主要な種類の暗号があります。ブロック暗号は、ブロック全体を一度に処理します。通常は長いバイト長になります。完全な入力ブロックを作成するのにデータが不足する場合は、データのパディングが必要です。つまり、暗号化の前に、ダミー・バイトを追加して暗号のブロック・サイズの倍数にします。これらのバイトは、復号化の段階で削除されます。パディングは、アプリケーションにより、または「PKCS5PADDING」などのパディング型を使用するように暗号を初期化することによって実行できます。これに対して、ストリーム暗号は入力データを一度に1つの小さい単位(通常はバイト、またはビットの場合もある)で処理します。これにより、暗号は任意の量のデータをパディングを行わずに処理できます。
操作モード
単純なブロック暗号を使用して暗号化する場合、2つの同じプレーン・テキストのブロックは、常に同じ暗号テキストのブロックを生成します。暗号テキストを破ろうとする暗号解読者が繰返しのテキストのブロックに気付くと、簡単に解読されてしまいます。暗号操作モードは、ブロック位置、または他の暗号テキスト・ブロックの値に基づく出力ブロック変更により、暗号テキストを予想しにくくします。最初のブロックには初期値が必要です。この値は、初期化ベクトル(IV)と呼ばれます。IVは暗号化の前に単純にデータを変更するため、ランダムである必要がありますが、必ずしも秘密である必要はありません。CBC (Cipher Block Chaining)、CFB (Cipher Feedback Mode)、OFB (Output Feedback Mode)など、様々なモードがあります。ECB (Electronic Codebook Mode)は、ブロック位置や他の暗号テキスト・ブロックの影響を受けないモードです。ECB暗号テキストは同じプレーン・テキスト/キーを使用する場合は同一になるため、このモードは、通常は暗号化アプリケーションに適していません。使用しないようにしてください。
AESやRSAなどの一部のアルゴリズムでは、異なる長さのキーを使用可能ですが、それ以外(3DESなど)では固定です。長いキーを使用する暗号化は、一般にメッセージ復元に対して抵抗力が強いことを意味します。いつものように、セキュリティと時間という相反するものの間で折合いを付けるため、適切なキーの長さを選択します。
ほとんどのアルゴリズムは、バイナリ・キーを使用します。ほとんどの人は、16進数で表現されていても、長いバイナリの数値を記憶できません。文字のパスワードの方が記憶するのは簡単です。通常、文字のパスワードは少数の文字([a-zA-Z0-9]など)から選択されるため、「パスワードベースの暗号化」(PBE)などのプロトコルは、文字のパスワードを取得して強力なバイナリ・キーを生成するように定義されています。パスワードから暗号化キーを取得しようとする攻撃者のタスク(一般の辞書の単語->値のマッピングが事前に計算されている、いわゆるレインボー・テーブル攻撃または計算済ディクショナリ攻撃による)を非常に時間のかかるものにするために、大半のPBE実装では、乱数への混入(saltと呼ばれる)を行い、計算済テーブルの有用性を低下させます。
関連データによる認証付き暗号化方式(AEAD)などの新しい暗号化モード(Galois/Counter Mode (GCM)など)では、データを暗号化し、同時に結果のメッセージを認証します。結果のAEADタグ(MAC)の計算にAAD (Additional Associated Data)を使用できますが、このAADデータは暗号テキストとして出力されません。たとえば、一部のデータは機密にする必要がありませんが、変更を検出するため、タグ計算に含める必要がある場合があります。タグ計算にAADを含めるには、Cipher.updateAAD()メソッドを使用できます。
GCMモードでのAES暗号の使用
GCMでのAES暗号はAEAD暗号で、非AEAD暗号とは使用パターンが異なります。通常のデータ以外にAADも取ります(AADは暗号化/復号化用のオプションですが、暗号化/復号化を行うデータの前に必ず指定する必要があります)。また、GCMを安全に使用するため、呼出し側ではキーとIVの組合せを再利用して暗号化を行わないでください。つまり、暗号オブジェクトは、暗号化操作のたびに異なるパラメータ・セットで明示的に初期化しなおす必要があります。
例2-2 GCMモードでAES Cipherを使用するサンプル・コード
SecretKey myKey = ...
byte[] myAAD = ...
byte[] plainText = ...
int myTLen = ...
byte[] myIv = ...
GCMParameterSpec myParams = new GCMParameterSpec(myTLen, myIv);
Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
c.init(Cipher.ENCRYPT_MODE, myKey, myParams);
// AAD is optional, if present, it must be supplied before any update/doFinal calls.
c.updateAAD(myAAD); // if AAD is non-null
byte[] cipherText = new byte[c.getOutputSize(plainText.length)];
// conclusion of encryption operation
int actualOutputLen = c.doFinal(plainText, 0, plainText.length, cipherText);
// To decrypt, same AAD and GCM parameters must be supplied
c.init(Cipher.DECRYPT_MODE, myKey, myParams);
c.updateAAD(myAAD);
byte[] recoveredText = c.doFinal(cipherText, 0, actualOutputLen);
// MUST CHANGE IV VALUE if the same key were to be used again for encryption
byte[] newIv = ...;
myParams = new GCMParameterSpec(myTLen, newIv);
Cipherオブジェクトの作成
Cipher
オブジェクトは、Cipher
getInstance() staticファクトリ・メソッドの1つを使用することによって取得されます。プロバイダ実装の要求および獲得方法を参照してください。ここでのアルゴリズム名は、アルゴリズム名だけでなく「変換」を指定する点で、ほかのエンジン・クラスの場合と少し異なります。変換は、指定された入力に対して実行し、なんらかの出力を生成する操作(または操作のセット)を説明する文字列です。変換には、暗号化アルゴリズム(AES
など)の名前が必ず含まれます。これにモードおよびパディング・スキームが続く場合もあります。
変換は、次の書式で記述されます。
- "algorithm/mode/padding"または
- "algorithm"
たとえば、次は有効な変換です。
"AES/CBC/PKCS5Padding"
"AES"
変換名だけを指定すると、要求された変換の実装がその環境で使用可能かどうかをシステムが判別し、複数の実装が存在する場合には優先度の高い実装が存在することを返します。
変換名とパッケージ・プロバイダの両方を指定すると、システムは要求されたパッケージ内に要求された変換の実装が存在するかどうかを判別し、存在しない場合には例外をスローします。
アルゴリズム、モードおよびパディングを完全に指定した変換を使用することをお薦めします。そうしないと、プロバイダはデフォルトを使用します。たとえば、SunJCEプロバイダとSunPKCS11プロバイダは、多くの対称暗号でECBをデフォルト・モードとして、PKCS5Paddingをデフォルト・パディングとして使用します。
このため、SunJCE
プロバイダでは、
Cipher c1 = Cipher.getInstance("AES/ECB/PKCS5Padding");
および
Cipher c1 = Cipher.getInstance("AES");
という文は、等価になります。
注意:
ECBモードは最も簡単に使用できるブロック暗号モードであり、JDKではデフォルトになっています。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 (ファイナル)整数の定数として定義されます。モードは、シンボリック名で参照できます。次に、各モードのシンボリック名および目的の説明を示します。
- ENCRYPT_MODE
- データの暗号化。
- DECRYPT_MODE
- データの復号化。
- WRAP_MODE
- 安全に転送するために
java.security.Key
をラッピングする。 - UNWRAP_MODE
- ラッピングされたキーを
java.security.Key
オブジェクトにアンラッピングする。
Cipher初期化メソッドは、それぞれ操作モード・パラメータ(opmode
)を取り、そのモード用のCipherオブジェクトを初期化します。その他のパラメータには、キー(key
)またはキーを含む証明書(certificate
)、アルゴリズム・パラメータ(params
)、および乱数の発生源(random
)などがあります。
Cipherオブジェクトを初期化する場合、次のいずれかのinit
メソッドを呼び出します。
public void init(int opmode, Key key);
public void init(int opmode, Certificate certificate);
public void init(int opmode, Key key, SecureRandom random);
public void init(int opmode, Certificate certificate,
SecureRandom random);
public void init(int opmode, Key key,
AlgorithmParameterSpec params);
public void init(int opmode, Key key,
AlgorithmParameterSpec params, SecureRandom random);
public void init(int opmode, Key key,
AlgorithmParameters params);
public void init(int opmode, Key key,
AlgorithmParameters params, SecureRandom random);
初期化ベクトルなどのパラメータを必要とするCipherオブジェクトを暗号化用に初期化する場合、init
メソッドにパラメータを何も指定しないと、ランダム・パラメータを生成するか、プロバイダ固有のパラメータ・セット(デフォルト)を使用することにより、基盤となる暗号実装が必須パラメータを提供すると見なされます。
ただし、パラメータを必要とするCipherオブジェクトを復号化用に初期化する場合、init
メソッドにパラメータを何も指定しないと、使用したinit
メソッドに応じてInvalidKeyException
またはInvalidAlgorithmParameterException
例外が発生します。
アルゴリズム・パラメータの管理を参照してください。
復号化には、暗号化に使用したのと同じパラメータを使用する必要があります。
Cipherオブジェクトを初期化すると、それまでに獲得した状態がすべて失われることに留意してください。つまり、Cipherを初期化することは、そのCipherの新規インスタンスを作成して初期化することと等価です。たとえば、指定されたキーで復号化を行うためにCipherを初期化してから、暗号化を行うために初期化すると、復号化モードで獲得した状態はすべて失われます。
データの暗号化および復号化
データの暗号化または復号化は、1つのステップで実行(単一部分操作)することも、複数のステップで実行(複数部分操作)することもできます。データの長さが不明な場合、またはデータが長すぎて一度にメモリーに格納できない場合に、複数部分操作は有用です。
1つのステップでデータの暗号化または復号化を行う場合、次のいずれかのdoFinal
メソッドを呼び出します。
public byte[] doFinal(byte[] input);
public byte[] doFinal(byte[] input, int inputOffset, int inputLen);
public int doFinal(byte[] input, int inputOffset,
int inputLen, byte[] output);
public int doFinal(byte[] input, int inputOffset,
int inputLen, byte[] output, int outputOffset)
複数のステップでデータの暗号化または復号化を行うには、次のいずれかのupdate
メソッドを呼び出します。
public byte[] update(byte[] input);
public byte[] update(byte[] input, int inputOffset, int inputLen);
public int update(byte[] input, int inputOffset, int inputLen,
byte[] output);
public int update(byte[] input, int inputOffset, int inputLen,
byte[] output, int outputOffset)
複数部分操作は、前述のdoFinal
メソッドのいずれか(最後のステップで入力データが残されている場合)、または次のdoFinal
メソッドのいずれか(最後のステップで入力データが残っていない場合)を使用して終了させます。
public byte[] doFinal();
public int doFinal(byte[] output, int outputOffset);
指定された変換の一部としてパディング(またはアンパディング)が要求された場合、すべてのdoFinal
メソッドで、必要なパディング(またはアンパディング)操作がすべて処理されます。
doFinal
を呼び出すと、Cipherオブジェクトはinit
を呼び出して初期化した時の状態にリセットされます。つまり、Cipherオブジェクトはリセットされて、さらにデータを暗号化または復号化するために使用できます(init
の呼出しで指定された操作モードにより異なる)。
キーのラッピングとアンラッピング
キーをラッピングすると、ある場所から別の場所へ安全に転送できます。
wrap/unwrap
APIはキー・オブジェクトに対して直接機能するため、このAPIを使用するとコードの記述が容易になります。次のメソッドを使用すると、ハードウェア・ベースのキーの安全な転送も可能になります。
Keyをwrapする場合、まずWRAP_MODEのCipherオブジェクトを初期化し、次のメソッドを呼び出します。
public final byte[] wrap(Key key);
ラップされたキーのバイト(wrap
を呼び出した結果)を、そのラップを解除する他のユーザーに提供する場合、受信者がunwrap
を実行するのに必要な、次の追加情報も送信する必要があります。
- キー・アルゴリズムの名前
- ラップされたキーの型(
Cipher.SECRET_KEY
、Cipher.PRIVATE_KEY
またはCipher.PUBLIC_KEY
のいずれか)
キー・アルゴリズム名は、次に示すようにKeyインタフェースからgetAlgorithm
メソッドを呼び出すことにより確認できます。
public String getAlgorithm();
wrap
への呼出しにより返されたバイトのラップを解除するには、UNWRAP_MODEのCipherオブジェクトを初期化してから、次を呼び出します。
public final Key unwrap(byte[] wrappedKey,
String wrappedKeyAlgorithm,
int wrappedKeyType));
ここで、wrappedKey
はラップへの呼出しにより返されたバイトを、wrappedKeyAlgorithm
はラップされたキーに関連付けられたアルゴリズムを、wrappedKeyType
はラップされたキーの型をそれぞれ指します。これは、Cipher.SECRET_KEY
、Cipher.PRIVATE_KEY
、Cipher.PUBLIC_KEY
のいずれかである必要があります。
アルゴリズム・パラメータの管理
基盤となるCipher実装により使用されるパラメータ(アプリケーションによりinit
メソッドに明示的に渡されたか、基盤となる実装自体により生成されたパラメータ)は、getParameters
メソッドを呼び出すことによりCipherオブジェクトから取得できます。このメソッドは、パラメータをjava.security.AlgorithmParameters
オブジェクト(または、パラメータが使用されない場合はnull
)として返します。パラメータが初期化ベクトル(IV)の場合には、getIV
メソッドを呼び出すことによってもパラメータを取得できます。
次の例では、パスワードベース暗号化(PBE)を実装するCipherオブジェクトを、パラメータを使用せずにキーのみを使用して初期化します。ただし、選択されたパスワードベース暗号化用に選択されたアルゴリズムは、saltおよびiteration countという2つのパラメータを必要とします。これらは、基盤となるアルゴリズム実装自体により生成されます。アプリケーションは、生成されたパラメータをCipherオブジェクトから取得できます。例2-3を参照してください。
復号化には、暗号化に使用したのと同じパラメータを使用する必要があります。これらは、それらのエンコーディングからインスタンス化でき、対応するCipherオブジェクトを復号化用に初期化する際に使用できます。例2-4を参照してください。
Cipherオブジェクトの初期化時にパラメータを一切指定せず、基盤となる実装がいずれかのパラメータを使用するかどうか不明な場合、CipherオブジェクトのgetParameters
メソッドを呼び出して、返される値をチェックするだけで確認できます。戻り値がnull
の場合、パラメータが使用されなかったことを示します。
SunJCE
プロバイダにより実装される次の暗号アルゴリズムは、パラメータを使用します。
- AES、DES-EDEおよびBlowfishは、フィードバック(つまりCBC、CFB、OFBまたはPCBC)モードでの使用時に、初期化ベクトル(IV)を使用します。
javax.crypto.spec.IvParameterSpec
クラスは、指定されたIVでのCipherオブジェクトの初期化に使用できます。また、CTRモードとGCMモードでは、IVが必要になります。 - PBE Cipherアルゴリズムは、saltと繰返し処理の回数を含む、一連のパラメータを使用します。
javax.crypto.spec.PBEParameterSpec
クラスを使用して、saltおよび繰返し処理回数を指定してPBEアルゴリズムを実装するCipherオブジェクトを初期化できます(例: PBEWithHmacSHA256AndAES_256)。
SealedObjectクラスのクラスを使用する場合、復号化操作で使用するアルゴリズム・パラメータの格納または転送について心配する必要はありません。このクラスは、シール(暗号化)に使用されるパラメータを暗号化されたオブジェクト・コンテンツに添付します。また、アンシール(復号化)でも同じパラメータを使用します。
例2-3 Cipherオブジェクトからパラメータを取得するサンプル・コード
アプリケーションは、暗号化用の生成されたパラメータを次の方法でCipherオブジェクトから取得できます。
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ベースのその他のクラス
Cipher
を内部的に使用して共通の暗号使用への簡単なアクセスを提供する、いくつかのヘルパー・クラスがあります。
Cipher Streamクラス
CipherInputStream
クラスとCipherOutputStream
クラスは、Cipherストリーム・クラスです。
CipherInputStreamクラス
このクラスは、通過するデータの暗号化または復号化を行うFilterInputStream
です。これは、InputStream
で構成されています。CipherInputStreamは、Cipherオブジェクトの挿入先の、セキュリティ保護された入力ストリームを表します。CipherInputStreamのread
メソッドは、基盤となるInputStreamから読み取られ、埋め込まれたCipherオブジェクトにより追加処理されたデータを返します。Cipherオブジェクトは、CipherInputStreamで使用する前に完全に初期化する必要があります。
たとえば、埋め込まれたCipherが復号化用に初期化されている場合、CipherInputStreamは基盤となるInputStreamから読み込んだデータの復号化を試みてから、データをアプリケーションに返します。
このクラスは、上位クラスjava.io.FilterInputStream
およびjava.io.InputStream
のセマンティックス(特にエラーに関するセマンティックス)に厳密に準拠します。このクラスは、上位クラスで指定されたメソッドを正確に保持し、それらすべてをオーバーライドします。このため、埋め込まれた暗号によるデータの追加処理が可能になります。さらに、このクラスは、上位クラスがスローしない例外をすべてキャッチします。特に、skip(long)
メソッドは、Cipherに処理されたデータのみを無視します。
このクラスを使用するプログラマにとって、このクラスで定義またはオーバーライドされていないメソッド(上位クラスのいずれかに後で追加された新規メソッドまたはコンストラクタ)を使用しないようにすることは重要です。これらのメソッドの設計と実装は、CipherInputStreamへのセキュリティ面の影響を考慮に入れていないためです。例2-5でその使用方法を参照してください。cipher1
が暗号化用に初期化されている場合を考えてみましょう。このプログラムは、ファイル/tmp/a.txt
から内容を読み取って暗号化し、結果(暗号化されたバイト)を/tmp/b.txt
に格納します。
例2-6では、CipherInputStream
およびFileInputStream
の複数のインスタンスを簡単に接続する方法を示します。この例では、cipher1
およびcipher2
が、それぞれ暗号化および復号化用に(対応するキーを使用して)初期化されているものとします。このプログラムは、ファイル/tmp/a.txt
の内容を/tmp/b.txt
にコピーします。ただし、/tmp/a.txt
からの読取り時に、最初に内容の暗号化、次に復号化が行われます。実際のところ、このプログラムはテキストを暗号化した後、すぐに復号化を行うため、CipherInputStreams
のチェーンをわかりやすく示す以外は特に有用なものではありません。
CipherInputStream
の読取りメソッドは、基盤となる暗号からデータが返されるまでブロックします。ブロック暗号が使用される場合、基盤となるInputStream
から暗号テキストの完全なブロックが取得される必要があります。
例2-5 CipherInputStreamとFileInputStreamを使用するサンプル・コード
次のコードは、暗号および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();
}
SealedObjectクラス
プログラマは、このクラスを使用してオブジェクトを生成し、暗号化アルゴリズムを利用してその機密性を保護することができます。
java.io.Serializable
インタフェースを実装するオブジェクトが指定された場合、元のオブジェクトを直列化形式(ディープ・コピー)でカプセル化するSealedObject
を作成し、AESなどの暗号化アルゴリズムを使用して、直列化された内容をシール(暗号化)することにより、機密性を保護できます。そのあと、暗号化された内容の暗号解読(適正な暗号解読キーを使用)、および直列化解除を行うことにより、元のオブジェクトを復元できます。
一般的な使用法を、次のコード例に示します。オブジェクトをシールする場合、シール対象のオブジェクトからSealedObject
を作成し、直列化されたオブジェクト内容を暗号化する、完全に初期化されたCipher
オブジェクトを作成します。この例では、文字列This is a secretがAESアルゴリズムを使用してシールされます。シール操作に使用されるすべてのアルゴリズム・パラメータは、SealedObject
の内部に格納されることに留意してください。
// create Cipher object
// NOTE: sKey is assumed to refer to an already-generated
// secret AES key.
Cipher c = Cipher.getInstance("AES");
c.init(Cipher.ENCRYPT_MODE, sKey);
// do the sealing
SealedObject so = new SealedObject("This is a secret", c);
シールされた元のオブジェクトは、次の異なる2つの方法で復元可能です。
- 厳密に同一のアルゴリズム、キー、パディング・スキームなどで初期化され、オブジェクトのシールに使用された
Cipher
オブジェクトを使用する方法。c.init(Cipher.DECRYPT_MODE, sKey); try { String s = (String)so.getObject(c); } catch (Exception e) { // do something };
この方法には、暗号解読キーに関する知識がなくても、シールされたオブジェクトのアンシールを実行できるという利点があります。たとえば、あるパーティが暗号オブジェクトを必須の暗号解読キーを使って初期化したあとで、別のパーティに渡すと、そのパーティはシールされたオブジェクトをアンシールできます。
- 適切な復号化キーを使用する方法(AESは対称暗号化アルゴリズムであるため、シールとアンシールに同じキーを使用できる)
try { String s = (String)so.getObject(sKey); } catch (Exception e) { // do something };
この方法では、
getObject
メソッドは、適切な暗号解読アルゴリズム用の暗号オブジェクトを作成し、シール済みのオブジェクトに格納された特定の暗号解読キーおよびアルゴリズム・パラメータ(存在する場合)を使用してそれを初期化します。この方法の利点は、オブジェクトをアンシールするパーティが、オブジェクトのシールに使用したパラメータ(IVなど)を追跡する必要がないことです。
Macクラス
MessageDigest
と同様に、メッセージ認証コード(MAC)は、送信された情報や信頼できないメディアに保存されている情報の整合性をチェックする方法を提供しますが、秘密キーが計算に含まれます。
適切なキーを持つ人だけが、受信したメッセージを検証できます。一般に、メッセージ認証コードは、秘密キーを共有する2つのパーティ間で送信される情報の有効性を検証する場合に使用されます。
暗号化ハッシュ機能に基づくMACメカニズムは、HMACと呼ばれます。HMACは、秘密共有キーと組み合せて、SHA-256などの任意の暗号化ハッシュ機能で使用できます。
Mac
クラスは、メッセージ認証コード(MAC)の機能を提供します。HMAC-SHA256の例を参照してください。
Macオブジェクトの作成
Mac
オブジェクトは、Mac
の getInstance() staticファクトリ・メソッドの1つを使用することによって取得されます。プロバイダ実装の要求および獲得方法を参照してください。
Macオブジェクトの初期化
Macオブジェクトは、常に(秘密)キーを使って初期化されます。またオプションとして、基盤となるMACアルゴリズムによっては、パラメータ・セットを使って初期化することもできます。
Macオブジェクトを初期化する場合、次のいずれかのinitメソッドを呼び出します。
public void init(Key key);
public void init(Key key, AlgorithmParameterSpec params);
javax.crypto.SecretKey
インタフェースを実装する任意の(秘密)キー・オブジェクトを使って、Macオブジェクトを初期化できます。これは、javax.crypto.KeyGenerator.generateKey()が返すオブジェクト、またはjavax.crypto.KeyAgreement.generateSecret()などが返すキー協定プロトコルの結果生成されるオブジェクト、またはjavax.crypto.spec.SecretKeySpec
のインスタンスです。
MACアルゴリズムの中には、Macオブジェクトの初期化に使用される(秘密)キー・オブジェクトに関連付けられた(秘密)キー・アルゴリズムが重要ではないものがあります(SunJCE
プロバイダのHMAC-MD5およびHMAC-SHA1実装の場合)。ただし、それ以外の場合、(秘密)キー・アルゴリズムは重要であり、(秘密)キー・オブジェクトが不適切な(秘密)キー・アルゴリズムで使用されると、InvalidKeyException
がスローされます。
MACの計算
MACは、1つのステップで計算(単一部分操作)することも、複数のステップで計算(複数部分操作)することもできます。データの長さが不明な場合、またはデータが長すぎて一度にメモリーに格納できない場合に、複数部分操作は有用です。
あるデータのMACを1回のステップで計算するには、次のdoFinalメソッドを呼び出します。
public byte[] doFinal(byte[] input);
複数のステップでデータのMACを計算するには、次のいずれかのupdate
メソッドを呼び出します。
public void update(byte input);
public void update(byte[] input);
public void update(byte[] input, int inputOffset, int inputLen);
複数部分操作は、前述のdoFinal
メソッド(最後のステップで入力データが残されている場合)、または次のdoFinal
メソッドのいずれか(最後のステップで入力データが残っていない場合)を使用して終了させます。
public byte[] doFinal();
public void doFinal(byte[] output, int outOffset);
Keyインタフェース
java.security.Key
インタフェースは、すべての不透明なキーに関するトップ・レベルのインタフェースです。すべての不透明なキー・オブジェクトが共有する機能を定義します。
ここまでは、キーとは何か、キーはどのように生成および表現されるかについて詳細は説明せずに、JCAの高レベルの使用に重点を置いてきました。ここではキーに注意を向けます。
「不透明な」キーの表現では、キーを構成するキー・データに直接アクセスできません。つまり、「不透明」さにより、キーへのアクセスが、Key
インタフェースによって定義されるgetAlgorithm
、getFormat
、およびgetEncoded
。
これは、対応するKeySpecインタフェース(KeySpecインタフェースを参照)で定義されているget
メソッドのいずれかによって各キー・データ値に個別にアクセスできる、透明な表現とは対照的です。
不透明なキーはすべて、次の3つの特性を持ちます。
- アルゴリズム
- そのキーのキー・アルゴリズムです。キー・アルゴリズムとは、通常の場合、暗号化アルゴリズムか非対称操作アルゴリズム(
AES
、DSA
またはRSA
など)のことであり、そのようなアルゴリズムや関連するアルゴリズム(SHA256withRSA
など)と協調動作します。キーのアルゴリズムの名前は、次のメソッドを使用して取得されます。String getAlgorithm()
- エンコードされた形式
- キーの外部エンコード形式は、キーを別の組織に転送する場合など、キーの標準表示がJava仮想マシンの外部で必要なときに使います。キーは標準形式(X.509やPKCS8など)に従ってエンコードされ、次のメソッドを使用して返されます。
byte[] getEncoded()
- フォーマット
- エンコードされたキーのフォーマット名です。次のメソッドから返されます。
String getFormat()
キーは一般的に、KeyGeneratorクラスやKeyPairGeneratorクラスなどのキー・ジェネレータ、証明書、KeyFactoryを使用するキー仕様(KeySpecインタフェースを参照)、またはキー管理で使用するキーストア・データベースにアクセスするKeystoreの実装から獲得します。KeyFactoryを使って、アルゴリズム依存型の方法でエンコードされたキーを解析することが可能です。
また、CertificateFactoryを使って、認証を解析することも可能です。
次に、java.security.interfaces
およびjavax.crypto.interfaces
パッケージ内のKey
インタフェースを拡張するインタフェースのリストを示します。
PublicKeyインタフェースとPrivateKeyインタフェース
PublicKey
およびPrivateKey
インタフェースはどちらもKey
インタフェースを拡張しますが、これらはメソッドを使わないインタフェースで、型の安全性および型の識別に使用します。
KeyPairクラス
KeyPair
クラスはキーのペア(公開キーと秘密キー)の簡単なホルダーです。
これには2つのpublicメソッドがあります。1つは秘密キーを返し、もう1つは公開キーを返します。
PrivateKey getPrivate()
PublicKey getPublic()
キー仕様のインタフェースおよびクラス
Key
オブジェクトとキー仕様(KeySpec
)は、キー・データの異なる2つの表現です。Cipher
はKey
オブジェクトを使用して暗号化アルゴリズムを初期化しますが、伝送またはストレージのためにキーを移植性の高い形式に変換する必要がある場合があります。
キーの透明な表現とは、対応する仕様クラスで定義されたget
メソッドの1つを使って、各キー・データ値に個々にアクセスできるということです。たとえば、DSAPrivateKeySpec
は、getX
、getP
、getQ
、およびgetG
メソッドを定義し、秘密キーx
およびキーの計算に使用するDSAアルゴリズムのパラメータ(素数のp
、サブ素数のq
、およびベースのg
)にアクセスします。キーがハードウェア・デバイス上に格納されている場合は、そのキー仕様には、デバイス上のキーの識別を助ける情報が含まれていることがあります。
この表現と対照的なのが、Keyインタフェースのインタフェースによって定義されるような、不透明な表現です。不透明なキーの表現では、キー要素フィールドに直接アクセスできません。つまり、不透明な表現では、キーへのアクセスが、Key
インタフェースによって定義されるgetAlgorithm
、getFormat
およびgetEncoded
の3つのメソッドに制限されます。
キーは、アルゴリズムに固有の方法か、またはアルゴリズムに依存しないエンコーディング形式(ASN.1など)で指定できます。たとえば、DSA秘密キーは、秘密キーのコンポーネントx
、p
、q
、およびg
によって指定するか(DSAPrivateKeySpec
を参照)、または、秘密キーのDERエンコーディングを使って指定することが可能です(PKCS8EncodedKeySpec
を参照)。
KeyFactoryクラスおよびSecretKeyFactoryクラスのクラスは、不透明なキーの表現と透明なキーの表現の間(つまり、Key
とKeySpec
の間。操作が可能であると想定)で変換を行うために使用できます。(たとえば、スマート・カード上の秘密キーは、カードから取り出せない場合があります。そのようなKey
は変換不可能です。)
次のセクションでは、java.security.spec
パッケージ内に含まれるキー仕様のインタフェースおよびクラスについて説明します。
KeySpecインタフェース
このインタフェースには、メソッドまたは定数が含まれていません。このインタフェースの唯一の目的は、すべてのキー仕様をグループ化することおよびそれらのグループに安全な型を提供することです。すべてのキー仕様で、このインタフェースを実装する必要があります。
EncodedKeySpecクラス
この抽象クラス(KeySpecインタフェースのインタフェースを実装する)は、エンコードされた形式の公開キーまたは秘密キーを表します。そのgetEncodedメソッドは、次のエンコードされたキーを返します。
abstract byte[] getEncoded();
このクラスのgetFormatメソッドは、次のエンコード形式の名前を返します。
abstract String getFormat();
固定実装PKCS8EncodedKeySpec
およびX509EncodedKeySpec
については、次のセクションを参照してください。
ジェネレータとファクトリ
JavaおよびJCA APIをはじめて使用する人は、ジェネレータとファクトリの区別がつかない場合があります。
ジェネレータは、新しいオブジェクトを生成する場合に使用されます。ジェネレータは、アルゴリズム依存またはアルゴリズム非依存で初期化できます。たとえば、Diffie-Hellman (DH)キー・ペアを作成するために、アプリケーションは必要なPおよびG値を指定できます。または、ジェネレータを適切なキーの長さで単純に初期化できます。ジェネレータは適切なPおよびG値を選択します。どちらの場合も、ジェネレータはパラメータに基づいて新しいキーを生成します。
一方、ファクトリは、既存のオブジェクト型から別のオブジェクト型へデータを変換する場合に使用されます。たとえば、アプリケーションで、DH秘密キーのコンポーネントが使用可能になっている場合があります。アプリケーションは、それらをKeySpecインタフェースとしてパッケージ化できますが、KeyAgreement
オブジェクトで使用できるようPrivateKeyオブジェクトに変換する必要があります。または逆の変換が必要な場合もあります。または、それらに証明書のバイト配列があるが、CertificateFactory
を使用して、X509Certificate
オブジェクトに変換する必要がある場合があります。アプリケーションは、変換を行うためにファクトリ・オブジェクトを使用します。
KeyFactoryクラス
KeyFactory
クラスは、不透明な暗号化Keyインタフェースとキー仕様のインタフェースおよびクラス(背後のキー・データの透明な表現)との間の変換を実行するために設計されたエンジン・クラスとアルゴリズムです。
キー・ファクトリは、双方向性があります。つまり、これによって、与えられたキー仕様(キーのデータ)から不透明なキー・オブジェクトを構築することも、キー・オブジェクトの背後のキー・データを適切な形式で取得することもできます。
同一のキーに対して、複数の互換性のあるキー仕様を存在させることもできます。たとえば、DSA公開キーは、コンポーネントy
、p
、q
およびg
によって指定することも(java.security.spec.DSAPublicKeySpec
を参照)、X.509標準に従ってDERエンコードを使用して指定することも(X509EncodedKeySpecクラスを参照)できます。
キー・ファクトリは、互換性のあるキー仕様間の変換に使用できます。互換性のあるキー仕様間の変換では、キーの構文解析が行われます。たとえば、X509EncodedKeySpec
をDSAPublicKeySpec
に変換する場合は、基本的にエンコードされたキーをコンポーネント単位に解析します。例については、「キー仕様およびKeyFactoryを使った署名の生成と検証」のセクションの最後を参照してください。
KeyFactoryオブジェクトの作成
KeyFactory
オブジェクトは、KeyFactory
のgetInstance() staticファクトリ・メソッドの1つを使用することによって取得されます。プロバイダ実装の要求および獲得方法を参照してください。
キー仕様とKeyオブジェクト間の変換
公開キー用のキー仕様がある場合は、generatePublic
メソッドを使って、その仕様から不透明なPublicKey
オブジェクトを取得できます。
PublicKey generatePublic(KeySpec keySpec)
同様に、秘密キー用のキー仕様がある場合は、generatePrivate
メソッドを使って、その仕様から不透明なPrivateKey
オブジェクトを取得できます。
PrivateKey generatePrivate(KeySpec keySpec)
Keyオブジェクトとキー仕様間の変換
Key
オブジェクトがある場合は、getKeySpec
メソッドの呼出しによって、対応するキー仕様オブジェクトを取得できます。
KeySpec getKeySpec(Key key, Class keySpec)
keySpec
は、キーのデータが返されるべき仕様クラスを識別します。たとえば、DSAPublicKeySpec.class
は、キーのデータがDSAPublicKeySpec
クラスのインスタンスに返される必要があることを指示します。キー仕様およびKeyFactoryを使った署名の生成と検証を参照してください。
SecretKeyFactoryクラス
SecretKeyFactory
クラスは、秘密キーのファクトリを表します。KeyFactoryクラス(KeyFactoryクラスを参照)とは異なり、javax.crypto.SecretKeyFactory
オブジェクトは秘密(対称)キーのみを処理し、一方、java.security.KeyFactory
オブジェクトはキー・ペアの公開キーおよび秘密キー・コンポーネントを処理します。
キー・ファクトリは、java.security.Key
型の不透明な暗号キーであるKeyインタフェースと、適切な形式の基本のキー・データの透明な表現であるキー仕様のインタフェースおよびクラスとの間の変換を行うために使用します。
java.security.Key
型のオブジェクト(java.security.PublicKey
、java.security.PrivateKey
、およびjavax.crypto.SecretKey
はそのサブクラス)は、その実装方法が不明であるため、不透明なキー・オブジェクトになります。基盤となる実装はプロバイダ依存であるため、ソフトウェア・ベースにもハードウェア・ベースにもできます。キー・ファクトリを使用すると、プロバイダは独自の暗号化キー実装を提供できるようになります。
たとえば、公開値y
、素数モジュラスp
、ベースg
で構成されるDiffie-Hellman公開キーのキー仕様を保持しており、同じ仕様を別のプロバイダのDiffie-Hellmanキー・ファクトリに送る場合、生成されるPublicKey
オブジェクトは大概、異なる基盤実装を保持するようになります。
プロバイダは、秘密キー・ファクトリがサポートするキー仕様をドキュメント化する必要があります。たとえば、SunJCE
プロバイダにより提供されるDESキーのSecretKeyFactory
は、DESKeySpec
をDESキーの透明表現としてサポートします。また、DES-EDEキーのSecretKeyFactory
はDESedeKeySpec
をDES-EDEキーの透明表現として、PBEのSecretKeyFactory
はPBEKeySpec
を基盤となるパスワードの透明表現として、それぞれサポートします。
次の例は、SecretKeyFactory
を使用して秘密キーデータをSecretKey
オブジェクトに変換する方法を示します。これは、以降のCipher
操作で使用できます。
// Note the following bytes are not realistic secret key data
// bytes but are simply supplied as an illustration of using data
// bytes (key material) you already have to build a DESedeKeySpec.
byte[] desEdeKeyData = getKeyData();
DESedeKeySpec desEdeKeySpec = new DESedeKeySpec(desEdeKeyData);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
SecretKey secretKey = keyFactory.generateSecret(desEdeKeySpec);
この場合、SecretKey
の基盤実装は、KeyFactory
のプロバイダに基づきます。
別の方法として、プロバイダに依存せずに、同じキー・データから等価な機能を持つSecretKey
オブジェクトを作成することも可能です。その場合、javax.crypto.SecretKey
インタフェースを実装するjavax.crypto.spec.SecretKeySpec
クラスを使用します。
byte[] aesKeyData = getKeyData();
SecretKeySpec secretKey = new SecretKeySpec(aesKeyData, "AES");
SecretKeyFactoryオブジェクトの作成
SecretKeyFactoryオブジェクトは、SecretKeyFactoryのgetInstance() staticファクトリ・メソッドの1つを使用することによって取得されます。プロバイダ実装の要求および獲得方法を参照してください。
キー仕様とSecret Keyオブジェクト間の変換
秘密キー用のキー仕様がある場合は、generateSecretメソッドを使って、その仕様から不透明なSecretKeyオブジェクトを取得できます。
SecretKey generateSecret(KeySpec keySpec)
Secret Keyオブジェクトとキー仕様間の変換
SecretKeyオブジェクトがある場合は、getKeySpecメソッドの呼出しによって、対応するキー仕様オブジェクトを取得できます。
KeySpec getKeySpec(Key key, Class keySpec)
keySpecは、キーのデータが返されるべき仕様クラスを識別します。たとえば、DESKeySpec.class
は、キーのデータがDESKeySpecクラスのインスタンスに返される必要があることを指示します。
KeyPairGeneratorクラス
KeyPairGenerator
クラスは、エンジン・クラス(エンジン・クラスとアルゴリズムを参照)であり、公開キーと秘密キーのペアの生成に使用します。
キーのペアの生成方法には、アルゴリズム独立型とアルゴリズム固有型の2つがあります。この2つの唯一の相違点は、オブジェクトの初期化にあります。
次に記述するメソッドの呼出し例は、キーのペアの生成を参照してください。
KeyPairGeneratorの作成
すべてのキーのペアは、最初にKeyPairGenerator
を使って生成します。KeyPairGenerator
オブジェクトは、KeyPairGenerator
の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
オブジェクトは、KeyGenerator
のgetInstance() staticファクトリ・メソッドの1つを使用することによって取得されます。プロバイダ実装の要求および獲得方法を参照してください。
KeyGeneratorオブジェクトの初期化
特定の対称キー・アルゴリズムのキー・ジェネレータは、そのアルゴリズムで使用可能な対称キーを作成します。また、生成されたキーに、アルゴリズム固有のパラメータ(存在する場合)を関連付けます。
キーの生成方法には、アルゴリズム独立型とアルゴリズム固有型の2つがあります。この2つの唯一の相違点は、オブジェクトの初期化にあります。
- アルゴリズムに依存しない初期化
すべてのキー・ジェネレータが、キー・サイズおよび乱数発生の元の概念を共有します。普遍的に共有されるこれら2つの引数の型をとる
init
メソッドが存在します。また、keysize
引数のみをとり、システムにより提供される乱数の発生源を使用するものや、乱数の発生源だけをとるものも存在します。public void init(SecureRandom random); public void init(int keysize); public void init(int keysize, SecureRandom random);
前述のアルゴリズム独立型
init
メソッドを呼び出すときは、その他のパラメータは指定しないため、生成されたキーに関連付けられるアルゴリズム固有のパラメータが存在する場合、そのパラメータの処理は、プロバイダによって異なります。 - アルゴリズム固有の初期化
アルゴリズム固有のパラメータのセットがすでに存在する状況では、
AlgorithmParameterSpec
引数を取る2つのinit
メソッドが存在します。このうちの一方はSecureRandom
引数も取りますが、他方では、乱数の発生源はシステムによって提供されます。public void init(AlgorithmParameterSpec params); public void init(AlgorithmParameterSpec params, SecureRandom random);
クライアントが(init
メソッドの呼出しによって) KeyGeneratorを明示的に初期化しない場合は、各プロバイダがデフォルトの初期化を提供(および文書化)する必要があります。
キーの作成
次のメソッドにより、秘密キーが生成されます。
public SecretKey generateKey();
KeyAgreementクラス
キー協定とは、複数のパーティが秘密情報を交換しなくても同じ暗号化キーを確立可能なプロトコルを指します。
各パーティは、秘密キーによってキー協定オブジェクトを初期化し、通信に参加する各パーティの公開キーを入力します。ほとんどの場合は2つのパーティのみですが、Diffie-Hellmanなどのアルゴリズムでは3つ以上のパーティの参加が可能です。公開キーがすべて入力されると、各KeyAgreement
オブジェクトは同じキーを生成(合意)します。
KeyAgreementクラスは、キー協定プロトコルの機能を提供します。共有の秘密の確立に関係するキーは、キー・ジェネレータのいずれか(KeyPairGenerator
またはKeyGenerator
)かKeyFactory
によって、またはキー協定プロトコルの中間フェーズの結果として作成されます。
KeyAgreementオブジェクトの作成
キー協定に関係する各パーティは、KeyAgreementオブジェクトを作成する必要があります。KeyAgreement
オブジェクトは、KeyAgreement
のgetInstance() staticファクトリ・メソッドの1つを使用することによって取得されます。プロバイダ実装の要求および獲得方法を参照してください。
KeyAgreementオブジェクトの初期化
非公開情報を使用してKeyAgreementオブジェクトを初期化できます。Diffie-Hellmanの場合、Diffie-Hellman秘密キーを使用して初期化します。補足的な初期化情報には、乱数の発生源、アルゴリズム・パラメータのセットが含まれます。要求されたキー協定アルゴリズムで、アルゴリズム・パラメータを指定する必要があり、またKeyAgreementオブジェクトの初期化にパラメータではなくキーだけが提供される場合、必須のアルゴリズム・パラメータをキーに含める必要があります。たとえば、Diffie-Hellmanアルゴリズムは、素数モジュラスp
およびベース・ジェネレータg
をパラメータとして使用します。
KeyAgreementオブジェクトを初期化する場合、次のいずれかのinit
メソッドを呼び出します。
public void init(Key key);
public void init(Key key, SecureRandom random);
public void init(Key key, AlgorithmParameterSpec params);
public void init(Key key, AlgorithmParameterSpec params,
SecureRandom random);
KeyAgreementフェーズの実行
各協定プロトコルは、キー協定に関係する各パーティが実行する必要のある多数のフェーズで構成されます。
キー協定の次のフェーズを実行するには、doPhaseメソッドを呼び出します。
public Key doPhase(Key key, boolean lastPhase);
key
パラメータには、そのフェーズで処理するキーが含まれます。ほとんどの場合、これは、キー協定に関係する他のいずれかのパーティの公開キー、または前のフェーズで生成された中間キーです。doPhase
は、このキー協定の他のパーティに送信する必要のある中間キーを返すため、他のパーティは続くフェーズでそのキーを処理できます。
lastPhase
パラメータには、実行するフェーズがキー協定の最後のフェーズかどうかを指定します。値FALSE
は、これがキー協定の最後のフェーズではない(このあとにフェーズが続く)ことを示します。値TRUE
は、これがキー協定の最後のフェーズであり、キー協定が完了する(次にgenerateSecret
が呼び出される)ことを示します。
2つのパーティ間のDiffie-Hellmanキー交換の例では、lastPhase
をTRUE
に設定してdoPhase
を1回呼び出します。Diffie-Hellmanで3つのパーティが存在する場合、doPhase
を2度呼び出します。最初はlastPhase
をFALSE
に設定し、2度目にはlastPhase
をTRUE
に設定します。
共有される秘密の生成
各パーティがすべての必須キー協定フェーズを実行したあとで、generateSecretメソッドのいずれかを呼び出して共有される秘密を計算できます。
public byte[] generateSecret();
public int generateSecret(byte[] sharedSecret, int offset);
public SecretKey generateSecret(String algorithm);
キーの管理
「キーストア」と呼ばれるデータベースは、キーおよび証明書のリポジトリを管理するために使用できます。証明書とは、あるエンティティが発行したデジタル署名付きの文書で、別なエンティティの公開キーが特定の値であることを証明しています。
キーストアの場所
ユーザー・キーストアは、デフォルトではユーザーのホーム・ディレクトリの.keystore
という名前のファイルに格納されます。ユーザーのホーム・ディレクトリは、user.home
システム・プロパティによって決まります。このデフォルト値は、オペレーティング・システムによって異なります。
- LinuxおよびmacOS:
/home/username/
- Windows:
C:\Users\username\
キーストア・ファイルは必要な場所に配置できます。環境によっては、複数のキーストアが存在することに意味がある場合があります。たとえば、1つのキーストアがユーザーの秘密キーを保持し、別のキーストアが、信頼関係を確立するために使用される証明書を保持する場合があります。
ユーザーのキーストア以外に、JDKは、さまざまな証明書発行局(CA)からの信頼できる証明書を格納するために使用されるシステム全体のキーストアも保持します。これらのCA証明書は、信頼性の判定を行う場合に使用できます。たとえば、SSL/TLS/DTLSでSunJSSE
プロバイダがリモート・ピアから証明書を提示された場合、デフォルトのトラスト・マネージャは、次のいずれかのファイルを調べて、その接続を信頼できるかどうかを判断します。
- LinuxおよびmacOS:
<java-home>/lib/security/cacerts
- Windows:
<java-home>\lib\security\cacerts
システム全体のcacerts
キーストアを使用する代わりに、アプリケーションは独自のキーストアを設定して使用するか、前述のユーザー・キーストアを使用できます。
キーストアの実装
KeyStore
クラスは、キーストア内の情報へのアクセスおよび情報の変更を行うための、明確に定義されたインタフェースを提供します。複数の異なる固定実装を生成できます(それぞれ特定の型のキーストアを実装)。
現在、KeyStore
を使用する2つのコマンド行ツール(keytool
およびjarsigner
)があります。様々なソースのコードに対して(システム・リソースへの)アクセス権を指定するポリシー・ファイルを処理する場合、このツールはPolicy
のリファレンス実装によっても使われます。KeyStore
はpublicとして使用可能なので、JDKユーザーはKeyStoreを使った他のセキュリティ・アプリケーションも作成できます。
アプリケーションでは、KeyStore
クラスのgetInstance
ファクトリ・メソッドを使うことで、さまざまなプロバイダから異なる型のキーストア実装を選択できます。キーストアの型は、キーストア情報のストレージ形式とデータ形式を定義するとともに、キーストア内の秘密キーとキーストア自体の整合性を保護するために使われるアルゴリズムを定義します。異なる型のキーストアの実装には、互換性はありません。
デフォルトのキーストア実装はpkcs12です。これは、RSA PKCS12 Personal Information Exchange Syntax Standardに基づくクロス・プラットフォーム・キーストアです。この規格は、ユーザーの秘密キー、証明書、その他の秘密を格納および転送することを主な目的としています。PKCS12キーストア内の個々のエントリに任意の属性を関連付けることができます。
keystore.type=pkcs12
ツールおよび他のアプリケーションで異なるデフォルト・キーストア実装を使用するには、この行を変更して様々な型を指定します。
keytool
などの一部のアプリケーションでも、-storetype
コマンド行パラメータによって、デフォルトのキーストアの型をオーバーライドできます。
注意:
キーストアの型の指定では、大文字と小文字は区別されません。たとえば、「jks」と「JKS」は同じものとして扱われます。PKCS12は、推奨されている、デフォルトのキーストア型です。ただし、他に3つの型のキーストアが、JDK実装に付属しています。
- jceksは、jksに対する独自の代替キーストア形式であり、トリプルDESによるパスワードベース暗号化を使用します。
jceks実装は、jksキーストア・ファイルを解析してjceks形式に変換できます。キーストア内の非公開キーエントリのパスワードを変更し、キーストアの型として
"-storetype jceks"
を指定することによって、「jks」型のキーストアを「jceks」型のキーストアにアップグレードできます。提供される(より)強力な暗号化によるキー保護を、デフォルト・キーストア内の「signkey」という名前の秘密キーに適用する場合は、次のコマンドを入力します。このコマンドにより、旧パスワードおよび新規パスワードの指定が求められます。
Java Development Kitツール仕様のkeytool -keypass -alias signkey -storetype jceks
keytool
に関する項を参照してください。 - jksがもうひとつのオプションです。これは、独自のキーストアの型(形式)を利用するもので、キーストアをファイルとして実装しています。この実装では、個々の秘密キーは独自の個別のパスワードによって保護され、キーストア全体の整合性も(秘密キーとは別の)パスワードによって保護されます。
- "dks"は、ドメイン・キーストアです。これは、単一の論理的なキーストアとして表されるキーストアの集合です。特定のドメインを構成するキーストアは、
DomainLoadStoreParameter
で説明されている構文を持つ構成データによって指定されます。
キーストアの実装は、プロバイダ・ベースです。独自のKeyStore実装を記述する必要がある場合は、Java暗号化アーキテクチャでのプロバイダの実装方法を参照してください。
KeyStoreクラス
KeyStore
クラスは、キーストア内の情報へのアクセスおよび情報の変更を行うための、明確に定義されたインタフェースを提供します。
KeyStore
クラスは、エンジン・クラスおよびアルゴリズムです。
このクラスは、メモリー内のキーおよび証明書のコレクションを表します。KeyStore
は次の2種類のエントリを管理します。
-
キーのエントリ: このタイプのキーストア・エントリには、非常に重要な暗号化キー情報が保持されます。この情報は、権限のないアクセスから保護する必要があります。通常、この種類のエントリに格納されるキーは秘密キーで、対応する公開キーを証明する証明書チェーンが伴います。
秘密キーおよび証明書チェーンは、デジタル署名を使った自己認証用に特定のエンティティが使用します。たとえば、ソフトウェア配布団体は、リリースまたはライセンスするソフトウェアの一部としてJARファイルにデジタル署名を付けます。
-
信頼できる証明書のエントリ: このタイプのエントリには、別の相手に属する単一の公開キー証明書が含まれます。これは、信頼できる証明書と呼ばれます。それは、証明書内の公開キーが、証明書のsubject(所有者)によって特定されるアイデンティティに由来するものであることを、キーストアの所有者が信頼するからです。
この種類のエントリは、ほかの組織の認証に使うことができます。
キーストア内の各エントリは、「別名」文字列によって識別されます。秘密キーとそれに関連付けられた証明書チェーンの場合は、これらの文字列はエンティティ自体が認証するというように、方法別に区別されます。たとえば、エンティティが異なる証明書発行局を使ったり、異なる公開キーアルゴリズムを使ったりして、エンティティ自体を認証することも可能です。
キーストアが持続性があるかどうか、および持続性がある場合に使われるメカニズムは、ここでは指定されません。この規則により、重要な(秘密または非公開)キーを保護するための様々な技術を使うことができます。スマート・カードまたはその他の統合暗号化エンジン(SafeKeyper)を使うことも1つの方法です。また、ファイルなどのより単純なメカニズムを様々な形式で使うこともできます。
主要なKeyStore
メソッドを次で説明します。
KeyStoreオブジェクトの作成
KeyStoreオブジェクトは、KeyStoreのgetInstance()メソッドのいずれかを使用して取得されます。プロバイダ実装の要求および獲得方法を参照してください。
特定キーストアのメモリーへのロード
KeyStore
オブジェクトを使う前に、loadメソッドによってメモリー内に実際のキーストア・データをロードする必要があります。
final void load(InputStream stream, char[] password)
オプションのパスワードを使って、キーストア・データの整合性をチェックします。パスワードが提供されない場合は、整合性のチェックは行われません。
空のキーストアを作成するには、InputStream
引数としてnull
をloadメソッドに渡します。
DKSキーストアは、DomainLoadStoreParameter
を代替のloadメソッドに渡すことによってロードされます。
final void load(KeyStore.LoadStoreParameter param)
キーストアの別名一覧の取得
キーストアのすべてのエントリには、一意の別名を介してアクセスします。aliases
メソッドは、キーストア内の別名の列挙を返します。
final Enumeration aliases()
キーストア・エントリの種類の決定
「KeyStoreクラス」で説明したように、キーストアのエントリには、2種類あります。次のメソッドは、与えられた別名によって指定されたエントリが、それぞれキーまたは証明書か、信頼できる証明書エントリであることを決定します。
final boolean isKeyEntry(String alias)
final boolean isCertificateEntry(String alias)
キーストア・エントリの追加、設定、および削除
setCertificateEntryメソッドは、証明書を指定された別名に割り当てます。
final void setCertificateEntry(String alias, Certificate cert)
alias
が存在しない場合は、その別名のついた信頼できる証明書のエントリが作成されます。alias
が存在し、信頼できる証明書のエントリが識別された場合は、それに関連付けられた証明書がcert
によって置き換えられます。
setKeyEntryメソッドは、alias
がまだ存在しない場合に、キーのエントリの追加または設定を行います。
final void setKeyEntry(String alias,
Key key,
char[] password,
Certificate[] chain)
final void setKeyEntry(String alias,
byte[] key,
Certificate[] chain)
バイト配列としてkey
を取るメソッドでは、この引数は、保護された形式のキーのバイトです。たとえば、SUN
プロバイダによって提供されるキーストアの実装では、key
バイト配列は、PKCS8標準の定義に従ってEncryptedPrivateKeyInfo
としてエンコードされた、保護された秘密キーを格納します。もう一方のメソッドのpassword
は、キーの保護に使うパスワードです。
deleteEntryメソッドは、エントリを削除します。
final void deleteEntry(String alias)
PKCS #12キーストアは、任意の属性を含むエントリをサポートします。属性を作成するには、PKCS12Attribute
クラスを使用します。新しいキーストア・エントリを作成するときは、属性を受け入れるコンストラクタを使用します。最後に、次のメソッドを使用してエントリをキーストアに追加します。
final void setEntry(String alias, Entry entry,
ProtectionParameter protParam)
キーストアからの情報の取得
getKey
メソッドは、与えられた別名に関連付けられたキーを返します。キーは、与えられたパスワードを使って復元されます。
final Key getKey(String alias, char[] password)
次のメソッドは、与えられた別名に関連付けられた証明書、または証明書チェーンをそれぞれ返します。
final Certificate getCertificate(String alias)
final Certificate[] getCertificateChain(String alias)
次の文を使って、与えられた証明書と一致した最初のエントリの名前(alias
)を決定できます。
final String getCertificateAlias(Certificate cert)
PKCS #12キーストアは、任意の属性を含むエントリをサポートします。次のメソッドを使用して、属性を含む可能性があるエントリを取得します。
final Entry getEntry(String alias, ProtectionParameter protParam)
次に、KeyStore.Entry.getAttributes
メソッドを使用してそのような属性を抽出し、KeyStore.Entry.Attribute
インタフェースのメソッドを使用してそれらを調べます。
キーストアの保存
メモリー内のキーストアを、store
メソッドを使って保存できます。
final void store(OutputStream stream, char[] password)
パスワードは、キーストア・データの統合チェックサムの計算に使われます。統合チェックサムは、キーストア・データのあとに追加されます。
DKSキーストアは、DomainLoadStoreParameter
を代替のstoreメソッドに渡すことによって格納されます。
final void store(KeyStore.LoadStoreParameter param)
アルゴリズム・パラメータのクラス
Key
およびKeyspec
と同様に、アルゴリズムの初期化パラメータはAlgorithmParameter
またはAlgorithmParameterSpec
によって表されます。
使用状況によっては、アルゴリズムでパラメータを直接使用できますが、転送や格納のためにパラメータを移植性の高い形式に変換する必要がある場合もあります。
AlgorithmParameterSpec
によるパラメータのセットの透明な表現とは、セットの各パラメータの値に個別にアクセスできることを意味します。これらの値には、対応する仕様クラスに定義されたget
メソッドの1つを使ってアクセスできます(DSAParameterSpec
では、getP
、getQ
、およびgetG
メソッドを定義して、それぞれp
、q
、およびg
にアクセスするなど)。
これに対して、AlgorithmParametersクラスのクラスは不透明な表現を提供します。不透明な表現では、パラメータ・フィールドに直接アクセスできません。パラメータ・セットに関連付けられたアルゴリズム名の取得(getAlgorithm
による)、およびそのパラメータ・セット用のある種のエンコードの取得(getEncoded
による)しかできません。
AlgorithmParameterSpecインタフェース
AlgorithmParameterSpec
は、暗号化パラメータの透明な仕様へのインタフェースです。このインタフェースには、メソッドまたは定数が含まれていません。このインタフェースの唯一の目的は、すべてのパラメータの仕様をグループ化すること(およびそれらのパラメータに安全な型を提供すること)です。すべてのパラメータの仕様で、このインタフェースを実装する必要があります。
次に、java.security.spec
パッケージとjavax.crypto.spec
パッケージ内のアルゴリズム・パラメータ仕様インタフェースおよびクラスを示します。
DHParameterSpec
DHGenParameterSpec
DSAParameterSpec
ECGenParameterSpec
ECParameterSpec
GCMParameterSpec
IvParameterSpec
MGF1ParameterSpec
OAEPParameterSpec
PBEParameterSpec
PSSParameterSpec
RC2ParameterSpec
RC5ParameterSpec
RSAKeyGenParameterSpec
次のアルゴリズム・パラメータ仕様は、特にXMLデジタル署名用に使用されます。
AlgorithmParametersクラス
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
オブジェクトは、AlgorithmParameterGenerator
のgetInstance() staticファクトリ・メソッドの1つを使用することによって取得されます。プロバイダ実装の要求および獲得方法を参照してください。
AlgorithmParameterGeneratorオブジェクトの初期化
AlgorithmParameterGenerator
オブジェクトは、アルゴリズム独立型、またはアルゴリズム固有型の2種類の方法で初期化できます。
アルゴリズム独立型の方法では、すべてのパラメータ・ジェネレータが「サイズ」および乱数発生の元という概念を共有するという特性を利用します。サイズの単位は、すべてアルゴリズム・パラメータで普遍的に共通していますが、その解釈はアルゴリズムにより異なります。たとえば、DSAアルゴリズムのパラメータの場合、「サイズ」は素数モジュラスのビット数のサイズに一致します。(特定のアルゴリズムのサイズの詳細は、Javaセキュリティ標準アルゴリズム名を参照。)この方法を使うと、アルゴリズム固有型のパラメータの生成値が(ある場合は)、デフォルト基準値になります。普遍的に共有されるこれら2つの引数の型をとるinit
メソッドが1つあります。
void init(int size, SecureRandom random);
また、size
引数だけをとり、システムが提供する乱数の発生源を使用するinit
メソッドもあります。
void init(int size)
3番目の方法では、パラメータ・ジェネレータ・オブジェクトの初期化にアルゴリズム固有型のセマンティックスを使います。アルゴリズム固有型のセマンティックスは、AlgorithmParameterSpec
オブジェクト内に提供されるアルゴリズム固有型のパラメータ生成値のセットによって表されます。
void init(AlgorithmParameterSpec genParamSpec,
SecureRandom random)
void init(AlgorithmParameterSpec genParamSpec)
たとえば、Diffie-Hellmanシステム・パラメータを生成するには、通常、パラメータ生成値は、プライム・モジュラスおよびランダム指数のサイズで構成されます。どちらのサイズもビット数で指定します。
アルゴリズム・パラメータの生成
AlgorithmParameterGeneratorオブジェクトを作成および初期化したら、generateParametersメソッドを使ってアルゴリズム・パラメータを生成できます。
AlgorithmParameters generateParameters()
CertificateFactoryクラス
CertificateFactory
クラスは、証明書ファクトリの機能を定義します。証明書ファクトリは、エンコーディングから証明書のオブジェクトおよび証明書失効リスト(CRL)のオブジェクトを生成するために使用されます。
CertificateFactory
クラスは、エンジン・クラスおよびアルゴリズムです。
X.509の証明書ファクトリは、java.security.cert.X509Certificate
のインスタンスである証明書、およびjava.security.cert.X509CRL
のインスタンスであるCRLを返します。
CertificateFactoryオブジェクトの作成
CertificateFactory
オブジェクトは、getInstance()
staticファクトリ・メソッドの1つを使用することによって取得されます。詳細は、プロバイダ実装の要求および獲得方法を参照してください。
証明書オブジェクトの生成
証明書オブジェクトを生成し、入力ストリームから読み込まれたデータを使って初期化するには、generateCertificate
メソッドを使います。
final Certificate generateCertificate(InputStream inStream)
特定の入力ストリームから読み込まれた証明書のコレクション・ビュー(空の可能性もある)を返すには、generateCertificates
メソッドを使います。
final Collection generateCertificates(InputStream inStream)
CRLオブジェクトの生成
証明書失効リスト(CRL)オブジェクトを生成し、入力ストリームから読み込まれたデータを使って初期化するには、generateCRL
メソッドを使います。
final CRL generateCRL(InputStream inStream)
特定の入力ストリームから読み込まれたCRLのコレクション・ビュー(空の可能性もある)を返すには、generateCRLs
メソッドを使います。
final Collection generateCRLs(InputStream inStream)
CertPathオブジェクトの生成
PKIX用の証明書パス・ビルダーおよびバリデータは、RFC 5280「Internet X.509 Public Key Infrastructure Certificate and CRL Profile」によって定義されます。
PKIX LDAP V2スキーマを使用して証明書とCRLをCollectionディレクトリおよびLDAPディレクトリから取得するための証明書ストアの実装も、RFC 2587としてIETFから使用可能です。
CertPath
オブジェクトを生成し、そのオブジェクトを入力ストリームから読み込まれたデータを使って初期化するには、次のいずれかのgenerateCertPath
メソッドを使用します(必要に応じて、データに対して使用するエンコーディングを指定します)。
final CertPath generateCertPath(InputStream inStream)
final CertPath generateCertPath(InputStream inStream,
String encoding)
CertPath
オブジェクトを生成し、証明書のリストを使用して初期化するには、次のメソッドを使用します。
final CertPath generateCertPath(List certificates)
この証明書ファクトリでサポートされているCertPath
エンコーディングのリストを取得するには、getCertPathEncodings
メソッドを呼び出します。
final Iterator getCertPathEncodings()
初めにデフォルトのエンコーディングが一覧表示されます。
SSL/TLS実装でのJCAの使用方法
JCAのクラスについて理解したため、次に、これらのクラスがどのように結合されてSSL/TLSのような高度なネットワーク・プロトコルを実装するかについて考えます。
「TLSおよびDTLSプロトコル」のSSL/TLSの概要の項に、プロトコルの機能のおおまかな説明があります。非対称(公開キー)暗号操作は対称操作(秘密キー)よりも大幅に遅いため、公開キー暗号方式は、実際のアプリケーション・データを保護するために使用される秘密キーを確立する場合に使用されます。非常に単純に言うと、SSL/TLSハンドシェークには、初期化データの交換、いくつかの公開キー操作の実行による秘密キーへの到達、およびそのキーを使用したそのあとのトラフィックの暗号化が含まれます。
注意:
ここで説明する詳細では、単純に、前述のクラスのいくつかをどのように使用できるかを示します。このセクションでは、SSL/TLS実装を構築するための十分な情報は提供しません。詳細は、Java Secure Socket Extension (JSSE)リファレンス・ガイドおよびRFC 5246: Transport Layer Security (TLS)プロトコル、バージョン1.2を参照してください。このSSL/TLS実装がJSSEプロバイダとして使用可能になると仮定します。Provider
クラスの固定実装が最初に記述され、最終的にプロバイダのSecurity
クラスのリストに登録されます。このプロバイダは、主に、アルゴリズム名から実際の実装クラスへのマッピングを提供します(たとえば、SSLContext.TLS->com.foo.TLSImpl)。アプリケーションが(SSLContext.getInstance("TLS")
によって)TLSインスタンスを要求すると、プロバイダのリストに、要求されたアルゴリズムが問合せられ、適切なインスタンスが作成されます。
実際のハンドシェークの詳細を説明する前に、JSSEのアーキテクチャのいくつかを簡単に確認する必要があります。JSSEアーキテクチャの中心は、SSLContext
です。コンテキストは、最終的にSSL/TLSプロトコルを実際に実装する最終オブジェクト(SSLSocket
およびSSLEngine
)を作成します。SSLContext
は、2つのコールバック・クラスKeyManager
およびTrustManager
によって初期化されます。これらを使用すると、アプリケーションは送信する認証データを最初に選択し、次にピアによって送信された資格を検証できます。
JSSE KeyManager
は、ピアに提示する資格の選択を行います。多くのアルゴリズムが可能ですが、一般的な方法は、RSAまたはDSA公開キーと秘密キーのペアを、ディスク・ファイルに基づくKeyStore
内のX509Certificate
とともに保持することです。KeyStore
オブジェクトが初期化されてファイルからロードされると、ファイルの未変換のバイトはKeyFactory
を使用してPublicKey
およびPrivateKey
オブジェクトに変換され、証明書チェーンのバイトはCertificateFactory
を使用して変換されます。資格が必要な場合、KeyManager
は単純にこのKeyStore
オブジェクトに問い合わせ、どの資格を提示するかを判断します。
KeyStore
の内容は、最初にkeytool
などのユーティリティを使用して作成されている場合があります。keytool
は、RSAまたはDSAのKeyPairGenerator
を作成し、適切なキー・サイズで初期化します。このジェネレータは、次に、KeyPair
(最終的にディスクに書き込まれる)を作成する場合に使用されます。これはkeytool
により、新しく作成された証明書とともにKeyStore
に格納されます。
JSSE TrustManager
は、ピアから受信した資格の検証を行います。資格の検証には多数の方法があります。その1つは、CertPath
オブジェクトを作成し、JDKの組込み公開キーインフラストラクチャ(PKI)フレームワークで検証を処理する方法です。内部的に、CertPath実装はSignature
オブジェクトを作成し、それを使用して証明書チェーン内のそれぞれの署名を検証する場合があります。
アーキテクチャの基本について理解することによって、SSL/TLSハンドシェークのステップのいくつかを調べることができます。クライアントは、ClientHelloメッセージをサーバーに送信することによって開始します。サーバーは使用する暗号群を選択し、ServerHelloメッセージ内でそれを返信し、暗号群の選択に基づいてJCAオブジェクトの作成を開始します。次の例では、サーバーのみの認証を使用します。
サーバーのみの認証を、次の例で説明します。この例は非常に単純化されていますが、より高レベルのプロトコルを作成するためにJSSEクラスをどのように組み合せることができるかを示しています。
例2-9 SSL/TLSサーバーでTLS_RSA_WITH_AES_128_CBC_SHAなどのRSAベースの暗号群を使用する
サーバーのKeyManager
がクエリーされ、適切なRSAエントリを返します。サーバーの資格(つまり、証明書/公開キー)がサーバーのCertificateメッセージ内で送信されます。クライアントのTrustManager
はサーバーの証明書を検証し、受け入れた場合、クライアントはSecureRandom
オブジェクトを使用していくつかのランダム・バイトを生成します。次に、サーバーの証明書内で見つかったPublicKey
によって初期化された暗号化非対称RSA Cipher
オブジェクトを使用して暗号化されます。この暗号化されたデータは、Client Key Exchangeメッセージ内で送信されます。サーバーは、対応するPrivateKey
を使用し、同様のCipher
を復号モードで使用してバイトを回復します。これらのバイトは、実際の暗号化キーを確立する場合に使用されます。
例2-10 一時的なDiffie-Hellmanキー協定アルゴリズムとともにTLS_DHE_DSS_WITH_AES_128_CBC_SHAなどのDSA署名アルゴリズムを選択する
両方の側でそれぞれ、KeyPairGenerator
を使用して、新しい一時的なDH公開キーと秘密キーのペアを確立します。各ジェネレータは、KeyFactory
およびDHPublicKeySpec
クラスを使用して、さらに細かく変換できるDHキーを作成します。それぞれの側は、次にKeyAgreement
オブジェクトを作成し、それぞれのDH PrivateKey
によって初期化します。サーバーはその公開キーをServerKeyExchange
メッセージで送信し(DSA署名アルゴリズムによって保護される)、クライアントはその公開キーをClientKeyExchangeメッセージで送信します。公開キーが別のKeyFactory
を使用して再度組み立てられると、これらは協定オブジェクトに渡されます。KeyAgreement
オブジェクトは、実際の暗号化キーを確立する場合に使用される合意済みのバイトを生成します。
実際の暗号化キーが確立されると、対称Cipher
オブジェクトを初期化するために秘密キーが使用され、この暗号が送信中のすべてのデータを保護する場合に使用されます。データが変更されたかどうかを判断できるようにMessageDigest
が作成され、ネットワークに送信されるデータのコピーを受信します。パケットが完全な場合、ダイジェスト(ハッシュ)がデータのあとに付加され、パケット全体がCipher
によって暗号化されます。AESなどのブロック暗号が使用される場合は、データにパディングを行なって完全なブロックにします。リモート側では、ステップは単純に逆になります。
暗号強度の構成
管轄ポリシー・ファイル(管轄ポリシー・ファイル形式を参照)とセキュリティ・プロパティ・ファイルを使用してJava Cryptography Extension (JCE)アーキテクチャの暗号強度を構成できます。
Oracle Java JDK 9より前は、Oracle実装によって許可されているデフォルト暗号強度は、強力だが制限付きでした(たとえば、128ビットに制限されたAESキー)。この制限をなくすには、管理者が、無制限強度の管轄ポリシー・ファイルのバンドルを別にダウンロードしてインストールします。管轄ポリシー・ファイルのメカニズムは、JDK 9用に改変されました。現在は、より柔軟な構成が可能になっています。Oracle JDKでは、デフォルト値が、limitedではなくunlimitedになりました。これまでどおり、管理者およびユーザーは、自分の地理的な場所に応じたすべての輸入/輸出ガイドラインに従う必要があります。アクティブな暗号強度は、構成ディレクトリにある管轄ポリシー・ファイルと組み合せてセキュリティ・プロパティ(通常は、java.security
プロパティ・ファイル内で設定される)を使用して決定されるようになっています。
無制限の暗号強度または強力だが制限付きの暗号強度を提供するために必要なJCEポリシー・ファイルはすべてJDKに含まれています。
暗号強度の設定
<java_home>/conf/security/policy
の下の各ディレクトリは、それらに含まれている管轄ポリシー・ファイルで定義された一連のポリシー構成を表します。ディレクトリ内のポリシー・ファイルによって表される特定の暗号強度設定をアクティブ化するには、crypto.policy
セキュリティ・プロパティ(ファイル<java_home>/conf/security/java.security
内で構成される)をそのディレクトリを指すように設定します。
limited
とunlimited
という2つのディレクトリがセットになっており、それぞれにいくつかのポリシー・ファイルが含まれています。デフォルトでは、crypto.policy
セキュリティ・プロパティは、次のように設定されています。 crypto.policy = unlimited
この値全体は、このディレクトリに含まれているファイル間で共通の部分です。これらのポリシー・ファイル設定は、VM規模で適用され、このVM上で実行されるすべてのアプリケーションに影響します。アプリケーション・レベルで暗号強度をオーバーライドする必要がある場合は、アプリケーションの暗号化制限の免責を取得する方法を参照してください。
Unlimitedディレクトリの内容
unlimited
ディレクトリには、次のポリシー・ファイルが含まれています。
-
<java_home>/conf/security/unlimited/default_US_export.policy
// Default US Export policy file. grant { // There is no restriction to any algorithms. permission javax.crypto.CryptoAllPermission; };
注意:
現在は米国からの暗号化の輸出に関する制限はないため、default_US_export.policy
ファイルは制限なしで設定されます。 -
<java_home>/conf/security/unlimited/default_local.policy
// Country specific policy file for countries with no limits on crypto strength. grant { // There is no restriction to any algorithms. permission javax.crypto.CryptoAllPermission; };
注意:
国によっては、現地の制限事項がある場合がありますが、このポリシー・ファイルはunlimited
ディレクトリにあるため、ここでは制限は示されません。
これら2つのファイルで定義されているように無制限暗号強度を選択するには、ファイル<java_home>/conf/security/java.security
内でcrypto.policy = unlimited
と設定します。
Limitedディレクトリの内容
limited
ディレクトリには、現在、次のポリシー・ファイルが含まれています。
-
<java_home>/conf/security/limited/default_US_export.policy
// Default US Export policy file. grant { // There is no restriction to any algorithms. permission javax.crypto.CryptoAllPermission; };
注意:
たとえこれがlimited
ディレクトリ内にあるとしても、現在は米国からの暗号化の輸出に関する制限はないため、default_US_export.policy
ファイルは制限なしで設定されます。 -
<java_home>/conf/security/limited/default_local.policy
// Some countries have import limits on crypto strength. This policy file // is worldwide importable. grant { permission javax.crypto.CryptoPermission "DES", 64; permission javax.crypto.CryptoPermission "DESede", *; permission javax.crypto.CryptoPermission "RC2", 128, "javax.crypto.spec.RC2ParameterSpec", 128; permission javax.crypto.CryptoPermission "RC4", 128; permission javax.crypto.CryptoPermission "RC5", 128, "javax.crypto.spec.RC5ParameterSpec", *, 12, *; permission javax.crypto.CryptoPermission "RSA", *; permission javax.crypto.CryptoPermission *, 128; };
注意:
このローカル・ポリシー・ファイルでは、デフォルトの制限が示されます。輸入制限があるものを含め、これは国によって許可される必要があります。ただし、法的ガイダンスを入手してください。 -
<java_home>/conf/security/limited/exempt_local.policy
// Some countries have import limits on crypto strength, but may allow for // these exemptions if the exemption mechanism is used. grant { // There is no restriction to any algorithms if KeyRecovery is enforced. permission javax.crypto.CryptoPermission *, "KeyRecovery"; // There is no restriction to any algorithms if KeyEscrow is enforced. permission javax.crypto.CryptoPermission *, "KeyEscrow"; // There is no restriction to any algorithms if KeyWeakening is enforced. permission javax.crypto.CryptoPermission *, "KeyWeakening"; };
注意:
輸入制限がある国は、limitedを使用する必要がありますが、免責メカニズムを採用できる場合は、これらの制限を緩和できます。アプリケーションの暗号化制限の免責を取得する方法を参照してください。ご自分の状況に応じた法的ガイダンスを入手してください。
カスタム暗号強度の設定
制限をlimited
またはunlimited
ディレクトリにあるポリシー・ファイル内の設定とは異なる暗号強度に設定するには、limited
およびunlimited
と同列に新しいディレクトリを作成し、そこにポリシー・ファイルを配置します。たとえば、custom
というディレクトリを作成するとします。このcustom
ディレクトリに、ファイルdefault_*export.policy
またはexempt_*local.policy
、あるいはその両方を含めます。
custom
ディレクトリ内のファイルで定義されているように暗号強度を選択するには、ファイル<java_home>/conf/security/java.security
内でcrypto.policy = custom
と設定します。
管轄ポリシー・ファイル形式
JCAの管轄ポリシー・ファイルは、Java形式のポリシー・ファイル(対応するアクセス権を指定する文を含む)で表されます。暗号強度の構成で説明したように、Javaポリシー・ファイルは、指定されたコード・ソースからのコードに許可するアクセス権を指定します。アクセス権は、システム・リソースへのアクセスを表します。JCAの場合、「リソース」は暗号化アルゴリズムです。また、暗号化制限はすべてのコードに適用されるため、コード・ソースを指定する必要はありません。
管轄ポリシー・ファイルは、1つ以上の「アクセス権エントリ」を含む、非常に基本的な「付与エントリ」で構成されます。
grant {
<permission entries>;
};
次に、管轄ポリシー・ファイルのアクセス権エントリの書式を示します。
permission <crypto permission class name>
[<alg_name>
[
[, <exemption mechanism name>]
[, <maxKeySize>
[, <AlgorithmParameterSpec class name>,
<parameters for constructing an AlgorithmParameterSpec object>
]
]
]
];
次に、AESアルゴリズムを128ビットの最大キー・サイズに制限する、管轄ポリシー・ファイルのサンプルを示します。
grant {
permission javax.crypto.CryptoPermission "AES", 128;
// ...
};
アクセス権エントリは、単語permission
で始まります。アクセス権エントリの各項目は、指定された順序で記述する必要があります。各エントリはセミコロンで終わります。識別子(grant
、permission
)では大文字と小文字は区別されませんが、<crypto permission class name>
と、値として引き渡される文字列については大文字と小文字が区別されます。アスタリスク(*
)は、すべてのアクセス権エントリ・オプションでワイルドカードとして使用できます。たとえば、<alg_name>
オプションにアスタリスクを指定すると、すべてのアルゴリズムという意味になります。
次の表に、アクセス権エントリのオプションを示します。
表2-1 アクセス権エントリのオプション
オプション | 説明 |
---|---|
<crypto permission class name> |
暗号化アクセス権クラスは、特定の環境下で特定のキー・サイズで、特定のアルゴリズムを使用するアプリケーションの機能に対応します。暗号化アクセス権クラスには、 |
<alg_name> |
"AES"や"RSA"など、暗号化アルゴリズムの標準名を指定する引用符付き文字列。オプションです。 |
<exemption mechanism name> |
免責メカニズムを示す引用符付き文字列。免責メカニズムを実施すると、暗号化制限が緩和されます。オプションです。 使用可能な免責メカニズム名には、「KeyRecovery」、「KeyEscrow」および「KeyWeakening」が含まれます。 |
<maxKeySize> |
指定したアルゴリズムに許可する最大キー・サイズ(ビット)を示す整数値。オプションです。 |
<AlgorithmParameterSpec class name> |
アルゴリズムの強度を指定するクラス名。オプションです。 アルゴリズムによっては、キー・サイズでアルゴリズムの強度を指定するだけでは不十分な場合があります。たとえば、「RC5」アルゴリズムの場合には、ラウンド数も考慮する必要があります。アルゴリズムの強度をキー・サイズだけで表現するのでは不十分な場合、このオプションを使用して、これを行う |
<parameters for constructing an AlgorithmParameterSpec object> |
指定されたAlgorithmParameterSpec オブジェクトを構成するためのパラメータのリスト。<AlgorithmParameterSpec class name> が指定されており、パラメータが必要な場合は必須です。
|
アプリケーションの暗号化制限の免責を取得する方法
注意:
大半のアプリケーション開発者には、この項で説明する内容は関係ありません。関係があるのは、作成するアプリケーションが、政府により暗号化制限の課された国に輸出される可能性があり、アプリケーションをその制限に適合させる必要がある場合だけです。デフォルトでは、アプリケーションは、どの強度の暗号化アルゴリズムでも使用できます。ただし、一部の国の政府による輸入管理制限のため、それらのアルゴリズムの強度を制限する必要がある場合があります。JCAフレームワークには、様々な管轄コンテキスト(場所)でアプリケーションに使用可能な暗号化アルゴリズムの最大強度に関して、制限を強制する機能が含まれます。管轄ポリシー・ファイルでこれらの制限を指定します。管轄ポリシー・ファイルとそれらを作成および構成する方法の詳細は、暗号強度の構成を参照してください。
これらの国の一部またはすべてで、特定のアプリケーションに対し、暗号化制限の一部またはすべての免責が許可されています。たとえば、特定の種類のアプリケーションは「特別」と見なされ、免責されます。また、キー復元などの「免責メカニズム」を利用するアプリケーションは、免責可能です。この種の国では、免責されたと見なされるアプリケーションは、免責されていないアプリケーションに許可されるよりも強力な暗号化にアクセスできます。
実行時にアプリケーションが「免責されている」と認識されるようにするには、次の条件を満たす必要があります。
- JARファイル内にアクセス権ポリシー・ファイルをバンドルする必要があります。アクセス権ポリシー・ファイルには、アプリケーションが保持する暗号化関連のアクセス権、およびそれを保持する条件(存在する場合)を指定します。
- アプリケーションおよびアクセス権ポリシー・ファイルを含むJARファイルには、アプリケーションが免責を受け取ったあとで発行されたコード署名証明書を使用して署名する必要があります。
次に、アプリケーションにいくつかの暗号化制限が適用されないようにするために必要なステップのサンプルを示します。これは、免責されたものとしてアプリケーションを認識および処理するため、JCAにより要求される情報を含む、基本情報です。実際には、アプリケーションを実行可能にする(政府が暗号化制限を課している)特定の国の免責要件を知る必要があります。また、免責されたアプリケーションの処理プロセスを保持するJCAフレームワーク・ベンダーの要件も理解しておく必要があります。詳細は、ベンダーにお尋ね下さい。
注意:
SunJCE
プロバイダは、ExemptionMechanismSpi
クラスの実装を提供しません
- アプリケーション・コードの記述およびコンパイル
- 適切な暗号化アクセス権を付与するアクセス権ポリシー・ファイルの作成
- テストの準備
- 政府の課す制限に関する政府承認の申請
- コード署名証明書の取得
- JARファイルへのアプリケーションおよびアクセス権ポリシー・ファイルのバンドル
- ステップ7.1: コード署名証明書の取得
- 制限国のユーザーと同じ環境の設定
- アクセス権ポリシー・ファイルのエントリによって指定された免責メカニズムを実装するプロバイダのインストール(免責メカニズムを使用するアプリケーションのみ)
- アプリケーションのテスト
- 米国政府による輸出承認の申請(必要な場合)
- アプリケーションのデプロイ
免責メカニズムを使用するアプリケーションに対する特殊コード要件
アプリケーションに、関連付けられたアクセス権ポリシー・ファイルがあり(同じJARファイル内)、そのアクセス権ポリシー・ファイルで免責メカニズムが指定されている場合、CipherのgetInstance
メソッドが呼び出されてCipherがインスタンス化されると、JCAコードは、インストール済プロバイダの中から、指定された免責メカニズムを実装するプロバイダを検索します。目的のプロバイダが見つかると、JCAは、そのプロバイダの実装に関連付けられたExemptionMechanism
APIオブジェクトをインスタンス化してから、ExemptionMechanism
オブジェクトを、getInstance
によって返されたCipherと関連付けます。
Cipherをインスタンス化した後、初期化する前に(Cipherのinit
メソッドを呼び出して)、コードから次のCipherメソッドを呼び出す必要があります。
public ExemptionMechanism getExemptionMechanism()
この呼出しにより、Cipherに関連付けられたExemptionMechanism
オブジェクトが返されます。次に、返されたExemptionMechanism
に対して次のメソッドを実行して、免責メカニズムの実装を初期化する必要があります。
public final void init(Key key)
ここで指定する引数の型は、このあとでCipher init
メソッドに指定する引数の型と同じにする必要があります。
ExemptionMechanism
の初期化が完了したら、通常と同じ方法でCipherを初期化して使用できます。
アクセス権ポリシー・ファイル
実行時にアプリケーションが暗号化制限の一部またはすべてを「免責」されていると認識されるには、JARファイル内にアクセス権ポリシー・ファイルをバンドルする必要があります。アクセス権ポリシー・ファイルには、アプリケーションが保持する暗号化関連のアクセス権、およびそれを保持する条件(存在する場合)を指定します。
免責されるアプリケーションにバンドルされるアクセス権ポリシー・ファイル内のアクセス権エントリの書式は、JDKとともにダウンロードされる管轄ポリシー・ファイルの書式と同じです。次にその書式を示します。
permission <crypto permission class name>
[<alg_name>
[
[, <exemption mechanism name>]
[, <maxKeySize>
[, <AlgorithmParameterSpec class name>,
<parameters for constructing an AlgorithmParameterSpec object>
]
]
]
];
管轄ポリシー・ファイル形式を参照してください。
免責されるアプリケーションのアクセス権ポリシー・ファイル
アプリケーションの中には、制限を完全に解除可能なものもあります。通常、その種のアプリケーションにバンドルするアクセス権ポリシー・ファイルには、次を含めるだけで十分です。
grant {
// There are no restrictions to any algorithms.
permission javax.crypto.CryptoAllPermission;
};
アプリケーションが1つの(またはいくつかの特定の)アルゴリズムだけを使用する場合、アクセス権ポリシー・ファイルには、CryptoAllPermissionを付与するのではなく、そのアルゴリズムを明示的に記述します。
たとえば、アプリケーションがBlowfishアルゴリズムだけを使用する場合、アクセス権ポリシー・ファイルですべてのアルゴリズムにCryptoAllPermissionを付与する必要はありません。Blowfishアルゴリズムが使用される場合、暗号化制限が存在しないことを指定するだけで十分です。この場合、アクセス権ポリシー・ファイルは、次のようになります。
grant {
permission javax.crypto.CryptoPermission "Blowfish";
};
免責メカニズムにより免責されるアプリケーションのアクセス権ポリシー・ファイル
免責メカニズムが導入されるためにアプリケーションが「免責される」と見なされる場合、アプリケーションにバンドルされるアクセス権ポリシー・ファイルに1つ以上の免責メカニズムを指定する必要があります。実行時に、これらの免責メカニズムのいずれかが機能していると、アプリケーションは免責されたものと見なされます。次のようなアクセス権エントリ内に、各免責メカニズムを指定する必要があります。
// No algorithm restrictions if specified
// exemption mechanism is enforced.
permission javax.crypto.CryptoPermission *,
"<ExemptionMechanismName>";
ここで、<ExemptionMechanismName>
には免責メカニズムの名前を指定します。指定可能な免責メカニズムの名前には、次が含まれます。
KeyRecovery
KeyEscrow
KeyWeakening
例として、キー復元またはキー・エスクローのいずれかが機能すると、アプリケーションが免責される場合を考えましょう。その場合、アクセス権ポリシー・ファイルには、次が含まれます。
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コード・サンプル
これらの例では、いくつかのJCAメカニズムの使用を説明します。Diffie-Hellmanキー交換、AES/GCMおよびHMAC-SHA256のサンプル・プログラムも参照してください。
MessageDigestオブジェクトの計算
MessageDigestオブジェクトを計算する手順を示す例。
例2-11 複製によるハッシュ実装
ハッシュ実装によっては、複製(コピー)を介して中間ハッシュをサポートするものもあります。次について、別々のハッシュを計算すると仮定します。
i1
i1およびi2
i1、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を使った署名の生成と検証
キー仕様と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
を取得したら、KeyFactory
getKeySpec
メソッドを使って、PublicKeyをDSAPublicKeySpec
に変換して、必要な場合にコンポーネントにアクセスすることが可能です。
DSAPublicKeySpec dsaPubKeySpec =
(DSAPublicKeySpec)keyFactory.getKeySpec(pubKey, DSAPublicKeySpec.class);
これで、DSA公開キーコンポーネントy
、p
、q
、およびg
に、DSAPublicKeySpec
クラスの対応するgetメソッド(getY
、getP
、getQ
、およびgetG
)を使用してアクセスできます。
乱数の生成
次のコード例では、SecureRandomクラスのDRBG実装の使用による、様々なセキュリティ強度で構成された乱数の生成を説明します。
SecureRandom drbg;
byte[] buffer = new byte[32];
// Any DRBG can be provided
drbg = SecureRandom.getInstance("DRBG");
drbg.nextBytes(buffer);
SecureRandomParameters params = drbg.getParameters();
if (params instanceof DrbgParameters.Instantiation) {
DrbgParameters.Instantiation ins = (DrbgParameters.Instantiation) params;
if (ins.getCapability().supportsReseeding()) {
drbg.reseed();
}
}
// The following call requests a weak DRBG instance. It is only
// guaranteed to support 112 bits of security strength.
drbg = SecureRandom.getInstance("DRBG",
DrbgParameters.instantiation(112, NONE, null));
// Both the next two calls will likely fail, because drbg could be
// instantiated with a smaller strength with no prediction resistance
// support.
drbg.nextBytes(buffer,
DrbgParameters.nextBytes(256, false, "more".getBytes()));
drbg.nextBytes(buffer,
DrbgParameters.nextBytes(112, true, "more".getBytes()));
// The following call requests a strong DRBG instance, with a
// personalization string. If it successfully returns an instance,
// that instance is guaranteed to support 256 bits of security strength
// with prediction resistance available.
drbg = SecureRandom.getInstance("DRBG", DrbgParameters.instantiation(
256, PR_AND_RESEED, "hello".getBytes()));
// Prediction resistance is not requested in this single call,
// but an additional input is used.
drbg.nextBytes(buffer,
DrbgParameters.nextBytes(-1, false, "more".getBytes()));
// Same for this call.
drbg.reseed(DrbgParameters.reseed(false, "extra".getBytes()));
2つのキーの同一性の判定
2つのキーが等しいかどうかを判断するサンプル・コードです。
しばしば、2つのキーの同一性の判定が必要になることがありますが、デフォルトのjava.lang.Object.equals
メソッドでは、必要な結果が得られない場合があります。プロバイダに対する依存度がもっとも少ない方法は、エンコードされたキーを比較する方法です。この方法で適切に比較できない場合(たとえば、RSAPrivateKey
とRSAPrivateCrtKey
を比較する場合)、各コンポーネントを比較する必要があります。
その方法を示すコードを次に示します。
static boolean keysEqual(Key key1, Key key2) {
if (key1.equals(key2)) {
return true;
}
if (Arrays.equals(key1.getEncoded(), key2.getEncoded())) {
return true;
}
// More code for different types of keys here.
// For example, the following code can check if
// an RSAPrivateKey and an RSAPrivateCrtKey are equal:
// if ((key1 instanceof RSAPrivateKey) &&
// (key2 instanceof RSAPrivateKey)) {
// if ((key1.getModulus().equals(key2.getModulus())) &&
// (key1.getPrivateExponent().equals(
// key2.getPrivateExponent()))) {
// return true;
// }
// }
return false;
}
Base64でエンコードされた証明書の読み取り
次の例は、Base64でエンコードされた証明書を読み取ります。認証の開始は、次のコードで指定されます。
-----BEGIN CERTIFICATE-----
最後は、次の行で終わります。
-----END CERTIFICATE-----
generateCertificate
の各呼出しが1つの証明書だけを使用し、入力ストリームの読出し位置がファイル内の次の証明書に置かれるように、FileInputStream
(mark
およびreset
をサポートしていない)をByteArrayInputStream
(これらのメソッドをサポート)に変換します。
try (FileInputStream fis = new FileInputStream(filename);
BufferedInputStream bis = new BufferedInputStream(fis)) {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
while (bis.available() > 0) {
Certificate cert = cf.generateCertificate(bis);
System.out.println(cert.toString());
}
}
認証応答の解析
次の例は、ファイル内に保存されたPKCS7形式の認証応答を解析し、そこから証明書をすべて抽出します。
try (FileInputStream fis = new FileInputStream(filename)) {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Collection<? extends Certificate> c = cf.generateCertificates(fis);
for (Certificate cert : c) {
System.out.println(cert);
}
// 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のサンプル・プログラム
次に、Diffie-Hellmanキー交換、AES/GCMおよびHMAC-SHA256のサンプル・プログラムを示します。
2つのパーティ間のDiffie-Hellmanキー交換
このプログラムでは、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キー交換
このプログラムでは、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の例
次に、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の秘密キーオブジェクトを生成し、それを使用して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());
}
}