// See file LICENSE for more information. library impl.mac.cbc_block_cipher_mac; import 'dart:typed_data'; import 'package:pointycastle/api.dart'; import 'package:pointycastle/src/registry/registry.dart'; import 'package:pointycastle/src/impl/base_mac.dart'; import 'package:pointycastle/block/modes/cbc.dart'; /// standard CBC Block Cipher MAC - if no padding is specified the default of /// pad of zeroes is used. class CBCBlockCipherMac extends BaseMac { static final FactoryConfig factoryConfig = DynamicFactoryConfig.regex( Mac, r'^(.+)/CBC_CMAC(/(.+))?$', (_, final Match match) => () { var cipher = BlockCipher(match.group(1)!); var padding = match.groupCount >= 3 && match.group(3) != null && match.group(3)!.isNotEmpty ? Padding(match.group(3)!) : null; return CBCBlockCipherMac.fromCipherAndPadding(cipher, padding); }, ); late Uint8List _mac; late Uint8List _buf; late int _bufOff; final BlockCipher _cipher; final Padding? _padding; final int _macSize; ParametersWithIV? _params; /// /// create a standard MAC based on a CBC block cipher. This will produce an /// authentication code half the length of the block size of the cipher. /// /// * [cipher] the cipher to be used as the basis of the MAC generation. CBCBlockCipherMac.fromCipher(BlockCipher cipher) : this(cipher, (cipher.blockSize * 8) ~/ 2, null); /// /// create a standard MAC based on a CBC block cipher. This will produce an /// authentication code half the length of the block size of the cipher. /// /// * [cipher] the cipher to be used as the basis of the MAC generation. /// * [padding] the padding to be used to complete the last block. CBCBlockCipherMac.fromCipherAndPadding(BlockCipher cipher, Padding? padding) : this(cipher, (cipher.blockSize * 8) ~/ 2, padding); /// /// create a standard MAC based on a block cipher with the size of the /// MAC been given in bits. This class uses CBC mode as the basis for the /// MAC generation. /// /// Note: the size of the MAC must be at least 24 bits (FIPS Publication 81), /// or 16 bits if being used as a data authenticator (FIPS Publication 113), /// and in general should be less than the size of the block cipher as it /// reduces the chance of an exhaustive attack (see Handbook of Applied /// Cryptography). /// /// * [cipher] the cipher to be used as the basis of the MAC generation. /// * [macSizeInBits] the size of the MAC in bits, must be a multiple of 8. CBCBlockCipherMac.fromCipherAndMacSize(BlockCipher cipher, int macSizeInBits) : this(cipher, macSizeInBits, null); /// /// create a standard MAC based on a block cipher with the size of the /// MAC been given in bits. This class uses CBC mode as the basis for the /// MAC generation. /// /// Note: the size of the MAC must be at least 24 bits (FIPS Publication 81), /// or 16 bits if being used as a data authenticator (FIPS Publication 113), /// and in general should be less than the size of the block cipher as it /// reduces the chance of an exhaustive attack (see Handbook of Applied /// Cryptography). /// /// * [cipher] the cipher to be used as the basis of the MAC generation. /// * [macSizeInBits] the size of the MAC in bits, must be a multiple of 8. /// * [padding] the padding to be used to complete the last block. CBCBlockCipherMac(BlockCipher cipher, int macSizeInBits, Padding? padding) : _cipher = CBCBlockCipher(cipher), _macSize = macSizeInBits ~/ 8, _padding = padding { if ((macSizeInBits % 8) != 0) { throw ArgumentError('MAC size must be multiple of 8'); } _mac = Uint8List(cipher.blockSize); _buf = Uint8List(cipher.blockSize); _bufOff = 0; } @override String get algorithmName { var paddingName = _padding != null ? '/${_padding.algorithmName}' : ''; return '${_cipher.algorithmName}_CMAC$paddingName'; } @override void init(CipherParameters params) { if (params is ParametersWithIV) { _params = params; } else if (params is KeyParameter) { final zeroIV = Uint8List(params.key.length); _params = ParametersWithIV(params, zeroIV); } reset(); _cipher.init(true, _params); } @override int get macSize => _macSize; @override void updateByte(int inp) { if (_bufOff == _buf.length) { _cipher.processBlock(_buf, 0, _mac, 0); _bufOff = 0; } _buf[_bufOff++] = inp; } @override void update(Uint8List inp, int inOff, int len) { if (len < 0) { throw ArgumentError('Can\'t have a negative input length!'); } var blockSize = _cipher.blockSize; var gapLen = blockSize - _bufOff; if (len > gapLen) { _buf.setRange(_bufOff, _bufOff + gapLen, inp.sublist(inOff)); _cipher.processBlock(_buf, 0, _mac, 0); _bufOff = 0; len -= gapLen; inOff += gapLen; while (len > blockSize) { _cipher.processBlock(inp, inOff, _mac, 0); len -= blockSize; inOff += blockSize; } } _buf.setRange(_bufOff, _bufOff + len, inp.sublist(inOff)); _bufOff += len; } /// Reset the mac generator. @override void reset() { // clean the buffer. for (var i = 0; i < _buf.length; i++) { _buf[i] = 0; } _bufOff = 0; // reset the underlying cipher. _cipher.reset(); _cipher.init(true, _params); if (_params != null) { _cipher.init(true, _params); } } @override int doFinal(Uint8List out, int outOff) { var blockSize = _cipher.blockSize; if (_padding == null) { // // pad with zeroes // while (_bufOff < blockSize) { _buf[_bufOff] = 0; _bufOff++; } } else { if (_bufOff == blockSize) { _cipher.processBlock(_buf, 0, _mac, 0); _bufOff = 0; } _padding.addPadding(_buf, _bufOff); } _cipher.processBlock(_buf, 0, _mac, 0); out.setRange(outOff, outOff + _macSize, _mac); reset(); return _macSize; } }