加密算法的秘钥持久化是是一个不可避免的话题,如何安全的存储更是重中之重。将秘钥保存在文件中是最简单的一种方法,下面来讨论如何使用Java代码将秘钥保存到文件中。
保存到文件中的秘钥最好用PBE进行口令加密。
下面介绍几种简单的保存秘钥的方式,以RSA秘钥为例。
PEM(Privacy Enhanced Mail)格式是一种安全的文件格式,主要用于存储证书、密钥和其他数据。它的格式遵循Base64编码规则,以“-----BEGIN CERTIFICATE-----“和”-----END CERTIFICATE-----"两句作为文件头尾标识。
-----BEGIN RSA PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCSpS0Ulc4uZFcq
0F5/1BFajwfzOLl/ToDFa46HdOotyK3pMSG0xjqVea2fc1Ufh1E+pDZBx6Fe/7rX
Ceqjx/77dxcgRJzI6yUb/EVuVSKSFL8wXFe/30HvmCIbyybJN393BrO3JJ6VvGIC
RUurvUlHR9Z6Rxil18XZ7oi4WlaJ15JPE/e1o2J1S0q5slUBA1icyrIPztV0NULQ
qExB5KmXwGDhm9sXUpBbAWc5oO5Ua+z8a8iiJ3qhK/iPyuUtnAJLkPma1Q++qOPv
dIrftw8rWpbbklu9X/cjoFbLPlx0MJhygNRoyV5BkCSj++77P5zh3H6x95gBNCaR
Hx10y+KpAgMBAAECggEBAIiZMhflv2w5bXTlKdGawht/9mDaavgqMQs0uEx3XJgP
QehhKsuQhwurj05jCVzYGJboMd4uTaQHRIyAoT+00VB+S110bDM/zo51/FrdFNnM
V5d9vQNuvKWpKcSIBPYUKzi9VnR3uP0zwHvq/qTYKkUqpp+6UDxTdOofJt3K0KeO
ZL+gFe7vSR0oioP+FJ4zrLVxsmRoqFz9Q+xtg4Md2/T5JsB0gNAD5ljFia8u1bNL
rR3IPcy/a4/5k2SqsLEIxiGKiijpvJw3Gj6/gDQXjpwTcx2GZIqYacZU9YjKoWi3
gAPFiEHNKH0/ZREsb+TFAKAaVS5SYhsir+EaYBmWZo0CgYEA7u4gYCXbYJ0+qIcL
L9xCOOeKxBkohgZ7gVOrE6AgsXdb9XmVtD9Ab1NVYVBSH7FzhRTGXd8fPsjRUwxD
PLY4O8ZjECp/aKl0oXtfxIWZTBlWyE1ztEidK+zAbPsd/cEZKWwmUKShPBMhQTFc
EBy9P9ax+tKlB1An138gdMur3Y8CgYEAnR84Id33ze06WSL6td0qhOvtTClRwxc7
c9N6YWCdJ2viDbEeo/4MW3WRKoNodZD6ivb3uXPma5yM1j26mnC3SLBlBzXvvG+w
M1DJHbZnRVwFdmVAhlCjbMlSIty+dIzm3JMNH6yzajYVI1iElVCCe8CJ9TC3PrLD
mzQ4kN3vkEcCgYAcbtwNdRPZOH03YvKy3FxJF0zNL01CPc0+w60tJbYeSFz6JmDp
VUmeteUIMp3DRcPAh4tfqmi2eQFoB6KMvAaPHnm1d8k3tEq72Mm5wkhO5QkNxyHI
x4dkrcTscynLg8oxwrwAhsblNkWT04LoK/NdgHgnqB+ZcjF7lJZRL9ehAwKBgQCV
2biHVFOKqr4DVUEXQh/TNc/T23SHRBkHlqIZTTOU7Wdi95HphdnzQGV6zmlJ90N8
VZ5d2RPhfiU0DxtxQB8NzG4OOnqylTLPPjURiW1k0SbEjDzF3RKAJmGXrkBWmCt2
IylZFDTpDzuJwlpwraBEtpTcq+GYgpXb6CoqwABC8QKBgQCunTRpMSm/NvmKAXSW
YWiR8TyJ0YxSiF58FZoEWHuz3/u96NHaQQgDd5m0WxlubUi2aJPdHQRZ3Pw/oZFp
BrJxN3WJdf36caYylN9LvoizGZezeK1gheJBhkfXhfvo9SQfvzQjyM5bHEnrOoFD
bW31uKY0VMqjhMEqqbJf808lsA==
-----END RSA PRIVATE KEY-----
PEM格式的证书包含数字证书(如SSL证书)、密钥、CSR(Certificate Signing Request)和其他文件。
PEM文件通常以.pem, .crt, .cer, .key等扩展名命名。
package com.qupeng.crypto.util;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.bouncycastle.util.io.pem.PemWriter;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import java.io.*;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.*;
public class SecretKeyPersistence {
public static void writePublicKeyToPem(PublicKey publicKey, Path keyFilePath) throws IOException {
try (FileWriter fileWriter = new FileWriter(keyFilePath.toFile()); PemWriter pemWriter = new PemWriter(fileWriter)) {
pemWriter.writeObject(new PemObject("RSA PUBLIC KEY", publicKey.getEncoded()));
}
}
public static PublicKey readPublicKeyFromPem(Path keyFilePath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
try (FileReader fileReader = new FileReader(keyFilePath.toFile()); PemReader pemReader = new PemReader(fileReader)) {
PemObject pemObject = pemReader.readPemObject();
X509EncodedKeySpec encPubKeySpec = new X509EncodedKeySpec(pemObject.getContent());
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(encPubKeySpec);
return publicKey;
}
}
public static void writePrivateKeyToPem(PrivateKey privateKey, Path keyFilePath) throws IOException {
try (FileWriter fileWriter = new FileWriter(keyFilePath.toFile()); PemWriter pemWriter = new PemWriter(fileWriter)) {
pemWriter.writeObject(new PemObject("RSA PRIVATE KEY", privateKey.getEncoded()));
}
}
public static PrivateKey readPrivateKeyFromPem(Path keyFilePath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
try (FileReader fileReader = new FileReader(keyFilePath.toFile()); PemReader pemReader = new PemReader(fileReader)) {
PemObject pemObject = pemReader.readPemObject();
PKCS8EncodedKeySpec encPriKeySpec = new PKCS8EncodedKeySpec(pemObject.getContent());
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(encPriKeySpec);
return privateKey;
}
}
}
引入bouncycastle依赖
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpg-jdk18on</artifactId>
<version>1.72</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.72</version>
</dependency>
将秘钥保存到普通的文本文件中。
package com.qupeng.crypto.util;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.bouncycastle.util.io.pem.PemWriter;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import java.io.*;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.*;
public class SecretKeyPersistence {
public static void writePublicKeyToTxt(PublicKey publicKey, Path keyFilePath) throws IOException {
Files.write(keyFilePath, new BASE64Encoder().encode(publicKey.getEncoded()).getBytes(), StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
}
public static PublicKey readPublicKeyFromTxt(Path keyFilePath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
X509EncodedKeySpec encPubKeySpec = new X509EncodedKeySpec(new BASE64Decoder().decodeBuffer(Strings.fromByteArray(Files.readAllBytes(keyFilePath))));
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(encPubKeySpec);
return publicKey;
}
public static void writePrivateKeyToTxt(PrivateKey privateKey, Path keyFilePath) throws IOException {
Files.write(keyFilePath, new BASE64Encoder().encode(privateKey.getEncoded()).getBytes(), StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
}
public static PrivateKey readPrivateKeyFromTxt(Path keyFilePath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
PKCS8EncodedKeySpec encPriKeySpec = new PKCS8EncodedKeySpec(new BASE64Decoder().decodeBuffer(Strings.fromByteArray(Files.readAllBytes(keyFilePath))));
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(encPriKeySpec);
return privateKey;
}
}
这里利用Java系列保存类 RSAPublicKeySpec。RSAPublicKeySpec用于描述秘钥规格。
package com.qupeng.crypto.util;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.bouncycastle.util.io.pem.PemWriter;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import java.io.*;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.*;
public class SecretKeyPersistence {
public static void writePublicKeyToObj(PublicKey publicKey, Path keyFilePath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(keyFilePath.toFile())))) {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPublicKeySpec rsaPublicKeySpec = keyFactory.getKeySpec(publicKey, RSAPublicKeySpec.class);
objectOutputStream.writeObject(rsaPublicKeySpec.getModulus());
objectOutputStream.writeObject(rsaPublicKeySpec.getPublicExponent());
}
}
public static PublicKey readPublicKeyFromObj(Path keyFilePath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, ClassNotFoundException {
try (ObjectInputStream objectInputStream = new ObjectInputStream(new BufferedInputStream(new FileInputStream(keyFilePath.toFile())))) {
BigInteger modulus = (BigInteger)objectInputStream.readObject();
BigInteger exponent = (BigInteger)objectInputStream.readObject();
RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(modulus, exponent);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(rsaPublicKeySpec);
}
}
public static void writePrivateKeyToObj(PublicKey privateKey, Path keyFilePath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(keyFilePath.toFile())))) {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPublicKeySpec rsaPublicKeySpec = keyFactory.getKeySpec(privateKey, RSAPublicKeySpec.class);
objectOutputStream.writeObject(rsaPublicKeySpec.getModulus());
objectOutputStream.writeObject(rsaPublicKeySpec.getPublicExponent());
}
}
public static PrivateKey readPrivateKeyFromObj(Path keyFilePath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, ClassNotFoundException {
try (ObjectInputStream objectInputStream = new ObjectInputStream(new BufferedInputStream(new FileInputStream(keyFilePath.toFile())))) {
BigInteger modulus = (BigInteger)objectInputStream.readObject();
BigInteger exponent = (BigInteger)objectInputStream.readObject();
RSAPrivateKeySpec rsaPrivateKeySpec = new RSAPrivateKeySpec(modulus, exponent);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(rsaPrivateKeySpec);
}
}
}