Search code examples
pythonrubyencryptionopenssl

Decrypt ruby DES-EDE3-CBC encrypted data in Python


I have a bunch of data which are encrypted in Ruby by following code

text = '12345678'
key = '6b4f0a29d4bba86add88be9d'
cipher = OpenSSL::Cipher.new('DES-EDE3-CBC').encrypt
cipher.key = key
s = cipher.update(text) + cipher.final
encrypted_text = s.unpack('H*')[0].upcase
# => 3B223AA60F1921F34CBBBAC209ACDCE4

It can be decrypted in Ruby

cipher = OpenSSL::Cipher.new('DES-EDE3-CBC').decrypt
cipher.key = key
s = [encrypted_text].pack("H*").unpack("C*").pack("c*")
cipher.update(s) + cipher.final
# => 12345678

Now I have to decrypt the data in Python. I wrote the encryption code as below:

from Crypto.Cipher import DES3
from Crypto import Random
from base64 import b64encode, b64decode
from Crypto.Util.Padding import pad, unpad

key = '6b4f0a29d4bba86add88be9d'
iv = Random.new().read(DES3.block_size)
cipher = DES3.new(key, DES3.MODE_CBC, iv)
text = '12345678'.encode()
encrypted = cipher.encrypt(text)
encrypted_text = encrypted.hex()
print(encrypted_text)
# => f84f555b02e3ee24

As you can see, the encrypted data is quite different, at least is the length. So decryption part does not work. How can I modify the Python code to compatible with Ruby? Unfortunately, the data were available there by ruby application so I can only modify Python.


Solution

  • For compatibility with the Ruby code, the Python code lacks:

    • the PKCS#7 padding
    • a zero IV (i.e. an IV consisting of all 0x00 values). For Triple DES, the blocksize and therefore the IV is 8 bytes in size.

    With this, the Python code has to be fixed as follows:

    from Crypto.Cipher import DES3
    from Crypto.Util.Padding import pad, unpad
    
    key = '6b4f0a29d4bba86add88be9d'
    iv = b'\0' *  DES3.block_size                       # apply a zero IV
    cipher = DES3.new(key, DES3.MODE_CBC, iv)
    text = pad('12345678'.encode(), DES3.block_size)    # pad
    encrypted = cipher.encrypt(text)
    encrypted_text = encrypted.hex()
    print(encrypted_text.upper()) # => 3B223AA60F1921F34CBBBAC209ACDCE4
    

    With this change, the Python code provides the result of the Ruby code. The corresponding decryption is:

    ...
    key = '6b4f0a29d4bba86add88be9d'
    iv = b'\0' *  DES3.block_size     
    cipher = DES3.new(key.encode(), DES3.MODE_CBC, iv)
    ciphertext = bytes.fromhex('3B223AA60F1921F34CBBBAC209ACDCE4')
    decrypted = cipher.decrypt(ciphertext)
    decryptedUnpadded = unpad(decrypted, DES3.block_size)
    print(decryptedUnpadded.decode('utf8')) # => 12345678
    

    Note, however, that the code has vulnerabilities:

    • The use of a static IV results in the reuse of key/IV pairs when applying a fixed key, which is a more or less severe vulnerability depending on the mode. To avoid this, a common approach is to generate a random IV for each encryption. This IV is not secret and is given to the encrypting side along with the ciphertext (generally concatenated).
    • Actually, the key should be a random byte sequence and not a string, because of the usually lower entropy. If the key material is a string, a key derivation function (at least PBKDF2) should be applied in conjunction with a random, not secret salt.
    • CBC is not authenticated encryption. Authenticated encryption guarantees authenticity/integrity in addition to confidentiality.
    • Triple DES is deprecated, the current standard is AES.