blob: 8ebba7e7b7966f5335297fc6077f6a79366120a6 [file] [log] [blame] [edit]
// See file LICENSE for more information.
library impl.block_cipher.modes.gctr;
import 'dart:typed_data';
import 'package:pointycastle/api.dart';
import 'package:pointycastle/src/impl/base_block_cipher.dart';
import 'package:pointycastle/src/registry/registry.dart';
import 'package:pointycastle/src/ufixnum.dart';
/// Implementation of GOST 28147 OFB counter mode (GCTR) on top of a [BlockCipher].
class GCTRBlockCipher extends BaseBlockCipher {
/// Intended for internal use.
static final FactoryConfig factoryConfig = DynamicFactoryConfig.suffix(
BlockCipher,
'/GCTR',
(_, final Match match) => () {
var underlying = BlockCipher(match.group(1)!);
return GCTRBlockCipher(underlying);
});
static const C1 = 16843012; //00000001000000010000000100000100
static const C2 = 16843009; //00000001000000010000000100000001
final BlockCipher _underlyingCipher;
late Uint8List _iv;
Uint8List? _ofbV;
Uint8List? _ofbOutV;
bool _firstStep = true;
late int _n3;
late int _n4;
GCTRBlockCipher(this._underlyingCipher) {
if (blockSize != 8) {
throw ArgumentError('GCTR can only be used with 64 bit block ciphers');
}
_iv = Uint8List(_underlyingCipher.blockSize);
_ofbV = Uint8List(_underlyingCipher.blockSize);
_ofbOutV = Uint8List(_underlyingCipher.blockSize);
}
@override
int get blockSize => _underlyingCipher.blockSize;
@override
String get algorithmName => '${_underlyingCipher.algorithmName}/GCTR';
@override
void reset() {
_ofbV!.setRange(0, _iv.length, _iv);
_underlyingCipher.reset();
}
/// Initialise the cipher and, possibly, the initialisation vector (IV).
/// If an IV isn't passed as part of the parameter, the IV will be all zeros.
/// An IV which is too short is handled in FIPS compliant fashion.
///
/// @param encrypting if true the cipher is initialised for
/// encryption, if false for decryption. //ignored by this CTR mode
/// @param params the key and other data required by the cipher.
/// @exception IllegalArgumentException if the params argument is
/// inappropriate.
@override
void init(bool encrypting, CipherParameters? params) {
_firstStep = true;
_n3 = 0;
_n4 = 0;
if (params is ParametersWithIV) {
var ivParam = params;
var iv = ivParam.iv;
if (iv.length < _iv.length) {
// prepend the supplied IV with zeros (per FIPS PUB 81)
var offset = _iv.length - iv.length;
_iv.fillRange(0, offset, 0);
_iv.setRange(offset, _iv.length, iv);
} else {
_iv.setRange(0, _iv.length, iv);
}
reset();
// if params is null we reuse the current working key.
if (ivParam.parameters != null) {
_underlyingCipher.init(true, ivParam.parameters);
}
} else {
// TODO: make this behave in a standard way (as the other modes of operation)
reset();
// if params is null we reuse the current working key.
if (params != null) {
_underlyingCipher.init(true, params);
}
}
}
@override
int processBlock(Uint8List inp, int inpOff, Uint8List out, int outOff) {
if ((inpOff + blockSize) > inp.length) {
throw ArgumentError('Input buffer too short');
}
if ((outOff + blockSize) > out.length) {
throw ArgumentError('Output buffer too short');
}
if (_firstStep) {
_firstStep = false;
_underlyingCipher.processBlock(_ofbV!, 0, _ofbOutV!, 0);
_n3 = _bytesToint(_ofbOutV, 0);
_n4 = _bytesToint(_ofbOutV, 4);
}
_n3 += C2;
_n4 += C1;
_intTobytes(_n3, _ofbV, 0);
_intTobytes(_n4, _ofbV, 4);
_underlyingCipher.processBlock(_ofbV!, 0, _ofbOutV!, 0);
// XOR the ofbV with the plaintext producing the cipher text (and the next input block).
for (var i = 0; i < blockSize; i++) {
out[outOff + i] = _ofbOutV![i] ^ inp[inpOff + i];
}
// change over the input block.
var offset = _ofbV!.length - blockSize;
_ofbV!.setRange(0, offset, _ofbV!.sublist(blockSize));
_ofbV!.setRange(offset, _ofbV!.length, _ofbOutV!);
return blockSize;
}
int _bytesToint(Uint8List? inp, int inpOff) {
return unpack32(inp, inpOff, Endian.little);
}
void _intTobytes(int num, Uint8List? out, int outOff) {
pack32(num, out, outOff, Endian.little);
}
}