当前位置: 首页 > news >正文

前后端RSA互相加解密、加签验签、密钥对生成(Java)

目录

  • 一、序言
  • 二、关于PKCS#1和PKCS#8格式密钥
    • 1、简介
    • 2、区别
  • 二、关于JSEncrypt
  • 三、关于jsrsasign
  • 四、前端RSA加解密、加验签示例
    • 1、相关依赖
    • 2、cryptoUtils工具类封装
    • 3、测试用例
  • 五、Java后端RSA加解密、加验签
    • 1、CryptoUtils工具类封装
    • 2、测试用例
  • 六、前后端加解密、加验签交互测试
    • 1、前端加密,后端解密
      • (1) 前端代码
      • (2) 后端代码
    • 2、后端加密,前端解密
      • (1) 后端代码
      • (2) 前端代码
    • 3、前端加签,后端验签
      • (1) 前端代码
      • (2) 后端代码
    • 4、后端加签,前端验签
      • (1) 后端代码
      • (2) 前端代码

一、序言

最近有一些安全性要求比较高的场景,我们提供API给第三方商户用于收单,其中有几个功能是绑卡、ATM/POS密码变更。

出于合规和监管要求,第三方商户不能保存卡号、CVV、密码等敏感信息,所以相关的敏感操作必须在接口提供方H5页面中完成。为了最大程度保证安全性,我们决定对敏感信息进行加密传输,同时对请求参数进行加签处理。

由于前端并不需要解密操作,最终我们选择RSA非对称加密,前端这块主要采用jsencrypt进行加解密,jsrsasign用来生成密钥对、加签验签。



二、关于PKCS#1和PKCS#8格式密钥

由于Java非对称加解密、加验签都是采用PKCS#8格式的密钥,PKCS#1格式的密钥跑不通,这里先简单介绍一下两者的区别。

1、简介

PKCS#1PKCS#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#8PKCS#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[] plainTextInBytes = transform(algorithm, Cipher.DECRYPT_MODE, key, ivParameterSpec, ciphertextInBytes);return new String(plainTextInBytes, DEFAULT_CHARSET);}/*** 非对称加密* @param publicKeyText 公钥* @param plainText 明文* @param algorithm 非对称加密算法* @return* @throws Exception*/public static String encryptAsymmetrically(String publicKeyText, String plainText, Algorithm algorithm) throws Exception {PublicKey publicKey = regeneratePublicKey(publicKeyText, algorithm);byte[] plainTextInBytes = plainText.getBytes(DEFAULT_CHARSET);byte[] ciphertextInBytes = transform(algorithm, Cipher.ENCRYPT_MODE, publicKey, plainTextInBytes);return BASE64_ENCODER.encodeToString(ciphertextInBytes);}/*** 非对称解密* @param privateKeyText 私钥* @param ciphertext 密文* @param algorithm 非对称加密算法* @return* @throws Exception*/public static String decryptAsymmetrically(String privateKeyText, String ciphertext, Algorithm algorithm) throws Exception {PrivateKey privateKey = regeneratePrivateKey(privateKeyText, algorithm);byte[] ciphertextInBytes = BASE64_DECODER.decode(ciphertext);byte[] plainTextInBytes = transform(algorithm, Cipher.DECRYPT_MODE, privateKey, ciphertextInBytes);return new String(plainTextInBytes, DEFAULT_CHARSET);}/*** 生成数字签名* @param privateKeyText 私钥* @param msg 传输的数据* @param encryptionAlgorithm 加密算法,见Algorithm中的加密算法* @param signatureAlgorithm 签名算法,见Algorithm中的签名算法* @return 数字签名* @throws Exception*/public static String doSign(String privateKeyText, String msg, Algorithm encryptionAlgorithm, Algorithm signatureAlgorithm)throws Exception {PrivateKey privateKey = regeneratePrivateKey(privateKeyText, encryptionAlgorithm);// Signature只支持签名算法Signature signature = Signature.getInstance(signatureAlgorithm.getName());signature.initSign(privateKey);signature.update(msg.getBytes(DEFAULT_CHARSET));byte[] signatureInBytes = signature.sign();return BASE64_ENCODER.encodeToString(signatureInBytes);}/*** 数字签名验证* @param publicKeyText 公钥* @param msg 传输的数据* @param signatureText 数字签名* @param encryptionAlgorithm 加密算法,见Algorithm中的加密算法* @param signatureAlgorithm 签名算法,见Algorithm中的签名算法* @return 校验是否成功* @throws Exception*/public static boolean doVerify(String publicKeyText, String msg, String signatureText, Algorithm encryptionAlgorithm,Algorithm signatureAlgorithm) throws Exception {PublicKey publicKey = regeneratePublicKey(publicKeyText, encryptionAlgorithm);Signature signature = Signature.getInstance(signatureAlgorithm.getName());signature.initVerify(publicKey);signature.update(msg.getBytes(DEFAULT_CHARSET));return signature.verify(BASE64_DECODER.decode(signatureText));}/*** 将密钥进行Base64位解码,重新生成SecretKey实例* @param secretKey 密钥* @param algorithm 算法* @return*/private static SecretKey decodeSymmetricKey(String secretKey, Algorithm algorithm) {byte[] key = BASE64_DECODER.decode(secretKey);return new SecretKeySpec(key, algorithm.getName());}private static IvParameterSpec decodeIv(String iv) {byte[] ivInBytes = BASE64_DECODER.decode(iv);return new IvParameterSpec(ivInBytes);}private static PublicKey regeneratePublicKey(String publicKeyText, Algorithm algorithm)throws NoSuchAlgorithmException, InvalidKeySpecException {byte[] keyInBytes = BASE64_DECODER.decode(publicKeyText);KeyFactory keyFactory = getKeyFactory(algorithm);// 公钥必须使用RSAPublicKeySpec或者X509EncodedKeySpecKeySpec publicKeySpec = new X509EncodedKeySpec(keyInBytes);PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);return publicKey;}private static PrivateKey regeneratePrivateKey(String key, Algorithm algorithm) throws Exception {byte[] keyInBytes = BASE64_DECODER.decode(key);KeyFactory keyFactory = getKeyFactory(algorithm);// 私钥必须使用RSAPrivateCrtKeySpec或者PKCS8EncodedKeySpecKeySpec privateKeySpec = new PKCS8EncodedKeySpec(keyInBytes);PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);return privateKey;}private static KeyFactory getKeyFactory(Algorithm algorithm) throws NoSuchAlgorithmException {KeyFactory keyFactory = KEY_FACTORY_CACHE.get(algorithm);if (keyFactory == null) {keyFactory = KeyFactory.getInstance(algorithm.getName());KEY_FACTORY_CACHE.put(algorithm, keyFactory);}return keyFactory;}private static byte[] transform(Algorithm algorithm, int mode, Key key, byte[] msg) throws Exception {return transform(algorithm, mode, key, null, msg);}private static byte[] transform(Algorithm algorithm, int mode, Key key, IvParameterSpec iv, byte[] msg) throws Exception {Cipher cipher = CIPHER_CACHE.get(algorithm);// double check,减少上下文切换if (cipher == null) {synchronized (CryptoUtils.class) {if ((cipher = CIPHER_CACHE.get(algorithm)) == null) {cipher = determineWhichCipherToUse(algorithm);CIPHER_CACHE.put(algorithm, cipher);}cipher.init(mode, key, iv);return cipher.doFinal(msg);}}synchronized (CryptoUtils.class) {cipher.init(mode, key, iv);return cipher.doFinal(msg);}}private static Cipher determineWhichCipherToUse(Algorithm algorithm) throws NoSuchAlgorithmException, NoSuchPaddingException {Cipher cipher;String transformation = algorithm.getTransformation();// 官方推荐的transformation使用algorithm/mode/padding组合,SunJCE使用ECB作为默认模式,使用PKCS5Padding作为默认填充if (StringUtils.isNotEmpty(transformation)) {cipher = Cipher.getInstance(transformation);} else {cipher = Cipher.getInstance(algorithm.getName());}return cipher;}/*** 算法分为加密算法和签名算法,更多算法实现见:<br/>* <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#impl">jdk8中的标准算法</a>*/public static class Algorithm {public interface Encryption {Algorithm AES_ECB_PKCS5 = new Algorithm("AES", "AES/ECB/PKCS5Padding", 128);Algorithm AES_CBC_PKCS5 = new Algorithm("AES", "AES/CBC/PKCS5Padding", 128);Algorithm DES_ECB_PKCS5 = new Algorithm("DES", "DES/ECB/PKCS5Padding", 56);Algorithm DES_CBC_PKCS5 = new Algorithm("DES", "DES/CBC/PKCS5Padding", 56);Algorithm RSA_ECB_PKCS1 = new Algorithm("RSA", "RSA/ECB/PKCS1Padding", 1024);Algorithm DSA = new Algorithm("DSA", 1024);}public interface Signing {Algorithm SHA1WithDSA = new Algorithm("SHA1withDSA", 1024);Algorithm SHA1WithRSA = new Algorithm("SHA1WithRSA", 2048);Algorithm SHA256WithRSA = new Algorithm("SHA256WithRSA", 2048);}@Getterprivate String name;@Getterprivate String transformation;@Getterprivate int keySize;public Algorithm(String name, int keySize) {this(name, null, keySize);}public Algorithm(String name, String transformation, int keySize) {this.name = name;this.transformation = transformation;this.keySize = keySize;}}@Data@NoArgsConstructor@AllArgsConstructorpublic static class AsymmetricKeyPair {private String publicKey;private String privateKey;}}

