RSA算法应用 前端加密(AngularJS) 后台解密 (java)
1.加密算法的介绍
RSA算法非常可靠,密钥越长,它就越难破解。
根据已经披露的文献,目前被破解的最长RSA密钥是768个二进制位。也就是说,长度超过768位的密钥,还无法破解(至少没人公开宣布)。因此可以认为,1024位的RSA密钥基本安全,2048位的密钥极其安全。
2.对称加密和非对称加密的区别
对称加密:
介绍:甲方选择某一种加密规则加密,乙方使用同一种规则解密。
优点: 算法公开、计算量小、加密速度快、加密效率高
缺点:数据传输之前必须协定密钥规则;
假如一方泄露密钥,加密算法则不再安全;
用户每次匹配不同的密钥规则都要使用不一样的唯一密钥,造成密钥数量巨大,负担增加。
使用场景:消息发送方需要加密大量数据时使用
代表:DES、AES
DES: 密钥的长度64位,8个字节
AES:密钥的长度可以为128、192和256位,也就是16个字节、24个字节和32个字节。
非对称加密:
介绍: 私钥公钥互相加密解密数据(可以使用很多对密钥)。私钥自己保管不能外泄,公钥可以交给任何请求方。
优点: 安全
缺点: 速度慢
使用场景:信息加密、登录认证、 数字签名(防止信息伪造)、数字证书
代表:RSA
数字证书:
问题起源:对1和3,发信者怎么知道从网上获取的公钥就是真的?没有遭受中间人攻击?
这样就需要第三方机构来保证公钥的合法性,这个第三方机构就是CA(Certificate Authority),证书中心。
CA用自己的私钥对信息原文所有者发布的公钥和相关信息进行加密,得出的内容就是数字证书。
信息原文的所有者以后发布信息时,除了带上自己的签名,还带上数字证书,就可以保证信息不被篡改了。信息的接收者先用CA给的公钥解出信息所有者的公钥,这样可以保证信息所有者的公钥是真正的公钥,然后就能通过该公钥证明数字签名是否真实了。
3.RSA算法说明
RSA加密是一种非对称加密。可以在不直接传递密钥的情况下,完成解密。这能够确保信息的安全性,避免了直接传递密钥所造成的被破解的风险。是由一对密钥来进行加解密的过程,分别称为公钥和私钥。
两者之间有数学相关,该加密算法的原理就是对一极大整数做因数分解的困难性来保证安全性。通常个人保存私钥,公钥是公开的(可能同时多人持有)。
4.RSA算法、签名区别
相同点:两种都是为了安全性考虑
区别: 算法加密是为了防止信息被泄露
签名是为了防止信息被篡改
场景一:B要给A传递一条消息,内容为某一指令。
RSA加密过程:
1)A生成一对密钥(公钥和私钥),私钥不公开,A自己保留。公钥公开,任何人可以获取。
2)A传递自己的公钥给B,B用A的公钥对消息进行加密。
3)A接受到B加密的消息,利用A自己的私钥对消息进行解密。
这个过程中只有两次传递,第一次:A传递公钥给B 第二次 B传递加密消息给A
即使消息被截获,因为只有A的私钥才能对消息进行解密,防止了消息内容的泄露。
场景二:A收到B发的消息后,需要进行回复“收到”。
RSA签名过程:
1)A生成一对密钥(公钥和私钥),私钥不公开,A自己保留。公钥公开,任何人可以获取。
2)A用自己的私钥对消息加签,形成签名,并对加签的消息和本身一起传递。
3)B收到消息后,在获取A的公钥进行验证,如果验证出来的内容和消息本身一致,证明消息是A回复的。
在这个过程中只有2次传递过程,第一次:A传递加签的消息和消息本身给B 第二次:B获取A的公钥,即使都被截获,也没有危险性,因为只有A的私钥才能对消息进行签名,即使知道了消息内容,也无法伪造带签名的回复给B,防止了消息内容的篡改。
5.RSA重要说明
Cipher(类):
为加密和解密提供密码功能
常见的几种模式:
ENCRYPT_MODE:加密模式
DECRYPT_MODE:解密模式
PUBLIC_KEY: 公钥
PRIVATE_KEY: 私钥
SECRET_KEY: 密钥
Base64(类):
Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。
Base64一般用于在HTTP协议下传输二进制数据,由于HTTP协议是文本协议,所以在HTTP协议下传输二进制数据需要将二进制数据转换为字符数据。然而直接转换是不行的。因为网络传输只能传输可打印字符。
什么是可打印字符?
在ASCII码中规定,0~31、127这33个字符属于控制字符,32~126这95个字符属于可打印字符,也就是说网络传输只能传输这95个字符,不在这个范围内的字符无法传输。那么该怎么才能传输其他字符呢?其中一种方式就是使用Base64
KeyFactory(类):
密钥工厂用于将密钥(Key 类型的不透明加密密钥)转换成密钥规范(底层密钥材料的透明表示)
重要参数:algorithm (密钥算法)
编码规范:DSAPublicKeySpec | X509EncodedKeySpec 两种编码规范可以互相转换
KeyPairGenerator(类):
生成 public/private 密钥对
-----------------------------------------------------------------------------------------------------
RSA加密算法要求:
密钥长度(n) 明文长度(m) 密文长度(w)
大小限制:0<m<n=w
如果m>n,就需要进行Padding。
考虑问题:明文以0作为结束,考虑二进制很难判定是否是内容结束符还是内容,所以引进padding
目前PKCS#1 Padding建议的字节长度为11
-----------------------------------------------------------------------------------------------------
PKCS(公钥密码学标准)
PKCS是基于二进制和ASCII编码来设计的,已经发布的标准有PKCS #1, #3, #5, #7, #8, #9, #10 #11, #12, and #15
PKCS包括算法指定(algorithm-specific)和算法独立(algorithm-independent)两种实现标准。
多种算法被支持,包括RSA算法和 Diffie-Hellman 密钥交换算法,然而只有后两种才特别详尽。
以下是公钥加密标准(PKCS):
PKCS #1 定义了基于RSA公钥密码系统 加密和签名数据的机制(重要)
PKCS #5 描述了一种 通过从密码衍生出密钥来加密字符串的方法
PKCS #7 为信息定义了大体语法,包括加密增强功能产生的信息,如数字签名和加密
PKCS #8 描述了私钥信息的格式,这个信息包括某些公钥算法的私钥,和一些可选的属性(重要)
-----------------------------------------------------------------------------------------------------
6.RSA加密填充模式
1.RSA_PKCS1_PADDING (填充模式,最常用的模式)
要求:
输入:必须 比 RSA 钥模长(modulus) 短至少11个字节, 也就是 RSA_size(rsa) – 11
如果输入的明文过长,必须切割, 然后填充输出:和modulus一样长
2.RSA_PKCS1_OAEP_PADDING
输入:RSA_size(rsa) – 41
输出:和modulus一样长
3.for RSA_NO_PADDING
输入:可以和RSA钥模长一样长,如果输入的明文过长,必须切割, 然后填充
输出:和modulus一样长
7.RSA解密乱码问题
Cipher cipher = Cipher.getInstance(RSA_Encrypt,provider);
修改为:
Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding",provider)
8.RSA 入门demo
import com.auth0.jwt.internal.org.apache.commons.codec.binary.Base64;
import et.test.database.base.TestEnvironment;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Test;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
/**
* @description:
* @author:
* @time: 2022/2/14 10:30
*/
public class RSAUtil {
public static final String CHARSET = "UTF-8";
private static final String RSA_ALGORITHM = "RSA";
//RSA解密模式(为了解决解密乱码问题) 算法/模式/填充模式
private static final String RSA_Encrypt = "RSA/None/PKCS1Padding";
private static final int keySize = 1024;
public static Map<String, String> createKeys() {
//添加提供者
BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);
// 产生用于安全加密的随机数
SecureRandom random = new SecureRandom();
// 为RSA算法创建一个KeyPairGenerator对象
KeyPairGenerator kpg;
try {
//生成指定类型的密钥对
//getInstance(String algorithm, String provider) provider 属于BouncyCastleProvider类
//getInstance(String algorithm)
kpg = KeyPairGenerator.getInstance(RSA_ALGORITHM,provider);
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException("No such algorithm-->[" + RSA_ALGORITHM + "]");
}
// 初始化KeyPairGenerator对象,密钥长度
kpg.initialize(keySize, random);
// 生成密匙对
KeyPair keyPair = kpg.generateKeyPair();
// 得到公钥
Key publicKey = keyPair.getPublic();
//转换成UTF-8的编码类型
String publicKeyStr = Base64.encodeBase64URLSafeString(publicKey.getEncoded());
// 得到私钥
Key privateKey = keyPair.getPrivate();
String privateKeyStr = Base64.encodeBase64URLSafeString(privateKey.getEncoded());
// map装载公钥和私钥
Map<String, String> keyPairMap = new HashMap<String, String>();
keyPairMap.put("publicKey", publicKeyStr);
keyPairMap.put("privateKey", privateKeyStr);
// 返回map
return keyPairMap;
}
/**
* 获取公钥
* @param publicKey 密钥字符串(经过base64编码)
* @return
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
*/
public static RSAPublicKey getPublicKey(String publicKey) throws Exception {
//初始化RSA模式工厂
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
//解码 公钥 转化成二进制
byte[] decodeBase64 = Base64.decodeBase64(publicKey);
//用X509规范 修饰公钥
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(decodeBase64);
//密钥工厂获取公钥
RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(x509EncodedKeySpec);
return key;
}
/**
* 得到私钥
* @param privateKey 密钥字符串(经过base64编码)
* @throws Exception
*/
public static RSAPrivateKey getPrivateKey(String privateKey) throws Exception {
//初始化RSA模式工厂
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
//解码 私钥 转化成二进制
byte[] decodeBase64 = Base64.decodeBase64(privateKey);
//用公钥加密标准PKCS#8规范 修饰私钥
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(decodeBase64);
//密钥工厂获取私钥
RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
return key;
}
/**
* 公钥加密
* @param data
* @param publicKey
* @return
*/
public static String publicEncrypt(String data, RSAPublicKey publicKey) {
try {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), publicKey.getModulus().bitLength()));
} catch (Exception e) {
throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);
}
}
/**
* 公钥解密
* @param data
* @param publicKey
* @return
*/
public static String publicDecrypt(String data, RSAPublicKey publicKey) {
try {
Cipher cipher = Cipher.getInstance(RSA_Encrypt);
cipher.init(Cipher.DECRYPT_MODE, publicKey);
return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), publicKey.getModulus().bitLength()), CHARSET);
} catch (Exception e) {
throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
}
}
/**
* 私钥加密
* @param data
* @param privateKey
* @return
*/
public static String privateEncrypt(String data, RSAPrivateKey privateKey) {
try {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
//每个Cipher初始化方法使用一个模式参数opmod,并用此模式初始化Cipher对象。此外还有其他参数,包括密钥key、包含密钥的证书certificate、算法参数params和随机源random。
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), privateKey.getModulus().bitLength()));
} catch (Exception e) {
throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);
}
}
/**
* 私钥解密
* @param data
* @param privateKey
* @return
*/
public static String privateDecrypt(String data, RSAPrivateKey privateKey) {
try {
BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);
// Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
Cipher cipher = Cipher.getInstance(RSA_Encrypt,provider);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), privateKey.getModulus().bitLength()), CHARSET);
} catch (Exception e) {
throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
}
}
/**
* rsa切割编码 转换成二进制输出 , ENCRYPT_MODE,加密数据 ,DECRYPT_MODE,解密数据
* RSA最多只能加密117个字节 超出117字节的编码要进行切割处理 然后再统一用Base64 进行处理
* @param cipher
* @param opmode
* @param datas
* @param keySize
* @return
*/
private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize) {
int maxBlock = 0; //最大块
if (opmode == Cipher.DECRYPT_MODE) {
maxBlock = keySize / 8;
} else {
maxBlock = keySize / 8 - 11;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] buff;
int i = 0;
try {
while (datas.length > offSet) {
if (datas.length - offSet > maxBlock) {
//可以调用以下的doFinal()方法完成加密或解密数据:
buff = cipher.doFinal(datas, offSet, maxBlock);
} else {
buff = cipher.doFinal(datas, offSet, datas.length - offSet);
}
out.write(buff, 0, buff.length);
i++;
offSet = i * maxBlock;
}
} catch (Exception e) {
throw new RuntimeException("加解密阀值为[" + maxBlock + "]的数据时发生异常", e);
}
byte[] resultDatas = out.toByteArray();
//关闭IO流
IOUtils.closeQuietly(out);
return resultDatas;
}
public static void main(String[] args) throws Exception {
Map<String, String> keyMap = RSAUtil.createKeys();
String publicKey = keyMap.get("publicKey");
String privateKey = keyMap.get("privateKey");
System.out.println("公钥: \n\r" + publicKey);
System.out.println("私钥: \n\r" + privateKey);
System.out.println("公钥加密——私钥解密");
String str = "我和你,你和他,他和我";
System.out.println("\r明文:\r\n" + str);
String encodedData = RSAUtil.publicEncrypt(str, RSAUtil.getPublicKey(publicKey)); //传入明文和公钥加密,得到密文
System.out.println("密文:\r\n" + encodedData);
String decodedData = RSAUtil.privateDecrypt(encodedData, RSAUtil.getPrivateKey(privateKey)); //传入密文和私钥,得到明文
System.out.println("解密后文字: \r\n" + decodedData);
}
}
9.系统集成RSA的初步方案
9.1 angular JS rsa加密
引入jsencrypt.min.js
npm install --save jsencrypt
将生成的jsencrypt.min.js 放于js目录下管理,登录页面引入js
<script src="build/lib/jsencrypt.min.js"></script>
js代码
// 进行RSA加密,publicKey是公钥。
function rsaEncryption(publicKey, password) {
publicKey =
"-----BEGIN PUBLIC KEY-----" + publicKey + "-----END PUBLIC KEY-----";
var encrypt = new JSEncrypt();
encrypt.setPublicKey(publicKey);
return encrypt.encrypt(password);
};
测试----代码实现
// 进行RSA加密,publicKey是公钥。
$scope.rsaEncryption=function(publicKey, password) {
var encrypt = new JSEncrypt();
publicKey = "-----BEGIN PUBLIC KEY-----\n" +
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApVtqVfgSQ6hgyV+bmemY\n" +
"IdY7KVptVkRczQJVo9TKgD6l4tUs9bn1hxAmwRkERgotsviABo6Lx9IlqdTPQKg7\n" +
"3HUmbXbSPtiYQR/5M0CaGHx8XG+MbqE6oN54W8ecKvqe24qAiA/Wzb3oS2A9g2jl\n" +
"OKi035otQfc4runPe5i/fS/TN+ZrPk/I8/yD6HntzaG4mU+ipIM3oYQ4uyMgxAgB\n" +
"XOWlqONoyHELcAJQH3OoJci+i9ULs2EWEqo/r50SO/HgkYpEUtzOjyeoLtadb8XB\n" +
"rCvAeXy7D/lU7lIOyRfbDm1hAHgD4py3p1Hu4Rl8TeaVBPnA4+PvgnyWxUdq60J2\n" +
"DwIDAQAB\n" +
"-----END PUBLIC KEY-----"
encrypt.setPublicKey(publicKey);
password = "123456";
console.log("未加密密码:" + password);
console.log("正在加密....");
var code = encrypt.encrypt(password);
console.log("加密完成....");
console.log("加密后的密码为:" + password);
if (code === false) {
console.log("原密码为:" + password);
} else {
console.log("加密后的密码为:" + code);
}
return encrypt.encrypt(str);
}
----------------------------------超过117个字节,就切分开分别进行加密-----------------------------------
//1.判断JSEncrypt的类型是否为function类型
//2.判断是否存在 rsa 存在则使用 不存在则手动赋值
//3.超过117个字节,就切分开分别进行加密。
//4.如果加密失败就返回原数据
效果展示
9.2 Java rsa加密
需求分析:
接口:
1.返回公钥
实体类:
1.初始化密钥对
存在 直接使用 (获取指定路径下的.txt 文件)
不存在 生成新的密钥对
2.获取公钥
3.获取私钥
4.私钥解密
IOUtils.java
import java.io.*;
/**
* @description: IO 工具类, 读写文件
* @author: pzy
* @time: 2022/2/16 9:13
*/
public class IOUtil {
/**
* 单处写入 没有 不存在则创建文件这个逻辑
* RSA业务需要 否则存在代码渗入 工具类复用率低
* @param data
* @param file
* @throws IOException
*/
public static void writeFile(String data, File file) throws IOException {
OutputStream out = null;
try {
out = new FileOutputStream(file);
out.write(data.getBytes());
out.flush();
} finally {
close(out);
}
}
/**
*
* @param file
* @return
* @throws IOException
*/
public static String readFile(File file) throws IOException {
InputStream in = null;
ByteArrayOutputStream out = null;
try {
in = new FileInputStream(file);
out = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len = -1;
while ((len = in.read(buf)) != -1) {
out.write(buf, 0, len);
}
out.flush();
byte[] data = out.toByteArray();
return new String(data);
} finally {
close(in);
close(out);
}
}
/**
* 关闭流
* @param c
*/
public static void close(Closeable c) {
if (c != null) {
try {
c.close();
} catch (IOException e) {
// nothing
}
}
}
}
前端代码参考:
js引入:https://blog.youkuaiyun.com/sinat_28071063/article/details/109283926
js代码:https://www.cnblogs.com/daixiaotian/p/7305242.html?ivk_sa=1024320u
RSA,RSA2公钥私钥加密解密:https://www.bejson.com/enc/rsa/
后端代码参考:
https://www.cnblogs.com/m5n7xiao/p/9225065.html
https://blog.youkuaiyun.com/weixin_37380784/article/details/81017487
RSA非对称加解密速度慢且有长度限制,AES对称加解密速度快,如果需要安全传输较大的数据,一般 RSA 结合 AES 使用,就是所谓的 SSL