Java 密码学
时间:2023-02-12 14:30:00
Java 密码学
提示: 本材料仅供个人学习参考,不作为系统学习过程,请注意识别!
Java 密码学
- Java 密码学
- 一.编码算法
-
- 1.1 base64
- 1.2 URL编码
- 二.摘要算法
-
- 2.1 定义
- 2.2 使用场景
- 2.3 常见的算法
- 三.对称加密
-
- 3.1 定义
- 3.2 常见算法
- 3.3 分类
- 3.4 常用的块加密模式
- 3.5 块加密常用的填充模式
- 四.非对称加密
-
- 4.1 定义
- 4.2 为什么会出现?
- 4.3 常见算法
- 4.4 应用场景
一.编码算法
为了在网络中更方便地传输数据/本地存储字节数组,而不是加密和解密
1.1 base64
由A-Z、a-z、0-9、 、/由64个字符组成,base64以三个字节为一组,如果最后一组不到三个字节,则使用=号补充
Base 64的主要用途不是加密,而是将一些二进制数转换成普通字符,便于在网络上传输。 由于一些二进制字符属于传输协议中的控制字符,不能直接传输,因此需要转换。因为有些系统只能使用ASCII字符,Base64是用来将非的ASCII字符的数据转换成ASCII一种字符的方法,Base64特别适合在http,mime在协议下快速传输数据。例如,图片在网络中的传输。
Base64.不是安全领域的加密解密算法。虽然我们经常遇到所谓的加密解密算法,但我们经常遇到base64加密解密base64只能算是编码算法,适合传输数据内容。base64编码后,原文也变成了看不见的字符格式,但方法初级简单。
java代码:
package com.chuanglan.http; import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; import java.util.Base64; public class TestString {
public static void main(String[] args) throws Exception {
String str = "Base加密解密"; base64(str); enAndDeCode(str); } /** * Base64 */ public static void base64(String str) {
byte[] bytes = str.getBytes(); //Base64 加密 Sring encoded = Base64.getEncoder().encodeToString(bytes);
System.out.println("Base 64 加密后:" + encoded);
//Base64 解密
byte[] decoded = Base64.getDecoder().decode(encoded);
String decodeStr = new String(decoded);
System.out.println("Base 64 解密后:" + decodeStr);
System.out.println();
}
/** * BASE64加密解密 */
public static void enAndDeCode(String str) throws Exception {
String data = encryptBASE64(str.getBytes());
System.out.println("sun.misc.BASE64 加密后:" + data);
byte[] byteArray = decryptBASE64(data);
System.out.println("sun.misc.BASE64 解密后:" + new String(byteArray));
}
/** * BASE64解密 * @throws Exception */
public static byte[] decryptBASE64(String key) throws Exception {
return (new BASE64Decoder()).decodeBuffer(key);
}
/** * BASE64加密 */
public static String encryptBASE64(byte[] key) throws Exception {
return (new BASE64Encoder()).encodeBuffer(key);
}
}
1.2 URL编码
我们都知道Http协议中参数的传输是"key=value"这种键值对形式的,如果要传多个参数就需要用“&”符号对键值对进行分割。如"?name1=value1&name2=value2",这样在服务端在收到这种字符串的时候,会用“&”分割出每一个参数,然后再用“=”来分割出参数值。
例如: “name1=value1&name2=value2”我们来说一下客户端到服务端的概念上解析过程:
上述字符串在计算机中用ASCII吗表示为:
6E616D6531 3D 76616C756531 26 6E616D6532 3D 76616C756532。
6E616D6531:name1
3D:=
76616C756531:value1
26:&
6E616D6532:name2
3D:=
76616C756532:value2
服务端在接收到该数据后就可以遍历该字节流,首先一个字节一个字节的吃,当吃到3D这字节后,服务端就知道前面吃得字节表示一个key,继续向后,如果遇到26,说明从刚才吃的3D到26子节之间的是上一个key的value,以此类推就可以解析出客户端传过来的参数。
有个问题, 如果我的参数值中就包含=或&这种特殊字符的时候该怎么办。
例如: “name1=value1”,其中value1的值是“va&lu=e1”字符串,那么实际在传输过程中就会变成这样“name1=va&lu=e1”。我们的本意是就只有一个键值对,但是服务端会解析成两个键值对,这样就产生了奇异。
如何解决上述问题带来的歧义呢?解决的办法就是对参数进行URL编码
URL编码只是简单的在特殊字符的各个字节前加上%,例如,我们对上述会产生奇异的字符进行URL编码后结果:“name1=va%26lu%3D”,这样服务端会把紧跟在“%”后的字节当成普通的字节,就是不会把它当成各个参数或键值对的分隔符。
Java URL编码:
public static void main(String[] args) throws UnsupportedEncodingException {
String str = "java 学而不思则罔";
String encode = URLEncoder.encode(str, StandardCharsets.UTF_8.name());
System.out.println("encode:" + encode);//java+%E5%AD%A6%E8%80%8C%E4%B8%8D%E6%80%9D%E5%88%99%E7%BD%94
String decode = URLDecoder.decode(encode, StandardCharsets.UTF_8.name());
System.out.println("decode:" + decode);//学而不思则罔
}
二.摘要算法
2.1 定义
又叫Hash算法、散列函数、数字摘要、消息摘要,他是一种 单向算法,用户可以根据摘要算法对目标信息生成一段特定长度的唯一Hash值,但是不能通过这个Hash值重新获得目标信息
2.2 使用场景
密码 、信息完整性校验、数字签名
2.3 常见的算法
- MD5: 结果占128位,16个byte,转成16进制是32位
public static void main(String[] args) throws UnsupportedEncodingException {
String str = "java 学而不思则罔";
byte[] secretBytes = null;
try {
secretBytes = MessageDigest.getInstance("md5").digest(str.getBytes());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("没有这个md5算法!");
}
String md5code = new BigInteger(1, secretBytes).toString(16);
for (int i = 0; i < 32 - md5code.length(); i++) {
md5code = "0" + md5code;
}
System.out.println(md5code);//70eae8276e0b35f1cb67bc5c5f9111ce
}
- SHA: 安全散列算法,如:sha-256、sha-0、sha-1、sha-512
/** * 利用java原生的类实现SHA256加密 * @return */
public static void main(String[] args) throws UnsupportedEncodingException {
String str = "java 学而不思则罔";
MessageDigest messageDigest;
String encodeStr = "";
try {
messageDigest = MessageDigest.getInstance("SHA-256");
//SHA512加密
//messageDigest = MessageDigest.getInstance("SHA-512");
messageDigest.update(str.getBytes("UTF-8"));
encodeStr = byte2Hex(messageDigest.digest());
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
e.printStackTrace();
}
System.out.println(encodeStr);//df8312a2f52c5f5e36400a94c688ce278cb5b23026327b8371e7445286405927
}
/** * 将byte转为16进制 * * @param bytes * @return */
private static String byte2Hex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
String temp = null;
for (byte aByte : bytes) {
temp = Integer.toHexString(aByte & 0xFF);
if (temp.length() == 1) {
// 1得到一位的进行补0操作
sb.append("0");
}
sb.append(temp);
}
return sb.toString();
}
- MAC:消息认证码,是一种带有秘钥的Hash函数,兼容了MD和SHA算法的特性,并在此基础上加入了密钥。消息的散列值由只有通信双方知道的秘密密钥K来控制,因次,我们也常把MAC称为HMAC(keyed-Hash Message Authentication Code)。
MAC算法主要集合了MD和SHA两大系列消息摘要算法。MD系列的算法有HmacMD2、HmacMD4、HmacMD5三种算法;SHA系列的算法有HmacSHA1、HmacSHA224、HmacSHA256、HmacSHA384.HmacSHA512五种算法。
package com.tao.test;
import java.security.NoSuchAlgorithmException;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
/** * MAC算法工具类 * 对于HmacMD5、HmacSHA1、HmacSHA256、HmacSHA384、HmacSHA512应用的步骤都是一模一样的。具体看下面的代码 */
class MACCoder {
/** * 产生HmacMD5摘要算法的密钥 */
public static byte[] initHmacMD5Key() throws NoSuchAlgorithmException {
// 初始化HmacMD5摘要算法的密钥产生器
KeyGenerator generator = KeyGenerator.getInstance("HmacMD5");
// 产生密钥
SecretKey secretKey = generator.generateKey();
// 获得密钥
byte[] key = secretKey.getEncoded();
return key;
}
/** * HmacMd5摘要算法 * 对于给定生成的不同密钥,得到的摘要消息会不同,所以在实际应用中,要保存我们的密钥 */
public static String encodeHmacMD5(byte[] data, byte[] key) throws Exception {
// 还原密钥
SecretKey secretKey = new SecretKeySpec(key, "HmacMD5");
// 实例化Mac
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
//初始化mac
mac.init(secretKey);
//执行消息摘要
byte[] digest = mac.doFinal(data);
return new HexBinaryAdapter().marshal(digest);//转为十六进制的字符串
}
/** * 产生HmacSHA1摘要算法的密钥 */
public static byte[] initHmacSHAKey() throws NoSuchAlgorithmException {
// 初始化HmacMD5摘要算法的密钥产生器
KeyGenerator generator = KeyGenerator.getInstance("HmacSHA1");
// 产生密钥
SecretKey secretKey = generator.generateKey();
// 获得密钥
byte[] key = secretKey.getEncoded();
return key;
}
/** * HmacSHA1摘要算法 * 对于给定生成的不同密钥,得到的摘要消息会不同,所以在实际应用中,要保存我们的密钥 */
public static String encodeHmacSHA(byte[] data, byte[] key) throws Exception {
// 还原密钥
SecretKey secretKey = new SecretKeySpec(key, "HmacSHA1");
// 实例化Mac
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
//初始化mac
mac.init(secretKey);
//执行消息摘要
byte[] digest = mac.doFinal(data);
return new HexBinaryAdapter().marshal(digest);//转为十六进制的字符串
}
/** * 产生HmacSHA256摘要算法的密钥 */
public static byte[] initHmacSHA256Key() throws NoSuchAlgorithmException {
// 初始化HmacMD5摘要算法的密钥产生器
KeyGenerator generator = KeyGenerator.getInstance("HmacSHA256");
// 产生密钥
SecretKey secretKey = generator.generateKey();
// 获得密钥
byte[] key = secretKey.getEncoded();
return key;
}
/** * HmacSHA1摘要算法 * 对于给定生成的不同密钥,得到的摘要消息会不同,所以在实际应用中,要保存我们的密钥 */
public static String encodeHmacSHA256(byte[] data, byte[] key) throws Exception {
// 还原密钥
SecretKey secretKey = new SecretKeySpec(key, "HmacSHA256");
// 实例化Mac
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
//初始化mac
mac.init(secretKey);
//执行消息摘要
byte[] digest = mac.doFinal(data);
return new HexBinaryAdapter().marshal(digest);//转为十六进制的字符串
}
/** * 产生HmacSHA256摘要算法的密钥 */
public static byte[] initHmacSHA384Key() throws NoSuchAlgorithmException {
// 初始化HmacMD5摘要算法的密钥产生器
KeyGenerator generator = KeyGenerator.getInstance("HmacSHA384");
// 产生密钥
SecretKey secretKey = generator.generateKey();
// 获得密钥
byte[] key = secretKey.getEncoded();
return key;
}
/** * HmacSHA1摘要算法 * 对于给定生成的不同密钥,得到的摘要消息会不同,所以在实际应用中,要保存我们的密钥 */
public static String encodeHmacSHA384(byte[] data, byte[] key) throws Exception {
// 还原密钥
SecretKey secretKey = new SecretKeySpec(key, "HmacSHA384");
// 实例化Mac
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
//初始化mac
mac.init(secretKey);
//执行消息摘要
byte[] digest = mac.doFinal(data);
return new HexBinaryAdapter().marshal(digest);//转为十六进制的字符串
}
/** * 产生HmacSHA256摘要算法的密钥 */
public static byte[] initHmacSHA512Key() throws NoSuchAlgorithmException {
// 初始化HmacMD5摘要算法的密钥产生器
KeyGenerator generator = KeyGenerator.getInstance("HmacSHA512");
// 产生密钥
SecretKey secretKey = generator.generateKey();
// 获得密钥
byte[] key = secretKey.getEncoded();
return key;
}
/** * HmacSHA1摘要算法 * 对于给定生成的不同密钥,得到的摘要消息会不同,所以在实际应用中,要保存我们的密钥 */
public static String encodeHmacSHA512(byte[] data, byte[] key) throws Exception {
// 还原密钥
SecretKey secretKey = new SecretKeySpec(key, "HmacSHA512");
// 实例化Mac
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
//初始化mac
mac.init(secretKey);
//执行消息摘要
byte[] digest = mac.doFinal(data);
return new HexBinaryAdapter().marshal(digest);//转为十六进制的字符串
}
}
public class MACTest {
public static void main(String[] args) throws Exception {
String testString = "asdasd";
byte[] keyHmacMD5=MACCoder.initHmacMD5Key();
System.out.println(MACCoder.encodeHmacMD5(testString.getBytes(),keyHmacMD5));
byte[] keyHmacSHA1=MACCoder.initHmacSHAKey();
System.out.println(MACCoder.encodeHmacSHA(testString.getBytes(),keyHmacSHA1));
byte[] keyHmacSHA256=MACCoder.initHmacSHA256Key();
System.out.println(MACCoder.encodeHmacSHA256(testString.getBytes(),keyHmacSHA256));
byte[] keyHmacSHA384=MACCoder.initHmacSHA384Key();
System.out.println(MACCoder.encodeHmacSHA384(testString.getBytes(),keyHmacSHA384));
byte[] keyHmacSHA512=MACCoder.initHmacSHA512Key();
System.out.println(MACCoder.encodeHmacSHA512(testString.getBytes(),keyHmacSHA512));
}
}
其他: MD2、MD4、HAVAL
三.对称加密
3.1 定义
也叫单秘钥加密, 指加密和解密的过程使用的是相同的秘钥,相比于非对称加密只有一把钥匙,因此速度更快,更适合加解密大文件。
3.2 常见算法
- DES: 已经过时
- AES: 替换DES
那么为什么原来的DES会被取代呢,,原因就在于其使用56位密钥,比较容易被破解。而AES可以使用128、192、和256位密钥,并且用128位分组加密和解密数据,相对来说安全很多。完善的加密算法在理论上是无法破解的,除非使用穷尽法。使用穷尽法破解密钥长度在128位以上的加密数据是不现实的,仅存在理论上的可能性。统计显示,即使使用目前世界上运算速度最快的计算机,穷尽128位密钥也要花上几十亿年的时间,更不用说去破解采用256位密钥长度的AES算法了。
package com.personal.custom.redis.redisson.config;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class ZzSecurityHelper {
/* * 加密用的Key 可以用26个字母和数字组成 使用AES-128-CBC加密模式,key需要为16位。 */
private static final String key = "hj7x89H$yuBI0456";
private static final String iv = "NIfb&95GUY86Gfgh";
public static String encryptAES(String data) throws Exception {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
int blockSize = cipher.getBlockSize();
byte[] dataBytes = data.getBytes();
int plaintextLength = dataBytes.length;
if (plaintextLength % blockSize != 0) {
plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
}
byte[] plaintext = new byte[plaintextLength];
System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes()); // CBC模式,需要一个向量iv,可增加加密算法的强度
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
byte[] encrypted = cipher.doFinal(plaintext);
return ZzSecurityHelper.encode(encrypted).trim(); // BASE64做转码。
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static String decryptAES(String data) throws Exception {
try {
byte[] encrypted1 = ZzSecurityHelper.decode(data);//先用base64解密
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original);
return originalString.trim();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/** * 编码 * * @param byteArray * @return */
public static String encode(byte[] byteArray) {
return new String(new Base64().encode(byteArray));
}
/** * 解码 * * @param base64EncodedString * @return */
public static byte[] decode(String base64EncodedString) {
return new Base64().decode(base64EncodedString);
}
public static void main(String[] args) throws Exception {
String encode = ZzSecurityHelper.encryptAES("java 学而不思则罔");
System.out.println(encode);//+FP2MywPzn2vuj9ilUOSOJGpeaDe8eJOX/0xI7PSX0s=
String decode = ZzSecurityHelper.decryptAES(encode);
System.out.println(decode);//java 学而不思则罔
}
}
- 其他:3DES、IDEA、RC4、RC5、RC6等
3.3 分类
- 分组加密,又叫块加密
- 序列加密
3.4 块加密常用的加密模式
参考:对称加密算法常用的五种分组模式
3.5 块加密常用的填充模式
为什么要填充? 对于固定的加密算法,每个块都有固定的大小,例如8个byte,明文分块后,加密前需要保证最后一个块的大小是8个byte,如果不够则使用特定的数据进行填充。
- NoPadding: 不自动填充
DES时要求明文必须是8个字节的整数倍,AES时是16个字节的整数倍 - PKCS5Padding/PKCS7Padding
四.非对称加密
4.1 定义
加密和解密使用的是两种不同的秘钥(public key 和 private key),公钥可以给任何人,私钥总是自己保留
4.2 为什么会出现
对称加密和解密使用相同的秘钥,但是对于不同的原始内容加密会采用不同的秘钥,导致秘钥数量巨大,难以维护
4.3 常见算法
- RSA
public static void main(String[] args) throws Exception {
// rsa 加解密的工具类
RSAUtil usaUtil = new RSAUtil();
String privateKey = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAMBPkejIDlj1bzPZ\n" +
"cv+UbiwTfqXvi50B3w5jurpRwDROFVPDlm0DOzK13ucx8ddPRB2DKAUCkgLEj+SQ\n" +
"ptLgpQnwEUG+MIcqMQxWBzwIENAAH8kwmCTrB5HIPyOFOIQXfvfjdtdpz84VfEII\n" +
"565DGI3AWiKI/x/yuphCtKKEtNYHAgMBAAECgYAzaRIomNK9FV/VDJyhlpydS9jf\n" +
"o1o/bvCieCbDCHfJg9ZPvknIesSomdYtGDz+wkIDYbyhGj7OXp3ZDJKMbWGw/8fw\n" +
"vXKXZu3Q8E6JwTQ7nZhlMQpI0PzxsBitItFQIoORzHjKmizdNzSK/x/lJ1QVZWws\n" +
"ftyW72uaabMseRnegQJBAODMm0026q2NgPlMXLYipHCDpIfLPCgllSRuQzwjyvz1\n" +
"bnlLdquSyyZDTmnN+zGFKLLDwzg6EnxicvwJz1Vgh+0CQQDbAJwTaQ7no23un180\n" +
"+EUEckELO9C//jyB/6yvCTll/03MNNwkD8gxl9aT8/8turgktawSNw6whVtwFVfM\n" +
"V+9DAkARB884MVHkJhVATcW0UrmMgJylYQNEs1wyL1xOoROOyHU/ITVzWCKl2nGF\n" +
"WIKQRNtJd8VBbDzcSYUWjRO1DyQdAkBnNC5Y51Viy51uqiQPrj+4DK+iP5nsID8b\n" +
"dAVIpywpaNqctPxY8icBV/CC4KUMQ8WrZwGjw9ZkUTP56dTqMzZJAkAvdmv6n3fv\n" +
"qhzXe/6yoURoawTzLoP10WXyhMS1K8Qpa8trGX9AvpGPh5XQ5FHXeX7gXTDousM0\n" +
"74HQuyYAWBxC";
String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAT5HoyA5Y9W8z2XL/lG4sE36l\n" +
"74udAd8OY7q6UcA0ThVTw5ZtAzsytd7nMfHXT0QdgygFApICxI/kkKbS4KUJ8BFB\n" +
"vjCHKjEMVgc8CBDQAB/JMJgk6weRyD8jhTiEF37343bXac/OFXxCCOeuQxiNwFoi\n" +
"iP8f8rqYQrSihLTWBwIDAQAB";
String encrypt = usaUtil.encryptByPrivateKey(privateKey, "这个是RSA加密方式!");
System.out.println("私钥加密后: " + encrypt);//私钥加密后: dhgwuj+tuATooRaErx9qgNwtSjgQdhgerHQKeuvvUzOCCdx6vH0M4UATN0Kul2BUcJ+cbAMlR2a3816RRO/oZtCX9vgFEtyatCNhLtkiFhL98xiugMaHikZxT11iipYUwECrfkIG9J5LUgzXRyQPQ/Nz2yM09aNgXoL9/yPaOLc=
String decrypt = usaUtil.decryptByPublicKey(publicKey, encrypt);
System.out.println("公钥解密后: " + decrypt);//公钥解密后: 这个是RSA加密方式!
}
工具类:
package com.personal;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class RSAUtil {
private static Logger LOGGER = LoggerFactory.getLogger(RSAUtil.class);
private static String KEY_ALGORITHM = "RSA";
public static final int KEY_SIZE = 2048; // 密钥长度, 一般2048
public static final int RESERVE_BYTES = 11;
public static final String CIPHER_ALGORITHM = "RSA/ECB/PKCS1Padding"; // 加密block需要预留11字节
public static final String SIGNATURE_ALGORITHM = "MD5withRSA";// sign值生成方式
public static final Charset CHARSET_UTF8 = Charset.forName("UTF-8");
private String algorithm;// 密钥生成模式
private String signature;// 签名sign生成模式
private Charset charset;// 编码格式
private int keySize;// RSA密钥长度必须是64的倍数,在512~65536之间
private int decryptBlock; // 默认keySize=2048的情况下, 256 bytes
private int encryptBlock; // 默认keySize=2048的情况下, 245 bytes
private static KeyFactory keyFactory;
static {
try {
keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
} catch (NoSuchAlgorithmException e) {
LOGGER.info("生成RSA密钥对异常,{}", e);
}
}
public RSAUtil() {
this(CIPHER_ALGORITHM);
}
public RSAUtil(String algorithm) {
this(algorithm, CHARSET_UTF8);
}
public RSAUtil(int keySize) {
this(CIPHER_ALGORITHM, keySize, CHARSET_UTF8, SIGNATURE_ALGORITHM);
}
public RSAUtil(String algorithm, Charset charset) {
this(algorithm, KEY_SIZE, charset, SIGNATURE_ALGORITHM);
}
public RSAUtil(String algorithm, int keySize, Charset charset, String signature) {
this.algorithm = algorithm;
this.signature = signature;
this.charset = charset;
this.keySize = keySize;
this.decryptBlock = this.keySize / 8;
this.encryptBlock = decryptBlock - RESERVE_BYTES;
}
/** * 公钥解密 * * @param publicKeyStr 公钥字符串 * @param data 需要解密的数据 * @return 解密后字符串 * @throws Exception 异常 */
public String decryptByPublicKey(String publicKeyStr, String data) throws Exception {
PublicKey publicKey = restorePublicKey(publicKeyStr);
return decryptByPublicKey(publicKey, data);
}
/** * 公钥解密 * * @param publicKey 公钥 * @param data 需要解密的数据 * @return 解密后字符串 * @throws Exception 异常 */
public String decryptByPublicKey(PublicKey publicKey, String data) throws Exception {
byte[] bytes = Base64.decodeBase64(data.getBytes(charset));
return decryptByPublicKey(publicKey, bytes);
}
/** * 公钥解密 * * @param publicKey 公钥 * @param data 需要解密的数据 * @return 解密后字符串 * @throws Exception 异常 */
public String decryptByPublicKey(PublicKey publicKey, byte[] data) throws Exception {
byte[] decrypt = null;
int dataLen = data.length;
// 计算分段解密的block数 (理论上应该能整除)
int nBlock = (dataLen / encryptBlock);
if ((dataLen % encryptBlock) != 0) {
// 余数非0,block数再加1
nBlock += 1;
}
// 输出buffer, 大小为nBlock个encryptBlock
ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * encryptBlock);
Cipher cipher = getCipher();
cipher.init(Cipher.DECRYPT_MODE, publicKey);
// 分段解密
for (int offset = 0; offset < data.length; offset += decryptBlock) {
// block大小: decryptBlock 或 剩余字节数
int inputLen = (data.length - offset);
if (inputLen > decryptBlock) {
inputLen = decryptBlock;
}
// 得到分段解密结果
byte[] decryptedBlock = cipher.doFinal(data, offset, inputLen);
// 追加结果到输出buffer中
outbuf.write(decryptedBlock);
}
outbuf.flush();
decrypt = outbuf.toByteArray();
outbuf.close();
return StringUtils.newString(decrypt, charset.name());
}
/** * 私钥加密 * * @param privateKeyStr 私钥字符串 * @param data 需要加密的数据 * @return 加密后字符串 */
public String encryptByPrivateKey(String privateKeyStr, String data) {
PrivateKey privateKey = restorePrivateKey(privateKeyStr);
return encryptByPrivateKey(privateKey, data);
}
/** * 私钥加密 * * @param privateKey 私钥 * @param data 需要加密的数据 * @return 加密后字符串 */
public String encryptByPrivateKey(PrivateKey privateKey, String data) {
byte[] bytes = data.getBytes(charset);
return encryptByPrivateKey(privateKey, bytes);
}
/** * 私钥加密 * * @param privateKey 私钥 * @param data 需要加密的数据 * @return 加密后字符串 */
public String encryptByPrivateKey(PrivateKey privateKey, byte[] data) {
byte[] encrypt = null;
int dataLen = data.length;
// 计算分段加密的block数 (向上取整)
int nBlock = (dataLen / encryptBlock);
if ((dataLen % encryptBlock) != 0) {
// 余数非0,block数再加1
nBlock += 1;
}
// 输出buffer, 大小为nBlock个decryptBlock
ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * decryptBlock);
try {
Cipher cipher = getCipher();
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
// 分段加密
for (int offset = 0; offset < dataLen; offset += encryptBlock) {
// block大小: encryptBlock 或 剩余字节数
int inputLen = (dataLen - offset);
if (inputLen > encryptBlock) {
inputLen = encryptBlock;
}
// 得到分段加密结果
byte[] encryptedBlock = cipher.doFinal(data, offset, inputLen);
// 追加结果到输出buffer中
outbuf.write(encryptedBlock);
}
outbuf.flush();
encrypt = outbuf.toByteArray();
outbuf.close();
} catch (InvalidKeyException | BadPaddingException | IllegalBlockSizeException | IOException e) {
LOGGER.info("使用RSA,加密数据异常", e);
}
byte[] enData = Base64.encodeBase64(encrypt);
return StringUtils.newString(enData, charset.name());
}
/** * 根据keyFactory生成Cipher * * @return cipher */
private Cipher getCipher() {
Cipher cipher = null;
try {
cipher = Cipher.getInstance(this.algorithm);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
LOGGER.info("生成RSA Cipher异常", e);
}
return cipher;
}
/** * 公钥还原,将公钥转化为PublicKey对象 * * @param publicKeyStr 公钥字符串 * @return PublicKey对象 */
public PublicKey restorePublicKey(String publicKeyStr) {
PublicKey publicKey = null;
byte[] keyBytes = Base64.decodeBase64(publicKeyStr.getBytes(charset));
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
try {
publicKey = keyFactory.generatePublic(x509KeySpec);
} catch (InvalidKeySpecException e) {
LOGGER.info("公钥还原,将公钥转化为PublicKey对象异常", e);
}
return publicKey;
}
/** * 私钥还原,将私钥转化为privateKey对象 * * @param privateKeyStr 私钥字符串 * @return PrivateKey对象 */
public PrivateKey restorePrivateKey(String privateKeyStr) {
PrivateKey privateKey = null;
byte[] keyBytes = Base64.decodeBase64(privateKeyStr.getBytes(charset));
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes);
try {
privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
} catch (InvalidKeySpecException e) {
LOGGER.info("私钥还原,将私钥转化为privateKey对象异常", e);
}
return privateKey;
}
/** * RSA生成sign值 * * @param privateKeyStr 私钥字符串 * @param data 数据 * @return sign值 * @throws Exception 异常 */
public String generateSign(String privateKeyStr, String data) throws Exception {
return generateSign(restorePrivateKey(privateKeyStr), data);
}
/** * RSA生成sign值 * * @param privateKey 私钥 * @param data 数据 * @return sign值 * @throws Exception 异常 */
public String generateSign(PrivateKey privateKey, String data) throws Exception {
Signature signature = Signature.getInstance(this.signature);
signature.initSign(privateKey);
signature.update(data.getBytes(charset));
return Base64.encodeBase64String(signature.sign());
}
/** * RSA验签 * * @param publicKeyStr 公钥字符串 * @param data 数据 * @param sign sign值 * @return 验签是否成功 * @throws Exception 异常 */
public boolean verifyRSA(String publicKeyStr, String data, String sign) throws Exception {
return verifyRSA(restorePublicKey(publicKeyStr), data, sign);
}
/** * RSA验签 * * @param publicKey 公钥 * @param data 数据 * @param sign sign值 * @return 验签是否成功 * @throws Exception 异常 */
public boolean verifyRSA(PublicKey publicKey, String data, String sign) throws Exception {
Signature signature = Signature.getInstance(this.signature);
signature.initVerify(publicKey);
signature.update(data.getBytes(charset));
return signature.verify(Base64.decodeBase64(sign));
}
public static void main(String[] args) {
RSAUtil rSAUtil = new RSAUtil();
String data = "123456789";
String privateKey = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCXIyAYK+NVoJ7DJEgTs3GyXIUu0+HtYm2mArKh8yUIbuiSN4aksNb4xAh0cS1YNg0G7prR+CKV7iUo1f8sA0VtN32XHxd6Fr3Q4QsC/+FH6HHS6GGI9i363j4YGtOwGEOyb9qt9YjFHlVqDyZiNYDazK3f+EZzfputFwVfZSp0M2fQhgBCZM2lycKvq/OJm79NDKMeld/ejpvNAqi7xehKy6eqNjdRiRmLcZvg6dW/A7QwlkQ/vCos5Mtd/kLu6oHgjXnBFiGsKGDScJYBvVFHcupmnE0eqJtnwr8OBIn9naeVaAHE46i6uxZtTe4K6QUM0JtdODL70EEjy7qG0WjpAgMBAAECggEBAIhFmIOS1th3CY8j4IU679IFT+SIERZsADeGCTCyvfpbngFwZUuLU1lbz8/F2D/IBHjynM+jLvQGlKS8RuaVUH0IYonm89EWPjHfJ4Gd269ta2viMUc/yPeAeXZcgfAuAKQb7I2bbKVnE1acsFwup68gi8n83vD2AEHSFvsLiXrZFZr9MmNIIyRfYZJCDPunZOv7XCgsDhdlYAx+Z092MwpEeLPEIpgueMHycU7AsBgq667m4/LP0l9CnCxk/cRUz9w5iZRmlyLLvL8Tox8VCcCtoOb7cxZpm1TYCQSDcVfhtIf4BKWjKBiHiHM17z0t111dEUc+XkbxacnPxDgn/80CgYEA3VgYY/1rPC5CPqmMXXl6iGdjsbSkJWaGigx3QkaUMwYv6xCxCL6zReNqHchbp+mvqNgWD6HPDDZk5dJnJeHvAdNCA6AQhodoPDFpYc70WgTciNv84cVTUSj9RGlRD9jvYN3k+lm8yxvykOc0xBsqE93lyNYEBuPE4cAi4LNBfgsCgYEArsz/94gXyvi/I2yAapkq3l+ieE+L731tc7FqB4QYVxyxUOgEla03++bRAikzOjT9YbGNl85khweNNle5J++3GL+Ak4hXmwHw6e1ZP4dP1d9OiBio96jn5okMD8AIFU/Y/JIoBr1o+k9jBkbOWOyVO5KD1z6ijGDCVMhovuWssVsCgYEApXjQcx/m5QyoFXRnLRI92m+Ahj9HX3ZwKg/7sB5XeHWtqQvHbYQzPZIvqKg6bSM0YQN6KqGKydR4RZ+v4RAwv6qRdWhaMlhUQnumDqrK3ek4fVAIkzgTe18rR9N7+F7zRfVc0xP3Idh41H8kYV71a/i9ahEk3Ym1jBc5e8ZGtdUCgYBv7erVqPp7SM6zsy2DlLKDlD9nxJ/5aZplY6xeRbKETWYpRXhyE2nuzjz1okYgNoAtR1FAbLOoVyiQLJnuPaxDl5SQY9Sc+CA42ne0m0N+0q/pq8i+VRSxZP4pM7C5XNi32irxLeYDqkPhaAOHo25nqAjuEjhppSeqvG1+F3l+UwKBgQC6puG4Xr/w4FSyKgRsDAE2tBKBllqLUh2S/68ptHpB54uO+bN1MI+I4B6CFArHfJlmPO/Vc4dC4IC2byrDd0uLjWT+D+7EzFgygpSxyYW5be/qjY/AZo7LfUbHQa77/uhhvCPZQZgYk+FEIIPIcruWejJmkbHpYE+WBtcFrsfjtw==";
String publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlyMgGCvjVaCewyRIE7NxslyFLtPh7WJtpgKyofMlCG7okjeGpLDW+MQIdHEtWDYNBu6a0fgile4lKNX/LANFbTd9lx8Xeha90OELAv/hR+hx0uhhiPYt+t4+GBrTsBhDsm/arfWIxR5Vag8mYjWA2syt3/hGc36brRcFX2UqdDNn0IYAQmTNpcnCr6vziZu/TQyjHpXf3o6bzQKou8XoSsunqjY3UYkZi3Gb4OnVvwO0MJZEP7wqLOTLXf5C7uqB4I15wRYhrChg0nCWAb1RR3LqZpxNHqibZ8K/DgSJ/Z2nlWgBxOOoursWbU3uCukFDNCbXTgy+9BBI8u6htFo6QIDAQAB";
String cusPubKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtxUeho47bzGLK+IrxCtysogzeLehTZT3xbD8W9P+Na63eA06BnrDWaXpLG5NiTdis/hRHwnIgwiOwcn3OdPNx7OjlzrtvwutUWC6nMOSdDHvZYFaWHIyvyFhljUbTTgW2QSJpX+IR1sopVha0G1SBxNazbs/9TGihjl4tc4Jg2e8D6ScOSJGxo1DzysyLxchw3A+1VVwiBTLEy4xS4nL14z2XSMcRrj5Pl9dO7r1O7G3+d5ubFNoq1/Ql+SzKRaxaMaP87CUJUxCrPffhcTvE4vihPOb7okcRgl3REvXdp9VSCmeTCZ9Gm/mBScazqQoGBbzcTDuCvc1zRG05qsvlwIDAQAB";
String pub = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAh+mOu6wdFTBftNwBpLsc5uw8UsSA0q3o6TCzCdFQXMvGkm27vSLjrZnMiPV+L13D6S2FK53N/Fc/ClqoaAtWTxCVZaoPCV28g296FJbVyIqGKZ6gh6GT2YRiDDtmrZkbRMpqtZzmOPgSuKb1SRpY9JmtLWnN3NSxUhWsi+bcAdzA1mC8Y2WgcMFQOHEDISY3/YDufVj4pXutOs/El0+kIY35so7lO1qnWLgZ9PxSs+Hb+zU2Oe/YMjDHPfGj+OQ7xT0L0s6/VRe7nDalm0vWqZeEk+Gky9oKMqGvlA6MovNo3kS5pWJvFxhaqC88yVdmnJvgKO6FwoxUMh4CQtKreQIDAQAB";
System.out.println(privateKey.length());
System.out.println(publicKey.length());
System.out.println(cusPubKey.length());
data = rSAUtil.encryptByPrivateKey(privateKey, data);
try {
String datas = rSAUtil.decryptByPublicKey(publicKey, data);
System.out.println(datas.equals("123456789"));
System.out.println(datas);
System.out.println(pub.length());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
- ECC、Diffie-Hellman、EI Gamal、DSA
4.4 应用场景
- 加解密
可以通过公钥加密,对应的就是私钥解密; 也可以使用私钥加密, 对应的就是公钥解密 - 数字签名
发送方A: 原始内容 --> 把原始内容用发送方的私钥加密 --> 通过摘要算法获取私钥加密后的hash值str1 --> 数字签名
接收方B: 对接收到的密文进行摘要算法得到str2,比较str2和str1是否相同(验签,防篡改) --> 验签成功 --> 用发送方的公钥解密接收到的内容,得到原始内容 - 数字信封
- 数字证书