この記事から得られる知識
java.security.spec
パッケージの役割と、Javaセキュリティアーキテクチャ全体におけるその重要性。- 鍵仕様 (
KeySpec
) とアルゴリズムパラメータ仕様 (AlgorithmParameterSpec
) の基本的な概念と、それらがなぜ必要とされるのか。 X509EncodedKeySpec
,PKCS8EncodedKeySpec
,RSAPublicKeySpec
,PBEKeySpec
といった主要な仕様クラスの具体的な使い方。- 公開鍵、秘密鍵、共通鍵を、バイト配列や数学的な構成要素から生成するための実践的なコード例。
- 暗号化処理(特にCBCモードやGCMモード)における初期化ベクトル (IV) などのパラメータを指定する方法。
第1章: java.security.specとは何か? – 「透明な」仕様の世界
Javaで暗号化やデジタル署名といったセキュリティ機能を実装する際、私たちはJava Cryptography Architecture (JCA) というフレームワークを利用します。このJCAの中核をなすのが、鍵やアルゴリズムのパラメータを扱う仕組みです。java.security.spec
パッケージは、この「鍵」と「パラメータ」を透明な (transparent) 形式で表現するためのクラスとインターフェースを提供します。
このパッケージは、主に2つの重要なインターフェースを中心に構成されています。
KeySpec
: 鍵を構成する鍵データの仕様です。鍵を数学的な値の集まりとして表現したり、標準的なエンコード形式(ASN.1など)で表現したりします。AlgorithmParameterSpec
: 暗号アルゴリズムが使用するパラメータの仕様です。例えば、暗号化モードで必要となる初期化ベクトル (IV) や、パスワードベース暗号化で用いるソルトなどがこれにあたります。
これらの「仕様」クラスを使うことで、開発者は特定の暗号プロバイダの実装に依存することなく、標準的な方法で鍵やパラメータを扱うことができます。これにより、コードの移植性や相互運用性が大幅に向上するのです。
第2章: 鍵仕様の基本 – KeySpecインターフェース
鍵を形作る設計図
KeySpec
はマーカーインターフェースであり、それ自体はメソッドを定義していません。このインターフェースを実装するクラスが、具体的な鍵の「仕様」を提供します。KeySpec
の実装は、大きく分けて2つのカテゴリに分類できます。
1. エンコードされた鍵仕様 (Encoded Key Specs)
鍵を特定の標準形式でエンコードしたバイト配列として扱うための仕様です。ファイルに保存された鍵を読み込んだり、ネットワーク経由で鍵を送受信したりする際に非常に重要です。
X509EncodedKeySpec
公開鍵の標準形式であるX.509 SubjectPublicKeyInfo形式でエンコードされた公開鍵を表します。これは、SSL/TLS証明書などで広く使われている形式です。通常、KeyFactory
と組み合わせて使用し、バイト配列からPublicKey
オブジェクトを生成します。
byte[] publicKeyBytes = ... // ファイルなどから読み込んだX.509形式の公開鍵データ
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
PKCS8EncodedKeySpec
秘密鍵の標準形式であるPKCS#8 PrivateKeyInfo形式でエンコードされた秘密鍵を表します。こちらもKeyFactory
と連携し、バイト配列からPrivateKey
オブジェクトを復元するために使われます。
byte[] privateKeyBytes = ... // ファイルなどから読み込んだPKCS#8形式の秘密鍵データ
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
2. アルゴリズム固有の鍵仕様 (Algorithm-Specific Key Specs)
鍵を構成する数学的な要素(数値など)を直接指定するための仕様です。特定のアルゴリズムの鍵をプログラム内で動的に生成する場合などに役立ちます。
RSAPublicKeySpec & RSAPrivateKeySpec
RSA鍵ペアの構成要素であるモジュラス (modulus) と指数 (exponent) をBigInteger
オブジェクトで直接指定します。
例えば、既知のモジュラスと公開指数からRSAPublicKey
を生成できます。
BigInteger modulus = new BigInteger("...");
BigInteger publicExponent = new BigInteger("65537");
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(modulus, publicExponent);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
同様に、秘密鍵もモジュラスと秘密指数から生成可能です。さらに、中国剰余定理 (CRT) の値を含むRSAPrivateCrtKeySpec
など、より詳細な仕様も存在します。
ECPublicKeySpec & ECPrivateKeySpec
楕円曲線暗号 (ECC) の鍵を、楕円曲線上の点 (アフィン座標) とドメインパラメータ (ECParameterSpec
) を使って指定します。
// ECParameterSpec (曲線の定義) の取得は省略
ECParameterSpec ecParams = ...;
ECPoint w = ...; // 公開鍵となる曲線上の点
ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(w, ecParams);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
PublicKey ecPublicKey = keyFactory.generatePublic(publicKeySpec);
PBEKeySpec
パスワードベース暗号化 (PBE: Password-Based Encryption) のための仕様です。ユーザーが入力したパスワード (char[]
)、ソルト (byte[]
)、反復回数 (iteration count)、鍵長 (key length) を指定して、実際の暗号鍵を生成するための元データとなります。
String
ではなくchar[]
でパスワードを扱うのは、処理後に配列の内容を上書きしてメモリからパスワードを消去できるようにするためで、セキュリティ上の重要なプラクティスです。
char[] password = "super_secret_password".toCharArray();
byte[] salt = new byte;
// SecureRandomなどでソルトを生成
int iterationCount = 65536;
int keyLength = 256;
PBEKeySpec pbeKeySpec = new PBEKeySpec(password, salt, iterationCount, keyLength);
// この後、SecretKeyFactoryで鍵を生成する
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
SecretKey key = factory.generateSecret(pbeKeySpec);
// セキュリティのため、使い終わったパスワード配列はクリアする
java.util.Arrays.fill(password, ' ');
SecretKeySpec (javax.crypto.spec)
厳密には java.security.spec
ではなく javax.crypto.spec
パッケージに属しますが、KeySpec
を実装しており、関連性が高いためここで紹介します。このクラスは、AESやDESなどの共通鍵暗号で使われる鍵データを、単純なバイト配列として直接指定するために使用します。
byte[] keyBytes = new byte; // 128-bit AES key
// SecureRandomなどで鍵データを生成
String algorithm = "AES";
SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, algorithm);
// この secretKeySpec は SecretKey インターフェースも実装しているため、
// そのまま Cipher.init() に渡すことができる
第3章: アルゴリズムパラメータ仕様 – AlgorithmParameterSpecインターフェース
アルゴリズムの振る舞いを調整する
暗号アルゴリズムは、鍵だけでなく、その動作を制御するための追加パラメータを必要とすることがよくあります。AlgorithmParameterSpec
は、これらのパラメータを透明な形で表現するためのマーカーインターフェースです。Cipher.init()
やKeyPairGenerator.initialize()
などのメソッドに渡すことで、アルゴリズムの挙動を細かく設定できます。
クラス名 | 主な用途と説明 |
---|---|
IvParameterSpec (javax.crypto.spec) | ブロック暗号のCBC, CFB, OFB, GCMモードなどで使用される初期化ベクトル (IV) を指定します。IVは、同じ鍵で同じ平文を暗号化しても、毎回異なる暗号文が生成されるようにするために不可欠です。 |
GCMParameterSpec (javax.crypto.spec) | AES/GCMモードで使用します。IVに加えて、認証タグの長さ (bit単位) を指定する必要があります。GCMは暗号化と同時に認証も行うAEAD (Authenticated Encryption with Associated Data) スキームの一つです。 |
PBEParameterSpec (javax.crypto.spec) | 古いPBE標準 (PKCS#5) で使用され、ソルトと反復回数を指定します。PBEKeySpec が鍵生成の元データであるのに対し、こちらは暗号化処理のパラメータとしてCipher.init() に渡されます。 |
ECGenParameterSpec | 楕円曲線暗号 (ECC) の鍵ペアを生成する際に、使用する曲線の名前(例: “secp256r1″)を文字列で指定します。KeyPairGenerator の初期化に使われます。 |
PSSParameterSpec | RSA-PSS署名スキームで使用するパラメータを指定します。ハッシュアルゴリズム、マスク生成関数 (MGF)、ソルト長などを細かく設定できます。 |
これらのパラメータ仕様は、特にCipher
クラスの初期化において重要な役割を果たします。例えば、AES/GCMで暗号化を行う際のコードは以下のようになります。
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKey secretKey = ...; // 鍵の準備
byte[] iv = new byte; // GCMでは12バイト(96ビット)のIVが推奨される
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(iv);
// 認証タグは128ビット (16バイト)
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmParameterSpec);
byte[] encryptedData = cipher.doFinal("plain text".getBytes());
復号時にも、暗号化時と全く同じIVと認証タグ長を持つGCMParameterSpec
を使ってCipher
を初期化する必要があります。
第4章: 実践的なコーディング例 – 仕様クラスの連携
理論から実践へ
これまで見てきた仕様クラスが、実際のアプリケーションでどのように連携して使われるのか、具体的なシナリオを通して見ていきましょう。
シナリオ1: ファイルから公開鍵・秘密鍵を読み込み、署名・検証を行う
OpenSSLなどで生成したPEM形式の鍵ファイルをJavaで読み込んで利用する、典型的な例です。(PEMからDER形式への変換は事前に行われているものとします)
// 1. ファイルから鍵データを読み込む (この部分は擬似コード)
byte[] publicKeyDer = Files.readAllBytes(Paths.get("public_key.der"));
byte[] privateKeyDer = Files.readAllBytes(Paths.get("private_key.der"));
// 2. KeySpec を使って PublicKey/PrivateKey オブジェクトを生成
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyDer);
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyDer);
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
System.out.println("鍵の読み込みに成功しました。");
System.out.println("公開鍵アルゴリズム: " + publicKey.getAlgorithm());
System.out.println("秘密鍵フォーマット: " + privateKey.getFormat());
// 3. 署名生成
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
byte[] message = "This is a test message.".getBytes("UTF-8");
signature.update(message);
byte[] signatureBytes = signature.sign();
System.out.println("署名を生成しました。");
// 4. 署名検証
signature.initVerify(publicKey);
signature.update(message);
boolean verified = signature.verify(signatureBytes);
System.out.println("署名の検証結果: " + (verified ? "成功" : "失敗"));
この例では、X509EncodedKeySpec
とPKCS8EncodedKeySpec
が、外部の鍵データとJavaのKey
オブジェクトとの間の橋渡し役として機能していることがわかります。
シナリオ2: AES/CBCによるデータの暗号化・復号
CBCモードでは初期化ベクトル (IV) が必須です。IvParameterSpec
を使ってこれを指定します。
// 1. 共通鍵を生成 (または取得)
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey secretKey = keyGen.generateKey();
// 2. CipherとIVの準備
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[cipher.getBlockSize()]; // AESのブロックサイズは16バイト
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
// 3. 暗号化
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
byte[] plaintext = "Sensitive data here".getBytes();
byte[] ciphertext = cipher.doFinal(plaintext);
// ----- ここで ciphertext と iv を相手に渡す -----
// 4. 復号 (受け取り側)
// 渡されたivから同じIvParameterSpecを再作成
IvParameterSpec receivedIvSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, receivedIvSpec);
byte[] decryptedText = cipher.doFinal(ciphertext);
System.out.println("復号結果: " + new String(decryptedText));
第5章: まとめ – なぜ「仕様」が重要なのか
java.security.spec
パッケージは、Javaのセキュリティ機能を支える屋台骨とも言える存在です。一見すると地味で、直接使う機会は少ないように感じるかもしれません。しかし、その役割は非常に重要です。
- 透明性とアクセス性: 鍵やパラメータの内部構造にアクセスする標準的な方法を提供します。これにより、鍵の構成要素を分析したり、異なるライブラリ間で鍵データを交換したりすることが可能になります。
- 相互運用性: X.509やPKCS#8といった業界標準のエンコーディング形式をサポートすることで、Javaアプリケーションが他の言語やシステムで作成された鍵を扱えるようになります。
- 柔軟性と拡張性: JCAのプロバイダベースのアーキテクチャと組み合わせることで、開発者はアルゴリズムの実装詳細を意識することなく、鍵やパラメータを「仕様」として抽象的に操作できます。
暗号化は、単にCipher.doFinal()
を呼び出すだけではありません。その裏側では、鍵がどのように表現され、どのようなパラメータでアルゴリズムが制御されているのかという「仕様」が常に存在しています。
本記事で解説したjava.security.spec
の各クラスへの理解を深めることは、Javaで堅牢かつセキュアなアプリケーションを構築するための確かな一歩となるでしょう。