Initial bytes incorrect after Java AES/CBC decryption

前端 未结 10 2041
鱼传尺愫
鱼传尺愫 2020-11-22 12:47

What\'s wrong with the following example?

The problem is that the first part of the decrypted string is nonsense. However, the rest is fine, I get...

相关标签:
10条回答
  • 2020-11-22 13:24

    Online Editor Runnable version:-

    import javax.crypto.Cipher;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    //import org.apache.commons.codec.binary.Base64;
    import java.util.Base64;
    
    public class Encryptor {
        public static String encrypt(String key, String initVector, String value) {
            try {
                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
                IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
    
                SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
                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);
                String s = new String(Base64.getEncoder().encode(encrypted));
                return s;
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            return null;
        }
    
        public static String decrypt(String key, String initVector, String encrypted) {
            try {
                IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
                SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
    
                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
                cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
    
                byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));
    
                return new String(original);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
    
            return null;
        }
    
        public static void main(String[] args) {
            String key = "Bar12345Bar12345"; // 128 bit key
            String initVector = "RandomInitVector"; // 16 bytes IV
    
            System.out.println(encrypt(key, initVector, "Hello World"));
            System.out.println(decrypt(key, initVector, encrypt(key, initVector, "Hello World")));
        }
    }
    
    0 讨论(0)
  • 2020-11-22 13:25

    In this answer I choose to approach the "Simple Java AES encrypt/decrypt example" main theme and not the specific debugging question because I think this will profit most readers.

    This is a simple summary of my blog post about AES encryption in Java so I recommend reading through it before implementing anything. I will however still provide a simple example to use and give some pointers what to watch out for.

    In this example I will choose to use authenticated encryption with Galois/Counter Mode or GCM mode. The reason is that in most case you want integrity and authenticity in combination with confidentiality (read more in the blog).

    AES-GCM Encryption/Decryption Tutorial

    Here are the steps required to encrypt/decrypt with AES-GCM with the Java Cryptography Architecture (JCA). Do not mix with other examples, as subtle differences may make your code utterly insecure.

    1. Create Key

    As it depends on your use-case, I will assume the simplest case: a random secret key.

    SecureRandom secureRandom = new SecureRandom();
    byte[] key = new byte[16];
    secureRandom.nextBytes(key);
    SecretKey secretKey = SecretKeySpec(key, "AES");
    

    Important:

    • always use a strong pseudorandom number generator like SecureRandom
    • use 16 byte / 128 bit long key (or more - but more is seldom needed)
    • if you want a key derived from a user password, look into a password hash function (or KDF) with stretching property like PBKDF2 or bcrypt
    • if you want a key derived from other sources, use a proper key derivation function (KDF) like HKDF (Java implementation here). Do not use simple cryptographic hashes for that (like SHA-256).

    2. Create the Initialization Vector

    An initialization vector (IV) is used so that the same secret key will create different cipher texts.

    byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
    secureRandom.nextBytes(iv);
    

    Important:

    • never reuse the same IV with the same key (very important in GCM/CTR mode)
    • the IV must be unique (ie. use random IV or a counter)
    • the IV is not required to be secret
    • always use a strong pseudorandom number generator like SecureRandom
    • 12 byte IV is the correct choice for AES-GCM mode

    3. Encrypt with IV and Key

    final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length
    cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
    byte[] cipherText = cipher.doFinal(plainText);
    

    Important:

    • use 16 byte / 128 bit authentication tag (used to verify integrity/authenticity)
    • the authentication tag will be automatically appended to the cipher text (in the JCA implementation)
    • since GCM behaves like a stream cipher, no padding is required
    • use CipherInputStream when encrypting large chunks of data
    • want additional (non-secret) data checked if it was changed? You may want to use associated data with cipher.updateAAD(associatedData); More here.

    3. Serialize to Single Message

    Just append IV and ciphertext. As stated above, the IV doesn't need to be secret.

    ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + cipherText.length);
    byteBuffer.put(iv);
    byteBuffer.put(cipherText);
    byte[] cipherMessage = byteBuffer.array();
    

    Optionally encode with Base64 if you need a string representation. Either use Android's or Java 8's built-in implementation (do not use Apache Commons Codec - it's an awful implementation). Encoding is used to "convert" byte arrays to string representation to make it ASCII safe e.g.:

    String base64CipherMessage = Base64.getEncoder().encodeToString(cipherMessage);
    

    4. Prepare Decryption: Deserialize

    If you have encoded the message, first decode it to byte array:

    byte[] cipherMessage = Base64.getDecoder().decode(base64CipherMessage)
    

    Important:

    • be careful to validate input parameters, so to avoid denial of service attacks by allocating too much memory.

    5. Decrypt

    Initialize the cipher and set the same parameters as with the encryption:

    final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    //use first 12 bytes for iv
    AlgorithmParameterSpec gcmIv = new GCMParameterSpec(128, cipherMessage, 0, 12);
    cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmIv);
    //use everything from 12 bytes on as ciphertext
    byte[] plainText = cipher.doFinal(cipherMessage, 12, cipherMessage.length - 12);
    

    Important:

    • don't forget to add associated data with cipher.updateAAD(associatedData); if you added it during encryption.

    A working code snippet can be found in this gist.


    Note that most recent Android (SDK 21+) and Java (7+) implementations should have AES-GCM. Older versions may lack it. I still choose this mode, since it is easier to implement in addition to being more efficient compared to similar mode of Encrypt-then-Mac (with e.g. AES-CBC + HMAC). See this article on how to implement AES-CBC with HMAC.

    0 讨论(0)
  • 2020-11-22 13:25

    It's often the good idea to rely on standard library provided solution:

    private static void stackOverflow15554296()
        throws
            NoSuchAlgorithmException, NoSuchPaddingException,        
            InvalidKeyException, IllegalBlockSizeException,
            BadPaddingException
    {
    
        // prepare key
        KeyGenerator keygen = KeyGenerator.getInstance("AES");
        SecretKey aesKey = keygen.generateKey();
        String aesKeyForFutureUse = Base64.getEncoder().encodeToString(
                aesKey.getEncoded()
        );
    
        // cipher engine
        Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    
        // cipher input
        aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
        byte[] clearTextBuff = "Text to encode".getBytes();
        byte[] cipherTextBuff = aesCipher.doFinal(clearTextBuff);
    
        // recreate key
        byte[] aesKeyBuff = Base64.getDecoder().decode(aesKeyForFutureUse);
        SecretKey aesDecryptKey = new SecretKeySpec(aesKeyBuff, "AES");
    
        // decipher input
        aesCipher.init(Cipher.DECRYPT_MODE, aesDecryptKey);
        byte[] decipheredBuff = aesCipher.doFinal(cipherTextBuff);
        System.out.println(new String(decipheredBuff));
    }
    

    This prints "Text to encode".

    Solution is based on Java Cryptography Architecture Reference Guide and https://stackoverflow.com/a/20591539/146745 answer.

    0 讨论(0)
  • 2020-11-22 13:28

    Lot of people including myself face lot of issues in making this work due to missing some information like, forgetting to convert to Base64, initialization vectors, character set, etc. So I thought of making a fully functional code.

    Hope this will be useful to you all: To compile you need additional Apache Commons Codec jar, which is available here: http://commons.apache.org/proper/commons-codec/download_codec.cgi

    import javax.crypto.Cipher;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    
    import org.apache.commons.codec.binary.Base64;
    
    public class Encryptor {
        public static String encrypt(String key, String initVector, String value) {
            try {
                IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
                SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "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 initVector, String encrypted) {
            try {
                IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
                SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "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;
        }
    
        public static void main(String[] args) {
            String key = "Bar12345Bar12345"; // 128 bit key
            String initVector = "RandomInitVector"; // 16 bytes IV
    
            System.out.println(decrypt(key, initVector,
                    encrypt(key, initVector, "Hello World")));
        }
    }
    
    0 讨论(0)
提交回复
热议问题