library impl.stream_cipher.chacha7539; import 'dart:typed_data'; import '../export.dart'; import '../api.dart'; import '../src/impl/base_stream_cipher.dart'; import '../src/registry/registry.dart'; import '../src/ufixnum.dart'; // ignore_for_file: omit_local_variable_types, prefer_single_quotes // ignore_for_file: non_constant_identifier_names, directives_ordering // ignore_for_file: prefer_typing_uninitialized_variables, camel_case_types // ignore_for_file: annotate_overrides /// RFC version of Daniel J. Bernstein's ChaCha20. This uses a 12 byte IV, among /// other changes. class ChaCha7539Engine extends BaseStreamCipher { static final FactoryConfig factoryConfig = DynamicFactoryConfig.prefix( StreamCipher, 'ChaCha7539/', (_, final Match match) => () { var rounds = int.parse(match.group(1)!); return ChaCha7539Engine.fromRounds(rounds); }); ChaCha7539Engine() { rounds = 20; } ChaCha7539Engine.fromRounds(this.rounds); int rounds = 20; static const STATE_SIZE = 16; static final _sigma = Uint8List.fromList([ 101, 120, 112, 97, 110, 100, 32, 51, 50, 45, 98, 121, 116, 101, 32, 107 ]); static final _tau = Uint8List.fromList([ 101, 120, 112, 97, 110, 100, 32, 49, 54, 45, 98, 121, 116, 101, 32, 107 ]); Uint8List? _workingKey; late Uint8List _workingIV; final _state = List.filled(STATE_SIZE, 0, growable: false); final _buffer = List.filled(STATE_SIZE, 0, growable: false); final _keyStream = Uint8List(STATE_SIZE * 4); var _keyStreamOffset = 0; var _initialised = false; @override String get algorithmName => 'ChaCha7539/$rounds'; @override void reset() { _state[12] = 0; if (_workingKey != null) { _setKey(_workingKey!, _workingIV); } } @override void init( bool forEncryption, covariant ParametersWithIV params) { var uparams = params.parameters; var iv = params.iv; if (iv.length != 12) { throw ArgumentError('ChaCha20-7539 requires exactly 12 bytes of IV'); } _workingIV = iv; _workingKey = uparams!.key; _setKey(_workingKey!, _workingIV); } @override int returnByte(int inp) { if (_keyStreamOffset == 0) { generateKeyStream(_keyStream); if (++_state[12] == 0) { ++_state[13]; } } var out = clip8(_keyStream[_keyStreamOffset] ^ inp); _keyStreamOffset = (_keyStreamOffset + 1) & 63; return out; } @override void processBytes( Uint8List inp, int inpOff, int len, Uint8List out, int outOff) { if (!_initialised) { throw StateError('ChaCha20 not initialized: please call init() first'); } if ((inpOff + len) > inp.length) { throw ArgumentError( 'Input buffer too short or requested length too long'); } if ((outOff + len) > out.length) { throw ArgumentError( 'Output buffer too short or requested length too long'); } for (var i = 0; i < len; i++) { if (_keyStreamOffset == 0) { generateKeyStream(_keyStream); if (++_state[12] == 0) throw StateError('Illegal increase of counter'); } out[i + outOff] = clip8(_keyStream[_keyStreamOffset] ^ inp[i + inpOff]); _keyStreamOffset = (_keyStreamOffset + 1) & 63; } } void _setKey(Uint8List keyBytes, Uint8List ivBytes) { _workingKey = keyBytes; _workingIV = ivBytes; _keyStreamOffset = 0; Uint8List constants; if (_workingKey!.length == 32) { constants = _sigma; } else { constants = _tau; } //Key _state[4] = unpack32(_workingKey, 0, Endian.little); _state[5] = unpack32(_workingKey, 4, Endian.little); _state[6] = unpack32(_workingKey, 8, Endian.little); _state[7] = unpack32(_workingKey, 12, Endian.little); _state[8] = unpack32(_workingKey, 16, Endian.little); _state[9] = unpack32(_workingKey, 20, Endian.little); _state[10] = unpack32(_workingKey, 24, Endian.little); _state[11] = unpack32(_workingKey, 28, Endian.little); _state[0] = unpack32(constants, 0, Endian.little); _state[1] = unpack32(constants, 4, Endian.little); _state[2] = unpack32(constants, 8, Endian.little); _state[3] = unpack32(constants, 12, Endian.little); _state[12] = 0; // IV var off = 0; for (var i = 0; i < 3; ++i) { _state[13 + i] = unpack32(_workingIV, off, Endian.little); off += 4; } _initialised = true; } void generateKeyStream(Uint8List output) { _core(rounds, _state, _buffer); var outOff = 0; for (var x in _buffer) { pack32(x, output, outOff, Endian.little); outOff += 4; } } /// The ChaCha20 core function void _core(int rounds, List input, List x) { var x00 = input[0]; var x01 = input[1]; var x02 = input[2]; var x03 = input[3]; var x04 = input[4]; var x05 = input[5]; var x06 = input[6]; var x07 = input[7]; var x08 = input[8]; var x09 = input[9]; var x10 = input[10]; var x11 = input[11]; var x12 = input[12]; var x13 = input[13]; var x14 = input[14]; var x15 = input[15]; for (var i = rounds; i > 0; i -= 2) { x00 += x04; x12 = crotl32(x12 ^ x00, 16); x08 += x12; x04 = crotl32(x04 ^ x08, 12); x00 += x04; x12 = crotl32(x12 ^ x00, 8); x08 += x12; x04 = crotl32(x04 ^ x08, 7); x01 += x05; x13 = crotl32(x13 ^ x01, 16); x09 += x13; x05 = crotl32(x05 ^ x09, 12); x01 += x05; x13 = crotl32(x13 ^ x01, 8); x09 += x13; x05 = crotl32(x05 ^ x09, 7); x02 += x06; x14 = crotl32(x14 ^ x02, 16); x10 += x14; x06 = crotl32(x06 ^ x10, 12); x02 += x06; x14 = crotl32(x14 ^ x02, 8); x10 += x14; x06 = crotl32(x06 ^ x10, 7); x03 += x07; x15 = crotl32(x15 ^ x03, 16); x11 += x15; x07 = crotl32(x07 ^ x11, 12); x03 += x07; x15 = crotl32(x15 ^ x03, 8); x11 += x15; x07 = crotl32(x07 ^ x11, 7); x00 += x05; x15 = crotl32(x15 ^ x00, 16); x10 += x15; x05 = crotl32(x05 ^ x10, 12); x00 += x05; x15 = crotl32(x15 ^ x00, 8); x10 += x15; x05 = crotl32(x05 ^ x10, 7); x01 += x06; x12 = crotl32(x12 ^ x01, 16); x11 += x12; x06 = crotl32(x06 ^ x11, 12); x01 += x06; x12 = crotl32(x12 ^ x01, 8); x11 += x12; x06 = crotl32(x06 ^ x11, 7); x02 += x07; x13 = crotl32(x13 ^ x02, 16); x08 += x13; x07 = crotl32(x07 ^ x08, 12); x02 += x07; x13 = crotl32(x13 ^ x02, 8); x08 += x13; x07 = crotl32(x07 ^ x08, 7); x03 += x04; x14 = crotl32(x14 ^ x03, 16); x09 += x14; x04 = crotl32(x04 ^ x09, 12); x03 += x04; x14 = crotl32(x14 ^ x03, 8); x09 += x14; x04 = crotl32(x04 ^ x09, 7); } var xup = [ x00, x01, x02, x03, x04, x05, x06, x07, x08, x09, x10, x11, x12, x13, x14, x15 ]; for (var i = 0; i < STATE_SIZE; ++i) { x[i] = csum32(xup[i], input[i]); } } @override dynamic noSuchMethod(Invocation invocation); }