英文:
Storing a Diffie-Hellman key pair for reuse in a KeyStore in Java
问题
我目前正在编写一个密码学的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密钥对。然而,对于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());
在我初始化密钥对之后,使用了如上的代码。
现在,为了解决这个问题,有没有办法:
- 以不同的方式对X509证书进行签名,以便存储Diffie-Hellman密钥对?
- 使用不同的方法将Diffie-Hellman密钥对存储在KeyStore中?
- 在KeyStore以外的其他安全位置存储Diffie-Hellman密钥对?
- 或者使用另一种密钥交换协议,并满足将中间值存储在KeyStore中的要求?
英文:
I'm currently writing a cryptographic Java program for which I implement a key exchange, so that two users with a running instance of the program (which don't have to run simultaneously) can agree on a shared secret key for AES encryption. I planned to use the Diffie Hellman key exchange protocol for this.
Therefore I generally followed this example by Oracle, with the addition of implementing Alice's and Bob's parts in different methods of the program. In this example, what Alice and Bob exchange is their encoded public keys
byte[] alicePubKeyEnc = aliceKpair.getPublic().getEncoded();
byte[] bobPubKeyEnc = bobKpair.getPublic().getEncoded();
respectively. In order to transmit these encoded public keys, I saved these byte arrays as files for each user to transmit it to the other user.
Now I want to handle the case that the user initiating the key exchange, say Alice, closes the program while waiting for the response of the other user, sending back their encoded public key as a file. On restarting the program, Alice would like to compute the shared secret key based on the public key received from Bob, and her own private key, which has to be stored somewhere while she had closed the program. Because my program already uses a PKCS12-KeyStore, I thought I could save the Diffie-Hellman key pair to that KeyStore.
Therefore, I followed the answer to this question with the approach of using a self-signed X509 certificate to store a RSA key pair. However, this obviously throws the error org.bouncycastle.operator.OperatorCreationException: cannot create signer: Supplied key (com.sun.crypto.provider.DHPrivateKey) is not a RSAPrivateKey instance
for the RSA signature algorithm:
String signatureAlgorithm = "SHA256WithRSA";
ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm)
.setProvider(bcProvider).build(keyPair.getPrivate());
After I initialized the key pair with
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("DH");
keyPairGen.initialize(bitLength);
KeyPair keyPair = keyPairGen.generateKeyPair();
Now to solve this, is there a way to either:
- Sign a X509 certificate differently, so that an Diffie-Hellman key pair can be stored?
- Store a Diffie-Hellman key pair in a KeyStore using a different approach?
- Store a Diffie-Hellman key pair securely elsewhere than in a KeyStore?
- Or use another way of key exchange protocol together with the requirement of storing the intermediate values in a KeyStore?
答案1
得分: 1
我在项目中使用了这个完整的示例解决方案,所以对其他人可能会有所帮助。它使用了 EC-Keypair(曲线“secp256r1”)和 ECDH 进行密钥交换。你需要 BouncyCastle,并且请注意,没有适当的异常处理,并且现有的密钥库将在没有通知的情况下被覆盖。
这是一段示例代码,用于将 ECDH 密钥对存储在 PKCS12 密钥库中:
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 {
// ...(略去其余的代码)
}
请注意,这里只是代码的一部分,我已经省略了大部分代码以保持简洁。如果你需要完整的代码或者有其他问题,请随时提问。
英文:
I used this full example solution in my project so it might be usefull for others. It's using a EC-Keypair (curve "secp256r1") and ECDH for KeyExchange. You need BouncyCastle and beware that there is no proper exception handling and that existing keystores will be overwritten without notice.
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();
}
}
This is the small output:
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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论