// See file LICENSE for more information. library impl.mac.cmac; 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/paddings/iso7816d4.dart'; import 'package:pointycastle/block/modes/cbc.dart'; /// CMAC - as specified at www.nuee.nagoya-u.ac.jp/labs/tiwata/omac/omac.html ///

/// CMAC is analogous to OMAC1 - see also en.wikipedia.org/wiki/CMAC ///

/// CMAC is a NIST recomendation - see /// csrc.nist.gov/CryptoToolkit/modes/800-38_Series_Publications/SP800-38B.pdf ///

/// CMAC/OMAC1 is a blockcipher-based message authentication code designed and /// analyzed by Tetsu Iwata and Kaoru Kurosawa. ///

/// CMAC/OMAC1 is a simple variant of the CBC MAC (Cipher Block Chaining Message /// Authentication Code). OMAC stands for One-Key CBC MAC. ///

/// It supports 128- or 64-bits block ciphers, with any key size, and returns /// a MAC with dimension less or equal to the block size of the underlying /// cipher. ///

class CMac extends BaseMac { static final FactoryConfig factoryConfig = DynamicFactoryConfig.suffix( Mac, '/CMAC', (_, final Match match) => () { var cipher = BlockCipher(match.group(1)!); return CMac.fromCipher(cipher); }, ); late Uint8List _poly; late Uint8List _zeros; late Uint8List _mac; late Uint8List _buf; late int _bufOff; final BlockCipher _cipher; final int _macSize; late Uint8List _lu, _lu2; ParametersWithIV? _params; /// /// create a standard MAC based on a CBC block cipher (64 or 128 bit block). /// This will produce an authentication code the length of the block size /// of the cipher. /// /// @param cipher the cipher to be used as the basis of the MAC generation. CMac.fromCipher(BlockCipher cipher) : this(cipher, cipher.blockSize * 8); /// /// create a standard MAC based on a block cipher with the size of the /// MAC been given in bits. ///

/// 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). /// /// @param cipher the cipher to be used as the basis of the MAC generation. /// @param macSizeInBits the size of the MAC in bits, must be a multiple of 8 and <= 128. CMac(BlockCipher cipher, int macSizeInBits) : _macSize = macSizeInBits ~/ 8, _cipher = CBCBlockCipher(cipher) { if ((macSizeInBits % 8) != 0) { throw ArgumentError('MAC size must be multiple of 8'); } if (macSizeInBits > (_cipher.blockSize * 8)) { throw ArgumentError( 'MAC size must be less or equal to ${_cipher.blockSize * 8}'); } _poly = lookupPoly(cipher.blockSize); _mac = Uint8List(cipher.blockSize); _buf = Uint8List(cipher.blockSize); _zeros = Uint8List(cipher.blockSize); _bufOff = 0; } @override String get algorithmName { var blockCipherAlgorithmName = _cipher.algorithmName.split('/').first; return '$blockCipherAlgorithmName/CMAC'; } static int shiftLeft(Uint8List block, Uint8List output) { var i = block.length; var bit = 0; while (--i >= 0) { var b = block[i] & 0xff; output[i] = (b << 1) | bit; bit = (b >> 7) & 1; } return bit; } Uint8List _doubleLu(Uint8List inp) { var ret = Uint8List(inp.length); var carry = shiftLeft(inp, ret); // NOTE: This construction is an attempt at a constant-time implementation. var mask = (-carry) & 0xff; ret[inp.length - 3] ^= _poly[1] & mask; ret[inp.length - 2] ^= _poly[2] & mask; ret[inp.length - 1] ^= _poly[3] & mask; return ret; } static Uint8List lookupPoly(int blockSizeLength) { int xor; switch (blockSizeLength * 8) { case 64: xor = 0x1B; break; case 128: xor = 0x87; break; case 160: xor = 0x2D; break; case 192: xor = 0x87; break; case 224: xor = 0x309; break; case 256: xor = 0x425; break; case 320: xor = 0x1B; break; case 384: xor = 0x100D; break; case 448: xor = 0x851; break; case 512: xor = 0x125; break; case 768: xor = 0xA0011; break; case 1024: xor = 0x80043; break; case 2048: xor = 0x86001; break; default: throw ArgumentError( 'Unknown block size for CMAC: ${blockSizeLength * 8}'); } final out = Uint8List(4); out[3] = xor >> 0; out[2] = xor >> 8; out[1] = xor >> 16; out[0] = xor >> 24; return out; } @override void init(covariant KeyParameter keyParams) { final zeroIV = Uint8List(keyParams.key.length); _params = ParametersWithIV(keyParams, zeroIV); // Initialize before computing L, Lu, Lu2 _cipher.init(true, _params!); //initializes the L, Lu, Lu2 numbers var L = Uint8List(_zeros.length); _cipher.processBlock(_zeros, 0, L, 0); _lu = _doubleLu(L); _lu2 = _doubleLu(_lu); // Reset _buf/_cipher state after computing L, Lu, Lu2 reset(); } @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; } @override int doFinal(Uint8List out, int outOff) { var blockSize = _cipher.blockSize; Uint8List? lu; if (_bufOff == blockSize) { lu = _lu; } else { ISO7816d4Padding().addPadding(_buf, _bufOff); lu = _lu2; } for (var i = 0; i < _mac.length; i++) { _buf[i] ^= lu[i]; } _cipher.processBlock(_buf, 0, _mac, 0); out.setRange(outOff, outOff + _macSize, _mac); reset(); return _macSize; } /// 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(); if (_params != null) { _cipher.init(true, _params!); } } }