在Python 2.7中复制Java的PBEWithMD5AndDES

如果不是立即显而易见,那么首先我要说的不是加密人。

我的任务是在Python 2.7中复制Java的PBEWithMD5AndDES(使用DES加密的MD5摘要)的行为。

我可以访问Python的加密工具包PyCrypto。

这是我试图复制其行为的Java代码:

import java.security.spec.KeySpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import java.security.spec.AlgorithmParameterSpec; import javax.crypto.spec.PBEParameterSpec; import javax.crypto.Cipher; import javax.xml.bind.DatatypeConverter; public class EncryptInJava { public static void main(String[] args) { String encryptionPassword = "q1w2e3r4t5y6"; byte[] salt = { -128, 64, -32, 16, -8, 4, -2, 1 }; int iterations = 50; try { KeySpec keySpec = new PBEKeySpec(encryptionPassword.toCharArray(), salt, iterations); SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndDES").generateSecret(keySpec); AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, iterations); Cipher encoder = Cipher.getInstance(key.getAlgorithm()); encoder.init(Cipher.ENCRYPT_MODE, key, paramSpec); String str_to_encrypt = "MyP455w0rd"; byte[] enc = encoder.doFinal(str_to_encrypt.getBytes("UTF8")); System.out.println("encrypted = " + DatatypeConverter.printBase64Binary(enc)); } catch (Exception e) { e.printStackTrace(); } } } 

对于给定的值,它输出以下内容:

 encrypted = Icy6sAP7adLgRoXNYe9N8A== 

这是我的火腿尝试将上面的内容移植到Python, encrypt_in_python.py

 from Crypto.Hash import MD5 from Crypto.Cipher import DES _password = 'q1w2e3r4t5y6' _salt = '\x80\x40\xe0\x10\xf8\x04\xfe\x01' _iterations = 50 plaintext_to_encrypt = 'MyP455w0rd' if "__main__" == __name__: """Mimic Java's PBEWithMD5AndDES algorithm to produce a DES key""" hasher = MD5.new() hasher.update(_password) hasher.update(_salt) result = hasher.digest() for i in range(1, _iterations): hasher = MD5.new() hasher.update(result) result = hasher.digest() key = result[:8] encoder = DES.new(key) encrypted = encoder.encrypt(plaintext_to_encrypt + ' ' * (8 - (len(plaintext_to_encrypt) % 8))) print encrypted.encode('base64') 

它输出一个完全不同的字符串。

是否可以将Java实现移植到使用标准Python库的Python实现?

显然,Python实现要求我加密的明文是八个字符的倍数,我甚至不确定如何填充我的明文输入以满足该条件。

谢谢你的帮助。

感谢GregS的评论,我能够对这个转换进行排序!

为了将来参考,这个Python代码模仿上面Java代码的行为:

 from Crypto.Hash import MD5 from Crypto.Cipher import DES _password = 'q1w2e3r4t5y6' _salt = '\x80\x40\xe0\x10\xf8\x04\xfe\x01' _iterations = 50 plaintext_to_encrypt = 'MyP455w0rd' # Pad plaintext per RFC 2898 Section 6.1 padding = 8 - len(plaintext_to_encrypt) % 8 plaintext_to_encrypt += chr(padding) * padding if "__main__" == __name__: """Mimic Java's PBEWithMD5AndDES algorithm to produce a DES key""" hasher = MD5.new() hasher.update(_password) hasher.update(_salt) result = hasher.digest() for i in range(1, _iterations): hasher = MD5.new() hasher.update(result) result = hasher.digest() encoder = DES.new(result[:8], DES.MODE_CBC, result[8:16]) encrypted = encoder.encrypt(plaintext_to_encrypt) print encrypted.encode('base64') 

这个Python代码在Python 2.7中输出以下内容:

 Icy6sAP7adLgRoXNYe9N8A== 

再次感谢GregS指出我正确的方向!

对于Python 3.6,我使用以下代码进行了测试,它可以从上面稍作改动:

 from Crypto.Hash import MD5 from Crypto.Cipher import DES import base64 import re _password = b'q1w2e3r4t5y6' _salt = b'\x80\x40\xe0\x10\xf8\x04\xfe\x01' _iterations = 50 plaintext_to_encrypt = 'MyP455w0rd' # Pad plaintext per RFC 2898 Section 6.1 padding = 8 - len(plaintext_to_encrypt) % 8 plaintext_to_encrypt += chr(padding) * padding if "__main__" == __name__: """Mimic Java's PBEWithMD5AndDES algorithm to produce a DES key""" hasher = MD5.new() hasher.update(_password) hasher.update(_salt) result = hasher.digest() for i in range(1, _iterations): hasher = MD5.new() hasher.update(result) result = hasher.digest() encoder = DES.new(result[:8], DES.MODE_CBC, result[8:16]) encrypted = encoder.encrypt(plaintext_to_encrypt) print (str(base64.b64encode(encrypted),'utf-8')) decoder = DES.new(result[:8], DES.MODE_CBC, result[8:]) d = str(decoder.decrypt(encrypted),'utf-8') print (re.sub(r'[\x01-\x08]','',d)) 

输出:

Icy6sAP7adLgRoXNYe9N8A ==

MyP455w0rd

我从这里找到了一个

 import base64 import hashlib import re import os from Crypto.Cipher import DES def get_derived_key(password, salt, count): key = password + salt for i in range(count): m = hashlib.md5(key) key = m.digest() return (key[:8], key[8:]) def decrypt(msg, password): msg_bytes = base64.b64decode(msg) salt = '\xA9\x9B\xC8\x32\x56\x35\xE3\x03' enc_text = msg_bytes (dk, iv) = get_derived_key(password, salt, 2) crypter = DES.new(dk, DES.MODE_CBC, iv) text = crypter.decrypt(enc_text) return re.sub(r'[\x01-\x08]','',text) def encrypt(msg, password): salt = '\xA9\x9B\xC8\x32\x56\x35\xE3\x03' pad_num = 8 - (len(msg) % 8) for i in range(pad_num): msg += chr(pad_num) (dk, iv) = get_derived_key(password, salt, 2) crypter = DES.new(dk, DES.MODE_CBC, iv) enc_text = crypter.encrypt(msg) return base64.b64encode(enc_text) def main(): msg = "hello" passwd = "xxxxxxxxxxxxxx" encrypted_msg = encrypt(msg, passwd) print encrypted_msg plain_msg = decrypt(encrypted_msg, passwd) print plain_msg if __name__ == "__main__": main() 

如果您在Python 3中使用较新的Cryptodome库,则还需要将plaintext_to_encrypt编码为’latin-1’,如下所示。

 from Cryptodome.Hash import MD5 from Cryptodome.Cipher import DES import base64 import re _password = b'q1w2e3r4t5y6' _salt = b'\x80\x40\xe0\x10\xf8\x04\xfe\x01' _iterations = 50 plaintext_to_encrypt = 'MyP455w0rd' # Pad plaintext per RFC 2898 Section 6.1 padding = 8 - len(plaintext_to_encrypt) % 8 plaintext_to_encrypt += chr(padding) * padding if "__main__" == __name__: """Mimic Java's PBEWithMD5AndDES algorithm to produce a DES key""" hasher = MD5.new() hasher.update(_password) hasher.update(_salt) result = hasher.digest() for i in range(1, _iterations): hasher = MD5.new() hasher.update(result) result = hasher.digest() encoder = DES.new(result[:8], DES.MODE_CBC, result[8:16]) encrypted = encoder.encrypt(plaintext_to_encrypt.encode('latin-1')) #encoded plaintext print (str(base64.b64encode(encrypted),'utf-8')) decoder = DES.new(result[:8], DES.MODE_CBC, result[8:]) d = str(decoder.decrypt(encrypted),'utf-8') print (re.sub(r'[\x01-\x08]','',d))