2

I'm trying to decrypt data using PyCrypto. The data was encoded in Java with the javax.crypto package. The encryption is Triple DES (referred to as "DESede" in Java). As far as I can tell, default settings are used for everything. However, when I go to decrypt the data in Python there is always a problem with the data.

Here's the Java code that does encrypting/decrypting:

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import java.security.spec.KeySpec;

public final class Encrypter
{
    public static final String DESEDE_ENCRYPTION = "DESede";

    private KeySpec keySpec;
    private SecretKeyFactory keyFactory;
    private Cipher cipher;

    private static final String UNICODE_FORMAT = "UTF8";

    public Encrypter(String encryptionKey)
        throws Exception
    {
        byte[] keyAsBytes = encryptionKey.getBytes(UNICODE_FORMAT);
        keySpec = new DESedeKeySpec(keyAsBytes);
        keyFactory = SecretKeyFactory.getInstance(DESEDE_ENCRYPTION);
        cipher = Cipher.getInstance(DESEDE_ENCRYPTION);
    }

    public String encryptString(String unencryptedString)
    {
        SecretKey key = keyFactory.generateSecret(keySpec);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] cleartext = unencryptedString.getBytes(UNICODE_FORMAT);
        byte[] ciphertext = cipher.doFinal(cleartext);

        BASE64Encoder base64encoder = new BASE64Encoder();
        return base64encoder.encode(ciphertext);
    }

    public String decryptString(String encryptedString)
    {
        SecretKey key = keyFactory.generateSecret(keySpec);
        cipher.init(Cipher.DECRYPT_MODE, key);
        BASE64Decoder base64decoder = new BASE64Decoder();
        byte[] ciphertext = base64decoder.decodeBuffer(encryptedString);
        byte[] cleartext = cipher.doFinal(ciphertext);

        return bytesToString(cleartext);
    }

    private static String bytesToString(byte[] bytes)
    {
        StringBuilder sb = new StringBuilder();
        for (byte aByte : bytes)
        {
            sb.append((char) aByte);
        }
        return sb.toString();
    }
}

But when I take one of the base64-encoded strings that was produced by this code, I can't decode it. Here's an example of some of the python code I've tried:

from Crypto.Cipher import DES3
import array

key = <value of the key, as a hex string>
encryptedvalue = <the value that's encrypted, as a string>
keyarray = array.array('B', key.decode("hex"))
des = DES3.new(keyarray)
value = des.decrypt(encryptedvalue.decode('base64'))

value.decode('utf-8') # Gives me an error

The errors I've gotten have looked along the lines of

UnicodeDecodeError: 'utf8' codec can't decode byte 0xa7 in position 6: invalid start byte

Which means that somewhere along the way, I haven't gotten something set up correctly. I've been working on this for a few hours, even going so far as trying to look into the SunJCE source code, which implements DESede, to see what defaults they use, but to no avail. I'm going to be using this as part of a script that runs automatically, so I'd really rather not have to use Java to do my decryption. Does anyone know what I need to do to decrypt my data correctly?

Rob Watts
  • 6,866
  • 3
  • 39
  • 58
  • Don't know python but going by the error it seems that python is trying to decode a utf-8 encoded value which does not have equivalent *Unicode* value. – Ravi Trivedi Apr 25 '13 at 00:55
  • Not sure how you are supposed to pass data from java to python but I think your problem lies in data transportation where encoding becomes different at python receiving end, which may need to be handled before decoding in base64 and utf-8 – Ravi Trivedi Apr 25 '13 at 01:11
  • 1 more thing I would suggest is that check the default setting of how python decodes base64 and utf-8. It may be differing from java settings. – Ravi Trivedi Apr 25 '13 at 01:22
  • I don't think you need to decode value. Have you tried just printing exactly what you get from decode? Also, I'm fairly certain if you use the two argument init method for a cipher in Java then it will generate a random initialization vector. You will need to make sure the IV is the same between Java and Python. – Pace Apr 25 '13 at 02:24
  • @Pace that's one of the reasons I was trying to dig into the source code - the Java code doesn't specify an IV when encrypting or decrypting. If it is using an IV it must be using some default IV value, or else the Java decryption wouldn't work. – Rob Watts Apr 25 '13 at 15:27
  • @RaviTrivedi I doubt that the problem is in data transportation - the encrypted data is stored as a base64 encoded string in a database. I'd also be surprised if the default of how python decodes base64 and utf-8 is different, but I'll double check that. – Rob Watts Apr 25 '13 at 15:32
  • @RaviTrivedi It did have to do with the encoding. The key, although it is definitely a hex string, is not used as a hex string. – Rob Watts Apr 25 '13 at 16:42
  • Are you getting the same byte array on both sides for a key? In other words, in Java, if you pass "0123" into the Encrypter constructor are you passing in "30313233" for the key in python? – Pace Apr 25 '13 at 16:57
  • Your code works for me if I use a key of "012345678901234567890123" in Java and "303132333435363738393031323334353637383930313233" in python. If I do the last decode into utf-8 I get a unicode string. If I don't I get a regular str. – Pace Apr 25 '13 at 17:08
  • @Pace The problem was right there - I was expected to use the same string in each, and didn't notice that the Java was using a hex string (e.g "af3b3569ed") as utf8. – Rob Watts Apr 25 '13 at 17:15
  • Nice. So hex string needed to be converted into proper hex utf-8 bytes. Python got confused with hex string supplied by java. – Ravi Trivedi Apr 26 '13 at 00:26

1 Answers1

3

All I had to do to make it work was to change this line

keyarray = array.array('B', key.decode("hex"))

to this:

keyarray = array.array('B', key.encode("utf-8"))

This matches the way that java was encoding the key, allowing me to have the correct encryption key.


If you've come here hoping to learn something from this question, here's some general advice:

  1. Double-check your assumptions: The key string was a hex string, so I assumed it was being used as such.
  2. Make sure you know what your assumptions are: I didn't consciously think about how I was making an assumption about how the key was used. This tends to be a very frequent problem in both programming and life in general.
  3. Check all the values along the way (especially when you have an Oracle): Looking at the values in the byte arrays was what led me to realizing my problem.
Rob Watts
  • 6,866
  • 3
  • 39
  • 58