一、前言
最近项目中需要用到DES,3DES解密算法,所以了解一下。正好CSDN上有关于DES,3DES的资料。边看边写一下总结。
二、参考资料
密码学之DES,3DES详解与Python实现
三、总结
1.定义
DES(Data Encryption Standard-数据加密标准)属于对称加密,即使用相同的密钥来完成加密和解密。分组长度为8个字节64bit(密钥每个字节的最后一位都没有采用,所以有效位只有56位)。
2.算法原理
2.1密钥编排
第一步:
密钥置换(64bit -> 56bit) ,将64位的密钥K通过置换表PC-1置换得到56位的密钥K0
第二步:
将K0分为左右两半各28位,C0和D0
第三步:
根据’左移’时间表移动C0和D0上的位,得到C1,…,C16,D1,…,D16
第四步:
16对Cn和Dn分别合并得到16个56位的密钥。将每个56位的密钥通过置换表PC-2的到16个48位的密钥Kn。
这就通过’原始密钥’,'PC-1’表,'左移’时间表,'PC-2’表完成了密钥拓展。
2.2明文运算
第一步:明文填充与工作模式
des明文分组模式为64比特,当明文长度不够分组大小时,需要指定某种标准进行填充。
第二步:IP盒置换(初始置换)
64bit明文块M与初始置换IP表进行初始置换的到新的64bit的IP
第三步:分为左右各32bit
置换后的IP分为32bit的左半部分L0和32bit的右半部分R0
第四步:16轮运算
使用函数f对两个块(32位的数据块和48位的密钥Kn)进行操作,以生成一个32位的块。+表示XOR加法。从L0和R0开始,迭代16次,得到L16和R16。
Ln = Rn-1
Rn = Ln-1 + F(Rn-1, Kn) # +是XOR运算
一轮运算中的F函数:
Rn-1(32bit)->E(Rn-1)(48bit)->E(Rn-1)+Kn(48bit)->S盒压缩置换(32bit)->P盒置换(32bit)=F(Rn-1, Kn)
在f(Rn-1, Kn)中,首先将Rn-1从32位扩展为48位,这是通过重复Rn-1的一些位的选择表来完成的,这个选择表称为函数E,即E(Rn-1)实现了32位的输入块和48的输出块。
于是f(Rn-1, Kn)=E(Rn-1)+Kn,这样就的到了48位,也就是8组6位。
然后进行S盒压缩置换:根据这8组6位来寻找S盒子中的地址,位于该地址的是一个4位的数字。于是8组6位转化称为了8组4位共32位。
接着进行P盒置换,将刚刚的32位置换为32位,这样F函数就实现了。
然后执行一次异或操作得到Rn。
第五步:逆初始置换(末置换)
将第四步得到的R16和L16合并(R16在左)得到64位,再利用FP(Final Permutation)表置换,得到64位的加密结果。
2.3用到的置换表
中间涉及的置换表有IP(明文置换第一步)、PC_1(密钥置换第一步)、PC_2(密钥置换第四步)、E(明文置换第四步中的F函数中的E函数)、S_BOX(明文置换第四步中的F函数中的S盒)、P(明文置换第四步中的F函数最后的P盒置换)、FP(明文置换第五步)、SHIFT(密钥置换第三步)
四、3DES及DES与AES的区别
1.3DES
3DES是三重DES的意思,它表示明文经过DES密钥1加密,经过DES密钥2解密,再通过DES密钥3加密得到密文的过程。
这个设计出于两个原因:
引用:1.The second key is used to DES-decrypt the encrypted message. (Since the second key is not the right key, this decryption just scrambles the data further.)
第二个密钥用于对加密的消息进行DES解密。 (由于第二个密钥不是正确的密钥,因此该解密只会进一步扰乱数据。)这个方案由IBM公司设计和提出,理由是这样的话,当三个密钥相同时,3重DES等于普通的DES,这样就可以实现向下的兼容,使用DES的,比如老旧的银行系统等等,兼容性对它们很重要,因此这个方案被采纳了
DES与AES的区别
参考资料:
加密标准中DES与AES到底是什么?两者有啥区别?
总的来说,AES是比DES更先进的加密算法。
五、项目需求及Python实现
1.需求
甲方提供了JS上的加密方式是基于Javascript的AES-192加密算法
const SECRET = "xxxxxxxxxxxx"; // 长为12个字节
function decrypt(encrypted) {
const decipher = crypto.createDecipher("aes192", SECRET);
let decrypted = decipher.update(encrypted, "hex", "utf8")
decrypted += decipher.final("utf8");
return decrypted;
}
然后后端实现了一个Java版本的AES-192加密算法,其中关键的一步是
keySizeBites = 24; // AES-192加密算法对应192位,即24字节
ivSize = cipher.getBlockSize(); // AES块的大小,固定为128位,即16字节
final byte[][] keyAndIV = EVP_BytesToKey(keySizeBites, ivSize, md5, salt: null, password.getBytes(), count:1);
现在要求用Python实现加密该算法。
2.分析
资料:python之js解密_如何在Python中解密来自JavaScript CryptoJS.AES.encrypt的密码中提到:在JS上,有两种加密密码的方法,它们分别是:
- A. crypto.createCipher(algorithm, password)
- B. crypto.createCipheriv(algorithm, key, iv)
而上面的代码块采用的是方法A(它支持通过EVP_BytesToKey函数从提供的密码派生密钥和IV向量),而方法B才相当于python中的AES.new()方法(pyCrypto只支持密钥向量类型加密),因此我们必须手动实现OpenSSL的EVP_BytesToKey函数。
3.Python实现
3.1导入Crypto模块
No module named “Crypto” 解决方案
参考:No module named “Crypto” 解决方案
某些脚本需要用到Crypto
库,但当pip install Crypto
后仍提示:No module named ‘Crypto’
,解决方案如下:
pip uninstall crypto pycryptodome
pip install pycryptodome
pycrypto
和crypto
是同一个库,crypto
在 python
中又被称为pycrypto
,它是一个第三方库,但是已经停止更新了,所以不建议大家安装。pycryptodome
是crypto
的延伸版本,用法和crypto
是一模一样的,可以完全替代crypto
。
3.2Python实现EVP_BytesToKey函数
参考资料:在Python中实现OpenSSL AES加密给出了Python中的EVP_BytesToKey函数,它可以基于密码得到密钥和初始向量。
def EVP_BytesToKey(password, salt, key_len, iv_len):
"""
Derive the key and the IV from the given password and salt.
"""
from hashlib import md5
dtot = md5(password + salt).digest()
d = [ dtot ]
while len(dtot)<(iv_len+key_len):
d.append( md5(d[-1] + password + salt).digest() )
dtot += d[-1]
return dtot[:key_len], dtot[key_len:key_len+iv_len]
3.3Python实现AES/CBC/PKCS5Padding 加解密
参考资料:[Python] AES/CBC/PKCS5Padding 加解密给出了Python基于密钥和初始向量实现CBC模式,PKCS5Padding填充的AES-192算法(输出bytes编码为base64)。因为我们的需求是要求hex编码,所以更改后的代码为
from Crypto.Cipher import AES
import base64
def EVP_BytesToKey(password, salt, key_len, iv_len):
"""
Derive the key and the IV from the given password and salt.
"""
from hashlib import md5
dtot = md5(password + salt).digest()
d = [ dtot ]
while len(dtot)<(iv_len+key_len):
d.append( md5(d[-1] + password + salt).digest() )
dtot += d[-1]
return dtot[:key_len], dtot[key_len:key_len+iv_len]
def encrypt(text):
"""
AES/CBC/PKCS5Padding 加密
"""
BLOCK_SIZE = AES.block_size
# 需要加密的文件,不足BLOCK_SIZE的补位(text可能是含中文,而中文字符utf-8编码占3个位置,gbk是2,所以需要以len(text.encode()),而不是len(text)计算补码)
text = text + (BLOCK_SIZE - len(text.encode()) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(text.encode()) % BLOCK_SIZE)
# 创建一个aes对象 key 秘钥 mdoe 定义模式 iv#偏移量--必须16字节
cipher = AES.new(key=key, mode=AES.MODE_CBC, IV=iv)
# 利用AES对象进行加密
encrypted_text = cipher.encrypt(text.encode())
return base64.b16encode(encrypted_text).decode('utf-8').lower()
def decrypt(encrypted_text):
"""
AES/CBC/PKCS5Padding 解密
"""
# 需要解密的文件
encrypted_text = base64.b16decode(encrypted_text.upper())
# 创建一个aes对象 key 秘钥 mdoe 定义模式 iv#偏移量--必须16字节
cipher = AES.new(key=key, mode=AES.MODE_CBC, IV=iv)
# 利用AES对象进行解密
decrypted_text = cipher.decrypt(encrypted_text)
# 去除补位
dec_res = decrypted_text[:-ord(decrypted_text[len(decrypted_text) - 1:])]
return dec_res.decode()
password = 'xxxxxxxxxxxxx'.encode() # bytes类型的密码
key_len, iv_len = 24, 16 # AES-192
AESkey = EVP_BytesToKey(password, b'', key_len, iv_len) # 通过密码的到密钥和向量
key, iv = AESkey[0], AESkey[1]
print('密钥: %s\n向量: %s' % (key, iv))
text = 'abcdefghijklmnhi' # 明文
print('明文:', text)
jiami = encrypt(text)
print('密文:', jiami)
jiemi = decrypt(jiami)
print('解密:', jiemi)