什么导致了错误"java.security.InvalidKeyException: Parameters missing"?

33
我正在尝试使用AES加密和解密字符串,但是出现了一个错误,我不知道如何解决。这是代码:

public class EncryptionTest{

public static void main(String[] args) {        
    String encrypt = new String(encrypt("1234567890123456"));
    System.out.println("decrypted value:" + (decrypt("ThisIsASecretKey",encrypt)));
}

public static String encrypt(String value) {
    try {
        byte[] raw = new byte[]{'T', 'h', 'i', 's', 'I', 's', 'A', 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y'};
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
        byte[] encrypted = cipher.doFinal(value.getBytes());
        System.out.println("encrypted string:" + (new String(encrypted)));
        return new String(skeySpec.getEncoded());
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    return null;
}

public static String decrypt(String key, String encrypted) {
    try {
        SecretKeySpec skeySpec = new SecretKeySpec(Base64.decodeBase64(key), "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(skeySpec.getEncoded(),"AES"));
            (*)
        byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));
        original.toString();
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    return null;
}  
}

当我运行它时,“decription”值为null。在(***)之前就失败了!
它给了我一个异常:
java.security.InvalidKeyException: 参数丢失 在com.sun.crypto.provider.CipherCore.init(CipherCore.java:388) 在com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:186) 在javax.crypto.Cipher.implInit(Cipher.java:787) 在javax.crypto.Cipher.chooseProvider(Cipher.java:849) 在javax.crypto.Cipher.init(Cipher.java:1213) 在javax.crypto.Cipher.init(Cipher.java:1153) 在firma.XmlEncryptionTest.decrypt(EncryptionTest.java:63) 在firma.XmlEncryptionTest.main(EncryptionTest.java:41)
其中第63行是(***)之前的那一行。我不知道我做错了什么,也不知道如何解决。我在网上搜索了一下,但没有找到缺少的参数是什么。

1
你在密码中使用了两个不同的init()...尝试使用相同的。 - Shark
是的,这部分 new SecretKeySpec(skeySpec.getEncoded(),"AES") 看起来很奇怪 - 为什么不直接使用 skeySpec - Duncan Jones
@DuncanJones,如果我使用"skeySpec"代替"new SecretKeySpec(skeySpec.getEncoded(),"AES")",我会得到一个新的错误:无效的AES密钥长度:12个字节,在同一行... - Igr
@Shark 我在两个密码中都使用了相同的init()方法 "cipher.init(Cipher.DECRYPT_MODE, skeySpec);",结果出现了异常:无效的AES密钥长度:12字节。 - Igr
不一样的...看看这里 - Shark
3个回答

58

你代码中的主要问题是没有指定IV值。在进行CBC模式加密时,必须指定一个IV值,并在执行CBC模式解密时使用相同的值。

另一个问题是从字节数组创建字符串和Base64编码的混合使用。你每次都从解密方法中返回null。即使你想返回original.toString(),也是错误的(因为toString()在字节数组上不起作用)。

下面是改进后的代码版本。虽然离最优还有一段距离,但它可以编译并运行。你需要改进它以使用随机IV。此外,如果你计划从密码派生密钥,请不要仅获取字节,而是使用诸如PBKDF2之类的派生函数。在JNCryptor源代码中可以看到使用PBKDF2的示例。

public class EncryptionTest {

  public static void main(String[] args) {
    try {

      String key = "ThisIsASecretKey";
      byte[] ciphertext = encrypt(key, "1234567890123456");
      System.out.println("decrypted value:" + (decrypt(key, ciphertext)));

    } catch (GeneralSecurityException e) {
      e.printStackTrace();
    }
  }

  public static byte[] encrypt(String key, String value)
      throws GeneralSecurityException {

    byte[] raw = key.getBytes(Charset.forName("UTF-8"));
    if (raw.length != 16) {
      throw new IllegalArgumentException("Invalid key size.");
    }

    SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, skeySpec,
        new IvParameterSpec(new byte[16]));
    return cipher.doFinal(value.getBytes(Charset.forName("UTF-8")));
  }

