【问题标题】:How to fix Invalid AES key length?如何修复无效的 AES 密钥长度?
【发布时间】:2015-06-03 22:28:06
【问题描述】:

我正在从事一个文本加密和解密项目(遵循 Struts 2)

每当我输入密码和纯文本时,我都会收到 Invalid AES Key Length 错误。

服务类

package com.anoncrypt.services;

import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

public class SymAES
{
    private static final String ALGORITHM = "AES";
    private static byte[] keyValue= new byte[] { 'T', 'h', 'i', 's', 'I', 's', 'A', 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y' };

     public  String encode(String valueToEnc) throws Exception {
        Key key = new SecretKeySpec(keyValue, ALGORITHM);
        Cipher c = Cipher.getInstance(ALGORITHM);
        c.init(Cipher.ENCRYPT_MODE, key);
        byte[] encValue = c.doFinal(valueToEnc.getBytes());
        String encryptedValue = new BASE64Encoder().encode(encValue);
        return encryptedValue;
    }

    public  String decode(String encryptedValue) throws Exception {
        Key key = new SecretKeySpec(keyValue, ALGORITHM);
        Cipher c = Cipher.getInstance(ALGORITHM);
        c.init(Cipher.DECRYPT_MODE, key);
        byte[] decordedValue = new BASE64Decoder().decodeBuffer(encryptedValue);
        byte[] decValue = c.doFinal(decordedValue);
        String decryptedValue = new String(decValue);
        return decryptedValue;
    }

    public  void start(String passcode)throws Exception
    {
        keyValue = passcode.getBytes();
    }
}

这是错误

java.security.InvalidKeyException: Invalid AES key length: 6 bytes
    com.sun.crypto.provider.AESCrypt.init(AESCrypt.java:87)
    com.sun.crypto.provider.ElectronicCodeBook.init(ElectronicCodeBook.java:93)
    com.sun.crypto.provider.CipherCore.init(CipherCore.java:582)
    com.sun.crypto.provider.CipherCore.init(CipherCore.java:458)
    com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:307)
    javax.crypto.Cipher.implInit(Cipher.java:797)
    javax.crypto.Cipher.chooseProvider(Cipher.java:859)
    javax.crypto.Cipher.init(Cipher.java:1229)
    javax.crypto.Cipher.init(Cipher.java:1166)
    com.anoncrypt.services.SymAES.encode(SymAES.java:35)
    com.anoncrypt.actions.SymEncrypt.execute(SymEncrypt.java:24)

【问题讨论】:

  • 我猜你没有 Unlimited Strength Jurisdiction Policy(JRE 不是默认的)文件。stackoverflow.com/questions/2568841/…
  • 我有无限力量管辖权政策 jars@nambari
  • 16 字节在这里通俗地说是 16 个字符。
  • 不,一个字符可以超过一个字节,最好查找unicode。 ????‍????‍????‍????是 25 个字节:(F0 9F 91 A8 E2 80 8D F0 9F 91 A9 E2 80 8D F0 9F 91 A6 E2 80 8D F0 9F 91 A6)。一些更简单的东西,€ 是 3 个字节(E2 82 AC),它是欧元货币符号。提示:您可以删除您的答案。
  • @zaph 取决于您使用的编码。我认为在 UTF-8 中 € 是 3 个字节?看 unicode 对你没有帮助,你需要使用的编码。

标签: java encryption aes


【解决方案1】:

一般须知:

  1. 密钥 != 密码
    • SecretKeySpec 需要一个密钥,而不是密码。见下文
  2. 这可能是由于阻止使用 32 字节密钥的策略限制。查看其他答案

你的情况

问题是第 1 个问题:您传递的是密码而不是密钥。

AES 仅支持 16、24 或 32 字节的密钥大小。您要么需要准确地提供该金额,要么根据您输入的内容派生密钥。

有多种方法可以从密码短语中获取密钥。为此,Java 提供了 PBKDF2 实现。

我用erickson的answer画了一张全图(只加密,因为解密类似,但包括拆分密文):

SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);

KeySpec spec = new PBEKeySpec("password".toCharArray(), salt, 65536, 256); // AES-256
SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] key = f.generateSecret(spec).getEncoded();
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");

byte[] ivBytes = new byte[16];
random.nextBytes(ivBytes);
IvParameterSpec iv = new IvParameterSpec(ivBytes);

Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.ENCRYPT_MODE, keySpec, iv);
byte[] encValue = c.doFinal(valueToEnc.getBytes());

byte[] finalCiphertext = new byte[encValue.length+2*16];
System.arraycopy(ivBytes, 0, finalCiphertext, 0, 16);
System.arraycopy(salt, 0, finalCiphertext, 16, 16);
System.arraycopy(encValue, 0, finalCiphertext, 32, encValue.length);

return finalCiphertext;

