一、前言
1.1 问题思考
- 为什么需要加密 / 解密?
- 信息泄露可能造成什么影响?
二、 基础回顾
2.1 加密技术
加密技术是最常用的安全保密手段,利用技术手段把重要的数据变为乱码(加密)传送,到达目的地后再用相同或不同的手段还原(解密)。
加密技术包括两个元素:算法和密钥。算法是将普通的信息或者可以理解的信息与一串数字(密钥)结合,产生不可理解的密文的步骤,密钥是用来对数据进行编码和解密的一种算法。在安全保密中,可通过适当的钥加密技术和管理机制来保证网络的信息通信安全。简言之:
- 算法:加密 / 解密所使用的转换规则
- 密钥:加密 / 解密所使用的指令或代码
2.2 加密的目的与方式
加密目的:就是为了保护数据在存储状态下和在传输过程中,不被窃取、解读和利用。简单的说:确保数据的机密性和保护信息的完整性;
加密的方式:包括单向散列加密、对称加密、非对称加密三种。
2.3 加密方式简述
方式一:单向散列加密
根据输入长度信息进行散列计算,得到固定长度输出,常用于密码保存,常见的是MD5,SHA等,通常会加盐处理;
特点: 加密效率高、单方向加密
安全性:不安全(相对于对称加密)
使用情况:比较主流的加密方式
方式二:对称加密
采用单钥密码系统加密方法,同一个密钥可以同时用作信息的加密和解密。常见有AES
特点:加密效率高、双方使用的密钥相同
安全性:不安全(相对于非对称加密)
使用情况:比较主流的加密方式
方式三:非对称加密
加密和解密使用的是不同的秘钥,其中一个对外公开,称为公钥,另一个被称为私钥。若使用公钥对数据进行加密,则只有使用对应的私钥才能解密,反之亦然。常见的有RSA。
关于密钥
- 公钥:任何人都可以持有,一般用于加密作用
- 私钥:只有自己持有,一般用于数字签名,签名的数据,可以证明是私钥持有人发送的数据,私钥签名的数据,私钥持有人无法否认自己发送这个消息
特点
- 公钥加密的只有对应的私钥能解开
- 加密解密效率很低,一般不做大量数据加解密使用
安全性较高,使用的时候,一般配合对称机密使用,建立之初先使用非对称加密,协商好对称加密的算法和密钥,然后使用对称加密,进行后续加解密。
比如:使用对称加密加密很大的数据,非对称加密可以加密对称加密的密钥,报文一起传输,这样既保证了安全性,又保证了加密效率。
三、RSA加密实践
3.1 RSA 加解密工具类
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
/**
* @contract: 公众号:技术能量站
* @desc: RSA加密和解密工具-普通版
*/
public class RSAUtil01 {
/**
* 数字签名,密钥算法
*/
private static final String RSA_KEY_ALGORITHM = "RSA";
/**
* 数字签名签名/验证算法
*/
private static final String SIGNATURE_ALGORITHM = "MD5withRSA";
/**
* 公钥 key
*/
private static final String PUBLIC_KEY = "RSAPublicKey";
/**
* 私钥 key
*/
private static final String PRIVATE_KEY = "RSAPrivateKey";
/**
* RSA密钥长度,RSA算法的默认密钥长度是1024密钥长度必须是64的倍数,在512到65536位之间
*/
private static final int KEY_SIZE = 1024;
/**
* 生成密钥对
*/
private static Map<String, String> initKey() throws Exception {
KeyPairGenerator keygen = KeyPairGenerator.getInstance(RSA_KEY_ALGORITHM);
SecureRandom secrand = new SecureRandom();
/**
* 初始化随机产生器
*/
secrand.setSeed("initSeed".getBytes());
/**
* 初始化密钥生成器
* 设置密钥对的bit数 越大越安全
*/
keygen.initialize(KEY_SIZE, secrand);
KeyPair keys = keygen.genKeyPair();
// 获取公钥
byte[] pub_key = keys.getPublic().getEncoded();
String publicKeyString = Base64.encodeBase64String(pub_key);
// 获取私钥
byte[] pri_key = keys.getPrivate().getEncoded();
String privateKeyString = Base64.encodeBase64String(pri_key);
Map<String, String> keyPairMap = new HashMap<>();
keyPairMap.put(PUBLIC_KEY, publicKeyString);
keyPairMap.put(PRIVATE_KEY, privateKeyString);
return keyPairMap;
}
/**
* 密钥转成字符串
*
* @param key
* @return
*/
public static String encodeBase64String(byte[] key) {
return Base64.encodeBase64String(key);
}
/**
* 密钥转成byte[]
*
* @param key
* @return
*/
public static byte[] decodeBase64(String key) {
return Base64.decodeBase64(key);
}
/**
* 公钥加密
*
* @param data 加密前的字符串
* @param publicKey 公钥
* @return 加密后的字符串
* @throws Exception
*/
public static String encryptByPubKey(String data, String publicKey) throws Exception {
byte[] pubKey = RSAUtil01.decodeBase64(publicKey);
byte[] enSign = encryptByPubKey(data.getBytes(), pubKey);
return Base64.encodeBase64String(enSign);
}
/**
* 公钥加密
*
* @param data 待加密数据
* @param pubKey 公钥
* @return
* @throws Exception
*/
public static byte[] encryptByPubKey(byte[] data, byte[] pubKey) throws Exception {
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(pubKey);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
/**
* 私钥加密
*
* @param text 加密前的字符串
* @param privateKey 私钥
* @return 加密后的字符串
* @throws Exception
*/
public static String encryptByPriKey(String text, String privateKey) {
try {
byte[] priKey = RSAUtil01.decodeBase64(privateKey);
byte[] enSign = encryptByPriKey(text.getBytes(), priKey);
return Base64.encodeBase64String(enSign);
} catch (Exception e) {
throw new RuntimeException("加密字符串[" + text + "]时遇到异常", e);
}
}
/**
* 私钥加密
*
* @param data 待加密的数据
* @param priKey 私钥
* @return 加密后的数据
* @throws Exception
*/
public static byte[] encryptByPriKey(byte[] data, byte[] priKey) throws Exception {
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(priKey);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 公钥解密
*
* @param data 待解密的数据
* @param pubKey 公钥
* @return 解密后的数据
* @throws Exception
*/
public static byte[] decryptByPubKey(byte[] data, byte[] pubKey) throws Exception {
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(pubKey);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
/**
* 公钥解密
*
* @param data 解密前的字符串
* @param publicKey 公钥
* @return 解密后的字符串
* @throws Exception
*/
public static String decryptByPubKey(String data, String publicKey) throws Exception {
byte[] pubKey = RSAUtil01.decodeBase64(publicKey);
byte[] design = decryptByPubKey(Base64.decodeBase64(data), pubKey);
return new String(design);
}
/**
* 私钥解密
*
* @param data 待解密的数据
* @param priKey 私钥
* @return
* @throws Exception
*/
public static byte[] decryptByPriKey(byte[] data, byte[] priKey) throws Exception {
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(priKey);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 私钥解密
*
* @param secretText 解密前的字符串
* @param privateKey 私钥
* @return 解密后的字符串
* @throws Exception
*/
public static String decryptByPriKey(String secretText, String privateKey) {
try {
byte[] priKey = RSAUtil01.decodeBase64(privateKey);
byte[] design = decryptByPriKey(Base64.decodeBase64(secretText), priKey);
return new String(design);
} catch (Exception e) {
throw new RuntimeException("解密字符串[" + secretText + "]时遇到异常", e);
}
}
/**
* RSA签名
*
* @param data 待签名数据
* @param priKey 私钥
* @return 签名
* @throws Exception
*/
public static String sign(byte[] data, byte[] priKey) throws Exception {
// 取得私钥
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(priKey);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
// 生成私钥
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
// 实例化Signature
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
// 初始化Signature
signature.initSign(privateKey);
// 更新
signature.update(data);
return Base64.encodeBase64String(signature.sign());
}
/**
* RSA校验数字签名
*
* @param data 待校验数据
* @param sign 数字签名
* @param pubKey 公钥
* @return boolean 校验成功返回true,失败返回false
*/
public boolean verify(byte[] data, byte[] sign, byte[] pubKey) throws Exception {
// 实例化密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
// 初始化公钥
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(pubKey);
// 产生公钥
PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
// 实例化Signature
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
// 初始化Signature
signature.initVerify(publicKey);
// 更新
signature.update(data);
// 验证
return signature.verify(sign);
}
public static void main(String[] args) {
try {
Map<String, String> keyMap = initKey();
String publicKeyString = keyMap.get(PUBLIC_KEY);
String privateKeyString = keyMap.get(PRIVATE_KEY);
System.out.println("公钥:" + publicKeyString);
System.out.println("length: " + publicKeyString.length());
System.out.println("私钥:" + privateKeyString);
System.out.println("length: " + privateKeyString.length());
// 待加密数据
String data = "admin123";
// 公钥加密
String encrypt = RSAUtil01.encryptByPubKey(data, publicKeyString);
// 私钥解密
String decrypt = RSAUtil01.decryptByPriKey(encrypt, privateKeyString);
System.out.println("加密前:" + data);
System.out.println("明文length:" + data.length());
System.out.println("加密后:" + encrypt);
System.out.println("解密后:" + decrypt);
} catch (Exception e) {
e.printStackTrace();
}
}
}
如图所示, 生成一对1024 bit 的 RSA 密钥对,并加解密成功。
3.2 深入实践案例探索
案例一:报文长度过长加解密失败
测试发现当明文过长时,加密异常,返回如下报错
原因分析: RSA 加解密时,对加密的数据大小有限制,最大不大于密钥长度。
在使用 1024 位的密钥时,最大可以加密 1024/8 = 128字节的数据,此时需要对数据进行分组加密,分组加密后的加密串拼接成一个字符串返回给客户端。如果 Padding 方式使用默认的 OPENSSL_PKCS1_PADDING(需要占用11字节用于填充),则明文长度最多为 128 - 11 = 117 Bytes。
同理,当解密的密文超过128Byte时,也需要进行分组解密
案例二:分段加解密的的实现
实现代码:
/**
* 分段加密
*/
public static String encrypt(String plainText, String publicKeyStr) throws Exception {
System.out.println("明文lenth为"+plainText.length());
byte[] plainTextArray = plainText.getBytes();
PublicKey publicKey = getPublicKey(publicKeyStr);
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
int inputLen = plainTextArray.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
int i = 0;
byte[] cache;
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(plainTextArray, offSet, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(plainTextArray, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptText = out.toByteArray();
out.close();
return Base64.getEncoder().encodeToString(encryptText);
}
/**
* 分段解密
*/
public static String decrypt(String encryptTextHex, String privateKeyStr) throws Exception{
byte[] encryptText = Base64.getDecoder().decode(encryptTextHex);
PrivateKey privateKey = getPrivateKey(privateKeyStr);
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
int inputLen = encryptText.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(encryptText, offSet, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(encryptText, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_DECRYPT_BLOCK;
}
byte[] plainText = out.toByteArray();
out.close();
return new String(plainText);
}
案例三:当密钥长度非默认的1024,改为2048bit时,如何生成密钥、如何分段
如上文提到, 当密钥对改为 2048 位时, 最大加密明文大小 = 2048(bit) / 8 - 11(byte) = 245 byte
/**
* RSA最大加密明文大小
*/
private static final int MAX_ENCRYPT_BLOCK = 245;
/**
* RSA最大解密密文大小
*/
private static final int MAX_DECRYPT_BLOCK = 256;
/**
* 生成密钥对
*/
public static Map<String, Object> initKey() throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
//设置密钥对的bit数 越大越安全
keyPairGen.initialize(2048);
KeyPair keyPair = keyPairGen.generateKeyPair();
//获取公钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
//获取私钥
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
Map<String, Object> keyMap = new HashMap(2);
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return keyMap;
}
四、总结
4.1 核心点简述
- RSA加密默认密钥长度是1024,但是密钥长度必须是64的倍数,在512到65536位之间即可。
- RSA加密数据有长度限制,如果加密数据太长(大于密钥长度)会报错,此时的解决方案是 可以分段加密。
- RSA如果采用分段加密,当密钥对改为2048位时,RSA最大加解密文大小也需要调整:
- RSA密钥长度=1024时, 最大加密明文长度是117,解密明文长度是128;
- RSA密钥长度=2048时, 最大加密明文长度是245,解密明文长度是256;
4.2 PKCS1 和 PKCS8 的区别
PKCS#1格式
以-----BEGIN RSA PRIVATE KEY-----开头
以-----END RSA PRIVATE KEY-----结束
PKCS#8格式
以-----BEGIN PRIVATE KEY-----开头
以-----END PRIVATE KEY-----结束
通常JAVA中需要PKCS8 格式的密钥,在线 RSA 加解密网站:https://www.toolscat.com/decode/rsa 有助于提效。