| // 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 |
| /// <p> |
| /// CMAC is analogous to OMAC1 - see also en.wikipedia.org/wiki/CMAC |
| /// </p><p> |
| /// CMAC is a NIST recomendation - see |
| /// csrc.nist.gov/CryptoToolkit/modes/800-38_Series_Publications/SP800-38B.pdf |
| /// </p><p> |
| /// CMAC/OMAC1 is a blockcipher-based message authentication code designed and |
| /// analyzed by Tetsu Iwata and Kaoru Kurosawa. |
| /// </p><p> |
| /// CMAC/OMAC1 is a simple variant of the CBC MAC (Cipher Block Chaining Message |
| /// Authentication Code). OMAC stands for One-Key CBC MAC. |
| /// </p><p> |
| /// 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. |
| /// </p> |
| 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. |
| /// <p> |
| /// 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!); |
| } |
| } |
| } |