Initial bytes incorrect after Java AES/CBC decryption

前端 未结 10 2036
鱼传尺愫
鱼传尺愫 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:03

    The IV that your using for decryption is incorrect. Replace this code

    //Decrypt cipher
    Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
    decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
    

    With this code

    //Decrypt cipher
    Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    IvParameterSpec ivParameterSpec = new IvParameterSpec(encryptCipher.getIV());
    decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
    

    And that should solve your problem.


    Below includes an example of a simple AES class in Java. I do not recommend using this class in production environments, as it may not account for all of the specific needs of your application.

    import java.nio.charset.StandardCharsets;
    import java.security.InvalidAlgorithmParameterException;
    import java.security.InvalidKeyException;
    import java.security.NoSuchAlgorithmException;
    import java.security.SecureRandom;
    import javax.crypto.BadPaddingException;
    import javax.crypto.Cipher;
    import javax.crypto.IllegalBlockSizeException;
    import javax.crypto.NoSuchPaddingException;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    import java.util.Base64;
    
    public class AES 
    {
        public static byte[] encrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
        {       
            return AES.transform(Cipher.ENCRYPT_MODE, keyBytes, ivBytes, messageBytes);
        }
    
        public static byte[] decrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
        {       
            return AES.transform(Cipher.DECRYPT_MODE, keyBytes, ivBytes, messageBytes);
        }
    
        private static byte[] transform(final int mode, final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
        {
            final SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            final IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
            byte[] transformedBytes = null;
    
            try
            {
                final Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
    
                cipher.init(mode, keySpec, ivSpec);
    
                transformedBytes = cipher.doFinal(messageBytes);
            }        
            catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) 
            {
                e.printStackTrace();
            }
            return transformedBytes;
        }
    
        public static void main(final String[] args) throws InvalidKeyException, InvalidAlgorithmParameterException
        {
            //Retrieved from a protected local file.
            //Do not hard-code and do not version control.
            final String base64Key = "ABEiM0RVZneImaq7zN3u/w==";
    
            //Retrieved from a protected database.
            //Do not hard-code and do not version control.
            final String shadowEntry = "AAECAwQFBgcICQoLDA0ODw==:ZtrkahwcMzTu7e/WuJ3AZmF09DE=";
    
            //Extract the iv and the ciphertext from the shadow entry.
            final String[] shadowData = shadowEntry.split(":");        
            final String base64Iv = shadowData[0];
            final String base64Ciphertext = shadowData[1];
    
            //Convert to raw bytes.
            final byte[] keyBytes = Base64.getDecoder().decode(base64Key);
            final byte[] ivBytes = Base64.getDecoder().decode(base64Iv);
            final byte[] encryptedBytes = Base64.getDecoder().decode(base64Ciphertext);
    
            //Decrypt data and do something with it.
            final byte[] decryptedBytes = AES.decrypt(keyBytes, ivBytes, encryptedBytes);
    
            //Use non-blocking SecureRandom implementation for the new IV.
            final SecureRandom secureRandom = new SecureRandom();
    
            //Generate a new IV.
            secureRandom.nextBytes(ivBytes);
    
            //At this point instead of printing to the screen, 
            //one should replace the old shadow entry with the new one.
            System.out.println("Old Shadow Entry      = " + shadowEntry);
            System.out.println("Decrytped Shadow Data = " + new String(decryptedBytes, StandardCharsets.UTF_8));
            System.out.println("New Shadow Entry      = " + Base64.getEncoder().encodeToString(ivBytes) + ":" + Base64.getEncoder().encodeToString(AES.encrypt(keyBytes, ivBytes, decryptedBytes)));
        }
    }
    

    Note that AES has nothing to do with encoding, which is why I chose to handle it separately and without the need of any third party libraries.

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

    Another solution using java.util.Base64 with Spring Boot

    Encryptor Class

    package com.jmendoza.springboot.crypto.cipher;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    import javax.crypto.Cipher;
    import javax.crypto.spec.SecretKeySpec;
    import java.nio.charset.StandardCharsets;
    import java.util.Base64;
    
    @Component
    public class Encryptor {
    
        @Value("${security.encryptor.key}")
        private byte[] key;
        @Value("${security.encryptor.algorithm}")
        private String algorithm;
    
        public String encrypt(String plainText) throws Exception {
            SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
            Cipher cipher = Cipher.getInstance(algorithm);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
            return new String(Base64.getEncoder().encode(cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8))));
        }
    
        public String decrypt(String cipherText) throws Exception {
            SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
            Cipher cipher = Cipher.getInstance(algorithm);
            cipher.init(Cipher.DECRYPT_MODE, secretKey);
            return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)));
        }
    }
    

    EncryptorController Class

    package com.jmendoza.springboot.crypto.controller;
    
    import com.jmendoza.springboot.crypto.cipher.Encryptor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/cipher")
    public class EncryptorController {
    
        @Autowired
        Encryptor encryptor;
    
        @GetMapping(value = "encrypt/{value}")
        public String encrypt(@PathVariable("value") final String value) throws Exception {
            return encryptor.encrypt(value);
        }
    
        @GetMapping(value = "decrypt/{value}")
        public String decrypt(@PathVariable("value") final String value) throws Exception {
            return encryptor.decrypt(value);
        }
    }
    

    application.properties

    server.port=8082
    security.encryptor.algorithm=AES
    security.encryptor.key=M8jFt46dfJMaiJA0
    

    Example

    http://localhost:8082/cipher/encrypt/jmendoza

    2h41HH8Shzc4BRU3hVDOXA==

    http://localhost:8082/cipher/decrypt/2h41HH8Shzc4BRU3hVDOXA==

    jmendoza

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

    This is an improvement over the accepted answer.

    Changes:

    (1) Using random IV and prepend it to the encrypted text

    (2) Using SHA-256 to generate a key from a passphrase

    (3) No dependency on Apache Commons

    public static void main(String[] args) throws GeneralSecurityException {
        String plaintext = "Hello world";
        String passphrase = "My passphrase";
        String encrypted = encrypt(passphrase, plaintext);
        String decrypted = decrypt(passphrase, encrypted);
        System.out.println(encrypted);
        System.out.println(decrypted);
    }
    
    private static SecretKeySpec getKeySpec(String passphrase) throws NoSuchAlgorithmException {
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        return new SecretKeySpec(digest.digest(passphrase.getBytes(UTF_8)), "AES");
    }
    
    private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
        return Cipher.getInstance("AES/CBC/PKCS5PADDING");
    }
    
    public static String encrypt(String passphrase, String value) throws GeneralSecurityException {
        byte[] initVector = new byte[16];
        SecureRandom.getInstanceStrong().nextBytes(initVector);
        Cipher cipher = getCipher();
        cipher.init(Cipher.ENCRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
        byte[] encrypted = cipher.doFinal(value.getBytes());
        return DatatypeConverter.printBase64Binary(initVector) +
                DatatypeConverter.printBase64Binary(encrypted);
    }
    
    public static String decrypt(String passphrase, String encrypted) throws GeneralSecurityException {
        byte[] initVector = DatatypeConverter.parseBase64Binary(encrypted.substring(0, 24));
        Cipher cipher = getCipher();
        cipher.init(Cipher.DECRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
        byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted.substring(24)));
        return new String(original);
    }
    
    0 讨论(0)
  • 2020-11-22 13:19

    Optimized version of the accepted answer.

    • no 3rd party libs

    • includes IV into the encrypted message (can be public)

    • password can be of any length

    Code:

    import java.io.UnsupportedEncodingException;
    import java.security.SecureRandom;
    import java.util.Base64;
    
    import javax.crypto.Cipher;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    
    public class Encryptor {
        public static byte[] getRandomInitialVector() {
            try {
                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
                SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
                byte[] initVector = new byte[cipher.getBlockSize()];
                randomSecureRandom.nextBytes(initVector);
                return initVector;
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            return null;
        }
    
        public static byte[] passwordTo16BitKey(String password) {
            try {
                byte[] srcBytes = password.getBytes("UTF-8");
                byte[] dstBytes = new byte[16];
    
                if (srcBytes.length == 16) {
                    return srcBytes;
                }
    
                if (srcBytes.length < 16) {
                    for (int i = 0; i < dstBytes.length; i++) {
                        dstBytes[i] = (byte) ((srcBytes[i % srcBytes.length]) * (srcBytes[(i + 1) % srcBytes.length]));
                    }
                } else if (srcBytes.length > 16) {
                    for (int i = 0; i < srcBytes.length; i++) {
                        dstBytes[i % dstBytes.length] += srcBytes[i];
                    }
                }
    
                return dstBytes;
            } catch (UnsupportedEncodingException ex) {
                ex.printStackTrace();
            }
    
            return null;
        }
    
        public static String encrypt(String key, String value) {
            return encrypt(passwordTo16BitKey(key), value);
        }
    
        public static String encrypt(byte[] key, String value) {
            try {
                byte[] initVector = Encryptor.getRandomInitialVector();
                IvParameterSpec iv = new IvParameterSpec(initVector);
                SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
    
                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
                cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
    
                byte[] encrypted = cipher.doFinal(value.getBytes());
                return Base64.getEncoder().encodeToString(encrypted) + " " + Base64.getEncoder().encodeToString(initVector);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
    
            return null;
        }
    
        public static String decrypt(String key, String encrypted) {
            return decrypt(passwordTo16BitKey(key), encrypted);
        }
    
        public static String decrypt(byte[] key, String encrypted) {
            try {
                String[] encryptedParts = encrypted.split(" ");
                byte[] initVector = Base64.getDecoder().decode(encryptedParts[1]);
                if (initVector.length != 16) {
                    return null;
                }
    
                IvParameterSpec iv = new IvParameterSpec(initVector);
                SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
    
                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
                cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
    
                byte[] original = cipher.doFinal(Base64.getDecoder().decode(encryptedParts[0]));
    
                return new String(original);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
    
            return null;
        }
    }
    

    Usage:

    String key = "Password of any length.";
    String encrypted = Encryptor.encrypt(key, "Hello World");
    String decrypted = Encryptor.decrypt(key, encrypted);
    System.out.println(encrypted);
    System.out.println(decrypted);
    

    Example output:

    QngBg+Qc5+F8HQsksgfyXg== yDfYiIHTqOOjc0HRNdr1Ng==
    Hello World
    
    0 讨论(0)
  • 2020-11-22 13:21

    Here a solution without Apache Commons Codec's Base64:

    import javax.crypto.Cipher;
    import javax.crypto.spec.SecretKeySpec;
    
    public class AdvancedEncryptionStandard
    {
        private byte[] key;
    
        private static final String ALGORITHM = "AES";
    
        public AdvancedEncryptionStandard(byte[] key)
        {
            this.key = key;
        }
    
        /**
         * Encrypts the given plain text
         *
         * @param plainText The plain text to encrypt
         */
        public byte[] encrypt(byte[] plainText) throws Exception
        {
            SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
    
            return cipher.doFinal(plainText);
        }
    
        /**
         * Decrypts the given byte array
         *
         * @param cipherText The data to decrypt
         */
        public byte[] decrypt(byte[] cipherText) throws Exception
        {
            SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, secretKey);
    
            return cipher.doFinal(cipherText);
        }
    }
    

    Usage example:

    byte[] encryptionKey = "MZygpewJsCpRrfOr".getBytes(StandardCharsets.UTF_8);
    byte[] plainText = "Hello world!".getBytes(StandardCharsets.UTF_8);
    AdvancedEncryptionStandard advancedEncryptionStandard = new AdvancedEncryptionStandard(
            encryptionKey);
    byte[] cipherText = advancedEncryptionStandard.encrypt(plainText);
    byte[] decryptedCipherText = advancedEncryptionStandard.decrypt(cipherText);
    
    System.out.println(new String(plainText));
    System.out.println(new String(cipherText));
    System.out.println(new String(decryptedCipherText));
    

    Prints:

    Hello world!
    դ;��LA+�ߙb*
    Hello world!
    
    0 讨论(0)
  • 2020-11-22 13:21

    Looks to me like you are not dealing properly with your Initialization Vector (IV). It's been a long time since I last read about AES, IVs and block chaining, but your line

    IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
    

    does not seem to be OK. In the case of AES, you can think of the initialization vector as the "initial state" of a cipher instance, and this state is a bit of information that you can not get from your key but from the actual computation of the encrypting cipher. (One could argue that if the IV could be extracted from the key, then it would be of no use, as the key is already given to the cipher instance during its init phase).

    Therefore, you should get the IV as a byte[] from the cipher instance at the end of your encryption

      cipherOutputStream.close();
      byte[] iv = encryptCipher.getIV();
    

    and you should initialize your Cipher in DECRYPT_MODE with this byte[] :

      IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
    

    Then, your decryption should be OK. Hope this helps.

    0 讨论(0)
提交回复
热议问题