RSA+AES数字信封加解密设计
登录认证、鉴权这些都做好了过后。就开始我们的加密设计了、这里采用了简化数字信封进行加密。首先客户端(浏览器)先请求一份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数字信封加解密设计相关推荐
- sm2格式数字信封加解密详解
sm2格式数字信封 0.参考链接 密码行业标准化技术委员会http://www.gmbz.org.cn/main/bzlb.html SM2密码算法使用规范http://www.gmbz.org.cn ...
- java 数字信封_本地证书实现数字信封加解密demo-java
[实例简介] 提供获取加密证书接口.数字信封加密以及数字信封解密接口源码,IDEA编译,测试数据符合标准openssl,测试的时候要注意证书和私钥的存放路径 [实例截图] [核心代码] SealEnv ...
- SM4、AES,RSA,DES等加解密,以及一些其他常用工具方法整理
工作中接触到了SM4,AES,RSA等算法的加解密,这里整理下来,以备后续其他地方需要使用到. 主要用到的第三方包为hutool 后台引入依赖的方式为: <!-- hutool工具包 --> ...
- rsa不同编程语言互相加解密
rsa互通密钥对生成及互通加解密(c#,java,php) 摘要 在数据安全上rsa起着非常大的作用,特别是数据网络通讯的安全上.当异构系统在数据网络通讯上对安全性有所要求时,rsa将作为其中的一种选 ...
- 阿里云专属KMS信封加解密工具类
此工具类主要实现,字符串的加密,输出为Base64编码的字符串.字符串的解密,输入为Base64编码的字符串.工具类里的IV向量参数,需要用户自己保存,工具类中是默认写死在代码中的,并不规范.IV向量 ...
- openssl qt 生成秘钥_关于openssl作的rsa生成密钥及加解密
关于openssl做的rsa生成密钥及加解密 谁能给个在QtCreator上用openssl做的rsa生成密钥及加解密的例子参考下 网上找的都是片段 不全 谢谢! RSA openssl qtc ...
- aes加密算法python语言实现_如何用Python实现AES CCM的加解密
1.简介 AES CCM被广泛应用于现代通讯中,在学习过程中需要验证数据的加解密的结果,那么有个方便修改的Python脚本工具就是一个迫切的需求. 2. 实施 我们下面介绍如何实现AES CCM的Py ...
- aes 256 ecb 加解密 pkcs7补全 python JS
python aes 256 ecb 加解密 功能 实现 Python ECB 256 JS版本 ECB 算法 JS版本 CBC 算法Pkcs7填充 SQL AES 在线验证网站 notice 功能 ...
- Crypto++入门学习笔记(DES、AES、RSA、SHA-256)(加解密)
转自http://www.cppblog.com/ArthasLee/archive/2010/12/01/135186.html 最近,基于某些原因和需要,笔者需要去了解一下Crypto++库,然后 ...
最新文章
- Excel数组公式从入门到精通之精通篇
- delegate的使用总结
- Bootstrap CustomBox 弹层
- html如何复用其它页面,编写可以复用的 HTML 模板
- 同时获取同一等级下多个class值的节点的方法
- 试卷代号6098计算机应用基础,2231电大《Visual Basic程序设计》试题和答案200507
- 同步滚动两个DataGrid
- 6 个珍藏已久 IDEA 小技巧,这一波全部分享给你!
- Java 持久化操作之 --io流与序列化
- win10一直卡在自动修复_分享:win10自动修复过程中无法正确启动怎么办?
- mysql教程查询语句_最全的mysql查询语句整理
- 西瓜书+实战+吴恩达机器学习(十九)特征选择(过滤式选择、包裹式选择)
- Maven阿里云镜像配置
- ePass.CreateFile
- 开发移动网页应用的一些技术指导
- laravel获取最后一条
- 第十期 路由器调试之HelloWorld 《路由器就是开发板》
- BTC源码分析 区块(一)
- PHP导入(百万级)Excel表格数据
- 【天池基础知识】 - 查看特征变量的相关性(计算相关性系数+画出热力图+筛选特征变量+Box-Cox变换)
热门文章
- 为什么无法在 Adobe XD 中导入或导出文件?
- 计算机应用基础第一学期期末考试答案,大学计算机应用基础期末考试试卷及答案...
- 【论文译文】RealNVP
- MThings 下载地址 (支持以主机或从机方式进行MODBUS协议簇调试调测运维 工具 助手)
- Centos7 yum转为阿里源
- php使用pg中copy命令,PGSQL COPY命令导入/导出数据
- 现在连连支付使用的人多吗???安全吗??
- 华为最美小姐姐被外派墨西哥后...
- 计算机键盘在线识别,usb键盘,教您电脑usb键盘无法识别解决方法
- 远程桌面RDP后无法使用串流如Steam、Teamviewer、向日葵、Moonlight等