Javaプラットフォームは、言語の安全性、暗号化、公開キー・インフラストラクチャ、認証、安全な通信、アクセス制御など、セキュリティに重点を置いています。
JCAはプラットフォームの主要な部分であり、「プロバイダ」アーキテクチャ、およびデジタル署名、メッセージ・ダイジェスト(ハッシュ)、証明書と証明書検証、暗号化(対称/非対称ブロック/ストリーム暗号)、キーの生成と管理、安全な乱数生成などのための一連のAPIが含まれています。 これらのAPIによって、開発者はアプリケーション・コードにセキュリティを簡単に統合できます。 アーキテクチャは、次の方針に基づいて設計されました。
実装の独立性: アプリケーションでセキュリティ・アルゴリズムを実装する必要はありません。 Javaプラットフォームからセキュリティ・サービスを要求できます。 セキュリティ・サービスはプロバイダ(次を参照)に実装されています。プロバイダは、標準インタフェースによってJavaプラットフォームにプラグインされます。 アプリケーションは、複数の独立したプロバイダにセキュリティ機能を依存する場合があります。
実装の相互運用性: プロバイダは、アプリケーション間で相互運用できます。 具体的には、アプリケーションは特定のプロバイダにバインドされず、プロバイダは特定のアプリケーションにバインドされません。
アルゴリズムの拡張性: Javaプラットフォームには、現在広く使用されている基本的なセキュリティ・サービスを実装する多数の組込みプロバイダが含まれています。 ただし、一部のアプリケーションは、まだ実装されていない普及しつつある規格や独自のサービスに依存している場合があります。 Javaプラットフォームは、そのようなサービスを実装するカスタム・プロバイダのインストールをサポートします。
JDKで使用できるその他の暗号化通信ライブラリはJCAプロバイダ・アーキテクチャを使用しますが、これらについては別途説明します。 Java Secure Socket Extension (JSSE)は、Secure Socket Layer (SSL)およびTransport Layer Security (TLS)の実装へのアクセスを提供します。 通信しているアプリケーション間で安全にメッセージを交換するために、Java Generic Security Services (JGSS) (Kerberosによる) API、およびSimple Authentication and Security Layer (SASL)を使用することもできます。
JDK 1.4より前は、JCEはバンドルされていない製品であり、そのため、JCAとJCEは常に別個のコンポーネントとして参照されていました。 現在JCEはJDKにバンドルされているため、区別は明確でなくなっています。 JCEはJCAと同じアーキテクチャを使用するため、JCEはJCAの一部として考えることができます。
JDKのJCAには、次の2つのソフトウェア・コンポーネントが含まれます。
java.security、javax.crypto、javax.crypto.spec、javax.crypto.interfacesなどのパッケージが含まれます。 Sun、SunRsaSign、SunJCEなどの実際のプロバイダ。実際の暗号化実装が含まれます。特定のJCAプロバイダを取り上げる場合は、常にプロバイダの名前で明示的に指定されます。
警告: JCAを使用すると、アプリケーションへのセキュリティ機能の組込みが簡単になります。 ただし、このドキュメントでは、APIの説明に必要な概念の基本的な紹介以外、セキュリティ/暗号化の理論については説明しません。 このドキュメントでは、特定のアルゴリズムの長所と短所やプロトコルの設計についても説明しません。 暗号化は高度なトピックであり、これらのツールを最適に使用するには、信頼できる、可能なかぎり最近のリファレンスを参照するようにしてください。
実行している内容とその理由を常に理解している必要があります。ランダム・コードを単純にコピーし、それが用途を満たす筋書きを完全に解決してくれるとは考えないでください。 間違ったツールまたはアルゴリズムが選択されたため、セキュリティまたはパフォーマンスに重大な問題があるアプリケーションが数多く配備されています。
JCAは、次の方針に基づいて設計されました。
実装の独立性とアルゴリズムの独立性は相補関係にあります。そのため、デジタル署名やメッセージ・ダイジェストなどの暗号化サービスを、実装の詳細やこれらの概念の基になるアルゴリズムを意識しないで使用できます。 完全なアルゴリズムの独立性は不可能ですが、JCAは標準化された、アルゴリズム固有のAPIを提供します。 実装の独立性が望ましくない場合、JCAでは、開発者が特定の実装を指定できます。
アルゴリズムの独立性は、暗号化「エンジン」(サービス)の型を定義し、これらの暗号化エンジンの機能を提供するクラスを定義することにより実現しています。 これらのクラスは、エンジン・クラスと呼ばれ、例としては、MessageDigest、Signature、KeyFactory、KeyPairGenerator、およびCipherクラスがあります。
実装の独立性は、「プロバイダ」ベースのアーキテクチャを使って実現されます。 暗号化サービス・プロバイダ(CSP) (このドキュメントでは「プロバイダ」とも呼ばれる)とは、デジタル署名アルゴリズム、メッセージ・ダイジェスト・アルゴリズム、キーの変換サービスなどの1つ以上の暗号化を実装するパッケージやパッケージ・セットを意味します。 プログラムは単に、特定のサービス(たとえばDSA署名アルゴリズム)を実行する特定の型のオブジェクト(たとえばSignatureオブジェクト)を要求するだけで、インストールされているプロバイダの1つから実装を獲得できます。 必要に応じて、プログラムは特定プロバイダから実装を要求することもできます。 たとえば、より高速で安全性の高いバージョンが利用できるときは、プロバイダをアプリケーションに対して透過的に更新できます。
実装の相互操作性とは、さまざまな実装が互いに機能でき、互いのキーを使ったり、互いの署名を検証できることを意味します。 つまり、1つのプロバイダが生成した同じアルゴリズムやキーを別のプロバイダが使えたり、あるプロバイダが生成した署名を別のプロバイダが検証できるということです。
アルゴリズムの拡張性とは、サポートされるエンジン・クラスの1つに当てはまる新規アルゴリズムを容易に追加できることを意味します。
java.security.Providerは、すべてのセキュリティ・プロバイダの基底クラスです。 各CSPはこのクラスのインスタンスを含みます。インスタンスはプロバイダの名前を含み、実装するセキュリティ・サービス/アルゴリズムをすべて一覧表示します。 特定のアルゴリズムのインスタンスが必要な場合、JCAフレームワークはプロバイダのデータベースに問い合わせ、一致するものが見つかった場合はそのインスタンスが作成されます。
プロバイダは、通知されている暗号化アルゴリズムの固定実装を提供するパッケージ(または、パッケージのセット)を含みます。 各JDKのインストールでは、1つ以上のプロバイダがインストールされ、デフォルトで構成されます。 追加プロバイダは静的または動的に追加できます(ProviderクラスとSecurityクラスを参照)。 クライアントは実行環境を構成し、プロバイダの優先順位を指定できます。 優先順位とは、特定プロバイダの指定がない場合に、要求されたサービスに関して検索されるプロバイダの順位です。
JCAを使用するには、アプリケーションは単に特定の型のオブジェクト(たとえばMessageDigest)と特定のアルゴリズムまたはサービス(たとえばSHA-256アルゴリズム)を要求し、インストールされているプロバイダのうちの1つから実装を獲得します。 あるいは、特定プロバイダのオブジェクトを要求することもできます。 各プロバイダは、それぞれの参照名を持ちます。
md = MessageDigest.getInstance("SHA-256");
md = MessageDigest.getInstance("SHA-256", "ProviderC");
次の図は、「SHA-256」メッセージ・ダイジェスト実装の要求を示しています。 この図は、様々なメッセージ・ダイジェスト・アルゴリズム(「SHA-256」、「SHA-384」および「SHA-512」)を実装する3つの異なるプロバイダを示しています。 プロバイダは、左から右への優先順位(1から3)で並べられています。 最初の図では、アプリケーションはプロバイダ名を指定せずにSHA-256アルゴリズム実装を要求しています。 プロバイダが優先順位に従って検索され、その特定のアルゴリズムを提供する最初のプロバイダであるProviderBから実装が返されます。 2番目の図では、アプリケーションは特定のプロバイダであるProviderCからSHA-256アルゴリズム実装を要求しています。 この場合は、優先順位の高いプロバイダであるProviderBもSHA-256実装を提供しますが、ProviderCから実装が返されます。
![]() |
![]() |
| 図1a: プロバイダ検索 | 図1b: 特定のプロバイダの要求 |
図1a: プロバイダ検索および図1b: 特定のプロバイダの要求の説明
JDKでの暗号化実装は、様々なプロバイダ(Sun、SunJSSE、SunJCE、SunRsaSign)によって配布されます。これは主に歴史的な理由によりますが、それらが提供する機能およびアルゴリズムの型によるところもあります。 その他のJava実行環境は、必ずしもこれらのSunプロバイダを含みません。したがって、特定のプロバイダが使用可能になることがわかっている場合を除き、アプリケーションはプロバイダ固有の実装を要求しないようにする必要があります。
JCAには、インストールされているプロバイダおよびそれらがサポートするサービスについて、ユーザーが問い合わせることができるAPIセットがあります。
このアーキテクチャを使用すると、エンド・ユーザーがプロバイダを追加することも簡単になります。 多くのサード・パーティのプロバイダ実装がすでに使用可能です。 プロバイダの記述、インストール、および登録の方法の詳細は、Providerクラスを参照してください。
前述のように、アルゴリズムの独立性は、サービスの型にアクセスするためにすべてのアプリケーションが使用する汎用で高レベルのAPI (Application Programming Interface)を定義することによって実現されます。 実装の独立性は、すべてのプロバイダ実装を、明確に定義されたインタフェースに準拠させることによって実現されます。 したがって、エンジン・クラスのインスタンスは、同じメソッド署名を持つ実装クラスに基づいています。 アプリケーション呼出しはエンジン・クラスによって渡され、基盤となる実装に伝送されます。 実装は要求を処理し、正しい結果を返します。
各エンジン・クラス内のアプリケーションAPIメソッドは、対応するSPI (Service Provider Interface)を実装するクラスによってプロバイダの実装に渡されます。 つまり、各エンジン・クラスに対応する抽象SPIクラスが存在し、この抽象SPIクラスによって各暗号化サービス・プロバイダのアルゴリズムが実装する必要があるメソッドが定義されます。 SPIクラスの名前は、対応するエンジン・クラス名のあとにSpiを追加した名前になります。 たとえばSignatureエンジン・クラスは、デジタル署名アルゴリズムの機能へのアクセスを提供します。 実際のプロバイダ実装は、SignatureSpiのサブクラス内で提供されます。 アプリケーションは各エンジン・クラスのAPIメソッドを呼び出し、APIメソッドは実際の実装内のSPIメソッドを呼び出します。
各SPIクラスは、抽象クラスです。 指定したアルゴリズムに対する特定の型のサービスの実装を提供するには、プロバイダは、対応するSPIクラスをサブクラス化して、すべての抽象メソッドの実装を提供する必要があります。
APIの各エンジン・クラスについては、エンジン・クラス特有のgetInstance() ファクトリ・メソッドを呼び出すことによって実装インスタンスが要求され、インスタンスが生成されます。 ファクトリ・メソッドは、クラスのインスタンスを返すstaticメソッドです。 エンジン・クラスは、前述のフレームワーク・プロバイダ選択メカニズムを使用して実際の基盤となる実装(SPI)を取得し、実際のエンジン・オブジェクトを作成します。 エンジン・クラスの各インスタンスは、SPIオブジェクトと呼ばれる対応するSPIクラスのインスタンスを、privateフィールドとしてカプセル化します。 APIオブジェクトのすべてのAPIメソッドは、finalとして宣言し、それらを実装することによって、カプセル化されるSPIオブジェクトの対応するSPIメソッドが呼び出されます。
これを明確にするために、次のコードと図を確認してください。
import javax.crypto.*;
Cipher c = Cipher.getInstance("AES");
c.init(ENCRYPT_MODE, key);
アプリケーションが「AES」Cipherインスタンスを取得する方法の例の説明
ここで、アプリケーションは「AES」javax.crypto.Cipherインスタンスを必要とし、使用するプロバイダは問題となりません。 アプリケーションはCipherエンジン・クラスのgetInstance()ファクトリ・メソッドを呼び出し、ファクトリ・メソッドはJCAフレームワークに「AES」をサポートする最初のプロバイダ・インスタンスを見つけることを要求します。 フレームワークはインストールされている各プロバイダに問い合わせ、Providerクラスのプロバイダのインスタンスを取得します。 Providerクラスは、使用可能なアルゴリズムのデータベースです。 フレームワークは各プロバイダを検索し、最終的にCSP3で適切なエントリを見つけます。 このデータベース・エントリは、CipherSpiを拡張する実装クラスcom.foo.AESCipherを指定するため、Cipherエンジン・クラスによる使用に適しています。 com.foo.AESCipherのインスタンスが作成され、javax.crypto.Cipherの新しく作成されるインスタンスにカプセル化され、それがアプリケーションに返されます。 アプリケーションがCipherインスタンス上でinit()操作を実行すると、Cipherエンジン・クラスはその要求をcom.foo.AESCipherクラス内の対応するengineInit()バッキング・メソッドに渡します。
「付録A」には、Java環境に対して定義されている標準名が一覧表示されています。 ほかのサード・パーティ・プロバイダが、これらのサービスまたは追加サービスの独自の実装を定義している場合があります。
「キーストア」と呼ばれるデータベースは、キーおよび証明書のリポジトリを管理するために使用できます。 キーストアは、認証、暗号化、または署名のためにデータが必要なアプリケーションで利用できます。
アプリケーションは、KeyStoreクラスの実装を経由してキーストアにアクセスできます。キーストア・クラスの実装は、java.securityパッケージ内にあります。 推奨されるデフォルト・キーストア・タイプ(形式)はpkcs12であり、これは、RSA PKCS12 Personal Information Exchange Syntax Standardに基づいています。 デフォルトのキーストア・タイプは、独自仕様の形式であるjksです。 独自の代替キーストア形式であるjceksや、RSA PKCS11 Standardに基づいた、ハードウェア・セキュリティ・モジュールやスマートカードなどの暗号化トークンへのアクセスがサポートされているpkcs11など、その他のキーストア形式も使用できます。
アプリケーションは、前述の同じプロバイダ・メカニズムを使用して、異なるプロバイダから異なるキーストア実装を選択できます。
詳細は、「キー管理」のセクションを参照してください。
このセクションでは、主要なJCA APIについて説明します。
エンジン・クラスは、特定の暗号化アルゴリズムまたはプロバイダに依存しない、特定の型の暗号化サービスへのインタフェースを提供します。 エンジンは、次のいずれかを提供します。
次のエンジン・クラスが使用可能です。
SecureRandom: 乱数または擬似乱数の生成に使用します。MessageDigest: 指定データのメッセージ・ダイジェスト(ハッシュ)の計算に使います。Signature: キーによって初期化されます。データの署名およびデジタル署名の検証に使用します。Cipher: キーによって初期化されます。データの暗号化/復号化に使用します。 対称バルク暗号化(例: AES)、非対称暗号化(例: RSA)、パスワードベース暗号化(例: PBE)など、様々なタイプのアルゴリズムがあります。 MessageDigestと同様にこれらもハッシュ値を生成しますが、メッセージの整合性を保護するために最初にキーによって初期化されます。KeyFactory: Key型の既存の不透明な暗号化キーを「キー仕様」(基本のキー・データの透明な表現)に変換したり、その逆の変換を行うために使用します。SecretKeyFactory: SecretKey型の既存の不透明な暗号化キーを「キー仕様」(基本のキー・データの透明な表現)に変換したり、その逆の変換を行うために使用します。 SecretKeyFactoryは、秘密(対称)キーのみを作成する特殊なKeyFactoryです。 KeyPairGenerator: 指定のアルゴリズムでの使用に適合した、新しい公開キー、非公開キーペアの生成に使用します。KeyGenerator: 指定のアルゴリズムで使用するための、新しい秘密キーの生成に使用します。KeyAgreement: 特定の暗号化操作に使用する特定のキーの合意および確立のために、複数のパーティによって使用されます。AlgorithmParameters: パラメータのエンコードおよびデコードなど、特定のアルゴリズムのパラメータの格納に使用します。AlgorithmParameterGenerator: 特定のアルゴリズムに適したアルゴリズム・パラメータ・セットの生成に使用します。KeyStore: キーストアの作成および管理に使用します。 キーストアは、キーのデータベースです。 キーストア内の非公開キーには、それらのキーに関連付けられた証明書チェーンがあります。証明書チェーンは、対応する公開キーを認証します。 また、キーストアには、信頼できるエンティティからの証明書も格納されています。 CertificateFactory: 公開キーの証明書および証明書失効リスト(CRL)の作成に使用します。CertPathBuilder: 証明書チェーン(証明書パスとも呼ばれる)の構築に使用します。CertPathValidator: 証明書チェーンの検証に使います。CertStore: リポジトリからCertificateとCRLを取得するために使用します。ノート: ジェネレータ(generator)は最新の内容でオブジェクトを作成しますが、ファクトリ(factory)は既存の構成要素(エンコードなど)からオブジェクトを作成します。
このセクションでは、JCAで提供するコア・クラスとインタフェースについて説明します。
ProviderクラスとSecurityクラスSecureRandom、MessageDigest、Signature、Cipher、Mac、KeyFactory、SecretKeyFactory、KeyPairGenerator、KeyGenerator、KeyAgreement、AlgorithmParameters、AlgorithmParameterGenerator、KeyStore、およびCertificateFactory、エンジン・クラスKeyインタフェースおよびクラスノート: CertPathBuilder、CertPathValidator、およびCertStoreエンジン・クラスの詳細は、「Java PKIプログラマーズ・ガイド」を参照してください。
このガイドでは、もっとも有用な高レベルのクラスについて最初に説明し(Provider、Security、SecureRandom、MessageDigest、Signature、Cipher、およびMac)、次にさまざまなサポート・クラスについて説明します。 現時点では、キー(公開、非公開、および秘密)はさまざまなJCAクラスによって生成および表現され、高レベルのクラスによってその操作の一部として使用されるという説明だけで十分です。
このセクションでは、各クラスおよびインタフェースのメイン・メソッドの署名を示します。 該当する例のセクションには、これらのクラス(MessageDigest、Signature、KeyPairGenerator、SecureRandom、KeyFactory、およびキー仕様クラス)の例があります。
関連するSecurity APIパッケージの完全なリファレンス・ドキュメントは、次のパッケージのサマリーにあります。
java.securityjavax.cryptojava.security.certjava.security.specjavax.crypto.spec java.security.interfaces javax.crypto.interfacesProviderクラス「暗号化サービス・プロバイダ」(このドキュメントでは「プロバイダ」とも呼ばれる)とは、JDK Security APIの暗号化機能のサブセットの固定実装を提供するパッケージまたはパッケージ・セットです。 Providerクラスは、このようなパッケージやパッケージ・セットへのインタフェースです。 プロバイダ名、バージョン番号、その他の情報にアクセスするためのメソッドを備えています。 Providerクラスを使用すると、暗号化サービスの実装を登録するだけでなく、その他のセキュリティ・サービスの実装を登録することもできます。その他のセキュリティ・サービスは、JDK Security APIまたはその拡張機能の1つの一部として定義されている場合があります。
暗号化サービスの実装を提供するには、エンティティ(開発グループなど)は実装コードを作成し、Providerクラスのサブクラスを生成します。 Providerサブクラスのコンストラクタは、プロバイダが実装したサービスを検索するためにJDK Security APIが使用する各種プロパティの値を設定します。 つまり、サブクラスは、サービスを実装するクラス名を指定します。
プロバイダ・パッケージが実装できるサービスには、さまざまな種類があります。詳細については、「エンジン・クラスとアルゴリズム」を参照してください。
実装が違うと、特徴も違ってくる場合があります。 ソフトウェア・ベースのものもあれば、ハードウェア・ベースのものもあります。 プラットフォーム独立のものもあれば、プラットフォーム固有のものもあります。 また、レビューや評価用に使えるプロバイダ・コードもあれば、使えないものもあります。 JCAでは、エンド・ユーザーと開発者の双方が独自のニーズを決定できます。
このセクションでは、エンド・ユーザーのニーズに合った暗号実装のインストール方法、および開発者のニーズに合った実装の要求方法を説明します。
ノート: プロバイダの実装の詳細は、ガイド「Java暗号化アーキテクチャ用プロバイダの実装方法」を参照してください。
このAPIの各エンジン・クラスについては、実装インスタンスが要求され、そのエンジン・クラスのgetInstanceメソッドの1つを呼び出してインスタンスが生成されます。このときに、希望するアルゴリズムの名前と実装するプロバイダ(またはProviderクラス)の名前(オプション)を指定します。
static EngineClassName getInstance(String algorithm)
throws NoSuchAlgorithmException
static EngineClassName getInstance(String algorithm, String provider)
throws NoSuchAlgorithmException, NoSuchProviderException
static EngineClassName getInstance(String algorithm, Provider provider)
throws NoSuchAlgorithmException
EngineClassNameは、希望するエンジンの型(MessageDigest/Cipher/その他)です。 たとえば、
MessageDigest md = MessageDigest.getInstance("SHA-256");
KeyAgreement ka = KeyAgreement.getInstance("DH", "SunJCE");
これらはそれぞれ、SHA-256 MessageDigestオブジェクトおよびDH KeyAgreementオブジェクトのインスタンスを返します。
「付録A」には、Java環境での使用について標準化されている名前のリストがあります。 一部のプロバイダには、同じアルゴリズムを参照する別名がある場合もあります。 たとえば、SHA256アルゴリズムは、SHA-256と呼ばれる場合があります。 必ずしもすべてのプロバイダが同じようにアルゴリズム名の別名を持つわけではないため、アプリケーションは別名ではなく標準名を使用する必要があります。
ノート: アルゴリズム名の大文字と小文字は区別されません。 たとえば、次の呼出しはすべて等価とみなされます。
MessageDigest.getInstance("SHA256")
MessageDigest.getInstance("sha256")
MessageDigest.getInstance("sHa256")
プロバイダを指定しない場合、getInstanceは、指定されたアルゴリズムに関連する、要求された暗号化サービスの実装に対応する登録プロバイダを検索します。 どのJava仮想マシン(JVM)でも、プロバイダは指定の「優先順位」でインストールされます。優先順位とは、特定のプロバイダの指定がない場合に、プロバイダ・リストが検索される順位です。 たとえば、JVMに、PROVIDER_1とPROVIDER_2という2つのプロバイダがインストールされているとします。 次のようになっていると仮定します。
PROVIDER_1はSHA-256およびDESedeを実装。 PROVIDER_1の優先順位が1 (最高の優先順位)。 PROVIDER_2はSHA256withDSA、SHA-256、RC5およびRSAを実装。 PROVIDER_2の優先順位は2。 PROVIDER_1が最高の優先順位を持ち最初に検索されるので、PROVIDER_1実装が返される。 PROVIDER_1が検索される。 実装が見つからないので次にPROVIDER_2が検索される。 実装が見つかるので、このプロバイダが返される。 NoSuchAlgorithmExceptionがスローされる。 プロバイダ引数を持つgetInstanceメソッドは、必要なアルゴリズムを提供するプロバイダを指定したい開発者のためのものです。 たとえば、連邦証明書を受け取ったプロバイダ実装を連邦局が使いたいとします。 PROVIDER_1のSHA256withDSA実装はこの証明書を受け取っていないのに対し、PROVIDER_2のDSA実装がこれを受け取っているとします。
連邦局のプログラムは次の呼出しを実行します。PROVIDER_2が認証された実装を持つため、これを指定します。
Signature dsa = Signature.getInstance("SHA256withDSA", "PROVIDER_2");
この場合、PROVIDER_2がインストールされていないと、インストールされている別のプロバイダが要求アルゴリズムを実装している場合であっても、NoSuchProviderExceptionがスローされます。
プログラムは、(SecurityクラスのgetProvidersメソッドを使用して)インストールされているプロバイダのリストを獲得し、リストから選択することもできます。
ノート: 汎用アプリケーションは、特定のプロバイダから暗号化サービスを要求しないようにする必要があります。 そうでない場合、アプリケーションは特定のプロバイダに結び付けられることになり、そのプロバイダはほかのJava実装で使用できない可能性があります。 特定の要求されたプロバイダよりも優先順位が高い、使用可能な最適化されたプロバイダ(たとえば、PKCS11によるハードウェア・アクセラレータまたはMicrosoftのMSCAPIなどのネイティブOS実装)を利用できない場合もあります。
暗号化プロバイダを使用するには、最初にインストールし、次に静的または動的に登録する必要があります。 さまざまなSunプロバイダがこのリリースに同梱されており(SUN、SunJCE、SunJSSE、SunRsaSignなど)、すでにインストールおよび登録されています。 以降のセクションでは、追加プロバイダのインストールおよび登録方法について説明します。
プロバイダ・クラスのインストールには、次の2つの方法があります。
クラスが格納されているzipまたはJARファイルを、classpathの任意の場所に置く。 一部のアルゴリズムの型(暗号)では、プロバイダは署名されたJARファイルである必要があります。
プロバイダは、標準の拡張ディレクトリに配置された場合、インストール型拡張機能と見なされます。 JDKでは、次の場所に配置されます。
<java-home>/lib/ext<java-home>\lib\extここで、<java-home>は、実行ソフトウェアのインストール先ディレクトリ(JRE (Java Runtime Environment)のトップレベル・ディレクトリまたはJava JDKソフトウェアのjreディレクトリ)を指します。 たとえば、JDK 6を/home/user1/JDK1.6.0ディレクトリ(Solaris)、またはC:\Java\JDK1.6.0ディレクトリ(Microsoft Windows)にインストールした場合、JARファイルを次のディレクトリにインストールする必要があります。
/home/user1/JDK1.6.0/jre/lib/extC:\JDK1.6.0\jre\lib\ext同様に、JRE 6を/home/user1/jre1.6.0ディレクトリ(Solaris)、またはC:\jre1.6.0ディレクトリ(Microsoft Windows)にインストールした場合、JARファイルを次のディレクトリにインストールする必要があります。
/home/user1/jre1.6.0/lib/extC:\jre1.6.0\lib\ext次のステップでは、登録済みプロバイダのリストにプロバイダを追加します。 プロバイダは、Javaアプリケーションを実行する前にセキュリティ・プロパティ構成ファイルを編集して静的に登録するか、または実行時にメソッドを呼び出して動的に登録できます。 破壊行為を行うプロバイダのインストールが実行環境に追加されるのを防ぐために、プロバイダを動的に登録しようとするアプリケーションは適切な実行特権を保持している必要があります。
構成ファイルは、次のディレクトリに格納されています。
<java-home>/lib/security/java.security<java-home>\lib\security\java.security登録されたプロバイダごとに、このファイルは次の形式の文を保持します。
security.provider.n=masterClassName
これはプロバイダを宣言し、その優先順位nを指定します。 優先順位とは、特定プロバイダの指定がない場合に、要求されたアルゴリズムに関して検索されるプロバイダの順位です。 順位は1から始まり、1が最優先で次に2、3...と続きます。
masterClassNameには、プロバイダの「マスター・クラス」を完全修飾名で指定します。 プロバイダのドキュメントでそのマスター・クラスを指定します。 このクラスは、常にProviderクラスのサブクラスです。 サブクラス・コンストラクタは、プロバイダが実装したアルゴリズムやその他の機能を検索するためにJava暗号化APIに必要な各種プロパティの値を設定します。
JDKには、「SUN」や「SunJCE」など、標準で自動的にインストールおよび構成されるプロバイダが付属します。 「SUN」プロバイダのマスター・クラスは、sun.security.providerパッケージ内のSUNクラスです。対応するjava.securityファイル・エントリは、次のとおりです。
security.provider.5=sun.security.provider.Sun
別のJCAプロバイダを利用する場合、代替プロバイダを参照する行を追加し、優先順位を指定します。必要に応じてほかのプロバイダの順序も調整します。
CompanyXのプロバイダのマスター・クラスがcom.companyx.provider.ProviderXで、このプロバイダを優先順位が第8位のプロバイダとして構成するとします。 このためには、次の行をjava.securityファイルに追加します。
security.provider.8=com.companyx.provider.ProviderX
SecurityクラスのaddProviderまたはinsertProviderAtのどちらかのメソッドを呼び出します。 この登録はVMインスタンス間で持続的ではなく、適切な特権を持つ「信頼できる」プログラムでしか実行できません。 「セキュリティ」を参照してください。
暗号化プロバイダが使用され(つまり、Cipher、KeyAgreement、KeyGenerator、MacまたはSecretKeyFactoryの実装を提供するプロバイダ)、プロバイダがインストール型拡張機能ではない場合、JCAを使用するアプレットまたはアプリケーションをセキュリティ・マネージャがインストールされている環境で実行するときはアクセス権を付与する必要がある場合があります。 通常、アプレットの実行時にはセキュリティ・マネージャが常にインストールされます。アプリケーションの場合でも、アプリケーション自体のコードまたはコマンド行引数で指定することにより、セキュリティ・マネージャをインストールできます。 デフォルト・システムのポリシー構成ファイルは、インストール型拡張機能(つまり、拡張機能ディレクトリにインストールされている)にすべてのアクセス権を付与するため、インストール型拡張機能にアクセス権を設定する必要はありません。
ベンダーの提供する各プロバイダ用ドキュメントには、必須のアクセス権やそれを付与する方法が記載されています。 たとえば、拡張機能がインストール型ではなく、セキュリティ・マネージャがインストールされている場合、プロバイダに次のアクセス権を付与する必要があります。
java.lang.RuntimePermission "getProtectionDomain" (クラス保護ドメインを取得するため)。 プロバイダは、自己整合性チェックの実行過程で、独自の保護ドメインの取得が必要になる場合があります。 java.security.SecurityPermission "putProviderProperty.{name}" (プロバイダ・プロパティを設定するため、{name}には実際のプロバイダ名を指定)。たとえば、名前が「MyJCE」で、コードがmyjce_provider.jar内に存在するプロバイダにアクセス権を付与するサンプル・コードを次に示します。 この種の文は、ポリシー・ファイルに記述されます。 この例では、myjce_provider.jarファイルは/localWorkディレクトリに格納されるものとします。
grant codeBase "file:/localWork/myjce_provider.jar" {
permission java.lang.RuntimePermission "getProtectionDomain";
permission java.security.SecurityPermission
"putProviderProperty.MyJCE";
};
Providerクラス・メソッド各Providerクラス・インタフェースは、名前(現時点では大文字小文字を区別する)、バージョン番号、およびプロバイダとそのサービスの文字列記述を持ちます。 次のメソッドを呼び出して、この情報についてProviderのインスタンスを照会できます。
public String getName() public double getVersion() public String getInfo()
SecurityクラスSecurityクラスは、インストールされているプロバイダおよびセキュリティに関するプロパティを管理します。 このクラスに含まれるのは、インスタンスが生成されないstaticメソッドだけです。 プロバイダの追加または削除、およびSecurityプロパティの設定用メソッドは、信頼できるプログラムによってしか実行できません。 現時点での「信頼できるプログラム」は、次のどれかです。
実行されるコードは、常に、特定の「コード・ソース」から来ると考えられます。 コード・ソースには、そのコードの元の場所(URL)だけでなく、コードの署名に使用された非公開キーに対応する任意の公開キーへの参照も含まれています。 コード・ソース内の公開キーは、ユーザーのキーストアの別名(記号名)で参照します。
ポリシー構成ファイル内では、コード・ソースは、コード・ベース(URL)、および(signedByで始まる)別名の2つのコンポーネントで表されます。別名は、コードの署名の検証に使う必要のある公開キーを含んだキーストアのエントリを識別します。
このようなファイル内の各「許可」文は、指定されたコード・ソースにアクセス権のセットを与え、許可されるアクションを指定します。
次に、ポリシー構成ファイルの例を示します。
grant codeBase "file:/home/sysadmin/", signedBy "sysadmin" {
permission java.security.SecurityPermission "insertProvider.*";
permission java.security.SecurityPermission "removeProvider.*";
permission java.security.SecurityPermission "putProviderProperty.*";
};
この構成ファイルは、ローカル・ファイル・システム上の/home/sysadmin/ディレクトリ内の署名されたJARファイルからロードされたコードが、プロバイダの追加または削除、あるいはプロバイダのプロパティの設定ができることを指定します。 JARファイルの署名は、ユーザーのキーストア内の別名sysadminで参照する公開キーを使って検証できます。
コード・ソースを指定するコンポーネントは、どちらも(または両方を)省略可能です。 次に、codeBaseを省略した構成ファイルの例を示します。
grant signedBy "sysadmin" {
permission java.security.SecurityPermission "insertProvider.*";
permission java.security.SecurityPermission "removeProvider.*";
};
このポリシーが施行された場合、sysadminによって署名されたJARファイルのコードによるプロバイダの追加または削除が可能になります。JARファイルの場所は関係ありません。
次に、署名者を省略した例を示します。
grant codeBase "file:/home/sysadmin/" {
permission java.security.SecurityPermission "insertProvider.*";
permission java.security.SecurityPermission "removeProvider.*";
};
この場合、ローカル・ファイル・システムの/home/sysadmin/ディレクトリに置かれたコードは、プロバイダの追加と削除を行うことができます。 コードに署名は必要ありません。
codeBaseとsignedByを両方とも省略した例を次に示します。
grant {
permission java.security.SecurityPermission "insertProvider.*";
permission java.security.SecurityPermission "removeProvider.*";
};
この例では、コード・ソースを指定するコンポーネントがどちらも省略されています。したがって、出所がどこか、署名が付いているか、だれの署名が付いているかに関係なく、どのコードでもプロバイダの追加と削除が行えます。 この付与はセキュリティ・ホールを作り出す可能性があるため、絶対にお薦めしません。 信頼されていないコードによってプロバイダがインストールされ、それにより適切に機能する実装に依存する後続のコードに影響する可能性があります。 (たとえば、破壊行為を行うCipherオブジェクトが、受信した機密情報を取り込んで格納する場合があります。)
次の表に、Securityクラス内のメソッドのうち、インストールされているProviderを照会するために使用できるメソッドと、実行時にプロバイダをインストールまたは削除するためのメソッドの要約を示します。
| メソッド | 説明 |
|---|---|
static Provider[] getProviders() |
インストールされているすべてのプロバイダを含む配列(つまり、各パッケージ・プロバイダのProviderサブクラス)を返します。 配列内のProviderの順序は、優先順位に従います。 |
static Provider getProvider |
providerNameというProviderを返します。 このProviderが見つからないとnullを返します。 |
| メソッド | 説明 |
|---|---|
static int addProvider(Provider provider) |
インストールされたProviderリストの末尾にProviderを追加します。 Providerが追加された優先順位を返すか、すでにインストールされているためにProviderが追加されなかった場合は-1を返します。 |
static int insertProviderAt (Provider provider, int position) |
指定された位置に新規のProviderを追加します。 あるプロバイダが要求された場所にインストールされた場合は、それまでその場所にあったプロバイダ、およびpositionより後の位置のすべてのプロバイダの順位は、1つ後になり、一覧の最後尾に向かって1つ移動します。 Providerが追加された優先順位を返すか、すでにインストールされているためにProviderが追加されなかった場合は-1を返します。 |
| メソッド | 説明 |
|---|---|
static void removeProvider(String name) |
指定された名前のProviderを削除します。 プロバイダがインストールされていない場合は、何もせず復帰します。 指定されたプロバイダが削除されると、そのプロバイダよりもあとの位置にあるすべてのプロバイダの位置が1つ前になり、インストールされたプロバイダ一覧の先頭に向かって1つ移動します。 |
ノート: プロバイダの優先順位を変更する場合は、まず目的のプロバイダを削除してから、希望する優先順位の位置に挿入し直す必要があります。
Securityクラスは、システムに関するセキュリティのプロパティ・リストを保持します。 これらのプロパティはSystemプロパティに似ていますが、セキュリティ関連です。 これらのプロパティは、静的にも動的にも設定できます。 静的セキュリティ・プロパティの例("security.provider.i"セキュリティ・プロパティによるプロバイダの静的な登録)についてはすでに説明しました。 プロパティを動的に設定する場合、信頼できるプログラムは次のメソッドを使用できます。
static String getProperty(String key) static void setProperty(String key, String datum)ノート: セキュリティ・プロバイダのリストは、VMの起動中に確立されます。したがって、前述のメソッドは、プロバイダ・リストを変更する場合に使用してください。
構成ファイルは、次のディレクトリに格納されています。
<java-home>/lib/security/java.security<java-home>\lib\security\java.securitySecureRandomクラスSecureRandomクラスは、乱数ジェネレータ(RNG)の機能を提供するエンジン・クラスです。 暗号として強力な乱数を生成する点がjava.lang.Randomクラスとは異なります。 ジェネレータのランダム性が不十分な場合、保護メカニズムが簡単に脅かされることになります。 暗号化キーやアルゴリズムのパラメータの生成など、乱数は暗号化全体で使用されます。
すべてのJava SE実装は、java.security.Securityクラスのsecurerandom.strongAlgorithmsプロパティで提供される、もっとも強力な(もっともランダム性の高い)SecureRandomの実装を示す必要があります。 この実装は、特に強力な乱数値が必要な場合に使用できます。
SecureRandomオブジェクトの作成SecureRandomのインスタンスは、いくつかの方法で取得できます。
すべてのJava SE実装は、引数なしのコンストラクタnew SecureRandom()を使用してデフォルトのSecureRandomを提供します。
SecureRandomの特定の実装を取得するには、いずれかのgetInstance()staticファクトリ・メソッドを使用します。
java.security.Securityクラスのsecurerandom.strongAlgorithmsプロパティで定義される強力なSecureRandom実装を取得するには、getInstanceStrong()メソッドを使用します。 このプロパティは、重要な値を生成するのに適したプラットフォーム実装をリストします。
SecureRandomオブジェクトのシードまたは再シード呼出し側が、setSeedメソッドの1つへの呼出しで、getInstanceメソッドへの呼出しに従う場合以外は、SecureRandomの実装は、ジェネレータの内部状態自体を完全にランダム化しようとします。
synchronized public void setSeed(byte[] seed) public void setSeed(long seed)
SecureRandomオブジェクトに一度シードが入れられると、これはオリジナルのシードと同じようにランダムにビットを生成します。
SecureRandomオブジェクトは、setSeedメソッドの1つを使って常に再シードされる可能性があります。 指定されたシードは、既存のシードと置き換えられるのではなく、既存のシードに追加されます。したがって、呼出しを繰り返しても、ランダム性が減少しないことが保証されます。
SecureRandomオブジェクトの使用ランダム・バイトを得るには、呼出し側は単純に任意の長さの配列を渡します。すると、この配列にランダム・バイトが入ります。
synchronized public void nextBytes(byte[] bytes)
ほかの乱数ジェネレータにシードを入れる場合など、必要な場合は、generateSeedメソッドを呼び出して、与えられた数のシード・バイトを生成できます。
byte[] generateSeed(int numBytes)
MessageDigestクラスMessageDigestクラスはエンジン・クラスで、安全な暗号化メッセージ・ダイジェスト(SHA-256やSHA-512など)の機能を提供するように設計されています。 安全な暗号化メッセージ・ダイジェストは、任意サイズの入力(バイト配列)を取り、固定サイズ出力を生成します。これをダイジェストまたはハッシュと言います。
たとえば、SHA-256のアルゴリズムは32バイトのダイジェストを生成し、SHA-512のアルゴリズムは64バイトのダイジェストを生成します。
ダイジェストには次の2つの特徴があります。
メッセージ・ダイジェストを使い、一意で、信頼できるデータ識別子を生成します。 データ識別子をデータの「チェックサム」または「デジタル指紋」と呼ぶこともあります。 メッセージのうちの1ビットを変更するだけで、異なるダイジェスト値が生成されます。
メッセージ・ダイジェストには多くの用途があり、意図的かどうかにかかわらず、いつデータが変更されたかを判断できます。 近年、一般的なアルゴリズムに弱点があるかどうかを判断するために大きな努力が払われていますが、結果はさまざまです。 ダイジェスト・アルゴリズムを選択する場合は、最近のリファレンスを調べて、そのステータスと当面のタスクでの妥当性を判断する必要があります。
MessageDigestオブジェクトの作成ダイジェスト計算の最初のステップは、メッセージ・ダイジェストのインスタンスを生成します。 MessageDigestオブジェクトは、MessageDigestクラスのgetInstance() staticファクトリ・メソッドの1つを使用することによって取得されます。 ファクトリ・メソッドは、初期化されたメッセージ・ダイジェスト・オブジェクトを返します。 したがって、このあとで初期化を行う必要はありません。
データのダイジェストを計算するための次のステップは、初期化したメッセージ・ダイジェスト・オブジェクトにデータを入れることです。 すべてを一度に、またはチャンクで入れることができます。 次のいずれかのupdateメソッドを呼び出すことによって、メッセージ・ダイジェストに渡すことができます。
void update(byte input) void update(byte[] input) void update(byte[] input, int offset, int len)
updateを呼び出してデータ・チャンクを入れたあとで、次のdigestメソッドのうちのどれか1つを使用してダイジェストを計算します。
byte[] digest() byte[] digest(byte[] input) int digest(byte[] buf, int offset, int len)
最初のメソッドは、計算されたダイジェストを返します。 2番目のメソッドは、ダイジェストのバイト配列を返すdigest()を呼び出す前に、入力バイトの配列で最後のupdate(input)を行います。 最後のメソッドは、offsetから開始する提供バッファbuf内に算出ダイジェストを格納します。lenは、ダイジェストに割り当てられたbuf内のバイト数です。このメソッドは、実際にbuf内に格納されたバイト数を返します。 バッファ内に十分な領域がない場合、メソッドは例外をスローします。
詳細は、「コード例」セクションのMessageDigestの計算の例を参照してください。
SignatureクラスSignatureクラスはエンジン・クラスであり、DSAやRSAwithMD5などの暗号化デジタル署名アルゴリズムの機能を提供するように設計されています。 安全な暗号化署名アルゴリズムは、任意サイズの入力と非公開キーを取り、署名と呼ばれる比較的短い(固定サイズの場合もよくある)バイト文字列を生成します。このプロパティは次のとおりです。
また、指定の署名が、関連データの実際の認証署名かどうかを検証することもできます。
Signatureオブジェクトは、非公開キーでの署名に対して初期化され、署名するデータを与えられます。 結果の署名バイトは、通常は署名されたデータとともに保持されます。 検証が必要な場合、別のSignatureオブジェクトが作成され、検証に対して初期化されて対応する公開キーを与えられます。 データおよび署名バイトはSignature (署名)オブジェクトに渡され、データと署名が一致した場合に、Signatureオブジェクトは成功を報告します。
署名はメッセージ・ダイジェストに似ていますが、提供する保護の種類の点で目的が大きく異なっています。 実際、「SHA256withRSA」などのアルゴリズムは、メッセージ・ダイジェスト「SHA256」を使用して最初に大きなデータ・セットを管理しやすい形式に「圧縮」し、次に「RSA」アルゴリズムで結果の32バイトのメッセージ・ダイジェストに署名します。
データの署名および検証の例は、例のセクションを参照してください。
Signatureオブジェクトの状態Signatureオブジェクトはモデル・オブジェクトです。 つまり、Signatureオブジェクトは、常に指定の状態にあり、この状態で1つの型の操作だけを実行できます。 状態は、個々のクラスで定義したfinal (ファイナル)整数の定数で表されます。
Signatureオブジェクトに可能な状態は、次の3つです。
UNINITIALIZEDSIGNVERIFYSignatureオブジェクトがはじめて生成されるときは、UNINITIALIZEDの状態です。 Signatureクラスは、状態をSIGNに変更するinitSignと、状態をVERIFYに変更するinitVerifyの2つの初期化メソッドを定義します。
Signatureオブジェクトの作成署名を付けたり、検証したりする最初のステップは、Signatureのインスタンスを生成します。 Signatureオブジェクトは、Signature getInstance() staticファクトリ・メソッドの1つを使用することによって取得されます。
Signatureオブジェクトの初期化Signatureオブジェクトを使うには、最初に必ずこれを初期化します。 初期化メソッドは、オブジェクトを署名用に使うか検証用に使うかに応じて異なります。
署名で使う場合、オブジェクトは最初に、署名を生成するエンティティの非公開キーを使って初期化する必要があります。 この初期化は、次のメソッドを呼び出して実行します。
final void initSign(PrivateKey privateKey)このメソッドでは、
SignatureオブジェクトはSIGN状態になります。
Signatureオブジェクトを検証で使う場合は、最初に、署名を検証するエンティティの公開キーを使って初期化する必要があります。 この初期化は、次のいずれかのメソッドを呼び出して実行します。
final void initVerify(PublicKey publicKey)
final void initVerify(Certificate certificate)
このメソッドでは、SignatureオブジェクトはVERIFY状態になります。
Signatureオブジェクトを署名用に初期化した場合(このオブジェクトがSIGN状態の場合)、署名を付けるデータをオブジェクトに入れることができます。 これは、次のupdateメソッドのどれか1つを1回または複数回呼び出して実行します。
final void update(byte b) final void update(byte[] data) final void update(byte[] data, int off, int len)
署名を付けるデータがすべてSignatureオブジェクトに入るまで、updateメソッドを呼び出します。
署名を生成するには、signメソッドの1つを呼び出すだけです。
final byte[] sign() final int sign(byte[] outbuf, int offset, int len)
最初のメソッドは、署名の結果をバイト配列で返します。 2番目のメソッドは、オフセット位置offsetから開始する提供バッファoutbuf内に署名の結果を格納します。lenは、署名に割り当てられたoutbuf内のバイト数です。 このメソッドは、実際に格納されたバイト数を返します。
署名のエンコードは、アルゴリズムに依存します。 Java暗号化アーキテクチャでのASN.1エンコードの使用方法については、標準名のドキュメントを参照してください。
signメソッドを呼び出すと、signature (署名)オブジェクトは、initSignを呼び出して最初に署名用に初期化されたときの状態にリセットされます。 つまり、オブジェクトをリセットするので、必要であればupdateとsignを新たに呼び出して、同じ非公開キーを使って別の署名を生成できます。
あるいは異なる非公開キーを指定して、initSignへの新規呼出しを作成したり、(署名を検証するためにSignatureオブジェクトを初期化する目的で) initVerifyへの新規呼出しを作成したりできます。
Signatureオブジェクトを検証用に初期化した場合(VERIFY状態にある場合)は、指定の署名が、それに関連したデータの実際の認証署名であるかどうかを検証できます。 この処理を開始するには、検証データ(署名自体ではない)をオブジェクトに入れます。 データは、次のいずれかのupdateメソッドを呼び出すことによってオブジェクトに渡されます。
final void update(byte b) final void update(byte[] data) final void update(byte[] data, int off, int len)
検証するデータがすべてSignatureオブジェクトに入るまで、updateメソッドを呼び出します。 これで、次のいずれかのverifyメソッドを呼び出して署名を検証できます。
final boolean verify(byte[] signature) final boolean verify(byte[] signature, int offset, int length)
引数は、署名を含むバイト配列である必要があります。 このバイト配列は、前にいずれかのsignメソッドの呼出しによって返された署名バイトを保持します。
verifyメソッドは、エンコードされた署名がupdateメソッドに入れたデータの認証署名かどうかを示すbooleanを返します。
verifyメソッドを呼び出すと、signature (署名)オブジェクトは、initVerifyを呼び出して検証用に初期化されたときの状態にリセットされます。 つまり、オブジェクトをリセットするので、initVerifyへの呼出しで指定した公開キーを持つ識別の別の署名を検証できます。
あるいは異なる非公開キーを指定し、(異なるエンティティの署名を検証するためにSignatureオブジェクトを初期化する目的で) initVerifyへの新規呼出しを作成したり、(署名を生成するためにSignatureオブジェクトを初期化する目的で) initSignへの新規呼出しを作成したりできます。
Cipherクラスは、暗号化および復号化で使用される暗号機能を提供します。 暗号化とは、データ(「クリアテキスト」と呼ばれる)および「キー」を受け取って、キーを知らないサード・パーティにとって無意味なデータ(「暗号テキスト」)を生成する処理のことです。 復号化はその逆で、暗号テキストおよびキーを受け取り、クリアテキストを生成する処理です。
暗号化には、対称 (秘密キーとも呼ばれる)と非対称 (または公開キー暗号化方式)という2つの主要な種類があります。 対称暗号化方式では、同じ秘密キーがデータの暗号化と復号化の両方を行います。 データの機密性を保持するには、キーを非公開にすることが重要となります。 一方、非対称暗号化方式は、公開キーと非公開キーのペアを使用してデータを暗号化します。 一方のキーで暗号化されたデータは、他方のキーで復号化されます。 ユーザーは最初に公開キーと非公開キーのペアを生成し、だれでもアクセスできる信頼できるデータベースに公開キーを発行します。 そのユーザーと安全に通信することを希望するユーザーは、取得した公開キーを使用してデータを暗号化します。 非公開キーの保有者のみが復号化できます。 このスキームでは、非公開キーを機密にすることが重要となります。
RSAなどの非対称アルゴリズムは、通常、対称アルゴリズムよりも大幅に遅くなります。 これらのアルゴリズムは、大量データを効率的に保護するように設計されていません。 実際には、非対称アルゴリズムは、対称アルゴリズムを初期化するために使用される少量の秘密キーを交換する場合に使用されます。
ブロックとストリームという2つの主要な種類の暗号があります。 ブロック暗号は、ブロック全体を一度に処理します。通常は長いバイト長になります。 完全な入力ブロックを作成するのにデータが不足する場合は、データのパディングが必要です。つまり、暗号化の前に、ダミー・バイトを追加して暗号のブロック・サイズの倍数にします。 これらのバイトは、復号化の段階で削除されます。 パディングは、アプリケーションにより、または「PKCS5PADDING」などのパディング型を使用するように暗号を初期化することによって実行できます。 これに対して、ストリーム暗号は入力データを一度に1つの小さい単位(通常はバイト、またはビットの場合もある)で処理します。 これにより、暗号は任意の量のデータをパディングを行わずに処理できます。
単純なブロック暗号を使用して暗号化する場合、2つの同じプレーン・テキストのブロックは、常に同じ暗号テキストのブロックを生成します。 暗号テキストを破ろうとする暗号解読者が繰返しのテキストのブロックに気付くと、簡単に解読されてしまいます。 テキストに複雑さを加えるために、フィードバック・モードは、暗号化アルゴリズムを適用する前に、以前の出力ブロックを使用して入力ブロックを変更します。 最初のブロックには初期値が必要です。この値は、初期化ベクトル(IV)と呼ばれます。 IVは暗号化の前に単純にデータを変更するため、ランダムである必要がありますが、必ずしも秘密である必要はありません。 CBC (Cipher Block Chaining)、CFB (Cipher Feedback Mode)、OFB (Output Feedback Mode)など、様々なモードがあります。 ECB (Electronic Codebook Mode)は、ブロック位置や他の暗号テキスト・ブロックの影響を受けないモードです。 ECB暗号テキストは同じプレーン・テキスト/キーを使用する場合は同一になるため、このモードは、通常は暗号化アプリケーションに適していません。使用しないようにしてください。
AESやRSAなどの一部のアルゴリズムでは、異なる長さのキーを使用可能ですが、それ以外(3DESなど)では固定です。 長いキーを使用する暗号化は、一般にメッセージ復元に対して抵抗力が強いことを意味します。 いつものように、セキュリティと時間という相反するものの間で折合いを付けるため、適切なキーの長さを選択します。
ほとんどのアルゴリズムは、バイナリ・キーを使用します。 ほとんどの人は、16進数で表現されていても、長いバイナリの数値を記憶できません。 文字のパスワードの方が記憶するのは簡単です。 通常、文字のパスワードは少数の文字([a-zA-Z0-9]など)から選択されるため、「パスワードベースの暗号化」(PBE)などのプロトコルは、文字のパスワードを取得して強力なバイナリ・キーを生成するように定義されています。 パスワードから暗号化キーを取得しようとする攻撃者のタスク(一般の辞書の「単語->値」のマッピングが事前に計算されている、いわゆる「ディクショナリ攻撃」による)を非常に時間のかかるものにするために、大半のPBE実装では、キーのランダム性を高めるために乱数への混入(saltと呼ばれる)が行われます。
関連データによる認証付き暗号化方式(AEAD)などの新しい暗号化モード(Galois/Counter Mode (GCM)など)では、データを暗号化し、同時に結果のメッセージを認証します。 結果のAEADタグ(Mac)の計算に、AAD (Additional Associated Data)を使用できますが、このAADデータは暗号テキストとして出力されません。 たとえば、一部のデータは機密にする必要がありませんが、変更を検出するため、タグ計算に含める必要がある場合があります。 タグ計算にAADを含めるには、Cipher.updateAAD()メソッドを使用できます。
GCMでのAES暗号はAEAD暗号で、非AEAD暗号とは使用パターンが異なります。 通常のデータ以外にAADも取ります(AADは暗号化/復号化用のオプションですが、暗号化/復号化を行うデータの前に必ず指定する必要があります)。 また、GCMを安全に使用するため、呼出し側ではキーとIVの組合せを再利用して暗号化を行わないでください。 つまり、暗号オブジェクトは、暗号化操作のたびに異なるパラメータ・セットで明示的に初期化しなおす必要があります。
SecretKey myKey = ...
byte[] myAAD = ...
byte[] plainText = ...
int myTLen = ...
byte[] myIv = ...
GCMParameterSpec myParams = new GCMParameterSpec(myTLen, myIv);
Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
c.init(Cipher.ENCRYPT_MODE, myKey, myParams);
// AAD is optional, if present, it must be supplied before any update/doFinal calls.
c.updateAAD(myAAD); // if AAD is non-null
byte[] cipherText = new byte[c.getOutputSize(plainText.length)];
c.doFinal(plainText, 0, plainText.length, cipherText); // conclusion of encryption operation
// To decrypt, same AAD and GCM parameters must be supplied
c.init(Cipher.DECRYPT_MODE, myKey, myParams);
c.updateAAD(myAAD);
byte[] recoveredText = c.doFinal(cipherText);
// MUST CHANGE IV VALUE if the same key were to be used again for encryption
byte[] newIv = ...;
myParams = new GCMParameterSpec(myTLen, newIv);
Cipherオブジェクトは、Cipher getInstance() staticファクトリ・メソッドの1つを使用することによって取得されます。 ここでのアルゴリズム名は、アルゴリズム名だけでなく「変換」を指定する点で、ほかのエンジン・クラスの場合と少し異なります。 変換は、指定された入力に対して実行し、なんらかの出力を生成する操作(または操作のセット)を説明する文字列です。変換には、暗号化アルゴリズム(AESなど)の名前が必ず含まれます。それにモードおよびパディング・スキームが続く場合もあります。
変換は、次の書式で記述されます。
たとえば、次は有効な変換です。
"AES/CBC/PKCS5Padding"
"AES"
変換名だけを指定すると、要求された変換の実装がその環境で使用可能かどうかをシステムが判別し、複数の実装が存在する場合には優先度の高い実装が存在することを返します。
変換名とパッケージ・プロバイダの両方を指定すると、システムは要求されたパッケージ内に要求された変換の実装が存在するかどうかを判別し、存在しない場合には例外をスローします。
アルゴリズム、モードおよびパディングを完全に指定した変換を使用することをお薦めします。 そうしないと、プロバイダはデフォルトを使用します。 たとえば、SunJCEプロバイダとSunPKCS11プロバイダは、多くの対称暗号でECBをデフォルト・モードとして、PKCS5Paddingをデフォルト・パディングとして使用します。
このため、SunJCEプロバイダでは、
Cipher c1 = Cipher.getInstance("AES/ECB/PKCS5Padding");
および
Cipher c1 = Cipher.getInstance("AES");
の2つの文は、等価になります。
ノート: ECBモードは最も簡単に使用できるブロック暗号モードで、JDK/JREではデフォルトになっています。 ECBは、単一のデータ・ブロックに対しては正常に機能しますが、複数のデータ・ブロックには絶対に使用しないでください。
CFBやOFBなどのモードを使用すると、ブロック暗号は、暗号の実際のブロック・サイズよりも小さい単位でデータを暗号化できます。 このようなモードを要求する場合、AES/CFB8/NoPaddingおよびAES/OFB32/PKCS5Padding変換で示されるように、一度に処理するビット数を、モード名に追加することで指定することもできます。 数値を指定しない場合、プロバイダ固有のデフォルトが使用されます。 (たとえば、SunJCEプロバイダではAESにデフォルトの128ビットが使用されます。) したがって、CFB8やOFB8などの8ビット・モードを使用することで、ブロック暗号をバイト指向のストリーム暗号に変換できます。
このドキュメントの「付録A」には、変換のアルゴリズム名、モード、およびパディング・スキーム・コンポーネントの指定に使用可能な標準名のリストが掲載されています。
ファクトリ・メソッドにより返されるオブジェクトは初期化されていないため、使用する前に初期化する必要があります。
getInstanceを介して取得されたCipherオブジェクトは、次の4つのモードのいずれかで初期化する必要があります。これらのモードは、Cipherクラスのfinal (ファイナル)整数の定数として定義されます。 モードは、シンボリック名で参照できます。次に、各モードのシンボリック名および目的の説明を示します。
java.security.Keyをラッピングする。java.security.Keyオブジェクトにアンラッピングする。Cipher初期化メソッドは、それぞれ操作モード・パラメータ(opmode)を取り、そのモード用のCipherオブジェクトを初期化します。 その他のパラメータには、キー(key)またはキーを含む証明書(certificate)、アルゴリズム・パラメータ(params)、および乱数の発生源(random)が含まれます。
Cipherオブジェクトを初期化する場合、次のいずれかのinitメソッドを呼び出します。
public void init(int opmode, Key key);
public void init(int opmode, Certificate certificate);
public void init(int opmode, Key key, SecureRandom random);
public void init(int opmode, Certificate certificate,
SecureRandom random);
public void init(int opmode, Key key,
AlgorithmParameterSpec params);
public void init(int opmode, Key key,
AlgorithmParameterSpec params, SecureRandom random);
public void init(int opmode, Key key,
AlgorithmParameters params);
public void init(int opmode, Key key,
AlgorithmParameters params, SecureRandom random);
初期化ベクトルなどのパラメータを必要とするCipherオブジェクトを暗号化用に初期化する場合、initメソッドにパラメータを何も指定しないと、ランダム・パラメータを生成するか、プロバイダ固有のパラメータ・セット(デフォルト)を使用することにより、基盤となる暗号実装が必須パラメータを提供すると見なされます。
ただし、パラメータを必要とするCipherオブジェクトを復号化用に初期化する場合、initメソッドにパラメータを何も指定しないと、使用したinitメソッドに応じてInvalidKeyExceptionまたはInvalidAlgorithmParameterException例外が発生します。
詳細は、「アルゴリズム・パラメータの管理」に関するセクションを参照してください。
復号化には、暗号化に使用したのと同じパラメータを使用する必要があります。
Cipherオブジェクトを初期化すると、それまでに獲得した状態がすべて失われることに留意してください。 つまり、Cipherを初期化することは、そのCipherの新規インスタンスを作成して初期化することと等価です。 たとえば、指定されたキーで復号化を行うためにCipherを初期化してから、暗号化を行うために初期化すると、復号化モードで獲得した状態はすべて失われます。
データの暗号化または復号化は、1つのステップで実行(「単一部分操作」)することも、複数のステップで実行(「複数部分操作」)することもできます。 データの長さが不明な場合、またはデータが長すぎて一度にメモリーに格納できない場合に、複数部分操作は有用です。
1つのステップでデータの暗号化または復号化を行う場合、次のいずれかのdoFinalメソッドを呼び出します。
public byte[] doFinal(byte[] input);
public byte[] doFinal(byte[] input, int inputOffset, int inputLen);
public int doFinal(byte[] input, int inputOffset,
int inputLen, byte[] output);
public int doFinal(byte[] input, int inputOffset,
int inputLen, byte[] output, int outputOffset)
複数のステップでデータの暗号化または復号化を行うには、次のいずれかのupdateメソッドを呼び出します。
public byte[] update(byte[] input);
public byte[] update(byte[] input, int inputOffset, int inputLen);
public int update(byte[] input, int inputOffset, int inputLen,
byte[] output);
public int update(byte[] input, int inputOffset, int inputLen,
byte[] output, int outputOffset)
複数部分操作は、上記のdoFinalメソッドのいずれか(最後のステップで入力データが残される場合)、または次のdoFinalメソッドのいずれか(最後のステップで入力データが残らない場合)を使って終了させます。
public byte[] doFinal();
public int doFinal(byte[] output, int outputOffset);
指定された変換の一部としてパディング(またはアンパディング)が要求された場合、すべてのdoFinalメソッドで、必要なパディング(またはアンパディング)操作がすべて処理されます。
doFinalを呼び出すと、Cipherオブジェクトはinitを呼び出して初期化した時の状態にリセットされます。 つまり、Cipherオブジェクトはリセットされて、さらにデータを暗号化または復号化するために使用できます(initの呼出しで指定された操作モードにより異なる)。
キーをラッピングすると、ある場所から別の場所へ安全に転送できます。
wrap/unwrap APIはキー・オブジェクトに対して直接機能するため、このAPIを使用するとコードの記述が容易になります。 次のメソッドを使用すると、ハードウェア・ベースのキーの安全な転送も可能になります。
Keyをwrapする場合、まずWRAP_MODEのCipherオブジェクトを初期化し、次のメソッドを呼び出します。
public final byte[] wrap(Key key);
ラップされたキーのバイト(wrapを呼び出した結果)を、そのラップを解除するほかのユーザーに提供する場合、受信者がunwrapを実行するのに必要な、次の追加情報も送信する必要があります。
Cipher.SECRET_KEY、Cipher.PRIVATE_KEY、またはCipher.PUBLIC_KEYのいずれか。キー・アルゴリズム名は、次に示すようにKeyインタフェースからgetAlgorithmメソッドを呼び出すことにより確認できます。
public String getAlgorithm();
wrapへの呼出しにより返されたバイトのラップを解除するには、UNWRAP_MODEのCipherオブジェクトを初期化してから、次を呼び出します。
public final Key unwrap(byte[] wrappedKey,
String wrappedKeyAlgorithm,
int wrappedKeyType));
ここで、wrappedKeyはラップへの呼出しにより返されたバイトを、wrappedKeyAlgorithmはラップされたキーに関連付けられたアルゴリズムを、wrappedKeyTypeはラップされたキーの型をそれぞれ指します。 これは、Cipher.SECRET_KEY、Cipher.PRIVATE_KEY、Cipher.PUBLIC_KEYのいずれかである必要があります。
基盤となるCipher実装により使用されるパラメータ(アプリケーションによりinitメソッドに明示的に渡されたか、基盤となる実装自体により生成された)は、getParametersメソッドを呼び出すことによりCipherオブジェクトから取得できます。このメソッドは、パラメータをjava.security.AlgorithmParametersオブジェクト(パラメータが使用されない場合はnull)として返します。 パラメータが初期化ベクトル(IV)の場合には、getIVメソッドを呼び出すことによってもパラメータを取得できます。
次の例では、パスワードベース暗号化(PBE)を実装するCipherオブジェクトを、パラメータを使用せずにキーのみを使用して初期化します。 ただし、選択されたパスワードベース暗号化用に選択されたアルゴリズムは、saltおよびiteration countという2つのパラメータを必要とします。 これらは、基盤となるアルゴリズム実装自体により生成されます。 アプリケーションは、生成されたパラメータを次の方法でCipherオブジェクトから取得できます。
import javax.crypto.*;
import java.security.AlgorithmParameters;
// get cipher object for password-based encryption
Cipher c = Cipher.getInstance("PBEWithHmacSHA256AndAES_256");
// initialize cipher for encryption, without supplying
// any parameters. Here, "myKey" is assumed to refer
// to an already-generated key.
c.init(Cipher.ENCRYPT_MODE, myKey);
// encrypt some data and store away ciphertext
// for later decryption
byte[] cipherText = c.doFinal("This is just an example".getBytes());
// retrieve parameters generated by underlying cipher
// implementation
AlgorithmParameters algParams = c.getParameters();
// get parameter encoding and store it away
byte[] encodedAlgParams = algParams.getEncoded();
復号化には、暗号化に使用したのと同じパラメータを使用する必要があります。 これらは、エンコーディングからインスタンス化することが可能であり、次に示すように、対応するCipherオブジェクトを復号化用に初期化する際に使用できます。
import javax.crypto.*;
import java.security.AlgorithmParameters;
// get parameter object for password-based encryption
AlgorithmParameters algParams;
algParams = AlgorithmParameters.getInstance("PBEWithHmacSHA256AndAES_256");
// initialize with parameter encoding from above
algParams.init(encodedAlgParams);
// get cipher object for password-based encryption
Cipher c = Cipher.getInstance("PBEWithHmacSHA256AndAES_256");
// initialize cipher for decryption, using one of the
// init() methods that takes an AlgorithmParameters
// object, and pass it the algParams object from above
c.init(Cipher.DECRYPT_MODE, myKey, algParams);
Cipherオブジェクトの初期化時にパラメータを一切指定せず、基盤となる実装がいずれかのパラメータを使用するかどうか不明な場合、CipherオブジェクトのgetParametersメソッドを呼び出して、返される値をチェックするだけで確認できます。 戻り値がnullの場合、パラメータが使用されなかったことを示します。
SunJCEプロバイダにより実装される次の暗号アルゴリズムは、パラメータを使用します。
javax.crypto.spec.IvParameterSpecクラスは、指定されたIVでのCipherオブジェクトの初期化に使用できます。 javax.crypto.spec.PBEParameterSpecクラスを使用して、saltおよび繰返し処理回数を指定してPBEアルゴリズムを実装するCipherオブジェクトを初期化できます(たとえば: PBEWithHmacSHA256AndAES_256)。 SealedObjectクラスを使用する場合、復号化操作に使用するアルゴリズム・パラメータの格納または転送について心配する必要はありません。 このクラスは、シール(暗号化)に使用されるパラメータを暗号化されたオブジェクト・コンテンツに添付します。また、アンシール(復号化)でも同じパラメータを使用します。
CipherのupdateおよびdoFinalメソッドの中には、呼出し側による出力バッファの指定が可能なものがあります。暗号化または復号化されたデータは、このバッファ内に出力されます。 この場合、暗号化または復号化操作の結果を保持できるだけの大きさのバッファを渡すことは重要です。
Cipher内の次のメソッドを使用して、設定すべき出力バッファのサイズを確認できます。
public int getOutputSize(int inputLen)
CipherベースのクラスCipherを内部的に使用して共通の暗号使用への簡単なアクセスを提供する、いくつかのヘルパー・クラスがあります。
このクラスは、通過するデータの暗号化または復号化を行うFilterInputStreamです。 これは、InputStreamまたはそのサブクラスのいずれか、およびCipherで構成されます。 CipherInputStreamは、Cipherオブジェクトの挿入先の、セキュリティ保護された入力ストリームを表します。 CipherInputStreamのreadメソッドは、基盤となるInputStreamから読み取られ、埋め込まれたCipherオブジェクトにより追加処理されたデータを返します。 Cipherオブジェクトは、CipherInputStreamで使用する前に完全に初期化する必要があります。
たとえば、埋め込まれたCipherが復号化用に初期化されている場合、CipherInputStreamは基盤となるInputStreamから読み込んだデータの復号化を試みてから、データをアプリケーションに返します。
このクラスは、上位クラスjava.io.FilterInputStreamおよびjava.io.InputStreamのセマンティックス(特にエラーに関するセマンティックス)に厳密に準拠します。 このクラスは、上位クラスで指定されたメソッドを正確に保持し、それらすべてをオーバーライドします。このため、埋め込まれた暗号によるデータの追加処理が可能になります。 さらに、このクラスは、上位クラスがスローしない例外をすべてキャッチします。 特に、skip(long)メソッドは、Cipherにより処理されたデータのみを無視します。
このクラスを使用するプログラマにとって、このクラスで定義またはオーバーライドされていないメソッド(上位クラスのいずれかにあとで追加された新規メソッドまたはコンストラクタ)を使用しないようにすることは重要です。これらのメソッド実装は、CipherInputStreamへのセキュリティ面の影響を考慮に入れていないためです。
使用方法の一例として、cipher1が暗号化用に初期化されている場合を考えてみましょう。 次のコードは、暗号およびFileInputStreamを含むCipherInputStreamを使用して、入力ストリーム・データを暗号化する方法を示します。
FileInputStream fis;
FileOutputStream fos;
CipherInputStream cis;
fis = new FileInputStream("/tmp/a.txt");
cis = new CipherInputStream(fis, cipher1);
fos = new FileOutputStream("/tmp/b.txt");
byte[] b = new byte[8];
int i = cis.read(b);
while (i != -1) {
fos.write(b, 0, i);
i = cis.read(b);
}
fos.close();
前述のプログラムは、ファイル/tmp/a.txtからコンテンツを読み取って暗号化し、結果(暗号化されたバイト)を/tmp/b.txtに格納します。
次の例は、CipherInputStreamおよびFileInputStreamの複数インスタンスを簡単に接続する方法を示します。 この例では、cipher1およびcipher2が、それぞれ暗号化および復号化用に(対応するキーを使用して)初期化されているものとします。
FileInputStream fis;
FileOutputStream fos;
CipherInputStream cis1, cis2;
fis = new FileInputStream("/tmp/a.txt");
cis1 = new CipherInputStream(fis, cipher1);
cis2 = new CipherInputStream(cis1, cipher2);
fos = new FileOutputStream("/tmp/b.txt");
byte[] b = new byte[8];
int i = cis2.read(b);
while (i != -1) {
fos.write(b, 0, i);
i = cis2.read(b);
}
fos.close();
前述のプログラムは、ファイル/tmp/a.txtの内容を/tmp/b.txtにコピーします。ただし、/tmp/a.txtからの読取り時に、最初に内容の暗号化、次に復号化が行われます。 実際のところ、このプログラムはテキストを暗号化した後、すぐに復号化を行うため、CipherInputStreamsのチェーンをわかりやすく示す以外は特に有用なものではありません。
CipherInputStreamの読取りメソッドは、基盤となる暗号からデータが返されるまでブロックします。 ブロック暗号が使用される場合、基盤となるInputStreamから暗号テキストの完全なブロックが取得される必要があります。
このクラスは、通過するデータの暗号化または復号化を行うFilterOutputStreamです。 これは、OutputStreamまたはそのサブクラスのいずれか、およびCipherで構成されます。 CipherOutputStreamは、Cipherオブジェクトの挿入先の、セキュリティ保護された出力ストリームを表します。 CipherOutputStreamのwriteメソッドは、埋め込まれたCipherオブジェクトを使ってデータを処理してから、基盤となるOutputStreamにデータを書き出します。 Cipherオブジェクトは、CipherOutputStreamで使用する前に完全に初期化する必要があります。
たとえば、埋め込まれたCipherが暗号化用に初期化されている場合、CipherOutputStreamはデータを暗号化してから、基盤となる出力ストリームに書き出します。
このクラスは、上位クラスjava.io.OutputStreamおよびjava.io.FilterOutputStreamのセマンティックス(特にエラーに関するセマンティックス)に厳密に準拠します。 このクラスは、上位クラスで指定されたメソッドを正確に保持し、それらすべてをオーバーライドします。このため、埋め込まれた暗号によるすべてのデータの追加処理が可能になります。 さらに、このクラスは、上位クラスがスローしない例外をすべてキャッチします。
このクラスを使用するプログラマにとって、このクラスで定義またはオーバーライドされていないメソッド(上位クラスのいずれかにあとで追加された新規メソッドまたはコンストラクタ)を使用しないようにすることは重要です。これらのメソッド実装は、CipherOutputStreamへのセキュリティ面の影響を考慮に入れていないためです。
使用方法の一例として、cipher1が暗号化用に初期化されている場合を考えてみましょう。 次のコードは、暗号およびFileOutputStreamを含むCipherOutputStreamを使用して、暗号化されたデータを出力ストリームに書き出す方法を示します。
FileInputStream fis;
FileOutputStream fos;
CipherOutputStream cos;
fis = new FileInputStream("/tmp/a.txt");
fos = new FileOutputStream("/tmp/b.txt");
cos = new CipherOutputStream(fos, cipher1);
byte[] b = new byte[8];
int i = fis.read(b);
while (i != -1) {
cos.write(b, 0, i);
i = fis.read(b);
}
cos.flush();
前述のプログラムは、ファイル/tmp/a.txtからコンテンツを読み取って暗号化し、結果(暗号化されたバイト)を/tmp/b.txtに格納します。
次の例は、CipherOutputStreamおよびFileOutputStreamの複数インスタンスを簡単に接続する方法を示します。 この例では、cipher1およびcipher2が、それぞれ復号化および暗号化用に(対応するキーを使用して)初期化されているものとします。
FileInputStream fis;
FileOutputStream fos;
CipherOutputStream cos1, cos2;
fis = new FileInputStream("/tmp/a.txt");
fos = new FileOutputStream("/tmp/b.txt");
cos1 = new CipherOutputStream(fos, cipher1);
cos2 = new CipherOutputStream(cos1, cipher2);
byte[] b = new byte[8];
int i = fis.read(b);
while (i != -1) {
cos2.write(b, 0, i);
i = fis.read(b);
}
cos2.flush();
上記のプログラムは、ファイル/tmp/a.txtの内容を/tmp/b.txtにコピーします。ただし、/tmp/b.txtに書き込む前に、内容の暗号化および復号化が行われます。
ブロック暗号アルゴリズムを使用する場合は、データが暗号化されて基盤となる出力ストリームに送信される前に、プレーン・テキスト・データの完全なブロックをCipherOutputStreamに与える必要があります。
このクラスのflushとcloseメソッドには、ほかに1つの重要な相違点があります。カプセル化されたCipherオブジェクトがパディングを有効にしてブロック暗号アルゴリズムを実装する場合、この相違点に特に留意する必要があります。
flushは、カプセル化されたCipherオブジェクトにより処理済みのバッファリングされた出力バイトをすべて強制的に書き出すことにより、基盤となるOutputStreamをフラッシュします。 カプセル化されたCipherオブジェクトによりバッファリングされ、処理待ち状態にあるバイトは、書き出されません。 closeは、基盤となるOutputStreamを閉じて、関連付けられたすべてのシステム・リソースを解放します。 カプセル化されたCipherオブジェクトのdoFinalメソッドを呼び出して、このオブジェクトによりバッファリングされたすべてのバイトを処理します。さらにflushメソッドを呼び出して、処理したバイトを基盤となるストリームに書き込みます。 プログラマは、このクラスを使用してオブジェクトを生成し、暗号化アルゴリズムを利用してその機密性を保護することができます。
java.io.Serializableインタフェースを実装するオブジェクトが指定された場合、元のオブジェクトを直列化形式(ディープ・コピー)でカプセル化するSealedObjectを作成し、AESなどの暗号化アルゴリズムを使用して、直列化された内容をシール(暗号化)することにより、機密性を保護できます。 その後、暗号化された内容の復号化(適正な復号化キーを使用)、および直列化解除を行うことにより、元のオブジェクトを復元できます。
一般的な使用法を、次のコード例に示します。オブジェクトをシールする場合、シール対象のオブジェクトからSealedObjectを作成し、直列化されたオブジェクト内容を暗号化する、完全に初期化されたCipherオブジェクトを作成します。 この例では、文字列This is a secretがAESアルゴリズムを使用してシールされます。 シール操作に使用されるすべてのアルゴリズム・パラメータは、SealedObjectの内部に格納されることに留意してください。
// create Cipher object
// NOTE: sKey is assumed to refer to an already-generated
// secret AES key.
Cipher c = Cipher.getInstance("AES");
c.init(Cipher.ENCRYPT_MODE, sKey);
// do the sealing
SealedObject so = new SealedObject("This is a secret", c);
シールされた元のオブジェクトは、次の異なる2つの方法で復元可能です。
Cipherオブジェクトを使用する方法。
c.init(Cipher.DECRYPT_MODE, sKey);
try {
String s = (String)so.getObject(c);
} catch (Exception e) {
// do something
};
この方法には、暗号解読キーに関する知識がなくても、シールされたオブジェクトのアンシールを実行できるという利点があります。 たとえば、あるパーティが暗号オブジェクトを必須の暗号解読キーを使って初期化したあとで、別のパーティに渡すと、そのパーティはシールされたオブジェクトをアンシールできます。
try {
String s = (String)so.getObject(sKey);
} catch (Exception e) {
// do something
};
この方法では、getObjectメソッドは、適切な暗号解読アルゴリズム用の暗号オブジェクトを作成し、シール済みのオブジェクトに格納された特定の暗号解読キーおよびアルゴリズム・パラメータ(存在する場合)を使用してそれを初期化します。 この方法の利点は、オブジェクトをアンシールするパーティが、オブジェクトのシールに使用したパラメータ(IVなど)を追跡する必要がないことです。
MessageDigestと同様に、メッセージ認証コード(MAC)は、送信された情報や信頼できないメディアに保存されている情報の整合性をチェックする方法を提供しますが、秘密キーが計算に含まれます。 適切なキーを持つ人だけが、受信したメッセージを検証できます。 一般に、メッセージ認証コードは、秘密キーを共有する2つのパーティ間で送信される情報の有効性を検証する場合に使用されます。
暗号化ハッシュ機能に基づくMACメカニズムは、HMACと呼ばれます。 HMACは、秘密共有キーと組み合せて、SHA-256などの任意の暗号化ハッシュ機能で使用できます。
Macクラスは、メッセージ認証コード(MAC)の機能を提供します。 「コード例」を参照してください。
Macオブジェクトの作成Macオブジェクトは、Mac getInstance() staticファクトリ・メソッドの1つを使用することによって取得されます。
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は、1つのステップで計算(「単一部分操作」)することも、複数のステップで計算(「複数部分操作」)することもできます。 データの長さが不明な場合、またはデータが長すぎて一度にメモリーに格納できない場合に、複数部分操作は有用です。
あるデータのMACを1回のステップで計算するには、次のdoFinalメソッドを呼び出します。
public byte[] doFinal(byte[] input);
複数のステップでデータのMACを計算するには、次のいずれかのupdateメソッドを呼び出します。
public void update(byte input);
public void update(byte[] input);
public void update(byte[] input, int inputOffset, int inputLen);
複数部分操作は、上記のdoFinalメソッド(最後のステップで入力データが残される場合)、または次のdoFinalメソッドのいずれか(最後のステップで入力データが残らない場合)を使って終了させます。
public byte[] doFinal();
public void doFinal(byte[] output, int outOffset);
Keyインタフェースここまでは、キーとは何か、キーはどのように生成および表現されるかについて詳細は説明せずに、JCAの高レベルの使用に重点を置いてきました。 ここではキーに注意を向けます。
java.security.Keyインタフェースは、すべての不透明なキーに関するトップ・レベルのインタフェースです。 すべての不透明なキー・オブジェクトが共有する機能を定義します。
「不透明な」キーの表現では、キーを構成するキー・データに直接アクセスできません。 つまり、「不透明」さにより、キーへのアクセスが、Keyインタフェースによって定義されるgetAlgorithm、getFormat、およびgetEncoded。
これと対照的なのが透明な表現で、この場合は、対応する仕様クラス内に定義されたgetメソッドの1つを使用して、各キー・データの値に個々にアクセスできます。
不透明なキーはすべて、次の3つの特性を持ちます。
AES、DSA、RSAなど)で、これらのアルゴリズムや関連アルゴリズム(SHA256withRSAなど)と連携して機能します。キー・アルゴリズムの名前は、次のメソッドを使用して取得します。
String getAlgorithm()
byte[] getEncoded()
String getFormat()
KeyGeneratorやKeyPairGeneratorなどのキー・ジェネレータ、証明書、キー仕様 (KeyFactoryを使用)、またはキー管理で使用するキーストア・データベースにアクセスするKeyStoreの実装から獲得します。 KeyFactoryを使って、アルゴリズム依存型の方法でエンコードされたキーを解析することが可能です。
また、CertificateFactoryを使って、認証を解析することも可能です。
次に、java.security.interfacesおよびjavax.crypto.interfacesパッケージ内のKeyインタフェースを拡張するインタフェースのリストを示します。
PublicKeyインタフェースとPrivateKeyインタフェースPublicKeyおよびPrivateKeyインタフェースはどちらもKeyインタフェースを拡張しますが、これらはメソッドを使わないインタフェースで、型の安全性および型の識別に使用します。
KeyPairクラスKeyPairクラスはキーのペア(公開キーと非公開キー)の簡単なホルダーです。 これには2つのpublicメソッドがあります。1つは非公開キーを返し、もう1つは公開キーを返します。
PrivateKey getPrivate() PublicKey getPublic()
Keyオブジェクトとキー仕様(KeySpec)は、キー・データの異なる2つの表現です。 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インタフェースこのインタフェースには、メソッドまたは定数が含まれていません。 このインタフェースの唯一の目的は、すべてのキー仕様をグループ化することおよびそれらのグループに安全な型を提供することです。 すべてのキー仕様で、このインタフェースを実装する必要があります。
KeySpecサブインタフェースKeyインタフェースと同じように、KeySpecインタフェースの同様のセットがあります。
EncodedKeySpecクラスこの抽象クラス(KeySpecインタフェースを実装する)は、エンコードされた形式の公開キーまたは非公開キーを表します。 そのgetEncodedメソッドは、次のエンコードされたキーを返します。
abstract byte[] getEncoded();このクラスの
getFormatメソッドは、次のエンコード形式の名前を返します。
abstract String getFormat();
固定実装PKCS8EncodedKeySpecおよびX509EncodedKeySpecについては、次のセクションを参照してください。
PKCS8EncodedKeySpecクラスこのクラスは、EncodedKeySpecのサブクラスで、PKCS8標準で指定された形式に従って、非公開キーのDERエンコードを表現します。 そのgetEncodedメソッドは、PKCS8標準に従ってエンコードされたキーのバイトを返します。 そのgetFormatメソッドは、文字列「PKCS#8」を返します。
X509EncodedKeySpecクラスこのクラスは、EncodedKeySpecのサブクラスで、X.509標準で指定された形式に従って、公開キーのDERエンコードを表現します。 そのgetEncodedメソッドは、X.509標準に従ってエンコードされたキーのバイトを返します。 そのgetFormatメソッドは、文字列「X.509」を返します。
JavaおよびJCA APIをはじめて使用する人は、ジェネレータとファクトリの区別がつかない場合があります。
ジェネレータは、新しいオブジェクトを生成する場合に使用されます。 ジェネレータは、アルゴリズム依存またはアルゴリズム非依存で初期化できます。 たとえば、Diffie-Hellman (DH)キー・ペアを作成するために、アプリケーションは必要なPおよびG値を指定できます。または、ジェネレータを適切なキーの長さで単純に初期化できます。ジェネレータは適切なPおよびG値を選択します。 どちらの場合も、ジェネレータはパラメータに基づいて新しいキーを生成します。
一方、ファクトリは、既存のオブジェクト型から別のオブジェクト型へデータを変換する場合に使用されます。 たとえば、アプリケーションにDH非公開キーの使用可能なコンポーネントがある場合があります。アプリケーションは、それらをKeySpecとしてパッケージ化できますが、KeyAgreementオブジェクトが使用できるようPrivateKeyオブジェクトに変換する必要があります。または逆の変換が必要な場合もあります。 または、それらに証明書のバイト配列があるが、CertificateFactoryを使用して、X509Certificateオブジェクトに変換する必要がある場合があります。 アプリケーションは、変換を行うためにファクトリ・オブジェクトを使用します。
KeyFactoryクラスKeyFactoryクラスは、不透明な暗号化Keyとキー仕様 (背後のキー・データの透明な表現)間の変換を実行するために設計されたエンジン・クラスです。
キー・ファクトリには双方向性があります。 つまり、これによって、与えられたキー仕様(キーのデータ)から不透明なキー・オブジェクトを構築することも、キー・オブジェクトの背後のキー・データを適切な形式で取得することもできます。
同一のキーに対して、複数の互換性のあるキー仕様を存在させることもできます。 たとえば、DSA公開キーは、コンポーネントy、p、q、およびgによって指定することも(java.security.spec.DSAPublicKeySpecを参照)、X.509標準に従ってDERエンコードを使用して指定することも(X509EncodedKeySpecを参照)できます。
キー・ファクトリは、互換性のあるキー仕様間の変換に使用できます。 互換性のあるキー仕様間の変換では、キーの構文解析が行われます。たとえば、X509EncodedKeySpecをDSAPublicKeySpecに変換する場合は、基本的にエンコードされたキーをコンポーネント単位に解析します。 例については、「キー仕様およびKeyFactoryを使った署名の生成と検証」のセクションの最後を参照してください。
KeyFactoryオブジェクトの作成KeyFactoryオブジェクトは、KeyFactory getInstance() staticファクトリ・メソッドの1つを使用することによって取得されます。
公開キー用のキー仕様がある場合は、generatePublicメソッドを使って、その仕様から不透明なPublicKeyオブジェクトを取得できます。
PublicKey generatePublic(KeySpec keySpec)
同様に、非公開キー用のキー仕様がある場合は、generatePrivateメソッドを使って、その仕様から不透明なPrivateKeyオブジェクトを取得できます。
PrivateKey generatePrivate(KeySpec keySpec)
Keyオブジェクトがある場合は、getKeySpecメソッドの呼出しによって、対応するキー仕様オブジェクトを取得できます。
KeySpec getKeySpec(Key key, Class keySpec)
keySpecは、キーのデータが返されるべき仕様クラスを識別します。 たとえば、DSAPublicKeySpec.classは、キーのデータがDSAPublicKeySpecクラスのインスタンスに返されるべきであることを指示します。
詳細は、例のセクションを参照してください。
このクラスは、秘密キーのファクトリを表します。 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つを使用することによって取得されます。
秘密キー用のキー仕様がある場合は、generateSecretメソッドを使って、その仕様から不透明なSecretKeyオブジェクトを取得できます。
SecretKey generateSecret(KeySpec keySpec)
Secret Keyオブジェクトがある場合は、getKeySpecメソッドの呼出しによって、対応するキー仕様オブジェクトを取得できます。
KeySpec getKeySpec(Key key, Class keySpec)
keySpecは、キーのデータが返されるべき仕様クラスを識別します。 たとえば、DESKeySpec.classは、キーのデータがDESKeySpecクラスのインスタンスに返されるべきであることを指示します。
KeyPairGeneratorクラスKeyPairGeneratorクラスはエンジン・クラスで、公開キーと非公開キーのペアの生成に使います。
キーのペアの生成方法には、アルゴリズム独立型とアルゴリズム固有型の2つがあります。 この2つの唯一の相違点は、オブジェクトの初期化にあります。
次に記述するメソッドの呼出し例は、例のセクションを参照してください。
KeyPairGeneratorの作成すべてのキーのペアは、最初にKeyPairGeneratorを使って生成します。 KeyPairGeneratorオブジェクトは、KeyPairGenerator getInstance() staticファクトリ・メソッドの1つを使用することによって取得されます。
KeyPairGeneratorの初期化特定のアルゴリズム用のキーのペアのジェネレータは、そのアルゴリズムで使うことができる公開キーまたは非公開キーを作成します。 また、アルゴリズム固有型のパラメータを生成された各キーに関連付けます。
まず、キーのペアを初期化してからでなければ、キーのペアはキーを生成できません。 ほとんどの場合、アルゴリズム独立型の初期化で十分です。 ただし、その他の場合は、アルゴリズム固有型の初期化を使用できます。
すべてのキー・ジェネレータは、キー・サイズおよび乱数発生の元の概念を共有します。 キー・サイズは、アルゴリズムごとに解釈が異なります。 たとえば、DSAアルゴリズムの場合、キー・サイズはモジュラスの長さと一致します。 特定のアルゴリズムのキー・サイズについては、標準名のドキュメントを参照してください。
普遍的に共有されるこれら2つの引数の型をとるinitializeメソッドがあります。
void initialize(int keysize, SecureRandom random)また、
keysize引数だけをとり、システムが提供する乱数の発生源を使用するinitializeメソッドもあります。
void initialize(int keysize)
上記のアルゴリズムに依存しないinitializeメソッドを呼び出した場合、パラメータが指定されないため、それぞれのキーに関連したアルゴリズム固有のパラメータが存在する場合、これをどのように扱うかはプロバイダに任されます。
アルゴリズムがDSAアルゴリズムで、モジュラスのサイズ(キー・サイズ)が512、768、または1024の場合は、SUNプロバイダはp、q、およびgパラメータ用に事前に計算した値を使用します。 モジュラスのサイズが前述の値の1つでない場合は、SUNプロバイダは、新しいパラメータのセットを作成します。 これらの3つのモジュラス・サイズ以外の、事前に計算されたパラメータ・セットを持つプロバイダが存在する可能性もあります。 また、事前に計算されたパラメータがなく、常に新しいパラメータ・セットを作成するプロバイダが存在する可能性もあります。
アルゴリズム固有のパラメータのセットがすでに存在する状況では(DSAの「コミュニティ・パラメータ」など)、AlgorithmParameterSpec引数を取るinitializeメソッドが2つあります。 このうちの一方はSecureRandom引数も取りますが、他方では、乱数の発生源はシステムによって提供されます。
void initialize(AlgorithmParameterSpec params,
SecureRandom random)
void initialize(AlgorithmParameterSpec params)
詳細は例のセクションを参照してください。
キーのペアの生成手順は、初期化(およびアルゴリズム)に関係なく、常に同じです。 必ずKeyPairGeneratorから次のメソッドを呼び出します。
KeyPair generateKeyPair()
generateKeyPairを呼び出すたびに、異なるキーのペアが作られます。
キー・ジェネレータは、対称アルゴリズム用の秘密キーを生成します。
KeyGeneratorの作成KeyGeneratorオブジェクトは、KeyGenerator getInstance() staticファクトリ・メソッドの1つを使用することによって取得されます。
特定の対称キー・アルゴリズムのキー・ジェネレータは、そのアルゴリズムで使用可能な対称キーを作成します。 また、生成されたキーに、アルゴリズム固有のパラメータ(存在する場合)を関連付けます。
キーの生成方法には、アルゴリズム独立型とアルゴリズム固有型の2つがあります。 この2つの唯一の相違点は、オブジェクトの初期化にあります。
すべてのキー・ジェネレータが、キー・サイズおよび乱数発生の元の概念を共有します。 普遍的に共有されるこれら2つの引数の型をとるinitメソッドが存在します。 また、keysize引数のみをとり、システムにより提供される乱数の発生源を使用するものや、乱数の発生源だけをとるものも存在します。
public void init(SecureRandom random);
public void init(int keysize);
public void init(int keysize, SecureRandom random);
前述のアルゴリズム独立型initメソッドを呼び出すときは、その他のパラメータは指定しないため、生成されたキーに関連付けられるアルゴリズム固有のパラメータが存在する場合、そのパラメータの処理は、プロバイダによって異なります。
アルゴリズム固有のパラメータのセットがすでに存在する状況では、AlgorithmParameterSpec引数を取る2つのinitメソッドが存在します。 このうちの一方はSecureRandom引数も取りますが、他方では、乱数の発生源はシステムによって提供されます。
public void init(AlgorithmParameterSpec params);
public void init(AlgorithmParameterSpec params, SecureRandom random);
クライアントが(initメソッドの呼出しによって) KeyGeneratorを明示的に初期化しない場合は、各プロバイダがデフォルトの初期化を提供(および文書化)する必要があります。
次のメソッドにより、秘密キーが生成されます。
public SecretKey generateKey();
キー協定とは、複数のパーティが秘密情報を交換しなくても同じ暗号化キーを確立可能なプロトコルを指します。
各パーティは、非公開キーによってキー協定オブジェクトを初期化し、通信に参加する各パーティの公開キーを入力します。 ほとんどの場合は2つのパーティのみですが、Diffie-Hellmanなどのアルゴリズムでは3つ以上のパーティの参加が可能です。 公開キーがすべて入力されると、各KeyAgreementオブジェクトは同じキーを生成(合意)します。
KeyAgreementクラスは、キー協定プロトコルの機能を提供します。 共有の秘密の確立に関係するキーは、キー・ジェネレータのいずれか(KeyPairGeneratorまたはKeyGenerator)かKeyFactoryによって、またはキー協定プロトコルの中間フェーズの結果として作成されます。
キー協定に関係する各パーティは、KeyAgreementオブジェクトを作成する必要があります。 KeyAgreementオブジェクトは、KeyAgreement getInstance() staticファクトリ・メソッドの1つを使用することによって取得されます。
非公開情報を使用して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);
各協定プロトコルは、キー協定に関係する各パーティが実行する必要のある多数のフェーズで構成されます。
キー協定の次のフェーズを実行するには、doPhaseメソッドを呼び出します。
public Key doPhase(Key key, boolean lastPhase);
keyパラメータには、そのフェーズで処理するキーが含まれます。 ほとんどの場合、これは、キー協定に関係する他のいずれかのパーティの公開キー、または前のフェーズで生成された中間キーです。doPhaseは、このキー協定の他のパーティに送信する必要のある中間キーを返すため、他のパーティは続くフェーズでそのキーを処理できます。
lastPhaseパラメータには、実行するフェーズがキー協定の最後のフェーズかどうかを指定します。値FALSEは、これがキー協定の最後のフェーズではない(このあとにフェーズが続く)ことを示します。値TRUEは、これがキー協定の最後のフェーズであり、キー協定が完了する(次にgenerateSecretが呼び出される)ことを示します。
2つのパーティ間のDiffie-Hellmanの例では、lastPhaseをTRUEに設定してdoPhaseを呼び出します。 Diffie-Hellmanで3つのパーティが存在する場合、doPhaseを2度呼び出します。最初はlastPhaseをFALSEに設定し、2度目にはlastPhaseをTRUEに設定します。
各パーティがすべての必須キー協定フェーズを実行したあとで、generateSecretメソッドのいずれかを呼び出して共有される秘密を計算できます。
public byte[] generateSecret();
public int generateSecret(byte[] sharedSecret, int offset);
public SecretKey generateSecret(String algorithm);
「キーストア」と呼ばれるデータベースは、キーおよび証明書のリポジトリを管理するために使用できます。 証明書とは、あるエンティティが発行したデジタル署名付きの文書で、別なエンティティの公開キーが特定の値であることを証明しています。
ユーザー・キーストアは、デフォルトではユーザーのホーム・ディレクトリの.keystoreという名前のファイルに格納されます。ユーザーのホーム・ディレクトリは、"user.home"システム・プロパティによって決まります。 Solarisシステムの場合、"user.home"がデフォルトでユーザーのホーム・ディレクトリになっています。 Win32システムの場合、指定されたユーザー名がuNameのとき、"user.home"のデフォルトは次のように設定されます。
キーストア・ファイルは必要な場所に配置できます。 環境によっては、複数のキーストアが存在することに意味がある場合があります。 たとえば、JSSE (SSL/TLS)では、1つのキーストアがユーザーの非公開キーを保持し、別のキーストアが信頼関係を確立するために使用される証明書を保持する場合があります。
ユーザーのキーストア以外に、JDKは、さまざまな証明書発行局(CA)からの信頼できる証明書を格納するために使用されるシステム全体のキーストアも保持します。 これらのCA証明書は、信頼性の判定を行う場合に使用できます。 たとえば、SSL/TLSでSunJSSEプロバイダがリモート・ピアから証明書を提示された場合、デフォルトのトラスト・マネージャは、
<java-home>/lib/ext/cacerts<java-home>\lib\ext\cacertsファイルに問い合わせて、接続が信頼できるかどうかを判断します。 システム全体のcacertsキーストアを使用する代わりに、アプリケーションは独自のキーストアを設定して使用するか、前述のユーザー・キーストアを使用できます。
KeyStoreクラスは、キーストア内の情報へのアクセスおよび情報の変更を行うための、明確に定義されたインタフェースを提供します。 複数の異なる固定実装を生成できます(それぞれ特定の型のキーストアを実装)。
現在、KeyStoreを利用するコマンド行ツールには、keytoolとjarsignerの2つがあります。また、GUIベースのpolicytoolというツールもあります。 様々なソースのコードに対して(システム・リソースへの)アクセス権を指定するポリシー・ファイルを処理する場合、このツールはPolicyのリファレンス実装によっても使われます。 KeyStoreはpublicとして使用可能なので、JDKユーザーはKeyStoreを使った他のセキュリティ・アプリケーションも作成できます。
アプリケーションでは、KeyStoreクラスのgetInstanceファクトリ・メソッドを使うことで、さまざまなプロバイダから異なる型のキーストア実装を選択できます。 キーストアの型は、キーストア情報のストレージ形式とデータ形式を定義するとともに、キーストア内の非公開キーとキーストア自体の整合性を保護するために使われるアルゴリズムを定義します。 異なる型のキーストアの実装には、互換性はありません。
推奨のキーストア実装は「pkcs12」です。 これは、RSA PKCS12 Personal Information Exchange Syntax Standardに基づくクロス・プラットフォーム・キーストアです。 この規格は、ユーザーの非公開キー、証明書、その他の秘密を格納および転送することを主な目的としています。 PKCS12キーストア内の個々のエントリに任意の属性を関連付けることができます。
デフォルトのキーストア実装の型は「jks」で、java.securityファイル内の次の行に指定されています。
keystore.type=jks
ツールおよび他のアプリケーションで異なるデフォルトのキーストア実装を使用するには、この行を変更して別のデフォルトの型を指定します。 たとえば、デフォルトのキーストア実装として「pkcs12」を使用するには、この行を次のように変更します。
keystore.type=pkcs12
keytoolなどの一部のアプリケーションでも、-storetypeコマンド行パラメータによって、デフォルトのキーストアの型をオーバーライドできます。
他に2つの型のキーストアが、JDK実装に付属しています。
jceksは、jksに対する独自の代替キーストア形式であり、トリプルDESによるパスワードベース暗号化を使用します。
Sunの「jceks」実装は、「jks」キーストア・ファイルを解析して「jceks」形式に変換できます。 キーストア内の非公開キーエントリのパスワードを変更し、キーストアの型として-storetype jceksを指定することによって、「jks」型のキーストアを「jceks」型のキーストアにアップグレードできます。 提供される(より)強力な暗号化によるキー保護を、デフォルト・キーストア内の「signkey」という名前の非公開キーに適用する場合は、次のコマンドを入力します。このコマンドにより、旧パスワードおよび新規パスワードの指定が求められます。
keytool -keypasswd -alias signkey -storetype jceks
keytoolとキーストア、およびその管理方法の詳細は、「セキュリティ・ツール」を参照してください。
"dks"は、ドメイン・キーストアです。 これは、単一の論理的なキーストアとして表されるキーストアの集合です。 特定のドメインを構成するキーストアは、「DomainLoadStoreParameter」で説明されている構文を持つ構成データによって指定されます。
キーストアの実装は、プロバイダ・ベースです。 独自のキーストア実装を記述することに関心がある開発者は、「Java暗号化アーキテクチャ用プロバイダの実装方法」でこのトピックの詳細を参照してください。
KeyStoreクラスKeyStoreクラスは、キーストア内の情報へのアクセスおよび情報の変更を行うための明確に定義されたインタフェースを提供するエンジン・クラスです。
このクラスは、メモリー内のキーおよび証明書のコレクションを表します。 KeyStoreは次の2種類のエントリを管理します。
この種類のキーストア・エントリには、非常に重要な暗号化キーの情報が保持されます。情報は、保護された形式で格納され、権限のないアクセスを防ぎます。 通常、この種類のエントリに格納されるキーは非公開キーで、対応する公開キーを証明する証明書チェーンが伴います。
非公開キーおよび証明書チェーンは、デジタル署名を使った自己認証用に特定のエンティティが使用します。 たとえば、ソフトウェア配布団体は、リリースまたはライセンスするソフトウェアの一部としてJARファイルにデジタル署名を付けます。
この種類のエントリには、別の組織に属する単一の公開キーが含まれます。 これは信頼できる証明書と呼ばれますが、それは、キーストアの所有者が、証明書内の公開キーが実際に証明書のサブジェクト (所有者)によって識別されたアイデンティティに属することを信頼するためです。
この種類のエントリは、ほかの組織の認証に使うことができます。
キーストア内の各エントリは、「別名」文字列によって識別されます。 非公開キーとそれに関連付けられた証明書チェーンの場合は、これらの文字列はエンティティ自体が認証するというように、方法別に区別されます。 たとえば、エンティティが異なる証明書発行局を使ったり、異なる公開キー・アルゴリズムを使ったりして、エンティティ自体を認証することも可能です。
キーストアが持続性があるかどうか、および持続性がある場合に使われるメカニズムは、ここでは指定されません。 この規則により、重要な(秘密または非公開)キーを保護するための様々な技術を使うことができます。 スマート・カードまたはその他の統合暗号化エンジン(SafeKeyper)を使うことも1つの方法です。また、ファイルなどのより単純なメカニズムを様々な形式で使うこともできます。
主要なKeyStoreメソッドを次で説明します。
KeyStoreオブジェクトの作成KeyStoreオブジェクトは、KeyStore getInstance() staticファクトリ・メソッドの1つを使用することによって取得されます。
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キーストアは、任意の属性を含むエントリをサポートします。 属性を作成するには、java.security.PKCS12Attributeクラスを使用します。 新しいキーストア・エントリを作成するときは、属性を受け入れるコンストラクタを使用します。 最後に、次のメソッドを使用してエントリをキーストアに追加します。
final void setEntry(String alias, Entry entry,
ProtectionParameter protParam)
getKeyメソッドは、与えられた別名に関連付けられたキーを返します。 キーは、与えられたパスワードを使って復元されます。
final Key getKey(String alias, char[] password)
次のメソッドは、与えられた別名に関連付けられた証明書、または証明書チェーンをそれぞれ返します。
final Certificate getCertificate(String alias) final Certificate[] getCertificateChain(String alias)
次の文を使って、与えられた証明書と一致した最初のエントリの名前(alias)を決定できます。
final String getCertificateAlias(Certificate cert)
PKCS #12キーストアは、任意の属性を含むエントリをサポートします。 次のメソッドを使用して、属性を含む可能性があるエントリを取得します。
final Entry getEntry(String alias, ProtectionParameter protParam)
次に、KeyStore.Entry.getAttributesメソッドを使用してそのような属性を抽出し、KeyStore.Entry.Attributeインタフェースのメソッドを使用してそれらを調べます。
メモリー内のキーストアを、storeメソッドを使って保存できます。
final void store(OutputStream stream, char[] password)
パスワードは、キーストア・データの統合チェックサムの計算に使われます。統合チェックサムは、キーストア・データのあとに追加されます。
DKSキーストアは、DomainLoadStoreParameterを代替のstoreメソッドに渡すことによって格納されます。
final void store(KeyStore.LoadStoreParameter param)
KeyおよびKeyspecと同様に、アルゴリズムの初期化パラメータはAlgorithmParameterまたはAlgorithmParameterSpecによって表されます。 使用状況によっては、アルゴリズムでパラメータを直接使用できますが、転送や格納のためにパラメータを移植性の高い形式に変換する必要がある場合もあります。
AlgorithmParameterSpecによるパラメータのセットの透明な表現とは、セットの各パラメータの値に個別にアクセスできることを意味します。 これらの値には、対応する仕様クラスに定義されたgetメソッドの1つを使ってアクセスできます(DSAParameterSpecでは、getP、getQ、およびgetGメソッドを定義して、それぞれp、q、およびgにアクセスするなど)。
これに対して、AlgorithmParametersクラスは不透明な表現を提供します。「不透明な」表現では、パラメータ・フィールドに直接アクセスできません。 パラメータ・セットに関連付けられたアルゴリズム名の取得(getAlgorithmによる)、およびそのパラメータ・セット用のある種のエンコードの取得(getEncodedによる)しかできません。
AlgorithmParameterSpecインタフェースAlgorithmParameterSpecは、暗号化パラメータの透明な仕様へのインタフェースです。 このインタフェースには、メソッドまたは定数が含まれていません。 このインタフェースの唯一の目的は、すべてのパラメータの仕様をグループ化すること(およびそれらのパラメータに安全な型を提供すること)です。 すべてのパラメータの仕様で、このインタフェースを実装する必要があります。
java.security.specおよびjavax.crypto.specパッケージに含まれるアルゴリズム・パラメータ仕様のインタフェースおよびクラスについては、JDK Javadoc APIドキュメントで説明されています。
次のアルゴリズム・パラメータ仕様は、JSR 105の一部として特にデジタル署名に使用されます。
AlgorithmParametersクラスAlgorithmParametersクラスは、暗号化パラメータの不透明な表現を提供するエンジン・クラスです。 特定のAlgorithmParameterSpecオブジェクトを使用するか、または認識される書式でパラメータをエンコードすることによって、AlgorithmParametersクラスを初期化できます。 結果の仕様をgetParameterSpecメソッドで取得できます(次のセクションを参照)。
AlgorithmParametersオブジェクトの作成AlgorithmParametersオブジェクトは、AlgorithmParameters getInstance() staticファクトリ・メソッドの1つを使用することによって取得されます。
AlgorithmParametersオブジェクトの初期化AlgorithmParametersオブジェクトのインスタンスが生成されたら、適切なパラメータ仕様またはパラメータのエンコードにより、initを呼び出すことで初期化する必要があります。
void init(AlgorithmParameterSpec paramSpec) void init(byte[] params) void init(byte[] params, String format)
これらのinitメソッドで、paramsは、エンコードされたパラメータを含む配列で、formatは、デコード形式の名前です。 params引数を指定し、format引数を指定しないinitメソッドでは、パラメータのプライマリ・デコード形式が使われます。 パラメータのASN.1仕様が存在する場合は、プライマリ・デコード形式は、ASN.1です。
AlgorithmParametersオブジェクトは、1回だけ初期化できます。 再利用はできません。 AlgorithmParametersオブジェクトで表現されるパラメータのバイト・エンコードは、getEncodedへの呼出しによって取得できます。
byte[] getEncoded()
このメソッドは、パラメータをプライマリ・エンコード形式で返します。 この種のパラメータのASN.1仕様が存在する場合は、プライマリ復号化形式は、ASN.1です。
特定のエンコード形式でパラメータが返されるようにするには、次のように記述します。
byte[] getEncoded(String format)
formatがnullの場合は、ほかのgetEncodedメソッドと同様に、プライマリ・エンコード形式が使われます。
SUNプロバイダによって提供されるデフォルトのAlgorithmParametersの実装では、format引数は、現在のところ無視されます。AlgorithmParametersオブジェクトから透明な仕様への変換アルゴリズム・パラメータの透明なパラメータの仕様は、getParameterSpecへの呼出しにより、AlgorithmParametersオブジェクトから取得できます。
AlgorithmParameterSpec getParameterSpec(Class paramSpec)
paramSpecは、パラメータが返されるべき仕様クラスを識別します。 たとえば、仕様クラスDSAParameterSpec.classが識別された場合、パラメータがDSAParameterSpecクラスのインスタンスに返されるべきであることを示します。 このクラスはjava.security.specパッケージにあります。
AlgorithmParameterGeneratorクラスAlgorithmParameterGeneratorクラスは、特定のアルゴリズムに適した新しいパラメータのセットの生成に使用されるエンジン・クラスです。アルゴリズムは、AlgorithmParameterGeneratorのインスタンスの作成時に指定されます。 このオブジェクトは、既存のアルゴリズム・パラメータのセットがなく、最初から生成する場合に使用されます。
AlgorithmParameterGeneratorオブジェクトの作成AlgorithmParameterGeneratorオブジェクトは、AlgorithmParameterGenerator getInstance() staticファクトリ・メソッドの1つを使用することによって取得されます。
AlgorithmParameterGeneratorオブジェクトの初期化AlgorithmParameterGeneratorオブジェクトは、アルゴリズム独立型、またはアルゴリズム固有型の2種類の方法で初期化できます。
アルゴリズム独立型の方法では、すべてのパラメータ・ジェネレータが「サイズ」および乱数発生の元という概念を共有するという特性を利用します。 サイズの単位は、すべてアルゴリズム・パラメータで普遍的に共通していますが、その解釈はアルゴリズムにより異なります。 たとえば、DSAアルゴリズムのパラメータの場合、「サイズ」は素数モジュラスのビット数のサイズに一致します。 特定のアルゴリズムのサイズについては、標準名のドキュメントを参照してください。 この方法を使うと、アルゴリズム固有型のパラメータの生成値が(ある場合は)、デフォルト基準値になります。 普遍的に共有されるこれら2つの引数の型をとるinitメソッドが1つあります。
void init(int size, SecureRandom random);
また、size引数だけをとり、システムが提供する乱数の発生源を使用するinitメソッドもあります。
void init(int size)
3番目の方法では、パラメータ・ジェネレータ・オブジェクトの初期化にアルゴリズム固有型のセマンティックスを使います。アルゴリズム固有型のセマンティックスは、AlgorithmParameterSpecオブジェクト内に提供されるアルゴリズム固有型のパラメータ生成値のセットによって表されます。
void init(AlgorithmParameterSpec genParamSpec,
SecureRandom random)
void init(AlgorithmParameterSpec genParamSpec)
たとえば、Diffie-Hellmanシステム・パラメータを生成するには、通常、パラメータ生成値は、プライム・モジュラスおよびランダム指数のサイズで構成されます。どちらのサイズもビット数で指定します。
AlgorithmParameterGeneratorオブジェクトを作成および初期化したら、generateParametersメソッドを使ってアルゴリズム・パラメータを生成できます。
AlgorithmParameters generateParameters()
CertificateFactoryクラスCertificateFactoryクラスは、証明書ファクトリの機能を定義するエンジン・クラスです。証明書ファクトリは、証明書および証明書失効リスト(CRL)オブジェクトをそのエンコードから生成するために使用されます。
X.509の証明書ファクトリは、java.security.cert.X509Certificateのインスタンスである証明書、およびjava.security.cert.X509CRLのインスタンスであるCRLを返します。
CertificateFactoryオブジェクトの作成CertificateFactoryオブジェクトは、getInstance() staticファクトリ・メソッドの1つを使用することによって取得されます。
証明書オブジェクトを生成し、入力ストリームから読み込まれたデータを使って初期化するには、generateCertificateメソッドを使います。
final Certificate generateCertificate(InputStream inStream)
特定の入力ストリームから読み込まれた証明書のコレクション・ビュー(空の可能性もある)を返すには、generateCertificatesメソッドを使います。
final Collection generateCertificates(InputStream inStream)
証明書失効リスト(CRL)オブジェクトを生成し、入力ストリームから読み込まれたデータを使って初期化するには、generateCRLメソッドを使います。
final CRL generateCRL(InputStream inStream)特定の入力ストリームから読み込まれたCRLのコレクション・ビュー(空の可能性もある)を返すには、
generateCRLsメソッドを使います。
final Collection generateCRLs(InputStream inStream)
CertPathオブジェクトの生成PKIX用の証明書パス・ビルダーおよびバリデータは、RFC 3280「Internet X.509 Public Key Infrastructure Certificate and CRL Profile」によって定義されます。
PKIX LDAP V2スキーマを使用して証明書とCRLをCollectionディレクトリおよびLDAPディレクトリから取得するための証明書ストアの実装も、RFC 2587としてIETFから使用可能です。
CertPathオブジェクトを生成し、そのオブジェクトを入力ストリームから読み込まれたデータを使って初期化するには、次のいずれかのgenerateCertPathメソッドを使用します(必要に応じて、データに対して使用するエンコーディングを指定します)。
final CertPath generateCertPath(InputStream inStream)
final CertPath generateCertPath(InputStream inStream,
String encoding)
CertPathオブジェクトを生成し、証明書のリストを使用して初期化するには、次のメソッドを使用します。
final CertPath generateCertPath(List certificates)
証明書ファクトリでサポートされているCertPathエンコーディングのリストを取得するには、getCertPathEncodingsメソッドを呼び出します。
final Iterator getCertPathEncodings()初めにデフォルトのエンコーディングが一覧表示されます。
JCAのクラスについて理解したため、次に、これらのクラスがどのように結合されてSSL/TLSのような高度なネットワーク・プロトコルを実装するかについて考えます。 非対称(公開キー)暗号操作は対称操作(秘密キー)よりも大幅に遅いため、公開キー暗号方式は、実際のアプリケーション・データを保護するために使用される秘密キーを確立する場合に使用されます。 非常に単純に言うと、SSL/TLSハンドシェークには、初期化データの交換、いくつかの公開キー操作の実行による秘密キーへの到達、およびそのキーを使用したそのあとのトラフィックの暗号化が含まれます。
この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オブジェクトの作成を開始します。 次の例では、サーバーのみの認証を使用します。
最初の例では、サーバーはTLS_RSA_WITH_AES_128_CBC_SHAなどのRSAベースの暗号群を使用しようとします。 サーバーのKeyManagerがクエリーされ、適切なRSAエントリを返します。 サーバーの資格(つまり、証明書/公開キー)がサーバーのCertificateメッセージ内で送信されます。 クライアントのTrustManagerはサーバーの証明書を検証し、受け入れた場合、クライアントはSecureRandomオブジェクトを使用していくつかのランダム・バイトを生成します。 次に、サーバーの証明書内で見つかったPublicKeyによって初期化された暗号化非対称RSA Cipherオブジェクトを使用して暗号化されます。 この暗号化されたデータは、Client Key Exchangeメッセージ内で送信されます。 サーバーは、対応するPrivateKeyを使用し、同様のCipherを復号モードで使用してバイトを回復します。 これらのバイトは、実際の暗号化キーを確立する場合に使用されます。
別の例では、一時的なDiffie-Hellmanキー協定アルゴリズムとTLS_DHE_DSS_WITH_AES_128_CBC_SHAなどのDSA署名アルゴリズムが選択されます。 両方の側でそれぞれ、KeyPairGeneratorを使用して、新しい一時的なDH公開キーと非公開キーのペアを確立します。 各ジェネレータは、KeyFactoryおよびDHPublicKeySpecクラスを使用して、さらに細かく変換できるDHキーを作成します。 それぞれの側は、次にKeyAgreementオブジェクトを作成し、それぞれのDH PrivateKeyによって初期化します。 サーバーはその公開キーをServerKeyExchangeメッセージ内で送信し(DSA署名アルゴリズムによって保護される)、クライアントはその公開キーをClientKeyExchangeメッセージ内で送信します。 公開キーが別のKeyFactoryを使用して再度組み立てられると、これらは協定オブジェクトに渡されます。 KeyAgreementオブジェクトは、実際の暗号化キーを確立する場合に使用される合意済みのバイトを生成します。
実際の暗号化キーが確立されると、対称Cipherオブジェクトを初期化するために秘密キーが使用され、この暗号が送信中のすべてのデータを保護する場合に使用されます。 データが変更されたかどうかを判断できるようにMessageDigestが作成され、ネットワークに送信されるデータのコピーを受信します。 パケットが完全な場合、ダイジェスト(ハッシュ)がデータのあとに付加され、パケット全体がCipherによって暗号化されます。 AESなどのブロック暗号が使用される場合は、データにパディングを行なって完全なブロックにします。 リモート側では、ステップは単純に逆になります。
これは非常に単純化されていますが、これらのクラスがどのように結合されて高レベルのプロトコルを作成するかを示しています。
ノート: 大半のアプリケーション開発者には、このセクションで説明する内容は関係ありません。 関係があるのは、作成するアプリケーションが、政府により暗号化制限の課された国に輸出される可能性があり、アプリケーションをその制限に適合させる必要がある場合だけです。
デフォルトでは、アプリケーションは、どの強度の暗号化アルゴリズムでも使用できます。 ただし、一部の国の政府による輸入管理制限のため、それらのアルゴリズムの強度を制限する必要がある場合があります。 JCAフレームワークには、様々な管轄コンテキスト(場所)でアプリケーションに使用可能な暗号化アルゴリズムの最大強度に関して、制限を強制する機能が含まれます。 管轄ポリシー・ファイルでこれらの制限を指定します。 管轄ポリシー・ファイルおよびその作成方法と構成方法の詳細は、付録B: 管轄ポリシー・ファイル形式および付録C: 暗号強度の構成を参照してください。
これらの国の一部またはすべてで、特定のアプリケーションに対し、暗号化制限の一部またはすべての免責が許可されています。 たとえば、特定の種類のアプリケーションは「特別」と見なされ、免責されます。 また、キー復元などの「免責メカニズム」を利用するアプリケーションは、免責可能です。 この種の国では、免責されたと見なされるアプリケーションは、免責されていないアプリケーションに許可されるよりも強力な暗号化にアクセスできます。
実行時にアプリケーションが「免責」されていると認識されるようにするには、次の条件を満たす必要があります。
次に、アプリケーションにいくつかの暗号化制限が適用されないようにするために必要なステップのサンプルを示します。 これは、免責されたものとしてアプリケーションを認識および処理するため、JCAにより要求される情報を含む、基本情報です。 実際には、アプリケーションを実行可能にする(政府が暗号化制限を課している)特定の国の免責要件を知る必要があります。 また、免責されたアプリケーションの処理プロセスを保持するJCAフレームワーク・ベンダーの要件も理解しておく必要があります。 詳細は、ベンダーにお尋ね下さい。
ノート: SunJCEプロバイダは、ExemptionMechanismSpiクラスの実装を提供しません。
アプリケーションに、関連付けられたアクセス権ポリシー・ファイルがあり(同じJARファイル内)、そのアクセス権ポリシー・ファイルで免責メカニズムが指定されている場合、CipherのgetInstanceメソッドが呼び出されてCipherがインスタンス化されると、JCAコードは、インストール済プロバイダの中から、指定された免責メカニズムを実装するプロバイダを検索します。 目的のプロバイダが見つかると、JCAは、そのプロバイダの実装に関連付けられたExemptionMechanismオブジェクトをインスタンス化してから、ExemptionMechanismオブジェクトを、getInstanceによって返されたCipherと関連付けます。
Cipherをインスタンス化した後、初期化する前に(Cipherのinitメソッドを呼び出して)、コードから次のCipherメソッドを呼び出す必要があります。
public ExemptionMechanism getExemptionMechanism()
この呼出しにより、Cipherに関連付けられたExemptionMechanismオブジェクトが返されます。 次に、返されたExemptionMechanismに対して次のメソッドを実行して、免責メカニズムの実装を初期化する必要があります。
public final void init(Key key)
ここで指定する引数の型は、この後でCipher initメソッドに指定する引数の型と同じにする必要があります。
ExemptionMechanismの初期化が完了したら、通常と同じ方法でCipherを初期化して使用できます。
実行時にアプリケーションが暗号化制限の一部またはすべてを「免責」されていると認識されるようにするには、JARファイル内にアクセス権ポリシー・ファイルをバンドルする必要があります。 アクセス権ポリシー・ファイルには、アプリケーションが保持する暗号化関連のアクセス権、およびそれを保持する条件(存在する場合)を指定します。
ノート: アプリケーションにバンドルするアクセス権ポリシー・ファイルの名前は、cryptoPermsにする必要があります
免責されるアプリケーションにバンドルされるアクセス権ポリシー・ファイル内のアクセス権エントリの書式は、JDKとともにダウンロードされる管轄ポリシー・ファイルの書式と同じです。次にその書式を示します。
permission <crypto permission class name>[ <alg_name>
[[, <exemption mechanism name>][, <maxKeySize>
[, <AlgorithmParameterSpec class name>,
<parameters for constructing an AlgorithmParameterSpec object>
]]]];
付録B: 管轄ポリシー・ファイルの形式を参照してください。
アプリケーションの中には、制限を完全に解除可能なものもあります。 通常、その種のアプリケーションにバンドルするアクセス権ポリシー・ファイルには、次を含めるだけで十分です。
grant {
// There are no restrictions to any algorithms.
permission javax.crypto.CryptoAllPermission;
};
アプリケーションが1つの(またはいくつかの特定の)アルゴリズムだけを使用する場合、アクセス権ポリシー・ファイルには、CryptoAllPermissionを付与するのではなく、そのアルゴリズムを明示的に記述します。 たとえば、アプリケーションがBlowfishアルゴリズムだけを使用する場合、アクセス権ポリシー・ファイルですべてのアルゴリズムにCryptoAllPermissionを付与する必要はありません。 Blowfishアルゴリズムが使用される場合、暗号化制限が存在しないことを指定するだけで十分です。 この場合、アクセス権ポリシー・ファイルは、次のようになります。
grant {
permission javax.crypto.CryptoPermission "Blowfish";
};
免責メカニズムが導入されるためにアプリケーションが「免責される」と見なされる場合、アプリケーションにバンドルされるアクセス権ポリシー・ファイルに1つ以上の免責メカニズムを指定する必要があります。 実行時に、これらの免責メカニズムのいずれかが機能していると、アプリケーションは免責されたものと見なされます。 次のようなアクセス権エントリ内に、各免責メカニズムを指定する必要があります。
// No algorithm restrictions if specified
// exemption mechanism is enforced.
permission javax.crypto.CryptoPermission *,
"<ExemptionMechanismName>";
<ExemptionMechanismName>には、免責メカニズムの名前を指定します。 指定可能な免責メカニズムの名前には、次が含まれます。
例として、キー復元またはキー・エスクローのいずれかが機能すると、アプリケーションが免責される場合を考えましょう。 その場合、アクセス権ポリシー・ファイルには、次が含まれます。
grant {
// No algorithm restrictions if KeyRecovery is enforced.
permission javax.crypto.CryptoPermission *,
"KeyRecovery";
// No algorithm restrictions if KeyEscrow is enforced.
permission javax.crypto.CryptoPermission *,
"KeyEscrow";
};
ノート: 免責メカニズムを指定するアクセス権エントリには、最大キー・サイズを指定しないでください。 許可されるキーのサイズは、実際にはインストールされた免責管轄ポリシー・ファイルにより決定されます。詳細は、次のセクションを参照してください。
実行時にアプリケーションが(getInstanceメソッドを呼び出すことで) Cipherをインスタンス化し、かつそのアプリケーションが関連するアクセス権ポリシー・ファイルを保持する場合、JCAはアクセス権ポリシー・ファイルにgetInstanceの呼出しで指定されたアルゴリズムに適用されるエントリが含まれるかどうかをチェックします。 該当するエントリが含まれ、そのエントリがCryptoAllPermissionを付与するか免責メカニズムの実施を指定しない場合、このアルゴリズムには暗号化制限が存在しないことを意味します。
アクセス権ポリシー・ファイルにgetInstanceの呼出しで指定されたアルゴリズムに適用されるエントリが含まれ、かつエントリで免責メカニズムの実施が指定されている場合、免責管轄ポリシー・ファイルがチェックされます。 免責されるアクセス権に、関連するアルゴリズムおよび免責メカニズムのエントリが含まれており、そのエントリがアプリケーションにバンドルされたアクセス権ポリシー・ファイル内のアクセス権により暗黙的に設定されている場合、および指定された免責メカニズムの実装がいずれかの登録済プロバイダから利用可能な場合、Cipherの最大キー・サイズおよびアルゴリズム・パラメータ値は、免責アクセス権エントリにより決定されます。
アプリケーションにバンドルされるアクセス権ポリシー・ファイル内の関連するエントリに、暗黙的に設定された免責アクセス権が存在しない場合、またはいずれかの登録済みプロバイダから利用可能な、指定された免責メカニズムの実装が存在しない場合、デフォルトの標準暗号化アクセス権のみがアプリケーションに付与されます。
次に示すのは、JCAメカニズムのいくつかの使用を説明する短い例です。 また、全体を示す実際の例が「付録D」にあります。
MessageDigestオブジェクトの計算最初に、次の例のようにしてメッセージ・ダイジェストを作成します。
MessageDigest sha = MessageDigest.getInstance("SHA-256");
この呼出しは、正しく初期化されたメッセージ・ダイジェスト・オブジェクトを変数shaに代入します。 この実装は、National Institute for Standards and Technology (NIST)のFIPS 180-2ドキュメントの定義に従って、Secure Hashアルゴリズム(SHA-256)を実装します。 標準名とアルゴリズムに関する詳細は、「付録A」を参照してください。
次に、3つのバイト配列i1、i2およびi3があるとします。この3つの配列から形成される合計入力のメッセージ・ダイジェストを計算します。 このダイジェスト(または「ハッシュ」)は、次の呼出しを使って計算できます。
sha.update(i1); sha.update(i2); sha.update(i3); byte[] hash = sha.digest();
次の一連の呼出しを使っても同じです。
sha.update(i1); sha.update(i2); byte[] hash = sha.digest(i3);メッセージ・ダイジェストが計算されると、メッセージ・ダイジェスト・オブジェクトは自動的にリセットされ、新しいデータを受信してそのダイジェストを計算できる状態になります。 以前の状態(たとえば
update呼出しで入れたデータ)はすべて失われます。
ハッシュ実装によっては、複製(コピー)を介して中間ハッシュをサポートするものもあります。 次について、別々のハッシュを計算すると仮定します。
i1i1 and i2i1, i2, and i3次のように処理します。
/* compute the hash for i1 */ sha.update(i1); byte[] i1Hash = sha.clone().digest(); /* compute the hash for i1 and i2 */ sha.update(i2); byte[] i12Hash = sha.clone().digest(); /* compute the hash for i1, i2 and i3 */ sha.update(i3); byte[] i123hash = sha.digest();
このコードは、SHA-256実装が複製(コピー)可能な場合にのみ機能します。 メッセージ・ダイジェストの実装の中には、複製(コピー)可能なものもあれば、不可能なものもあります。 複製(コピー)が可能かどうかを判断するには、次のようにMessageDigestオブジェクトを複製し、発生する可能性のある例外をキャッチします。
try {
// try and clone it
/* compute the hash for i1 */
sha.update(i1);
byte[] i1Hash = sha.clone().digest();
// ...
byte[] i123hash = sha.digest();
} catch (CloneNotSupportedException cnse) {
// do something else, such as the code shown below
}
メッセージ・ダイジェストが複製不可能な場合は、別の多少複雑な方法を使います。つまり、複数のダイジェストを生成して中間ダイジェストを計算します。 この場合、計算する中間ダイジェストの数をあらかじめ知っておく必要があります。
MessageDigest md1 = MessageDigest.getInstance("SHA-256");
MessageDigest md2 = MessageDigest.getInstance("SHA-256");
MessageDigest md3 = MessageDigest.getInstance("SHA-256");
byte[] i1Hash = md1.digest(i1);
md2.update(i1);
byte[] i12Hash = md2.digest(i2);
md3.update(i1);
md3.update(i2);
byte[] i123Hash = md3.digest(i3);
この例では、「DSA」(デジタル署名アルゴリズム)という名前のアルゴリズムについて、「公開 - 非公開」のキーのペアを生成し、このキーのペアをそのあとの例で使用します。 キーは、2048ビット・モジュラスで生成します。 ここでは、アルゴリズム実装を提供するプロバイダは考慮しません。
最初のステップは、DSAアルゴリズムのキーを生成するためのキーのペア・ジェネレータ・オブジェクトの獲得です。
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA");
次のステップは、キーのペア・ジェネレータの初期化です。 ほとんどの場合、アルゴリズム独立型の初期化で十分ですが、アルゴリズム固有型の初期化を使う必要がある場合もあります。
すべてのキー・ジェネレータは、キー・サイズおよび乱数発生の元の概念を共有します。 KeyPairGeneratorクラス初期化メソッドには、最低限キー・サイズが必要です。 乱数の発生源が明示的に提供されない場合、もっとも高い優先順位でインストールされているプロバイダのSecureRandom実装が使用されます。 したがって、キー・サイズ2048でキーを生成するには、次のコードを呼び出すだけです。
keyGen.initialize(2048);次のコードは、特定の、追加でシードされる
SecureRandomオブジェクトを使用する方法を示しています。
SecureRandom random = SecureRandom.getInstance("DRBG", "SUN");
random.setSeed(userSeed);
keyGen.initialize(2048, random);
前述のアルゴリズム独立型のinitializeメソッドを呼び出した場合、パラメータが指定されないため、それぞれのキーに関連したアルゴリズム固有のパラメータが存在する場合、これをどのように扱うかはプロバイダに任されます。 プロバイダは、事前に計算されたパラメータ値を使うことも、新しい値を生成することもできます。
アルゴリズム固有のパラメータのセットがすでに存在する状況では(DSAの「コミュニティ・パラメータ」など)、AlgorithmParameterSpec引数を取るinitializeメソッドが2つあります。 キーのペアのジェネレータがDSAアルゴリズムのジェネレータで、DSA固有のパラメータ・セットp、qおよびgがあり、これを使ってキーのペアを生成するとします。 次のコードを実行してキーのペアのジェネレータを初期化できます。DSAParameterSpecはAlgorithmParameterSpecです。
DSAParameterSpec dsaSpec = new DSAParameterSpec(p, q, g); keyGen.initialize(dsaSpec);
pのパラメータは、長さがモジュラスの長さ(「サイズ」)の素数です。 したがって、モジュラスの長さを指定するためにほかのメソッドを呼び出す必要はありません。 最終ステップは、キーのペアの実際の生成です。 使用した初期化の型(アルゴリズム独立型またはアルゴリズム固有型)に関係なく、同じコードを使ってキーのペアを生成します。
KeyPair pair = keyGen.generateKeyPair();
次の署名の生成および検証の例では、前述のキーのペアの例で生成されたKeyPairを利用しています。
最初に署名オブジェクトを生成します。
Signature dsa = Signature.getInstance("SHA256withDSA");
キーのペアの例で生成したキーのペアを使い、非公開キーを指定してオブジェクトを初期化します。このあとでdataというバイト配列に署名を付けます。
/* Initializing the object with a private key */ PrivateKey priv = pair.getPrivate(); dsa.initSign(priv); /* Update and sign the data */ dsa.update(data); byte[] sig = dsa.sign();
署名の検証は簡単です。 ここでも、キーのペアの例で生成したキーのペアを使います。
/* Initializing the object with the public key */
PublicKey pub = pair.getPublic();
dsa.initVerify(pub);
/* Update and verify the data */
dsa.update(data);
boolean verifies = dsa.verify(sig);
System.out.println("signature verifies: " + verifies);
KeyFactoryを使った署名の生成と検証公開キー/非公開キー・ペア (たとえば、上記のキー・ペアの例で生成された) を使うよりも、単にDSA非公開キーのコンポーネントx (非公開キー)、p (素数)、q (サブ素数)、およびg (ベース)を使うとします。
さらに、非公開キーを使ってsomeDataというバイト配列内のあるデータにデジタル署名を付けたいとします。 次のステップを実行します。このステップは、キー仕様の作成、およびキー・ファクトリを使ってキー仕様からPrivateKeyを取得する方法も示しています。initSignにはPrivateKeyが必要です。
DSAPrivateKeySpec dsaPrivKeySpec = new DSAPrivateKeySpec(x, p, q, g);
KeyFactory keyFactory = KeyFactory.getInstance("DSA");
PrivateKey privKey = keyFactory.generatePrivate(dsaPrivKeySpec);
Signature sig = Signature.getInstance("SHA256withDSA");
sig.initSign(privKey);
sig.update(someData);
byte[] signature = sig.sign();
Aliceが署名したデータを使いたいとします。 彼女がそのデータを使うため、および署名を検証するためには、彼女に次の3つのものを送る必要があります。
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)を使用してアクセスできます。
しばしば、2つのキーの同一性の判定が必要になることがありますが、デフォルトのjava.lang.Object.equalsメソッドでは、必要な結果が得られない場合があります。 プロバイダに対する依存度がもっとも少ない方法は、エンコードされたキーを比較する方法です。 この方法で適切に比較できない場合(たとえば、RSAPrivateKeyとRSAPrivateCrtKeyを比較する場合)、各コンポーネントを比較する必要があります。 その方法を示すコードを次に示します。
static boolean keysEqual(Key key1, Key key2) {
if (key1.equals(key2)) {
return true;
}
if (Arrays.equals(key1.getEncoded(), key2.getEncoded())) {
return true;
}
// More code for different types of keys here.
// For example, the following code can check if
// an RSAPrivateKey and an RSAPrivateCrtKey are equal:
// if ((key1 instanceof RSAPrivateKey) &&
// (key2 instanceof RSAPrivateKey)) {
// if ((key1.getModulus().equals(key2.getModulus())) &&
// (key1.getPrivateExponent().equals(
// key2.getPrivateExponent()))) {
// return true;
// }
// }
return false;
}
次の例は、Base64でエンコードされた証明書を読み取ります。認証の開始は、次のコードで指定されます。
-----BEGIN CERTIFICATE-----最後は、次の行で終わります。
-----END CERTIFICATE-----
generateCertificateの各呼出しが1つの証明書だけを使用し、入力ストリームの読出し位置がファイル内の次の証明書に置かれるように、FileInputStream (markおよびresetをサポートしていない)をByteArrayInputStream (これらのメソッドをサポート)に変換します。
FileInputStream fis = new FileInputStream(filename);
BufferedInputStream bis = new BufferedInputStream(fis);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
while (bis.available() > 0) {
Certificate cert = cf.generateCertificate(bis);
System.out.println(cert.toString());
}
次の例は、ファイル内に保存されたPKCS7形式の認証応答を解析し、そこから証明書をすべて抽出します。
FileInputStream fis = new FileInputStream(filename);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Collection c = cf.generateCertificates(fis);
Iterator i = c.iterator();
while (i.hasNext()) {
Certificate cert = (Certificate)i.next();
System.out.println(cert);
}
このセクションでは、キーの生成、Cipherオブジェクトの作成と初期化、およびファイルの暗号化と復号化という一連の処理について説明します。 この例全体で、Advanced Encryption Standard (AES)を使用します。
AESキーを作成するには、AES用のKeyGeneratorをインスタンス化する必要があります。 特定のAESキー生成実装について考慮する必要はないため、プロバイダは指定しません。 KeyGeneratorを初期化しないため、AESキーの作成にはシステム提供の乱数発生源およびデフォルトのキー・サイズが使用されます。
KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecretKey aesKey = keygen.generateKey();
キーを生成したあと、同じKeyGeneratorを使用してほかのキーを再度作成できます。
次のステップは、Cipherインスタンスの作成です。 これには、CipherクラスのいずれかのgetInstanceファクトリ・メソッドを使用します。 次のコンポーネントを含む必須の変換名を、スラッシュ(/)で区切って指定する必要があります。
この例では、Cipher Block ChainingモードおよびPKCS5パディングでAES暗号を作成します。 特定の必須変換の実装について考慮する必要はないため、プロバイダは指定しません。
AESの標準アルゴリズム名はAES、Cipher Block Chainingモードの標準名はCBC、PKCS5パディングの標準名はPKCS5Paddingです。
Cipher aesCipher;
// Create the cipher
aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
前で生成されたaesKeyを使用して、Cipherオブジェクトを暗号化用に初期化します。
// Initialize the cipher for encryption
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
// Our cleartext
byte[] cleartext = "This is just an example".getBytes();
// Encrypt the cleartext
byte[] ciphertext = aesCipher.doFinal(cleartext);
// Initialize the same cipher for decryption
aesCipher.init(Cipher.DECRYPT_MODE, aesKey);
// Decrypt the ciphertext
byte[] cleartext1 = aesCipher.doFinal(ciphertext);
cleartextとcleartext1は、同一です。
この例では、ユーザーにパスワードを要求し、暗号化キーをそのパスワードから導き出します。
パスワードを収集し、java.lang.String型のオブジェクトに格納するのは、適切と考えられます。 ただし、注意すべき点があります。それは、String型のオブジェクトは不変であるということです。このため、使用後にStringの内容を変更(上書き)またはゼロにするようなメソッドは存在しません。 この機能のために、Stringオブジェクトは、ユーザー・パスワードなどセキュリティ上重要な情報の格納には適しません。 セキュリティ関連の情報は、常にchar型の配列に収集および格納するようにしてください。
この理由で、javax.crypto.spec.PBEKeySpecクラスは、パスワードをchar型の配列として受け取り、返します。 入力ストリームから文字配列パスワードを読み込む1つの方法として、「付録D」のサンプル・コード内のReadPasswordクラスを参照してください。
PKCS5で定義されたパスワードベース暗号化(PBE)を使用するには、saltおよびiteration countを指定する必要があります。 復号化時にも、暗号化時と同じsaltおよび繰返し処理の回数を使用する必要があります。 より新しいPBEアルゴリズムは、1000以上の繰返し処理回数を使用します。
PBEKeySpec pbeKeySpec;
PBEParameterSpec pbeParamSpec;
SecretKeyFactory keyFac;
// Salt
byte[] salt = new SecureRandom().nextBytes(salt);
// Iteration count
int count = 1000;
// Create PBE parameter set
pbeParamSpec = new PBEParameterSpec(salt, count);
// Prompt user for encryption password.
// Collect user password as char array, and convert
// it into a SecretKey object, using a PBE key
// factory.
char[] password = System.console.readPassword("Enter encryption password: ");
pbeKeySpec = new PBEKeySpec(password);
keyFac = SecretKeyFactory.getInstance("PBEWithHmacSHA256AndAES_256");
SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);
// Create PBE Cipher
Cipher pbeCipher = Cipher.getInstance("PBEWithHmacSHA256AndAES_256");
// Initialize PBE Cipher with key and parameters
pbeCipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);
// Our cleartext
byte[] cleartext = "This is another example".getBytes();
// Encrypt the cleartext
byte[] ciphertext = pbeCipher.doFinal(cleartext);
2つおよび3つのパーティ間でDiffie-Hellmanキー交換を実行するサンプル・プログラムに関しては、「付録D」を参照してください。
JDK Security APIは、さまざまなアルゴリズム、証明書、およびキーストアのタイプの標準名を必要とし、これらを使用します。 以前にこの付録Aおよびその他のセキュリティ仕様(JSSE/CertPath/その他)にあった仕様名は、標準名のドキュメントにまとめられました。 このドキュメントには、アルゴリズムの仕様に関する詳細も含まれています。 特定のプロバイダの情報は、「Sun Provider Documentation」にあります。
JDKでの暗号化実装は、主に歴史的な理由により、さまざまなプロバイダによって配布されます(Sun、SunJSSE、SunJCE、SunRsaSign)。 これらのプロバイダは、すべてのJDK実装で使用可能ではない場合があるため、真に移殖可能なアプリケーションは、特定のプロバイダを指定せずにgetInstance()を呼び出す必要があります。 特定のプロバイダを指定するアプリケーションは、基盤となるオペレーティング環境(PKCSやMicrosoftのCAPIなど)用に調整されたネイティブ・プロバイダを使用できない場合があります。
SunPKCS11プロバイダ自体には暗号化アルゴリズムは含まれていませんが、代わりに要求を基盤となるPKCS11実装に送ります。 「PKCS#11リファレンス・ガイド」および基盤となるPKCS11実装を参照して、必要なアルゴリズムがPKCS11プロバイダで使用可能かどうかを判断するようにしてください。 同様に、Windowsシステムでは、SunMSCAPIプロバイダは暗号化機能を提供しませんが、代わりに基盤となるオペレーティング・システムに要求を処理するよう渡します。
JCAの管轄ポリシー・ファイルは、Java形式のポリシー・ファイル(対応するアクセス権を指定する文を含む)で表されます。 「デフォルトのPolicyの実装とポリシー・ファイルの構文」に説明されているように、Javaポリシー・ファイルでは、指定されたコード・ソースからコードに付与するアクセス権を指定します。 アクセス権は、システム・リソースへのアクセスを表します。 JCAの場合、「リソース」は暗号化アルゴリズムです。また、暗号化制限はすべてのコードに適用されるため、コード・ソースを指定する必要はありません。
管轄ポリシー・ファイルは、1つ以上の「アクセス権エントリ」を含む、非常に基本的な「付与エントリ」で構成されます。
grant {
<permission entries>;
};
次に、管轄ポリシー・ファイルのアクセス権エントリの書式を示します。
permission <crypto permission class name>[ <alg_name>
[[, <exemption mechanism name>][, <maxKeySize>
[, <AlgorithmParameterSpec class name>,
<parameters for constructing an
AlgorithmParameterSpec object>]]]];
次に、「Blowfish」アルゴリズムを64ビットの最大キー・サイズに制限する、管轄ポリシー・ファイルのサンプルを示します。
grant {
permission javax.crypto.CryptoPermission "Blowfish", 64;
// ...
};
アクセス権エントリは、permissionで始まります。 前述のテンプレート内の<crypto permission class name>には、javax.crypto.CryptoPermissionなどの具体的なアクセス権クラス名を指定します。 暗号化アクセス権クラスは、特定の環境下で特定のキー・サイズで、特定のアルゴリズムを使用するアプリケーション/アプレットの機能に対応します。 暗号化アクセス権クラスには、CryptoPermissionおよびCryptoAllPermissionの2つが存在します。 特別なCryptoAllPermissionクラスは、暗号化関連のアクセス権すべてを表します。つまり、暗号化関連の制限はないことを示します。
<alg_name>を指定する場合、「AES」や「RSA」など暗号化アルゴリズムの標準名を表す文字列(「付録A」を参照)を引用符で囲んで指定します。
<exemption mechanism name>を指定する場合、免責メカニズムを指す文字列を引用符で囲んで指定します。免責メカニズムを実施すると、暗号化制限が緩和されます。 使用可能な免責メカニズム名には、「KeyRecovery」、「KeyEscrow」および「KeyWeakening」が含まれます。
<maxKeySize>には、指定したアルゴリズムに許可する最大キー・サイズ(ビット)を示す整数値を指定します。
アルゴリズムによっては、キー・サイズでアルゴリズムの強度を指定するだけでは不十分な場合があります。 たとえば、「RC5」アルゴリズムの場合には、ラウンド数も考慮する必要があります。 アルゴリズムの強度をキー・サイズだけで表現するのでは不十分な場合、アクセス権エントリでAlgorithmParameterSpecクラス名(javax.crypto.spec.RC5ParameterSpecなど)、および指定されたAlgorithmParameterSpecオブジェクトの構築用パラメータ・リストも指定する必要があります。
アクセス権エントリの各項目は、指定された順序で記述する必要があります。 各エントリはセミコロンで終わります。
識別子(grant、permission)では大文字と小文字は区別されませんが、<crypto permission class name>と、値として引き渡される文字列については大文字と小文字が区別されます。
ノート: 「*」は、すべてのアクセス権エントリ・オプションでワイルドカードとして使用できます。 たとえば、<alg_name>オプションに「*」を指定すると、「すべてのアルゴリズム」という意味になります。
管轄ポリシー・ファイル(付録B: 管轄ポリシー・ファイル形式を参照)とセキュリティ・プロパティ・ファイルを使用してJava Cryptography Extension (JCE)アーキテクチャの暗号強度を構成できます。
Oracle実装で許可されているデフォルトの暗号強度は、無制限です。 ただし、管理者およびユーザーは、これまでどおり、それぞれの地理的な場所に応じたすべての輸入/輸出ガイドラインに従う必要があります。 アクティブな暗号強度は、構成ディレクトリにある管轄ポリシー・ファイルと組み合せてセキュリティ・プロパティ(通常は、java.securityプロパティ・ファイル内で設定される)を使用して決定されます。
無制限の暗号強度または強力だが制限付きの暗号強度を提供するために必要なJCEポリシー・ファイルはすべてJDKに含まれています。
JDK 8u161、7u171および6u181以降、<java_home>/jre/lib/security/policyの各サブディレクトリは、その中の管轄ポリシー・ファイルにより定義されたポリシー構成を表しています。 このディレクトリには、デフォルトで2つのサブディレクトリ(limitedおよびunlimited)が含まれ、それぞれにJARファイル内にパッケージ化されたポリシー・ファイルの完全セットが含まれています。
<java_home>jre/lib/security/policyに含まれる特定のポリシー構成をアクティブ化するには、(ファイル<java_home>/jre/lib/security/java.security内の) crypto.policyセキュリティ・プロパティの値を、ポリシー構成が含まれるサブディレクトリの名前に設定します。 デフォルトでは、crypto.policyセキュリティ・プロパティは定義されていません。
JDKは、次のようにポリシー構成を決定します。
crypto.policyセキュリティ・プロパティが定義されている場合、JDKは、このセキュリティ・プロパティにより指定されたポリシー構成を使用します。crypto.policyセキュリティ・プロパティが設定されておらず、レガシーの<java_home>/lib/securityディレクトリ内に従来のUS_export_policy.jarファイル(強力だが制限付きの暗号強度)とlocal_policy.jarファイル(無制限の暗号強度)が存在する場合、JDKは、これらのJARファイル内に指定されたポリシー構成を使用します。 これにより、ユーザーが古いJDKバージョンからアップグレードする場合に互換性を維持できます。 crypto.policyセキュリティ・プロパティが設定されておらず、<java_home>/lib/securityディレクトリ内にUS_export_policy.jarファイルおよびlocal_policy.jarファイルが存在しない場合、JDKは、無制限の暗号強度(cryto.policy=unlimitedに相当)を使用します。ポリシー構成の設定はVM全体に適用され、このVM上で実行されるすべてのアプリケーションに影響を及ぼします。 アプリケーション・レベルで暗号強度をオーバーライドする必要がある場合は、「アプリケーションの暗号化制限の免責を取得する方法」を参照してください。
unlimitedディレクトリには、無制限の暗号強度を提供する次のポリシー・ファイルが含まれています。
<java_home>/jre/lib/security/unlimited/local_policy.jar
default_local.policy: 国によっては、現地の制限事項がある場合がありますが、このポリシー・ファイルはunlimitedディレクトリにあるため、ここでは制限は示されません。
// Country-specific policy file for countries with no limits on crypto strength.
grant {
// There is no restriction to any algorithms.
permission javax.crypto.CryptoAllPermission;
};
<java_home>/jre/lib/security/unlimited/US_export_policy.jar
default_US_export.policy: 現在は米国からの暗号化の輸出に関する制限はないため、このファイルは制限なしで設定されます。
// Manufacturing policy file.
grant {
// There is no restriction to any algorithms.
permission javax.crypto.CryptoAllPermission;
};
limitedディレクトリには、強力だが制限付きの暗号強度を提供する次のポリシー・ファイルが含まれています。
<java_home>/jre/lib/security/limited/local_policy.jar
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;
};
exempt_local.policy: 輸入制限がある国は、"limited"を使用する必要がありますが、免責メカニズムを採用できる場合は、これらの制限を緩和できます。 アプリケーションの暗号化制限の免責を取得する方法を参照してください。 必ず、状況に応じた法的ガイダンスを入手してください。
// Some countries have import limits on crypto strength. So this file
// will be useful.
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";
};
<java_home>/jre/lib/security/limited/US_export_policy.jar
default_US_export.policy: これがlimitedディレクトリ内にあっても、現在は米国からの暗号化の輸出に関する制限はないため、このファイルは制限なしで設定されます。
// Manufacturing policy file.
grant {
// There is no restriction to any algorithms.
permission javax.crypto.CryptoAllPermission;
};
limitedまたはunlimitedディレクトリにあるポリシー・ファイル内の設定とは異なる暗号強度を構成するには、<java_home>/jre/lib/security内に新しいサブディレクトリを作成し、そこにポリシー・ファイルを配置します。 たとえば、<java_home>/jre/lib/security/customというディレクトリを作成できます。 このcustomディレクトリに、ファイルdefault_*export.policyまたはexempt_*local.policy、あるいはその両方を含めます。
customディレクトリ内のファイルで定義されている暗号強度を選択するには、<java_home>/jre/lib/security/java.securityファイル内にcrypto.policy=customを設定します。
/*
* 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();
}
}
/*
* 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();
}
}
/*
* Copyright (c) 1997, 2001, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;
/**
* This program generates a Blowfish key, retrieves its raw bytes, and
* then reinstantiates a Blowfish key from the key bytes.
* The reinstantiated key is used to initialize a Blowfish cipher for
* encryption.
*/
public class BlowfishKey {
public static void main(String[] args) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("Blowfish");
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();
SecretKeySpec skeySpec = new SecretKeySpec(raw, "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted =
cipher.doFinal("This is just an example".getBytes());
}
}
/*
* 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());
}
}
/*
* @(#)ReadPassword.java 1.1 06/06/07
*
* Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
import java.util.*;
import java.io.*;
import java.security.*;
public class ReadPassword {
/**
* Read a password from the InputStream "in".
* <p>
* As Strings are immutable, passwords should be stored as an array
* of characters, which can be blanked out when no longer needed.
* <p>
* If the provided InputStream is the System's Console, this method
* uses the non-echoing readPassword() method of java.io.Console
* (new to JDK 6). If not, a fallback implementation is used.
* <p>
* NOTE: For expository purposes, and because some applications do
* not understand multi-byte characters, only 8-bit ASCII passwords
* are handled here.
* <p>
* NOTE: If a SecurityManager is used, the default standard
* java.policy file found in the JDK (i.e.
* <java-home>/lib/security/java.policy) allows reading the
* line.separator property. If your environment is different, this
* code will need to be granted the appropriate privilege.
*
* @param in
* the InputStream used to obtain the password.
*
* @return A character array containing the password or passphrase,
* not including the line-termination characters,
* or null if an end of stream has been reached.
*
* @throws IOException
* if an I/O problem occurs
*/
public static final char[] readPassword(InputStream in)
throws IOException {
/*
* If available, directly use the java.io.Console class to
* avoid character echoing.
*/
if (in == System.in && System.console() != null) {
// readPassword returns "" if you just print ENTER,
return System.console().readPassword();
}
/*
* If a console is not available, read the InputStream
* directly. This approach may cause password echoing.
*
* Since different operating systems have different End-Of-Line
* (EOL) sequences, this algorithm should allow for
* platform-independent implementations. Typical EOL sequences
* are a single line feed ('\n'), or a carriage return/linefeed
* combination ('\r\n'). However, some OS's use a single
* a carriage return ('\r'), which complicates portability.
*
* Since we may not have the ability to push bytes back into the
* InputStream, another approach is used here. The javadoc for
* <code>java.lang.System.getProperties()</code> specifies that
* the set of system properties will contain a system-specific
* value for the "line.separator". Scan for this character
* sequence instead of hard-coding a particular sequence.
*/
/*
* Enclose the getProperty in a doPrivileged block to minimize
* the call stack permission required.
*/
char [] EOL = AccessController.doPrivileged(
new PrivilegedAction<char[]>() {
public char[] run() {
String s = System.getProperty("line.separator");
// Shouldn't happen.
if (s == null) {
throw new RuntimeException(
"line.separator not defined");
}
return s.toCharArray();
}
});
char [] buffer = new char[128];
try {
int len = 0; // len of data in buffer.
boolean done = false; // found the EOL sequence
int b; // byte read
while (!done) {
/*
* realloc if necessary
*/
if (len >= buffer.length) {
char [] newbuffer = new char[len + 128];
System.arraycopy(buffer, 0, newbuffer, 0, len);
Arrays.fill(buffer, ' ');
buffer = newbuffer;
}
/*
* End-of-Stream?
*/
if ((b = in.read()) == -1) {
// Return as much as we have, null otherwise.
if (len == 0) {
return null;
}
break;
} else {
/*
* NOTE: In the simple PBE example here,
* only 8 bit ASCII characters are handled.
*/
buffer[len++] = (char) b;
}
/*
* check for the EOL sequence. Do we have enough bytes?
*/
if (len >= EOL.length) {
int i = 0;
for (i = 0; i < EOL.length; i++) {
if (buffer[len - EOL.length + i] != EOL[i]) {
break;
}
}
done = (i == EOL.length);
}
}
/*
* If we found the EOL, strip the EOL chars.
*/
char [] result = new char[done ? len - EOL.length : len];
System.arraycopy(buffer, 0, result, 0, result.length);
return result;
} finally {
/*
* Zero out the buffer.
*/
if (buffer != null) {
Arrays.fill(buffer, ' ');
}
}
}
}