Skip to content
Advertisement

Plain text encrypt from Dart code but decrypt from Java code

When I encrypt plain text using Dart,and encrypted text is decrypted from Java code, I get this error:

javax.crypto.BadPaddingException: pad block corrupted
  at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher$BufferedGenericBlockCipher.doFinal(Unknown Source)
  at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(Unknown Source)
  at javax.crypto.Cipher.doFinal(Cipher.java:2168)
  at AesUtil.doFinal(AesUtil.java:75)
  at AesUtil.decrypt(AesUtil.java:60)
  at Main.main(Main.java:18)

Same IV, salt and passphase value using Java side for key generation, but the generated key is different and also cipher test is different. I am using same method for key generation. I don’t know what is missing in Dart code.

dependencies:
    encrypt: ^5.0.1
    hex: ^0.2.0
    password_hash_plus: ^4.0.0

Dart code is:

var random = Random.secure();
var values = List<int>.generate(16, (i) => random.nextInt(255));

// final salt = aes.IV.fromSecureRandom(16);
final salt = hex.encode(values);
final generator = PBKDF2(hashAlgorithm: sha1);
final key = aes.Key.fromBase64(generator.generateBase64Key("1234567891234567", salt, 1000, 16));


final iv = aes.IV.fromSecureRandom(16);

final encrypter =
    aes.Encrypter(aes.AES(key, mode: aes.AESMode.cbc, padding: 'PKCS7'));
final encrypted = encrypter.encrypt(st.password!, iv: iv);

var str = '${iv.base16}::${salt}::${encrypted.base64}';
var bytes = utf8.encode(str);
var base64Str = base64.encode(bytes);


//final decrypt = encrypter.decrypt64("/vvAYMc3rgCvPvuSVU/qQw==", iv: iv);

print(
    '------------------------------,n encrypt ${(encrypted.base64)}-----------'
     //'--ndecrypted ${decrypt}-----------base64--------$base64Str-----'
    'nkey = ${key.base64} array--niv = ${iv.base16}--salt= {${salt}');

And Java code is:

class Main {
    public static void main(String[] args) {
        AesUtil aesUtil = new AesUtil();
        String encrypt = aesUtil.encrypt("b9266c74df614967d9acaa2878bff87c", "6ab7c799d6411f9d0c8e048ad526eeee", "1234567891234567", "Jitu@123456");
        String a = aesUtil.decrypt("01e6a073a4255c92e704bd94d76d75c5", "98a21e07ed34afc523c5f5938c9202db", "1234567891234567", "MumTfpnzZh9bk94yiTuA+g==");

        System.out.println("encrypt = " + encrypt + " ndecrpty valaue----" + a);
    }
}

Encryption code in Java:

import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Arrays;

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

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.springframework.stereotype.Component;

public class AesUtil {
    private final int keySize;
    private final int iterationCount;
    private final Cipher cipher;

    public AesUtil() {
        this.keySize = 128;
        this.iterationCount = 1000;
        try {
            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
            cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            e.printStackTrace();
            throw fail(e);
        } catch (NoSuchProviderException e) {
            throw new RuntimeException(e);
        }
    }

    public String encrypt(String salt, String iv, String passphrase, String plaintext) {
        try {
            SecretKey key = generateKey(salt, passphrase);
            System.out.println("encryption key-------= " + base64(key.getEncoded()));
            byte[] encrypted = doFinal(Cipher.ENCRYPT_MODE, key, iv, plaintext.getBytes("ISO-8859-1"));
            return base64(encrypted);
        } catch (UnsupportedEncodingException e) {
            throw fail(e);
        }
    }

    public String decrypt(String salt, String iv, String passphrase, String ciphertext) {
        try {
            SecretKey key = generateKey(salt, passphrase);
            System.out.println("decrypt key-------= " + base64(key.getEncoded()));
            byte[] decrypted = doFinal(Cipher.DECRYPT_MODE, key, iv, base64(ciphertext));
            return new String(decrypted, "ISO-8859-1");
        } catch (UnsupportedEncodingException e) {
            return null;
        } catch (Exception e) {
            return null;
        }
    }

    private byte[] doFinal(int encryptMode, SecretKey key, String iv, byte[] bytes) {
        try {
            IvParameterSpec IivParameterSpec = new IvParameterSpec(hex(iv));
            System.out.println("----iv--= " + hex(IivParameterSpec.getIV()));
            cipher.init(encryptMode, key, IivParameterSpec);
            return cipher.doFinal(bytes);
        } catch (InvalidKeyException
                 | InvalidAlgorithmParameterException
                 | IllegalBlockSizeException
                 | BadPaddingException e) {
            e.printStackTrace();
            return null;
        }
    }

