纯文本从Dart代码加密,但从Java代码解密

ccrfmcuu  于 2023-02-20  发布在  Java
关注(0)|答案(1)|浏览(173)

当我使用Dart加密纯文本,然后从Java代码中解密加密文本时,我收到以下错误:

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)

相同的IV、salt和pasphase值使用Java端生成密钥,但生成的密钥不同,密码测试也不同。我使用相同的方法生成密钥。我不知道Dart代码中缺少了什么。

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

Dart代码为:

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}');

Java代码是:

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);
    }
}

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);
        }
    }
}
ar7v8xwq

ar7v8xwq1#

解密失败是因为两个代码使用了不同的salt,因此生成了不同的密钥。最终,这是由于 password_hash_plus Dart库的PBKDF 2实现设计不当所致。
在Java代码中,应用了一个 random salt,在本例中为0xb 9266 c74 df 614967 d9 acaa 2878 bff 87 c。在main()中,salt以十六进制编码传递到encrypt(),在generateKey()中以十六进制解码,生成的字节序列用于密钥派生。
另一方面,password_hash_plus 库的generateBase64Key()方法要求salt为字符串,并 * 在内部 * 进行UTF-8编码,请参见here。因此,只能处理可进行UTF-8解码的salt。对于 random salt,通常情况并非如此,因为这些salt会被UTF-8解码破坏。
当然,Dart代码中应用的salt的十六进制编码也不起作用,因为generateBase64Key()在内部不执行十六进制解码,而是执行UTF-8编码。
由于salt通常是随机字节序列,因此 password_hash_plus 库的PBKDF 2实现的设计是不合适的。相反,需要将salt作为字节序列(Uint8ListList<int>)传递的实现,例如PointyCastle的PBKDF 2实现:

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);
}
...

Dart代码返回十六进制编码的salt和IV,以及Base64编码的密文。如果此数据与这些编码一起传递到Java代码的decrypt()方法,则解密成功。
请注意,迭代计数1000对于PBKDF 2来说通常太小。

相关问题