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