287 lines
7 KiB
Dart
287 lines
7 KiB
Dart
// 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!);
|
|
}
|
|
}
|
|
}
|