Расшифровать шифрование AES / CBC / PKCS5Padding в Dart

У меня уже есть код шифрования в java. Теперь я хочу использовать API со своего сервера. Даже попробовав различные руководства и примеры кодов, я не могу успешно расшифровать хэш.

Я знаю, что фиксированная соль и внутривенное вливание вообще не рекомендуются. Но для простоты и понимания проблемы я оставил соль и IV на 00000000000000000000000000000000;

Хеш после шифрования из Java = XjxCg0KK0ZDWa4XMFhykIw ==; Используемый закрытый ключ = Mayur12354673645

Может ли кто-нибудь помочь мне расшифровать указанную выше строку с помощью дротика.

Код JAVA

public String encrypt(String salt, String iv, String passphrase,
                              String plaintext) {
            try {
                SecretKey key = generateKey(salt, passphrase);
                byte[] encrypted = doFinal(Cipher.ENCRYPT_MODE, key, iv, plaintext
                        .getBytes("UTF-8"));
                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);
                byte[] decrypted = doFinal(Cipher.DECRYPT_MODE, key, iv,
                        base64(ciphertext));
                return new String(decrypted, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                throw fail(e);
            }
        }    
    
    private SecretKey generateKey(String salt, String passphrase) {
                try {
                    SecretKeyFactory factory = SecretKeyFactory
                            .getInstance("PBKDF2WithHmacSHA1");
                    KeySpec spec = new PBEKeySpec(passphrase.toCharArray(), hex(salt),
                            iterationCount, keySize);
                    SecretKey key = new SecretKeySpec(factory.generateSecret(spec)
                            .getEncoded(), "AES");
                    return key;
                } catch (Exception e) {
                    e.printStackTrace();
                    return null;
                }
            }

private byte[] doFinal(int encryptMode, SecretKey key, String iv,
                           byte[] bytes) {
        try {
            cipher.init(encryptMode, key, new IvParameterSpec(hex(iv)));
            return cipher.doFinal(bytes);
        } catch (Exception e) {
            e.printStackTrace();
            throw fail(e);
        }
    }

Мой код дротика

import 'package:pointycastle/block/aes_fast.dart';
import 'package:pointycastle/block/modes/cbc.dart';
import 'package:pointycastle/digests/sha1.dart';
import 'package:pointycastle/key_derivators/pbkdf2.dart';
import 'package:pointycastle/macs/hmac.dart';
import 'package:pointycastle/paddings/pkcs7.dart';
import 'package:pointycastle/pointycastle.dart';
import 'dart:convert';
import 'dart:typed_data';
import 'package:convert/convert.dart';
import 'dart:developer';

import 'package:pointycastle/random/fortuna_random.dart';

const KEY_SIZE = 16;
const ITERATION_COUNT = 5;

class EncryptionHandler {
  static const CBC_MODE = 'CBC';

  static Uint8List deriveKey(dynamic password,
      {String salt = '0000000000000000',
      int iterationCount = ITERATION_COUNT,
      int derivedKeyLength = KEY_SIZE}) {
    if (password == null || password.isEmpty) {
      throw new ArgumentError('password must not be empty');
    }

    if (password is String) {
      password = createUint8ListFromString(password);
    }

    Uint8List saltBytes = createUint8ListFromString(salt);
    String hexSalt = formatBytesAsHexString(saltBytes);

    KeyDerivator keyDerivator =
    new PBKDF2KeyDerivator(new HMac(new SHA1Digest(), 64));

    Pbkdf2Parameters params =
        new Pbkdf2Parameters(saltBytes, iterationCount, derivedKeyLength);

    keyDerivator.init(params);

    return keyDerivator.process(password);
  }

  Uint8List createUint8ListFromHexString(String hex) {
    var result = new Uint8List(hex.length ~/ 2);
    for (var i = 0; i < hex.length; i += 2) {
      var num = hex.substring(i, i + 2);
      var byte = int.parse(num, radix: 16);
      result[i ~/ 2] = byte;
    }
    return result;
  }

