RSA密钥格式与私钥加密

RSA的密钥格式和私钥密码

RSA加密的密钥长度一般有1024、2048、4096,密钥长度越长越安全,一般选2048

密钥格式分为PKCS#1和PKCS#8两种,其中前者是RSA专用的密钥格式,后者是通用的密钥编码格式规则、带有密钥归属何种算法、密钥本身是否是加密等描述信息。也就是说使用PKCS#8格式时,是可以对RSA私钥进行加密的,一般搭配AES-256-CBC来对私钥进行加密,所以这时候还会有一个“私钥密码”、本质上是用来通过PBKDF2(含盐+多次迭代)衍生AES密钥的用户输入的密码。

生成加密私钥的Java实现

使用

<dependency>
     <groupId>org.bouncycastle</groupId>
     <artifactId>bcprov-jdk15on</artifactId>
     <version>1.70</version>
</dependency>
<dependency>
     <groupId>org.bouncycastle</groupId>
     <artifactId>bcpkix-jdk15on</artifactId>
     <version>1.70</version>
</dependency>       

工具类: 把RSAPrivateCrtKey转成RSAPrivateKey类型的字节数组,也就是转成pkcs1格式

import org.bouncycastle.asn1.pkcs.RSAPrivateKey;
import java.io.IOException;
import java.security.interfaces.RSAPrivateCrtKey;

public class BouncyCastleUtil {
    public static byte[] convertRsaPrivateKeyToPkcs1(RSAPrivateCrtKey privKey) throws IOException {
        RSAPrivateKey keyStruct = new RSAPrivateKey(
                privKey.getModulus(),
                privKey.getPublicExponent(),
                privKey.getPrivateExponent(),
                privKey.getPrimeP(),
                privKey.getPrimeQ(),
                privKey.getPrimeExponentP(),
                privKey.getPrimeExponentQ(),
                privKey.getCrtCoefficient());
        return keyStruct.getEncoded();
    }
}

Controller:

/getRsaKey接口参数中rsaFormat如果是PKCS#8且rsaPass不为空则生成PKCS#8格式加密私钥

import com.wangan.springbootone.util.BouncyCastleUtil;
import lombok.Data;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PKCS8Generator;
import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8EncryptorBuilder;
import org.bouncycastle.operator.OutputEncryptor;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemWriter;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import java.io.StringWriter;
import java.security.*;
import java.security.interfaces.RSAPrivateCrtKey;

@RestController
@RequestMapping("/api/rsa")
public class RsaController {

    @PostConstruct
    public void registerProvider() {
        Security.addProvider(new BouncyCastleProvider());
    }

    @PostMapping("/getRsaKey")
    public RsaKeyResponse generateRsaKey(@RequestBody RsaKeyRequest req) throws Exception {
        int keySize = req.getRsaLength();
        String format = req.getRsaFormat();
        String password = req.getRsaPass();

        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(keySize);
        KeyPair pair = keyGen.generateKeyPair(); //密钥默认是pkcs8的

        String publicKeyPem = convertPublicKeyToPem(pair.getPublic()); //生成公钥pem
        String privateKeyPem;

        if ("pkcs#1".equalsIgnoreCase(format)) {
            if (password != null && !password.isEmpty()) {
                throw new IllegalArgumentException("PKCS#1 格式不支持加密私钥,请选择 PKCS#8");
            }
            privateKeyPem = convertToPkcs1Pem((RSAPrivateCrtKey) pair.getPrivate());  //生成PKCS#1格式私钥
        } else if ("pkcs#8".equalsIgnoreCase(format)) {
            if (password != null && !password.isEmpty()) {
                privateKeyPem = convertToEncryptedPkcs8Pem(pair.getPrivate(), password);  //生成PKCS#8格式加密私钥
            } else {
                privateKeyPem = convertToPkcs8Pem(pair.getPrivate());   //生成PKCS#8格式私钥
            }
        } else {
            throw new IllegalArgumentException("不支持的密钥格式: " + format);
        }

        return new RsaKeyResponse(200, "OK", new RsaKeyData(publicKeyPem, privateKeyPem));
    }

    private String convertPublicKeyToPem(PublicKey publicKey) throws Exception {
        StringWriter writer = new StringWriter();
        try (PemWriter pemWriter = new PemWriter(writer)) {
            pemWriter.writeObject(new PemObject("PUBLIC KEY", publicKey.getEncoded()));
        }
        return writer.toString();
    }

    private String convertToPkcs8Pem(PrivateKey privateKey) throws Exception {
        StringWriter writer = new StringWriter();
        try (PemWriter pemWriter = new PemWriter(writer)) {
            pemWriter.writeObject(new PemObject("PRIVATE KEY", privateKey.getEncoded()));
        }
        return writer.toString();
    }

