twonly-app-dependencies/pointycastle/lib/stream/chacha20poly1305.dart
2025-12-07 16:10:41 +01:00

503 lines
12 KiB
Dart

library impl.stream_cipher.chacha20poly1305;
import 'dart:math';
import 'dart:typed_data';
import 'package:pointycastle/src/registry/registry.dart';
import '../export.dart';
import '../src/impl/base_aead_cipher.dart';
import '../src/ufixnum.dart';
import '../src/utils.dart' as utils;
// ignore_for_file: non_constant_identifier_names
class ChaCha20Poly1305 extends BaseAEADCipher {
static final FactoryConfig factoryConfig = StaticFactoryConfig(
AEADCipher,
'ChaCha20-Poly1305',
() => ChaCha20Poly1305(ChaCha7539Engine(), Poly1305()));
static const BUF_SIZE = 64;
static const KEY_SIZE = 32;
static const NONCE_SIZE = 12;
static const MAC_SIZE = 16;
static final MAX = pow(2, 63) - 1;
static final MIN = -pow(2, 63);
static final Uint8List ZEROES = Uint8List(MAC_SIZE - 1);
static final int AADLimit = (MAX - MIN).toInt();
static const DATA_LIMIT = (1 << 32 - 1) * 64;
final ChaCha7539Engine chacha20;
final Poly1305 poly1305;
final Uint8List _key = Uint8List(KEY_SIZE);
final Uint8List _nonce = Uint8List(NONCE_SIZE);
final Uint8List _buf = Uint8List(MAC_SIZE + BUF_SIZE);
final Uint8List _mac = Uint8List(MAC_SIZE);
@override
String get algorithmName => 'ChaCha20-Poly1305';
@override
Uint8List get mac => _mac;
Uint8List? _initialAAD;
late int _aadCount;
late int _dataCount;
int _state = State.UNINITIALIZED;
late int _bufPos;
ChaCha20Poly1305(this.chacha20, this.poly1305);
@override
void init(bool forEncryption, CipherParameters params) {
KeyParameter initKeyParam;
Uint8List? initNonce;
CipherParameters chacha20Params;
if (params is AEADParameters) {
var aeadParams = params;
var macSizeBits = aeadParams.macSize;
if ((MAC_SIZE * 8) != macSizeBits) {
throw ArgumentError('Invalid value for MAC size: $macSizeBits');
}
initKeyParam = aeadParams.parameters as KeyParameter;
initNonce = aeadParams.nonce;
chacha20Params = ParametersWithIV(initKeyParam, initNonce);
_initialAAD = aeadParams.associatedData;
} else if (params is ParametersWithIV) {
var ivParams = params;
initKeyParam = ivParams.parameters as KeyParameter;
initNonce = ivParams.iv;
chacha20Params = ivParams;
_initialAAD = null;
} else {
throw ArgumentError('invalid parameters passed to ChaCha20Poly1305');
}
// Validate key
if (KEY_SIZE != initKeyParam.key.length) {
throw ArgumentError('Key must be 256 bits');
}
// Validate nonce
if (NONCE_SIZE != initNonce.length) {
throw ArgumentError('Nonce must be 96 bits');
}
utils.arrayCopy(initKeyParam.key, 0, _key, 0, KEY_SIZE);
utils.arrayCopy(initNonce, 0, _nonce, 0, NONCE_SIZE);
chacha20.init(true, chacha20Params as ParametersWithIV<KeyParameter>);
_state = forEncryption ? State.ENC_INIT : State.DEC_INIT;
resetBool(true, false);
}
@override
// ignore: missing_return
int getOutputSize(int len) {
var total = max(0, len) + _bufPos;
switch (_state) {
case State.DEC_INIT:
case State.DEC_AAD:
case State.DEC_DATA:
return max(0, total - MAC_SIZE);
case State.ENC_INIT:
case State.ENC_AAD:
case State.ENC_DATA:
return total + MAC_SIZE;
default:
throw StateError('state = $_state');
}
}
@override
int getUpdateOutputSize(int len) {
var total = max(0, len) + _bufPos;
switch (_state) {
case State.DEC_INIT:
case State.DEC_AAD:
case State.DEC_DATA:
total = max(0, total - MAC_SIZE);
break;
case State.ENC_INIT:
case State.ENC_AAD:
case State.ENC_DATA:
break;
default:
throw StateError('');
}
return total - (total % BUF_SIZE);
}
@override
void processAADByte(int inp) {
checkAAD();
_aadCount = incrementCount(_aadCount, 1, AADLimit);
poly1305.updateByte(inp);
}
@override
void processAADBytes(Uint8List inp, int inOff, int len) {
if (inOff < 0) {
throw ArgumentError('\'inOff\' cannot be negative');
}
if (len < 0) {
throw ArgumentError('\'len\' cannot be negative');
}
if (inOff > (inp.length - len)) {
throw ArgumentError('Input buffer too short');
}
checkAAD();
if (len > 0) {
_aadCount = incrementCount(_aadCount, len, AADLimit);
poly1305.update(inp, inOff, len);
}
}
@override
int processByte(int inp, Uint8List out, int outOff) {
checkData();
switch (_state) {
case State.DEC_DATA:
{
_buf[_bufPos] = inp;
if (++_bufPos == _buf.length) {
poly1305.update(_buf, 0, BUF_SIZE);
processData(_buf, 0, BUF_SIZE, out, outOff);
utils.arrayCopy(_buf, BUF_SIZE, _buf, 0, MAC_SIZE);
_bufPos = MAC_SIZE;
return BUF_SIZE;
}
return 0;
}
case State.ENC_DATA:
{
_buf[_bufPos] = inp;
if (++_bufPos == BUF_SIZE) {
processData(_buf, 0, BUF_SIZE, out, outOff);
poly1305.update(out, outOff, BUF_SIZE);
_bufPos = 0;
return BUF_SIZE;
}
return 0;
}
default:
throw StateError('');
}
}
@override
int processBytes(
Uint8List inp, int inOff, int len, Uint8List out, int outOff) {
if (inOff < 0) {
throw ArgumentError('\'inOff\' cannot be negative');
}
if (len < 0) {
throw ArgumentError('\'len\' cannot be negative');
}
if (inOff > (inp.length - len)) {
throw ArgumentError('Input buffer too short');
}
if (outOff < 0) {
throw ArgumentError('\'outOff\' cannot be negative');
}
checkData();
var resultLen = 0;
switch (_state) {
case State.DEC_DATA:
{
for (var i = 0; i < len; ++i) {
_buf[_bufPos] = inp[inOff + i];
if (++_bufPos == _buf.length) {
poly1305.update(_buf, 0, BUF_SIZE);
processData(_buf, 0, BUF_SIZE, out, outOff + resultLen);
utils.arrayCopy(_buf, BUF_SIZE, _buf, 0, MAC_SIZE);
_bufPos = MAC_SIZE;
resultLen += BUF_SIZE;
}
}
break;
}
case State.ENC_DATA:
{
if (_bufPos != 0) {
while (len > 0) {
--len;
_buf[_bufPos] = inp[inOff++];
if (++_bufPos == BUF_SIZE) {
processData(_buf, 0, BUF_SIZE, out, outOff);
poly1305.update(out, outOff, BUF_SIZE);
_bufPos = 0;
resultLen = BUF_SIZE;
break;
}
}
}
while (len >= BUF_SIZE) {
processData(inp, inOff, BUF_SIZE, out, outOff + resultLen);
poly1305.update(out, outOff + resultLen, BUF_SIZE);
inOff += BUF_SIZE;
len -= BUF_SIZE;
resultLen += BUF_SIZE;
}
if (len > 0) {
utils.arrayCopy(inp, inOff, _buf, 0, len);
_bufPos = len;
}
break;
}
default:
throw StateError('');
}
return resultLen;
}
@override
int doFinal(Uint8List out, int outOff) {
if (outOff < 0) {
throw ArgumentError('\'outOff\' cannot be negative');
}
checkData();
for (var i = 0; i < _mac.length; i++) {
_mac[i] = 0x00;
}
var resultLen = 0;
switch (_state) {
case State.DEC_DATA:
{
if (_bufPos < MAC_SIZE) {
throw ArgumentError('data too short');
}
resultLen = _bufPos - MAC_SIZE;
if (outOff > (out.length - resultLen)) {
throw ArgumentError('Output buffer too short');
}
if (resultLen > 0) {
poly1305.update(_buf, 0, resultLen);
processData(_buf, 0, resultLen, out, outOff);
}
finishData(State.DEC_FINAL);
if (!utils.constantTimeAreEqualOffset(
MAC_SIZE, _mac, 0, _buf, resultLen)) {
throw ArgumentError('mac check in ChaCha20Poly1305 failed');
}
break;
}
case State.ENC_DATA:
{
resultLen = _bufPos + MAC_SIZE;
// ignore: invariant_booleans
if (outOff > (out.length - resultLen)) {
throw ArgumentError('Output buffer too short');
}
if (_bufPos > 0) {
processData(_buf, 0, _bufPos, out, outOff);
poly1305.update(out, outOff, _bufPos);
}
finishData(State.ENC_FINAL);
utils.arrayCopy(_mac, 0, out, outOff + _bufPos, MAC_SIZE);
break;
}
default:
throw StateError('');
}
resetBool(false, true);
return resultLen;
}
@override
void reset() {
resetBool(true, true);
}
void resetBool(bool clearMac, bool resetCipher) {
for (var i = 0; i < _buf.length; i++) {
_buf[i] = 0;
}
if (clearMac) {
for (var i = 0; i < _mac.length; i++) {
_mac[i] = 0x00;
}
}
_aadCount = 0;
_dataCount = 0;
_bufPos = 0;
switch (_state) {
case State.DEC_INIT:
case State.ENC_INIT:
break;
case State.DEC_AAD:
case State.DEC_DATA:
case State.DEC_FINAL:
_state = State.DEC_INIT;
break;
case State.ENC_AAD:
case State.ENC_DATA:
case State.ENC_FINAL:
_state = State.ENC_FINAL;
return;
default:
throw StateError('');
}
if (resetCipher) {
chacha20.reset();
}
initMAC();
if (_initialAAD != null) {
processAADBytes(_initialAAD!, 0, _initialAAD!.length);
}
}
void checkAAD() {
switch (_state) {
case State.DEC_INIT:
_state = State.DEC_AAD;
break;
case State.ENC_INIT:
_state = State.ENC_AAD;
break;
case State.DEC_AAD:
case State.ENC_AAD:
break;
case State.ENC_FINAL:
throw StateError('ChaCha20Poly1305 cannot be reused for encryption');
default:
throw StateError('');
}
}
void checkData() {
switch (_state) {
case State.DEC_INIT:
case State.DEC_AAD:
finishAAD(State.DEC_DATA);
break;
case State.ENC_INIT:
case State.ENC_AAD:
finishAAD(State.ENC_DATA);
break;
case State.DEC_DATA:
case State.ENC_DATA:
break;
case State.ENC_FINAL:
throw StateError('ChaCha20Poly1305 cannot be reused for encryption');
default:
throw StateError('');
}
}
void finishAAD(int nextState) {
padMAC(_aadCount);
_state = nextState;
}
void finishData(int nextState) {
padMAC(_dataCount);
var lengths = Uint8List(16);
pack32(_aadCount, lengths, 0, Endian.little);
pack32(_dataCount, lengths, 8, Endian.little);
poly1305.update(lengths, 0, 16);
poly1305.doFinal(_mac, 0);
_state = nextState;
}
int incrementCount(int count, int increment, int limit) {
if (count + MIN > (limit - increment) + MIN) {
throw StateError('Limit exceeded');
}
return count + increment;
}
void initMAC() {
var firstBlock = Uint8List(64);
try {
chacha20.processBytes(firstBlock, 0, 64, firstBlock, 0);
poly1305.init(KeyParameter.offset(firstBlock, 0, 32));
} finally {
for (var i = 0; i < firstBlock.length; i++) {
firstBlock[i] = 0;
}
}
}
void padMAC(int count) {
var partial = (count & 4294967295) & (MAC_SIZE - 1);
if (0 != partial) {
poly1305.update(ZEROES, 0, MAC_SIZE - partial);
}
}
void processData(
Uint8List inp, int inOff, int inLen, Uint8List out, int outOff) {
if (outOff > (out.length - inLen)) {
throw ArgumentError('Output buffer too short');
}
chacha20.processBytes(inp, inOff, inLen, out, outOff);
_dataCount = incrementCount(_dataCount, inLen, DATA_LIMIT);
}
}
class State {
static const UNINITIALIZED = 0;
static const ENC_INIT = 1;
static const ENC_AAD = 2;
static const ENC_DATA = 3;
static const ENC_FINAL = 4;
static const DEC_INIT = 5;
static const DEC_AAD = 6;
static const DEC_DATA = 7;
static const DEC_FINAL = 8;
}