与银行的交互中为了保证数据保密以及防止数据篡改,通常使用RSA非对称加密算法对数据进行加密和签名,现在也因为安全监管的要求,开始使用SM2算法。对接过程中发现对密钥的生成,存储和读取有很多不解的地方,该文用于记录遇到的相关问题。文中只会涉及到openssl以及Java security库的简单应用

密钥生成

私钥生成

openssl genrsa -out rsa_private.pem 2048
  • genrsa:是openssl生成RSA私钥的标准命令
  • -out:t 指定输出的文件
  • 2048 指rsa算法长度,可以替换为其他长度,例如1024。

PKCS#8标准转换

需要注意的是openssl默认生成的是PKCS#1标准的密钥,存储格式是PEM。Java标准库默认是不支持PKCS#1标准,因此需要转换为PKCS#8标准。

openssl pkcs8 -topk8 -inform PEM -in rsa_private.pem -outform PEM -nocrypt > rsa_private_pkcs8.pem
  • pkcs8:是openssl转换pcks8的标准命令
  • -topk8:通常,输入时需要 PKCS#8 私钥,并且将写入传统格式的私钥。使用 -topk8 选项,情况正好相反:它读取传统格式的私钥并写入 PKCS#8 格式的密钥。
  • -inform:输入key的格式,可以是PEM,也可以是DER
  • -in:输入文件
  • -outform:输出格式,可以是PEM,也可以是DER
  • -nocrypt:对密钥不加密

PEM和DER是密钥的两种编码方式,PEM格式是以-----BEGIN PRIVATE KEY----------BEGIN PUBLIC KEY-----开头,以-----END PRIVATE KEY-----或者-----END PUBLIC KEY-----结尾,中间是经过Base64编码后的内容,每64字节做一次换行。

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0E4UTGrrbvjmK/1Fvm4R
jic0hAn4N54mFCPeCOVZ5VTo5mVlU1mxyaIpFvjOH811JRsYezmiXE3lXF2qqkOY
GFB5CqFmqgxyEpXtKWMbYS1Ou0fzHEZJHHioqx7SBEYBuHsQu8Y+dMort09V7kJv
AS7CjJqlXZBPa/PWGRG6RwGU2XUt8DDa8mNA632G5Z1I612yVMP8K5xTMcisJSKN
zZpmw+pKd5Atzbx1AgWfUmc6n++jNYqMBSpMEqXePf+dAt/9y0cMeS/9UOmQ6YUu
Iyr5CZxU0TtGE2AlFemz7NBKiIa8ol78cgyl15HZx5Ip9PJmHDcH6c2XcqtCD5M4
awIDAQAB
-----END PUBLIC KEY-----
PEM格式的公钥

DER格式是直接密钥的二进制编码。

如果需要从PKCS#8转换为PKCS#1格式可以使用如下命令

openssl rsa -in rsa_private_pkcs8.pem -out rsa_private.pem

导出公钥

-- 导出pem格式
openssl rsa -in rsa_private.pem -pubout -outform PEM -out  rsa_public.pem
-- 导出DER格式
openssl rsa -in rsa_private.pem -pubout -outform DER -out  rsa_public.der

标准转换方法与私钥转换一致

自签名证书生成

# 使用自有私钥生成证书
openssl req -new -x509 -days 365 -key rsa_private.key -out cert.crt
  • -new:是生成证书请求
  • -x509: 生成证书格式
  • -key:指定私钥,这里pkcs#1或者PKCS#8都是可以的
  • -out:输出证书文件

Java读取密钥文件

读取PEM格式私钥

public static PrivateKey readPemPrivateKey(String filename) throws Exception {
        // 读取文件
        String key = new String(Files.readAllBytes(Paths.get(filename)), Charset.defaultCharset());
        String privateKeyPEM = key
            .replace("-----BEGIN PRIVATE KEY-----", "")
            .replaceAll("\n", "")
            .replace("-----END PRIVATE KEY-----", "");
        byte[] encoded = Base64.getDecoder().decode(privateKeyPEM);
        PKCS8EncodedKeySpec pkcs8 = new PKCS8EncodedKeySpec(encoded);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return kf.generatePrivate(pkcs8);
    }

读取PEM格式公钥

    public static PublicKey readPemPublicKey(String filename) throws Exception {
        String key = new String(Files.readAllBytes(Paths.get(filename)), Charset.defaultCharset());
    String publicKeyPEM = key
        .replace("-----BEGIN PUBLIC KEY-----", "")
        .replaceAll(System.lineSeparator(), "")
        .replace("-----END PUBLIC KEY-----", "");
byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);

KeyFactory keyFactory = KeyFactory.getInstance("RSA"); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded); return (RSAPublicKey) keyFactory.generatePublic(keySpec);

}

读取DER格式私钥

    public static PrivateKey readDerPrivateKey(String filename) throws Exception {
        // 读取文件
        byte[] keyBytes = Files.readAllBytes(Paths.get(filename));
        PKCS8EncodedKeySpec pkcs8 = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        PrivateKey privateKey = kf.generatePrivate(pkcs8);
        // 转换编码
        String data = Base64.getEncoder().encodeToString(privateKey.getEncoded());
        System.out.println(data);
        return privateKey;
    }

读取DER格式公钥

    public static PrivateKey readDerPrivateKey(String filename) throws Exception {
        // 读取文件
        byte[] keyBytes = Files.readAllBytes(Paths.get(filename));
        PKCS8EncodedKeySpec pkcs8 = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        PrivateKey privateKey = kf.generatePrivate(pkcs8);
        // 转换编码
        String data = Base64.getEncoder().encodeToString(privateKey.getEncoded());
        System.out.println(data);
        return privateKey;
    }

Java读取X509证书

    public static X509Certificate loadCert(String filename) throws Exception {
        FileInputStream in = new FileInputStream(filename);
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        X509Certificate validateCert = (X509Certificate) cf.generateCertificate(in);
        return validateCert;
    }

使用加载的密钥完成加密和解密

    public static void doEncryptAndDecrypt(PublicKey publicKey, PrivateKey privateKey, String msg) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        String out = Base64.getEncoder().encodeToString(cipher.doFinal(msg.getBytes(StandardCharsets.UTF_8)));
        // 解密
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        out = new String(cipher.doFinal(Base64.getDecoder().decode(out)));
        if (msg.equals(out)) {
            System.out.println("加密、解密OK");
        }
    }

参考资料