library impl.block_cipher.modes.gcm; import 'dart:math' show min; import 'dart:typed_data'; import 'package:pointycastle/src/ct.dart'; import '../../api.dart'; import '../../src/impl/base_aead_block_cipher.dart'; import '../../src/registry/registry.dart'; class GCMBlockCipher extends BaseAEADBlockCipher { /// Intended for internal use. // ignore: non_constant_identifier_names static final FactoryConfig factoryConfig = DynamicFactoryConfig.suffix( BlockCipher, '/GCM', (_, final Match match) => () { var underlying = BlockCipher(match.group(1)!); return GCMBlockCipher(underlying); }); late Uint8List _h; late Uint8List _counter; late Uint8List _e; late Uint8List _e0; late Uint8List _x; late int _processedBytes; int _blocksRemaining = 0; GCMBlockCipher(super.cipher); @override String get algorithmName => '${underlyingCipher.algorithmName}/GCM'; @override void init(bool forEncryption, CipherParameters? params) { var bs = underlyingCipher.blockSize; _blocksRemaining = (2 ^ 36 - 64) ~/ bs; super.init(forEncryption, params); } @override void reset() { var bs = underlyingCipher.blockSize; _blocksRemaining = (2 ^ 36 - 64) ~/ bs; super.reset(); } @override void prepare(KeyParameter keyParam) { if (macSize != 16) { throw ArgumentError('macSize should be equal to 16 for GCM'); } underlyingCipher.init(true, keyParam); _h = Uint8List(blockSize); underlyingCipher.processBlock(_h, 0, _h, 0); _counter = _computeInitialCounter(nonce); _e0 = Uint8List(16); _computeE(_counter, _e0); _e = Uint8List(16); _x = Uint8List(16); _processedBytes = 0; } Uint8List _computeInitialCounter(Uint8List iv) { var counter = Uint8List(16); if (iv.length == 12) { counter.setAll(0, iv); counter[15] = 1; } else { _gHASH(counter, iv); var length = Uint8List.view((Uint32List(4)..[0] = iv.length * 8).buffer); length = Uint8List.fromList(length.reversed.toList()); _gHASHBlock(counter, length); } return counter; } @override int processBlock(Uint8List inp, int inpOff, Uint8List out, int outOff) { var length = blockSize < inp.length - inpOff ? blockSize : inp.length - inpOff; var i = Uint8List(blockSize); i.setAll(0, inp.skip(inpOff).take(length)); _processedBytes += length; _getNextCTRBlock(_e); var o = Uint8List.fromList(i); _xor(o, _e); if (length < blockSize) o.fillRange(length, blockSize, 0); out.setRange(outOff, outOff + length, o); var c = forEncryption ? o : i; _gHASHBlock(_x, c); return length; } void _gHASH(Uint8List x, Uint8List y) { var block = Uint8List(16); for (var i = 0; i < y.length; i += 16) { block.setAll(0, y.sublist(i, min(i + 16, y.length))); block.fillRange(min(i + 16, y.length) - i, 16, 0); _gHASHBlock(x, block); } } void _gHASHBlock(Uint8List x, Uint8List y) { _xor(x, y); _mult(x, _h); } void _getNextCTRBlock(Uint8List out) { // // This is tested manually by forcing _blocksRemaining to 1 and trying to run // the unit tests. Otherwise it takes 64GB of data to exhaust. // if (_blocksRemaining == 0) { throw StateError('Attempt to process too many blocks'); } _blocksRemaining--; _counter[15]++; for (var i = 15; i >= 12 && _counter[i] == 0; i--) { _counter[i] = 0; if (i > 12) _counter[i - 1]++; } _computeE(_counter, out); } void _computeE(Uint8List inp, Uint8List out) { underlyingCipher.processBlock(inp, 0, out, 0); } final Uint8List r = Uint8List(16)..[0] = 0xe1; void _mult(Uint8List x, Uint8List y) { var v = x; var z = Uint8List(x.length); for (var i = 0; i < 128; i++) { CT_xor(z, v, _bit(y, i)); CT_xor(v, r, _shiftRight(v)); } x.setAll(0, z); } void _xor(Uint8List x, Uint8List? y) { for (var i = 0; i < x.length; i++) { x[i] ^= y![i]; } } bool _bit(Uint8List x, int n) { var byte = n ~/ 8; var mask = 1 << (7 - n % 8); return x[byte] & mask == mask; } bool _shiftRight(Uint8List x) { var overflow = false; for (var i = 0; i < x.length; i++) { var nextOverflow = x[i] & 0x1 == 0x1; x[i] >>= 1; if (overflow) x[i] |= 0x80; overflow = nextOverflow; } return overflow; } @override int doFinal(Uint8List out, int outOff) { var result = remainingInput.isNotEmpty ? processBlock(remainingInput, 0, out, outOff) : 0; var len = Uint8List.view((Uint32List(4) ..[2] = aad!.length * 8 ..[0] = _processedBytes * 8) .buffer); len = Uint8List.fromList(len.reversed.toList()); _gHASHBlock(_x, len); _xor(_x, _e0); if (forEncryption) { out.setAll(outOff + result, _x); result += _x.length; } validateMac(); return result; } @override Uint8List get mac => _x; @override void processAADBytes(Uint8List inp, int inpOff, int len) { var block = Uint8List(16); for (var i = 0; i < len; i += 16) { block.fillRange(0, 16, 0); block.setAll( 0, inp.sublist(inpOff + i, inpOff + min(i + 16, len) as int)); _gHASHBlock(_x, block); } } }