java - 存储 Diffie-Hellman 密钥对以在 Java 中的 KeyStore 中重用
问题描述
我目前正在编写一个加密 Java 程序,我为其实现了密钥交换,以便具有程序运行实例(不必同时运行)的两个用户可以就 AES 加密的共享密钥达成一致。我计划为此使用 Diffie Hellman 密钥交换协议。
因此,我通常按照Oracle 的这个例子,在程序的不同方法中添加了实现 Alice 和 Bob 的部分。在这个例子中,Alice 和 Bob 交换的是他们的编码公钥
byte[] alicePubKeyEnc = aliceKpair.getPublic().getEncoded();
byte[] bobPubKeyEnc = bobKpair.getPublic().getEncoded();
分别。为了传输这些编码的公钥,我将这些字节数组保存为文件,供每个用户将其传输给其他用户。
现在我想处理这样一种情况,即发起密钥交换的用户,比如 Alice,在等待其他用户的响应时关闭程序,将他们编码的公钥作为文件发回。在重新启动程序时,Alice 希望根据从 Bob 收到的公钥和她自己的私钥计算共享密钥,该私钥在她关闭程序时必须存储在某个地方。因为我的程序已经使用了 PKCS12-KeyStore,我想我可以将 Diffie-Hellman 密钥对保存到该 KeyStore。
因此,我通过使用自签名 X509 证书来存储 RSA 密钥对的方法来遵循这个问题的答案。但是,这显然会引发org.bouncycastle.operator.OperatorCreationException: cannot create signer: Supplied key (com.sun.crypto.provider.DHPrivateKey) is not a RSAPrivateKey instance
RSA 签名算法的错误:
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();
现在要解决这个问题,有没有办法:
- 以不同方式签署 X509 证书,以便可以存储 Diffie-Hellman 密钥对?
- 使用不同的方法将 Diffie-Hellman 密钥对存储在 KeyStore 中?
- 将 Diffie-Hellman 密钥对安全地存储在 KeyStore 之外的其他地方?
- 或者使用另一种密钥交换协议方式以及将中间值存储在 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: https://stackoverflow.com/a/50801856/8166854, 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
推荐阅读
- javascript - 删除具有未定义值的对象数组的 JSON 键
- python - 更改 pytorch 权重衰减值可防止验证损失和训练损失减少
- javascript - 如何从响应对象中获取特定值
- ios - 快速检查一个数组是否包含另一个数组的元素
- android - 在 Flutter 中使用 precacheImage 函数时如何避免内存不足?
- cassandra - Cassandra 中具有 WHERE 条件性能的 COUNT(*) vs. COUNT(1) vs COUNT(column)
- vue.js - 无法将对象添加到数组
- ssl - 无法加载 TLS 文件数据库
- swift - 在 Image SwiftUI 上点击动画
- rust - StreamExt .scan() 方法上的“预期绑定生命周期参数,找到具体生命周期”