JCEでDiffie-Hellman鍵交換を行う

JCEDiffie-Hellman鍵交換を行う方法を調べた時のメモ*1。内容はAとBが秘密鍵を交換するシンプルなものです。

最初にAがベースとなる素数等のパラメータを生成します。AlgorithmParameterGeneratorを使わずに、AlgorithmParametersのinit()にDHAlgorithmParameterSpecを直接生成して渡しても良いような気もしますが試していません。それとビットサイズの1024はJCEのデフォルトサイズに書いてあった数字をそのまま使っています。

AlgorithmParameterGenerator algoParamGenerator = AlgorithmParameterGenerator.getInstance("DH");
algoParamGenerator.init(1024);
AlgorithmParameters algoParams = algoParamGenerator.generateParameters();
DHParameterSpec paramSpec = (DHParameterSpec)algoParams.getParameterSpec(DHParameterSpec.class);

次にAが乱数の生成、パラメータと乱数からmodの計算を行います。計算結果はKeyPairに格納されるようです。PrivateKeyにパラメータと生成した乱数、PublicKeyにパラメータとmodした値が入るようです。

KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("DH");
keyPairGen.initialize(paramSpec);
KeyPair keyPair = keyPairGen.generateKeyPair();

ここまで準備ができたら、PublicKeyをBに渡します。オブジェクトのままでは渡せないので、一旦byte[]にエンコードしておきます。

byte[] encodedKey = keyPair.getPublic().getEncoded();
// encodedKeyをBに渡す

AからエンコードされたPublicKeyを受け取ったBは、それをPublicKeyオブジェクトに戻します。またB側のmod計算に必要となるため、PublicKeyからDHPublicKeySpecを取り出し、A側と同じ値を持つDHParameterSpecを作成します。

KeyFactory keyFactory = KeyFactory.getInstance("DH");
PublicKey otherPublicKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedPublicKey));
DHPublicKeySpec keySpec = keyFactory.getKeySpec(otherPublicKey, DHPublicKeySpec.class);
DHParameterSpec paramSpec = new DHParameterSpec(keySpec.getP(), keySpec.getG());

先ほどと同じように、B側の乱数生成、パラメータと乱数からmodの計算を行います。

KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("DH");
keyPairGen.initialize(paramSpec);
KeyPair keyPair = keyPairGen.generateKeyPair();

計算結果をBからAに送り返してやります。

byte[] encodedKey = keyPair.getPublic().getEncoded();
// encodedKeyをAに渡す

これで準備が完了したので、お互いに秘密鍵を生成します。鍵の生成はAとBで同じ方法になります。

シークレットの生成には、KeyAgreementクラスを利用します。利用するPrivateKeyは自分のもの、PublicKeyは相手から受け取ったものです。

keyAgreement = KeyAgreement.getInstance("DH");
keyAgreement.init(keyPair.getPrivate());
keyAgreement.doPhase(otherPublicKey, true);

これでKeyAgreementの設定が完了して、お互いに同じシークレットを共有した状態になります。後はこのシークレットを使って秘密鍵を生成するだけです。

一番簡単な方法はgenerateSecretを利用する方法です。引数にアルゴリズム名を指定すると、その鍵が生成されます。ただアルゴリズム名だけですので、ビットサイズの指定など細かい設定が出来ません。

SecretKey secretKey = keyAgreement.generateSecret("AES");

もう一つはシークレットをbyte[]として取り出してそれを利用する方法です。generateSecret()では、シークレットの先頭から必要バイト分をそのまま鍵として利用するようですので、ここではそのマネをして256ビットのAESキーを生成するコードを載せておきます。

byte[] secret = keyAgreement.generateSecret();
SecretKey secretKey = new SecretKeySpec(secret, 0, 256/8, "AES");

*1:書いてる人は素人なので、真面目にセキュリティが必要な人は見ない方が良いです。