【问题标题】:Replicate Java's PBEWithMD5AndDES in Python 2.7在 Python 2.7 中复制 Java 的 PBEWithMD5AndDES
【发布时间】:2014-08-01 19:47:42
【问题描述】:

如果不是很明显,让我先说我不是加密人。

我的任务是在 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 实现要求我加密的明文是 8 个字符的倍数,我什至不确定如何填充明文输入以满足该条件。

感谢您的帮助。

【问题讨论】:

  • rfc 2898,特别是section 6.1。您需要在 CBC 模式下使用 DES,密钥是前 8 个字节,IV 是哈希结果的后 8 个字节。
  • 这是一种错误的密码派生方法,不应使用。而是使用随机盐在 HMAC 上迭代大约 100 毫秒(盐需要与哈希一起保存)。使用 password_hash、PBKDF2、Bcrypt 等函数和类似函数。关键是让攻击者花费大量时间通过蛮力寻找密码。请参阅 OWASP(开放式 Web 应用程序安全项目)Password Storage Cheat Sheet
  • 另请参阅 Security Stackexchange 上的 How to securely hash passwords, The Theory

标签: java python-2.7 encryption md5 des


【解决方案1】:

感谢 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 为我指明了正确的方向!

【讨论】:

  • 其实万一有人(像我)想知道 \xe0 是如何计算为 -32 的十六进制的,你这样做的方式如下:所以你找到 31 的十六进制,即0x1f 并从 0xff 中减去它。所以 0xff - 0x1f = 0xe0。这就是你如何推导出负整数的十六进制等价物。有关数字系统和不同数字系统中的负数表示的相同(为什么以及如何)Google 的解释。
【解决方案2】:

对于 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

【讨论】:

    【解决方案3】:

    我从here找到了一个

    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()
    

    【讨论】:

      【解决方案4】:

      如果您在 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))
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-11-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-04-09
        • 2019-09-30
        • 1970-01-01
        • 2016-12-23
        相关资源
        最近更新 更多