【问题标题】:java.security.InvalidKeyException: Wrong key size during decryptionjava.security.InvalidKeyException:解密期间密钥大小错误
【发布时间】:2015-12-15 21:59:15
【问题描述】:

在解密过程中,我得到了“错误的密钥大小”或“未正确填充的最终块”的混合,这取决于我正在运行的操作系统。

在 Win7 上,使用 IBMJCE 或 SUNJCE(均为 Java8),25% 的时间解密失败:

javax.crypto.BadPaddingException:给定最终块未正确填充 在 com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:811) 在 com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:676) 在 com.sun.crypto.provider.DESedeCipher.engineDoFinal(DESedeCipher.java:294) 在 javax.crypto.Cipher.doFinal(Cipher.java:2087)

在 mac 上,使用 SUNJCE,100% 的时间解密失败:

java.security.InvalidKeyException:错误的密钥大小 在 com.sun.crypto.provider.DESedeCrypt.init(DESedeCrypt.java:69) 在 com.sun.crypto.provider.CipherBlockChaining.init(CipherBlockChaining.java:91) 在 com.sun.crypto.provider.CipherCore.init(CipherCore.java:469) 在 com.sun.crypto.provider.DESedeCipher.engineInit(DESedeCipher.java:197) 在 javax.crypto.Cipher.implInit(Cipher.java:791) 在 javax.crypto.Cipher.chooseProvider(Cipher.java:849) 在 javax.crypto.Cipher.init(Cipher.java:1348)

使用DESEde,我认为密钥大小需要为24,我可以看到在windows上,解密后总是24字节,而在mac上,它永远不是24字节。

这里是起点。在 decryptWithSymmetricKey 期间总是抛出异常。请注意,我对大部分代码(特定于 DESede)进行了短周期处理,无法进一步缩小范围(对于安全领域来说非常新)。

public static void runtest() throws Exception {
    String symmPad = "DESede/CBC/PKCS5Padding";
    String asymmPad = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
    String pubKeyFp = "somekey";
    String stringToEncrypt = "abcdefg";

    KeyGenerator kgen = KeyGenerator.getInstance(DESEDE);
    kgen.init(112);
    SecretKey secKey = kgen.generateKey();

    String encryptedKey = encryptSymmetricKey(secKey, asymmPad);
    String encryptedData = encryptDataWithSymmetricKey(stringToEncrypt, secKey, symmPad);

    String decryptedKey = decryptWithPrivateKey(encryptedKey, pubKeyFp, asymmPad);
    String decryptedData = decryptWithSymmetricKey(encryptedData, decryptedKey, symmPad);
}

这里我们加密对称密钥,两种环境下的密钥长度都是24

private static String encryptSymmetricKey(SecretKey secKey, String asymmPadding) throws Exception {
    KeyPair keyPair = getKeyPair("self4");
    Cipher cipher = Cipher.getInstance(asymmPadding);

    OAEPParameterSpec ospec = new OAEPParameterSpec(SHA256, MGF1, MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT);
    cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic(), ospec);

    String secKeyEncoded = new String(secKey.getEncoded());
    byte[] encrypted = cipher.doFinal(secKeyEncoded.getBytes());

    char[] encoded = Hex.encodeHex(encrypted);
    return new String(encoded);
}

这里我们用对称密钥加密我们的字符串

private static String encryptDataWithSymmetricKey(String data, SecretKey secretKey, String symmPadding) throws Exception {
    Cipher cipher = Cipher.getInstance(symmPadding);
    IvParameterSpec iv = new IvParameterSpec(new byte[8]);
    cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);

    byte[] encrypted = cipher.doFinal(data.getBytes());
    char[] encoded = Hex.encodeHex(encrypted);
    return new String(encoded);
}

解密和解码对称密钥是我第一次在 mac 上看到可变长度密钥的时候。

