Search code examples
javasslencryptionopenssljavax.crypto

Migrate java decryption from openssl (>1.1.1 with -md sha256) to (<1.1.1 with --pbkdf2) doesn't work


I am migrating a java code that encrypts and decrypts messages just as openssl below 1.1.1 with this command :

echo -n "password" | openssl enc -aes-256-cbc -a -k secretKey -md sha256

This works fine and without problems, the problem comes when we want to use openssl 1.1.1 or higher, we get the famous message :

*** WARNING : deprecated key derivation used.
Using -iter or -pbkdf2 would be better.

Reading , both options are based on -pbkdf2 , in one case we choose the number of iterations (-iter) and in the other case we leave the default iterations (-pbkdf2)so I have kept it this way.

echo -n "password" | openssl enc -aes-256-cbc -a -k secretKey -pbkdf2

I have seen that the only method that uses the javax.crypto library and that makes reference to -pbkdf2 is SecretKeyFactory.getInstance("PBKDF2withHmacSHA256") as it says here, I have tried to adapt it to my original code ( below with this line ) changing my keyandIV for this one but I can't make it work, I get this error :

Exception in thread "main" modified.EncryptionDecryptionException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
Caused by: javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.

I have added the new method to generate the keyAndIV and left the old one, but I can't get it to work. Do you have any idea what could be wrong ? Thank you very much !

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Base64;
import java.util.Base64.Decoder;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;


public class modified
{

  private static final String SECRET_KEY = "secretKey";
  private static final String SALT_KEY   = "Salted__";
  private static final int keylen = 32;
  private static final int ivlen = 16;


  public static void main(String[] args) throws Exception {
      String example= "U2FsdGVkX19hh2C4Gxe3ghDb/qY0x8rC4SZaZEMv5yg=";

      String decryptedText = modified.decryptPassword( example.toCharArray());
      System.out.println("value -> "+decryptedText);
  }

  private modified()
  {
    throw new IllegalStateException("Utility class");
  }

  public static String decryptPassword(char[] source) throws EncryptionDecryptionException
  {
    return decrypt(source, SECRET_KEY);
  }

  public static String encryptPassword(char[] source) throws EncryptionDecryptionException
  {

    return encrypt(source, SECRET_KEY);
  }

  private static String decrypt(char[] source, String sk) throws EncryptionDecryptionException
  {
    try
    {

      final byte[] magic = SALT_KEY.getBytes(StandardCharsets.UTF_8);
      final Decoder decoder = Base64.getDecoder();
      final byte[] inBytes = decoder.decode(String.valueOf(source));

      checkMagic(magic, inBytes);
      byte[] salt = Arrays.copyOfRange(inBytes, magic.length, magic.length + 8);

      final Cipher cipher = getCipher(Cipher.DECRYPT_MODE, source, sk, salt);
      final byte[] clear = cipher.doFinal(inBytes, 16, inBytes.length - 16);

      return new String(clear, StandardCharsets.UTF_8);
    }
    catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
        | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e)
    {
      throw new EncryptionDecryptionException(e.getMessage(), e);
    }
  }

  private static String encrypt(char[] source, String sk) throws EncryptionDecryptionException
  {
    try
    {
      final byte[] magic = SALT_KEY.getBytes(StandardCharsets.UTF_8);
      final byte[] inBytes = String.valueOf(source).getBytes(StandardCharsets.UTF_8);
      byte[] salt = (new SecureRandom()).generateSeed(8);

      final Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, source, sk, salt);

      byte[] data = cipher.doFinal(inBytes);
      data = concat(concat(magic, salt), data);

      return Base64.getEncoder().encodeToString(data);
    }
    catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
        | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e)
    {
      throw new EncryptionDecryptionException(e.getMessage(), e);
    }
  }

  private static Cipher getCipher(int cipherMode, char[] source, String sk, byte[] salt)
      throws NoSuchAlgorithmException,
      NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException
  {
    final byte[] secret = sk.getBytes(StandardCharsets.UTF_8);
    final byte[] passAndSalt = concat(secret, salt);

    //byte[] keyAndIv = getKeyAndIv(passAndSalt);
    byte[] keyAndIv = getNewKeyAndIvThatSupportPBKDF(source, salt);

    final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32);
    final byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48);

    final SecretKeySpec key = new SecretKeySpec(keyValue, "AES");

    final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(cipherMode, key, new IvParameterSpec(iv));

    Arrays.fill(passAndSalt, (byte) 0);
    return cipher;
  }

  private static byte[] concat(final byte[] a, final byte[] b)
  {
    final byte[] c = new byte[a.length + b.length];
    System.arraycopy(a, 0, c, 0, a.length);
    System.arraycopy(b, 0, c, a.length, b.length);
    return c;
  }

  private static void checkMagic(final byte[] magic, final byte[] inBytes)
  {
    final byte[] shouldBeMagic = Arrays.copyOfRange(inBytes, 0,
                                                    magic.length);
    if (!Arrays.equals(shouldBeMagic, magic))
    {
      throw new EncryptionDecryptionException("DecriptionException: Bad magic number");
    }
  }


  private static byte[] getNewKeyAndIvThatSupportPBKDF(char[] source,  byte[] salt) throws EncryptionDecryptionException
  {
    try
    {
      return SecretKeyFactory.getInstance("PBKDF2withHmacSHA256")
          .generateSecret( new PBEKeySpec(source, salt, 10000, (keylen+ivlen)*8)
          ).getEncoded();
    }
    catch (InvalidKeySpecException | NoSuchAlgorithmException e)
    {
      throw new EncryptionDecryptionException(e.getMessage(), e);
    }
  }

  private static byte[] getKeyAndIv(final byte[] passAndSalt) throws NoSuchAlgorithmException
  {
    byte[] hash = new byte[0];
    byte[] keyAndIv = new byte[0];
    for (int i = 0; i < 3; i++)
    {
      final byte[] data = concat(hash, passAndSalt);
      MessageDigest md;
      md = MessageDigest.getInstance("SHA-256");
      hash = md.digest(data);
      keyAndIv = concat(keyAndIv, hash);
    }
    return keyAndIv;
  }

}



