Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange DES behavior - decryption is successful using different keys

Occasionally, I meet an interesting, strange thing: same block of encrypted text can be decrypted using several different key!

Can anyone please indicate me what's going wrong? Thanks a lot.

Please don't try to let me switch to triple DES/AES etc, I just want to know where the problem is - the way calling the Java SDK, or the bug in Java SDK?

Below is output on Windows 7, same result in Linux box:

D:\>java -version
java version "1.7.0_21"
Java(TM) SE Runtime Environment (build 1.7.0_21-b11)
Java HotSpot(TM) 64-Bit Server VM (build 23.21-b01, mixed mode)

D:\>java DESTest -e 12345678 abcde977

encrypted as [17fd146fa6fdbb5db667efe657dfcb60]

D:\>java DESTest -d 17fd146fa6fdbb5db667efe657dfcb60 abcde977

decryted as [12345678]

D:\>java DESTest -d 17fd146fa6fdbb5db667efe657dfcb60 abcde976

decryted as [12345678]

D:\>java DESTest -d 17fd146fa6fdbb5db667efe657dfcb60 abcde967

decryted as [12345678]

D:\>java DESTest -d 17fd146fa6fdbb5db667efe657dfcb60 abcde867

decryted as [12345678]

D:\>java DESTest -d 17fd146fa6fdbb5db667efe657dfcb60 abcdf867
Exception in thread "main" java.lang.RuntimeException: javax.crypto.BadPaddingEx
ception: Given final block not properly padded
        at DESTest.des(DESTest.java:46)
        at DESTest.dec(DESTest.java:31)
        at DESTest.main(DESTest.java:19)
Caused by: javax.crypto.BadPaddingException: Given final block not properly padd
ed
        at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:811)
        at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:676)
        at com.sun.crypto.provider.DESCipher.engineDoFinal(DESCipher.java:314)
        at javax.crypto.Cipher.doFinal(Cipher.java:2087)
        at DESTest.des(DESTest.java:44)
        ... 2 more

D:\>java DESTest -e 12345678 abcde976

encrypted as [17fd146fa6fdbb5db667efe657dfcb60]

D:\>java DESTest -e 12345678 abcde967

encrypted as [17fd146fa6fdbb5db667efe657dfcb60]

D:\>

The source code:

import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;

public class DESTest {
    public static void main(String[] args) {
        if (args.length < 3) {
            System.out.println("usage: java " + DESTest.class.getCanonicalName() + " -e|-d text key");
            return;
        }
        String mode = args[0].trim();
        String text = args[1].trim();
        String key = args[2].trim();
        try {
            String s = "-d".equalsIgnoreCase(mode) ? dec(text, key) : enc(text, key);
            System.out.println("\n" + ("-d".equalsIgnoreCase(mode) ? "decryted as [" : "encrypted as [") + s + "]");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    private static String enc(String plainText, String key) throws UnsupportedEncodingException {
        return new String(encHex(des(plainText.getBytes("UTF-8"), key, Cipher.ENCRYPT_MODE)));
    }

    private static String dec(String encrypted, String key) throws UnsupportedEncodingException {
        return new String(des(decHex(encrypted), key, Cipher.DECRYPT_MODE), "UTF-8");
    }

    private static byte[] des(byte[] bytes, String key, int cipherMode) {
        final String encoding = "UTF-8";
        try {
            DESKeySpec desKey = new DESKeySpec(key.getBytes(encoding));
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
            SecretKey securekey = keyFactory.generateSecret(desKey);
            // SecretKey securekey = new SecretKeySpec(key.getBytes(encoding), "DES");//same result as the 3 lines above
            Cipher cipher = Cipher.getInstance("DES");
            SecureRandom random = new SecureRandom();
            cipher.init(cipherMode, securekey, random);
            return cipher.doFinal(bytes);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();

    private static String encHex(byte[] bytes) {
        final char[] chars = new char[bytes.length * 2];
        for (int i = 0, j = 0; i < bytes.length; i++) {
            chars[j++] = HEX_CHARS[(0xF0 & bytes[i]) >>> 4];
            chars[j++] = HEX_CHARS[0x0F & bytes[i]];
        }
        return new String(chars);
    }

    private static byte[] decHex(String hex) {
        final int len = hex.length();
        final byte[] bytes = new byte[len / 2];
        for (int i = 0, j = 0; j < len; i++) {
            int f = Character.digit(hex.charAt(j), 16) << 4;
            j++;
            f = f | Character.digit(hex.charAt(j), 16);
            j++;
            bytes[i] = (byte) (f & 0xFF);
        }
        return bytes;
    }
}
like image 390
reqresp Avatar asked Jul 19 '16 13:07

reqresp


2 Answers

The DES operation (both encryption and decryption) ignores the lsbit of each byte of the key. That is, if you flip any of the lsbits within the key, the operation remains the same. That's what is happening in the keys you tried: the ASCII code for space is 0x20, while the ASCII code for ! is 0x21; they differ only in the lsbit. So, if the key has a byte with the ASCII code for a space, you could replace it with a !, and it'll still be able to decrypt. Similarly, the ASCII code for * is 0x2a, while the ASCII code for + is 0x2b; also differs only in the lsbit.

In the original DES standard, the lsbit was supposed to be used as a parity check bit (with each byte always having odd parity). It was supposed to be a weak error check for manually entered keys. Nowadays, no one does this parity check, and so the lsbit gets ignored.

Extracted from Poncho's insightful Answer on Cryptography Stackexchange.

like image 109
fdam Avatar answered Nov 05 '22 21:11

fdam


DES has a 56-bit key, the lsbit of each key byte was initiallly used for parity, now it is ignored.

The answer is: do not use DES! DES is insecure and has been superceeded by AES (Advanced Encryption Standard) AES was specifically designed to replace DES.

Further one should not use a character string as a key, the best practice is to derive an encryption key from the character string with a function such as PBKDF2 (Password Based Key Derivation Function).

like image 1
zaph Avatar answered Nov 05 '22 21:11

zaph