  static String formatBytesAsHexString(Uint8List bytes) {
    var result = new StringBuffer();
    for (var i = 0; i < bytes.lengthInBytes; i++) {
      var part = bytes[i];
      result.write('${part < 16 ? '0' : ''}${part.toRadixString(16)}');
    }
    return result.toString();
  }

  static Uint8List pad(Uint8List src, int blockSize) {
    var pad = new PKCS7Padding();
    pad.init(null);

    int padLength = blockSize - (src.length % blockSize);
    var out = new Uint8List(src.length + padLength)..setAll(0, src);
    pad.addPadding(out, src.length);

    return out;
  }

  static Uint8List unpad(Uint8List src) {
    var pad = new PKCS7Padding();
    pad.init(null);

    int padLength = pad.padCount(src);
    int len = src.length - padLength;

    return new Uint8List(len)..setRange(0, len, src);
  }

  static String encrypt(String password, String plaintext,
      {String mode = CBC_MODE}) {
    Uint8List derivedKey = deriveKey(password);
    KeyParameter keyParam = new KeyParameter(derivedKey);
    BlockCipher aes = new AESFastEngine();

    var rnd = FortunaRandom();
    rnd.seed(keyParam);
    Uint8List iv = createUint8ListFromString("0000000000000000");

    BlockCipher cipher;
    ParametersWithIV params = new ParametersWithIV(keyParam, iv);
    cipher = new CBCBlockCipher(aes);
    cipher.init(true, params);

    Uint8List textBytes = createUint8ListFromString(plaintext);
    Uint8List paddedText = pad(textBytes, aes.blockSize);
    Uint8List cipherBytes = _processBlocks(cipher, paddedText);
    Uint8List cipherIvBytes = new Uint8List(cipherBytes.length + iv.length)
      ..setAll(0, iv)
      ..setAll(iv.length, cipherBytes);

    return base64.encode(cipherIvBytes);
  }

  static String decrypt(String password, String ciphertext) {
    log('Password: $password');
    Uint8List derivedKey = deriveKey(password);
    log('derivedKey: $derivedKey');
    KeyParameter keyParam = new KeyParameter(derivedKey);
    log('keyParam: $keyParam');
    BlockCipher aes = new AESFastEngine();

    Uint8List cipherIvBytes = base64.decode(ciphertext);
    log('cipherIvBytes: $cipherIvBytes');
    Uint8List iv = createUint8ListFromString("0000000000000000");
    // Uint8List iv = new Uint8List(aes.blockSize)
    //   ..setRange(0, aes.blockSize, cipherIvBytes);
    log('iv: $iv');
    BlockCipher cipher;
    ParametersWithIV params = new ParametersWithIV(keyParam, iv);
    log('params: $params');
    cipher = new CBCBlockCipher(aes);
    log('cipher: $cipher');
    cipher.init(false, params);

    int cipherLen = cipherIvBytes.length - aes.blockSize;
    Uint8List cipherBytes = new Uint8List(cipherLen)
      ..setRange(0, cipherLen, cipherIvBytes, aes.blockSize);
    Uint8List paddedText = _processBlocks(cipher, cipherBytes);
    log('cipher: $paddedText');
    Uint8List textBytes = paddedText;
    // Uint8List textBytes = unpad(paddedText);

    return new String.fromCharCodes(textBytes);
  }

  static Uint8List createUint8ListFromString(String s) {
    var ret = new Uint8List(s.length);
    for (var i = 0; i < s.length; i++) {
      ret[i] = s.codeUnitAt(i);
    }
    return ret;
  }

  static Uint8List _processBlocks(BlockCipher cipher, Uint8List inp) {
    var out = new Uint8List(inp.lengthInBytes);

    for (var offset = 0; offset < inp.lengthInBytes;) {
      var len = cipher.processBlock(inp, offset, out, offset);
      offset += len;
    }

    return out;
  }
}

