DES cipher with pyca/cryptography (PBEWithMD5AndDES)

Question:

To support some legacy application I need to implement PBEWithMD5AndDES (RFC2898 Section 6.1) in python. I know this is insecure, deprecated and should not be used anymore. But this is sadly the requirement I have.

I already have a working version that uses PyCrypto/PyCryptodome but I would need to introduce PyCryptodome as additional dependency to the project which is something I want to avoid. As we are already using pyca/cryptography in other parts of our code I’d prefer this library over PyCrypto(dome). However due to the nature of PBEWithMD5AndDES I need DES encryption support but pyca/cryptography only supports Triple DES (3DES) as far as I understood.

Is there a way to (single) DES encrypt something using pyca/cryptography? Basically I need to replace the following usage of Crypto.Cipher.DES from with something from pyca/cryptography:

key, init_vector = _pbkdf1_md5(a_password, a_salt, a_iterations)
cipher = DES.new(key, DES.MODE_CBC, init_vector)
encrypted_message = cipher.encrypt(encoded_message)

**UPDATE**:

Thanks to @SquareRootOfTwentyThree I ended up with this:

(key, init_vector) = _pbkdf1_md5(a_password, a_salt, a_iterations)
cipher = Cipher(algorithms.TripleDES(key), modes.CBC(init_vector), default_backend())
encryptor = self.cipher.encryptor()
encrypted = encryptor.update(encoded_message)
encryptor.finalize()

def _pbkdf1_md5(a_password, a_salt, a_iterations):
    digest = Hash(MD5(), default_backend())
    digest.update(a_password)
    digest.update(a_salt)

    key = None
    for i in range(a_iterations):
        key = digest.finalize()
        digest = Hash(MD5(), default_backend())
        digest.update(key)

    digest.finalize()

    return key[:8], key[8:16]
Asked By: dpr

||

Answers:

Is there a way to (single) DES encrypt something using pyca/cryptography?

Yes, just pass an 8 byte key to cryptography.hazmat.primitives.ciphers.algorithms.TripleDES. This will use the same key for each DES transform within triple-DES.

Triple-DES is also known as DES-EDE for Encrypt, Decrypt and then Encrypt. If you use the same key for each then one of the encrypt / decrypt pairs will result in the identity function, leaving just a single DES encrypt.


Note that not all triple DES implementations will accept a single key (since single DES is usually present), but this one does:

The secret key. This must be kept secret. Either 64, 128, or 192 bits long. DES only uses 56, 112, or 168 bits of the key as there is a parity byte in each component of the key. Some writing refers to there being up to three separate keys that are each 56 bits long, they can simply be concatenated to produce the full key.

although I must admit that you’d have to understand how triple-DES works to make any sense of that text.

Note as well that the implementation of DES-EDE for single DES is currently not optimized, it will perform all three operations even if two of them cancel each other out.

Thanks to @SquareRootOfTwentyThre, My DES-ECB Example:

PyCrypto/PyCryptodome

    import base64
    # pip install pycryptodome
    from Crypto.Cipher import DES
    from Crypto.Util.Padding import pad

    key = "the key"
    data = "Hello World"

    key = key.encode("utf-8")
    key = (key + b'' * (8 - len(key) % 8))[:8]

    data_p = pad(data.encode("utf-8"), key_size, 'pkcs7')
    result = DES.new(key, DES.MODE_ECB).encrypt(data_p)
    b64result = base64.b64encode(result)

    print("Key:", key)
    print("Data: ", data_p)
    print("Result: ",result)
    print("base64: ", b64result)

pyca/cryptography

    import base64
    from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
    from cryptography.hazmat.primitives import padding

    key = "the key"
    data = "Hello World"

    key = key.encode("utf-8")
    key = (key + b'' * (8 - len(key) % 8))[:8]

    padd = padding.PKCS7(64).padder()
    data_p = padd.update(data.encode("utf-8")) + padd.finalize()

    cipher = Cipher(algorithms.TripleDES(key), modes.ECB())
    encryptor = cipher.encryptor()
    
    result = encryptor.update(data_p) + encryptor.finalize()
    b64result = base64.b64encode(result)

    print("Key:", key)
    print("Data: ", data_p)
    print("Result: ", result)
    print("base64: ", b64result)

Result:

enter image description here

Answered By: tinyhare
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.