存储 Diffie-Hellman 密钥对以便在 Java 的 KeyStore 中重复使用

Storing a Diffie-Hellman key pair for reuse in a KeyStore in Java

我目前正在编写一个加密 Java 程序,我为其实现了密钥交换,这样两个用户就可以使用该程序的 运行ning 实例(不必 运行 同时)可以就 AES 加密的共享密钥达成一致。我计划为此使用 Diffie Hellman 密钥交换协议。

因此,我通常遵循 this example by Oracle,并在程序的不同方法中添加了实现 Alice 和 Bob 的部分。在这个例子中,Alice 和 Bob 交换的是他们编码的 public keys

byte[] alicePubKeyEnc = aliceKpair.getPublic().getEncoded();
byte[] bobPubKeyEnc = bobKpair.getPublic().getEncoded();

分别。为了传输这些编码的 public 密钥,我将这些字节数组保存为文件,供每个用户将其传输给其他用户。

现在我想处理以下情况:启动密钥交换的用户,比如 Alice,在等待其他用户的响应时关闭了程序,将他们编码的 public 密钥作为文件发回。在重新启动程序时,Alice 想根据从 Bob 收到的 public 密钥和她自己的私钥计算共享密钥,在她关闭程序时,私钥必须存储在某个地方。因为我的程序已经使用了 PKCS12-KeyStore,所以我想我可以将 Diffie-Hellman 密钥对保存到那个 KeyStore。

因此,我遵循的方法,使用自签名X509证书来存储RSA密钥对。但是,对于 RSA 签名算法,这显然会引发错误 org.bouncycastle.operator.OperatorCreationException: cannot create signer: Supplied key (com.sun.crypto.provider.DHPrivateKey) is not a RSAPrivateKey instance

String signatureAlgorithm = "SHA256WithRSA";

ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm)
        .setProvider(bcProvider).build(keyPair.getPrivate());

在我用

初始化密钥对之后
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("DH");
keyPairGen.initialize(bitLength);
KeyPair keyPair = keyPairGen.generateKeyPair();

现在要解决这个问题,有没有办法:

  1. 以不同方式签署 X509 证书,以便可以存储 Diffie-Hellman 密钥对?
  2. 使用不同的方法在 KeyStore 中存储 Diffie-Hellman 密钥对?
  3. 将 Diffie-Hellman 密钥对安全地存储在 KeyStore 之外的其他地方?
  4. 或者使用另一种密钥交换协议方式,同时要求将中间值存储在 KeyStore 中?

我在我的项目中使用了这个完整的示例解决方案,因此它可能对其他人有用。它使用 EC 密钥对(曲线 "secp256r1")和 ECDH 进行密钥交换。您需要 BouncyCastle 并注意没有适当的异常处理,并且 现有密钥库将被覆盖,恕不另行通知.

import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

import javax.crypto.KeyAgreement;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;