    private String convertToEncryptedPkcs8Pem(PrivateKey privateKey, String password) throws Exception {
        JceOpenSSLPKCS8EncryptorBuilder encryptorBuilder =
                new JceOpenSSLPKCS8EncryptorBuilder(PKCS8Generator.AES_256_CBC)
                        .setProvider("BC")
                        .setPassword(password.toCharArray());

        OutputEncryptor encryptor = encryptorBuilder.build();
        JcaPKCS8Generator generator = new JcaPKCS8Generator(privateKey, encryptor);
        PemObject pemObject = generator.generate(); //生成加密后的私钥pem

        StringWriter writer = new StringWriter();
        try (PemWriter pemWriter = new PemWriter(writer)) {
            pemWriter.writeObject(pemObject);
        }
        return writer.toString();
    }

    private String convertToPkcs1Pem(RSAPrivateCrtKey privKey) throws Exception {
        byte[] encoded = BouncyCastleUtil.convertRsaPrivateKeyToPkcs1(privKey);
        StringWriter writer = new StringWriter();
        try (PemWriter pemWriter = new PemWriter(writer)) {
            pemWriter.writeObject(new PemObject("RSA PRIVATE KEY", encoded));
        }
        return writer.toString();
    }

    // 请求DTO
    @Data
    public static class RsaKeyRequest {
        private int rsaLength;
        private String rsaFormat;
        private String rsaPass;
    }

    // 响应结构
    @Data
    public static class RsaKeyResponse {
        private int code;
        private String msg;
        private RsaKeyData data;

        public RsaKeyResponse(int code, String msg, RsaKeyData data) {
            this.code = code;
            this.msg = msg;
            this.data = data;
        }
    }

    // 响应中包含的 key 数据
    @Data
    public static class RsaKeyData {
        private String publicKey;
        private String privateKey;

        public RsaKeyData(String pub, String pri) {
            this.publicKey = pub;
            this.privateKey = pri;
        }
    }
}

测试页面:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>RSA 密钥生成器</title>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <style>
        textarea { width: 100%; height: 150px; }
        body.loading { cursor: wait; opacity: 0.5; }
    </style>
</head>
<body>
<h2>RSA 密钥生成</h2>

<label>密钥长度:</label>
<select id="rsaLength">
    <option value="1024">1024</option>
    <option value="2048" selected>2048</option>
    <option value="4096">4096</option>
</select><br><br>

<label>密钥格式:</label>
<select id="rsaFormat">
    <option value="pkcs#1">PKCS#1</option>
    <option value="pkcs#8" selected>PKCS#8</option>
</select><br><br>

<label>私钥密码(可选):</label>
<input type="text" id="rsaPrivatePass" placeholder="输入则加密私钥"><br>
<small>注意:仅 PKCS#8 支持私钥加密,算法为 AES-256-CBC</small><br><br>

<button id="rsaCreateBtn">生成密钥</button>

<h3>公钥:</h3>
<textarea id="rsaPublic" readonly></textarea>

<h3>私钥:</h3>
<textarea id="rsaPrivate" readonly></textarea>

<script>
        function alertError(opt) { alert(opt.con); }

        function doPost(url, data, success, fail) {
            $.ajax({
                url: url,
                type: 'POST',
                contentType: 'application/json',
                data: JSON.stringify(data),
                success: success,
                error: fail
            });
        }

        $("#rsaCreateBtn").click(function() {
            var t = $("#rsaLength").val(),
                e = $("#rsaFormat").val(),
                n = $("#rsaPrivatePass").val();

            if (e === "pkcs#1" && n.trim() !== "") {
                alert("PKCS#1 格式不支持加密私钥,请选择 PKCS#8 格式");
                return;
            }

            var i = "/api/rsa/getRsaKey",
                o = {
                    rsaLength: parseInt(t),
                    rsaFormat: e,
                    rsaPass: n
                };

            $("body").addClass("loading");
            doPost(i, o, function(t) {
                if (t.code === 200 || t.code === "200") {
                    $("#rsaPublic").val(t.data.publicKey);
                    $("#rsaPrivate").val(t.data.privateKey);
                } else {
                    alertError({ con: t.msg });
                }
                $("body").removeClass("loading");
            }, function() {
                alertError({ con: "生成失败,请稍候重试" });
                $("body").removeClass("loading");
            });
        });
    </script>
</body>
</html>

生成的密钥:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3927m67urLE9//HKZK6u
CdIkABCy4DDGc5KmLNdUsw4P+EU62trZYX6zjfxjppx+0UaepzZsQ544ESEZP/k2
6oKo7piaUCx7aWjGp0v3KaZvnWYl+1EEU3gke5SgOo97QPIQY736lOwCIvCmY6rv
Hpkob6w3nG34kxZxxMqJsnlUizbT79gqLlQ0lHUaG9utL5ulXbAmj/MLn2mM2+pQ
T9nHRR4anAFQT4ScORwOuCltlD5WTdsO/8Y4TFZdyYa1p3rz8yYjxlpL5eSnXiyA
r0W/sI30nKBTUirli4OwOghSII0rGTv43bR5WbDcOs3wNcQY8qZzuE7V9R6qDQ3x
FQIDAQAB
-----END PUBLIC KEY-----

