| // See file LICENSE for more information. |
| |
| library impl.stream_cipher.eax; |
| |
| import 'dart:core'; |
| import 'dart:math'; |
| import 'dart:typed_data'; |
| |
| import 'package:pointycastle/api.dart'; |
| import 'package:pointycastle/macs/cmac.dart'; |
| import 'package:pointycastle/src/impl/base_aead_cipher.dart'; |
| import 'package:pointycastle/src/registry/registry.dart'; |
| import 'package:pointycastle/src/utils.dart'; |
| import 'package:pointycastle/stream/ctr.dart'; |
| |
| /// EAX mode based on CTR and CMAC/OMAC1. |
| /// |
| /// Encrypts plaintext and outputs the ciphertext with the concatenated mac. |
| /// Decrypts and verifies ciphertext with the concatenated mac and returns the plaintext. |
| /// Ported from BouncyCastle's Java impl: https://github.com/bcgit/bc-java/blob/master/core/src/main/java/org/bouncycastle/crypto/modes/EAXBlockCipher.java |
| class EAX extends BaseAEADCipher { |
| static final FactoryConfig factoryConfig = DynamicFactoryConfig.suffix( |
| AEADCipher, |
| '/EAX', |
| (_, final Match match) => () { |
| var digestName = match.group(1); |
| return EAX(BlockCipher(digestName!)); |
| }); |
| |
| static const _nonceTAG = 0x0; |
| static const _aadTAG = 0x1; |
| static const _cipherTAG = 0x2; |
| |
| final CTRStreamCipher _ctr; |
| final CMac _cMac; |
| late bool _forEncryption; |
| late KeyParameter _keyParam; |
| late CipherParameters _initParams; |
| late Uint8List _nonceMac; |
| late Uint8List _aadMac; |
| late Uint8List _cipherMac; |
| late Uint8List _bufBlock; |
| late int _bufOff; |
| late bool _bufFull; |
| late bool _aadFinished; |
| |
| int get _blockSize => underlyingCipher.blockSize; |
| |
| /// The cipher used in CTR and CMAC |
| final BlockCipher underlyingCipher; |
| |
| int _macSize; |
| |
| /// The byte size that the [mac] calculated by [doFinal] must be. |
| int get macSize => _macSize; |
| |
| /// The MAC (also known as Tag), calculated and cached by [doFinal]. |
| /// |
| /// Will not be cleared on [reset] or [init]. |
| @override |
| late Uint8List mac; |
| |
| @override |
| String get algorithmName => '${underlyingCipher.algorithmName}/EAX'; |
| |
| EAX(this.underlyingCipher) |
| : _ctr = CTRStreamCipher(underlyingCipher), |
| _cMac = CMac(underlyingCipher, underlyingCipher.blockSize * 8), |
| _macSize = underlyingCipher.blockSize ~/ 2; |
| |
| /// Initializes this for addition of AAD and en/decryption of data. |
| @override |
| void init(bool forEncryption, CipherParameters params) { |
| _forEncryption = forEncryption; |
| _initParams = params; |
| Uint8List initNonce; |
| Uint8List? initAAD; |
| |
| if (params is AEADParameters) { |
| _macSize = params.macSize ~/ 8; |
| _keyParam = params.parameters as KeyParameter; |
| initNonce = params.nonce; |
| initAAD = params.associatedData; |
| } else if (params is ParametersWithIV) { |
| _keyParam = params.parameters as KeyParameter; |
| initNonce = params.iv; |
| } else { |
| throw ArgumentError( |
| '${params.runtimeType} is not ParametersWithIV or AEADParameters', |
| 'params'); |
| } |
| |
| _nonceMac = Uint8List(_blockSize); |
| _cMac |
| ..init(_keyParam) |
| ..update( |
| Uint8List(_blockSize)..[_blockSize - 1] = _nonceTAG, 0, _blockSize) |
| ..update(initNonce, 0, initNonce.length) |
| ..doFinal(_nonceMac, 0); |
| |
| _aadFinished = false; |
| _aadMac = Uint8List(_blockSize); |
| _cMac |
| ..init(_keyParam) |
| ..update( |
| Uint8List(_blockSize)..[_blockSize - 1] = _aadTAG, 0, _blockSize); |
| if (initAAD != null) processAADBytes(initAAD, 0, initAAD.length); |
| |
| _cipherMac = Uint8List(_blockSize); |
| _ctr.init(_forEncryption, ParametersWithIV(_keyParam, _nonceMac)); |
| |
| _bufBlock = Uint8List(_macSize); |
| _bufOff = 0; |
| _bufFull = false; |
| } |
| |
| /// Calculates, caches and if used as decrypter also verifies this [mac], |
| /// calls [reset] and returns the number of bytes written. |
| @override |
| int doFinal(Uint8List out, int outOff) { |
| _cMac.doFinal(_cipherMac, 0); |
| _calculateMac(); |
| |
| if (_forEncryption) { |
| if (out.length < outOff + _macSize) { |
| throw ArgumentError( |
| 'actual length: ${out.length}, ' |
| 'min: ${outOff + _macSize}', |
| 'out'); |
| } |
| out.setAll(outOff, mac); |
| |
| reset(); |
| return _macSize; |
| } else { |
| if (!_bufFull) { |
| throw StateError('Did not process enough data ' |
| 'for MAC to be collected from input.'); |
| } |
| if (!_verifyMac(_inMac, 0)) { |
| throw StateError('MAC does not match.'); |
| } |
| |
| reset(); |
| return 0; |
| } |
| } |
| |
| /// Initializes this with the parameters last given to [init]. |
| @override |
| void reset() { |
| init(_forEncryption, _initParams); |
| } |
| |
| /// Processes further AAD. |
| /// |
| /// Must not be used when en-/decryption of data had begun. |
| @override |
| void processAADByte(int inp) { |
| if (_aadFinished) { |
| throw StateError('Must not be used when en-/decryption ' |
| 'of data had begun.'); |
| } |
| _cMac.updateByte(inp); |
| } |
| |
| /// Processes further AAD. |
| /// |
| /// Must not be used after en-/decryption of data has begun. |
| @override |
| void processAADBytes(Uint8List inp, int inpOff, int len) { |
| if (_aadFinished) { |
| throw StateError('Must not be used when en-/decryption ' |
| 'of data had begun.'); |
| } |
| _cMac.update(inp, inpOff, len); |
| } |
| |
| /// Processes the input and returns the amount of bytes written. |
| @override |
| int processByte(int inp, Uint8List out, int outOff) { |
| _ensureAadMacFinished(); |
| |
| var r = out[outOff] = _ctr.returnByte(inp); |
| |
| if (_forEncryption) { |
| _cMac.updateByte(r); |
| return 1; |
| } |
| return _bufByte(inp); |
| } |
| |
| /// Processes the input and returns the amount of bytes written. |
| @override |
| int processBytes( |
| Uint8List inp, int inOff, int len, Uint8List out, int outOff) { |
| _ensureAadMacFinished(); |
| |
| if (_forEncryption) { |
| _ctr.processBytes(inp, inOff, len, out, outOff); |
| _cMac.update(out, outOff, len); |
| return len; |
| } else { |
| return _buf(inp, inOff, len, out, outOff); |
| } |
| } |
| |
| /// Returns the amount of bytes being outputted |
| /// by the next [processBytes] and [doFinal] for [len] bytes input. |
| @override |
| int getOutputSize(int len) { |
| if (_forEncryption) { |
| return len + _macSize; |
| } else if (_bufFull) { |
| return len; |
| } else { |
| return max(0, len + _bufOff - _macSize); |
| } |
| } |
| |
| /// Returns the amount of bytes being outputted |
| /// by the next [processBytes] for [len] bytes input. |
| @override |
| int getUpdateOutputSize(int len) { |
| if (_forEncryption) { |
| return len; |
| } else if (_bufFull) { |
| return len; |
| } else { |
| return max(0, len + _bufOff - _macSize); |
| } |
| } |
| |
| /// Returns true if provided [match] is equal to this [mac]. |
| bool _verifyMac(Uint8List match, int off) { |
| return constantTimeAreEqualOffset(_macSize, mac, 0, match, off); |
| } |
| |
| void _ensureAadMacFinished() { |
| if (!_aadFinished) { |
| _cMac |
| ..doFinal(_aadMac, 0) |
| ..init(_keyParam) |
| ..update(Uint8List(_blockSize)..[_blockSize - 1] = _cipherTAG, 0, |
| _blockSize); |
| _aadFinished = true; |
| } |
| } |
| |
| Uint8List get _inMac { |
| var inMac = Uint8List(_macSize); |
| for (var i = 0; i < _macSize; i++) { |
| inMac[i] = _bufBlock[_bufOff + i]; |
| } |
| return inMac; |
| } |
| |
| int _buf(Uint8List inp, int inOff, int len, Uint8List out, int outOff) { |
| var macLen = min(_macSize, len); |
| var macOff = inOff + len - macLen; |
| var processed = len - macLen; |
| |
| _cMac.update(inp, inOff, processed); |
| _ctr.processBytes(inp, inOff, processed, out, outOff); |
| |
| for (var i = 0; i < macLen; i++) { |
| if (_bufFull) { |
| _cMac.updateByte(_bufBlock[_bufOff]); |
| out[outOff + processed + i] = _ctr.returnByte(_bufBlock[_bufOff]); |
| processed++; |
| } |
| |
| _bufBlock[_bufOff] = inp[macOff + i]; |
| |
| if (_bufOff == _macSize - 1) { |
| _bufOff = 0; |
| _bufFull = true; |
| } else { |
| _bufOff++; |
| } |
| } |
| return processed; |
| } |
| |
| int _bufByte(int inp) { |
| var r = 1; |
| if (_bufFull) { |
| _cMac.updateByte(_bufBlock[_bufOff]); |
| _ctr.returnByte(_bufBlock[_bufOff]); |
| } else { |
| r = 0; |
| } |
| |
| _bufBlock[_bufOff] = inp; |
| |
| if (_bufOff == 15) { |
| _bufOff = 0; |
| _bufFull = true; |
| } else { |
| _bufOff++; |
| } |
| return r; |
| } |
| |
| void _calculateMac() { |
| mac = Uint8List(_macSize); |
| for (var i = 0; i < _macSize; i++) { |
| mac[i] = _nonceMac[i] ^ _aadMac[i] ^ _cipherMac[i]; |
| } |
| } |
| } |