I've implemented streamed AES-GCM encryption/decryption. That means I am required to call: processBytes() with chunks of data, then I will call doFinal() to flush the remaining buffer and generate/verify MAC.
If you find them useful, please go ahead and merge them upstream. I am adding more internal unit tests, however I've limited ability to confirm it's not breaking anything within PointyCastle. If you need more input on these issues or fix, please let me know.
Issue 1
Encrypted cipherText doesn't correspond to input. Some bytes are overwritten by previous contents of _bufBlock.
In a below example I am trying to encrypt:
abcdefghijklmnop-qrstuvwxyz123456
whereas, this ends up being encrypted:
abcdefghijklmnop-qrstuvwxyz12345q
Example code to reproduce this:
import 'dart:convert';
import 'dart:typed_data';
import 'package:pointycastle/api.dart';
import 'package:pointycastle/block/aes.dart';
import 'package:pointycastle/block/modes/gcm.dart';
import "package:hex/hex.dart";
void main() async {
const TAG_LENGTH = 16;
const BLOCK_SIZE = 16;
final key = hexToUint8List('88fe0ff8c4eaf468d4cd9d9a9831662488fe0ff8c4eaf468d4cd9d9a98316624');
final nonce = hexToUint8List('3c95422167063c9542216706');
final plaintextPart1 = Uint8List.fromList(utf8.encode("abcdefghijklmnop-"));
final plaintextPart2 = Uint8List.fromList(utf8.encode("qrstuvwxyz123456"));
final cipher = GCMBlockCipher(AESEngine());
cipher.init(true, AEADParameters(KeyParameter(key), TAG_LENGTH * 8, nonce, Uint8List(0)));
var bufferOut = Uint8List(plaintextPart1.length);
final bytesProcessed = cipher.processBytes(plaintextPart1, 0, plaintextPart1.length, bufferOut, 0);
bufferOut = bufferOut.sublist(0, bytesProcessed);
var bufferOut2 = Uint8List(plaintextPart2.length);
final bytesProcessed2 = cipher.processBytes(plaintextPart2, 0, plaintextPart2.length, bufferOut2, 0);
bufferOut2 = bufferOut2.sublist(0, bytesProcessed2);
var bufferOut3 = Uint8List(TAG_LENGTH + BLOCK_SIZE);
final bytesProcessed3 = cipher.doFinal(bufferOut3, 0);
bufferOut3 = bufferOut3.sublist(0, bytesProcessed3);
final cipherTextWithMac = bufferOut + bufferOut2 + bufferOut3;
print("Ciphertext: " + HEX.encode(cipherTextWithMac.sublist(0, cipherTextWithMac.length - TAG_LENGTH))); // f0c2b9571faa064c7b730143ef1a8699e871859250fcac171571ee56639d9c9644
print("MAC: " + HEX.encode(cipherTextWithMac.sublist(cipherTextWithMac.length - TAG_LENGTH))); // ba49063f7908d98cbb69df8ead55973d
// DECRYPT
final decryptCipher = GCMBlockCipher(AESEngine());
decryptCipher.init(false, AEADParameters(KeyParameter(key), TAG_LENGTH * 8, nonce, Uint8List(0)));
final decryptedPlainText = decryptCipher.process(Uint8List.fromList(cipherTextWithMac));
print(" PRE: " + utf8.decode(plaintextPart1 + plaintextPart2));
print("POST: " + utf8.decode(decryptedPlainText));
// FAIL:
// abcdefghijklmnop-qrstuvwxyz123456
// !=
// abcdefghijklmnop-qrstuvwxyz12345q
assert(utf8.decode(plaintextPart1) + utf8.decode(plaintextPart2) == utf8.decode(decryptedPlainText));
}
Uint8List hexToUint8List(String hex) {
if (!(hex is String)) {
throw 'Expected string containing hex digits';
}
if (hex.length % 2 != 0) {
throw 'Odd number of hex digits';
}
var l = hex.length ~/ 2;
var result = new Uint8List(l);
for (var i = 0; i < l; ++i) {
var x = int.parse(hex.substring(i * 2, (2 * (i + 1))), radix: 16);
if (x.isNaN) {
throw 'Expected hex string';
}
result[i] = x;
}
return result;
}
Issue 2.
During decryption, for some inputs, buffer size returned by getOutputSize is too small.
In some cases there might be up to 16 bytes coming from an internal buffer: _bufBlock.
Change in: getOutputSize was required.
Issue 3.
Decryption doesn't happen until input exceeds 16 bytes (e.g. 17 bytes).
Fix. "base_aead_block_cipher.dart:194", change from: while (len > blockSize) to while (len >= blockSize)
I've implemented streamed AES-GCM encryption/decryption. That means I am required to call:
processBytes()
with chunks of data, then I will calldoFinal()
to flush the remaining buffer and generate/verify MAC.There are multiple issues which I've managed to address, changes are pretty tiny. They are in my fork: https://github.com/bcgit/pc-dart/compare/master...tomekit:pc-dart:master
If you find them useful, please go ahead and merge them upstream. I am adding more internal unit tests, however I've limited ability to confirm it's not breaking anything within PointyCastle. If you need more input on these issues or fix, please let me know.
Issue 1 Encrypted cipherText doesn't correspond to input. Some bytes are overwritten by previous contents of _bufBlock.
In a below example I am trying to encrypt:
abcdefghijklmnop-qrstuvwxyz123456
whereas, this ends up being encrypted:abcdefghijklmnop-qrstuvwxyz12345q
Example code to reproduce this:
Issue 2. During decryption, for some inputs, buffer size returned by
getOutputSize
is too small. In some cases there might be up to 16 bytes coming from an internal buffer:_bufBlock
. Change in:getOutputSize
was required.Issue 3. Decryption doesn't happen until input exceeds 16 bytes (e.g. 17 bytes). Fix. "base_aead_block_cipher.dart:194", change from:
while (len > blockSize)
towhile (len >= blockSize)