概念
加密领域主要有国际算法和国密算法两种体系。国密算法是国家密码局认定的国产密码算法。国际算法是由美国安全局发布的算法。由于国密算法安全性高等一系列原因。国内的银行和支付机构都推荐使用国密算法。
从上图可知,对称加密算法在算法体系里占了半壁江山。因为国际和国密算法的过程差异并不大。只是应用的数学公式和秘钥位数不同。DES在里面算是基础,所以今天主要介绍一下DES的原理。
原理
密码
咱们从加密的原理说起。举个最简单的加密:
我有一段明文:520
我的秘钥是:221
我的加密算法是:加法
加密后的密文就是:741
如果这个密文被截获了,接受者看到的信息是:741(气死你),和原来明文520是完全不同的,不能知道信息原有的意思。
从这个简单的例子可知:加密是包含:明文、秘钥、加密算法和密文四个要素的。加密算法可公开可不公开,常见的算法有:位移、循环位移、异或、置换、数学函数。
凯撒密码
加密技术从古罗马凯撒时候就在用:凯撒密码
凯撒密码是古罗马时期凯撒大帝和他的将军们通信时使用的加密方式:
明文:由26个字母组成
秘钥:1到25之间的任意数字
加密算法:循环位移
密文:举例明文为eat 秘钥为2,对照上面图片的凯撒密码盘可以得到密文是gcv。
咱们来动手写个凯撒密码(代码已经上传github: (https://github.com/xiexiaojing/yuna):
代码语言:javascript复制@Test
public void caesarCipher() {
String text = "love was growing in eyes"; //明文
int key = 3; //秘钥
String cipher = encryptCaesarCipher(text, key); //密文
System.out.println(cipher);
}
//凯撒密码加密算法
private String encryptCaesarCipher(String text, int key) {
char[] chars = text.toCharArray();
for(int i=0; i<chars.length; i ) {
if(chars[i] != ' ') {
chars[i] = (char)(chars[i] key);
}
//如果超过了26个字母,则减去26
if(chars[i]>122) {
chars[i] = (char)(chars[i]-26);
}
}
return new String(chars);
}
运行得到密文:oryh zdv jurzlqj lq hbhv
凯撒密码连小朋友都能破解。一旦被人知道用的凯撒密码,算法是已知的。要破解秘钥拿个明文和密文试试就知道了。就是平时说的暴力破解法可以很容易破解。对于这种全是英文字符的也可以使用频率分析法。频率分析法可以理解为基于大数据的方法,因为26个字母中,e的使用频率高。如果比如一篇文章,单词足够多的话,出现频率最高的字母xxx就是e。xxx的char值e的char值就是秘钥了。
DES
概述
DES全称为Data Encryption Standard,即数据加密标准,是一种分组加密算法。其分组长度为64bit,密钥长度为64bit,其中8bit为校验位,所以实际长度为56bit。
先介绍一下校验位。举个例子,咱们的身份证号码都是18位。这18位包含:
其中最后一位就是校验位,原理是利用将前面部分利用某种算法计算得到一个数。如果校验位与算法得到的不一致,则数据是有问题的。所以身份证本身是有不通过查库就可以简单验证有效性功能的。
回到DES算法。DES算法的秘钥必须是64位,参与加密计算的是56位。这是原始秘钥。这个原始秘钥会用一个函数转换成16个64位秘钥。
加密过程
DES的加密过程:
明文64位->初始IP置换->16轮加密变换->逆初始IP置换->密文
DES算法的这个过程又被称为Feistel网络。
简单解释下:
明文我们自己想写多长写多长。但是加
密的时候每次以64bit作为一个分组。最后将密文拼接起来。
然后执行一个IP置换(初始置换)操作。IP置换就是按位置换。举例来说64bit就是64个0和1。把第40个位置上的数换成第50个位置上的数就是置换了。
置换好的64个bit会分成两个32bit。然后用相同的加密算法每次传不同的转换后秘钥做16轮。
然后将两组32bit拼接起来再进行一次IP置换(终结置换)变成密文
分组组合
刚才介绍的加密过程是把明文的一块怎么加密成密文。DES密码块与密码块连接方式遵循对称加密的方式。
对称加密有两种方式,一种是分组加密,一种是序列加密。
分组加密,也叫块加密(block cyphers),一次加密明文中的一个块。是将明文按一定的位长分组,明文组经过加密运算得到密文组,密文组经过解密运算(加密运算的逆运算),还原成明文组。
序列加密,也叫流加密(stream cyphers),一次加密明文中的一个位。是指利用少量的密钥通过某种复杂的运算(密码算法)产生大量的伪随机位流,用于对明文位流的加密。
分组加密算法中,分组密码的设计基本遵循混淆原则和扩散原则。有ECB,CBC,CFB,OFB这4种算法模式。DES有ECB和CBC两种实现。
ECB模式
ECB模式就是每组明文分别加密后拼接起来。
CBC模式
CBC即密码分组链接(Cipher-block chaining)的简称。在CBC模式中,每个明文块先与前一个密文块进行异或后,再进行加密。在这种方法中,每个密文块都依赖于它前面的所有明文块。同时,为了保证每条消息的唯一性,在第一个块中需要使用初始化向量。在DES中,初始化向量就是秘钥。
程序实现
我们用程序来实现一下DES加密算法
代码语言:javascript复制@Test
public void desCiper() throws Exception{
String key = "12345678";
String text = "我知道我是任性很任性,伤透了你的心"; //明文
String cipher = encryptDesCipher(text, key); //密文
System.out.println(cipher);
text = decryptDesCipher(cipher, key); //明文
System.out.println(text);
}
//DES加密算法
private String encryptDesCipher(String text, String origKey) throws Exception{
Key key = new SecretKeySpec(origKey.getBytes(), "DES");
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.ENCRYPT_MODE, key);
return Base64.encode(cipher.doFinal(text.getBytes()));
}
//DES加密算法
private String decryptDesCipher(String text, String origKey) throws Exception{
Key key = new SecretKeySpec(origKey.getBytes(), "DES");
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.DECRYPT_MODE, key);
return new String(cipher.doFinal(Base64.decode(text)));
}
运行结果:
xo6mmVe8j1/d60cAAiFz1HAXxihi2FH5d0zMWILvEYISWR52lguy2TbMZQ4vuulCdO8WvxMRuXE=
我知道我是任性很任性,伤透了你的心
3DES
3DES即3重加密算法,是对每个数据块应用三次DES算法。这是为了应对计算机计算能力增强,DES变的容易破解而产生的。
主要方法就是DES秘钥64位,3DES秘钥64*3=192位,分成个三个秘钥,进行2轮DES加密,1轮DES解密。最终得到结果。
AES
AES全称是高级加密标准(Advanced Encryption Standard)。是用来替代DES/3DES的。主要过程如下,这里不过多介绍。
应用
我们来回顾下https的SSL握手过程:
SSL握手的最后,双方会用非对称秘钥协商出一个对称秘钥。用对称秘钥来加密传输的数据。之所以这样做是因为非对称加密安全性高但是效率低,对称秘钥正好相反。对称秘钥可以被暴力破解,破解需要时间。如果破解出来时就过期了,再通信就用另外的秘钥就能保证信息安全。
我们来模拟一下已经协商好的秘钥之后http客户端与服务器端的通信。(代码已经上传github:(https://github.com/xiexiaojing/yuna):
代码语言:javascript复制@Test
代码语言:javascript复制public void client() throws Exception {
int i = 1;
while (i <= 2) {
Socket socket = new Socket("127.0.0.1", 520);
//向服务器端第一次发送字符串
OutputStream netOut = socket.getOutputStream();
InputStream io = socket.getInputStream();
String msg = i == 1 ? "客户端:我知道我是任性太任性,伤透了你的心。我是追梦的人,追一生的缘分。":
"客户端:我愿意嫁给你,你却不能答应我。";
System.out.println(msg);
netOut.write(encryptDesCipher(msg.getBytes(), "12345678"));
netOut.flush();
byte[] bytes = new byte[i==1?104:64];
io.read(bytes);
String response = new String(decryptDesCipher(bytes,"12345678"));
System.out.println(response);
netOut.close();
io.close();
socket.close();
i ;
}
}
@Test
public void server() throws Exception {
ServerSocket serverSocket = new ServerSocket(520);
int i = 1;
while (i <= 2) {
String msg = i == 1 ? "服务端:我知道你是任性太任性,伤透了我的心。同是追梦的人,难舍难分。" :
"服务端:你愿意嫁给我,我却不能向你承诺。";
Socket socket = serverSocket.accept();
InputStream io = socket.getInputStream();
byte[] bytes = new byte[i==1?112:64];
io.read(bytes);
System.out.println(new String(decryptDesCipher(bytes,"12345678")));
OutputStream os = socket.getOutputStream();
System.out.println(msg);
byte[] outBytes = encryptDesCipher(msg.getBytes(), "12345678");
os.write(outBytes);
os.flush();
os.close();
io.close();
i ;
}
}
代码语言:javascript复制//DES加密算法
private byte[] encryptDesCipher(byte[] text, String origKey) throws Exception {
Key key = new SecretKeySpec(origKey.getBytes(), "DES");
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(text);
}
//DES加密算法
private byte[] decryptDesCipher(byte[] text, String origKey) throws Exception {
Key key = new SecretKeySpec(origKey.getBytes(), "DES");
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.DECRYPT_MODE, key);
return cipher.doFinal(text);
}
运行结果
客户端:我知道我是任性太任性,伤透了你的心。我是追梦的人,追一生的缘分。
服务端:我知道你是任性太任性,伤透了我的心。同是追梦的人,难舍难分。
客户端:我愿意嫁给你,你却不能答应我。
服务端:你愿意嫁给我,我却不能向你承诺。
这个程序实现了简单的客户端和服务器端的DES加密方式通信。稍加改造可以实现一个信息加密的聊天小程序。
总结
本文使用概念、原理、应用的传统型逻辑架构来对DES做系统梳理。里面涉及到的一些基本知识限于篇幅省略了一些。
比如Base64编码。它是加密时常用的编码方式,我们平时所看到的密钥都是base64后的结果。可以简单理解为对2的6次方进行64进制运算,可防止乱码丢失字节。
再比如填充字节这部分也没有介绍,有兴趣可以自己查阅下。
加解密很多人工作中或多或少都有涉及。我个人认为只要涉及的地方至少要了解到原理和架构层面,才能避免遇到问题时【拿着锤子找钉子】找不到真正问题的窘境。而只有动手实践才能避免当时了解了,过后又忘了需要再看一遍而产生重复工作的问题。