2

I have some java code which I am trying to replicate in Dart (class can be found at here)

    /**
     * Encrypt object with password
     * @param data Object to be encrypted
     * @param secret Password to use for encryption
     * @return Encrypted version of object
     */
    public static EncryptedBytes encrypt(String data, SecretKey secret) throws InvalidKeyException {

        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secret);

            // properly encode the complete ciphertext
            //logEncrypt(password, object);

            byte[] encodedData = cipher.doFinal(Base64.getEncoder().encode(data.getBytes(Charset.forName("UTF-8"))));
            byte[] params = cipher.getParameters().getEncoded();
            String paramAlgorithm = cipher.getParameters().getAlgorithm();

            return new EncryptedBytes(encodedData, params, paramAlgorithm);
        } catch (NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException | BadPaddingException | IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Decrypt data with secret
     * @param encryptedBytes Object to be decrypted
     * @param secret Password to use for decryption
     * @return Decrypted version of object
     */
    public static String decrypt(EncryptedBytes encryptedBytes, @NonNull SecretKey secret) throws InvalidKeyException {
        try {

            // get parameter object for password-based encryption
            AlgorithmParameters algParams = AlgorithmParameters.getInstance(encryptedBytes.getParamAlgorithm());

            // initialize with parameter encoding from above
            algParams.init(encryptedBytes.getParams());

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, secret, algParams);

            return new String(Base64.getDecoder().decode(cipher.doFinal(encryptedBytes.getData())), Charset.forName("UTF-8"));
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException | IOException | InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        }
        return null;
    }

The EncryptedBytes class is simply just a data holder

@RequiredArgsConstructor
@Getter
public class EncryptedBytes {

    private final byte[] data;
    private final byte[] params;
    private final String paramAlgorithm;

}

Right now in Dart I'm using PointyCastle and have gotten this close (not tested it though)

 static EncryptedBytes encrypt(String data, KeyParameter keyParameter) {
    final AESFastEngine aes = AESFastEngine()..init(false, keyParameter); // false=decrypt

    Uint8List encryptedData = aes.process(utf8.encode(data)); // Needs to convert to UTF8 then Base64 and finally be encrypted
    Uint8List params;

    String algorithm = aes.algorithmName;

    return EncryptedBytes(encryptedData, params, algorithm);
  }

  static String decrypt(EncryptedBytes data, KeyParameter keyParameter) {
    final AESFastEngine aes = AESFastEngine()..init(true, keyParameter); // true=encrypt

    String encryptedData = utf8.decode(aes.process(data.data)); // Needs to be decrypted, then decoded from Base64 and finally UTF8

    return encryptedData;
  }

I'm not entirely sure what I can use to get an equivalent of the java code above in Dart. Base64 returns a String for encoding and requires a string for decoding while aes.process() requires and returns Uint8List

Richard Heap
  • 48,344
  • 9
  • 130
  • 112
Fern
  • 93
  • 6
  • 16
  • Why does the java code base64 encode the bytes before encrypting them. That doesn't make much sense. The cipher needs an array of bytes, which is what you have before the base 64 step. After that step you end up with a string, which is not what the cipher needs. – Richard Heap Dec 29 '19 at 23:15
  • Where are you getting the IV in the Java code? – Richard Heap Dec 29 '19 at 23:17
  • @RichardHeap The parameters such as IV are provided when encrypting using EncryptedBytes and then retrieve those parameters when decrypting the object. Look [decrypt](https://github.com/Fernthedev/light-chat/blob/master/java/core/src/main/java/com/github/fernthedev/core/encryption/util/EncryptionUtil.java#L93-L98) and [encrypt](https://github.com/Fernthedev/light-chat/blob/master/java/core/src/main/java/com/github/fernthedev/core/encryption/util/EncryptionUtil.java#L73-L77) – Fern Dec 30 '19 at 00:30
  • Do you happen to know how the Java implementation encodes the parameters here? `getParameters().getEncoded()` Is it in ASN.1? – Richard Heap Dec 30 '19 at 00:58
  • @RichardHeap I believe it is [this](https://hatebin.com/meocpdrooa) I tried my best to find where the code goes. Hope this answers your question. – Fern Dec 30 '19 at 01:24

1 Answers1

2

Here's a working example of encode in Java, decode in pointycastle.

Java

String plainText = "Hello World!";

KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(256);

SecretKey secret = generator.generateKey();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);

byte[] encodedData = cipher.doFinal(Base64.getEncoder().encode(plainText.getBytes(StandardCharsets.UTF_8)));

Base64.Encoder encoder = Base64.getEncoder();
System.out.println(encoder.encodeToString(secret.getEncoded()));
System.out.println(encoder.encodeToString(cipher.getParameters().getEncoded())); // the DER encoded IV
System.out.println(encoder.encodeToString(encodedData));

Dart

import 'dart:convert';
import 'dart:typed_data';

import 'package:pointycastle/export.dart';

void main() {
  // the following 3 values were output from the above Java code
  var key = base64.decode('9JYmap3xB79oyBkY6ZIdJCXaOr/CurCK8XUsRZL9XXI=');
  var params = base64.decode('BBChkSMIq/v35PRRWAJGwtTr');
  var cipherText =
      base64.decode('Dh+lg2IMzcLC0toDRSoNMAQoR7MWKMLMPRi7KtdQdmw=');
  var iv = params.sublist(2); // strip the 4, 16 DER header

  var cipher = PaddedBlockCipherImpl(
    PKCS7Padding(),
    CBCBlockCipher(AESFastEngine()),
  );

  cipher.init(
    false /*decrypt*/,
    PaddedBlockCipherParameters<CipherParameters, CipherParameters>(
      ParametersWithIV<KeyParameter>(KeyParameter(key), iv),
      null,
    ),
  );

  var plainishText = cipher.process(cipherText);

  print(utf8.decode(base64.decode(utf8.decode(plainishText))));
}

Encrypting in Dart

  var key = Uint8List(32); // the 256 bit key
  var plainText = 'Ciao Mondo';
  var random = Random.secure();
  var params = Uint8List(18)
    ..[0] = 4
    ..[1] = 16;
  for (int i = 2; i < 18; i++) {
    params[i] = random.nextInt(256);
  }
  var iv = params.sublist(2);

  var cipher = PaddedBlockCipherImpl(
    PKCS7Padding(),
    CBCBlockCipher(AESFastEngine()),
  )..init(
      true /*encrypt*/,
      PaddedBlockCipherParameters<CipherParameters, CipherParameters>(
        ParametersWithIV<KeyParameter>(KeyParameter(key), iv),
        null,
      ),
    );

  var plainBytes = utf8.encode(base64.encode(utf8.encode(plainText)));
  var cipherText = cipher.process(plainBytes);

  // cipherText is the cipher text
  // params is the Java compatible params
Richard Heap
  • 48,344
  • 9
  • 130
  • 112
  • I didn't really need the java code, just dart code but thanks anyway as it's useful. I just realized how redundant encoding and decoding the string in Base64 is when I could have just used UTF8 encoding/decoding by itself. Now all I need is encrypting in Dart, which the only thing I'm missing is getting the params and storing them in bytes the same way java does. – Fern Dec 30 '19 at 23:55
  • Thank you for you super helpful answer ;) – Fern Dec 31 '19 at 01:02
  • Please note the several minor changes to fix the padding problem. And, yes, I agree your base64 route isn't needed, but I guess you already have the Java end implemented. – Richard Heap Dec 31 '19 at 01:18