Solution

  • You use source (which is the base64-encoded ciphertext-plus) where you should use the secret key. Here's a trimmed and minimally-corrected version of your code:

    //nopackage
    import java.nio.charset.StandardCharsets;
    //--import java.security.MessageDigest;
    //--import java.security.SecureRandom;
    import java.util.Arrays;
    import java.util.Base64;
    
    import javax.crypto.Cipher;
    import javax.crypto.SecretKeyFactory;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.PBEKeySpec;
    import javax.crypto.spec.SecretKeySpec;
    
    
    public class SO76624363encpbkdf2
    {
    
      private static final String SECRET_KEY = "secretKey";
      private static final String SALT_KEY   = "Salted__";
      private static final int keylen = 32;
      private static final int ivlen = 16;
    
    
      public static void main(String[] args) throws Exception {
          String example= "U2FsdGVkX19hh2C4Gxe3ghDb/qY0x8rC4SZaZEMv5yg=";
    
          String decryptedText = decryptPassword( example.toCharArray());
          System.out.println("value -> "+decryptedText);
      }
    
      public static String decryptPassword(char[] source) throws Exception
      {
        return decrypt(source, SECRET_KEY);
      }
    /*
      public static String encryptPassword(char[] source) throws Exception
      {
    
        return encrypt(source, SECRET_KEY);
      }
    */
      private static String decrypt(char[] source, String sk) throws Exception
      {
          final byte[] magic = SALT_KEY.getBytes(StandardCharsets.UTF_8);
          final byte[] inBytes = Base64.getDecoder() .decode(String.valueOf(source));
          checkMagic(magic, inBytes);
          byte[] salt = Arrays.copyOfRange(inBytes, magic.length, magic.length + 8);
    
          final Cipher cipher = getCipher(Cipher.DECRYPT_MODE, source, sk, salt);
          final byte[] clear = cipher.doFinal(inBytes, 16, inBytes.length - 16);
    
          return new String(clear, StandardCharsets.UTF_8);
      }
    /*
      private static String encrypt(char[] source, String sk) throws Exception
      {
          final byte[] magic = SALT_KEY.getBytes(StandardCharsets.UTF_8);
          final byte[] inBytes = String.valueOf(source).getBytes(StandardCharsets.UTF_8);
          byte[] salt = (new SecureRandom()).generateSeed(8);
    
          final Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, source, sk, salt);
    
          byte[] data = cipher.doFinal(inBytes);
          data = concat(concat(magic, salt), data);
    
          return Base64.getEncoder().encodeToString(data);
      }
    */
      private static Cipher getCipher(int cipherMode, char[] source, String sk, byte[] salt)
          throws Exception
      {
        //--final byte[] secret = sk.getBytes(StandardCharsets.UTF_8);
        //--final byte[] passAndSalt = concat(secret, salt);
    
        //byte[] keyAndIv = getKeyAndIv(passAndSalt);
        byte[] keyAndIv = getNewKeyAndIvThatSupportPBKDF(sk, salt);
    
        final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32);
        final byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48);
    
        final SecretKeySpec key = new SecretKeySpec(keyValue, "AES");
    
        final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(cipherMode, key, new IvParameterSpec(iv));
    
        //--Arrays.fill(passAndSalt, (byte) 0);
        return cipher;
      }
    /*
      private static byte[] concat(final byte[] a, final byte[] b)
      {
        final byte[] c = new byte[a.length + b.length];
        System.arraycopy(a, 0, c, 0, a.length);
        System.arraycopy(b, 0, c, a.length, b.length);
        return c;
      }
    */
      private static void checkMagic(final byte[] magic, final byte[] inBytes) throws Exception
      {
        final byte[] shouldBeMagic = Arrays.copyOfRange(inBytes, 0,
                                                        magic.length);
        if (!Arrays.equals(shouldBeMagic, magic))
        {
          throw new Exception("Bad magic number");
        }
      }
    
    
      private static byte[] getNewKeyAndIvThatSupportPBKDF(String secret,  byte[] salt) throws Exception
      {
          return SecretKeyFactory.getInstance("PBKDF2withHmacSHA256")
              .generateSecret( new PBEKeySpec(secret.toCharArray(), salt, 10000, (keylen+ivlen)*8)
              ).getEncoded();
      }
    /*
      private static byte[] getKeyAndIv(final byte[] passAndSalt) throws NoSuchAlgorithmException
      {
        byte[] hash = new byte[0];
        byte[] keyAndIv = new byte[0];
        for (int i = 0; i < 3; i++)
        {
          final byte[] data = concat(hash, passAndSalt);
          MessageDigest md;
          md = MessageDigest.getInstance("SHA-256");
          hash = md.digest(data);
          keyAndIv = concat(keyAndIv, hash);
        }
        return keyAndIv;
      }
    */
    }