| // See file LICENSE for more information. |
| |
| library impl.block_cipher.modes.cbc; |
| |
| import 'dart:typed_data'; |
| |
| import 'package:pointycastle/api.dart'; |
| import 'package:pointycastle/src/registry/registry.dart'; |
| import 'package:pointycastle/src/impl/base_block_cipher.dart'; |
| |
| /// Implementation of Cipher-Block-Chaining (CBC) mode on top of a [BlockCipher]. |
| class CBCBlockCipher extends BaseBlockCipher { |
| /// Intended for internal use. |
| static final FactoryConfig factoryConfig = DynamicFactoryConfig.suffix( |
| BlockCipher, |
| '/CBC', |
| (_, final Match match) => () { |
| var underlying = BlockCipher(match.group(1)!); |
| return CBCBlockCipher(underlying); |
| }); |
| |
| final BlockCipher _underlyingCipher; |
| |
| late Uint8List _iv; |
| Uint8List? _cbcV; |
| Uint8List? _cbcNextV; |
| |
| late bool _encrypting; |
| |
| CBCBlockCipher(this._underlyingCipher) { |
| _iv = Uint8List(blockSize); |
| _cbcV = Uint8List(blockSize); |
| _cbcNextV = Uint8List(blockSize); |
| } |
| |
| @override |
| String get algorithmName => '${_underlyingCipher.algorithmName}/CBC'; |
| @override |
| int get blockSize => _underlyingCipher.blockSize; |
| |
| @override |
| void reset() { |
| _cbcV!.setAll(0, _iv); |
| _cbcNextV!.fillRange(0, _cbcNextV!.length, 0); |
| |
| _underlyingCipher.reset(); |
| } |
| |
| @override |
| void init(bool forEncryption, covariant ParametersWithIV params) { |
| if (params.iv.length != blockSize) { |
| throw ArgumentError( |
| 'Initialization vector must be the same length as block size'); |
| } |
| |
| _encrypting = forEncryption; |
| _iv.setAll(0, params.iv); |
| |
| reset(); |
| |
| _underlyingCipher.init(forEncryption, params.parameters); |
| } |
| |
| @override |
| int processBlock(Uint8List inp, int inpOff, Uint8List out, int outOff) => |
| _encrypting |
| ? _encryptBlock(inp, inpOff, out, outOff) |
| : _decryptBlock(inp, inpOff, out, outOff); |
| |
| int _encryptBlock(Uint8List inp, int inpOff, Uint8List out, int outOff) { |
| if ((inpOff + blockSize) > inp.length) { |
| throw ArgumentError('Input buffer too short'); |
| } |
| |
| // XOR the cbcV and the input, then encrypt the cbcV |
| for (var i = 0; i < blockSize; i++) { |
| _cbcV![i] ^= inp[inpOff + i]; |
| } |
| |
| var length = _underlyingCipher.processBlock(_cbcV!, 0, out, outOff); |
| |
| // copy ciphertext to cbcV |
| _cbcV!.setRange(0, blockSize, |
| Uint8List.view(out.buffer, out.offsetInBytes + outOff, blockSize)); |
| |
| return length; |
| } |
| |
| int _decryptBlock(Uint8List inp, int inpOff, Uint8List out, int outOff) { |
| if ((inpOff + blockSize) > inp.length) { |
| throw ArgumentError('Input buffer too short'); |
| } |
| |
| _cbcNextV!.setRange(0, blockSize, |
| Uint8List.view(inp.buffer, inp.offsetInBytes + inpOff, blockSize)); |
| |
| var length = _underlyingCipher.processBlock(inp, inpOff, out, outOff); |
| |
| // XOR the cbcV and the output |
| for (var i = 0; i < blockSize; i++) { |
| out[outOff + i] ^= _cbcV![i]; |
| } |
| |
| // swap the back up buffer into next position |
| Uint8List? tmp; |
| |
| tmp = _cbcV; |
| _cbcV = _cbcNextV; |
| _cbcNextV = tmp; |
| |
| return length; |
| } |
| } |