【问题标题】:Java AES encryption and decryptionJava AES 加解密
【发布时间】:2012-06-01 07:55:07
【问题描述】:

我想使用 16 字节密钥的 128 位 AES 加密来加密和解密密码。我在解密值时收到javax.crypto.BadPaddingException 错误。解密时我是否遗漏了什么?

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

public 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");
        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 (NoSuchAlgorithmException ex) {
        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
    } catch (IllegalBlockSizeException ex) {
        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
    } catch (BadPaddingException ex) {
        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
    } catch (InvalidKeyException ex) {
        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
    } catch (NoSuchPaddingException ex) {
        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
    }
    return null;
}

public String decrypt(String key, String encrypted) {
    try {
        SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(), "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(skeySpec.getEncoded(), "AES"));
        //getting error here
        byte[] original = cipher.doFinal(encrypted.getBytes());
        return new String(original);
    } catch (IllegalBlockSizeException ex) {
        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
    } catch (BadPaddingException ex) {
        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
    } catch (InvalidKeyException ex) {
        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
    } catch (NoSuchAlgorithmException ex) {
        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
    } catch (NoSuchPaddingException ex) {
        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
    }
    return null;
}  

错误信息

encrypted string:�Bj�.�Ntk�F�`�
encrypted key:ThisIsASecretKey
decrypted value:null
May 25, 2012 12:54:02 PM bean.Test decrypt
SEVERE: null
javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
at com.sun.crypto.provider.AESCipher.engineDoFinal(DashoA13*..)
at javax.crypto.Cipher.doFinal(DashoA13*..)
at bean.Test.decrypt(Test.java:55)
at bean.Test.main(Test.java:24)

最后我正在使用基于@QuantumMechanic 答案的以下解决方案

public class Test {

public String encryptionKey;

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

public String encrypt(String value) {
    try {
        // Get the KeyGenerator
        KeyGenerator kgen = KeyGenerator.getInstance("AES");
        kgen.init(256);
        // Generate the secret key specs.
        SecretKey skey = kgen.generateKey();
        byte[] raw = skey.getEncoded();
        String key = new Base64().encodeAsString(raw);
        this.encryptionKey = key;
        System.out.println("------------------Key------------------");
        System.out.println(key);
        System.out.println("--------------End of Key---------------");
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
        String encrypt = (new Base64()).encodeAsString(cipher.doFinal(value.getBytes()));
        System.out.println("encrypted string:" + encrypt);
        return encrypt;
    } catch (NoSuchAlgorithmException ex) {
        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
    } catch (IllegalBlockSizeException ex) {
        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
    } catch (BadPaddingException ex) {
        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
    } catch (InvalidKeyException ex) {
        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
    } catch (NoSuchPaddingException ex) {
        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
    }
    return null;
}

public String decrypt(String key, String encrypted) {
    try {
        Key k = new SecretKeySpec(Base64.getDecoder().decode(key), "AES");
        Cipher c = Cipher.getInstance("AES");
        c.init(Cipher.DECRYPT_MODE, k);
        byte[] decodedValue = Base64.getDecoder().decode(encrypted);
        byte[] decValue = c.doFinal(decodedValue);
        String decryptedValue = new String(decValue);
        return decryptedValue;
    } catch (IllegalBlockSizeException ex) {
        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
    } catch (BadPaddingException ex) {
        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
    } catch (InvalidKeyException ex) {
        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
    } catch (NoSuchAlgorithmException ex) {
        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
    } catch (NoSuchPaddingException ex) {
        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
    }
    return null;
}

}

【问题讨论】:

    标签: java aes encryption


    【解决方案1】:

    如果对于分组密码,您不打算使用包含填充方案的Cipher 转换,则需要将明文中的字节数设为密码块大小的整数倍。

    因此,要么将明文填充为 16 字节的倍数(这是 AES 块大小),要么在创建 Cipher 对象时指定填充方案。例如,您可以使用:

    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    

    除非您有充分的理由不这样做,否则请使用 JCE 实现中已经包含的填充方案。他们已经想到了一些你必须自己去理解和处理的微妙之处和极端情况。


    好的,您的第二个问题是您使用String 来保存密文。

    一般来说,

    String s = new String(someBytes);
    byte[] retrievedBytes = s.getBytes();
    

    使someBytesretrievedBytes 相同。

    如果您希望/必须将密文保存在 String 中,请先对密文字节进行 base64 编码,然后从 base64 编码的字节构造 String。然后,当您解密时,您将getBytes()String 中获取base64 编码的字节,然后对它们进行base64 解码以获得真正的密文,然后解密。

    这个问题的原因是大多数(全部?)字符编码不能将任意字节映射到有效字符。因此,当您从密文创建 String 时,String 构造函数(它应用字符编码将字节转换为字符)本质上必须丢弃一些字节,因为它无法理解它们。因此,当您从字符串中获取字节时,它们与您放入字符串中的字节不同。

    在 Java 中(以及一般的现代编程中),你不能假设一个字符 = 一个字节,除非你完全知道你在处理 ASCII。这就是为什么如果你想从任意字节构建字符串,你需要使用 base64(或类似的东西)。

    【讨论】:

    • 块密码加密算法要求数据(在您的情况下为 mypassword)是 n 字节的倍数,其中 n 取决于密钥大小。查看this wiki 部分了解更多信息。
    • 我已将明文数据从mypassword 更改为1234567890123456,但我仍然遇到同样的异常。我的钥匙也是16B
    • 完美,BASE64EncoderBASE64Decoder 完成了这项工作。谢谢
    • @Praneeth 你是怎么解决的?你在哪里使用 BASE64 编码器或解码器?
    【解决方案2】:
    import javax.crypto.*;    
    import java.security.*;  
    public class Java {
    
    private static SecretKey key = null;         
       private static Cipher cipher = null; 
    
       public static void main(String[] args) throws Exception
       {
    
          Security.addProvider(new com.sun.crypto.provider.SunJCE());
    
          KeyGenerator keyGenerator =
             KeyGenerator.getInstance("DESede");
          keyGenerator.init(168);
          SecretKey secretKey = keyGenerator.generateKey();
          cipher = Cipher.getInstance("DESede");
    
          String clearText = "I am an Employee";
          byte[] clearTextBytes = clearText.getBytes("UTF8");
    
          cipher.init(Cipher.ENCRYPT_MODE, secretKey);
          byte[] cipherBytes = cipher.doFinal(clearTextBytes);
          String cipherText = new String(cipherBytes, "UTF8");
    
          cipher.init(Cipher.DECRYPT_MODE, secretKey);
          byte[] decryptedBytes = cipher.doFinal(cipherBytes);
          String decryptedText = new String(decryptedBytes, "UTF8");
    
          System.out.println("Before encryption: " + clearText);
          System.out.println("After encryption: " + cipherText);
          System.out.println("After decryption: " + decryptedText);
       }
    }
    
    
    // Output
    
    /*
    Before encryption: I am an Employee  
    After encryption: }?ス?スj6?スm?スZyc?ス?ス*?ス?スl#l?スdV  
    After decryption: I am an Employee  
    */
    

    【讨论】:

      【解决方案3】:

      这是上面提到的实现:

      import javax.crypto.Cipher;
      import javax.crypto.SecretKey;
      import javax.crypto.SecretKeyFactory;
      import javax.crypto.spec.PBEKeySpec;
      import javax.crypto.spec.SecretKeySpec;
      
      import org.apache.commons.codec.binary.Base64;
      import org.apache.commons.codec.binary.StringUtils;
      
      try
      {
          String passEncrypt = "my password";
          byte[] saltEncrypt = "choose a better salt".getBytes();
          int iterationsEncrypt = 10000;
          SecretKeyFactory factoryKeyEncrypt = SecretKeyFactory
                  .getInstance("PBKDF2WithHmacSHA1");
          SecretKey tmp = factoryKeyEncrypt.generateSecret(new PBEKeySpec(
                  passEncrypt.toCharArray(), saltEncrypt, iterationsEncrypt,
                  128));
          SecretKeySpec encryptKey = new SecretKeySpec(tmp.getEncoded(),
                  "AES");
      
          Cipher aesCipherEncrypt = Cipher
                  .getInstance("AES/ECB/PKCS5Padding");
          aesCipherEncrypt.init(Cipher.ENCRYPT_MODE, encryptKey);
      
          // get the bytes
          byte[] bytes = StringUtils.getBytesUtf8(toEncodeEncryptString);
      
          // encrypt the bytes
          byte[] encryptBytes = aesCipherEncrypt.doFinal(bytes);
      
          // encode 64 the encrypted bytes
          String encoded = Base64.encodeBase64URLSafeString(encryptBytes);
      
          System.out.println("e: " + encoded);
      
          // assume some transport happens here
      
          // create a new string, to make sure we are not pointing to the same
          // string as the one above
          String encodedEncrypted = new String(encoded);
      
          //we recreate the same salt/encrypt as if its a separate system
          String passDecrypt = "my password";
          byte[] saltDecrypt = "choose a better salt".getBytes();
          int iterationsDecrypt = 10000;
          SecretKeyFactory factoryKeyDecrypt = SecretKeyFactory
                  .getInstance("PBKDF2WithHmacSHA1");
          SecretKey tmp2 = factoryKeyDecrypt.generateSecret(new PBEKeySpec(passDecrypt
                  .toCharArray(), saltDecrypt, iterationsDecrypt, 128));
          SecretKeySpec decryptKey = new SecretKeySpec(tmp2.getEncoded(), "AES");
      
          Cipher aesCipherDecrypt = Cipher.getInstance("AES/ECB/PKCS5Padding");
                  aesCipherDecrypt.init(Cipher.DECRYPT_MODE, decryptKey);
      
          //basically we reverse the process we did earlier
      
          // get the bytes from encodedEncrypted string
          byte[] e64bytes = StringUtils.getBytesUtf8(encodedEncrypted);
      
          // decode 64, now the bytes should be encrypted
          byte[] eBytes = Base64.decodeBase64(e64bytes);
      
          // decrypt the bytes
          byte[] cipherDecode = aesCipherDecrypt.doFinal(eBytes);
      
          // to string
          String decoded = StringUtils.newStringUtf8(cipherDecode);
      
          System.out.println("d: " + decoded);
      
      }
      catch (Exception e)
      {
          e.printStackTrace();
      }
      

      【讨论】:

        【解决方案4】:

        试试这个,一个更简单的解决方案。

        byte[] salt = "ThisIsASecretKey".getBytes(); Key key = new SecretKeySpec(salt, 0, 16, "AES"); 密码密码 = Cipher.getInstance("AES");

        【讨论】:

        • 哦,请确保您的密钥长度为 16 个字符。
        • 密钥为 16B ThisIsASecretKey
        【解决方案5】:

        您声明要加密/解密密码。我不确定您的具体用例是什么,但一般来说,密码不会以可以解密的形式存储。一般做法是对密码加盐并使用适当强大的单向哈希(例如 PBKDF2)。

        查看以下链接了解更多信息。

        http://crackstation.net/hashing-security.htm

        【讨论】:

          【解决方案6】:

          加密/解密一个大型视频而不抛出 JavaOutOfMemoryException 并使用 Java SecureRandom 生成初始化向量的完整示例。还描述了将密钥字节存储到数据库,然后从这些字节重建相同的密钥。

          https://stackoverflow.com/a/18892960/185022

          【讨论】:

            猜你喜欢
            • 2013-04-19
            • 2019-10-27
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-11-03
            相关资源
            最近更新 更多