0

I am working with meet-in-the-middle attack on 2DES. I have implemented the DES encryption/decryption and it is working. The way I am trying to achieve this is by storing, within a for loop, the intermediate ciphers as the key of a HashMap and the possible keys as the values of the HashMap. Both as integers - Started with byte[], but figured out that a key cannot be an array. However, within this for loop I also want to make sure that the possible keys are unique i.e. I have a while loop, which makes sure that the size of the HashMap is 2^20 (only 20 bits of the DES key are effective). Aftwards, I try to find the keys, which have matching intermediate cipher text by iterating through the HashMap with a foreach and compare each intermediate cipher text from the encryptions with the intermediate cipher text from the decryptions.

However, I am unable to find the match as it takes too long to finish. I have been waiting for like 20 mins without any result.

while (intermediateCipher.size() < Math.pow(2, 20)) {
            byte[] key = generateDesKey();

            intermediateCipher.put(ByteBuffer.wrap(encrypt(key, plainText)).getInt() , ByteBuffer.wrap(key).getInt());
        } 

        int count = 0;
        for (Entry<Integer, Integer> arr : intermediateCipher.entrySet()) {
            byte[] k2 = ByteBuffer.allocate(8).putInt(arr.getValue()).array();
            int temp = ByteBuffer.wrap(decrypt(k2, cipherText2)).getInt();
            if (intermediateCipher.containsKey(temp)) {
                count++;
                byte[] k1 = ByteBuffer.allocate(8).putInt(intermediateCipher.get(temp)).array();

                if (encrypt(k2, encrypt(k1, plainText)) == cipherText2) {
                    System.out.println("Key is " + k1 + " " + k2);
                }
            }
        }

intermediateCipher is my HashMap .

PlainText is a byte[] of 8 bytes, cipherText2 is the encryption result of 2DES on the plainText and generateDesKey is the method which generates 64 bits, where the parity bits are skipped in order to make sure that the 20 bits are effective, and convert the bits to a byte[] as DES requires that