  public static String decrypt(String key, byte[] encrypted)
      throws GeneralSecurityException {

    byte[] raw = key.getBytes(Charset.forName("UTF-8"));
    if (raw.length != 16) {
      throw new IllegalArgumentException("Invalid key size.");
    }
    SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");

    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.DECRYPT_MODE, skeySpec,
        new IvParameterSpec(new byte[16]));
    byte[] original = cipher.doFinal(encrypted);

    return new String(original, Charset.forName("UTF-8"));
  }
}

6
是否创建随机或零IV(如果有的话)取决于特定的JCE提供程序。明智的做法是不使用提供程序默认值,而是始终为那些需要IV的模式提供IV。出于同样的原因,您应始终明确请求正确的块密码模式和填充模式。 - Maarten Bodewes
1
你写道:“在进行CBC模式加密时,必须指定IV值,并在执行CBC模式解密时使用相同的值。”然后是:“您需要改进此代码以使用随机IV。”问题:如果我需要在解密时使用相同的IV,如何使用“随机”的IV呢? - simpleuser
@user9999999 IV不是敏感值,因此通常与密文一起存储/发送。 - Duncan Jones
你好 @simpleuser,由于 IV 需要匹配,因此一种方法是每个方向都有可能的 IV 值列表,然后双方必须就决定使用哪个 IV 达成一致。这个索引必须在通信的初始握手期间进行通信。 - daparic

12
如果您使用类似CBC的块链接模式,则还需要向Cipher提供IvParameterSpec。
public class EncryptionTest {

static byte[] raw = new byte[]{'T', 'h', 'i', 's', 'I', 's', 'A', 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y'};

static SecureRandom rnd = new SecureRandom();

static IvParameterSpec iv = new IvParameterSpec(rnd.generateSeed(16));

public static void main(String[] args) {
    String encrypt = encrypt("1234567890123456");
    System.out.println("decrypted value:" + (decrypt("ThisIsASecretKey", encrypt)));
}

public static String encrypt(String value) {
    try {

        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec,iv);
        byte[] encrypted = cipher.doFinal(value.getBytes());
        System.out.println("encrypted string:" + Base64.encodeBase64String(encrypted));
        return Base64.encodeBase64String(encrypted);
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    return null;
}

public static String decrypt(String key, String encrypted) {
    try {
        SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(), "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
        cipher.init(Cipher.DECRYPT_MODE, skeySpec,iv);
        byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));

        return new String(original);
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    return null;
}

}


10

虽然已经有点晚了,但我仍然为其他人提供我的解决方案。请参考以下可用于加密和解密数据的代码。

public static byte[] encrypt(String value) {
        byte[] encrypted = null;
        try {

            byte[] raw = new byte[]{'T', 'h', 'i', 's', 'I', 's', 'A', 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y'};
            Key skeySpec = new SecretKeySpec(raw, "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            byte[] iv = new byte[cipher.getBlockSize()];

            IvParameterSpec ivParams = new IvParameterSpec(iv);
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec,ivParams);
            encrypted  = cipher.doFinal(value.getBytes());
            System.out.println("encrypted string:" + encrypted.length);

        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return encrypted;
    }

    public static  byte[]  decrypt(byte[] encrypted) {
         byte[] original = null;
         Cipher cipher = null;
        try {
            byte[] raw = new byte[]{'T', 'h', 'i', 's', 'I', 's', 'A', 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y'};
            Key key = new SecretKeySpec(raw, "AES");
            cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            //the block size (in bytes), or 0 if the underlying algorithm is not a block cipher
            byte[] ivByte = new byte[cipher.getBlockSize()];
            //This class specifies an initialization vector (IV). Examples which use
            //IVs are ciphers in feedback mode, e.g., DES in CBC mode and RSA ciphers with OAEP encoding operation.
            IvParameterSpec ivParamsSpec = new IvParameterSpec(ivByte);
            cipher.init(Cipher.DECRYPT_MODE, key, ivParamsSpec);
            original= cipher.doFinal(encrypted);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return original;
    }  

谢谢你的解决方案!它有效并帮助了我 :D - Kala J

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接