登录认证、鉴权这些都做好了过后。就开始我们的加密设计了、这里采用了简化数字信封进行加密。首先客户端(浏览器)先请求一份RSA非对称密钥、如果我们采用了openresty或者有能力在nginx开发C模块的插件,就可以在这里保留一份用户的私钥,如果不行就直接在应用网关上面保存(也可以在应用网关直接读取redis获得);然后在浏览器发起请求的时候、请求体加密时本地自己生成AES密钥、在使用获得的公钥对AES密钥进行一次加密(加密结果放在请求头中),最后将请求发送到后端。后端先使用RSA的私钥解密请求头中的AES加密密钥,得到了AES明文密钥后对请求体进行解密。后端返回时,如果需要加密返回结果、也可以使用该AES密钥对结果进行加密。下图以有nginx自定义C模块或者openresty进行加解密的方式进行描述。

AES加密

采用AES进行对称加密时、需要注意的是需要采用CBC模式。CBC模式会增加向量来保证加密的强度。还有为什么明明使用了RSA还需要在用AES做什么呢?为什么不全部使用RSA呢?

首先RSA的强度是要比AES更强的、但是RSA有一个缺陷就是密文太长的话、解密的速度太慢。所以综合了两者的优点、使用RSA对AES的密钥加密,使用AES对报文体进行加密。这样既保证了密码的强度、又保证了加解密的速度问题。

AES加解密工具类

