> 文章列表 > Java RSA加解密算法学习

Java RSA加解密算法学习

Java RSA加解密算法学习

一、前言

1.1 问题思考

  1. 为什么需要加密 / 解密?
  2. 信息泄露可能造成什么影响?

二、 基础回顾

2.1 加密技术

加密技术是最常用的安全保密手段,利用技术手段把重要的数据变为乱码(加密)传送,到达目的地后再用相同或不同的手段还原(解密)。

加密技术包括两个元素:算法和密钥。算法是将普通的信息或者可以理解的信息与一串数字(密钥)结合,产生不可理解的密文的步骤,密钥是用来对数据进行编码和解密的一种算法。在安全保密中,可通过适当的钥加密技术和管理机制来保证网络的信息通信安全。简言之:

  • 算法:加密 / 解密所使用的转换规则
  • 密钥:加密 / 解密所使用的指令或代码
    Java RSA加解密算法学习

2.2 加密的目的与方式

加密目的:就是为了保护数据在存储状态下和在传输过程中,不被窃取、解读和利用。简单的说:确保数据的机密性和保护信息的完整性;
加密的方式:包括单向散列加密、对称加密、非对称加密三种。

2.3 加密方式简述

方式一:单向散列加密

根据输入长度信息进行散列计算,得到固定长度输出,常用于密码保存,常见的是MD5,SHA等,通常会加盐处理;
Java RSA加解密算法学习
特点: 加密效率高、单方向加密
安全性:不安全(相对于对称加密)
使用情况:比较主流的加密方式

方式二:对称加密

采用单钥密码系统加密方法,同一个密钥可以同时用作信息的加密和解密。常见有AES
Java RSA加解密算法学习
特点:加密效率高、双方使用的密钥相同
安全性:不安全(相对于非对称加密)
使用情况:比较主流的加密方式

方式三:非对称加密

加密和解密使用的是不同的秘钥,其中一个对外公开,称为公钥,另一个被称为私钥。若使用公钥对数据进行加密,则只有使用对应的私钥才能解密,反之亦然。常见的有RSA。
Java RSA加解密算法学习
关于密钥

  • 公钥:任何人都可以持有,一般用于加密作用
  • 私钥:只有自己持有,一般用于数字签名,签名的数据,可以证明是私钥持有人发送的数据,私钥签名的数据,私钥持有人无法否认自己发送这个消息

特点

  1. 公钥加密的只有对应的私钥能解开
  2. 加密解密效率很低,一般不做大量数据加解密使用

安全性较高,使用的时候,一般配合对称机密使用,建立之初先使用非对称加密,协商好对称加密的算法和密钥,然后使用对称加密,进行后续加解密。

比如:使用对称加密加密很大的数据,非对称加密可以加密对称加密的密钥,报文一起传输,这样既保证了安全性,又保证了加密效率。

三、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);// 实例化SignatureSignature signature = Signature.getInstance(SIGNATURE_ALGORITHM);// 初始化Signaturesignature.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);// 实例化SignatureSignature signature = Signature.getInstance(SIGNATURE_ALGORITHM);// 初始化Signaturesignature.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 密钥对,并加解密成功。
Java RSA加解密算法学习

3.2 深入实践案例探索

案例一:报文长度过长加解密失败

测试发现当明文过长时,加密异常,返回如下报错
Java RSA加解密算法学习
原因分析: 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 核心点简述

  1. RSA加密默认密钥长度是1024,但是密钥长度必须是64的倍数,在512到65536位之间即可。
  2. RSA加密数据有长度限制,如果加密数据太长(大于密钥长度)会报错,此时的解决方案是 可以分段加密。
  3. 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 有助于提效。