public class StoreEcdhKeyInPKCS12KeystoreSO {
    public static void main(String[] args) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException, OperatorCreationException, CertificateException, KeyStoreException, IOException, UnrecoverableKeyException, InvalidKeySpecException, InvalidKeyException {
        System.out.println("Storing a ECDH Keypair in a PKCS12 Keystore");
        // you need BouncyCastle, get it from https://www.bouncycastle.org/latest_releases.html
        Security.addProvider(new BouncyCastleProvider());
        System.out.println("\nJava version: " + Runtime.version() + " BouncyCastle Version: " + Security.getProvider("BC"));
        // ### WARNING: no exception handling, existing keystores will be overwritten without notice ###
        String ecCurvename = "secp256r1";

        // alice's credentials
        String aliceKeystoreFilename = "alicekeystore.p12";
        char[] aliceKeystoreEntryPassword = "aliceEntryPassword".toCharArray();
        String aliceKeypairAlias = "aliceKeypairAlias";
        char[] aliceKeypairPassword = "aliceKeypairPassword".toCharArray();
        KeyPair aliceKeyPairGenerated;
        PrivateKey alicePrivateKeyLoaded;
        byte[] aliceReceivedPublicKeyFromBob;
        byte[] aliceSharedSecret;

        // bob's credentials
        String bobKeystoreFilename = "bobkeystore.p12";
        char[] bobKeystoreEntryPassword = "bobEntryPassword".toCharArray();
        String bobKeypairAlias = "bobKeypairAlias";
        char[] bobKeypairPassword = "bobKeypairPassword".toCharArray();
        KeyPair bobKeyPairGenerated;
        PrivateKey bobPrivateKeyLoaded;
        byte[] bobReceivedPublicKeyFromAlice;
        byte[] bobSharedSecret;

        // alice start keypair generation, comment out if you still have a keystore with the keys
        aliceKeyPairGenerated = generateEcdhKeyPair(ecCurvename);
        // save keypair
        storeEcdhKeypairInPKCS12Keystore(aliceKeystoreFilename, aliceKeystoreEntryPassword, aliceKeypairAlias, aliceKeypairPassword, aliceKeyPairGenerated);
        System.out.println("alice has a new keystore: " + aliceKeystoreFilename);

        // bob start keypair generation, comment out if you still have a keystore with the keys
        bobKeyPairGenerated = generateEcdhKeyPair(ecCurvename);
        storeEcdhKeypairInPKCS12Keystore(bobKeystoreFilename, bobKeystoreEntryPassword, bobKeypairAlias, bobKeypairPassword, bobKeyPairGenerated);
        System.out.println("bob has a new keystore: " + bobKeystoreFilename);

        // alice sends her public key to bob -e.g. you could code it with base64, here we're just cloning the key
        bobReceivedPublicKeyFromAlice = aliceKeyPairGenerated.getPublic().getEncoded().clone();

        // later on - bob received the the public key from alice and loads his key from keystore
        bobPrivateKeyLoaded = loadEcdhPrivateKeyFromPKCS12Keystore(bobKeystoreFilename, bobKeystoreEntryPassword, bobKeypairAlias, bobKeypairPassword);
        // bob creates the shared secret with public key from alice
        bobSharedSecret = createEcdhSharedSecret(bobPrivateKeyLoaded, bobReceivedPublicKeyFromAlice);
        System.out.println("SharedSecret Bob:   " + bytesToHex(bobSharedSecret));

        // bob sends his public key to alice -e.g. you could code it with base64, here we're just cloning the key
        aliceReceivedPublicKeyFromBob = loadEcdhPublicKeyFromPKCS12Keystore(bobKeystoreFilename, bobKeystoreEntryPassword, bobKeypairAlias, bobKeypairPassword).getEncoded().clone();

        // alice loads her private key from keystore and generates the SecretShare
        alicePrivateKeyLoaded = loadEcdhPrivateKeyFromPKCS12Keystore(aliceKeystoreFilename, aliceKeystoreEntryPassword, aliceKeypairAlias, aliceKeypairPassword);
        aliceSharedSecret = createEcdhSharedSecret(alicePrivateKeyLoaded, aliceReceivedPublicKeyFromBob);
        System.out.println("SharedSecret Alice: " + bytesToHex(aliceSharedSecret));

        // check that both SecretShare's are equal
        System.out.println("Compare aliceSharedSecret and bobSharedSecret: " + Arrays.equals(aliceSharedSecret, bobSharedSecret));
        // do what ever you want with your SharedSecret, e.g. shorten it using SHA256 for a AES encryption key
    }