import org.apache.commons.codec.binary.Base64;import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;/*** AES 加密工具类*/
public class AESUtils {public static final String CHAR_ENCODING = "UTF-8";public static final String AES_ALGORITHM = "AES/CBC/PKCS5Padding";/*** 加密** @param data 需要加密的内容* @param key  加密密码* @return*/public static byte[] encrypt(byte[] data, byte[] key, byte[] ivKey) {if (key.length != 16) {throw new RuntimeException("Invalid AES key length (must be 16 bytes)");}try {SecretKeySpec secretKey = new SecretKeySpec(key, "AES");byte[] enCodeFormat = secretKey.getEncoded();SecretKeySpec seckey = new SecretKeySpec(enCodeFormat, "AES");Cipher cipher = Cipher.getInstance(AES_ALGORITHM);// 创建密码器IvParameterSpec iv = new IvParameterSpec(ivKey);//使用CBC模式,需要一个向量iv,可增加加密算法的强度cipher.init(Cipher.ENCRYPT_MODE, seckey, iv);// 初始化byte[] result = cipher.doFinal(data);return result; // 加密} catch (Exception e) {e.printStackTrace();throw new RuntimeException("encrypt fail!", e);}}/*** 解密** @param data 待解密内容* @param key  解密密钥* @return*/public static byte[] decrypt(byte[] data, byte[] key,byte[] ivKey) {if (key.length != 16) {throw new RuntimeException("Invalid AES key length (must be 16 bytes)");}try {SecretKeySpec secretKey = new SecretKeySpec(key, "AES");byte[] enCodeFormat = secretKey.getEncoded();SecretKeySpec seckey = new SecretKeySpec(enCodeFormat, "AES");// 创建密码器Cipher cipher = Cipher.getInstance(AES_ALGORITHM);// 使用CBC模式,需要一个向量iv,可增加加密算法的强度IvParameterSpec iv = new IvParameterSpec(ivKey);// 初始化cipher.init(Cipher.DECRYPT_MODE, seckey, iv);byte[] result = cipher.doFinal(data);return result; // 解密} catch (Exception e) {e.printStackTrace();throw new RuntimeException("decrypt fail!", e);}}public static String encryptToBase64(String data, String key, String iVkey) {try {byte[] valueByte = encrypt(data.getBytes(CHAR_ENCODING), key.getBytes(CHAR_ENCODING),iVkey.getBytes(CHAR_ENCODING));return new String(Base64.encodeBase64(valueByte));} catch (UnsupportedEncodingException e) {throw new RuntimeException("encrypt fail!", e);}}public static String decryptFromBase64(String data, String key, String iVkey) {try {byte[] originalData = Base64.decodeBase64(data.getBytes());final char[] chars = getChars(decrypt(originalData, key.getBytes(CHAR_ENCODING),iVkey.getBytes(CHAR_ENCODING)));StringBuffer sb = new StringBuffer();for(int i= 0 ;i < chars.length; i++){if(chars[i] == '\0'){continue;}sb.append(chars[i]);}Arrays.fill(chars,' ');return sb.toString();} catch (UnsupportedEncodingException e) {throw new RuntimeException("decrypt fail!", e);}}private static char[] getChars(byte[] bytes){Charset cs = Charset.forName(CHAR_ENCODING);ByteBuffer bb = ByteBuffer.allocate(bytes.length);bb.put(bytes);bb.flip();CharBuffer cb = cs.decode(bb);return cb.array();}public static byte[] genarateRandomKey() {KeyGenerator keygen = null;try {keygen = KeyGenerator.getInstance(AES_ALGORITHM);} catch (NoSuchAlgorithmException e) {throw new RuntimeException(" genarateRandomKey fail!", e);}/* SecureRandom random = new SecureRandom();keygen.init(random);*/keygen.init(128);Key key = keygen.generateKey();return key.getEncoded();}}

RSA加密

上面提到会在nginx那边存储一份私钥、那么如何保证客户端(浏览器)的每一对公私玥都是能对应上的呢。这里就需要在做一个设计、就是客户端在请求公钥之前先获取一个临时的token、临时token跟正式token的区别在于临时token的权限是固定的一些URI。并且临时token中的userId是随机生成的虚拟的,临时token的时效也不用设置太长。当用户登录成功后token中的tokenId(之前提到的使用雪花字符串或者UUID)不变、只需要替换里面的userId跟重新设置redis的有效时长即可。这样nginx就可以使用token作为关键字存储每一个客户端对应的私钥了。

RSA工具类

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.*;
import java.util.HashMap;
import java.util.Map;/*** RSA处理工具类** @author hhs* @date 2019-10-22 09:54* @since JDK1.8*/
@Slf4j
public class RsaPassUtils {/*** 填充模式*/private final static String RSA_CIPHER = "RSA/ECB/OAEPWithSHA-1AndMGF1PADDING";/*** 密钥位数*/private final int KEY_SIZE = 2048;private  KeyPair KEY_PAIR;public RsaPassUtils(){try {SecureRandom random = new SecureRandom();KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");generator.initialize(KEY_SIZE, random);KEY_PAIR = generator.generateKeyPair();} catch (Exception e) {throw new RuntimeException(e);}}/*** 生成rsa公钥和私钥** @return Map {@link HashMap}: publicKey-公钥(RSAPublicKey),privateKey-私钥(RSAPrivateKey)* @author hhs* @date 2019-10-22 09:54*/public Map<String, Object> getRsaKey() {Map<String, Object> keyInfo = new HashMap<>(2);// 公钥RSAPublicKey publicKey = (RSAPublicKey) KEY_PAIR.getPublic();keyInfo.put("publicKey", publicKey);// 私钥RSAPrivateKey privateKey = (RSAPrivateKey) KEY_PAIR.getPrivate();keyInfo.put("privateKey", privateKey);return keyInfo;}/*** 生成rsa公钥和私钥** @return Map {@link HashMap}: publicKey-公钥(X509格式),privateKey-私钥(PKCS8格式)* @author hhs* @date 2019-10-22 09:54*/public Map<String, String> getX509AndPKCS8Key() {Map<String, Object> keyInfo = getRsaKey();Map<String, String> x509AndPKCS8Key = new HashMap<>(2);// 公钥RSAPublicKey publicKey = (RSAPublicKey) keyInfo.get("publicKey");
//        String rsaPublicKey = (new BASE64Encoder()).encodeBuffer(publicKey.getEncoded());String rsaPublicKey = Base64.encodeBase64String(publicKey.getEncoded());x509AndPKCS8Key.put("publicKey", rsaPublicKey);// 私钥RSAPrivateKey privateKey = (RSAPrivateKey) keyInfo.get("privateKey");
//        String rsaPrivateKey = (new BASE64Encoder()).encodeBuffer(privateKey.getEncoded());String rsaPrivateKey = Base64.encodeBase64String(privateKey.getEncoded());x509AndPKCS8Key.put("privateKey", rsaPrivateKey);return x509AndPKCS8Key;}/*** PKCS8的私钥字符串还原为RSA私钥** @param pkcs8Key {@link String} 待还原私钥字符串* @return RSAPrivateKey {@link RSAPrivateKey}* @author hhs* @date 2019-10-22 09:54*/private RSAPrivateKey getPrivateKey(String pkcs8Key) {PrivateKey privateKey = null;try {
//            byte[] decodeKey = (new BASE64Decoder()).decodeBuffer(pkcs8Key);byte[] decodeKey = Base64.decodeBase64(pkcs8Key);PKCS8EncodedKeySpec pkcs8 = new PKCS8EncodedKeySpec(decodeKey);KeyFactory keyFactory = KeyFactory.getInstance("RSA");privateKey = keyFactory.generatePrivate(pkcs8);} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {log.error("----------RSAUtils---------->PKCS8的私钥字符串还原为RSA私钥出错:{}", e.getMessage());}return (RSAPrivateKey) privateKey;}/*** X509的公钥字符串还原为RSA公钥** @param x509Key {@link String} 待还原公钥字符串* @return RSAPublicKey {@link RSAPublicKey}* @author hhs* @date 2019-10-22 09:54*/private RSAPublicKey getPublicKey(String x509Key) {PublicKey publicKey = null;try {
//            byte[] decodeKey = (new BASE64Decoder()).decodeBuffer(x509Key);byte[] decodeKey = Base64.decodeBase64(x509Key);X509EncodedKeySpec x509 = new X509EncodedKeySpec(decodeKey);KeyFactory keyFactory = KeyFactory.getInstance("RSA");publicKey = keyFactory.generatePublic(x509);} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {log.error("----------RSAUtils---------->X509的公钥字符串还原为RSA公钥:{}", e.getMessage());}return (RSAPublicKey) publicKey;}/*** <p>使用模和指数生成RSA公钥* 注意:【此代码用了默认补位方式,为RSA/None/PKCS1Padding,不同JDK默认的补位方式可能不同,如Android默认是RSA/None/NoPadding】* </p>** @param modulus        {@link BigInteger} 模* @param publicExponent {@link BigInteger} 公钥指数* @return RSAPublicKey {@link RSAPublicKey}* @author hhs* @date 2019-10-22 09:54*/public RSAPublicKey getPublicKey(BigInteger modulus, BigInteger publicExponent) {RSAPublicKey publicKey = null;try {KeyFactory keyFactory = KeyFactory.getInstance("RSA");RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, publicExponent);publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);log.info("----------RSAUtils---------->生成公钥:{}", publicKey);} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {log.error("----------RSAUtils---------->生成公钥异常:{}", e.getMessage());}return publicKey;}/*** <p>* 使用模和指数生成RSA私钥* 注意:【此代码用了默认补位方式,为RSA/None/PKCS1Padding,不同JDK默认的补位方式可能不同,如Android默认是RSA/None/NoPadding】* </p>** @param modulus         {@link BigInteger} 模* @param privateExponent {@link BigInteger} 私钥指数* @return RSAPrivateKey {@link RSAPrivateKey}* @author hhs* @date 2019-10-22 09:54*/public RSAPrivateKey getPrivateKey(BigInteger modulus, BigInteger privateExponent) {RSAPrivateKey privateKey = null;try {KeyFactory keyFactory = KeyFactory.getInstance("RSA");RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(modulus, privateExponent);privateKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);} catch (Exception e) {log.error("----------RSAUtils---------->生成私钥异常:{}", e.getMessage());}return privateKey;}/*** 公钥加密** @param publicKey {@link String} X509格式公钥* @param plaintext {@link String} 明文* @return String {@link String} 密文* @author hhs* @date 2019-10-22 09:54*/public String encryptPublicKey(String publicKey, String plaintext) {RSAPublicKey rsaPublicKey = getPublicKey(publicKey);String result = "";try {byte[] bytes = encryptByRsaKey(rsaPublicKey, plaintext.getBytes());result = Base64.encodeBase64String(bytes);} catch (Exception e) {log.error("----------RSAUtils---------->公钥加密异常:{}", e.getMessage());}return result;}/*** 公钥解密** @param publicKey  {@link String} X509格式公钥* @param ciphertext {@link String} 密文* @return String {@link String} 明文* @author hhs* @date 2019-10-22 09:54*/public String decryptPublicKey(String publicKey, String ciphertext) {byte[] bytes = Base64.decodeBase64(ciphertext);RSAPublicKey rsaPublicKey = getPublicKey(publicKey);String result = "";try {result = new String(decryptByRsaKey(rsaPublicKey, bytes), StandardCharsets.UTF_8);} catch (Exception e) {log.error("----------RSAUtils---------->私钥解密异常:{}", e.getMessage());}return result;}/*** 私钥加密** @param privateKey {@link String} PKCS8格式私钥* @param plaintext  {@link String} 明文* @return String {@link String} 密文* @author hhs* @date 2019-10-22 09:54*/public String encryptPrivateKey(String privateKey, String plaintext) {RSAPrivateKey rsaPrivateKey = getPrivateKey(privateKey);String result = "";try {byte[] bytes = encryptByRsaKey(rsaPrivateKey, plaintext.getBytes());result = Base64.encodeBase64String(bytes);} catch (Exception e) {log.error("----------RSAUtils---------->公钥加密异常:{}", e.getMessage());}return result;}/*** 私钥解密** @param privateKey {@link String} PKCS8格式私钥* @param ciphertext {@link String} 密文* @return String {@link String}* @author hhs* @date 2019-10-22 09:54*/public String decryptPrivateKey(String privateKey, String ciphertext) {byte[] bytes = Base64.decodeBase64(ciphertext);RSAPrivateKey rsaPrivateKey = getPrivateKey(privateKey);String result = "";try {result = new String(decryptByRsaKey(rsaPrivateKey, bytes), StandardCharsets.UTF_8);} catch (Exception e) {log.error("----------RSAUtils---------->私钥解密异常:{}", e.getMessage());}return result;}/*** 数据分组解密** @param key                 {@link Key} RSA公钥或者私钥* @param ciphertext-密文byte数组* @return byte[] 明文byte数组* @throws Exception {@link Exception} 异常数据* @author hhs* @date 2019-10-22 09:54*/private byte[] decryptByRsaKey(Key key, byte[] ciphertext) throws Exception {// Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding");Cipher cipher = Cipher.getInstance(RSA_CIPHER);cipher.init(2, key);int inputLen = ciphertext.length;ByteArrayOutputStream out = new ByteArrayOutputStream();int offSet = 0;for (int i = 0; inputLen - offSet > 0; offSet = i * 256) {byte[] cache;if (inputLen - offSet > 256) {cache = cipher.doFinal(ciphertext, offSet, 256);} else {cache = cipher.doFinal(ciphertext, offSet, inputLen - offSet);}out.write(cache, 0, cache.length);++i;}byte[] decryptedData = out.toByteArray();out.close();return decryptedData;}/*** 数据分组加密** @param key                {@link Key} Rsa公钥或私钥* @param plaintext-明文byte数组* @return byte[] 密文byte数组* @throws Exception {@link Exception} 异常数据* @author hhs* @date 2019-10-22 09:54*/private byte[] encryptByRsaKey(Key key, byte[] plaintext) throws Exception {// Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding");Cipher cipher = Cipher.getInstance(RSA_CIPHER);cipher.init(1, key);int inputLen = plaintext.length;ByteArrayOutputStream out = new ByteArrayOutputStream();int offSet = 0;for (int i = 0; inputLen - offSet > 0; offSet = i * 244) {byte[] cache;if (inputLen - offSet > 244) {cache = cipher.doFinal(plaintext, offSet, 244);} else {cache = cipher.doFinal(plaintext, offSet, inputLen - offSet);}out.write(cache, 0, cache.length);++i;}byte[] encryptedData = out.toByteArray();out.close();return encryptedData;}}

注意在填充时、不要在构造中添加"BC"参数,该模式配合到C语言进行解密时,C语言不好处理、所以需要将该参数去掉:

Cipher cipher = Cipher.getInstance(RSA_CIPHER,"BC");

RSA+AES数字信封加解密设计相关推荐

