Self-contained Java code to generate OpenPGP RSA keys using the BouncyCastle Java library. It's easy to miss important steps that improve the security of your code, and this post documents what I've learnt. The emphasis is on a complete working example that you can adapt for your needs.
I will be using BouncyCastle version 1.48 (currently in beta) in all these examples, and the lightweight APIs. You should use bcprov-jdk15on-148b11.jar and bcpg-jdk15on-148b11.jar to compile and run the code.
For better key management, you should generally use separate keys for signing and encryption. This code shows how you can generate a public key that uses two RSA keys for signing and encryption, and how to add signatures and cryptographic preferences. It also shows how you can change the hash iteration count on the passphrase that encrypts the private key to make it more resilient to a brute-force offline attack.
import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.math.BigInteger; import java.security.SecureRandom; import java.util.Date; import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; import org.bouncycastle.bcpg.sig.Features; import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.crypto.generators.RSAKeyPairGenerator; import org.bouncycastle.crypto.params.RSAKeyGenerationParameters; import org.bouncycastle.openpgp.PGPEncryptedData; import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPKeyRingGenerator; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; import org.bouncycastle.openpgp.operator.PGPDigestCalculator; import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyEncryptorBuilder; import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder; import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPair; public class RSAGen { public static void main(String args[]) throws Exception { char pass[] = {'h', 'e', 'l', 'l', 'o'}; PGPKeyRingGenerator krgen = generateKeyRingGenerator ("alice@example.com", pass); // Generate public key ring, dump to file. PGPPublicKeyRing pkr = krgen.generatePublicKeyRing(); BufferedOutputStream pubout = new BufferedOutputStream (new FileOutputStream("dummy.pkr")); pkr.encode(pubout); pubout.close(); // Generate private key, dump to file. PGPSecretKeyRing skr = krgen.generateSecretKeyRing(); BufferedOutputStream secout = new BufferedOutputStream (new FileOutputStream("dummy.skr")); skr.encode(secout); secout.close(); } public final static PGPKeyRingGenerator generateKeyRingGenerator (String id, char[] pass) throws Exception { return generateKeyRingGenerator(id, pass, 0xc0); } // Note: s2kcount is a number between 0 and 0xff that controls the // number of times to iterate the password hash before use. More // iterations are useful against offline attacks, as it takes more // time to check each password. The actual number of iterations is // rather complex, and also depends on the hash function in use. // Refer to Section 3.7.1.3 in rfc4880.txt. Bigger numbers give // you more iterations. As a rough rule of thumb, when using // SHA256 as the hashing function, 0x10 gives you about 64 // iterations, 0x20 about 128, 0x30 about 256 and so on till 0xf0, // or about 1 million iterations. The maximum you can go to is // 0xff, or about 2 million iterations. I'll use 0xc0 as a // default -- about 130,000 iterations. public final static PGPKeyRingGenerator generateKeyRingGenerator (String id, char[] pass, int s2kcount) throws Exception { // This object generates individual key-pairs. RSAKeyPairGenerator kpg = new RSAKeyPairGenerator(); // Boilerplate RSA parameters, no need to change anything // except for the RSA key-size (2048). You can use whatever // key-size makes sense for you -- 4096, etc. kpg.init (new RSAKeyGenerationParameters (BigInteger.valueOf(0x10001), new SecureRandom(), 2048, 12)); // First create the master (signing) key with the generator. PGPKeyPair rsakp_sign = new BcPGPKeyPair (PGPPublicKey.RSA_SIGN, kpg.generateKeyPair(), new Date()); // Then an encryption subkey. PGPKeyPair rsakp_enc = new BcPGPKeyPair (PGPPublicKey.RSA_ENCRYPT, kpg.generateKeyPair(), new Date()); // Add a self-signature on the id PGPSignatureSubpacketGenerator signhashgen = new PGPSignatureSubpacketGenerator(); // Add signed metadata on the signature. // 1) Declare its purpose signhashgen.setKeyFlags (false, KeyFlags.SIGN_DATA|KeyFlags.CERTIFY_OTHER); // 2) Set preferences for secondary crypto algorithms to use // when sending messages to this key. signhashgen.setPreferredSymmetricAlgorithms (false, new int[] { SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192, SymmetricKeyAlgorithmTags.AES_128 }); signhashgen.setPreferredHashAlgorithms (false, new int[] { HashAlgorithmTags.SHA256, HashAlgorithmTags.SHA1, HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA512, HashAlgorithmTags.SHA224, }); // 3) Request senders add additional checksums to the // message (useful when verifying unsigned messages.) signhashgen.setFeature (false, Features.FEATURE_MODIFICATION_DETECTION); // Create a signature on the encryption subkey. PGPSignatureSubpacketGenerator enchashgen = new PGPSignatureSubpacketGenerator(); // Add metadata to declare its purpose enchashgen.setKeyFlags (false, KeyFlags.ENCRYPT_COMMS|KeyFlags.ENCRYPT_STORAGE); // Objects used to encrypt the secret key. PGPDigestCalculator sha1Calc = new BcPGPDigestCalculatorProvider() .get(HashAlgorithmTags.SHA1); PGPDigestCalculator sha256Calc = new BcPGPDigestCalculatorProvider() .get(HashAlgorithmTags.SHA256); // bcpg 1.48 exposes this API that includes s2kcount. Earlier // versions use a default of 0x60. PBESecretKeyEncryptor pske = (new BcPBESecretKeyEncryptorBuilder (PGPEncryptedData.AES_256, sha256Calc, s2kcount)) .build(pass); // Finally, create the keyring itself. The constructor // takes parameters that allow it to generate the self // signature. PGPKeyRingGenerator keyRingGen = new PGPKeyRingGenerator (PGPSignature.POSITIVE_CERTIFICATION, rsakp_sign, id, sha1Calc, signhashgen.generate(), null, new BcPGPContentSignerBuilder (rsakp_sign.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1), pske); // Add our encryption subkey, together with its signature. keyRingGen.addSubKey (rsakp_enc, enchashgen.generate(), null); return keyRingGen; } }
You can check the generated keys using gpg as follows.
bash-3.2$ gpg --list-packet dummy.pkr :public key packet: version 4, algo 3, created 1359314310, expires 0 pkey[0]: [2048 bits] pkey[1]: [17 bits] :user ID packet: "alice@example.com" :signature packet: algo 3, keyid B18F6A3F90D38D55 version 4, created 1359314310, md5len 0, sigclass 0x13 digest algo 2, begin of digest b3 46 hashed subpkt 2 len 4 (sig created 2013-01-27) hashed subpkt 27 len 1 (key flags: 03) hashed subpkt 11 len 3 (pref-sym-algos: 9 8 7) hashed subpkt 21 len 5 (pref-hash-algos: 8 2 9 10 11) hashed subpkt 30 len 1 (features: 01) subpkt 16 len 8 (issuer key ID B18F6A3F90D38D55) data: [2046 bits] :public sub key packet: version 4, algo 2, created 1359314310, expires 0 pkey[0]: [2048 bits] pkey[1]: [17 bits] :signature packet: algo 3, keyid B18F6A3F90D38D55 version 4, created 1359314310, md5len 0, sigclass 0x18 digest algo 2, begin of digest a4 cf hashed subpkt 2 len 4 (sig created 2013-01-27) hashed subpkt 27 len 1 (key flags: 0C) subpkt 16 len 8 (issuer key ID B18F6A3F90D38D55) data: [2047 bits] bash-3.2$ gpg --list-packet dummy.skr :secret key packet: version 4, algo 3, created 1359314310, expires 0 skey[0]: [2048 bits] skey[1]: [17 bits] iter+salt S2K, algo: 9, SHA1 protection, hash: 8, salt: ad1e74252dac691f protect count: 4194304 (192) protect IV: 3f 44 39 77 d0 27 49 2c 97 9d d0 a8 b7 ae 6b 48 encrypted stuff follows :user ID packet: "alice@example.com" :signature packet: algo 3, keyid B18F6A3F90D38D55 version 4, created 1359314310, md5len 0, sigclass 0x13 digest algo 2, begin of digest b3 46 hashed subpkt 2 len 4 (sig created 2013-01-27) hashed subpkt 27 len 1 (key flags: 03) hashed subpkt 11 len 3 (pref-sym-algos: 9 8 7) hashed subpkt 21 len 5 (pref-hash-algos: 8 2 9 10 11) hashed subpkt 30 len 1 (features: 01) subpkt 16 len 8 (issuer key ID B18F6A3F90D38D55) data: [2046 bits] :secret sub key packet: version 4, algo 2, created 1359314310, expires 0 skey[0]: [2048 bits] skey[1]: [17 bits] iter+salt S2K, algo: 9, SHA1 protection, hash: 8, salt: ad1e74252dac691f protect count: 4194304 (192) protect IV: 2a e7 bc fd 1d a2 ac 86 81 34 ca db d0 f4 21 cd encrypted stuff follows :signature packet: algo 3, keyid B18F6A3F90D38D55 version 4, created 1359314310, md5len 0, sigclass 0x18 digest algo 2, begin of digest a4 cf hashed subpkt 2 len 4 (sig created 2013-01-27) hashed subpkt 27 len 1 (key flags: 0C) subpkt 16 len 8 (issuer key ID B18F6A3F90D38D55) data: [2047 bits] bash-3.2$