2、测试用例

AsymmetricKeyPair keyPair = CryptoUtils.generateAsymmetricKeyPair(Encryption.RSA_ECB_PKCS1);
String privateKey = keyPair.getPrivateKey();
String publicKey = keyPair.getPublicKey();
System.out.println("生成的私钥为:\n" + privateKey);
System.out.println("生成的公钥为:\n" + publicKey);String cipherText = CryptoUtils.encryptByRSA(publicKey, "test");
String plainText = CryptoUtils.decryptByRSA(privateKey, cipherText);
System.out.println("test加密后的密文为:\n" + cipherText);
System.out.println("解密后的明文为:" + plainText);String signature = CryptoUtils.signBySHA256WithRSA(privateKey, "message");
boolean isVerified = CryptoUtils.verifyBySHA256WithRSA(publicKey, "message", signature);
System.out.println("message加签后的签名为:" + signature);
System.out.println("验签是否通过:" + isVerified);

控制台输出如下:

生成的私钥为:
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAL4TdBqlxJByJURSqNzthP18t2Q6tzmdwfoTJka0yy/DjEL1/mBPdcygwWBfVYLBvwuBUOyG4pbf/de0pmgc9b7+SwNqqpHtmLxaEqW3ebovm0R2UFom0OS/5X5pdWNKuKDTCpP2xC1JSfRqS+7WNgFEhCK7hRWBzIpifXvLzZNtAgMBAAECgYAcjOd/qS6hU8PtQ01CAhtbyAPz9i3XZa7hVUcGj9mFTyYeWLzg0o6rMepaA3fgsCF2JPJ21LvsVbDXWbc1JER1LYIWYZ79XnYZQgezmfqeSFx+CJkndRZ/qXGnf/1EUJFjEafbkXGRvnv05B4QHBQGn3gfIa2xsLw2r3Yf8M5/gQJBAOXgqeH5yyJ6iNIw5S8EAR7VAUMPteQ/JqQ6l+wYcesj/prqhwRX4x2m/wKh6qM2zZcFLMujYTyJsIQkzmOkFXkCQQDTrO1bnO84dbEVOUv73g0J1jg/3EIbt8uh7T4Iu82/9ycUyQ5D8oANh53rfPf5DXycXWXRlu2gwGvsyNRW1LSVAkEAnYs6go/Cgw+9g2hVOcKhzfKnmcFDpHkPT5CEnB8ou8GAdcVz4SsmkSTpMnGrsE4X2n+Gcs23D1lCK15aQHms6QJBAJ/8GmXcph25Lj9JT/msaZReuaLimYCTmK/pPLKjJy4I4hveng6S8V/IeX4rtMwi+mTAXp1bgny2EpwjagG6wEUCQH9ap3J9fh8UicEPD1LO/GUTTNXuPiwWOcNgetntrjPAJx5b0bf6hEJOyEHtIsuSuzAD8G41o2tpsefact3EwzY=生成的公钥为:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+E3QapcSQciVEUqjc7YT9fLdkOrc5ncH6EyZGtMsvw4xC9f5gT3XMoMFgX1WCwb8LgVDshuKW3/3XtKZoHPW+/ksDaqqR7Zi8WhKlt3m6L5tEdlBaJtDkv+V+aXVjSrig0wqT9sQtSUn0akvu1jYBRIQiu4UVgcyKYn17y82TbQIDAQABtest加密后的密文为:
EA19/wkHbdXTc8sfLFhmnp/MWW3PLx2LeYFHWFNdhvY38+Zoa4Ci8HJw8okkxzTfsSkgsiybMaz82rwF9lfcuEWzjbuGeVOvdkI0p/Cv+PDfikMYwOsxA7OqBJ/Hktn25l/ryEv7TxYlMFQ48jB0KPw/0Ivec9qfX2pgnyBl7WM=解密后的明文为:testmessage加签后的签名为:
AByFyRoc/321db16voe9NQaicwkscTOGjBZGefWzB7dMadWXBtUPIK3CUXADLiiesehgAAcDbl06qVz++x/6xeWPCK2ucCfn9dFybZfmAIsn+3TATuDQIFvz/m2cHQAuH9fkmiGgMPOVY/VcILwri3RETuQ+wz4YSmP89o1cFqk=验签是否通过:true