  1. sm2格式数字信封加解密详解

    sm2格式数字信封 0.参考链接 密码行业标准化技术委员会http://www.gmbz.org.cn/main/bzlb.html SM2密码算法使用规范http://www.gmbz.org.cn ...

  2. java 数字信封_本地证书实现数字信封加解密demo-java

    [实例简介] 提供获取加密证书接口.数字信封加密以及数字信封解密接口源码,IDEA编译,测试数据符合标准openssl,测试的时候要注意证书和私钥的存放路径 [实例截图] [核心代码] SealEnv ...

  3. SM4、AES,RSA,DES等加解密,以及一些其他常用工具方法整理

    工作中接触到了SM4,AES,RSA等算法的加解密,这里整理下来,以备后续其他地方需要使用到. 主要用到的第三方包为hutool 后台引入依赖的方式为: <!-- hutool工具包 --> ...

  4. rsa不同编程语言互相加解密

    rsa互通密钥对生成及互通加解密(c#,java,php) 摘要 在数据安全上rsa起着非常大的作用,特别是数据网络通讯的安全上.当异构系统在数据网络通讯上对安全性有所要求时,rsa将作为其中的一种选 ...

  5. 阿里云专属KMS信封加解密工具类

    此工具类主要实现,字符串的加密,输出为Base64编码的字符串.字符串的解密,输入为Base64编码的字符串.工具类里的IV向量参数,需要用户自己保存,工具类中是默认写死在代码中的,并不规范.IV向量 ...

  6. openssl qt 生成秘钥_关于openssl作的rsa生成密钥及加解密

    关于openssl做的rsa生成密钥及加解密 谁能给个在QtCreator上用openssl做的rsa生成密钥及加解密的例子参考下  网上找的都是片段 不全   谢谢! RSA openssl qtc ...

  7. aes加密算法python语言实现_如何用Python实现AES CCM的加解密

    1.简介 AES CCM被广泛应用于现代通讯中,在学习过程中需要验证数据的加解密的结果,那么有个方便修改的Python脚本工具就是一个迫切的需求. 2. 实施 我们下面介绍如何实现AES CCM的Py ...

  8. aes 256 ecb 加解密 pkcs7补全 python JS

    python aes 256 ecb 加解密 功能 实现 Python ECB 256 JS版本 ECB 算法 JS版本 CBC 算法Pkcs7填充 SQL AES 在线验证网站 notice 功能 ...

  9. Crypto++入门学习笔记(DES、AES、RSA、SHA-256)(加解密)

    转自http://www.cppblog.com/ArthasLee/archive/2010/12/01/135186.html 最近,基于某些原因和需要,笔者需要去了解一下Crypto++库,然后 ...

最新文章

  1. Excel数组公式从入门到精通之精通篇
  2. delegate的使用总结
  3. Bootstrap CustomBox 弹层
  4. html如何复用其它页面,编写可以复用的 HTML 模板
  5. 同时获取同一等级下多个class值的节点的方法
  6. 试卷代号6098计算机应用基础,2231电大《Visual Basic程序设计》试题和答案200507
  7. 同步滚动两个DataGrid
  8. 6 个珍藏已久 IDEA 小技巧,这一波全部分享给你!
  9. Java 持久化操作之 --io流与序列化
  10. win10一直卡在自动修复_分享:win10自动修复过程中无法正确启动怎么办?
  11. mysql教程查询语句_最全的mysql查询语句整理
  12. 西瓜书+实战+吴恩达机器学习(十九)特征选择(过滤式选择、包裹式选择)
  13. Maven阿里云镜像配置
  14. ePass.CreateFile
  15. 开发移动网页应用的一些技术指导
  16. laravel获取最后一条
  17. 第十期 路由器调试之HelloWorld 《路由器就是开发板》
  18. BTC源码分析 区块(一)
  19. PHP导入(百万级)Excel表格数据
  20. 【天池基础知识】 - 查看特征变量的相关性(计算相关性系数+画出热力图+筛选特征变量+Box-Cox变换)

热门文章

  1. 为什么无法在 Adobe XD 中导入或导出文件?
  2. 计算机应用基础第一学期期末考试答案,大学计算机应用基础期末考试试卷及答案...
  3. 【论文译文】RealNVP
  4. MThings 下载地址 (支持以主机或从机方式进行MODBUS协议簇调试调测运维 工具 助手)
  5. Centos7 yum转为阿里源
  6. php使用pg中copy命令,PGSQL COPY命令导入/导出数据
  7. 现在连连支付使用的人多吗???安全吗??
  8. 华为最美小姐姐被外派墨西哥后...
  9. 计算机键盘在线识别,usb键盘,教您电脑usb键盘无法识别解决方法
  10. 远程桌面RDP后无法使用串流如Steam、Teamviewer、向日葵、Moonlight等