2

Problem: code below seems to correctly AES 256-encrypt, but when decrypting the first 16 bytes are non-readable "bytes", or not decrypted, or other. The text after the first 16 bytes is correctly decrypted.


I've seen lots of postings on this problem where when you decrypt AES 256, the first 16 bytes are still, well, bytes not text. I've tried things suggested here like combining the IV bytes to the encrypted bytes before Base64Encodeing, but that didn't work. I also made sure that my two web methods (encrypt and decrypt) use the same IV bytes.

My standalone logic (a Java Application) works as expected. After many head-banging hours and searching and trying things I though I would ask some of the experts here.

I would appreciate any suggestions as to what could be wrong with the code below.

Two web services GET methods shown below, one for encrypt and the other for decrypt.

Thanks in advance,

get("/AESEncrypt/:stringToEncrypt/:Base64SecretKey", (request, response) ->
        {
            String myKey = request.params("secretKey");
            byte[]  ivBytes = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

            // assume encoded since string could be any characters
            String str2Encrypt = URLDecoder.decode(request.params("stringToEncrypt"), "UTF-8");

            //Salt please.
            String salt = "somesalt";
            final byte[] saltBytes = salt.getBytes("UTF-8");

            // Use SHA 256.
            final SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            final PBEKeySpec spec = new PBEKeySpec(
                    myKey.toCharArray(),
                    saltBytes,
                    65536,
                    256);

            final SecretKey secretKey = factory.generateSecret(spec);
            final SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");

            //encrypt the message
            final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secret);
            final AlgorithmParameters params = cipher.getParameters();

            final byte[] encryptedTextBytes = cipher.doFinal(str2Encrypt.getBytes("UTF-8"));
            return Base64.encodeBase64String(encryptedTextBytes);
        });

        //-----------

        get("/AESDecrypt/:stringToDecrypt/:decryptKey", (request, response) ->
        {
            String base64Key = request.params("decryptKey");
            String strToDecrypt =request.params("stringToDecrypt");
            String decryptedString = null;

            //Salt please.
            String salt = "somesalt";
            final byte[] saltBytes = salt.getBytes("UTF-8");

            // Derive the key
            final SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            final PBEKeySpec spec = new PBEKeySpec(
                    base64Key.toCharArray(),
                    saltBytes,
                    65536,
                    256
            );
            final SecretKey secretKey = factory.generateSecret(spec);
            final SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");

            // Decrypt the message
            final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

            final AlgorithmParameters params = cipher.getParameters();
           // byte[] ivBytes = params.getParameterSpec(IvParameterSpec.class).getIV();
            byte[] ivBytes = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

            cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(ivBytes));
            byte[] decryptedTextBytes = null;
            try
            {
                decryptedTextBytes = cipher.doFinal(Base64.decodeBase64(strToDecrypt));
            }
            catch (IllegalBlockSizeException e)
            {
                e.printStackTrace();
            }
            catch (BadPaddingException e)
            {
                e.printStackTrace();
            }

            return new String(decryptedTextBytes);
        });
Morkus
  • 517
  • 7
  • 21
  • Some feedback: CBC with padding is vulnerable to padding oracle attacks. Moreover even if it provides confidentiality, it doesn't provide integrity. You should ideally use GCM. IV is not secret, but it has to be random or unpredictable. You must not repeat an same IV once it has been used for an encryption. Once encryption is complete, you must clear the `SecretKey`, `SecretKeySpec`. Your encrypted text doesn't contain IV. IV should go along with the cipher text. At the rcvng end it will be extracted from cipher text.I recommend that you read https://stackoverflow.com/a/53015144/1235935 – Saptarshi Basu Nov 28 '18 at 18:34
  • Yes!!! That was it. THANK YOU!!! Quick question though. If you have a web service, how would you use a random IV? My understanding is that the same IV for encryption is used for decryption? – Morkus Nov 28 '18 at 18:54
  • please refer to the code in the link given above. It has the complete java implementation with detailed explanation. In short, you need to use `SecureRandom.getInstanceStrong()` – Saptarshi Basu Nov 28 '18 at 19:04

1 Answers1

2

You're passing the IV during decryption, but you're not passing it during encryption. So the IV is random, leading to a corrupted first block when you decrypt (because you're using a different IV). You meant this:

cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(ivBytes));

Note that encryption multiple messages with the same key and IV is insecure. You really do want a random IV, not a fixed one. But in any case you have to use the same one.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610