1

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:

  1. 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 []
  1. 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?

4

1 回答 1

0

事实证明,这样的密钥环生成是好的——问题在于选择的曲线。主密钥生成。要修复密钥生成:

  1. 一个人可以保持代码不变并选择一条与 ECDSA 方案一起使用的不同曲线,例如secp256r1,而不是Curve25519
    private static final ASN1ObjectIdentifier CURVE_OID = SECObjectIdentifiers.secp256r1;
  1. 但是,如果要使用 Curve25519(NB 这是一个不错的选择,更多信息请参阅https://safecurves.cr.yp.to/),那么我们必须将 EdDSA 与 Ed25519 一起使用(参见:RFC 8032)为万能钥匙。但是,子密钥/加密密钥的生成保持不变。Ed25519 是一种 EdDSA(与 ECDSA 相对)签名方案,曲线为 edwards25519。两个以正确的方式实现它,上面的代码有两点需要改变:
    private static final int MASTER_KEY_ALGORITHM = PublicKeyAlgorithmTags.EDDSA;
    private static final ASN1ObjectIdentifier MASTER_CURVE_OID = EdECObjectIdentifiers.id_Ed25519;
    private static final ASN1ObjectIdentifier SUB_CURVE_OID = CryptlibObjectIdentifiers.curvey25519;

以及主密钥对的生成,如下:

    public AsymmetricCipherKeyPair generateEd25519KeyPair() {

        Ed25519KeyPairGenerator keyPairGenerator = new Ed25519KeyPairGenerator();
        keyPairGenerator.init(new Ed25519KeyGenerationParameters(new SecureRandom()));

        return keyPairGenerator.generateKeyPair();
    }
于 2021-09-20T13:01:23.913 回答