How to encrypt and decrypt using AES CBC 256bit and PKCS5Padding in dart and also retrieve parameters
3,285
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
Author by
Fern
Updated on December 16, 2022Comments
-
Fern over 1 year
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 over 4 yearsWhy 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 over 4 yearsWhere are you getting the IV in the Java code?
-
Fern over 4 years
-
Richard Heap over 4 yearsDo you happen to know how the Java implementation encodes the parameters here?
getParameters().getEncoded()
Is it in ASN.1? -
Fern over 4 years@RichardHeap I believe it is this I tried my best to find where the code goes. Hope this answers your question.
-
-
Fern over 4 yearsI 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 over 4 yearsThank you for you super helpful answer ;)
-
Richard Heap over 4 yearsPlease 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.