    public static KeyPair generateEcdhKeyPair(String curvenameString) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "SunEC");
        ECGenParameterSpec ecParameterSpec = new ECGenParameterSpec(curvenameString);
        keyPairGenerator.initialize(ecParameterSpec);
        return keyPairGenerator.genKeyPair();
    }

    public static void storeEcdhKeypairInPKCS12Keystore(String filename, char[] entryPassword, String keypairAlias, char[] keypairPassword, KeyPair keypair) throws CertificateException, OperatorCreationException, KeyStoreException, IOException, NoSuchAlgorithmException {
        // --- create the self signed cert
        java.security.cert.Certificate cert = createSelfSigned(keypair);
        // --- create a new pkcs12 key store in memory
        KeyStore pkcs12 = KeyStore.getInstance("PKCS12");
        pkcs12.load(null, null);
        // --- create entry in PKCS12
        pkcs12.setKeyEntry(keypairAlias, keypair.getPrivate(), keypairPassword, new Certificate[]{cert});
        // --- store PKCS#12 as file
        try (FileOutputStream p12 = new FileOutputStream(filename)) {
            pkcs12.store(p12, entryPassword);
        } catch (NoSuchAlgorithmException | IOException e) {
            e.printStackTrace();
        }
    }

    public static PrivateKey loadEcdhPrivateKeyFromPKCS12Keystore(String filename, char[] entryPassword, String keypairAlias, char[] keypairPassword) throws CertificateException, OperatorCreationException, KeyStoreException, IOException, NoSuchAlgorithmException, UnrecoverableKeyException {
        // --- read PKCS#12 as file
        KeyStore pkcs12 = KeyStore.getInstance("PKCS12");
        try (FileInputStream p12 = new FileInputStream(filename)) {
            pkcs12.load(p12, entryPassword);
        }
        // --- retrieve private key
        return (PrivateKey) pkcs12.getKey(keypairAlias, keypairPassword);
    }

    public static PublicKey loadEcdhPublicKeyFromPKCS12Keystore(String filename, char[] entryPassword, String keypairAlias, char[] keypairPassword) throws CertificateException, OperatorCreationException, KeyStoreException, IOException, NoSuchAlgorithmException, UnrecoverableKeyException {
        // --- read PKCS#12 as file
        KeyStore pkcs12 = KeyStore.getInstance("PKCS12");
        try (FileInputStream p12 = new FileInputStream(filename)) {
            pkcs12.load(p12, entryPassword);
        }
        // --- retrieve public key
        Certificate cert = pkcs12.getCertificate(keypairAlias);
        return cert.getPublicKey();
    }

    public static byte[] createEcdhSharedSecret(PrivateKey privateKey, byte[] publicKeyByte) throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException {
        KeyAgreement keyAgree = KeyAgreement.getInstance("ECDH");
        keyAgree.init(privateKey);
        X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyByte);
        KeyFactory keyFactory = KeyFactory.getInstance("EC");
        PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
        keyAgree.doPhase(publicKey, true);
        return keyAgree.generateSecret();
    }

    private static X509Certificate createSelfSigned(KeyPair pair) throws OperatorCreationException, CertificateException {
        // source:  author Maarten Bodewes
        X500Name dnName = new X500Name("CN=publickeystorageonly");
        BigInteger certSerialNumber = BigInteger.ONE;
        Date startDate = new Date(); // now
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(startDate);
        calendar.add(Calendar.YEAR, 100); // 100 years validity
        Date endDate = calendar.getTime();
        ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithECDSA").build(pair.getPrivate());
        JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(dnName, certSerialNumber, startDate, endDate, dnName, pair.getPublic());
        return new JcaX509CertificateConverter().getCertificate(certBuilder.build(contentSigner));
    }

    private static String bytesToHex(byte[] bytes) {
        StringBuffer result = new StringBuffer();
        for (byte b : bytes) result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
        return result.toString();
    }
}

这是小输出:

Storing a ECDH Keypair in a PKCS12 Keystore

Java version: 11.0.6+8-b520.43 BouncyCastle Version: BC version 1.65
alice has a new keystore: alicekeystore.p12
bob has a new keystore: bobkeystore.p12
SharedSecret Bob:   ab457b66687fcaefca6d648d428a66b1642355be6c8fb5190624043a7de2215c
SharedSecret Alice: ab457b66687fcaefca6d648d428a66b1642355be6c8fb5190624043a7de2215c
Compare aliceSharedSecret and bobSharedSecret: true