六、前后端加解密、加验签交互测试

1、前端加密,后端解密

这里我们用前端生成的密钥对做测试。

(1) 前端代码

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}`);

控制台输出:

生成的私钥为:
-----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

(2) 后端代码

String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMv1adNmKNw4rtr2Dy92DZ3Nk1SCrGGetlq0sSgLY8bJsByAvP2ZOOiiWq2xDUVu8ZpQfM8v89tjegfrsIUcN/ZVLc68Ric3BIto85oQc9jBz+aCk1rXeNMePkSVqNaC/n4kJ73Y41ZgKAe0GvyqMyUNhk8VZd+QSNATSv+lGep3AgMBAAECgYBoKMvDry98z+HUZsb4iQSJK1xrU1SvgftEtXSnq7Fn6sZquABMTry2aXt/qqTJadAu653hvW5/Av1mICKEyBV3aT4OjQRGPMgp6WhXvQepUIuyi9qlfUVsJy/+J0zGKZeKsCFlwZ2e2j4Un7Bb//pgUfjJrbPtwC7U85oHjtJb6QJBAOdcm07ThSXFbicj2MuX9Gh7geMjncf6aqnrOwUFjO0d5OxfYRAxrZD1GghygHyoJ4ZOHgJ0s6HVEYjg/u6DBdsCQQDhrb4IOVdSew2cW15ft/5DAKUXRRQBfz0OxOs0Uv5k7zqI+YmysWVRGaZgj8oMZ7gYxN1eYNOKTwVjiuwbuyaVAkEA0OGSMpPT1WsvbVT26bFyb1Z6yTihvif/XxPKgFknh/kCcsoWFwnS+1nevBusl181+BLVE0CL4aM9pogEghB3GwJAWJTVzmyTdfCO+xxyAqg5yRrrsiKPI7dJxA5PNA6PhBbSpwkrn1Q6LIcg4y4NZKkhfbdoHK9s2REDUHsrCgd/sQJAALEe+PCXhcHWnwbm4kRFyJCO4dWkii7o28ohTRourlNsoEmiu1+7lt7PY1+C3D+6A4FFCY/HpGM0i0lJue8rZA==";
String cipherText = "KjcaDKLnBbvxRzuKMysqoz9MHRXCUNIH67+XDiFGTJbM8Rjw4Cei0CzjAPjk2jgAR37Kgh6lX2+Xg8AI9wEmzWr08bt8i2FFxVMrcfOCs5zI1y+2T7G9034f5b0gNx/Pc4dDz+1k453vo0AhCC0vrtb1OfbsRu5oOFns0TqoAMY=";// 解密后的明文应该为test
String plainText = CryptoUtils.decryptByRSA(privateKey, cipherText);
System.out.println("解密后的明文为:" + plainText);

控制台输出如下:

解密后的明文为:test

备注:

  • 从前端复制过来的密钥需要去掉-----BEGIN PRIVATE KEY-----前缀。
  • 从前端复制过来的密钥带有换行,记得去掉中间的换行符。

2、后端加密,前端解密

这里我们用后端生成的密钥对做测试。

(1) 后端代码

AsymmetricKeyPair keyPair = CryptoUtils.generateAsymmetricKeyPair(Encryption.RSA_ECB_PKCS1);
String privateKey = keyPair.getPrivateKey();
String publicKey = keyPair.getPublicKey();
System.out.println("生成的私钥为:\n" + privateKey);
System.out.println("生成的公钥为:\n" + publicKey);String cipherText = CryptoUtils.encryptByRSA(publicKey, "test");
System.out.println("test加密后的内容为:\n" + cipherText);

控制台输出为:

生成的私钥为:
MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAJPh8WiDo3dhKHW9w86D4DX84pHAG03PIeOHCSezbyKeyKsuHA3qayGJ7JqQgWis557uawz95/EbBzzwmO0oy+l16fkiRXcRppU/UbW6PUBbIpJNqCjnKcw+DA5UXVDmIv0xXOP34jlnkY1DxnimqMAkgut8gncgdFxO0ap0us6lAgMBAAECgYBzlVpQ/OqMCRVNiYd8ZxicOc6Aaq0skKOFKWsfa6CGZ6KiIMTun3UiXqHeYOm0fcf/MYvcOKvLh/uNRuPQIV3WKAWJ6r+dXQ+LjzmrH4QDMcmkMn0OKxxbe56MASPWka7/08GLiE1FJLDo8DEkBQnlDHqnt4e7BoZSgYVhWv52AQJBAMcr0O7xiB/Ge9aQzqYQJvdQ4JI53pM0lEx5HPzQjbrMjC1flb572js1ajKckkuTX+nxzyTzC3JtfvGCcMqaaoECQQC+E9LoSfZZHpVFCx4ZIh2VgzrGYnelktb6MenILhdji2j9i6ZyAqyg8TjL+W9/kAKnaNAV2j6GF8/bOTX0UGolAkAcUWiFcKXwDqJw4WngRo+jvkYPxFaXC3TCYr3yXByqoIaVtO9vg+CFZpTQ2V4bjLqoYo8XK89G17ai0+8Bf28BAkA+BGvRHKjDJSZg86KrYqUybjHUHraZEFMSKQz1IozBDvB/oXv6QQMgM/RrIQSPI2aqRpl2N9IkoEpSZdVD1KT9AkBtoz4Eg3Nuy1XdCCrTqTMioY0hP74xCcgURpooxmL2xhNUYu6PJr+g4lkaiq8e/2Cr0ZQlps/pEDgaPdHDf3Et
生成的公钥为:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCT4fFog6N3YSh1vcPOg+A1/OKRwBtNzyHjhwkns28insirLhwN6mshieyakIForOee7msM/efxGwc88JjtKMvpden5IkV3EaaVP1G1uj1AWyKSTago5ynMPgwOVF1Q5iL9MVzj9+I5Z5GNQ8Z4pqjAJILrfIJ3IHRcTtGqdLrOpQIDAQAB
test加密后的内容为:
RgJxG+VSizKgfLnXjsqzTl9h0cUzm460EyHhdL3/qZLNbd6IVcU1Am+OOsbFd9W8GtNhJiCERybgjCucr4c3/EQLXtF8vNHVMFp9ycDW4T+8FMmFQn0f/+oJ7/i9uEoNd9W8nWJcSRHuTw1+rl4Mc7KnmwvdaTV2ZLOxBG6oAK8=

(2) 前端代码

const privateKey = `MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAJPh8WiDo3dhKHW9w86D4DX84pHAG03PIeOHCSezbyKeyKsuHA3qayGJ7JqQgWis557uawz95/EbBzzwmO0oy+l16fkiRXcRppU/UbW6PUBbIpJNqCjnKcw+DA5UXVDmIv0xXOP34jlnkY1DxnimqMAkgut8gncgdFxO0ap0us6lAgMBAAECgYBzlVpQ/OqMCRVNiYd8ZxicOc6Aaq0skKOFKWsfa6CGZ6KiIMTun3UiXqHeYOm0fcf/MYvcOKvLh/uNRuPQIV3WKAWJ6r+dXQ+LjzmrH4QDMcmkMn0OKxxbe56MASPWka7/08GLiE1FJLDo8DEkBQnlDHqnt4e7BoZSgYVhWv52AQJBAMcr0O7xiB/Ge9aQzqYQJvdQ4JI53pM0lEx5HPzQjbrMjC1flb572js1ajKckkuTX+nxzyTzC3JtfvGCcMqaaoECQQC+E9LoSfZZHpVFCx4ZIh2VgzrGYnelktb6MenILhdji2j9i6ZyAqyg8TjL+W9/kAKnaNAV2j6GF8/bOTX0UGolAkAcUWiFcKXwDqJw4WngRo+jvkYPxFaXC3TCYr3yXByqoIaVtO9vg+CFZpTQ2V4bjLqoYo8XK89G17ai0+8Bf28BAkA+BGvRHKjDJSZg86KrYqUybjHUHraZEFMSKQz1IozBDvB/oXv6QQMgM/RrIQSPI2aqRpl2N9IkoEpSZdVD1KT9AkBtoz4Eg3Nuy1XdCCrTqTMioY0hP74xCcgURpooxmL2xhNUYu6PJr+g4lkaiq8e/2Cr0ZQlps/pEDgaPdHDf3Et`;
const cipherText = `RgJxG+VSizKgfLnXjsqzTl9h0cUzm460EyHhdL3/qZLNbd6IVcU1Am+OOsbFd9W8GtNhJiCERybgjCucr4c3/EQLXtF8vNHVMFp9ycDW4T+8FMmFQn0f/+oJ7/i9uEoNd9W8nWJcSRHuTw1+rl4Mc7KnmwvdaTV2ZLOxBG6oAK8=`;
const plainText = CryptoUtils.decryptByRSA(privateKey, cipherText);
console.log(`解密后的内容为:${plainText}`);

控制台输出:

解密后的内容为:test

3、前端加签,后端验签

这里我们用前端生成的密钥对做测试。

(1) 前端代码

const {privateKey, publicKey} = CryptoUtils.generateRsaKeyWithPKCS8();
console.log(`生成的私钥为:\n${privateKey}`);
console.log(`生成的公钥为:\n${publicKey}`);const signature = CryptoUtils.signBySHA256WithRSA(privateKey, "test");
console.log(`生成的签名:\n${signature}`);

控制台输出如下:

生成的私钥为:
-----BEGIN PRIVATE KEY-----
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBALxzJ+U/N/lE+AZO
zMbzx+5WrWDu31oKlH+jth54APGHY+Cmqvi0dUeuSbv238tkem95GTy1kQRz9CMB
WvvVdNYC78E8maBaMKb+GZKVIScZ6eo8gkXxEoB7a3/y6a8L/SpQGgaI78JsnVGJ
ijf+GaGtfbl+dIQMxIc6gbNZOM/PAgMBAAECgYEAiAKE1Mwf1eSVLdhJq33e2oHs
eH1u7kmci9LYan0qESgqScWAuCdmTenYhbTUKLPIOhQoxsw0cgZOOcWMqR2SSHW/
CK0Ql/5jP94wyX5Gw62jFXpLTVKQ1piiNjoujTKOsnkdtfh/JnjdeWh7GEcHhGGw
yin0F1aHZgex4MkG0EECQQDsqJJxESC5QjZl5f6EDIbkSznmeV0IPfgIs8uN1QID
jFbS97QhClSrsev4LXxuGkltOpWbJUkG+nmjZhDPnV1nAkEAy9nzQndYxiv/XLpd
+Id2pB+SBRs/panFFEIoEEb4UoY7QPLfQ6ZOQQ9+vC5r1tWQtIhwiEjhYaGhnBZ1
TASRWQJBAOADNRMnxlT2Uu2jhobSIMFqX7VEvgY2Ollqb0yjC1P2fJ0X8X6w+7LG
KPnzfGvwH/7vzHteEME1SPydeV48tBMCQQCtCsZElar2DkMnI8zBO7yqdWIuk4Lj
zclN+RqpNpV0+B00dPaxJmsnL1AVzhIcvA2qMmfUSImJpvrY1PedIAOBAkAvjVU0
3USMjvv5iudWyqqRy8Q9s2qeWYEiv+bFeLExiqGj9kmOkIfPcm96ElZr1F7PlaQb
wj2A+D2oaQGcQqUm
-----END PRIVATE KEY-----生成的公钥为:
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8cyflPzf5RPgGTszG88fuVq1g
7t9aCpR/o7YeeADxh2Pgpqr4tHVHrkm79t/LZHpveRk8tZEEc/QjAVr71XTWAu/B
PJmgWjCm/hmSlSEnGenqPIJF8RKAe2t/8umvC/0qUBoGiO/CbJ1RiYo3/hmhrX25
fnSEDMSHOoGzWTjPzwIDAQAB
-----END PUBLIC KEY-----生成的签名:
Q9Mtq3gxi2YJ07FQtbry5zxGljomzKQNewhj10Ba10b3roAAdQUzqd+QyP7rqARdPQgt0ClDgvtaL2TNYLc4URh7E3Kgx8T6pSFlPnU/b3cfCoVRPrr/gJBrsCkbNMITNXpVQpwIYe3P1z+OrCUHuaQR82yCVUz3y43oOiE6qIY=

(2) 后端代码

String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8cyflPzf5RPgGTszG88fuVq1g7t9aCpR/o7YeeADxh2Pgpqr4tHVHrkm79t/LZHpveRk8tZEEc/QjAVr71XTWAu/BPJmgWjCm/hmSlSEnGenqPIJF8RKAe2t/8umvC/0qUBoGiO/CbJ1RiYo3/hmhrX25fnSEDMSHOoGzWTjPzwIDAQAB";
String signature = "Q9Mtq3gxi2YJ07FQtbry5zxGljomzKQNewhj10Ba10b3roAAdQUzqd+QyP7rqARdPQgt0ClDgvtaL2TNYLc4URh7E3Kgx8T6pSFlPnU/b3cfCoVRPrr/gJBrsCkbNMITNXpVQpwIYe3P1z+OrCUHuaQR82yCVUz3y43oOiE6qIY=";
boolean isVerified = CryptoUtils.verifyBySHA256WithRSA(publicKey, "test", signature);
System.out.println("是否验签通过:" + isVerified);

控制台输出:

是否验签通过:true

4、后端加签,前端验签

这里我们用后端生成的密钥对做测试。

(1) 后端代码

AsymmetricKeyPair keyPair = CryptoUtils.generateAsymmetricKeyPair(Encryption.RSA_ECB_PKCS1);
String privateKey = keyPair.getPrivateKey();
String publicKey = keyPair.getPublicKey();
System.out.println("生成的私钥为:\n" + privateKey);
System.out.println("生成的公钥为:\n" + publicKey);String signature = CryptoUtils.signBySHA256WithRSA(privateKey, "test");
System.out.println("生成的签名为:\n" + signature);

控制台输出如下:

生成的私钥为:
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKx1iT0ObyRKC06wf+WN1v9LTQwd68X1b8U5+ZAh9Qf7m77HiQtLg1y91v0b70Dr/HRP7juGLFTnK5+NJMcjGNqDfDFqCQtA3eam2UABbwHS76qRFQbSg5QKApvDcfOZtqWmbwwSDMkI5GnYKbSO3EZZCYBBXzplabKQCKcmGKOjAgMBAAECgYAovOb7RkKYxuje4LCFkDjeO3Jqz1KXg3+wjh5Wnr7b8OJ8cXP8+AyCxtFXHtcoddY/v3XeF7a3I5hZayTp6W+AI1OTYhWs9Eqas8B7bNV2rJPFnK9nTiF727bgptJfGuUG8mYxRzIQleHoWqpV9i/ttcEUPM4GGcIfpnwb16NBAQJBANPBLuTCyeDbSW79MmsiTNUeCnljM/UQYUfIpygviNX1iVbsh1lI/l85bN47niIt66j4c5MPOKJOv2Hf3yYqvIECQQDQfmzfLo7deqsizkJAFKggH99ab24iC+VEDtsHlsl212NC36xenoWwuIcP8fJd1UyWY5lwzzCdBKsrt0UeSd4jAkBwrv3AWHPLh4YFXRHGdyNBydGzFPpiL8xEwd9KADml+hqSuh2wgqpyjAGGJV2aPKuKaGRAXro5jQRFFjgOfHGBAkEAsq22ViqLa0nmgmSrqElLsIRAITvf8bOqHwJwOXfDXmLGgZg5G7nVLxdlQIgEQuA6y6O960zVB6vpmgRtasC5awJBAJkwLiKikvPxC8vwhZvkjr+UrbDorUKcuCyDVYxXsSNW8SNs+AV54wEI1Mem5LOhNPKbum6bwwfTf74gC/l4jtw=
生成的公钥为:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsdYk9Dm8kSgtOsH/ljdb/S00MHevF9W/FOfmQIfUH+5u+x4kLS4Ncvdb9G+9A6/x0T+47hixU5yufjSTHIxjag3wxagkLQN3mptlAAW8B0u+qkRUG0oOUCgKbw3Hzmbalpm8MEgzJCORp2Cm0jtxGWQmAQV86ZWmykAinJhijowIDAQAB
生成的签名为:
JQ2FWaAbHWIkl4uSIxyMNbARFzSNKc7mOtXidm7hCRN85D8DVgZll02DYcWRSnn/ejOOxOrEPF8AcYHWx1repHh/jHcwv2focjF3Yne7NkQ4yGvgILDD2s1BIEfU0EH3tFLMIebyU8V54eMMtjDLQ65LZB6PH+5X8s3F6yAPI70=

(2) 前端代码

const publicKey = `-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsdYk9Dm8kSgtOsH/ljdb/S00MHevF9W/FOfmQIfUH+5u+x4kLS4Ncvdb9G+9A6/x0T+47hixU5yufjSTHIxjag3wxagkLQN3mptlAAW8B0u+qkRUG0oOUCgKbw3Hzmbalpm8MEgzJCORp2Cm0jtxGWQmAQV86ZWmykAinJhijowIDAQAB-----END PUBLIC KEY-----`;
const signature = `JQ2FWaAbHWIkl4uSIxyMNbARFzSNKc7mOtXidm7hCRN85D8DVgZll02DYcWRSnn/ejOOxOrEPF8AcYHWx1repHh/jHcwv2focjF3Yne7NkQ4yGvgILDD2s1BIEfU0EH3tFLMIebyU8V54eMMtjDLQ65LZB6PH+5X8s3F6yAPI70=`;
const message = `test`;
const isVerified = CryptoUtils.verifyBySHA256WithRSA(publicKey, signature, message);
console.log(`是否验签通过:${isVerified}`);

控制台输出如下:

是否验签通过:true

备注:因为我们在前端解析密钥时读取的是标准pem格式密钥,所以从后端复制过来的公钥一定要加上-----BEGIN PUBLIC KEY-----前缀和-----END PUBLIC KEY-----后缀,否则会报错。

在这里插入图片描述

相关文章:

前后端RSA互相加解密、加签验签、密钥对生成(Java)

目录一、序言二、关于PKCS#1和PKCS#8格式密钥1、简介2、区别二、关于JSEncrypt三、关于jsrsasign四、前端RSA加解密、加验签示例1、相关依赖2、cryptoUtils工具类封装3、测试用例五、Java后端RSA加解密、加验签1、CryptoUtils工具类封装2、测试用例六、前后端加解密、加验签交互…...

基于Java+SpringBoot+Vue前后端分离学生宿舍管理系统设计与实现

博主介绍&#xff1a;✌全网粉丝3W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建、毕业项目实战、项目定制✌ 博主作品&#xff1a;《微服务实战》专栏是本人的实战经验总结&#xff0c;《S…...

前端高频面试题—JavaScript篇(二)

&#x1f4bb;前端高频面试题—JavaScript篇&#xff08;二&#xff09; &#x1f3e0;专栏&#xff1a;前端面试题 &#x1f440;个人主页&#xff1a;繁星学编程&#x1f341; &#x1f9d1;个人简介&#xff1a;一个不断提高自我的平凡人&#x1f680; &#x1f50a;分享方向…...

【微信小游戏开发笔记】第二节:Cocos开发界面常用功能简介

Cocos开发界面常用功能简介 本章只介绍微信小游戏开发时常用的功能&#xff0c;其他功能不常用&#xff0c;写多了记不住&#xff08;其实是懒 -_-!&#xff09;&#xff1a; 层级管理器&#xff0c;用于操作各个节点。资源管理器&#xff0c;用于操作各种文件资源。场景编辑…...

3分钟,学会了一个调试CSS的小妙招

Ⅰ. 作用 用于调试CSS , 比控制台添更加方便&#xff0c;不需要寻找 &#xff1b;边添加样式&#xff0c;边可以查看效果&#xff0c;适合初学者对CSS 的理解和学习&#xff1b; Ⅱ. 快速实现&#xff08;两边&#xff09; ① 显示这个样式眶 给 head 和 style 标签添加一个…...

【项目精选】基于jsp的健身俱乐部会员系统

点击下载源码 社会可行性 随着社会的发展和计算机技术的进步&#xff0c;人类越来越依赖于信息化的管理系统&#xff0c;这种系统能更加方便的获得信息以及处理信息。人们都改变了过去的思维&#xff0c;开始走向了互联网的时代&#xff0c;在 可行性小结 本章在技术可行性上…...

java注解

1. Java注解(Annotation) 2. Java注解分类 3. JDK基本注解 4. JDK元注解 5. 注解分类 6. 自定义注解开发 7. 提取Annotation信息 8. 注解处理器 9. 动态注解处理器(spring aop方式) 1. Java注解(Annotation) Java注解是附加在代码中的一些元信息&#xff0c;用于…...

移动测试相关

一、环境搭建 准备工作&#xff1a; &#xff08;python、pycharm安装配置好&#xff09; 1、Java SDK 安装配置 Java Downloads | Oracle 下载安装后配置系统环境变量&#xff1a;JAVA_HOME&#xff08;jdk根目录路径&#xff09;和path&#xff08;jdk根目录下的bin目录路径…...

SIGIR22:User-controllable Recommendation Against Filter Bubbles

User-controllable Recommendation Against Filter Bubbles 摘要 推荐系统经常面临过滤气泡的问题&#xff1a;过度推荐基于用户特征以及历史交互的同质化项目。过滤气泡将会随着反馈循环增长&#xff0c;缩小了用户兴趣。现有的工作通常通过纳入诸如多样性和公平性等准确性之…...

Python中的进程线程

文章目录前言多进程与多线程基本概念多进程multiprocessing 类对象进程池subprocess模块进程间通信多线程threading实现线程操作线程共享所有变量线程锁参考资料前言 又花了点时间学习了一下Python中的多线程与多进程的知识点&#xff0c;梳理一下供复习参考 多进程与多线程 …...

python(8):使用conda update更新conda后,anaconda所有环境崩溃----问题没有解决,不要轻易更新conda

文章目录0. 教训1. 问题:使用conda update更新conda后&#xff0c;anaconda所有环境崩溃1.1 问题描述1.2 我搜索到的全网最相关的问题----也没有解决3 尝试流程记录3.1 重新安装pip3.2 解决anaconda编译问题----没成功0. 教训 (1) 不要轻易使用conda update更新conda----我遇到…...

c++11 标准模板(STL)(std::multimap)(四)

定义于头文件 <map> template< class Key, class T, class Compare std::less<Key>, class Allocator std::allocator<std::pair<const Key, T> > > class multimap;(1)namespace pmr { template <class Key, class T…...

乐观锁及悲观锁

目录 1.乐观锁 (1).定义 (2).大体流程 (3).实现 (4).总结 2.悲观锁 (1).定义 (2).大体流程 (3).实现 (4).缺点 (5).总结 1.乐观锁 (1).定义 乐观锁在操作数据时非常乐观&#xff0c;认为别的线程不会同时修改数据所以不会上锁&#xff0c;但是在更新的时候会判断一…...

常见的锁策略

注意: 接下来讲解的锁策略不仅仅是局限于 Java . 任何和 "锁" 相关的话题, 都可能会涉及到以下内容. 这些特性主要是给锁的实现者来参考的.普通的程序猿也需要了解一些, 对于合理的使用锁也是有很大帮助的. 1.乐观锁 vs 悲观锁 悲观锁: &#xff08;认为出现锁冲…...

springboot学习(八十) springboot中使用Log4j2记录分布式链路日志

在分布式环境中一般统一收集日志&#xff0c;但是在并发大时不好定位问题&#xff0c;大量的日志导致无法找出日志的链路关系。 可以为每一个请求分配一个traceId&#xff0c;记录日志时&#xff0c;记录此traceId&#xff0c;从网关开始&#xff0c;依次将traceId记录到请求头…...

10种ADC软件滤波方法及程序

10种ADC软件滤波方法及程序一、10种ADC软件滤波方法1、限幅滤波法&#xff08;又称程序判断滤波法&#xff09;2、中位值滤波法3、算术平均滤波法4、递推平均滤波法&#xff08;又称滑动平均滤波法&#xff09;5、中位值平均滤波法&#xff08;又称防脉冲干扰平均滤波法&#x…...

第五章:Windows server加域

加入AD域&#xff1a;教学视频&#xff1a;https://www.bilibili.com/video/BV1xM4y1D7oL/?spm_id_from333.999.0.0首先我们选择一个干净的&#xff0c;也就是新建的没动过的Windows server虚拟机。我们将DNS改成域的ip地址&#xff0c;还要保证它们之间能ping的通&#xff0c…...

Elasticsearch:获取 nested 类型数组中的所有元素

在我之前的文章 “Elasticsearch: object 及 nested 数据类型” 对 nested 数据类型做了一个比较详细的介绍。在实际使用中&#xff0c;你在构建查询时肯定会遇到一些问题。根据官方文档介绍&#xff0c;nested 类型字段在隐藏数组中索引其每个项目&#xff0c;这允许独立于索引…...

English Learning - Day53 作业打卡 2023.2.7 周二

English Learning - Day53 作业打卡 2023.2.7 周二引言1. 我必须承认&#xff0c;我之前学习没你用功。have to VS must2. 这跟我想得一样简单。3. 生活并不像它看上去那么顺风顺水&#xff0c;但也不会像我们想象得那么难。Look VS seem4. 你比去年高多了。5. 你关心你的工作胜…...

SpringMVC--注解配置SpringMVC、SpringMVC执行流程

注解配置SpringMVC 使用配置类和注解代替web.xml和SpringMVC配置文件的功能 创建初始化类&#xff0c;代替web.xml 在Servlet3.0环境中&#xff0c;容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类&#xff0c; 如果找到的话就用它来配置Servle…...

JavaScript中数组常用的方法

文章目录前言常用数组方法1、 join( )2、push&#xff08;&#xff09;与 pop&#xff08;&#xff09;3、shift&#xff08;&#xff09;与 unshift&#xff08;&#xff09;4、sort&#xff08;&#xff09;5、reverse&#xff08;&#xff09;6、slice&#xff08;&#xff…...

ModuleNotFoundError: No module named ‘pip‘

项目场景&#xff1a;pip 错误 Traceback (most recent call last): File "E:\KaiFa\Python\Python38\lib\runpy.py", line 194, in _run_module_as_main return _run_code(code, main_globals, None, File "E:\KaiFa\Python\Python38\lib\runpy.py&qu…...

ROS2 入门应用 发布和订阅(C++)

ROS2 入门应用 发布和订阅&#xff08;C&#xff09;1. 创建功能包2. 创建源文件2.1. 话题发布2.2. 话题订阅3. 添加依赖关系4. 添加编译信息4.1. 添加搜索库4.2. 增加可执行文件4.3. 增加可执行文件位置5. 编译和运行1. 创建功能包 在《ROS2 入门应用 工作空间》中已创建和加…...

XSS漏洞,通过XSS实现网页挂马

**今天讲下通过XSS实现网页挂马~*&#xff0c;目的是了解安全方面知识&#xff0c;提升生活网络中辨别度 原理&#xff1a; 实验分为两部分&#xff1a; 1、通过Kali linux&#xff0c;利用MS14_064漏洞&#xff0c;制作一个木马服务器。存在该漏洞的用户一旦通过浏览器访问木…...

家政服务小程序实战教程09-图文卡片

小程序还有一类需求就是展示服务的列表&#xff0c;我们这里用图文卡片组件来实现&#xff0c;我们先要添加一个标题&#xff0c;使用网格布局来实现 第一列添加一个文本组件&#xff0c;第二列添加一个图标组件 修改文本组件的文本内容&#xff0c;设置外边距 设置第二列的样式…...

国内唯一一部在CentOS下正确编译安装和使用RediSearch的教程

开篇 Redis6开始增加了诸多激动人心的模块&#xff0c;特别是&#xff1a;RedisJSON和RediSearch。这两个模块已经完全成熟了。它们可以直接使用我们的生产上的Redis服务器来做全文搜索&#xff08;二级搜索&#xff09;以取得更廉价的硬件成本、同时在效率上竟然超过了Elastic…...

前端对于深拷贝和浅拷贝的应用和思考

浅拷贝 浅拷贝 &#xff1a; 浅拷贝是指对基本类型的值拷贝&#xff0c;以及对对象类型的地址拷贝。它是将数据中所有的数据引用下来&#xff0c;依旧指向同一个存放地址&#xff0c;拷贝之后的数据修改之后&#xff0c;也会影响到原数据的中的对象数据。最简单直接的浅拷贝就…...

Java基础常见面试题(三)

String 字符型常量和字符串常量的区别&#xff1f; 形式上: 字符常量是单引号引起的一个字符&#xff0c;字符串常量是双引号引起的若干个字符&#xff1b; 含义上: 字符常量相当于一个整型值( ASCII 值)&#xff0c;可以参加表达式运算&#xff1b;字符串常量代表一个地址值…...

C++设计模式(13)——装饰模式

亦称&#xff1a; 装饰者模式、装饰器模式、Wrapper、Decorator 意图 装饰模式是一种结构型设计模式&#xff0c; 允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。 问题 假设你正在开发一个提供通知功能的库&#xff0c; 其他程序可使用它向用户发…...

ESP-01S通过AT指令上报数据到阿里云物模型

ESP-01S使用AT指令上报数据到阿里云物模型 上篇文章介绍了如何用AT指令连接阿里云并进行通信&#xff1a;https://blog.csdn.net/weixin_46251230/article/details/128995530 但最终需要将传感器数据上报到云平台显示&#xff0c;所以需要建立阿里云物模型 阿里云平台建立物…...

昆明做网站找天度/蚁百杭州网站seo优化

2019独角兽企业重金招聘Python工程师标准>>> netstat -an 查看端口 netstat -aon|findstr "4444" tasklist|findstr "2434" netstat -anb 查看监听端口的pid 结合任务管理器关闭 tracert ip 跟踪路由过程 dxdiag 查系统显卡等 ------------li…...

美团网站做疏通广告/google手机官网

在Code Review代码的过程中经常会发现有些同学对于各种java中的对象理解比较混乱。基于这种情况&#xff0c;对常见的几种对象进行简单说明。 我们常见的对象主要是VO,PO,POJO,BO,DAO,DTO这几种。 VO value object值对象 ViewObject表现层对象 主要对应界面显示的数据对象。对于…...

免费网站app下载/淘宝流量网站

*) RequestMapping(value"/xxx/{id}",method{RequestMethod.GET}) method 不写的话&#xff0c;默认GET、POST都支持&#xff0c;根据前端方式自动适应 转载于:https://www.cnblogs.com/mySummer/p/11057147.html...

网站建设策划书格式及范文/网络推广外包公司

如何在控制台获取到某个元素的Scope呢&#xff1f;假设&#xff0c;页面元素为&#xff1a;<label>Name:</label><input type"text" ng-model"yourName" placeholder"Enter a name here"><h1>{{yourName}}</h1>→…...

seo是什么职位的缩写/谷歌seo搜索

Sql 四大排名函数&#xff08;ROW_NUMBER、RANK、DENSE_RANK、NTILE&#xff09;简介 排名函数是Sql Server2005新增的功能&#xff0c;下面简单介绍一下他们各自的用法和区别。我们新建一张Order表并添加一些初始数据方便我们查看效果。 CREATE TABLE [dbo].[Order]([ID] [int…...

政府网站需求分析/排名优化方案

lamp&#xff08;Web应用软件&#xff09;Linux ApacheMysql/MariaDBPerl/PHP/Python一组常用来搭建动态网站或者服务器的开源软件&#xff0c;本身都是各自独立的程序&#xff0c;但是因为常被放在一起使用&#xff0c;拥有了越来越高的兼容度&#xff0c;共同组成了一个强大的…...