RSA加密和数字签名在Java中常见应用【原创】
相关术语解释:
1、私钥(PrivateKey)的生成
1.1、加载 PKCS #8 标准的PEM编码的字符串,并生成私钥(RSAPrivateKey)
关于PKCS #8: In cryptography, PKCS #8 is a standard syntax for storing private key information. PKCS #8 is one of the family of standards called Public-Key Cryptography Standards (PKCS) created by RSA Laboratories。PKCS #8 private keys are typically exchanged in the PEM base64-encoded format
如和生成RSA PEM 格式的私钥文件以及如何转换成 PKCS #8,参考: 《通过OpenSSL来生成PEM格式的私钥、PKCS8格式的私钥、公钥|pfx格式的私钥、cer格式的公钥》
私钥 PEM 内容样例如下:
-----BEGIN PRIVATE KEY-----
MIIBVgIBADANBgkqhkiG9w0BAQEFAASCAUAwggE8AgEAAkEAq7BFUpkGp3+LQmlQ
Yx2eqzDV+xeG8kx/sQFV18S5JhzGeIJNA72wSeukEPojtqUyX2J0CciPBh7eqclQ
2zpAswIDAQABAkAgisq4+zRdrzkwH1ITV1vpytnkO/NiHcnePQiOW0VUybPyHoGM
/jf75C5xET7ZQpBe5kx5VHsPZj0CBb3b+wSRAiEA2mPWCBytosIU/ODRfq6EiV04
lt6waE7I2uSPqIC20LcCIQDJQYIHQII+3YaPqyhGgqMexuuuGx+lDKD6/Fu/JwPb
5QIhAKthiYcYKlL9h8bjDsQhZDUACPasjzdsDEdq8inDyLOFAiEAmCr/tZwA3qeA
ZoBzI10DGPIuoKXBd3nk/eBxPkaxlEECIQCNymjsoI7GldtujVnr1qT+3yedLfHK
srDVjIT3LsvTqw==
-----END PRIVATE KEY-----
使用下面的方法来生成私钥(RSAPrivateKey)需要删除上面的“-----BEGIN PRIVATE KEY-----” 和“-----END PRIVATE KEY-----”
Java 代码:
package rsa; import java.security.KeyFactory; import java.security.interfaces.RSAPrivateKey; import java.security.spec.PKCS8EncodedKeySpec; import lombok.SneakyThrows; import lombok.experimental.UtilityClass; import org.apache.commons.codec.binary.Base64; @UtilityClass public class PrivateKeyGen { /** * 加载 PKCS8 私钥证书(PEM base64-encoded format) * <br/> PKCS #8 is a standard syntax for storing private key information. * <br/> PKCS #8 is one of the family of standards called Public-Key Cryptography Standards (PKCS) created by RSA Laboratories. * * @param privateKeyPem 私钥文件内容(PEM Base64编码) */ @SneakyThrows public static RSAPrivateKey getPrivateKey(String privateKeyPem){ KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyPem)); return (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec); } }
1.2、加载 PFX(PKCS #12 标准)文件并生成私钥(PrivateKey)
java代码:
package rsa; import java.io.FileInputStream; import java.security.KeyStore; import java.security.PrivateKey; import lombok.Cleanup; import lombok.SneakyThrows; import lombok.experimental.UtilityClass; @UtilityClass public class PrivateKeyGen { /** * 读取 PFX 格式的证书文件并生成 {@link PrivateKey} 类型实例 * * @param keyStorePath PFX 格式的证书文件路径 * @param keyStorePasswd KeyStroe 的 password */ @SneakyThrows public static PrivateKey getPrivateKey(String keyStorePath, String keyStorePasswd) { @Cleanup FileInputStream fis = new FileInputStream(keyStorePath); KeyStore store = KeyStore.getInstance("PKCS12"); store.load(fis, keyStorePasswd.toCharArray()); String alia = store.aliases().nextElement(); return (PrivateKey) store.getKey(alia, keyStorePasswd.toCharArray()); } }
1.3、根据证书的模(Modulus)和指数(Exponent)来生成私钥(PrivateKey)
import java.math.BigInteger; import java.security.Key; import java.security.KeyFactory; import java.security.spec.RSAPrivateKeySpec; import lombok.SneakyThrows; /** * @author xfyou */ public class RsaPrivateKey { @SneakyThrows private Key generatePrivateKey(byte[] keyModulus, byte[] keyExponent) { return KeyFactory.getInstance("RSA").generatePrivate(new RSAPrivateKeySpec(newBigInteger(keyModulus), newBigInteger(keyExponent))); } private static BigInteger newBigInteger(byte[] keyInfo) { return new BigInteger(1, keyInfo); } }
2、公钥(PublicKey)的生成
2.1、加载 PFX(PKCS #12 标准)文件并生成(导出)公钥(PublicKey)
Java代码:
package rsa; import java.io.FileInputStream; import java.security.KeyStore; import java.security.PublicKey; import lombok.Cleanup; import lombok.SneakyThrows; import lombok.experimental.UtilityClass; /** * KeyGen */ @UtilityClass public class KeyGen { /** * 读取 PFX 格式的证书文件并生成 {@link PublicKey} 类型实例 * * @param keyStorePath PFX 格式的证书文件路径 * @param keyStorePasswd KeyStroe 的 password */ @SneakyThrows public static PublicKey getPublicKey(String keyStorePath, String keyStorePasswd) { @Cleanup FileInputStream fis = new FileInputStream(keyStorePath); KeyStore store = KeyStore.getInstance("PKCS12"); store.load(fis, keyStorePasswd.toCharArray()); String alia = store.aliases().nextElement(); return store.getCertificate(alia).getPublicKey(); } }
2.2、加载 符合 X.509 国际标准 PEM base64-encoded format 的证书内容,并生成公钥(RSAPublicKey)
需要删除证书内容(字符串)中的 “-----BEGIN CERTIFICATE-----” 和 “-----END CERTIFICATE-----”
公钥 PEM 证书内容样例如下:
-----BEGIN CERTIFICATE-----
MIIC6DCCAlGgAwIBAgIUI2ZSO2i7FA4iBKUOvjsZRzCQj8YwDQYJKoZIhvcNAQEL
BQAwgYUxCzAJBgNVBAYTAkNOMREwDwYDVQQIDAhTaGFuZ0hhaTERMA8GA1UEBwwI
mu1GI8mCpMYVGyUnJVNHqb3PG5uECbcKk8SfVg==
-----END CERTIFICATE-----
Java代码:
package rsa; import java.security.KeyFactory; import java.security.interfaces.RSAPublicKey; import java.security.spec.X509EncodedKeySpec; import lombok.SneakyThrows; import lombok.experimental.UtilityClass; import org.apache.commons.codec.binary.Base64; @UtilityClass public class KeyGen { /** * 加载 PEM base64-encoded format 的公钥证书内容,并生成 {@link RSAPublicKey} 类型实例 * * @param pemContent PEM base64-encoded format 的公钥证书内容,此公钥证书符合 X.509 国际标准 * @return {@link RSAPublicKey} 类型实例 */ @SneakyThrows private RSAPublicKey getPublicKey(String pemContent) { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(pemContent)); return (RSAPublicKey) keyFactory.generatePublic(x509KeySpec); } }
2.3、加载 符合 X.509 国际标准的CER(*.cer)格式的证书文件,并生成公钥(PublicKey)
Java代码:
package rsa; import java.io.FileInputStream; import java.security.PublicKey; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import lombok.Cleanup; import lombok.SneakyThrows; import lombok.experimental.UtilityClass; @UtilityClass public class KeyGen { /** * 读取符合 X.509 国际标准的 CER 格式的公钥证书文件,并生成 {@link PublicKey} 类型的实例 * * @param cerPath 公钥证书文件(*.cer)的路径 */ @SneakyThrows public static PublicKey getPublicKey(String cerPath) { @Cleanup FileInputStream bais = new FileInputStream(cerPath); CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate) cf.generateCertificate(bais); return cert.getPublicKey(); } }
如果通过读取完整的 PEM 证书内容(字符串)来生成公钥证书(PublicKey)则通过以下方式。
Java代码
package rsa; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.security.PublicKey; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import lombok.Cleanup; import lombok.SneakyThrows; import lombok.experimental.UtilityClass; @UtilityClass public class KeyGen { /** * 读取符合 X.509 国际标准的公钥证书的 PEM base64-encoded 内容字符串 ,并生成 {@link PublicKey} 类型的实例 * * @param pubKeyCertPem 公钥证书 PEM base64-encoded 内容字符串 */ @SneakyThrows public static PublicKey getPublicKey(String pubKeyCertPem) { @Cleanup InputStream is = new ByteArrayInputStream(pubKeyCertPem.getBytes(StandardCharsets.UTF_8)); CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate) cf.generateCertificate(is); return cert.getPublicKey(); } }
2.4、根据证书的模(Modulus)和指数(Exponent)来生成公钥(PublicKey)
import java.math.BigInteger; import java.security.Key; import java.security.KeyFactory; import java.security.spec.RSAPublicKeySpec; import lombok.SneakyThrows; public class RsaPublicKey { @SneakyThrows private Key generatePublicKey(byte[] keyModulus, byte[] keyExponent) { return KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(newBigInteger(keyModulus), newBigInteger(keyExponent))); } private static BigInteger newBigInteger(byte[] keyInfo) { return new BigInteger(1, keyInfo); } }
3、RSA非对称-加密
公钥加密,私钥解密 或 私钥加密,公钥解密
Java代码:
package rsa; import javax.crypto.Cipher; import lombok.SneakyThrows; import lombok.experimental.UtilityClass; import org.apache.commons.codec.binary.Base64; @UtilityClass public class CryptTool { /** * RSA 公钥加密 * * @param data 待加密的数据 * @return 加密后的字节数组 */ @SneakyThrows public byte[] encrypt(byte[] data) { Cipher cipher = Cipher.getInstance("RSA"); // The key is the {@link java.security.PublicKey} instance cipher.init(Cipher.ENCRYPT_MODE, publicKey); return cipher.doFinal(data); } /** * RSA 公钥加密 * * @param data 待加密的数据 * @return 加密后并Base64的字符串 */ @SneakyThrows public String encrypt(byte[] data) { Cipher cipher = Cipher.getInstance("RSA"); // The key is the {@link java.security.PublicKey} instance cipher.init(Cipher.ENCRYPT_MODE, publicKey); return Base64.encodeBase64String(cipher.doFinal(data)); } }
4、RSA非对称-解密
Java代码:
package rsa; import java.nio.charset.StandardCharsets; import javax.crypto.Cipher; import lombok.SneakyThrows; import lombok.experimental.UtilityClass; @UtilityClass public class CryptTool { /** * RSA 私钥解密 * * @param data 待解密的数据 * @return 解密后的字节数组 */ @SneakyThrows public byte[] decrypt(byte[] data) { Cipher cipher = Cipher.getInstance("RSA"); // The key is the {@link java.security.PrivateKey} instance cipher.init(Cipher.DECRYPT_MODE, privateKey); return cipher.doFinal(data); } /** * RSA 私钥解密 * * @param data 待解密的数据 * @return 解密后的字符串 */ @SneakyThrows public String decrypt(String data) { Cipher cipher = Cipher.getInstance("RSA"); // The key is the {@link java.security.PrivateKey} instance cipher.init(Cipher.DECRYPT_MODE, privateKey); return new String(cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8); } }
4、RSA非对称-签名
Java代码:
package rsa; import java.security.Signature; import lombok.SneakyThrows; import lombok.experimental.UtilityClass; import org.apache.commons.codec.binary.Base64; @UtilityClass public class CryptTool { /** * RSA 使用私钥进行签名,可能的 Signature Algrithom: </br> * <ol> * <li>SHA1withRSA</li> * <li>SHA256withRSA</li> * <li>SHA384withRSA</li> * <li>SHA512withRSA</li></li> * </ol> * @param data 待签名的数据 * @return 签名后的数据 */ @SneakyThrows public byte[] sign(byte[] data) { Signature signature = Signature.getInstance("SHA256withRSA"); signature.initSign(privateKey); signature.update(data); return signature.sign(); } /** * RSA 使用私钥进行签名,可能的 Signature Algrithom: </br> * <ol> * <li>SHA1withRSA</li> * <li>SHA256withRSA</li> * <li>SHA384withRSA</li> * <li>SHA512withRSA</li></li> * </ol> * @param data 待签名的数据 * @return 签名后的数据 */ @SneakyThrows public String sign(byte[] data) { Signature signature = Signature.getInstance("SHA256withRSA"); signature.initSign(privateKey); signature.update(data); return Base64.encodeBase64String(signature.sign()); } }
4、RSA非对称-验签
Java代码:
package rsa; import java.security.Signature; import lombok.SneakyThrows; import lombok.experimental.UtilityClass; import org.apache.commons.codec.binary.Base64; @UtilityClass public class CryptTool { /** * RSA 公钥验签 * * @param data 待验签的数据 * @param sign 对方已签名的数据 * @return 验证结果 */ @SneakyThrows public boolean verify(byte[] data, String sign) { Signature signature = Signature.getInstance("SHA256withRSA"); signature.initVerify(publicKey); signature.update(data); return signature.verify(Base64.decodeBase64(sign)); } }
附:Signature
Algorithms
The algorithm names in this section can be specified when generating an instance of
Signature
.
Alg. Name | Description |
---|---|
NONEwithRSA |
The RSA signature algorithm which does not use a digesting algorithm (e.g. MD5/SHA1) before performing the RSA operation. For more information about the RSA Signature algorithms, please see PKCS1. |
MD2withRSA MD5withRSA |
The MD2/MD5 with RSA Encryption signature algorithm which uses the MD2/MD5 digest algorithm and RSA to create and verify RSA digital signatures as defined in PKCS1. |
SHA1withRSA SHA256withRSA |
The signature algorithm with SHA-* and the RSA encryption algorithm as defined in the OSI Interoperability Workshop, using the padding conventions described in PKCS1. |
NONEwithDSA |
The Digital Signature Algorithm as defined in FIPS PUB 186-2. The data must be exactly 20 bytes in length. This algorithms is also known under the alias name of rawDSA. |
SHA1withDSA |
The DSA with SHA-1 signature algorithm which uses the SHA-1 digest algorithm and DSA to create and verify DSA digital signatures as defined in FIPS PUB 186. |
NONEwithECDSA SHA1withECDSA SHA256withECDSA SHA384withECDSA SHA512withECDSA (ECDSA) |
The ECDSA signature algorithms as defined in ANSI X9.62. Note:"ECDSA" is an ambiguous name for the "SHA1withECDSA" algorithm and should not be used. The formal name "SHA1withECDSA" should be used instead. |
<digest>with<encryption> |
Use this to form a name for a signature algorithm with a particular message digest (such as MD2 or MD5) and algorithm (such as RSA or DSA), just as was done for the explicitly-defined standard names in this section (MD2withRSA, etc.). For the new signature schemes defined in PKCS1 v 2.0, for which the <digest>with<encryption> form is insufficient, <digest>with<encryption>and<mgf> can be used to form a name. Here, <mgf> should be replaced by a mask generation function such as MGF1. Example: MD5withRSAandMGF1. |