项目地址
相关openssl命令
# OpenSSL 1.1.1h 22 Sep 2020
# 生成一个3072bit的RSA私钥
openssl genpkey -algorithm RSA-PSS -pkeyopt rsa_keygen_bits:3072 -pkeyopt rsa_keygen_pubexp:65537 -pkeyopt rsa_pss_keygen_md:sha256 -pkeyopt rsa_pss_keygen_mgf1_md:sha256 -pkeyopt rsa_pss_keygen_saltlen:32 -out rsa_pss_private_3072_restricted_nopassword.pem
# 查看私钥
openssl pkey -in rsa_pss_private_3072_restricted_nopassword.pem -text
# 导出公钥
openssl pkey -in rsa_pss_private_3072_restricted_nopassword.pem -out rsa_pss_public_3072_restricted_nopassword.pem -pubout
# 生成一个口令保护的3072bit的RSA私钥 Hello@123
openssl genpkey -aes256 -algorithm RSA-PSS -pkeyopt rsa_keygen_bits:3072 -pkeyopt rsa_keygen_pubexp:65537 -pkeyopt rsa_pss_keygen_md:sha256 -pkeyopt rsa_pss_keygen_mgf1_md:sha256 -pkeyopt rsa_pss_keygen_saltlen:32 -out rsa_pss_private_3072_restricted_password.pem
# 查看私钥
openssl pkey -in rsa_pss_private_3072_restricted_password.pem -text
# 导出公钥
openssl pkey -in rsa_pss_private_3072_restricted_password.pem -out rsa_pss_public_3072_restricted_password.pem -pubout
<bouncycastle.version>1.69bouncycastle.version>
<dependency>
<groupId>org.bouncycastlegroupId>
<artifactId>bcprov-jdk15onartifactId>
<version>${bouncycastle.version}version>
dependency>
<dependency>
<groupId>org.bouncycastlegroupId>
<artifactId>bcpkix-jdk15onartifactId>
<version>${bouncycastle.version}version>
dependency>
package com.ysx.utils.crypto.rsa;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
import org.bouncycastle.operator.InputDecryptorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import org.bouncycastle.pkcs.PKCSException;
import org.bouncycastle.util.io.pem.PemObject;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.FileReader;
import java.io.IOException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PSSParameterSpec;
import java.security.spec.X509EncodedKeySpec;
/**
* @author youngbear
* @email [email protected]
* @date 2021/7/20 23:29
* @blog https://blog.csdn.net/next_second
* @github https://github.com/YoungBear
* @description 使用BouncyCastle RSA 工具类
* 参考:https://www.baeldung.com/java-read-pem-file-keys
*/
public class RSAUtilsBC {
/**
* 加密算法
*/
private static final String ENCRYPT_ALGORITHM = "RSA/None/OAEPWithSHA-256AndMGF1Padding";
/**
* 签名算法 SHA256withRSA 算法,PSS 填充模式
*/
private static final String SIGNATURE_ALGORITHM = "SHA256withRSA/PSS";
/**
* 签名填充盐值长度
*/
private static final int SIGNATURE_SALT_LENGTH = 32;
static {
Security.addProvider(new BouncyCastleProvider());
}
/**
* 使用公钥加密
*
* @param publicKey 公钥
* @param data 待加密数据明文
* @return 密文
* @throws NoSuchPaddingException 异常
* @throws NoSuchAlgorithmException 异常
* @throws NoSuchProviderException 异常
* @throws InvalidKeyException 异常
* @throws IllegalBlockSizeException 异常
* @throws BadPaddingException 异常
*/
public static byte[] encrypt(PublicKey publicKey, byte[] data)
throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher = Cipher.getInstance(ENCRYPT_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
/**
* 使用私钥解密
*
* @param privateKey 私钥
* @param cipherData 待解密的密文
* @return 明文
* @throws NoSuchPaddingException 异常
* @throws NoSuchAlgorithmException 异常
* @throws NoSuchProviderException 异常
* @throws InvalidKeyException 异常
* @throws IllegalBlockSizeException 异常
* @throws BadPaddingException 异常
*/
public static byte[] decrypt(PrivateKey privateKey, byte[] cipherData)
throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher = Cipher.getInstance(ENCRYPT_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(cipherData);
}
/**
* 签名
*
* @param privateKey 私钥
* @param data 数据
* @return 数据的签名值
* @throws NoSuchAlgorithmException 异常
* @throws NoSuchProviderException 异常
* @throws InvalidAlgorithmParameterException 异常
* @throws InvalidKeyException 异常
* @throws SignatureException 异常
*/
public static byte[] signature(PrivateKey privateKey, byte[] data) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, InvalidKeyException, SignatureException {
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME);
PSSParameterSpec pssParameterSpec = new PSSParameterSpec(MGF1ParameterSpec.SHA256.getDigestAlgorithm(),
"MGF1", MGF1ParameterSpec.SHA256, SIGNATURE_SALT_LENGTH, 1);
signature.setParameter(pssParameterSpec);
signature.initSign(privateKey);
signature.update(data);
return signature.sign();
}
/**
* 验签
*
* @param publicKey 公钥
* @param data 数据
* @param sign 签名值
* @return 验签是否成功
* @throws NoSuchAlgorithmException 异常
* @throws NoSuchProviderException 异常
* @throws InvalidAlgorithmParameterException 异常
* @throws InvalidKeyException 异常
* @throws SignatureException 异常
*/
public static boolean verify(PublicKey publicKey, byte[] data, byte[] sign) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, InvalidKeyException, SignatureException {
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME);
PSSParameterSpec pssParameterSpec = new PSSParameterSpec(MGF1ParameterSpec.SHA256.getDigestAlgorithm(),
"MGF1", MGF1ParameterSpec.SHA256, SIGNATURE_SALT_LENGTH, 1);
signature.setParameter(pssParameterSpec);
signature.initVerify(publicKey);
signature.update(data);
return signature.verify(sign);
}
/**
* 从文件加载口令保护的私钥
*
* @param filePath 私钥文件路径
* @param password 口令
* @return 私钥
* @throws IOException 异常
* @throws OperatorCreationException 异常
* @throws PKCSException 异常
*/
public static PrivateKey decodeEncryptedPrivateKey(String filePath, String password) throws IOException, OperatorCreationException, PKCSException {
try (PEMParser pemParser = new PEMParser(new FileReader(filePath))) {
Object pem = pemParser.readObject();
if (pem instanceof PKCS8EncryptedPrivateKeyInfo) {
JcaPEMKeyConverter converter = new JcaPEMKeyConverter()
.setProvider(BouncyCastleProvider.PROVIDER_NAME);
PKCS8EncryptedPrivateKeyInfo keyInfo = (PKCS8EncryptedPrivateKeyInfo) pem;
InputDecryptorProvider pkcs8Prov = new JceOpenSSLPKCS8DecryptorProviderBuilder()
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
.build(password.toCharArray());
return converter.getPrivateKey(keyInfo.decryptPrivateKeyInfo(pkcs8Prov));
}
throw new RuntimeException("invalid key file.");
}
}
/**
* 从文件加载私钥(无口令保护)
*
* @param filePath 私钥文件路径
* @return 私钥
* @throws IOException 异常
*/
public static PrivateKey decodePrivateKey(String filePath) throws IOException {
try (PEMParser pemParser = new PEMParser(new FileReader(filePath))) {
Object pem = pemParser.readObject();
if (pem instanceof PrivateKeyInfo) {
JcaPEMKeyConverter converter = new JcaPEMKeyConverter()
.setProvider(BouncyCastleProvider.PROVIDER_NAME);
PrivateKeyInfo keyInfo = (PrivateKeyInfo) pem;
return converter.getPrivateKey(keyInfo);
}
throw new RuntimeException("invalid key file.");
}
}
/**
* 从文件加载公钥
*
* @param filePath 公钥文件路径
* @return 私钥
* @throws IOException 异常
*/
public static PublicKey decodePublicKey(String filePath) throws IOException {
try (PEMParser pemParser = new PEMParser(new FileReader(filePath))) {
JcaPEMKeyConverter converter = new JcaPEMKeyConverter()
.setProvider(BouncyCastleProvider.PROVIDER_NAME);
SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(pemParser.readObject());
return converter.getPublicKey(publicKeyInfo);
}
}
/**
* 从文件加载公钥2
*
* @param filePath 公钥文件路径
* @return 公钥
* @throws IOException 异常
* @throws NoSuchAlgorithmException 异常
* @throws InvalidKeySpecException 异常
* @throws NoSuchProviderException 异常
*/
public static PublicKey decodePublicKey2(String filePath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
try (PEMParser pemParser = new PEMParser(new FileReader(filePath))) {
PemObject pemObject = pemParser.readPemObject();
byte[] content = pemObject.getContent();
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(content);
KeyFactory factory = KeyFactory.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME);
return factory.generatePublic(pubKeySpec);
}
}
}
package com.ysx.utils.crypto.rsa;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.pkcs.PKCSException;
import org.bouncycastle.util.encoders.Hex;
import org.junit.Assert;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
/**
* @author youngbear
* @email [email protected]
* @date 2021/7/20 23:48
* @blog https://blog.csdn.net/next_second
* @github https://github.com/YoungBear
* @description
*/
public class RSAUtilsBCTest {
private static final Logger LOGGER = LoggerFactory.getLogger(RSAUtilsBCTest.class);
private static final String BASE_PATH = RSAUtilsBCTest.class.getResource("/openssl/").getPath();
@Test
public void encryptTest() throws IOException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, NoSuchProviderException, InvalidKeyException {
String publicKeyFilePath = BASE_PATH + "rsa_pss_public_3072_restricted_password.pem";
PublicKey publicKey = RSAUtilsBC.decodePublicKey(publicKeyFilePath);
byte[] data = "London2012".getBytes(StandardCharsets.UTF_8);
byte[] cipherData = RSAUtilsBC.encrypt(publicKey, data);
String cipherDataHexString = Hex.toHexString(cipherData);
System.out.println("cipherDataHexString: " + cipherDataHexString);
}
@Test
public void decryptTestSuccess() throws IOException, OperatorCreationException, PKCSException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, NoSuchProviderException, InvalidKeyException {
String privateKeyFilePath = BASE_PATH + "rsa_pss_private_3072_restricted_password.pem";
String password = "Hello@123";
PrivateKey privateKey = RSAUtilsBC.decodeEncryptedPrivateKey(privateKeyFilePath, password);
String cipherDataHexString = "5ae58323930a9f0dac220483dba7e2dd1da4d56ebbfc458ff64eae0f1df599db0f3c4438e280108f1e443071efc3012b30b75a0aea565fe89d876da4d32f3574538c5410fd86531ec402775f1d3574343d726add0da44fc14b18716c4e4b2a4fc309036813b75ba5863515c394b1280ab9b94b26b576a7880c094f39bd25821992c7fd28271986eab957515458419014c1890c82a9c814bbc6eef0006fed23f62ea2a2e0f7a30cfaf5873f66267c4032e14773f2b965d9d66ad9ca492a8b847c59f454b6795d24456f288bb7420b8f5e62b34057057f339c01af5460f21e2f73dd1f90a8559b0bfbc8bea0003dcb0d9c1cda7b7f8fa169d636ddc1584d94b48bf0cfcd2f369fa3ce959ef3478b034c7253e738a7ac6b6cd999d57f7451752aa8fc6afdec6a1451b00eaef540f340c5a65d2b9084c3ad70d1e7dcf5b8847a14c7c1d2c92cf6f220d881582c7a5f5a03cba436223c5b0d4eb4b5fbc1357c477b435a16d9eb54ac9373c675ff50dc36b6b626547fe7930e8ec08676613787fcd7df";
byte[] cipherData = Hex.decode(cipherDataHexString);
byte[] plainData = RSAUtilsBC.decrypt(privateKey, cipherData);
String plainText = new String(plainData, StandardCharsets.UTF_8);
Assert.assertEquals("London2012", plainText);
}
@Test
public void decryptTestFailed() throws IOException, OperatorCreationException, PKCSException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, NoSuchProviderException, InvalidKeyException {
String privateKeyFilePath = BASE_PATH + "rsa_pss_private_3072_restricted_password.pem";
String password = "Hello@123";
PrivateKey privateKey = RSAUtilsBC.decodeEncryptedPrivateKey(privateKeyFilePath, password);
String cipherDataHexString = "5ae58323930a9f0dac220483dba7e2dd1da4d56ebbfc458ff64eae0f1df599db0f3c4438e280108f1e443071efc3012b30b75a0aea565fe89d876da4d32f3574538c5410fd86531ec402775f1d3574343d726add0da44fc14b18716c4e4b2a4fc309036813b75ba5863515c394b1280ab9b94b26b576a7880c094f39bd25821992c7fd28271986eab957515458419014c1890c82a9c814bbc6eef0006fed23f62ea2a2e0f7a30cfaf5873f66267c4032e14773f2b965d9d66ad9ca492a8b847c59f454b6795d24456f288bb7420b8f5e62b34057057f339c01af5460f21e2f73dd1f90a8559b0bfbc8bea0003dcb0d9c1cda7b7f8fa169d636ddc1584d94b48bf0cfcd2f369fa3ce959ef3478b034c7253e738a7ac6b6cd999d57f7451752aa8fc6afdec6a1451b00eaef540f340c5a65d2b9084c3ad70d1e7dcf5b8847a14c7c1d2c92cf6f220d881582c7a5f5a03cba436223c5b0d4eb4b5fbc1357c477b435a16d9eb54ac9373c675ff50dc36b6b626547fe7930e8ec08676613787fcd7df";
byte[] cipherData = Hex.decode(cipherDataHexString);
byte[] plainData = RSAUtilsBC.decrypt(privateKey, cipherData);
String plainText = new String(plainData, StandardCharsets.UTF_8);
Assert.assertNotEquals("London2013", plainText);
}
@Test
public void signAndVerifyTest() throws IOException, OperatorCreationException, PKCSException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, SignatureException, NoSuchProviderException, InvalidKeyException {
String privateKeyFilePath = BASE_PATH + "rsa_pss_private_3072_restricted_password.pem";
String password = "Hello@123";
PrivateKey privateKey = RSAUtilsBC.decodeEncryptedPrivateKey(privateKeyFilePath, password);
byte[] data = "Beijing2008".getBytes(StandardCharsets.UTF_8);
byte[] signature = RSAUtilsBC.signature(privateKey, data);
String signHexString = Hex.toHexString(signature);
System.out.println("signHexString: " + signHexString);
}
@Test
public void verifyTestSuccess() throws IOException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, SignatureException, NoSuchProviderException, InvalidKeyException {
byte[] data = "Beijing2008".getBytes(StandardCharsets.UTF_8);
byte[] signature = Hex.decode("077bacf8b43da3f84b38bec809fabcf8c6dd321dde4987beca58c33fcfae7f71cfe1d0e19ec1b91802efc016da36e598e03599d6c0c6dad4209a3360d817c6119e4f8b69d342e9a263c67d03dd3212c35f34a23fcb7ae266a21f4277d0bd0d0783b6807b1f8157babc78a7ca84035029c4554e1dc843b17886d5c26d394d50be210140d40a117535a37cc20f26e93da53c52f8d112d9fbc9670fe1a08ba9753f4d204d37ffdf97f7539ddafbe95af10550b05a78a31eef9217059c556775f5bbdf88aba4ab5a30ac48989d428bb6c163910f3c34659e97b01388fb39b38c0e80b6ec69b359c30f114a9bf53253a60239d7fab2f4737af5fd9c5b15cee6c86bd65f9d89af77c93ccdf3510b9b2bb65b04020bd7570714cd230b89fb333ea0c126d59f9f40ef7e24af8bbf0eb3613e1e325251e9e6b9de2db2f19cca12b15583dfbceb56687113c8d46c4f0dbac8facde217ad2bed9c46d053d6458a64a439f6d5dbabc7e208c1e608318180882083890bd08c4dc89b9abb4084bc991901d4aaff");
String publicKeyFilePath = BASE_PATH + "rsa_pss_public_3072_restricted_password.pem";
PublicKey publicKey = RSAUtilsBC.decodePublicKey(publicKeyFilePath);
boolean verify = RSAUtilsBC.verify(publicKey, data, signature);
Assert.assertTrue(verify);
}
@Test
public void verifyTestFailed() throws IOException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, SignatureException, NoSuchProviderException, InvalidKeyException {
byte[] data = "Beijing2008".getBytes(StandardCharsets.UTF_8);
byte[] signature = Hex.decode("077bacf8b43da3f84b38bec809fabcf8c6dd321dde4987be");
String publicKeyFilePath = BASE_PATH + "rsa_pss_public_3072_restricted_password.pem";
PublicKey publicKey = RSAUtilsBC.decodePublicKey(publicKeyFilePath);
boolean verify = RSAUtilsBC.verify(publicKey, data, signature);
Assert.assertFalse(verify);
}
@Test
public void decodeEncryptedPrivateKeyTest() throws IOException, OperatorCreationException, PKCSException {
String filePath = BASE_PATH + "rsa_pss_private_3072_restricted_password.pem";
String password = "Hello@123";
PrivateKey privateKey = RSAUtilsBC.decodeEncryptedPrivateKey(filePath, password);
String algorithm = privateKey.getAlgorithm();
int length = ((RSAPrivateKey) privateKey).getModulus().bitLength();
LOGGER.info("algorithm: {}, length: {}", algorithm, length);
Assert.assertEquals("RSASSA-PSS", algorithm);
Assert.assertEquals(3072, length);
}
@Test
public void decodePrivateKeyTest() throws IOException {
String filePath = BASE_PATH + "rsa_pss_private_3072_restricted_nopassword.pem";
PrivateKey privateKey = RSAUtilsBC.decodePrivateKey(filePath);
String algorithm = privateKey.getAlgorithm();
int length = ((RSAPrivateKey) privateKey).getModulus().bitLength();
LOGGER.info("algorithm: {}, length: {}", algorithm, length);
Assert.assertEquals("RSASSA-PSS", algorithm);
Assert.assertEquals(3072, length);
}
@Test
public void decodePublicKeyTest() throws IOException {
String filePath = BASE_PATH + "rsa_pss_public_3072_restricted_password.pem";
PublicKey publicKey = RSAUtilsBC.decodePublicKey(filePath);
String algorithm = publicKey.getAlgorithm();
int length = ((RSAPublicKey) publicKey).getModulus().bitLength();
LOGGER.info("algorithm: {}, length: {}", algorithm, length);
Assert.assertEquals("RSASSA-PSS", algorithm);
Assert.assertEquals(3072, length);
}
@Test
public void decodePublicKeyTest2() throws IOException {
String filePath = BASE_PATH + "rsa_pss_public_3072_restricted_nopassword.pem";
PublicKey publicKey = RSAUtilsBC.decodePublicKey(filePath);
String algorithm = publicKey.getAlgorithm();
int length = ((RSAPublicKey) publicKey).getModulus().bitLength();
LOGGER.info("algorithm: {}, length: {}", algorithm, length);
Assert.assertEquals("RSASSA-PSS", algorithm);
Assert.assertEquals(3072, length);
}
@Test
public void decodePublicKey2Test() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
String filePath = BASE_PATH + "rsa_pss_public_3072_restricted_password.pem";
PublicKey publicKey = RSAUtilsBC.decodePublicKey2(filePath);
String algorithm = publicKey.getAlgorithm();
int length = ((RSAPublicKey) publicKey).getModulus().bitLength();
LOGGER.info("algorithm: {}, length: {}", algorithm, length);
Assert.assertEquals("RSASSA-PSS", algorithm);
Assert.assertEquals(3072, length);
}
@Test
public void decodePublicKey2Test2() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
String filePath = BASE_PATH + "rsa_pss_public_3072_restricted_nopassword.pem";
PublicKey publicKey = RSAUtilsBC.decodePublicKey2(filePath);
String algorithm = publicKey.getAlgorithm();
int length = ((RSAPublicKey) publicKey).getModulus().bitLength();
LOGGER.info("algorithm: {}, length: {}", algorithm, length);
Assert.assertEquals("RSASSA-PSS", algorithm);
Assert.assertEquals(3072, length);
}
}