As such, the following code seems to correctly generate a PGP keyring with EC keys (as in: it can be parsed with Bouncycastle). However, both Thunderbird and GnuPG do have issues with it. Here is the code, which is based on an already working RSA/Elgamal based implementation of mine (I included only the relevant methods for the sake of readability):
private static final Provider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider();
private static final ASN1ObjectIdentifier CURVE_OID = CryptlibObjectIdentifiers.curvey25519;
private static final int MASTER_KEY_ALGORITHM = ECDSA;
private static final int SUB_KEY_ALGORITHM = ECDH;
private static final int MASTER_KEY_FLAGS = AUTHENTICATION | CERTIFY_OTHER | SIGN_DATA | ENCRYPT_STORAGE | ENCRYPT_COMMS;
private static final int SUB_KEY_FLAGS = ENCRYPT_COMMS | ENCRYPT_STORAGE;
private static final int[] PREFERRED_HASH_ALGORITHMS = {SHA256, SHA1, SHA384, SHA512, SHA224};
private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = {AES_256, AES_192, AES_128};
public PGPKeyRingGenerator createPGPKeyRingGenerator(String identity, String passphrase, int keySize)
throws PGPException, InvalidAlgorithmParameterException {
PGPKeyPair masterKeyPair = generateEcPgpKeyPair(MASTER_KEY_ALGORITHM);
PGPKeyPair subKeyPair = generateEcPgpKeyPair(SUB_KEY_ALGORITHM);
PGPDigestCalculator sha1Calc = new BcPGPDigestCalculatorProvider().get(SHA1);
PGPDigestCalculator sha256Calc = new BcPGPDigestCalculatorProvider().get(SHA256);
PGPSignatureSubpacketVector masterKeySubPacket = generateMasterkeySubpacket(MASTER_KEY_FLAGS);
PGPSignatureSubpacketVector subKeySubPacket = generateSubkeySubpacket(SUB_KEY_FLAGS);
PGPKeyRingGenerator keyRingGenerator =
new PGPKeyRingGenerator(POSITIVE_CERTIFICATION, masterKeyPair, identity, sha1Calc, masterKeySubPacket,
null,
new JcaPGPContentSignerBuilder(masterKeyPair.getPublicKey().getAlgorithm(), SHA256)
.setProvider(BOUNCY_CASTLE_PROVIDER),
new JcePBESecretKeyEncryptorBuilder(AES_256, sha256Calc)
.setProvider(BOUNCY_CASTLE_PROVIDER)
.build(passphrase.toCharArray()));
keyRingGenerator.addSubKey(subKeyPair, subKeySubPacket, null);
return keyRingGenerator;
}
private AsymmetricCipherKeyPair generateEcKeyPair(ASN1ObjectIdentifier curveOid) {
X9ECParameters curve = CustomNamedCurves.getByOID(curveOid);
ECNamedDomainParameters ecDomainParameters = new ECNamedDomainParameters(curveOid, curve.getCurve(), curve.getG(), curve.getN(), curve.getH(), curve.getSeed());
ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
keyPairGenerator.init(new ECKeyGenerationParameters(ecDomainParameters, new SecureRandom()));
return keyPairGenerator.generateKeyPair();
}
private BcPGPKeyPair generateEcPgpKeyPair(int algorithm)
throws InvalidAlgorithmParameterException, PGPException {
return new BcPGPKeyPair(algorithm, generateEcKeyPair(CURVE_OID), new Date());
}
private PGPSignatureSubpacketVector generateMasterkeySubpacket(int keyFlags) {
PGPSignatureSubpacketGenerator subpacketGen = new PGPSignatureSubpacketGenerator();
subpacketGen.setKeyFlags(false, keyFlags);
subpacketGen.setPreferredSymmetricAlgorithms(false, PREFERRED_SYMMETRIC_ALGORITHMS);
subpacketGen.setPreferredHashAlgorithms(false, PREFERRED_HASH_ALGORITHMS);
return subpacketGen.generate();
}
private PGPSignatureSubpacketVector generateSubkeySubpacket(int keyFlags) {
PGPSignatureSubpacketGenerator subpacketGen = new PGPSignatureSubpacketGenerator();
subpacketGen.setKeyFlags(false, keyFlags);
return subpacketGen.generate();
}
So, what do I get here and what are the issues:
- When I pick SHA1 as hashing alg. for the master key, GnuPG actually does read the keyring, but the subkey is missing the "E" (encryption) key capability (and also we get the perfectly correct warning about SHA1 being insufficient):
>> gpg ./ec-sha1.asc
gpg: WARNING: no command supplied. Trying to guess what you mean ...
gpg: ECDSA key 8DF084241E957FFF requires a 256 bit or larger hash (hash is SHA1)
gpg: ECDSA key 8DF084241E957FFF requires a 256 bit or larger hash (hash is SHA1)
pub cv25519 2021-09-17 [SCA]
553E322AB50692F67E23FE7B8DF084241E957FFF
uid Foo Bar <foo@bar.loc>
sub cv25519 2021-09-17 []
- With SHA256, the keyring cannot be parsed at all:
>> gpg ./ec.asc
gpg: WARNING: no command supplied. Trying to guess what you mean ...
gpg: Fatal: _gcry_mpi_ec_add_points: Montgomery not yet supported
Can anyone spot the issue with the code, so that both GnuPG and Thunderbird will be able to correctly parse the keyring?