前后端RSA互相加解密加签验签密钥对生成(Java)
Posted 凌波漫步&
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前后端RSA互相加解密加签验签密钥对生成(Java)相关的知识,希望对你有一定的参考价值。
目录
- 一、序言
- 二、关于PKCS#1和PKCS#8格式密钥
- 二、关于JSEncrypt
- 三、关于jsrsasign
- 四、前端RSA加解密、加验签示例
- 五、Java后端RSA加解密、加验签
- 六、前后端加解密、加验签交互测试
一、序言
最近有一些安全性要求比较高的场景,我们提供API给第三方商户用于收单,其中有几个功能是绑卡、ATM/POS密码变更。
出于合规和监管要求,第三方商户不能保存卡号、CVV、密码等敏感信息,所以相关的敏感操作必须在接口提供方H5页面中完成。为了最大程度保证安全性,我们决定对敏感信息进行加密传输,同时对请求参数进行加签处理。
由于前端并不需要解密操作,最终我们选择RSA
非对称加密,前端这块主要采用jsencrypt
进行加解密,jsrsasign
用来生成密钥对、加签验签。
二、关于PKCS#1和PKCS#8格式密钥
由于Java非对称加解密、加验签都是采用PKCS#8
格式的密钥,PKCS#1
格式的密钥跑不通,这里先简单介绍一下两者的区别。
1、简介
PKCS#1
和PKCS#8
是两个不同的数字证书标准。
-
PKCS#1是一个公钥加密标准,它定义了使用RSA算法进行加密和签名的格式。主要用于对数字签名、加密以及数字签名验证等应用。
-
PKCS#8则是一个私钥保护标准,它定义了私钥的存储格式。它主要用于在文件中对私钥进行保护,以防止意外泄露或不当使用。
总的来说,PKCS#1
是针对公钥的标准,而PKCS#8
是针对私钥的标准。
2、区别
两者的密钥格式不一样,下面以标准.pem
格式为例,看下PKCS#1格式和PKCS#8格式密钥的区别:
- PKCS#1格式私钥:
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQC5BW6T9GVaaG/epGDjPpY3wN0DrBt+NojvxkEgpUdOAxgAepqe
...
TbzKH/LEqZN8WVau3bf41yAx2YoaOsIJJtOUTYcfh14=
-----END RSA PRIVATE KEY-----
- PKCS#8格式私钥:
-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALkFbpP0ZVpob96k
...
wgkm05RNhx+HXg==
-----END PRIVATE KEY-----
- PKCS#1格式公钥:
-----BEGIN RSA PUBLIC KEY-----
MIICXAIBAAKBgQC5BW6T9GVaaG/epGDjPpY3wN0DrBt+NojvxkEgpUdOAxgAepqe
...
TbzKH/LEqZN8WVau3bf41yAx2YoaOsIJJtOUTYcfh14=
-----END RSA PUBLIC KEY-----
- PKCS#8格式公钥:
-----BEGIN PUBLIC KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALkFbpP0ZVpob96k
...
wgkm05RNhx+HXg==
-----END PUBLIC KEY-----
二、关于JSEncrypt
JSEncrypt是前端RSA加解密、密钥生成的一个实现方案,API也比较简单,更多请参考:JSEncrypt官网。
官网有个线上的Demo,可以生成非对称密钥,还有加解密。
Home页有相关的使用示例,还是很简单的。
// Create the encryption object and set the key.
var crypt = new JSEncrypt();
crypt.setKey(__YOUR_OPENSSL_PRIVATE_OR_PUBLIC_KEY__); //You can use also setPrivateKey and setPublicKey, they are both alias to setKey
//Eventhough the methods are called setPublicKey and setPrivateKey, remember
//that they are only alias to setKey, so you can pass them both a private or
//a public openssl key, just remember that setting a public key allows you to only encrypt.
var text = 'test';
// Encrypt the data with the public key.
var enc = crypt.encrypt(text);
// Now decrypt the crypted text with the private key.
var dec = crypt.decrypt(enc);
// Now a simple check to see if the round-trip worked.
if (dec === text)
alert('It works!!!');
else
alert('Something went wrong....');
备注:Download页有
JSEncrypt
官网demo的源代码。
三、关于jsrsasign
jsrsasign
是一个开源的纯javascript加密库,支持RSA/RSAPSS/ECDSA/DSA签名和验签,PKCS#1/5/8公私钥生成等等。
在Github上有两篇文章,一篇是关于如何使用Signature
类进行加签、验签的,一篇是关于jsrsasign
和Java互用性的,里面有通过Java加签,通过jsrsasign
验签的demo。
更多相关类和方法的使用和描述可以参考,jsrsasign API文档说明。
四、前端RSA加解密、加验签示例
1、相关依赖
// JSEncrypt
npm i jsencrypt
// jsrsasign
npm i jsrsasign
2、cryptoUtils工具类封装
import CryptoJS from "crypto-js";
import JSEncrypt from "jsencrypt";
import JsRsaSign from "jsrsasign";
/**
* RSA加密
* @param publicKey 公钥
* @param plainText 明文
* @returns * 密文
*/
export function encryptByRSA(publicKey, plainText)
const encryptor = new JSEncrypt();
encryptor.setPublicKey(publicKey);
return encryptor.encrypt(plainText);
/**
* RSA解密
* @param privateKey 私钥
* @param cipherText 密文
* @returns * 明文
*/
export function decryptByRSA(privateKey, cipherText)
const decrypter = new JSEncrypt();
decrypter.setPrivateKey(privateKey);
return decrypter.decrypt(cipherText);
/**
* 生成RSA密钥对,填充模式为PKCS8。
* 更多模式参考:<a href="https://kjur.github.io/jsrsasign/api/symbols/KEYUTIL.html">https://kjur.github.io/jsrsasign/api/symbols/KEYUTIL.html</a>
* @returns privateKey: (string|string|*), publicKey: (string|string|*)
*/
export function generateRsaKeyWithPKCS8()
const keyPair = JsRsaSign.KEYUTIL.generateKeypair("RSA", 1024);
const privateKey = JsRsaSign.KEYUTIL.getPEM(keyPair.prvKeyObj, "PKCS8PRV");
const publicKey = JsRsaSign.KEYUTIL.getPEM(keyPair.pubKeyObj);
return privateKey, publicKey ;
/**
* SHA256和RSA加签
* @param privateKey 私钥
* @param msg 加签内容
* @returns string Base64编码签名内容
*/
export function signBySHA256WithRSA(privateKey, msg)
const key = JsRsaSign.KEYUTIL.getKey(privateKey);
const signature = new JsRsaSign.KJUR.crypto.Signature(
alg: "SHA256withRSA",
);
signature.init(key);
signature.updateString(msg);
// 签名后的为16进制字符串,这里转换为16进制字符串
return JsRsaSign.hextob64(signature.sign());
/**
* SHA256和RSA验签
* @param publicKey 公钥:必须为标准pem格式。如果是PKCS1格式,必须包含-----BEGIN RSA PRIVATE KEY-----,如果是PKCS8格式,必须包含-----BEGIN PRIVATE KEY-----
* @param base64SignStr Base64编码签名字符串
* @param msg 原内容
* @returns boolean 是否验签通过
*/
export function verifyBySHA256WithRSA(publicKey, base64SignStr, msg)
const key = JsRsaSign.KEYUTIL.getKey(publicKey);
const signature = new JsRsaSign.KJUR.crypto.Signature(
alg: "SHA256withRSA",
);
signature.init(key);
signature.updateString(msg);
// 需要将Base64进制签名字符串转换成16进制字符串
return signature.verify(JsRsaSign.b64tohex(base64SignStr));
3、测试用例
先在前端测试下密钥对生成,RSA加解密、加验签,测试代码如下:
import * as CryptoUtils from '@/utils/cryptoUtils.js';
const privateKey, publicKey = CryptoUtils.generateRsaKeyWithPKCS8();
console.log(`生成的私钥为:\\n$privateKey`);
console.log(`生成的公钥为:\\n$publicKey`);
const cipherText = CryptoUtils.encryptByRSA(publicKey, "test");
console.log(`test加密后的内容为:\\n$cipherText`);
const plainText = CryptoUtils.decryptByRSA(privateKey, cipherText);
console.log(`解密后的内容为:\\n$plainText`);
const signature = CryptoUtils.signBySHA256WithRSA(privateKey, "test");
console.log(`生成的签名:\\n$signature`);
const isVerified = CryptoUtils.verifyBySHA256WithRSA(publicKey, signature, "test");
console.log(`是否验签通过:$isVerified`);
控制台输出结果为:
生成的私钥为:
-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMv1adNmKNw4rtr2
Dy92DZ3Nk1SCrGGetlq0sSgLY8bJsByAvP2ZOOiiWq2xDUVu8ZpQfM8v89tjegfr
sIUcN/ZVLc68Ric3BIto85oQc9jBz+aCk1rXeNMePkSVqNaC/n4kJ73Y41ZgKAe0
GvyqMyUNhk8VZd+QSNATSv+lGep3AgMBAAECgYBoKMvDry98z+HUZsb4iQSJK1xr
U1SvgftEtXSnq7Fn6sZquABMTry2aXt/qqTJadAu653hvW5/Av1mICKEyBV3aT4O
jQRGPMgp6WhXvQepUIuyi9qlfUVsJy/+J0zGKZeKsCFlwZ2e2j4Un7Bb//pgUfjJ
rbPtwC7U85oHjtJb6QJBAOdcm07ThSXFbicj2MuX9Gh7geMjncf6aqnrOwUFjO0d
5OxfYRAxrZD1GghygHyoJ4ZOHgJ0s6HVEYjg/u6DBdsCQQDhrb4IOVdSew2cW15f
t/5DAKUXRRQBfz0OxOs0Uv5k7zqI+YmysWVRGaZgj8oMZ7gYxN1eYNOKTwVjiuwb
uyaVAkEA0OGSMpPT1WsvbVT26bFyb1Z6yTihvif/XxPKgFknh/kCcsoWFwnS+1ne
vBusl181+BLVE0CL4aM9pogEghB3GwJAWJTVzmyTdfCO+xxyAqg5yRrrsiKPI7dJ
xA5PNA6PhBbSpwkrn1Q6LIcg4y4NZKkhfbdoHK9s2REDUHsrCgd/sQJAALEe+PCX
hcHWnwbm4kRFyJCO4dWkii7o28ohTRourlNsoEmiu1+7lt7PY1+C3D+6A4FFCY/H
pGM0i0lJue8rZA==
-----END PRIVATE KEY-----
生成的公钥为:
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL9WnTZijcOK7a9g8vdg2dzZNU
gqxhnrZatLEoC2PGybAcgLz9mTjoolqtsQ1FbvGaUHzPL/PbY3oH67CFHDf2VS3O
vEYnNwSLaPOaEHPYwc/mgpNa13jTHj5ElajWgv5+JCe92ONWYCgHtBr8qjMlDYZP
FWXfkEjQE0r/pRnqdwIDAQAB
-----END PUBLIC KEY-----
test加密后的内容为:
KjcaDKLnBbvxRzuKMysqoz9MHRXCUNIH67+XDiFGTJbM8Rjw4Cei0CzjAPjk2jgAR37Kgh6lX2+Xg8AI9wEmzWr08bt8i2FFxVMrcfOCs5zI1y+2T7G9034f5b0gNx/Pc4dDz+1k453vo0AhCC0vrtb1OfbsRu5oOFns0TqoAMY=
解密后的内容为:test
生成的签名:
t0koTqhiWmq/wEvI/ieJq5kZj7Dc/limF7GNVtHNLReqLVBXZvAZrOIwdqda7LBHBSHcRZBISWtbuyDiOR9KFPObrOgOEUOdfACUMzjWKCtO8ZgcQ+U02FyGeeH2rT9rJEJAXDEM+Kn3+H4ZdbrUFPY3jQRl535wnK9CLpxqAG4=
是否验签通过:true
备注:为什么在前端生成PKCS#8格式密钥呢?
因为在Java中非对称加解密、加验签都是用的PKCS#8,PKCS#1格式密钥需要转换成PKCS#8。
五、Java后端RSA加解密、加验签
1、CryptoUtils工具类封装
import com.universe.crypto.CryptoUtils.Algorithm.Encryption;
import com.universe.crypto.CryptoUtils.Algorithm.Signing;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 支持AES、DES、RSA加密、数字签名以及生成对称密钥和非对称密钥对
*/
public class CryptoUtils
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
private static final Encoder BASE64_ENCODER = Base64.getEncoder();
private static final Decoder BASE64_DECODER = Base64.getDecoder();
private static final Map<Algorithm, KeyFactory> KEY_FACTORY_CACHE = new ConcurrentHashMap<>();
private static final Map<Algorithm, Cipher> CIPHER_CACHE = new HashMap<>();
/**
* 生成对称密钥,目前支持的算法有AES、DES
* @param algorithm
* @return
* @throws NoSuchAlgorithmException
*/
public static String generateSymmetricKey(Algorithm algorithm) throws NoSuchAlgorithmException
KeyGenerator generator = KeyGenerator.getInstance(algorithm.getName());
generator.init(algorithm.getKeySize());
SecretKey secretKey = generator.generateKey();
return BASE64_ENCODER.encodeToString(secretKey.getEncoded());
/**
* 生成非对称密钥对,目前支持的算法有RSA、DSA。备注:默认生成的密钥格式为PKCS8
* @param algorithm
* @return
* @throws NoSuchAlgorithmException
*/
public static AsymmetricKeyPair generateAsymmetricKeyPair(Algorithm algorithm) throws NoSuchAlgorithmException
KeyPairGenerator generator = KeyPairGenerator.getInstance(algorithm.getName());
generator.initialize(algorithm.getKeySize());
KeyPair keyPair = generator.generateKeyPair();
String publicKey = BASE64_ENCODER.encodeToString(keyPair.getPublic().getEncoded());
String privateKey = BASE64_ENCODER.encodeToString(keyPair.getPrivate().getEncoded());
return new AsymmetricKeyPair(publicKey, privateKey);
public static String encryptByRSA(String publicKeyText, String plainText) throws Exception
return encryptAsymmetrically(publicKeyText, plainText, Encryption.RSA_ECB_PKCS1);
public static String decryptByRSA(String privateKeyText, String ciphertext) throws Exception
return decryptAsymmetrically(privateKeyText, ciphertext, Encryption.RSA_ECB_PKCS1);
/**
* SHA1签名算法和DSA加密算法结合使用生成数字签名
* @param privateKeyText
* @param msg
* @return 数字签名
* @throws Exception
*/
public static String signBySHA1WithDSA(String privateKeyText, String msg) throws Exception
return doSign(privateKeyText, msg, Encryption.DSA, Signing.SHA1WithDSA);
/**
* SHA1签名算法和RSA加密算法结合使用生成数字签名
* @param privateKeyText 私钥
* @param msg 待加签内容
* @return 数字签名
* @throws Exception
*/
public static String signBySHA1WithRSA(String privateKeyText, String msg) throws Exception
return doSign(privateKeyText, msg, Encryption.RSA_ECB_PKCS1, Signing.SHA1WithRSA);
/**
* SHA256签名算法和RSA加密算法结合使用生成数字签名
* @param privateKeyText 私钥
* @param msg 待加签内容
* @return 数字签名
* @throws Exception
*/
public static String signBySHA256WithRSA(String privateKeyText, String msg) throws Exception
return doSign(privateKeyText, msg, Encryption.RSA_ECB_PKCS1, Signing.SHA256WithRSA);
/**
* SHA1签名算法和DSA加密算法检验数字签名
* @param publicKeyText 公钥
* @param msg 待验签内容
* @param signatureText 数字
* @return 检验是否成功
* @throws Exception
*/
public static boolean verifyBySHA1WithDSA(String publicKeyText, String msg, String signatureText) throws Exception
return doVerify(publicKeyText, msg, signatureText, Encryption.DSA, Signing.SHA1WithDSA);
/**
* SHA1签名算法和RSA加密算法检验数字签名
* @param publicKeyText 公钥
* @param msg 待验签内容
* @param signatureText 签名
* @return 校验是否成功
* @throws Exception
*/
public static boolean verifyBySHA1WithRSA(String publicKeyText, String msg, String signatureText) throws Exception
return doVerify(publicKeyText, msg, signatureText, Encryption.RSA_ECB_PKCS1, Signing.SHA1WithRSA);
/**
* SHA256签名算法和RSA加密算法检验数字签名
* @param publicKeyText 公钥
* @param msg 待验签内容
* @param signatureText 签名
* @return 校验是否成功
* @throws Exception
*/
public static boolean verifyBySHA256WithRSA(String publicKeyText, String msg, String signatureText) throws Exception
return doVerify(publicKeyText, msg, signatureText, Encryption.RSA_ECB_PKCS1, Signing.SHA256WithRSA);
/**
* 对称加密
* @param secretKey 密钥
* @param iv 加密向量,只有CBC模式才支持,如果是CBC则必传
* @param plainText 明文
* @param algorithm 对称加密算法,如AES、DES
* @return
* @throws Exception
*/
public static String encryptSymmetrically(String secretKey, String iv, String plainText, Algorithm algorithm) throws Exception
SecretKey key = decodeSymmetricKey(secretKey, algorithm);
IvParameterSpec ivParameterSpec = StringUtils.isBlank(iv) ? null : decodeIv(iv);
byte[] plainTextInBytes = plainText.getBytes(DEFAULT_CHARSET);
byte[] ciphertextInBytes = transform(algorithm, Cipher.ENCRYPT_MODE, key, ivParameterSpec, plainTextInBytes);
return BASE64_ENCODER.encodeToString(ciphertextInBytes);
/**
* 对称解密
* @param secretKey 密钥
* @param iv 加密向量,只有CBC模式才支持,如果是CBC则必传
* @param ciphertext 密文
* @param algorithm 对称加密算法,如AES、DES
* @return
* @throws Exception
*/
public static String decryptSymmetrically(String secretKey, String iv, String ciphertext, Algorithm algorithm) throws Exception
SecretKey key = decodeSymmetricKey(secretKey, algorithm);
IvParameterSpec ivParameterSpec = StringUtils.isBlank(iv) ? null : decodeIv(iv);
byte[] ciphertextInBytes = BASE64_DECODER.decode(ciphertext);
byte<RSA加密解密及RSA加签验签
RSA安全性应用场景说明
在刚接触RSA的时候,会混淆RSA加密解密和RSA加签验签的概念。简单来说加密解密是公钥加密私钥解密,持有公钥(多人持有)可以对数据加密,但是只有持有私钥(一人持有)才可以解密并查看数据;加签验签是私钥加签公钥验签,持有私钥(一人持有)可以加签,持有公钥(多人持有)可以验签。
在金融行业在设计到数据交互传输的时候,需要考虑数据的安全性问题。下文通过介绍RSA的加密和加签两个特性,说明RSA加密技术在保障数据传输过程中的安全性以及实现数据的防篡改和防否机制的应用场景及代码实现。
RSA在数据安全上的应用
加密:数据传输的安全性
RSA加密解密主要应用于数据传输的安全性上。通过公钥加密、私钥解密的方式,可以达到公钥持有者能够加密数据,但只有私钥持有者才能够解密数据。通过加密传输数据能够避免被第三方截取者轻易的拦截数据,而通过RSA非对称加密的特性也能够较好的保证第三方截取者在没有私钥的情况下读取数据内容。
加签:数据的防篡改、防否认机制
数据的的防否认机制是指数据的接收者可以明确的知道数据的发送人。RSA的数字签名技术是将摘要信息用发送者的私钥(有且只有一人持有)加密,与原文一起传送给接收者。接收者只有用发送者的公钥才能解密被加密的摘要信息,然后用HASH函数对收到的原文产生一个摘要信息,与解密的摘要信息对比。如果相同,则说明数据是有持有私钥的发送者发出的(其他人没有私钥,无法生成一致的摘要信息)。因此在此基础上可以认为有且只有一个私钥的持有者才可以加签,达到数据的防止篡改防否认机制。
RSA数据安全传输的实现
基于RSA的加密和验签功能,在对于数据传输安全性要求较高的交互应用中,可以对原始数据进行加密加签。具体流程如下图↓
示例工程
GitHub项目地址:https://github.com/suyin58/rsa-demo
主要代码
package com.wjs.rsaDemo;
import java.io.ByteArrayOutputStream;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
/**
* RSA安全编码组件
*/
public abstract class RSAUtil{
public static final String KEY_ALGORITHM = "RSA";
public static final String SIGNATURE_ALGORITHM = "SHA1WithRSA";
private static final String PUBLIC_KEY = "RSAPublicKey";
private static final String PRIVATE_KEY = "RSAPrivateKey";
/**
* 用私钥对信息生成数字签名
*
* @param data
* 加密数据
* @param privateKey
* Base64编码格式的私钥
*
* @return 经过Base64编码的字符串
* @throws Exception
*/
public static String sign(byte[] data, String privateKey) throws Exception {
// 解密由base64编码的私钥
byte[] keyBytes = HashUtil.decryptBASE64(privateKey);
// 构造PKCS8EncodedKeySpec对象
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
// KEY_ALGORITHM 指定的加密算法
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
// 取私钥匙对象
PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);
// 用私钥对信息生成数字签名
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(priKey);
signature.update(data);
return HashUtil.encryptBASE64(signature.sign());
}
/**
* 校验数字签名
*
* @param data
* 加密数据
* @param publicKey
* 公钥
* @param sign
* 数字签名
*
* @return 校验成功返回true 失败返回false
* @throws Exception
*
*/
public static boolean verify(byte[] data, String publicKey, String sign)
throws Exception {
// 解密由base64编码的公钥
byte[] keyBytes = HashUtil.decryptBASE64(publicKey);
// 构造X509EncodedKeySpec对象
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
// KEY_ALGORITHM 指定的加密算法
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
// 取公钥匙对象
PublicKey pubKey = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(pubKey);
signature.update(data);
// 验证签名是否正常
return signature.verify(HashUtil.decryptBASE64(sign));
}
/**
* 解密<br>
* 用私钥解密
*
* @param data
* @param key Base64编码格式的私钥
* @return
* @throws Exception
*/
public static byte[] decryptByPrivateKey(byte[] data, String key)
throws Exception {
byte[] decryptedData = null;
// 对密钥解密
byte[] keyBytes = HashUtil.decryptBASE64(key);
// 取得私钥
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
// 对数据解密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
int maxDecryptBlockSize = getMaxDencryptBytesByPrivate(keyFactory, privateKey);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try {
int dataLength = data.length;
for (int i = 0; i < dataLength; i += maxDecryptBlockSize) {
int decryptLength = dataLength - i < maxDecryptBlockSize ? dataLength - i : maxDecryptBlockSize;
byte[] doFinal = cipher.doFinal(data, i, decryptLength);
bout.write(doFinal);
}
decryptedData = bout.toByteArray();
} finally {
if (bout != null) {
bout.close();
}
}
return decryptedData;
}
/**
* 将Base64编码的密文解密为字符串
* @param base64Str
* @param key
* @return
* @throws Exception
* @author renteng
* @date 2015年12月24日 下午12:35:34
*/
public static String decryptByPrivateKeyToString(String base64Str, String key) throws Exception{
byte[] data = HashUtil.decryptBASE64(base64Str);
byte[] oriData = decryptByPrivateKey(data, key);
return new String(oriData);
}
/**
* 获取公钥加密可加密的最大数据字节长度
* @param keyFactory
* @param key
* @return
*/
private static int getMaxEncryptBytesByPublicKey(KeyFactory keyFactory, Key key){
return getPublicKeyBitLength(keyFactory, key) / 8 - 11;
}
/**
* 获取公钥解密每块的字节长度
* @param keyFactory
* @param key
* @return
*/
private static int getMaxDencryptBytesByPrivate(KeyFactory keyFactory, Key key){
return getPrivateKeyBitLength(keyFactory, key) / 8;
}
/**
* 获取公钥加密可加密的最大数据字节长度
* @param keyFactory
* @param key
* @return
*/
private static int getMaxEncryptBytesByPrivate(KeyFactory keyFactory, Key key){
return getPrivateKeyBitLength(keyFactory, key) / 8 - 11;
}
/**
* 获取公钥解密每块的字节长度
* @param keyFactory
* @param key
* @return
*/
private static int getMaxDencryptBytesByPublicKey(KeyFactory keyFactory, Key key){
return getPublicKeyBitLength(keyFactory, key) / 8;
}
/**
* 获取公钥的字节长度
* @param keyFactory
* @param key
* @return
*/
private static int getPublicKeyBitLength(KeyFactory keyFactory, Key key){
try {
RSAPublicKeySpec publicKeySpec = keyFactory.getKeySpec(key, RSAPublicKeySpec.class);
return publicKeySpec.getModulus().bitLength();
} catch (Exception e) {
}
return 2048;
}
/**
* 获取私钥的字节长度
* @param keyFactory
* @param key
* @return
*/
private static int getPrivateKeyBitLength(KeyFactory keyFactory, Key key){
try {
RSAPrivateKeySpec publicKeySpec = keyFactory.getKeySpec(key, RSAPrivateKeySpec.class);
return publicKeySpec.getModulus().bitLength();
} catch (Exception e) {
}
return 2048;
}
/**
* 解密<br>
* 用公钥解密
*
* @param data
* @param key Base64编码格式的公钥
* @return
* @throws Exception
*/
public static byte[] decryptByPublicKey(byte[] data, String key)
throws Exception {
byte[] decryptedData = null;
// 对密钥解密
byte[] keyBytes = HashUtil.decryptBASE64(key);
// 取得公钥
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key publicKey = keyFactory.generatePublic(x509KeySpec);
// 对数据解密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, publicKey);
int maxDecryptBlockSize = getMaxDencryptBytesByPublicKey(keyFactory, publicKey);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try {
int dataLength = data.length;
for (int i = 0; i < dataLength; i += maxDecryptBlockSize) {
int decryptLength = dataLength - i < maxDecryptBlockSize ? dataLength - i
: maxDecryptBlockSize;
byte[] doFinal = cipher.doFinal(data, i, decryptLength);
bout.write(doFinal);
}
decryptedData = bout.toByteArray();
} finally {
if (bout != null) {
bout.close();
}
}
return decryptedData;
}
/**
* 加密<br>
* 用公钥加密
*
* @param data
* @param key Base64编码格式的公钥
* @return
* @throws Exception
*/
public static byte[] encryptByPublicKey(byte[] data, String key)
throws Exception {
byte[] encryptedData = null;
// 对公钥解密
byte[] keyBytes = HashUtil.decryptBASE64(key);
// 取得公钥
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key publicKey = keyFactory.generatePublic(x509KeySpec);
// 对数据加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
int maxEncryptBlockSize = getMaxEncryptBytesByPublicKey(keyFactory, publicKey);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try {
int dataLength = data.length;
for (int i = 0; i < data.length; i += maxEncryptBlockSize) {
int encryptLength = dataLength - i < maxEncryptBlockSize ? dataLength - i
: maxEncryptBlockSize;
byte[] doFinal = cipher.doFinal(data, i, encryptLength);
bout.write(doFinal);
}
encryptedData = bout.toByteArray();
} finally {
if (bout != null) {
bout.close();
}
}
return encryptedData;
}
/**
* 加密<br>
* 用私钥加密
*
* @param data 密文二进制数据
* @param key BASE64编码的私钥字符串
* @return
* @throws Exception
*/
public static byte[] encryptByPrivateKey(byte[] data, String key)
throws Exception {
byte[] encryptedData = null;
// 对密钥解密
byte[] keyBytes = HashUtil.decryptBASE64(key);
// 取得私钥
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
// 对数据加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
int maxEncryptBlockSize = getMaxEncryptBytesByPrivate(keyFactory, privateKey);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try {
int dataLength = data.length;
for (int i = 0; i < data.length; i += maxEncryptBlockSize) {
int encryptLength = dataLength - i < maxEncryptBlockSize ? dataLength - i
: maxEncryptBlockSize;
byte[] doFinal = cipher.doFinal(data, i, encryptLength);
bout.write(doFinal);
}
encryptedData = bout.toByteArray();
} finally {
if (bout != null) {
bout.close();
}
}
return encryptedData;
}
/**
* 取得私钥
*
* @param keyMap
* @return
* @throws Exception
*/
public static String getPrivateKey(Map<String, Object> keyMap)
throws Exception {
Key key = (Key) keyMap.get(PRIVATE_KEY);
return HashUtil.encryptBASE64(key.getEncoded());
}
/**
* 取得公钥
*
* @param keyMap
* @return
* @throws Exception
*/
public static String getPublicKey(Map<String, Object> keyMap)
throws Exception {
Key key = (Key) keyMap.get(PUBLIC_KEY);
return HashUtil.encryptBASE64(key.getEncoded());
}
/**
* 初始化密钥
*
* @return
* @throws Exception
*/
public static Map<String, Object> initKey() throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator
.getInstance(KEY_ALGORITHM);
keyPairGen.initialize(1024);
KeyPair keyPair = keyPairGen.generateKeyPair();
// 公钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
// 私钥
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
Map<String, Object> keyMap = new HashMap<String, Object>(2);
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return keyMap;
}
}
RSAutil
package com.wjs.rsaDemo;
import java.security.MessageDigest;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
/**
* 基础加密组件
*/
public abstract class HashUtil {
public static final String KEY_SHA = "SHA";
public static final String KEY_SHA_256 = "SHA-256";
public static final String KEY_MD5 = "MD5";
/**
* MAC算法可选以下多种算法
*
* <pre>
* HmacMD5
* HmacSHA1
* HmacSHA256
* HmacSHA384
* HmacSHA512
* </pre>
*/
public static final String KEY_MAC = "HmacMD5";
/**
* BASE64解密
*
* @param key
* @return
* @throws Exception
*/
public static byte[] decryptBASE64(String key) throws Exception {
return Base64.decodeBase64(key);
}
/**
* BASE64加密
*
* @param key
* @return
* @throws Exception
*/
public static String encryptBASE64(byte[] key) throws Exception {
return Base64.encodeBase64String(key);
}
/**
* MD5加密
*
* @param data
* @return
* @throws Exception
*/
public static byte[] encryptMD5(byte[] data) throws Exception {
MessageDigest md5 = MessageDigest.getInstance(KEY_MD5);
md5.update(data);
return md5.digest();
}
/**
* SHA加密
*
* @param data
* @return
* @throws Exception
*/
public static byte[] encryptSHA(byte[] data) throws Exception {
MessageDigest sha = MessageDigest.getInstance(KEY_SHA);
sha.update(data);
return sha.digest();
}
/**
* SHA-256加密
*
* @param data 待hash的二进制数据
* @return
* @throws Exception
*/
public static byte[] encryptSHA256(byte[] data) throws Exception {
MessageDigest sha = MessageDigest.getInstance(KEY_SHA_256);
sha.update(data);
return sha.digest();
}
/**
* 初始化HMAC密钥
*
* @return
* @throws Exception
*/
public static String initMacKey() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_MAC);
SecretKey secretKey = keyGenerator.generateKey();
return encryptBASE64(secretKey.getEncoded());
}
/**
* HMAC加密
*
* @param data
* @param key
* @return
* @throws Exception
*/
public static byte[] encryptHMAC(byte[] data, String key) throws Exception {
SecretKey secretKey = new SecretKeySpec(decryptBASE64(key), KEY_MAC);
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
return mac.doFinal(data);
}
}
HashUtil
package com.wjs.rsaDemo;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
public class RsaDemo {
/**
* 加签用公钥
*/
public static String signPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3GFaW5S8GI7ejxhnVjhC6is3k5ZXY+eHK7hV42W7aSPuQ1dg72XZ2D/cLIdv4wNf8H3vf0e2O0YNwuwst6rD/BWey0yBUnToTm6xsJg5dCMYNQCocgtDrBTgHYSkZY/eno0MMn1KdRN0ILvAvz4BmENOkfuD3TEHgzXZS+prDZOKIHfW0HUYNEGk3LQC6VKQawKY+QO7k188wokV85FzmYFvjkkOE0UHuFL/p2zGnwVv+ZHq34TzZQaQNnO3/INQqxi+HiPfpD2oTJDY1+cAqukCD46hM+4qPx4t6A1fe+Tr8GE4DeGW3+MIqcuCs79cGj1Xtca8AoGCqK/Nx2Y0JwIDAQAB";
/**
* 加签用私钥
*/
public static String signPrivateKey = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDcYVpblLwYjt6PGGdWOELqKzeTlldj54cruFXjZbtpI+5DV2DvZdnYP9wsh2/jA1/wfe9/R7Y7Rg3C7Cy3qsP8FZ7LTIFSdOhObrGwmDl0Ixg1AKhyC0OsFOAdhKRlj96ejQwyfUp1E3Qgu8C/PgGYQ06R+4PdMQeDNdlL6msNk4ogd9bQdRg0QaTctALpUpBrApj5A7uTXzzCiRXzkXOZgW+OSQ4TRQe4Uv+nbMafBW/5kerfhPNlBpA2c7f8g1CrGL4eI9+kPahMkNjX5wCq6QIPjqEz7io/Hi3oDV975OvwYTgN4Zbf4wipy4Kzv1waPVe1xrwCgYKor83HZjQnAgMBAAECggEAJWZYIUaijUBhwMMRdm5h3L+s1N0kw42dQOwtl0PChFtWqhMAHmCYkbx0rxHlCQ+fjn6w0FbpNDH1T+koxZqzW+qHYlT/dXDlo7nhaejLh0wVZZlQ/NmwiFmalyfVhm7eBuZE9aSRqEC+6ncyhMIPHzn88YVPoZAaiEfxMpL7y/e3UAXfnzMFXqVi2ihMbtu6w1FUMgnWjy8WqqwgoTFVdR6GjWUROAtfTUwZe6Fw+KVm2+KKgMWPPE2SPxXp3q0T6AwI6Eg1RbIjUYUsl/9DkUdXJBt9G6eaNbY7WkeRrN8HKMCA1zOIv2sJWEnHkg2N6FgTHkSWIBXjiJ5vsphXUQKBgQDvcd0+J0PT1qAphgg3xTjQBim7MOsFG2VvQH0r72Fj4qy2BOsRyuotBJBQlgyq/csyduHew/068qWCCQb534dTvVtOXXQ1rSvyIM/UjFM0/5BExtKluz1zm2tQt0KDEOyZ1OnbGryuV8Ap8ftAfy1XkiVeqo1E9T6ej1kRzopABQKBgQDrngzRKsQPKDaCHb7Wn4QxYq5u8bYXn/EymynOjJcGxeJ/KotFCcemWFlWjIsi/k0sCJ3yWATARqCd7eSYcW/AmNpe2b/g4w699WvlSWq+E5hCA/GVwQpwRJSYN/PhDqdkBRngJ/AD8lf6CEGxG3SaMYCfXCkdFYwYYgdRGfUXOwKBgA3ERiwkpcmwNVUt15sdQ77yG8Qfc+O/R321/3xfLwJHLhbpAXrsZ7pe4M1BU0khfmVQYHwmWJDjEpD/Y99J8sXlxTIkPWI4qqYpLMnTp5UMfIb3x3Sv50CWVv01DCXs+y19CFUInICJmwrOVtvGdBzs0ik3NRgZ4ZfMNhrH/TrhAoGAB55BndW7Jx5OvOBHVlssBAjDyRSJpbPnMZKwxFvpWi+1xhTTEfVh/i/nG5RJv2Tni9/vc3GDHdBqyxBxDrjEOz71+JEj0hqlVGEGDxDTobeyeZf1DLmEI+MjxtQwT3uQz/wWPRgte4MvcwcnUJmpqH6nQP/S2Hzk3bj1sZqcQRcCgYBG4JCI7b1qJnvOPyP4Eqt6GcZ3OpbwkwKkiYQ2lwgn+wHT/7l3HfpTpBKZFMSknrbD11MzE2Q6niP6luG+HZPBsLyEQduipqaFe/GLmTGjljnj2wG3981sQluyrNlNPbDrTQKQERkcy6v8L6NHnIsYrUT/uHC6UJTqVYWjk1WVeQ==";
/**
* 加密用私钥
*/
private static String encryptPrivateKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCd4FMhHvZGRBAMuRqn0ZmvvNFBkYyGsf9NOaS+CMAgHB4ib+ciDE3lCfYZHhi0vV2iMMBIanwaNev4c0uu4WyeFLQkMAUbgW/iIo+pR8xocjo9RPsTxN0IOL4KpSeTLM2MQZKnyffBugoIt15mZzzij6lcXy2CI/2wcNKg4Nw6v1iZ8Et+wEgqlPVeASaTAFSjmJNQn4aIy8Fpzll8ADP9neUQCMOWG87lDbU9RCs0YqAFfo61MyYj+KbIvhWwyycZJIrKFwKsEn4tViB5UFegRP4DgSjEzq/glvnwgVNBDiYFOKZ8lqkS+bfhHBA+w+X+fJ2CTalKm3+NZTHYeX5DAgMBAAECggEAf9p1N/NtGkZwgP0+2v1havKMvH70wPhReubdxZAsl1RuCxF4qxgv1PaWOI0pEOXyeDDm5z5lNozIhrJIbl3cqsC1ikDhQf827nlywnKE1Wj8RTYh50acgdYCAXjybbvw0k8gR4XGgTr6eUiWyHN+2TPiwg3KOwSOpF8aFHNFpsSbwrOg7xlzNov6M28B/godUpGgPv0PuN7M6DMOi18QiwWSPa1ycnBt7tWZkyEl/vMdAV0be2RfY93Lvzgp8a/mI+A/c8hcBJj5vJDwJR3CnqKR5RceOPrA4eT2zXJOcR43DfWPo7Q+7Buii1ERoSF3Um4t5JpSr/2j2xHdJGmRMQKBgQDfODepUuiKMW5IPVdkdF62oDmF/CLkizbDsAPXeG6tWPZUYX7cHkoEuPY0LxACIcM3ZnZyCUBtIDMk7bLEIw3wuXqh+1hVQrS5VCosbVq6LxcT5MMQouMxRtHSScdIcsGUYZSIVjlXuQdDXzrKhg3LSTLdWdYAb4pIygHb0UbsxwKBgQC1D5Izs3UGFXpySMdeDo8eF4TkmQqGlYI1rEkZxHNcSH9BGTJH3EmuAb5/8UxSDWX7GPpLJeexSVHrfADYl4CmYpDITpihhaB+t68b9yHY7EwO/gF2QcTvvwIIfWJmjPyCJTwT6wRtmv5WQnt7tt0ObZq0tovJVdn8cbpyVLAOpQKBgQCaTWs0siorNSZN65FY0JSUW8fH1dZs88sElMzjCs4/KCsHg2nFUW7LOux+gDXps1sWFc803y5ZARQ5p9KWgMDnMeASzwNt1LHHFuYcVe+MmnayesVY37B7ZMAwRG3sp98m6hlZ8XisKixaJx8l1mr8pnnxx2MGZBRMYs/MGyuTCwKBgALxOtX+P5OWu8OprRu5Ltg1V6KDXilruo72usVhbOJ+BxtetnN2f/gE7TyVBkF7GEIpWL/p4Mb/wwYJoNXkOGH7zhCDPnW5fy8v+veAX5tv05iWxh1O2k1vFDBhIT07Y0sWIdDNC+hgEWwDbpBHG3aFj3MKWGEwNPemPXpoJ+hFAoGBAJioGALh7yp4bpHTdwrGtSWm8TdXBx8nC8rFNI9SUtSopafN2eHLwZ6PnVvN6kgCIlQ7HbxQruuGKiKd9UDTbEbFIaidUAoyzZm7/cYc0YJRVXnQNLiiz758VHtx1YEdscMXj10X4NSK8sRMjeWQhT1v5PaNafG65oSkuyFrIlPk";
/**
* 加密用公钥
*/
public static String encryptPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAneBTIR72RkQQDLkap9GZr7zRQZGMhrH/TTmkvgjAIBweIm/nIgxN5Qn2GR4YtL1dojDASGp8GjXr+HNLruFsnhS0JDAFG4Fv4iKPqUfMaHI6PUT7E8TdCDi+CqUnkyzNjEGSp8n3wboKCLdeZmc84o+pXF8tgiP9sHDSoODcOr9YmfBLfsBIKpT1XgEmkwBUo5iTUJ+GiMvBac5ZfAAz/Z3lEAjDlhvO5Q21PUQrNGKgBX6OtTMmI/imyL4VsMsnGSSKyhcCrBJ+LVYgeVBXoET+A4EoxM6v4Jb58IFTQQ4mBTimfJapEvm34RwQPsPl/nydgk2pSpt/jWUx2Hl+QwIDAQAB";
public static void main(String[] args) {
// 组装数据
encode("加签数据");
// 解签
System.out.println("解签后数据---->" + decode());
}
// 参数容器
static Map<String, String> paramMap = new HashMap<String, String>();
private static String charset = "utf-8";
public static void encode(String bizParam) {
try {
// 使用网金社公钥对业务参数进行加密
String bizParamEncrypt = HashUtil
.encryptBASE64(RSAUtil.encryptByPublicKey(bizParam.getBytes(charset), encryptPublicKey));
paramMap.put("bizParams", URLEncoder.encode(bizParamEncrypt, charset));
// 计算并组装签名
String sign = RSAUtil.sign(bizParam.getBytes(charset), signPrivateKey);
paramMap.put("sign", URLEncoder.encode(sign, charset));
} catch (Exception e) {
e.printStackTrace();
}
}
public static String decode() {
// 解密参数
String bizParams = paramMap.get("bizParams");
try {
bizParams = URLDecoder.decode(bizParams, charset);
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
String bizParamsJson = null;
try {
bizParamsJson = new String(
RSAUtil.decryptByPrivateKey(HashUtil.decryptBASE64(bizParams), encryptPrivateKey));
} catch (Exception e) {
e.printStackTrace();
}
// 验签
checkSign(bizParamsJson, signPublicKey, paramMap.get("sign"));
return bizParamsJson;
}
private static void checkSign(String data, String publicKey, String sign) {
boolean verify = false;
try {
sign = URLDecoder.decode(sign, charset);
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
try {
verify = RSAUtil.verify(data.getBytes(charset), publicKey, sign);
} catch (Exception e) {
e.getMessage();
}
if (!verify) {
System.err.println("验签失败");
} else {
System.out.println("以上是关于前后端RSA互相加解密加签验签密钥对生成(Java)的主要内容,如果未能解决你的问题,请参考以下文章