JavaからGnuPG(PGP)の鍵を利用する

鍵生成のついでにJavaから利用する方法のメモ

自力で読み込みプログラムを作成すると大変なので、Bouncy Castleのライブラリを利用します。必要なjarファイルはbcprovとbcpgの二つになります。

最初にGnuPGの鍵が保存されているsecring.gpgを読み込みます。読み込まれた結果はPGPSecretKeyRingに格納されます。

FileInputStream is = new FileInputStream("secring.gpg");
PGPSecretKeyRing keyRing = new PGPSecretKeyRing(is);
is.close();

PGPSecretKeyRingは主鍵と副鍵からなる複数のPGPSecretKeyの集合になっています。getSecretKeys()で全ての鍵を取り出すことができます。またgetSecretKey()を使うと、ドキュメントに明記はされていませんが、主鍵を取り出すことができるようです。

secring.gpgに関連付けられたユーザIDは主鍵のPGPSecretKeyに格納されているようです。以下のようなコードでユーザIDを表示することができます。

public void printUserIDs(PGPSecretKeyRing keyRing) {
	Iterator it = keyRing.getSecretKey().getUserIDs();
	while(it.hasNext()) {
		System.out.println(it.next());
	}
}

secring.gpgには用途ごとに異なる鍵が含まれていますので、利用する際にはPGPSecretKeyRingから用途別に抜き出してやる必要があります。用途を調べるには署名用はPGPSecretKeyのisSigningKey()、暗号用はPGPPublicKeyのisEncryptionKey()を利用します。

/**
 * PGPSecretKeyRingから署名用の鍵を抜き出す
 */
public List<PGPSecretKey> getSigningKeyList(PGPSecretKeyRing keyRing) {
	List<PGPSecretKey> keyList = new ArrayList<PGPSecretKey>();
	
	Iterator it = keyRing.getSecretKeys();
	while(it.hasNext()) {
		PGPSecretKey secretKey = (PGPSecretKey) it.next();
		if (secretKey.isSigningKey()) {
			keyList.add(secretKey);
		}
	}

	return keyList;
}

/**
 * PGPSecretKeyRingから暗号用の鍵を抜き出す
 */
public static List<PGPSecretKey> getEncryptionKeyList(PGPSecretKeyRing keyRing) {
	List<PGPSecretKey> keyList = new ArrayList<PGPSecretKey>();

	Iterator it = keyRing.getSecretKeys();
	while(it.hasNext()) {
		PGPSecretKey secretKey = (PGPSecretKey) it.next();
		PGPPublicKey publicKey = secretKey.getPublicKey();
		if (publicKey.isEncryptionKey()) {
			keyList.add(secretKey);
		}
	}

	return keyList;
}

後はPGPSecretKeyからjava.securityのPrivateKeyやPublicKeyを取り出せば、JCEのインターフェイスを利用して署名等に利用できるようになります。

List<PGPSecretKey> signingKeyList = getSigningKeyList(keyRing);
PGPSecretKey secretKey = signingKeyList.get(0); // 例として最初に見つかった署名用鍵を利用
printFingerprint(secretKey);

if (Security.getProvider("BC") == null) {
	Security.addProvider(new BouncyCastleProvider());
}

char[] passphrase = "passphrase".toCharArray();
// 実際は何らかの方法でユーザからの入力などを行う
// JDK1.6以降ではコンソールからのパスワード入力が可能になったが、
// Eclipseのコンソールでは動作しないので注意
// char[] passphrase = System.console().readPassword();

// パスフレーズを用いて暗号化された鍵を取り出す。
// 取り出した後は、パスフレーズをメモリ上に残さないためにクリアする。
PGPPrivateKey pgpPrivateKey = secretKey.extractPrivateKey(passphrase, "BC");
Arrays.fill(passphrase, '0');

// PGPPrivateKey ==> PrivateKey
PrivateKey privateKey = pgpPrivateKey.getKey();

// PGPPublicKey ==> PublicKey
PGPPublicKey pgpPublicKeyKey = secretKey.getPublicKey();
PublicKey publicKey = pgpPublicKeyKey.getKey("BC");
public void printFingerprint(PGPSecretKey secretKey) {
	byte[] fingerprint = secretKey.getPublicKey().getFingerprint();
	for(int i = 0; i < fingerprint.length; i++) {
		System.out.print(String.format("%02X", fingerprint[i]));
		if ((i % 2) != 0) {
			System.out.print(" ");
		}
	}
	System.out.println("");
}

これでGnuPGの鍵を利用することが出来るようになりました。以下は署名の生成と検証に利用する例です。

// 署名生成
Signature signature = Signature.getInstance("SHA1withRSA");
signature.initSign(privateKey);
signature.update("hello".getBytes());
byte[] sign = signature.sign();

// 署名検証
signature.initVerify(publicKey);
signature.update("hello".getBytes());
boolean result = signature.verify(sign);
System.out.println("verify: " + (result ? "ok" : "bad"));