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;
}
}
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.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With