195 lines
5.6 KiB
Dart
195 lines
5.6 KiB
Dart
// See file LICENSE for more information.
|
|
|
|
library impl.stream_cipher.salsa20;
|
|
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:pointycastle/api.dart';
|
|
import 'package:pointycastle/src/impl/base_stream_cipher.dart';
|
|
import 'package:pointycastle/src/registry/registry.dart';
|
|
import 'package:pointycastle/src/ufixnum.dart';
|
|
|
|
/// Implementation of Daniel J. Bernstein's Salsa20 stream cipher, Snuffle 2005.
|
|
class Salsa20Engine extends BaseStreamCipher {
|
|
static final FactoryConfig factoryConfig =
|
|
StaticFactoryConfig(StreamCipher, 'Salsa20', () => Salsa20Engine());
|
|
|
|
static const _STATE_SIZE = 16;
|
|
|
|
static final _sigma = Uint8List.fromList('expand 32-byte k'.codeUnits);
|
|
static final _tau = Uint8List.fromList('expand 16-byte k'.codeUnits);
|
|
|
|
Uint8List? _workingKey;
|
|
late Uint8List _workingIV;
|
|
|
|
final _state = List<int>.filled(_STATE_SIZE, 0, growable: false);
|
|
final _buffer = List<int>.filled(_STATE_SIZE, 0, growable: false);
|
|
|
|
final _keyStream = Uint8List(_STATE_SIZE * 4);
|
|
var _keyStreamOffset = 0;
|
|
|
|
var _initialised = false;
|
|
|
|
@override
|
|
final String algorithmName = 'Salsa20';
|
|
|
|
@override
|
|
void reset() {
|
|
if (_workingKey != null) {
|
|
_setKey(_workingKey!, _workingIV);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void init(
|
|
bool forEncryption, covariant ParametersWithIV<KeyParameter> params) {
|
|
var uparams = params.parameters;
|
|
var iv = params.iv;
|
|
if (iv.length != 8) {
|
|
throw ArgumentError('Salsa20 requires exactly 8 bytes of IV');
|
|
}
|
|
|
|
_workingIV = iv;
|
|
_workingKey = uparams!.key;
|
|
|
|
_setKey(_workingKey!, _workingIV);
|
|
}
|
|
|
|
@override
|
|
int returnByte(int inp) {
|
|
if (_keyStreamOffset == 0) {
|
|
_generateKeyStream(_keyStream);
|
|
|
|
if (++_state[8] == 0) {
|
|
++_state[9];
|
|
}
|
|
}
|
|
|
|
var out = clip8(_keyStream[_keyStreamOffset] ^ inp);
|
|
_keyStreamOffset = (_keyStreamOffset + 1) & 63;
|
|
|
|
return out;
|
|
}
|
|
|
|
@override
|
|
void processBytes(
|
|
Uint8List? inp, int inpOff, int len, Uint8List? out, int outOff) {
|
|
if (!_initialised) {
|
|
throw StateError('Salsa20 not initialized: please call init() first');
|
|
}
|
|
|
|
if ((inpOff + len) > inp!.length) {
|
|
throw ArgumentError(
|
|
'Input buffer too short or requested length too long');
|
|
}
|
|
|
|
if ((outOff + len) > out!.length) {
|
|
throw ArgumentError(
|
|
'Output buffer too short or requested length too long');
|
|
}
|
|
|
|
for (var i = 0; i < len; i++) {
|
|
if (_keyStreamOffset == 0) {
|
|
_generateKeyStream(_keyStream);
|
|
|
|
if (++_state[8] == 0) {
|
|
++_state[9];
|
|
}
|
|
}
|
|
|
|
out[i + outOff] = clip8(_keyStream[_keyStreamOffset] ^ inp[i + inpOff]);
|
|
_keyStreamOffset = (_keyStreamOffset + 1) & 63;
|
|
}
|
|
}
|
|
|
|
void _setKey(Uint8List keyBytes, Uint8List ivBytes) {
|
|
_workingKey = keyBytes;
|
|
_workingIV = ivBytes;
|
|
|
|
_keyStreamOffset = 0;
|
|
var offset = 0;
|
|
Uint8List constants;
|
|
|
|
// Key
|
|
_state[1] = unpack32(_workingKey, 0, Endian.little);
|
|
_state[2] = unpack32(_workingKey, 4, Endian.little);
|
|
_state[3] = unpack32(_workingKey, 8, Endian.little);
|
|
_state[4] = unpack32(_workingKey, 12, Endian.little);
|
|
|
|
if (_workingKey!.length == 32) {
|
|
constants = _sigma;
|
|
offset = 16;
|
|
} else {
|
|
constants = _tau;
|
|
}
|
|
|
|
_state[11] = unpack32(_workingKey, offset, Endian.little);
|
|
_state[12] = unpack32(_workingKey, offset + 4, Endian.little);
|
|
_state[13] = unpack32(_workingKey, offset + 8, Endian.little);
|
|
_state[14] = unpack32(_workingKey, offset + 12, Endian.little);
|
|
_state[0] = unpack32(constants, 0, Endian.little);
|
|
_state[5] = unpack32(constants, 4, Endian.little);
|
|
_state[10] = unpack32(constants, 8, Endian.little);
|
|
_state[15] = unpack32(constants, 12, Endian.little);
|
|
|
|
// IV
|
|
_state[6] = unpack32(_workingIV, 0, Endian.little);
|
|
_state[7] = unpack32(_workingIV, 4, Endian.little);
|
|
_state[8] = _state[9] = 0;
|
|
|
|
_initialised = true;
|
|
}
|
|
|
|
void _generateKeyStream(Uint8List output) {
|
|
_salsa20Core(20, _state, _buffer);
|
|
var outOff = 0;
|
|
for (var x in _buffer) {
|
|
pack32(x, output, outOff, Endian.little);
|
|
outOff += 4;
|
|
}
|
|
}
|
|
|
|
/// The Salsa20 core function
|
|
void _salsa20Core(int rounds, List<int> input, List<int> x) {
|
|
x.setAll(0, input);
|
|
|
|
for (var i = rounds; i > 0; i -= 2) {
|
|
x[4] ^= crotl32(x[0] + x[12], 7);
|
|
x[8] ^= crotl32(x[4] + x[0], 9);
|
|
x[12] ^= crotl32(x[8] + x[4], 13);
|
|
x[0] ^= crotl32(x[12] + x[8], 18);
|
|
x[9] ^= crotl32(x[5] + x[1], 7);
|
|
x[13] ^= crotl32(x[9] + x[5], 9);
|
|
x[1] ^= crotl32(x[13] + x[9], 13);
|
|
x[5] ^= crotl32(x[1] + x[13], 18);
|
|
x[14] ^= crotl32(x[10] + x[6], 7);
|
|
x[2] ^= crotl32(x[14] + x[10], 9);
|
|
x[6] ^= crotl32(x[2] + x[14], 13);
|
|
x[10] ^= crotl32(x[6] + x[2], 18);
|
|
x[3] ^= crotl32(x[15] + x[11], 7);
|
|
x[7] ^= crotl32(x[3] + x[15], 9);
|
|
x[11] ^= crotl32(x[7] + x[3], 13);
|
|
x[15] ^= crotl32(x[11] + x[7], 18);
|
|
x[1] ^= crotl32(x[0] + x[3], 7);
|
|
x[2] ^= crotl32(x[1] + x[0], 9);
|
|
x[3] ^= crotl32(x[2] + x[1], 13);
|
|
x[0] ^= crotl32(x[3] + x[2], 18);
|
|
x[6] ^= crotl32(x[5] + x[4], 7);
|
|
x[7] ^= crotl32(x[6] + x[5], 9);
|
|
x[4] ^= crotl32(x[7] + x[6], 13);
|
|
x[5] ^= crotl32(x[4] + x[7], 18);
|
|
x[11] ^= crotl32(x[10] + x[9], 7);
|
|
x[8] ^= crotl32(x[11] + x[10], 9);
|
|
x[9] ^= crotl32(x[8] + x[11], 13);
|
|
x[10] ^= crotl32(x[9] + x[8], 18);
|
|
x[12] ^= crotl32(x[15] + x[14], 7);
|
|
x[13] ^= crotl32(x[12] + x[15], 9);
|
|
x[14] ^= crotl32(x[13] + x[12], 13);
|
|
x[15] ^= crotl32(x[14] + x[13], 18);
|
|
}
|
|
|
|
for (var i = 0; i < _STATE_SIZE; ++i) {
|
|
x[i] = sum32(x[i], input[i]);
|
|
}
|
|
}
|
|
}
|