    private SecretKey generateKey(String salt, String passphrase) {
        try {
            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
            byte[] s = hex(salt);
            System.out.println("salt-= " + hex(s));
            KeySpec spec = new PBEKeySpec(passphrase.toCharArray(), s, iterationCount, keySize);

            SecretKey key = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
            return key;
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            return null;
        }
    }

    public static String random(int length) {
        byte[] salt = new byte[length];
        new SecureRandom().nextBytes(salt);
        return hex(salt);
    }

    public static String base64(byte[] bytes) {
        return Base64.encodeBase64String(bytes);
    }

    public static byte[] base64(String str) {
        return Base64.decodeBase64(str);
    }

    public static String hex(byte[] bytes) {
        return Hex.encodeHexString(bytes);
    }

    public static byte[] hex(String str) {
        try {
            return Hex.decodeHex(str.toCharArray());
        } catch (DecoderException e) {
            throw new IllegalStateException(e);
        }
    }

    private IllegalStateException fail(Exception e) {
        return null;
    }

    public static byte[][] GenerateKeyAndIV(int keyLength, int ivLength, int iterations, byte[] salt, byte[] password, MessageDigest md) {

        int digestLength = md.getDigestLength();
        int requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength;
        byte[] generatedData = new byte[requiredLength];
        int generatedLength = 0;

        try {
            md.reset();

            // Repeat process until sufficient data has been generated
            while (generatedLength < keyLength + ivLength) {

                // Digest data (last digest if available, password data, salt if available)
                if (generatedLength > 0)
                    md.update(generatedData, generatedLength - digestLength, digestLength);
                md.update(password);
                if (salt != null)
                    md.update(salt, 0, 8);
                md.digest(generatedData, generatedLength, digestLength);

                // additional rounds
                for (int i = 1; i < iterations; i++) {
                    md.update(generatedData, generatedLength, digestLength);
                    md.digest(generatedData, generatedLength, digestLength);
                }

                generatedLength += digestLength;
            }

            // Copy key and IV into separate byte arrays
            byte[][] result = new byte[2][];
            result[0] = Arrays.copyOfRange(generatedData, 0, keyLength);
            if (ivLength > 0)
                result[1] = Arrays.copyOfRange(generatedData, keyLength, keyLength + ivLength);

            return result;

        } catch (DigestException e) {
            throw new RuntimeException(e);

        } finally {
            // Clean out temporary data
            Arrays.fill(generatedData, (byte) 0);
        }
    }
}

Advertisement

Answer

The decryption fails because in both codes different salts are used and therefore different keys are generated. Ultimately, this is due to an inappropriate design of the PBKDF2 implementation of the password_hash_plus Dart library.

In the Java code a random salt is applied, in this case 0xb9266c74df614967d9acaa2878bff87c. In main(), the salt is passed hex encoded to encrypt(), hex decoded in generateKey(), and the resulting byte sequence is used for key derivation.

The generateBase64Key() method of the password_hash_plus library, on the other hand, expects the salt as string and does a UTF-8 encoding internally, see here. Therefore, only salts that are UTF-8 decodable can be processed. This is generally not true for random salts, since these are corrupted by a UTF-8 decoding.
The hex encoding of the salt applied in the Dart code does not work either, of course, because generateBase64Key() does not perform a hex decoding internally but a UTF-8 encoding.

Since salts are generally random byte sequences, the design of the PBKDF2 implementation of the password_hash_plus library is unsuitable. Instead, an implementation is required where the salt is passed as byte sequence (Uint8List or List<int>), e.g. the PBKDF2 implementation of PointyCastle:

import 'package:pointycastle/export.dart';
import 'dart:typed_data';
...
final key = aes.Key(deriveKey("1234567891234567", Uint8List.fromList(values))); // Raw salt for key derivation!
final salt = hex.encode(values);                                                // Hex encoded salt for output!
...
Uint8List deriveKey(String passphrase, Uint8List salt){
    Uint8List passphraseBytes = Uint8List.fromList(utf8.encode(passphrase));
    KeyDerivator derivator = PBKDF2KeyDerivator(HMac(SHA1Digest(), 64));    // 64 byte block size
    Pbkdf2Parameters params = Pbkdf2Parameters(salt, 1000, 16);             // 16 byte key size
    derivator.init(params);
    return derivator.process(passphraseBytes);
}
...

The Dart code returns salt and IV hex encoded and the ciphertext Base64 encoded. If this data is passed with these encodings to the decrypt() method of the Java code, decryption is successful.

Be aware that an iteration count of 1000 is generally too small for PBKDF2.

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement