如何在 Java 中存储和重用密钥对?
How to store and reuse keypair in Java?
我想生成一个密钥对并重新使用它。
public static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048, new SecureRandom());
KeyPair pair = generator.generateKeyPair();
return pair;
}
我该怎么做?
这里有点问题:Java的重点几乎完全放在 TLS 和实现 TLS 所需的密码学上。对于 TLS,需要私钥和证书。所以你会遇到这样的情况:
- 必须生成一个(伪造的)自签名证书以与您的 public 密钥一起使用或;
- 必须找到另一种方法来存储没有证书的私钥。
但是,对于 (2.),您很快就会得到一种不太兼容的方法。如果您想这样做,您可以创建一个使用 PBE / CBC 加密的 PKCS#8 加密私钥。
所以这里有一些代码可以创建自签名证书并使用它来存储密钥。请注意到期日期,为了安全起见,您可以将其设置为 100 年(或者您实际上可以进行一些密钥管理)。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStore.Entry;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.UnrecoverableEntryException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Calendar;
import java.util.Date;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
public class StoreKeyPair {
public static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048, new SecureRandom());
KeyPair pair = generator.generateKeyPair();
return pair;
}
public static Certificate selfSign(KeyPair keyPair, String subjectDN)
throws OperatorCreationException, CertificateException, IOException
{
Provider bcProvider = new BouncyCastleProvider();
Security.addProvider(bcProvider);
long now = System.currentTimeMillis();
Date startDate = new Date(now);
X500Name dnName = new X500Name(subjectDN);
// Using the current timestamp as the certificate serial number
BigInteger certSerialNumber = new BigInteger(Long.toString(now));
Calendar calendar = Calendar.getInstance();
calendar.setTime(startDate);
// 1 Yr validity
calendar.add(Calendar.YEAR, 1);
Date endDate = calendar.getTime();
// Use appropriate signature algorithm based on your keyPair algorithm.
String signatureAlgorithm = "SHA256WithRSA";
SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair
.getPublic().getEncoded());
X509v3CertificateBuilder certificateBuilder = new X509v3CertificateBuilder(dnName,
certSerialNumber, startDate, endDate, dnName, subjectPublicKeyInfo);
ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm).setProvider(
bcProvider).build(keyPair.getPrivate());
X509CertificateHolder certificateHolder = certificateBuilder.build(contentSigner);
Certificate selfSignedCert = new JcaX509CertificateConverter()
.getCertificate(certificateHolder);
return selfSignedCert;
}
public static void main(String[] args) throws Exception {
KeyPair generatedKeyPair = generateKeyPair();
String filename = "test_gen_self_signed.pkcs12";
char[] password = "test".toCharArray();
storeToPKCS12(filename, password, generatedKeyPair);
KeyPair retrievedKeyPair = loadFromPKCS12(filename, password);
// you can validate by generating a signature and verifying it or by
// comparing the moduli by first casting to RSAPublicKey, e.g.:
RSAPublicKey pubKey = (RSAPublicKey) generatedKeyPair.getPublic();
RSAPrivateKey privKey = (RSAPrivateKey) retrievedKeyPair.getPrivate();
System.out.println(pubKey.getModulus().equals(privKey.getModulus()));
}
private static KeyPair loadFromPKCS12(String filename, char[] password)
throws KeyStoreException, NoSuchAlgorithmException, CertificateException,
FileNotFoundException, IOException, UnrecoverableEntryException {
KeyStore pkcs12KeyStore = KeyStore.getInstance("PKCS12");
try (FileInputStream fis = new FileInputStream(filename);) {
pkcs12KeyStore.load(fis, password);
}
KeyStore.ProtectionParameter param = new KeyStore.PasswordProtection(password);
Entry entry = pkcs12KeyStore.getEntry("owlstead", param);
if (!(entry instanceof PrivateKeyEntry)) {
throw new KeyStoreException("That's not a private key!");
}
PrivateKeyEntry privKeyEntry = (PrivateKeyEntry) entry;
PublicKey publicKey = privKeyEntry.getCertificate().getPublicKey();
PrivateKey privateKey = privKeyEntry.getPrivateKey();
return new KeyPair(publicKey, privateKey);
}
private static void storeToPKCS12(
String filename, char[] password,
KeyPair generatedKeyPair) throws KeyStoreException, IOException,
NoSuchAlgorithmException, CertificateException, FileNotFoundException,
OperatorCreationException {
Certificate selfSignedCertificate = selfSign(generatedKeyPair, "CN=owlstead");
KeyStore pkcs12KeyStore = KeyStore.getInstance("PKCS12");
pkcs12KeyStore.load(null, null);
KeyStore.Entry entry = new PrivateKeyEntry(generatedKeyPair.getPrivate(),
new Certificate[] { selfSignedCertificate });
KeyStore.ProtectionParameter param = new KeyStore.PasswordProtection(password);
pkcs12KeyStore.setEntry("owlstead", entry, param);
try (FileOutputStream fos = new FileOutputStream(filename)) {
pkcs12KeyStore.store(fos, password);
}
}
}
请注意,我懒得正确处理异常。
此代码使用 this answer 的略微改动版本,请参阅我的评论以了解我更改代码的原因。
public 密钥当然也可以单独存储。只需调用 getEncoded
并存储生成的 SubjectPublicKeyInfo 结构。
我想生成一个密钥对并重新使用它。
public static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048, new SecureRandom());
KeyPair pair = generator.generateKeyPair();
return pair;
}
我该怎么做?
这里有点问题:Java的重点几乎完全放在 TLS 和实现 TLS 所需的密码学上。对于 TLS,需要私钥和证书。所以你会遇到这样的情况:
- 必须生成一个(伪造的)自签名证书以与您的 public 密钥一起使用或;
- 必须找到另一种方法来存储没有证书的私钥。
但是,对于 (2.),您很快就会得到一种不太兼容的方法。如果您想这样做,您可以创建一个使用 PBE / CBC 加密的 PKCS#8 加密私钥。
所以这里有一些代码可以创建自签名证书并使用它来存储密钥。请注意到期日期,为了安全起见,您可以将其设置为 100 年(或者您实际上可以进行一些密钥管理)。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStore.Entry;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.UnrecoverableEntryException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Calendar;
import java.util.Date;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
public class StoreKeyPair {
public static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048, new SecureRandom());
KeyPair pair = generator.generateKeyPair();
return pair;
}
public static Certificate selfSign(KeyPair keyPair, String subjectDN)
throws OperatorCreationException, CertificateException, IOException
{
Provider bcProvider = new BouncyCastleProvider();
Security.addProvider(bcProvider);
long now = System.currentTimeMillis();
Date startDate = new Date(now);
X500Name dnName = new X500Name(subjectDN);
// Using the current timestamp as the certificate serial number
BigInteger certSerialNumber = new BigInteger(Long.toString(now));
Calendar calendar = Calendar.getInstance();
calendar.setTime(startDate);
// 1 Yr validity
calendar.add(Calendar.YEAR, 1);
Date endDate = calendar.getTime();
// Use appropriate signature algorithm based on your keyPair algorithm.
String signatureAlgorithm = "SHA256WithRSA";
SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair
.getPublic().getEncoded());
X509v3CertificateBuilder certificateBuilder = new X509v3CertificateBuilder(dnName,
certSerialNumber, startDate, endDate, dnName, subjectPublicKeyInfo);
ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm).setProvider(
bcProvider).build(keyPair.getPrivate());
X509CertificateHolder certificateHolder = certificateBuilder.build(contentSigner);
Certificate selfSignedCert = new JcaX509CertificateConverter()
.getCertificate(certificateHolder);
return selfSignedCert;
}
public static void main(String[] args) throws Exception {
KeyPair generatedKeyPair = generateKeyPair();
String filename = "test_gen_self_signed.pkcs12";
char[] password = "test".toCharArray();
storeToPKCS12(filename, password, generatedKeyPair);
KeyPair retrievedKeyPair = loadFromPKCS12(filename, password);
// you can validate by generating a signature and verifying it or by
// comparing the moduli by first casting to RSAPublicKey, e.g.:
RSAPublicKey pubKey = (RSAPublicKey) generatedKeyPair.getPublic();
RSAPrivateKey privKey = (RSAPrivateKey) retrievedKeyPair.getPrivate();
System.out.println(pubKey.getModulus().equals(privKey.getModulus()));
}
private static KeyPair loadFromPKCS12(String filename, char[] password)
throws KeyStoreException, NoSuchAlgorithmException, CertificateException,
FileNotFoundException, IOException, UnrecoverableEntryException {
KeyStore pkcs12KeyStore = KeyStore.getInstance("PKCS12");
try (FileInputStream fis = new FileInputStream(filename);) {
pkcs12KeyStore.load(fis, password);
}
KeyStore.ProtectionParameter param = new KeyStore.PasswordProtection(password);
Entry entry = pkcs12KeyStore.getEntry("owlstead", param);
if (!(entry instanceof PrivateKeyEntry)) {
throw new KeyStoreException("That's not a private key!");
}
PrivateKeyEntry privKeyEntry = (PrivateKeyEntry) entry;
PublicKey publicKey = privKeyEntry.getCertificate().getPublicKey();
PrivateKey privateKey = privKeyEntry.getPrivateKey();
return new KeyPair(publicKey, privateKey);
}
private static void storeToPKCS12(
String filename, char[] password,
KeyPair generatedKeyPair) throws KeyStoreException, IOException,
NoSuchAlgorithmException, CertificateException, FileNotFoundException,
OperatorCreationException {
Certificate selfSignedCertificate = selfSign(generatedKeyPair, "CN=owlstead");
KeyStore pkcs12KeyStore = KeyStore.getInstance("PKCS12");
pkcs12KeyStore.load(null, null);
KeyStore.Entry entry = new PrivateKeyEntry(generatedKeyPair.getPrivate(),
new Certificate[] { selfSignedCertificate });
KeyStore.ProtectionParameter param = new KeyStore.PasswordProtection(password);
pkcs12KeyStore.setEntry("owlstead", entry, param);
try (FileOutputStream fos = new FileOutputStream(filename)) {
pkcs12KeyStore.store(fos, password);
}
}
}
请注意,我懒得正确处理异常。
此代码使用 this answer 的略微改动版本,请参阅我的评论以了解我更改代码的原因。
public 密钥当然也可以单独存储。只需调用 getEncoded
并存储生成的 SubjectPublicKeyInfo 结构。