Friday, January 25, 2013

01 - Generating RSA keys

This is the first in a series of posts that provide self-contained Java code to do various practical operations in OpenPGP using the BouncyCastle library. It's easy to miss important steps that improve the security of your code, and these posts document what I've learnt. The emphasis will be on complete working examples 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.

The first post is about generating RSA keys. 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$ 

4 comments:

  1. Hello, this was great. I have the BC book but it was little help. The same trick with creating the PGPSignatureSubpacketGenerator to set the expiration time for the key does not work for the generation of a DSA/Elgamal key. Do you consult?

    ReplyDelete
  2. 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.

    Pgp encryption

    ReplyDelete
  3. Using essentially this code, when I try to use the pgp keys generated by this I get:

    gpg: can't handle public key algorithm 3
    gpg: can't handle public key algorithm 2
    gpg: no default secret key: Checksum error

    Any ideas?

    ReplyDelete
  4. Hi! Can you please update this code to match latest API version which is 1.56 (at the moment of this comment)?

    ReplyDelete