Java加解密(十)持久化秘钥

太叔英锐
2023-12-01

Java持久化秘钥

加密算法的秘钥持久化是是一个不可避免的话题,如何安全的存储更是重中之重。将秘钥保存在文件中是最简单的一种方法,下面来讨论如何使用Java代码将秘钥保存到文件中。

保存到文件中的秘钥最好用PBE进行口令加密。

下面介绍几种简单的保存秘钥的方式,以RSA秘钥为例。

1 保存PEM文件

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>

2 保存文本文件

将秘钥保存到普通的文本文件中。

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;
    }
}

3 Java序列化

这里利用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);
        }
    }
}
 类似资料: