奇怪的DES行为 - 使用不同的密钥解密成功

时间:2016-07-19 13:05:06

标签: java encryption des

偶尔,我遇到一个有趣的,奇怪的事情:使用几个不同的密钥可以解密相同的加密文本块!

有人可以告诉我出了什么问题吗?非常感谢。

请不要试图让我切换到三重DES / AES等,我只是想知道问题出在哪里 - 调用Java SDK的方式,还是Java SDK中的错误?

以下是Windows 7上的输出,在Linux框中输出相同:

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:\>

源代码:

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;
    }
}

2 个答案:

答案 0 :(得分:4)

DES操作(加密和解密)忽略密钥的每个字节的lsbit。也就是说,如果你翻转键中的任何lsbits,操作将保持不变。这就是您尝试的密钥中发生的情况:空间的ASCII代码是0x20,而ASCII代码是!是0x21;它们仅在lsbit上有所不同。因此,如果密钥具有带空格的ASCII代码的字节,则可以用!替换它,并且它仍然能够解密。类似地,*的ASCII码是0x2a,而+的ASCII码是0x2b;也只是在lsbit上有所不同。

在最初的DES标准中,lsbit应该用作奇偶校验位(每个字节总是具有奇校验)。它应该是手动输入密钥的弱错误检查。如今,没有人进行这种奇偶校验,因此lsbit会被忽略。

Poncho's上的Answer富有洞察力的Cryptography Stackexchange中提取。

答案 1 :(得分:1)

DES有一个56位密钥,每个密钥字节的lsbit最初用于奇偶校验,现在它被忽略了。

答案是:不要使用DES! DES不安全,已被AES(高级加密标准)取代,AES专门用于替代DES。

此外,不应使用字符串作为密钥,最佳做法是使用PBKDF2(基于密码的密钥派生函数)等函数从字符串派生加密密钥。