person Mayur_Thakur    schedule 25.09.2020    source источник


Ответы (1)


Код можно упростить, используя существующие библиотеки Dart для преобразования двоичного кода в шестнадцатеричный и наоборот. PointyCastle также поддерживает заполнение (PKCS7), поэтому настраиваемая реализация не требуется, что также сокращает код. В Интернете вы можете найти несколько реализаций дротиков для AES / CBC / PKCS7Padding в сочетании с PBKDF2, которые используют PointyCastle, например здесь и здесь.

Возможная реализация Dart для дешифрования с использованием pointycastle и convert, например, (для простоты без обработки исключений):

import 'dart:typed_data';
import "package:pointycastle/export.dart";
import 'package:convert/convert.dart';
import 'dart:convert';
...
static Uint8List decrypt(Uint8List ciphertext, Uint8List key, Uint8List iv) {
  CBCBlockCipher cipher = new CBCBlockCipher(new AESFastEngine());
  ParametersWithIV<KeyParameter> params = new ParametersWithIV<KeyParameter>(new KeyParameter(key), iv);
  PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null> paddingParams = new PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null>(params, null);
  PaddedBlockCipherImpl paddingCipher = new PaddedBlockCipherImpl(new PKCS7Padding(), cipher);
  paddingCipher.init(false, paddingParams);
  return paddingCipher.process(ciphertext);
}

static Uint8List generateKey(Uint8List salt, Uint8List passphrase){
  KeyDerivator derivator = new PBKDF2KeyDerivator(new HMac(new SHA1Digest(), 64));
  Pbkdf2Parameters params = new Pbkdf2Parameters(salt, 5, 16);
  derivator.init(params);
  return derivator.process(passphrase);
}

С опубликованными тестовыми данными:

String saltHex = '00000000000000000000000000000000';
String ivHex = '00000000000000000000000000000000';
String passphraseUtf8 = 'Mayur12354673645';
String ciphertextBase64 = "XjxCg0KK0ZDWa4XMFhykIw==";

Uint8List salt = hex.decode(saltHex);
Uint8List passphrase = utf8.encode(passphraseUtf8);
Uint8List key = generateKey(salt, passphrase);

Uint8List ciphertext = base64.decode(ciphertextBase64);
Uint8List iv = hex.decode(ivHex);
Uint8List decrypted = decrypt(ciphertext, key, iv);

print(utf8.decode(decrypted)); // This is working

зашифрованный текст можно расшифровать так: Это работает.

Альтернативой PointyCastle является пакет cryptography, который позволяет еще более компактно реализовать в текущем случае:

import 'package:cryptography/cryptography.dart';
import 'package:convert/convert.dart';
import 'dart:convert';
import 'dart:typed_data';
...
static Uint8List decrypt(Uint8List ciphertext, Uint8List key, Uint8List iv) {
  SecretKey secretKey = new SecretKey(key);
  Nonce nonce = new Nonce(iv);
  Uint8List decrypted = aesCbc.decryptSync(ciphertext, secretKey: secretKey, nonce: nonce);
  return decrypted;
}

static Uint8List generateKey(Uint8List salt, Uint8List passphrase){
  Pbkdf2 pbkdf2 = Pbkdf2(macAlgorithm: new Hmac(sha1), iterations: 5, bits: 128);
  return pbkdf2.deriveBitsSync(passphrase, nonce: Nonce(salt));
}

Обратите внимание, что на практике IV и соль должны генерироваться случайным образом для каждого шифрования (о котором вы уже упоминали в своем вопросе). Кроме того, количество итераций, равное 5, обычно слишком мало.

person user 9014097    schedule 25.09.2020
comment
Большое спасибо за то, что уделили свое драгоценное время, чтобы помочь мне. Этот образец работает как шарм. Для соли и iv, да, это будет динамически, и я также проверю количество итераций. - person Mayur_Thakur; 25.09.2020