blob: c3170801e6b596c3feea66989fc4cd8214cdf665 [file] [log] [blame]
// 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 &lt;= 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!);
}
}
}