public String decryptWithPrivateKey(String encryptedData, String pubKeyFp, String asymmPadding) throws Exception {
    loadKeystores();
    String alias = fingerPrintAliasMap.get(pubKeyFp);

    KeyPair keyPair = getKeyPair(alias);
    Cipher cipher = Cipher.getInstance(asymmPadding);

    OAEPParameterSpec oParamSpec = new OAEPParameterSpec(SHA256, MGF1, MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT);
    cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate(), oParamSpec);

    byte[] decoded = Hex.decodeHex(encryptedData.toCharArray());
    byte[] decrypted = cipher.doFinal(decoded);
    System.out.println("decoded and decrypted key length: " + decrypted.length); // 24 on windows, random on mac

    return new String(Hex.encodeHex(decrypted));
}

故障发生在这里 - 在 Windows 上,25% 的时间在 cipher.doFinal 期间失败,在 Mac 上,100% 的时间在 cipher.init 期间失败。

public String decryptWithSymmetricKey(String encryptedHexData, String symmKey, String symmPadding) throws Exception {

    byte[] key = Hex.decodeHex(symmKey.toCharArray());
    SecretKey skeySpec = new SecretKeySpec(key, DESEDE);
    IvParameterSpec iv = new IvParameterSpec(new byte[8]);

    Cipher cipher = Cipher.getInstance(symmPadding);
    cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv); // mac: Wrong key size

    byte[] decoded = Hex.decodeHex(encryptedHexData.toCharArray());
    byte[] deciphered = cipher.doFinal(decoded); // windows: Given final block not properly padded

    return new String(deciphered);
}

我假设如果我在 mac 上解决这个问题,它也应该在 windows 上解决它。

【问题讨论】:

    标签: java encryption cryptography java-8


    【解决方案1】:

    问题是encryptDataWithSymmetricKey方法中的以下两行:

    String secKeyEncoded = new String(secKey.getEncoded());
    byte[] encrypted = cipher.doFinal(secKeyEncoded.getBytes());
    

    由于您随机生成了 DES 密钥,它很可能包含不可打印的字符。通过调用new String(bytes),您会默默地丢弃那些破坏您的密钥的不可打印字符。改为使用:

    byte[] encrypted = cipher.doFinal(secKey.getEncoded());
    

    其他注意事项:

    另外,如果您生成 112 位,我不确定您的密钥大小是否正确。我认为 Java 期望密钥大小包括该安全级别的奇偶校验位,该大小为 128。您可能应该使用 192 位,这可以提供更好的安全级别(对于 DESede,只有 112 位,192 位密钥 = 168 位,不计算奇偶校验位)。

    您不应该使用 DESede。甚至 AES-128 也提供了更好的安全性。

    请不要使用零字节的静态 IV。使用像SecureRandom 这样的强随机源为每个加密生成一个IV。它不必是秘密的,因此您可以简单地将其添加到密文中,并且不要忘记在解密之前将其切掉。

    您没有对对称密文进行身份验证。您要么需要使用具有强 MAC(如 HMAC-SHA256)的 encrypt-then-MAC 方法,要么使用经过身份验证的操作模式(如 GCM 或 EAX)。

    【讨论】:

    • 不管字符是否可打印,关键的bytes是否按照平台默认的字符编码形成有效的序列,这两个都是用的方法。无效字节通常不会被丢弃,而是由特殊字符替换,例如'?',不过,对无效字节的处理是未指定的,无论如何细节都无关紧要。您的答案是正确的,因为这种转换是有损的。
    • 天哪,谢谢,花了 3 天时间试图解决这个问题,您的 cipher.doFinal() 解决方案为我解决了两端的问题。关于DESede,这是一个功能需求,所以我必须使用它。
    • 使用 SunJCE(我不能说 IBMJCE) DESede 的 KeyGenerator 调用 112 生成一个“双键”值,具有 112 位随机加(无用)奇偶校验存储在 24 个字节中为 K1,K2,K1。如果你调用 168,你会得到 168 位加上 24 字节的奇偶校验。这允许 DESede 的 Cipher 始终采用 24 字节表示,无论它是 K1、K2、K3 还是 K1、K2、K1 甚至 K1、K1、K1。当然,强度由于中间相遇而减少;见docs.oracle.com/javase/7/docs/technotes/guides/security/…。
    猜你喜欢
    • 1970-01-01
    • 2011-10-17
    • 2021-11-16
    • 1970-01-01
    • 2015-01-30
    • 1970-01-01
    • 1970-01-01
    • 2023-02-08
    相关资源
    最近更新 更多