blob: 7c66a96ed1ecfc2ab18e3a85fa35e4460b16dccd [file] [log] [blame]
// See file LICENSE for more information.
library impl.key_derivator.scrypt;
import 'dart:typed_data';
import 'package:pointycastle/api.dart';
import 'package:pointycastle/digests/sha256.dart';
import 'package:pointycastle/key_derivators/api.dart';
import 'package:pointycastle/key_derivators/pbkdf2.dart';
import 'package:pointycastle/macs/hmac.dart';
import 'package:pointycastle/src/impl/base_key_derivator.dart';
import 'package:pointycastle/src/registry/registry.dart';
import 'package:pointycastle/src/ufixnum.dart';
///
/// Implementation of SCrypt password based key derivation function. See the next link for info on
/// how to choose N, r, and p:
/// * <http://stackoverflow.com/questions/11126315/what-are-optimal-scrypt-work-factors>
///
/// This implementation is based on Java implementation by Will Glozer, which can be found at:
/// * <https://github.com/wg/scrypt>
///
class Scrypt extends BaseKeyDerivator {
static final FactoryConfig factoryConfig =
StaticFactoryConfig(KeyDerivator, 'scrypt', () => Scrypt());
static final int _maxValue = 0x7fffffff;
ScryptParameters? _params;
@override
final String algorithmName = 'scrypt';
@override
int get keySize => _params!.desiredKeyLength;
void reset() {
_params = null;
}
@override
void init(covariant ScryptParameters params) {
_params = params;
}
@override
int deriveKey(Uint8List inp, int inpOff, Uint8List out, int outOff) {
var key = _scryptJ(Uint8List.fromList(inp.sublist(inpOff)), _params!.salt,
_params!.N, _params!.r, _params!.p, _params!.desiredKeyLength);
out.setRange(0, keySize, key);
return keySize;
}
Uint8List _scryptJ(
Uint8List passwd, Uint8List salt, int N, int r, int p, int dkLen) {
if (N < 2 || (N & (N - 1)) != 0) {
throw ArgumentError('N must be a power of 2 greater than 1');
}
if (N > _maxValue / 128 / r) {
throw ArgumentError('Parameter N is too large');
}
if (r > _maxValue / 128 / p) {
throw ArgumentError('Parameter r is too large');
}
final dk = Uint8List(dkLen);
final b = Uint8List(128 * r * p);
final xy = Uint8List(256 * r);
final v = Uint8List(128 * r * N);
final pbkdf2 = PBKDF2KeyDerivator(HMac(SHA256Digest(), 64));
pbkdf2.init(Pbkdf2Parameters(salt, 1, p * 128 * r));
pbkdf2.deriveKey(passwd, 0, b, 0);
for (var i = 0; i < p; i++) {
_smix(b, i * 128 * r, r, N, v, xy);
}
pbkdf2.init(Pbkdf2Parameters(b, 1, dkLen));
pbkdf2.deriveKey(passwd, 0, dk, 0);
return dk;
}
void _smix(Uint8List B, int bi, int r, int N, Uint8List V, Uint8List xy) {
var xi = 0;
var yi = 128 * r;
_arraycopy(B, bi, xy, xi, 128 * r);
for (var i = 0; i < N; i++) {
_arraycopy(xy, xi, V, i * (128 * r), 128 * r);
_blockmixSalsa8(xy, xi, yi, r);
}
for (var i = 0; i < N; i++) {
var j = _integerify(xy, xi, r) & (N - 1);
_blockxor(V, j * (128 * r), xy, xi, 128 * r);
_blockmixSalsa8(xy, xi, yi, r);
}
_arraycopy(xy, xi, B, bi, 128 * r);
}
void _blockmixSalsa8(Uint8List by, int bi, int yi, int r) {
final X = Uint8List(64);
_arraycopy(by, bi + (2 * r - 1) * 64, X, 0, 64);
for (var i = 0; i < 2 * r; i++) {
_blockxor(by, i * 64, X, 0, 64);
_salsa20_8(X);
_arraycopy(X, 0, by, yi + (i * 64), 64);
}
for (var i = 0; i < r; i++) {
_arraycopy(by, yi + (i * 2) * 64, by, bi + (i * 64), 64);
}
for (var i = 0; i < r; i++) {
_arraycopy(by, yi + (i * 2 + 1) * 64, by, bi + (i + r) * 64, 64);
}
}
void _salsa20_8(Uint8List B) {
final b32 = Uint32List(16);
final x = Uint32List(16);
for (var i = 0; i < 16; i++) {
b32[i] = unpack32(B, i * 4, Endian.little);
}
_arraycopy(b32, 0, x, 0, 16);
for (var i = 8; 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 < 16; i++) {
b32[i] = x[i] + b32[i];
}
for (var i = 0; i < 16; i++) {
pack32(b32[i], B, i * 4, Endian.little);
}
}
void _blockxor(Uint8List s, int si, Uint8List d, int di, int len) {
for (var i = 0; i < len; i++) {
d[di + i] ^= s[si + i];
}
}
int _integerify(Uint8List b, int bi, int r) {
bi += (2 * r - 1) * 64;
return unpack32(b, bi, Endian.little);
}
void _arraycopy(
List<int> inp, int inpOff, List<int> out, int outOff, int len) =>
out.setRange(outOff, outOff + len, inp.sublist(inpOff));
}