其他注意事项:

  • 始终使用完全限定的密码名称。 AES 在这种情况下是不合适的,因为不同的 JVM/JCE 提供者可能使用不同的默认操作模式和填充。使用AES/CBC/PKCS5Padding。不要使用 ECB 模式,因为它在语义上不安全。
  • 如果您不使用 ECB 模式,则需要将 IV 与密文一起发送。这通常是通过在密文字节数组前加上 IV 来完成的。 IV 会自动为您创建,您可以通过 cipherInstance.getIV() 获取。
  • 无论何时发送某些东西,您都需要确保它在传输过程中没有被改动过。很难用 MAC 正确地实现加密。我建议您使用 CCM 或 GCM 等经过身份验证的模式。

【讨论】:

  • 另外要记住的是,对于超过 128 位(16 字节)的密钥大小,需要使用适当的权限设置 jre。有关更多信息,请参阅此问题和答案:stackoverflow.com/questions/6481627/…。在可以通过Cipher.getMaxAllowedKeyLength("AES") 访问的代码中,返回一个位而不是字节的值。
  • 我们如何使用GCMParameterSpec
  • @IgorGanapolsky 不知道,你自己看看:stackoverflow.com/…
  • @IgorGanapolsky 这看起来像是一个有效的例子:stackoverflow.com/a/44429596/1816580
  • @ed22 是的,你知道,但盐不需要保密(它可以是秘密的,但你需要以某种方式将盐传输到接收器)。
【解决方案2】:

我遇到了同样的问题,然后我将密钥设置为 16 字节,现在它可以正常工作了。精确地创建 16 字节的密钥。一定会成功的。

【讨论】:

    【解决方案3】:

    您可以验证密钥长度限制:

    int maxKeyLen = Cipher.getMaxAllowedKeyLength("AES");
    System.out.println("MaxAllowedKeyLength=[" + maxKeyLen + "].");
    

    【讨论】:

    • 然后呢?
    【解决方案4】:

    您可以使用此代码,此代码用于 AES-256-CBC,或者您可以将其用于其他 AES 加密。密钥长度错误主要出现在 256 位加密中。

    这个错误是由于我们在 SecretKeySpec 中传递的编码或字符集名称。假设,就我而言,我的密钥长度为 44,但我无法使用这个长密钥加密我的文本; Java 向我抛出了无效密钥长度的错误。因此,我在函数中将我的密钥作为 BASE64 传递,它将我的 44 长度密钥转换为 32 字节,这是 256 位加密所必需的。

    import javax.crypto.Cipher;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    import java.security.MessageDigest;
    import java.security.Security;
    import java.util.Base64;
    
    public class Encrypt {
    
        static byte [] arr = {1,2,3,4,5,6,7,8,9};
    
        // static byte [] arr = new byte[16];
    
          public static void main(String...args) {
            try {
             //   System.out.println(Cipher.getMaxAllowedKeyLength("AES"));
                Base64.Decoder decoder = Base64.getDecoder();
                // static byte [] arr = new byte[16];
                Security.setProperty("crypto.policy", "unlimited");
                String key = "Your key";
           //     System.out.println("-------" + key);
    
                String value = "Hey, i am adnan";
                String IV = "0123456789abcdef";
           //     System.out.println(value);
                // log.info(value);
              IvParameterSpec iv = new IvParameterSpec(IV.getBytes());
                //    IvParameterSpec iv = new IvParameterSpec(arr);
    
            //    System.out.println(key);
                SecretKeySpec skeySpec = new SecretKeySpec(decoder.decode(key), "AES");
             //   System.out.println(skeySpec);
                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            //    System.out.println("ddddddddd"+IV);
                cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
           //     System.out.println(cipher.getIV());
    
                byte[] encrypted = cipher.doFinal(value.getBytes());
                String encryptedString = Base64.getEncoder().encodeToString(encrypted);
    
                System.out.println("encrypted string,,,,,,,,,,,,,,,,,,,: " + encryptedString);
                // vars.put("input-1",encryptedString);
                //  log.info("beanshell");
            }catch (Exception e){
                System.out.println(e.getMessage());
            }
        }
    }
    

    【讨论】:

    • 请不要使用上面的代码,因为它使用了一个固定的初始化向量,密钥不应该从字符串派生,而是最好使用像 PBKDF2 这样的密钥派生。从字符串到字节数组的转换(反之亦然)没有编码,因此在具有不同标准编码的其他系统上解密很有可能会失败。
    • IV 向量用于我自己的目的,如果你有你自己的,你可以在那里使用,如果你不想使用任何初始化的 IV 向量,那么使用 " static byte [] arr = new字节[16];"这一行并通过这个 arr 向量代替 IV.getBytes();
    猜你喜欢
    • 2010-12-18
    • 2015-04-03
    • 1970-01-01
    • 1970-01-01
    • 2017-11-15
    • 1970-01-01
    • 1970-01-01
    • 2021-01-07
    • 1970-01-01
    相关资源
    最近更新 更多