对称加密算法详解和 DES/3DES 理解和实战
Posted 一个八月想偷懒的开发坑
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了对称加密算法详解和 DES/3DES 理解和实战相关的知识,希望对你有一定的参考价值。
前言
开发中包装一条鉴权使用了 3DES 作为数据加密方式,并详细学习了下对称加密的知识,加密体验中偶遇了几个坑,唉,记录一下不好的使用体验。
对称加密算法
对称密码算法是当今应用范围最广,使用频率最高的加密算法。它不仅应用于软件行业,在硬件行业同样流行。各种基础设施凡是涉及到安全需求,都会优先考虑对称加密算法。
对称密码算法的加密密钥和解密密钥相同,对于大多数对称密码算法,加解密过程互逆。
加解密通信模型
特点:算法公开、计算量小、加密速度快、加密效率高。
弱点:双方都使用同样密钥,安全性得不到保证。对称加密有流密码和分组密码两种,但是现在普遍使用的是分组密码。
分组密码加密模式
ECB:电子密码本(最常用的,每次加密均产生独立的密文分组,并且对其他的密文分组不会产生影响,也就是相同的明文加密后产生相同的密文)
CBC:加密块链(常用的,明文加密前需要先和前面的密文进行异或运算,也就是相同的明文加密后产生不同的密文)
ECB 模式是一种最古老,最简单的模式,将加密的数据分成若干组,每组的大小跟加密密钥长度相同;然后每组都用相同的密钥加密,如果最后一个分组长度不够64位,要补齐 64 位。
ECB 模式的特点是:每次 Key、明文、密文的长度都必须是 64 位;数据块重复排序不需要检测;相同的明文块(使用相同的密钥)产生相同的密文块,容易遭受字典攻击;一个错误仅仅会对一个密文块产生影响。
CBC 模式与 ECB 模式最大的不同是加入了初始向量。
CBC 模式的特点是:每次加密的密文长度为64位(8个字节);当相同的明文使用相同的密钥和初始向量的时候 CBC 模式总是产生相同的密文;密文块要依赖以前的操作结果,所以,密文块不能进行重新排列;可以使用不同的初始化向量来避免相同的明文产生相同的密文,一定程度上抵抗字典攻击;一个错误发生以后,当前和以后的密文都会被影响。
除了这两种常用的加密模式,还有:
CFB:密文反馈
OFB:输出反馈
CTR:计数器
这五种加密模式主要是密码学中算法在进行推导演算的时候所应用到的。
分组密码填充方式
NoPadding:无填充
PKCS5Padding
ISO10126Padding
常见的填充方式 PKCS5Padding,PKCS5Padding 表示当数据位数不足的时候要采用的数据补齐方式,也可以叫做数据填充方式。PKCS5Padding 这种填充方式,具体来说就是 "填充的数字代表所填字节的总数" 比如说,差两个字节,就是 ######22,差5个字节就是 ###55555,这样根据最后一个自己就可以知道填充的数字和数量。
常用对称密码
DES(Data Encryption Standard,数据加密标准)
3DES(Triple DES、DESede,进行了三重DES加密的算法)
AES(Advanced Encryption Standard,高级数据加密标准,AES算法可以有效抵制针对DES的攻击算法)
DES 加密算法
DES 全称为 Data EncryptionStandard,即数据加密标准。DES 加密算法是一种对称加密算法,所谓对称加密算法就是指对明文的加密以及对密文的解密用的是同一个密钥。
DES 使用一个 56 位的密钥以及附加的 8 位奇偶校验位,产生最大 64 位的分组大小。这是一个迭代的分组密码,使用称为 Feistel 的技术,其中将加密的文本块分成两半。使用子密钥对其中一半应用循环功能,然后将输出与另一半进行 "异或" 运算;接着交换这两半,这一过程会继续下去,但最后一个循环不交换。DES 使用 16 个循环,使用异或,置换,代换,移位操作四种基本运算。
Des算法的入口参数有三个:Key、Data、Mode。
Key: 为 8 个字节共 64 位,DES 算法规定,其中第 8、16、24、......64 位是奇偶校验位,不参与 DES 运算,所以常说 DES 的密钥为 56 位。 在 DES 加密和解密的过程当中,密钥的长度都必须是 8 字节的倍数。
Data: 8 个字节 64 位,是要被加密后解密的数据。
Mode: DES 的工作方式,加密、解密。
DES 安全性
安全性比较高的一种算法,目前只有一种方法可以破解该算法——穷举法。
采用 64 位密钥技术,实际只有 56 位有效,8 位用来校验的。譬如,有这样的一台 PC 机器,它能每秒计算一百万次,那么 256 位空间它要穷举的时间为 2285 年,所以这种算法还是比较安全的一种算法。
TripleDES,该算法被用来解决使用 DES 技术的 56 位时密钥日益减弱的强度,其方法是:使用两个独立密钥对明文运行 DES 算法三次,从而得到 112 位有效密钥强度。TripleDES 有时称为 DESede(表示加密、解密和加密这三个阶段)。
Java DES 工具类(建议使用3DES加密算法)
/**
* DES 生成密钥
*/
public static String product3DesKey() throws Exception {
KeyGenerator keyGen = KeyGenerator.getInstance("DES");
keyGen.init(56);
SecretKey secretKey = keyGen.generateKey();
return byteArrToHexStr(secretKey.getEncoded()).toUpperCase();
}
/**
* DES加密字符串
*
* @param encryptString
* 待加密的字符串
* @param encryptKey
* 加密密钥,要求为8位
* @return 加密成功返回加密后的字符串,失败返回源串
*/
public static String EncryptDES(String encryptString, String encryptKey, byte[] ivP) {
byte[] ret = null;
try {
// 从原始密匙数据创建DESKeySpec对象
byte[] key = encryptKey.getBytes(Encoding);
DESKeySpec dks = new DESKeySpec(key);
// 创建一个密匙工厂,然后用它把DESKeySpec转换成
// 一个SecretKey对象
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey securekey = keyFactory.generateSecret(dks);
// Cipher对象实际完成加密操作
Cipher cipher = Cipher.getInstance(DES);
// 用密匙初始化Cipher对象
IvParameterSpec iv = new IvParameterSpec(ivP);
cipher.init(Cipher.ENCRYPT_MODE, securekey, iv);
// 获取数据并加密
byte[] src = encryptString.getBytes(Encoding);
src = cipher.doFinal(src);
Base64 enc = new Base64();
ret = enc.encode(src);
} catch (Exception ex) {
ret = encryptString.getBytes();
}
return new String(ret);
}
/**
* DES解密字符串
*
* @param decryptString
* 待解密的字符串
* @param decryptKey
* 解密密钥,要求为8位,和加密密钥相同
* @return 解密成功返回解密后的字符串,失败返源串
*/
public static String DecryptDES(String decryptString, String decryptKey, byte[] ivP) {
String ret = "";
try {
// 从原始密匙数据创建一个DESKeySpec对象
byte[] key = decryptKey.getBytes(Encoding);
DESKeySpec dks = new DESKeySpec(key);
// 创建一个密匙工厂,然后用它把DESKeySpec对象转换成
// 一个SecretKey对象
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey securekey = keyFactory.generateSecret(dks);
// Cipher对象实际完成解密操作
Cipher cipher = Cipher.getInstance(DES);
// 用密匙初始化Cipher对象
IvParameterSpec iv = new IvParameterSpec(ivP);
cipher.init(Cipher.DECRYPT_MODE, securekey, iv);
// 获取数据并解密
Base64 dnc = new Base64();
byte[] src = dnc.decode(decryptString.getBytes());
src = cipher.doFinal(src);
ret = new String(src, 0, src.length, Encoding);
} catch (Exception ex) {
ret = decryptString;
}
return ret;
}
3DES 加密算法
3DES 又称 Triple DES,是 DES 加密算法的一种模式,它使用 3 条 56 位的密钥对数据进行三次加密。数据加密标准(DES)是美国的一种由来已久的加密标准,它使用对称密钥加密法,并于 1981 年被 ANSI 组织规范为 ANSI X.3.92。DES 使用 56 位密钥和密码块的方法,而在密码块的方法中,文本被分成 64 位大小的文本块然后再进行加密。由于计算机运算能力的增强,DES 密码的密钥长度变得容易被暴力破解,因此 3DES 更为安全。由于 3DES 的算法是公开的,所以算法本身没有密钥可言,主要依靠唯一密钥来确保数据加解密的安全。到目前为止,仍没有人能破解 3DES。
3DES(即Triple DES)是 DES 向 AES 过渡的加密算法,但其缺点:处理速度较慢、密钥计算时间较长、加密效率不高 。
Java 3DES 工具类:
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* dweeb
* 2019/1/4
* 3DES 工具类
* ProductKey
* Encrypt
* Decrypt
*/
public class ThreeDesUtils {
private static final Logger log = LoggerFactory.getLogger(ThreeDesUtils.class);
private static final String ENCODING_UTF8 = "UTF-8";
private static final String TRIPLEDES = "DESede";
private static final String TRIPLEDES_ECB_PKCS5_MODE = "DESede/ECB/PKCS5Padding";
private static final String IV = "12345ABC";//CBC需要初始化的key
private static final String TRIPLEDES_CBC_PKCS5_MODE = "DESede/CBC/PKCS5Padding";
private static String byteArrToHexStr(byte[] byteArr) throws Exception {
int iLen = byteArr.length;
// 每个byte用两个字符才能表示,所以字符串的长度是数组长度的两倍
StringBuffer sb = new StringBuffer(iLen * 2);
for (int i = 0; i < iLen; i++) {
int intTmp = byteArr[i];
// 把负数转换为正数
while (intTmp < 0) {
intTmp = intTmp + 256;
}
// 小于0F的数需要在前面补0
if (intTmp < 16) {
sb.append("0");
}
sb.append(Integer.toString(intTmp, 16));
}
return sb.toString();
}
private static byte[] hexStrToByteArr(String hexStr) throws Exception {
byte[] arrB = hexStr.getBytes();
int iLen = arrB.length;
// 两个字符表示一个字节,所以字节数组长度是字符串长度除以2
byte[] arrOut = new byte[iLen / 2];
for (int i = 0; i < iLen; i = i + 2) {
String strTmp = new String(arrB, i, 2);
arrOut[i / 2] = (byte) Integer.parseInt(strTmp, 16);
}
return arrOut;
}
/**
* 1、Base64 Encode
* 2、3DES Encrypt
* 3、ByteArrToHexStr
*/
public static String TDesBase64Encrypt(final String partnerMerkey,final String data) throws Exception {
String base64EncodeData = new BASE64Encoder().encode(data.getBytes(ENCODING_UTF8));
byte[] k = new byte[24];
byte[] key = partnerMerkey.getBytes(ENCODING_UTF8);
if (key.length < 16) {
//如果temp不够24位,则拷贝temp数组整个长度的内容到key数组中
System.arraycopy(key, 0, k, 0, key.length);
} else if (key.length == 16) {
System.arraycopy(key, 0, k, 0, key.length);
System.arraycopy(key, 0, k, 16, 8);
} else {
System.arraycopy(key, 0, k, 0, 24);
}
// --加密向量
final IvParameterSpec iv = new IvParameterSpec(IV.getBytes(ENCODING_UTF8));
final SecretKey secretKey = new SecretKeySpec(k, TRIPLEDES);
// 通过Chipher执行加密得到的是一个byte的数组,Cipher.getInstance("DES")就是采用ECB模式,cipher.init(Cipher.ENCRYPT_MODE,secretKey)就可以了.
Cipher cipher = Cipher.getInstance(TRIPLEDES_CBC_PKCS5_MODE);
cipher.init(Cipher.ENCRYPT_MODE, secretKey,iv);
byte[] cipherByte = cipher.doFinal(base64EncodeData.getBytes(ENCODING_UTF8));//加密data
return byteArrToHexStr(cipherByte).toUpperCase();
}
/**
* 1、HexStrToByteArr
* 2、3DES Decrypt
* 3、Base64 Decode
* 4、ToJSONObject
*/
public static JSONObject TDesBase64Decrypt(final String partnerMerkey, final String data) throws Exception {
byte[] k = new byte[24];
byte[] key = partnerMerkey.getBytes();
if (key.length < 16) {
//如果temp不够24位,则拷贝temp数组整个长度的内容到key数组中
System.arraycopy(key, 0, k, 0, key.length);
} else if (key.length == 16) {
System.arraycopy(key, 0, k, 0, key.length);
System.arraycopy(key, 0, k, 16, 8);
} else {
System.arraycopy(key, 0, k, 0, 24);
}
byte[] dataBytes = hexStrToByteArr(data);
// --加密向量
final IvParameterSpec iv = new IvParameterSpec(IV.getBytes(ENCODING_UTF8));
final SecretKey secretKey = new SecretKeySpec(k, TRIPLEDES);
// 通过Chipher执行加密得到的是一个byte的数组,Cipher.getInstance("DES")就是采用ECB模式,cipher.init(Cipher.ENCRYPT_MODE,secretKey)就可以了.
Cipher cipher = Cipher.getInstance(TRIPLEDES_CBC_PKCS5_MODE);
cipher.init(Cipher.DECRYPT_MODE, secretKey,iv);
byte[] cipherByte = cipher.doFinal(dataBytes);//加密data
byte[] base64DecodeData = new BASE64Decoder().decodeBuffer(new String(cipherByte,ENCODING_UTF8));
return JSONObject.parseObject(new String(base64DecodeData,ENCODING_UTF8));
}
/**
* 密钥长度需>=24
*/
public static String product3DesKey() throws Exception {
KeyGenerator keyGen = KeyGenerator.getInstance("DESede");
keyGen.init(168); //可指定密钥长度为112或168,默认为168
SecretKey secretKey = keyGen.generateKey();
return byteArrToHexStr(secretKey.getEncoded()).toUpperCase();
}
}
实战 Demo:
主要是加解密后的验签是否一致,其它没什么难度。
/**
* 测试
**/
public static void main(String[] args) throws Exception {
// System.out.println(product3DesKey());//产生key F83DB08AB5D5835D916E08D558F8E004CBC7163879DCA115
Map<String,Object> map = new HashMap<>();
String key = "F83DB08AB5D5835D916E08D558F8E004CBC7163879DCA115";
String s = "{\"trnCode_01\":\"BKCDAUTH\"}";
String sign = Md5Util.md5(new BASE64Encoder().encode(s.getBytes()));
map.put("msg",s);
map.put("sign",sign);
String reqMsg = JSONUtils.toJSONString(map);
System.out.println("请求报文:"+ reqMsg);
String s2 = TDesBase64Encrypt(key, reqMsg);
System.out.println("加密:"+ s2);
JSONObject jsonObject = TDesBase64Decrypt(key, s2);
System.out.println("解密:"+ jsonObject.toJSONString());
}
DES / 3DES 保证加密解密的一致性
在不同的平台上,只要能保证这几个参数的一致,就可以实现加密和解密的一致性,主要是用来验证数字签名,防止数据被篡改!
加密和解密的密钥一致
采用 CBC 模式的时候,要保证初始向量一致
采用相同的填充模式
随便总结
在实际运用中,遇到两个坑,一个是带中文数据转码后没有指明 UTF-8 而出现签名校验不一致问题,工具类内全指定后即可解决,另外一个是使用 ECB 模式后出现加密解密的签名不一致问题,使用 CBC 模式即可解决!不使用 AES 加密原因是根据业务数据的保密性不太涉及安全问题而为了速度选择 3DES 加密!不建议使用 DES!
(完)
以上是关于对称加密算法详解和 DES/3DES 理解和实战的主要内容,如果未能解决你的问题,请参考以下文章
加密算法系列之:des加密aes加密3des加密对称加密非对称加密Hash 算法