user8231110
  • 153
  • 9
  • 5
    Is there some course teaching this kind of thing? I saw a **very** [similar question](https://stackoverflow.com/questions/53436072/why-does-checking-if-a-hashmap-has-a-certain-value-take-very-long-to-execute-wit) just the other day, and it wasn't the first... – Krease Nov 23 '18 at 23:04
  • There are several concept and understanding errors. 1. The input to 2DES is an array of 8 bytes of which only the 7ms bytes are used. 2. 20 minutes is **nothing**, try days++. 3. "only 20 bits of the DES key are effective" makes is wrong and makes no sense. 4. 2DES has a 112-but key but is susceptible to the birthday paradox which reduces it. – zaph Nov 23 '18 at 23:07
  • @zaph. I mean that the key is 64bits, but the effective are 20bits and the rest is added with zeros. – user8231110 Nov 23 '18 at 23:14
  • Yes @flakes, it was me, but when I debugged the code I figured out that I was unable to use byte[] and String, so I changed to a integers for both the key and value. – user8231110 Nov 23 '18 at 23:20
  • @zaph, The reason why I say 20 bits is that, as you say. it will take long time to perform MITM on 56 bits key. Then I should generate 2^56 possible keys and then check. So, for the purpose of being able to see the results I work with 20 effective bits. I am still using 2 x 56 bits keys (actually 64 bits, which is reduced to 56 because of the parity bits) – user8231110 Nov 23 '18 at 23:23
  • 1
    Consider adding the the question that you are arbitrarily limiting the keys to 20-bits each. DES has a 56-bit key in 8 bytes, the "parity" bit is generally ignored these days. – zaph Nov 23 '18 at 23:29
  • Best would be to link to the task description directly (if it is online somewhere). – Thilo Nov 23 '18 at 23:58

2 Answers2

1

After reading your code, I have several suggestions to optimize it:

  1. Most important: Do not waste effort accessing the map twice if you can access just once: Instead of:
    if (intermediateCipher.containsKey(temp)) {
        byte[] k1 = intermediateCipher.get(temp);
        ...
    }

... reduce it to:

    byte[] k1 = intermediateCipher.get(temp);
    if (k1!=null) {
        ...
    }
  1. There is too much memory allocating within the loops, because it is useless to create a new ByteBuffer only for temporary operations and then discard it (too much GC overworking). If you are certain that the byte buffers used will have length 8 (or less), you can use a single buffer in the first loop:

    ByteBuffer tempBuffer=ByteBuffer.allocate(8);
    while (intermediateCipher.size() < Math.pow(2, 20)) {
        // Note: A call to rewind() must be done before each put/get operation on the buffer:
        byte[] key = generateDesKey();
        tempBuffer.rewind();
        tempBuffer.put(encrypt(key, plainText));
        tempBuffer.rewind();
        int mapKey=tempBuffer.getInt();
        tempBuffer.rewind();
        tempBuffer.put(key);
        tempBuffer.rewind();
        int mapValue=tempBuffer.getInt();
        intermediateCipher.put(mapKey, mapValue);
    }
    

In the second loop, a similar transformation could be done.

  1. As @Thilo suggested in a comment, it is always a good practice to pre-size your map in function of the expected maximum size. It shall go like this: Map<...> intermediateCipher=new HashMap<...>((int)(1.7d * expectedSize));

  2. Loop while (intermediateCipher.size() < Math.pow(2, 20)) shall be simplified to int len=Math.pow(2, 20); for (int i=0;i<len;i++)

Update

  1. If the call generateDesKey() returns the same on every iteration, you can set it before the loop.
Little Santi
  • 8,563
  • 2
  • 18
  • 46
  • I'm getting java.nio.BufferUnderflowException in line int mapKey = tempBuffer.put(encrypt(key, plainText)).getInt(); I did some research I found out that it means there are fewer than eight bytes remaining in this buffer, but how? – user8231110 Nov 24 '18 at 16:07
  • Ups. My fault. Right: That's because the buffer must be rewinded before each write to ensure the data is written at the beginning, and yet rewinded again before each read to ensure the data is read from the beginning. See my update at points 2 and 5. – Little Santi Nov 24 '18 at 20:12
  • Since arr.getValue() and intermediateCipher.get(temp) are integers, I need to convert them into Byte[]? Have tried with the following: byte[] k2 = ByteBuffer.allocate(8).putInt(arr.getValue()).array(); int temp = ByteBuffer.wrap(decrypt(k2, cipherText2)).getInt(); byte[] k1 = ByteBuffer.allocate(8).putInt(intermediateCipher.get(temp)).array(); – user8231110 Nov 24 '18 at 21:48
  • You need to convert them into byte[], OK. And you used those formulae. And what did you get? – Little Santi Nov 25 '18 at 15:33
  • I am getting a nullpointer Exception on byte[] k1 = ByteBuffer.allocate(8).putInt(intermediateCipher.get(temp)).array(); – user8231110 Nov 25 '18 at 16:32
  • `intermediateCipher.get(temp)` is returning null, then. Review your code. – Little Santi Nov 25 '18 at 16:51
  • By using a for loop as you suggested in #4, the size of the HashMap won't be 2^20 – user8231110 Nov 25 '18 at 20:49
  • @user8231110 Interesting. That can mean only that you are **overwriting keys** within the loop. Is that correct? – Little Santi Nov 25 '18 at 20:54
  • Yes, you're right. However, that is why I used a while loop in order to make sure that the size becomes 2^20 – user8231110 Nov 25 '18 at 20:56
  • So, there is where you are wasting time in your loop. If you cannot get an algorithm to assign a different key on each iteration, you will still be spending 20 minutes on each execution. – Little Santi Nov 25 '18 at 21:01
  • Can have if(intermediateCipher.containsValue(mapValue continue; in the beginning of the for loop. However, that will take a lot of time too – user8231110 Nov 25 '18 at 21:14
0

Here are some things you can try to shorten running time.

First, switch from HashMap to TreeMap. When a HashMap becomes too large, like 2^20, searching in the worst case is expected to go from O(1) to O(n), because all slots in the hash table are full with a large number of entries. However, TreeMap searching always runs in O(lg n).

Secondly, switch from Map<Integer, Integer> to Map<Integer, byte[]>. You only need to convert the Map keys to integers; you can leave the values as byte arrays, resulting in significantly fewer byte[] -> ByteBuffer -> int conversions.

Map<Integer, byte[]> intermediateCipher = new TreeMap<>();

and

while (intermediateCipher.size() < Math.pow(2, 20)) {
    byte[] key = generateDesKey();
    int encrypted = ByteBuffer.wrap(encrypt(key, plainText)).getInt();
    intermediateCipher.put(encrypted, key);
} 

int count = 0;
for (Entry<Integer, byte[]> arr : intermediateCipher.entrySet()) {
    byte[] k2 = arr.getValue();
    int temp = ByteBuffer.wrap(decrypt(k2, cipherText2)).getInt();

    if (intermediateCipher.containsKey(temp)) {
        count++;
        byte[] k1 = intermediateCipher.get(temp);
        if (encrypt(k2, encrypt(k1, plainText)) == cipherText2) {
            System.out.println("Key is " + k1 + " " + k2);
        }
    }
}
Leo Aso
  • 11,898
  • 3
  • 25
  • 46
  • "When a HashMap becomes too large, like 2^20, searching in the worst case is expected to go from O(1) to O(n), because all slots in the hash table are full with a large number of entries" Really? Wouldn't it just rehash with a larger number of buckets? (Might be a good idea to set the expected capacity up front to avoid doing too much of that). – Thilo Nov 23 '18 at 23:57
  • @Leo Aso, I have tried your suggestion and I have been waiting for approx. 20 mins without any result, unfortunately. I think it has something to do with the conversion from byte[] -> ByteBuffer -> int. – user8231110 Nov 24 '18 at 00:15