常用算法类别包括对称密钥算法、非对称密钥算法以及摘要算法。
在了解具体类别算法之前,要先了解以下几个概念:
简单点理解,就是加密和解密所用的密钥是相同的,用哪个密钥对明文进行加密,就用哪个密钥对密文进行解密
常用的对称密钥算法有DES、AES、SM4。
言下之意,就是加密和解密所用的密钥不同。生成的密钥是一对的,一个为公钥,一个为私钥,当使用公钥对明文进行加密后,就用私钥对密文进行解密
常用的非对称密钥算法有RSA、DSA、SM2。
将一串不定长的明文数据(消息)通过特定的算法,生成一串固定长度的摘要数据。摘要算法的特点是当明文数据(消息)的任何一部分被修改后,生成的摘要都不相同。
这样可以保证原明文数据在传输过程中没有被篡改,常用于数字签名、消息认证以及数据完整性检测。
常用的摘要算法有HASH、MD5、SM3。
本节主要是学习对称算法中的DES算法。
DES是Data Encryption Standard(数据加密标准)的缩写。它是由IBM公司研制的一种对称密码算法,美国国家标准局于1977年公布把它作为非机要部门使用的数据加密标准,三十年来,它一直活跃在国际保密通信的舞台上,扮演了十分重要的角色。
DES是一种分组加密算法。分组加密算法指的是DES每次只能对固定8个字节的数据进行加密或者解密,若数据长度超过8个字节,则需要分组,每8个字节为一组,不足8个字节的需要填充8个字节。
DES算法主要是通过移位、置换、扩展、压缩、异或等几种常用的数学运算对明文数据进行加密,从而加大破译的难度。
常用数学运算汇总:
主要包括单DES、TDES、3DES三种,三者都是使用DES算法,区别是密钥长度不同。
单DES
单DES**密钥为8个字节共64位**,有效密钥长度为56位,因为每个字节第8位为奇偶校验位,不参与加解密运算,因此第8位不相同的两个密钥对同一串明文数据进行加密后得到的结果是相同的。
举个例子(数据都是以16进制方式表示):
//两组密钥校验位不同,加密结果相同
密钥1 1111111111111111 第8位都为1
数据 + 0102030405060708
结果 = 73331971A6E1AB01
-------------------------------------
密钥2 1010101010101010 第8位都为0
数据 + 0102030405060708
结果 = 73331971A6E1AB01
那奇偶校验位是用来做什么的呢?
顾名思义,这是用于密钥中的每个字节是否符合奇校验或者偶校验而保留的,当一个字节的二进制数位中的“1”的总个数为奇数时,则称该字节符合奇校验;总个数为偶数时,则称该字节符合偶校验。
就像前面举的例子,密钥 1111111111111111
是符合偶校验的,而密钥1010101010101010
则是符合奇校验的。
当今计算机的处理能力和速度越来越高,单DES算法56位密钥已经能够被破解,由此产生了TDES、2DES、3DES。它们都是单DES的变种,是对DES算法进行某种变型和改进。
TDES
又称为2DES。指的是密钥长度为16个字节,是原来单DES的两倍。
在使用TDES对数据进行加密时,需要先把密钥分成长度为8个字节的两部分K1和K2,明文数据和单DES相同分为D1D2…Dn。
然后对D1进行加密,加密过程为:
使用K1对D1进行加密得到结果T1;
使用K2对T1进行解密得到结果T2;
使用K1对T2进行加密得到结果C1。
其中的加解密过程都是单DES算法,只是每次数据加密过程叠加了三次单DES的加解密算法。
举个例子:
密钥:11111111111111112222222222222222
数据:0102030405060708
密钥分为两部分-> K1: 1111111111111111
K2: 2222222222222222
使用K1加密得到-> T1: 73331971A6E1AB01
使用K2解密得到-> T2: E3A76B63B05270CC
使用K1加密得到-> C1: B8016234E48E685F
TDES加密最终结果为-> C1: B8016234E48E685F
3DES
3DES指的是密钥长度为24个字节,是单DES的三倍。
3DES的加密过程和TDES类似,它是把密钥分成三部分K1K2K3,加密过程同样是加密解密加密,只是第三次单DES加密使用的是K3,而TDES用的是K1。
同样,举个例子:
密钥为 111111111111111122222222222222221111111111111111
数据为 0102030405060708
最终结果和前面的例子一样为 B8016234E48E685F
对称加密分为分组加密和序列密码。
分组密码,也叫块加密(block cyphers),一次加密明文中的一个块。是将明文按一定的位长分组,明文组经过加密运算得到密文组,密文组经过解密运算(加密运算的逆运算),还原成明文组。
序列密码,也叫流加密(stream cyphers),一次加密明文中的一个位。是指利用少量的密钥(制乱元素)通过某种复杂的运算(密码算法)产生大量的伪随机位流,用于对明文位流的加密。
解密是指用同样的密钥和密码算法及与加密相同的伪随机位流,用以还原明文位流。
在分组加密算法中,有ECB,CBC,CFB,OFB这几种算法模式。
ECB是最简单的块密码加密模式,加密前根据加密块大小(如DES为64位)分成若干块,之后将每块使用相同的密钥单独加密,解密同理。
—–ECB加密过程—–
—–ECB解密过程—–
ECB模式由于每块数据的加密是独立的因此加密和解密都可以并行计算,ECB模式最大的缺点是相同的明文块会被加密成相同的密文块,这种方法在某些环境下不能提供严格的数据保密性。
举个例子:单DES算法的ECB模式
这种模式比较简单,各段数据分开加密,没有联系。
首先将明文数据按8个字节分成N组D1D2….Dn,最后一组不足8个字节时填充(一般为0x00或者0xFF)为8个字节。
然后,分别对D1、D2、…、Dn做加密,得到加密后的C1、C2、…、Cn,最后把加密后的分组数据拼在一起就得到最后的结果C1C2C3…Cn。
密钥为 1111111111111111
数据为 01020304050607080102030405060708
计算过程:
数据分为两组-> D1: 0102030405060708
D2: 0102030405060708
使用密钥加密得到-> C1: 73331971A6E1AB01
C2: 73331971A6E1AB01
拼接得到最终结果-> C1C2: 73331971A6E1AB0173331971A6E1AB01
CBC模式对于每个待加密的密码块在加密前会先与前一个密码块的密文异或然后再用加密器加密。第一个明文块与一个叫初始化向量的数据块异或。
—–CBC加密过程—–
—–CBC解密过程—–
CBC模式相比ECB有更高的保密性,但由于对每个数据块的加密依赖与前一个数据块的加密所以加密无法并行。与ECB一样在加密前需要对数据进行填充,不是很适合对流数据进行加密。
举个例子:单DES算法的CBC模式
这种模式各段数据的加密有了联系,不是单独的。
首先和ECB模式一样,将明文数据按8个字节分成N组D1D2….Dn,最后一组不足8个字节时填充(一般为0x00或者0xFF)为8个字节。
然后使用密钥对D1进行加密得到密文C1,接着将C1和D2进行异或操作得到的结果,再使用密钥进行加密得到C2。
以此类推,得到C3、C4、…、Cn
最后把前面加密获取到的分组数据拼在一起就得到最后的结果C1C2C3…Cn
还是拿前面那个例子的数据:
密钥为 1111111111111111
数据为 01020304050607080102030405060708
计算过程:
数据分为两组-> D1: 0102030405060708
D2: 0102030405060708
使用密钥加密得到-> C1: 73331971A6E1AB01
将C1和D2进行异或得到->新D2: 72311A75A3E7AC09
再使用密钥加密得到-> C2: C85E8384E36D1CD6
拼接得到最终结果-> C1C2: 73331971A6E1AB01C85E8384E36D1CD6
与ECB和CBC模式只能够加密块数据不同,CFB能够将块密文(Block Cipher)转换为流密文(Stream Cipher)。
—–CFB加密过程—–
—–CFB解密过程—–
注意:CFB、OFB和CTR模式中解密也都是用的加密器而非解密器。
CFB的加密工作分为两部分:
由于加密流程和解密流程中被块加密器加密的数据是前一段密文,因此即使明文数据的长度不是加密块大小的整数倍也是不需要填充的,这保证了数据长度在加密前后是相同的。
OFB是先用块加密器生成密钥流(Keystream),然后再将密钥流与明文流异或得到密文流,解密是先用块加密器生成密钥流,再将密钥流与密文流异或得到明文,由于异或操作的对称性所以加密和解密的流程是完全一样的。
—–OFB加密过程—–
—–OFB解密过程—–
OFB与CFB一样都非常适合对流数据的加密,OFB由于加密和解密都依赖与前一段数据,所以加密和解密都不能并行。
这里的填充指的是对于分组加密算法,需要将数据分成一个个固定长度的字节块,当长度不够时,需要使用规则进行填充。这里的规则指的就是填充模式。
DES算法是对64位数据的加密算法,如数据位数不足64位的倍数,需要填充,补充到64位的倍数。
NoPadding
API或算法本身不对数据进行处理,加密数据由加密双方约定填补算法。例如若对字符串数据进行加解密,可以补充\0或者空格,然后trim。
PKCS5Padding
以下是使用java语言实现的DES算法工具类,封装了常用的加密、解密、计算checkvalue以及分组模式、填充模式的实现。
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class DesCrypter {
private DesCryptType type;
private String groupMode; //分组模式 ECB、CBC、OFB、CFB
private String fillMode; //填充模式 NoPadding、PKCS5Padding
private byte[] initVector;
public DesCrypter(DesCryptType type) {
this(type, "ECB", "PKCS5Padding");
}
public DesCrypter(DesCryptType type, String groupMode) {
this(type, groupMode, "PKCS5Padding");
}
public DesCrypter(DesCryptType type, String groupMode, String fillMode) {
if (StringUtil.isEmpty(groupMode)) {
groupMode = "ECB";
}
if (StringUtil.isEmpty(fillMode)) {
fillMode = "PKCS5Padding";
}
this.type = type;
this.groupMode = groupMode;
this.fillMode = fillMode;
initVector = HexUtil.hexStringToByte("0000000000000000");
}
/**
* 生成随机密钥key
* @return
*/
public byte[] generateRandomKey() {
byte[] result = null;
String des = getDesString();
SecretKey key = generateSecretKey(des, HexUtil.hexStringToByte("11111111111111111111111111111111"));
if (key != null) {
result = key.getEncoded();
}
return result;
}
private String getDesString() {
String des = "";
if (this.type == DesCryptType.DES) {
des = "DES";
} else {
des = "DESede";
}
return des;
}
/**
* 使用DES算法加密data
* @param data 带加密的明文
* @param keyBytes 密钥
* @return 密文
*/
public byte[] encrypt(byte[] data, byte[] keyBytes) {
byte[] result = null;
String des = getDesString();
try {
//生成密钥
SecretKey key = generateSecretKey(des, keyBytes);
if (key == null) {
return null;
}
//加密
String cipherStr = getCipherStr(data.length, groupMode, fillMode);
Cipher cipher = Cipher.getInstance(cipherStr);
if (groupMode.equals("ECB")) {
cipher.init(Cipher.ENCRYPT_MODE, key);
} else {
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(initVector));
}
result = cipher.doFinal(data);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 设置分组模式的初始向量
* @param data
* @return
*/
public boolean setInitVector(byte[] data) {
if (data == null || data.length != 8) {
return false;
}
System.arraycopy(data, 0, initVector, 0, 8);
return true;
}
private String getCipherStr(int length, String groupMode, String fillMode) {
String des = getDesString();
String currFillMode = fillMode;
if (fillMode.equals("PKCS5Padding") && length % 8 == 0) {
//数据长度刚好是8的倍数,不填充
currFillMode = "NoPadding";
}
return des + "/" + groupMode + "/" + currFillMode;
}
/**
* 使用DES算法加密data
* @param data 待解密的密文
* @param keyBytes 密钥
* @return 明文
*/
public byte[] decrypt(byte[] data, byte[] keyBytes) {
byte[] result = null;
String des = getDesString();
try {
//生成密钥
SecretKey key = generateSecretKey(des, keyBytes);
if (key == null) {
return null;
}
//解密
String cipherStr = getCipherStr(data.length, groupMode, fillMode);
Cipher cipher = Cipher.getInstance(cipherStr);
if (groupMode.equals("ECB")) {
cipher.init(Cipher.DECRYPT_MODE, key);
} else {
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(initVector));
}
result = cipher.doFinal(data);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
private SecretKey generateSecretKey(String des, byte[] keyBytes) {
//第一种,原值返回
SecretKey key = null;
// try {
// DESKeySpec keySpec = new DESKeySpec(keyBytes);
// SecretKeyFactory factory = SecretKeyFactory.getInstance(des);
// key = factory.generateSecret(keySpec);
// } catch (Exception e) {
// e.printStackTrace();
// }
//第二种,原值返回
// key = new SecretKeySpec(keyBytes, des);
//第三种,随机生成
try {
KeyGenerator generator = KeyGenerator.getInstance(des);
int keyLen = getKeyLength();
generator.init(keyLen, new SecureRandom(keyBytes));
key = generator.generateKey();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
System.out.println("生成的DES密钥为:" + HexUtil.bcd2str(key.getEncoded()));
return key;
}
private int getKeyLength() {
int len = 56;
switch (this.type) {
case DES:
len = 56;
break;
case DES_2:
len = 112;
break;
case DES_3:
len = 168;
break;
}
return len;
}
/**
* 获取当前key的校验值
* @param key
* @return 4个字节校验结果
*/
public byte[] getCheckValue(byte[] key) {
byte[] zero = HexUtil.hexStringToByte("0000000000000000");
byte[] result = encrypt(zero, key);
if (result != null && result.length > 4) {
//截取四个字节
byte[] tmp = new byte[4];
System.arraycopy(result, 0, tmp, 0, 4);
result = tmp;
}
return result;
}
public enum DesCryptType {
DES,
DES_2,
DES_3
}
}
以下是在android当中使用DesCrpter的实例(只摘取片段)。
/**
* 输入密钥
* @param view
*/
public void inputKey(View view) {
showMessage("请输入密钥");
DialogFactory.showMessage(this, "请输入密钥", "11111111111111111111111111111111", "确定" ,
new View.OnClickListener() {
@Override
public void onClick(View v) {
//输入数据后
String text = DialogFactory.getContentMsg(DesTestActivity.this);
if (TextUtils.isEmpty(text)) {
showMessage("密钥不能为空", Color.RED);
DialogFactory.dismissAlert(DesTestActivity.this);
return;
}
// if (text.length() != 16) {
// showMessage("密钥长度不对,DES密钥长度为8个字节", Color.RED);
// DialogFactory.dismissAlert(DesTestActivity.this);
// return;
// }
keyBytes = HexUtil.hexStringToByte(text);
DialogFactory.dismissAlert(DesTestActivity.this);
showMessage(text);
}
}, "取消", null);
}
/**
* 随机生成密钥
* @param view
*/
public void generateKey(View view) {
DesCrypter desCrypter = new DesCrypter(DesCrypter.DesCryptType.DES_2);
byte[] key = desCrypter.generateRandomKey();
showMessage("生成密钥:" + HexUtil.bcd2str(key));
}
/**
* 设备初始向量
* @param view
*/
public void inputVector(View view) {
showMessage("设置初始向量");
DialogFactory.showMessage(this, "请输入密钥", "0000000000000000", "确定" ,
new View.OnClickListener() {
@Override
public void onClick(View v) {
//输入数据后
String text = DialogFactory.getContentMsg(DesTestActivity.this);
if (TextUtils.isEmpty(text)) {
showMessage("初始向量不能为空", Color.RED);
DialogFactory.dismissAlert(DesTestActivity.this);
return;
}
if (text.length() != 16) {
showMessage("初始向量长度不对,长度需为8个字节", Color.RED);
DialogFactory.dismissAlert(DesTestActivity.this);
return;
}
initVectorBytes = HexUtil.hexStringToByte(text);
DialogFactory.dismissAlert(DesTestActivity.this);
showMessage(text);
}
}, "取消", null);
}
/**
* DES 加密
* @param view
*/
public void encryptByDES(View view) {
if (keyBytes == null) {
showMessage("当前密钥为空,请先输入密钥");
return;
}
final String groupMode = this.mode == 0 ? "ECB" : "CBC";
DialogFactory.showMessage(this, "请输入数据", "0102030405060708", "确定" ,
new View.OnClickListener() {
@Override
public void onClick(View v) {
//输入数据后
String text = DialogFactory.getContentMsg(DesTestActivity.this);
if (TextUtils.isEmpty(text)) {
showMessage("数据不能为空");
DialogFactory.dismissAlert(DesTestActivity.this);
return;
}
byte[] data = HexUtil.hexStringToByte(text);
//开始加密
DesCrypter desCrypter = new DesCrypter(DesCrypter.DesCryptType.DES_2, groupMode);
//设置初始向量
if (initVectorBytes != null) {
desCrypter.setInitVector(initVectorBytes);
}
byte[] result = desCrypter.encrypt(data, keyBytes);
if (result == null) {
showMessage("加密出错,请重试");
DialogFactory.dismissAlert(DesTestActivity.this);
return;
}
showResult(text, HexUtil.bcd2str(result));
DialogFactory.dismissAlert(DesTestActivity.this);
}
}, "取消", null);
}
/**
* DES 解密
* @param view
*/
public void decryptByDES(View view) {
if (keyBytes == null) {
showMessage("当前密钥为空,请先输入密钥");
return;
}
final String groupMode = this.mode == 0 ? "ECB" : "CBC";
DialogFactory.showMessage(this, "请输入数据", "73331971A6E1AB0173331971A6E1AB01", "确定" ,
new View.OnClickListener() {
@Override
public void onClick(View v) {
//输入数据后
String text = DialogFactory.getContentMsg(DesTestActivity.this);
if (TextUtils.isEmpty(text)) {
showMessage("数据不能为空");
DialogFactory.dismissAlert(DesTestActivity.this);
return;
}
byte[] data = HexUtil.hexStringToByte(text);
//开始加密
DesCrypter desCrypter = new DesCrypter(DesCrypter.DesCryptType.DES_2, groupMode);
//设置初始向量
if (initVectorBytes != null) {
desCrypter.setInitVector(initVectorBytes);
}
byte[] result = desCrypter.decrypt(data, keyBytes);
if (result == null) {
showMessage("解密出错,请重试");
DialogFactory.dismissAlert(DesTestActivity.this);
return;
}
showResult(text, HexUtil.bcd2str(result));
DialogFactory.dismissAlert(DesTestActivity.this);
}
}, "取消", null);
}
/**
* 计算密钥的checkvalue
* @param view
*/
public void getCheckValue(View view) {
if (keyBytes == null) {
showMessage("当前密钥为空,请先输入密钥");
return;
}
DesCrypter desCrypter = new DesCrypter(DesCrypter.DesCryptType.DES_2);
byte[] ckv = desCrypter.getCheckValue(keyBytes);
showMessage("CHECKVALUE: " + HexUtil.bcd2str(ckv));
}