-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFKzBVBgkqhkiG9w0BBQ0wSDAnBgkqhkiG9w0BBQwwGgQUAfizxXS1WJ4rqpu6
9e1Qf78oZ/ICAggAMB0GCWCGSAFlAwQBKgQQLIRFmz9lZlViOPhYYVgFhwSCBNCj
UeoCj/mIe0WJ58xZxu76ThVFelYojGD3+LsUKtgoprHvR1mLlNa9DOVlBvxVvHZS
BSl64l2mSRc3OzMZOO0Atu2ltaL6VXlhI3sslFTZXhVfZRuEYWo/Qeq+sALe+9ul
5238u7t8fpABzxrQH1V6n80r1kBiNpo9RCzWCvbRgl8lVkzQQn5bUVE2kP17Jdmn
48CRBCfBEMOGqMs7S6QIMCQdJLQ2p0VpHhU+voxeaC+jUDRIkG/4+rrjqCsxGtWY
Q8O8ixlt9bJAhwv4sj34GNbYdHMTtv8lkgAQI/EU74sn4zpQpbMvjjQvyuiLYxV1
fPu6enpd892S51xDt+OMIbQ8tDvc8G06vwZn/fCc4uSaA5MFE14hFYW2k6wQK0GV
FYF5Ghmtq0UeU2V+LVGHpYHiOIduiA+EBFog8qyxbXpJSaO7m9h8vOyNrhZQB4SU
B3Z1cTASrN/sULcNsZy5Cn75SQtXHAkuV0KQ/7Xja5YM6GNHoYTUxtIz+Y9frO3W
rP1KJ1kq+9fXjqkdyQgoPTwCdpltI15Y2vw/1UpgHlB6FnoCvadgqRTCiWUJ4hum
iFVvAmB+gr2kNZ5KdWjM5vw5gBerP48FeWbJ6lw1WSieP/4f9JKkEOp29ulI4WnI
m/Vo45dxJOrU7u0x9r0cRs4O7Bcigwrw21yO/rNbv51ZubnmVbhTWwYk3uc5ZYwB
TAUirnLMNe2UtCZ+p2K1mb5uHyV6jVN4ZjsYb15zm6JhUyeO7/W0JWnvrM6rbhPA
TLuDD0aPU8Yk7w1bXu/Ibu403eGwXLkfAgfR+NmjfHTbMw2Q+zILPW4u8hrerl0/
c10WBvf65L+6aVwTo+M3B+alhr/oG6EubFMzwuNIkvGsra2siI6XZd3IkOpylZR2
raxYitFxk3o9WiIb6IcGJN2QVi+tpGuFnjOqtqo5AFMDAM9wJ/dNM+DUF6Z0KrNs
u18TwtR5liJN8rcRnQQJpZQcknFtpjVfm+oLR4k8RmKyVOAtgbJ0yVTH28ghPHE1
Wa1oA0ziV40jNDWKchq6D+dKDxuWzjpoZmqqPF3ys/5tz30NopDY45PaHCVtwA+R
OKFTAOtigysh4BMBbmodIHOf6ZISzPAZveRWow52HIrpj8LMEoR4UfP49KLqBm4p
b8xGBo0W5rFNXgLbCxJPJiUy13/Vvd5W1Z2GzPFWgB6AXagthApnpRU9hbEXvXZq
YD8fWURe7OEH9n/ukg/DCMWxPHr0qhUkr31zpBWg1R7cnrFrCWvnAY2bO/1vHJb/
t6LanQEIZmFzY/zoarFDWVuTOrMLUSoIn5RiOO4nsLsA3mkBZGBnf44PoO0aYnLS
IKcpql7Fg+iCqI6BKs/Hpp2n8/Y40V3pB0JhGLA+gdOMhui10r1CWjAerFx/ixEM
3EGfIxEWTw42Y0aYqCbC8sjmmmfWLnxt8XAabGiK9vr/Pj4hT7QlUbT/hx52vKGJ
TeMl3w2TAIKo3BwcJALdPx2hBrLaYymrPI3dbsjh3kRqdy0aYFniUyGZ/Jm2ugXg
rxsSK8glIFpmLWS36Tr62mtEZg24S7tPq99zEcl5EA3jy7ea79jYsAJDJkyVvG5D
CUxKyqgvAFe5KfSg1kY1wQeSSg4caf/a34kWBtEd/Q==
-----END ENCRYPTED PRIVATE KEY-----
参考

https://www.bejson.com/ 实际没实现AES-256-CBC对私钥加密的过程,可以用来验证加解密和签名
ChatGTP & DeepSeek

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。