SHA-256 with its own compression, and on it HMAC-SHA256 with the inner and outer states of the key computed once, HKDF-SHA256 and PBKDF2-HMAC-SHA256, whose iterations are two compressions over words; scrypt with Salsa20/8; ChaCha20, Poly1305 in 13-bit limbs and ChaCha20-Poly1305 with the tag compared in constant time; X25519 on the field of TweetNaCl in doubles, with the all-zero secret refused; the strict Ed25519 verification of internal/ed25519strict; Go's Base64 with the offsets of its errors, and age's Bech32. tool/gen_primitive_vectors.go computes every expected value with Go and x/crypto, from the inputs of RFC 5869, 7748, 7914, 8032 and 8439, edge cases and seeded random ones. The tests also run compiled to JavaScript, from a Dart copy of the JSON, without the cases that would take too long. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>v0.11
parent
c8bf3c0eab
commit
0c21f551c6
@ -0,0 +1,145 @@
|
||||
/// Base64 decoding as Go's encoding/base64, with its strict mode and the
|
||||
/// offset of its errors (CorruptInputError): the alphabet of age, of the
|
||||
/// DateKey and of the other encodings of the protocol is decoded with the
|
||||
/// same acceptance and the same error texts as the Go reference.
|
||||
///
|
||||
/// Internal: lib/datekeys.dart does not export it.
|
||||
library;
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
/// Bytes that Go's base64 rejects: its CorruptInputError, with the text
|
||||
/// `illegal base64 data at input byte N`.
|
||||
final class Base64Exception implements Exception {
|
||||
/// The error at byte [offset] of the input.
|
||||
const Base64Exception(this.offset);
|
||||
|
||||
/// The offset that Go reports.
|
||||
final int offset;
|
||||
|
||||
/// The text of Go's error.
|
||||
String get message => 'illegal base64 data at input byte $offset';
|
||||
|
||||
@override
|
||||
String toString() => message;
|
||||
}
|
||||
|
||||
const _std = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
const _url = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
||||
|
||||
Int16List _decodeMap(String alphabet) {
|
||||
final m = Int16List(256)..fillRange(0, 256, -1);
|
||||
for (var i = 0; i < alphabet.length; i++) {
|
||||
m[alphabet.codeUnitAt(i)] = i;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
final Int16List _stdMap = _decodeMap(_std);
|
||||
final Int16List _urlMap = _decodeMap(_url);
|
||||
|
||||
bool _isNewline(int c) => c == 0x0a || c == 0x0d;
|
||||
|
||||
/// Decodes [src] as Go's base64 Encoding of the standard alphabet, or the
|
||||
/// URL one when [url], with `=` padding when [padded], and in Go's strict
|
||||
/// mode, which rejects non-zero trailing bits, when [strict]. As in Go, CR
|
||||
/// and LF anywhere are skipped. Throws a [Base64Exception] with Go's offset.
|
||||
Uint8List goBase64Decode(
|
||||
List<int> src, {
|
||||
bool url = false,
|
||||
bool padded = true,
|
||||
bool strict = false,
|
||||
}) {
|
||||
final map = url ? _urlMap : _stdMap;
|
||||
final out = BytesBuilder(copy: false);
|
||||
final q = Int32List(4);
|
||||
var si = 0;
|
||||
while (si < src.length) {
|
||||
// Go's decodeQuantum.
|
||||
var dlen = 4;
|
||||
int? trailing;
|
||||
var j = 0;
|
||||
for (; j < 4; j++) {
|
||||
if (si == src.length) {
|
||||
if (j == 0) return out.takeBytes();
|
||||
if (j == 1 || padded) throw Base64Exception(si - j);
|
||||
dlen = j;
|
||||
break;
|
||||
}
|
||||
final c = src[si++];
|
||||
final v = c >= 0 && c < 256 ? map[c] : -1;
|
||||
if (v >= 0) {
|
||||
q[j] = v;
|
||||
continue;
|
||||
}
|
||||
if (_isNewline(c)) {
|
||||
j--;
|
||||
continue;
|
||||
}
|
||||
if (!padded || c != 0x3d) throw Base64Exception(si - 1);
|
||||
// The end, with padding.
|
||||
if (j < 2) throw Base64Exception(si - 1);
|
||||
if (j == 2) {
|
||||
// "==" is expected, the first "=" is already consumed.
|
||||
while (si < src.length && _isNewline(src[si])) {
|
||||
si++;
|
||||
}
|
||||
if (si == src.length) throw Base64Exception(src.length);
|
||||
if (src[si] != 0x3d) throw Base64Exception(si - 1);
|
||||
si++;
|
||||
}
|
||||
while (si < src.length && _isNewline(src[si])) {
|
||||
si++;
|
||||
}
|
||||
if (si < src.length) trailing = si;
|
||||
dlen = j;
|
||||
break;
|
||||
}
|
||||
for (var k = dlen; k < 4; k++) {
|
||||
q[k] = 0;
|
||||
}
|
||||
final b0 = q[0] << 2 | q[1] >> 4;
|
||||
final b1 = (q[1] & 15) << 4 | q[2] >> 2;
|
||||
final b2 = (q[2] & 3) << 6 | q[3];
|
||||
if (strict && dlen == 3 && b2 != 0) throw Base64Exception(si - 1);
|
||||
if (strict && dlen == 2 && (b1 != 0 || b2 != 0)) {
|
||||
throw Base64Exception(si - 2);
|
||||
}
|
||||
final bytes = [b0, b1, b2];
|
||||
out.add(Uint8List.fromList(bytes.sublist(0, dlen - 1)));
|
||||
if (trailing != null) throw Base64Exception(trailing);
|
||||
}
|
||||
return out.takeBytes();
|
||||
}
|
||||
|
||||
/// Encodes [bytes] in Base64 as Go's Encoding: the standard alphabet, or the
|
||||
/// URL one when [url], with `=` padding when [padded].
|
||||
String goBase64Encode(List<int> bytes, {bool url = false, bool padded = true}) {
|
||||
final alphabet = url ? _url : _std;
|
||||
final out = StringBuffer();
|
||||
var i = 0;
|
||||
for (; i + 3 <= bytes.length; i += 3) {
|
||||
final v = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
|
||||
out
|
||||
..write(alphabet[v >> 18])
|
||||
..write(alphabet[v >> 12 & 63])
|
||||
..write(alphabet[v >> 6 & 63])
|
||||
..write(alphabet[v & 63]);
|
||||
}
|
||||
final rest = bytes.length - i;
|
||||
if (rest == 1) {
|
||||
final v = bytes[i] << 16;
|
||||
out
|
||||
..write(alphabet[v >> 18])
|
||||
..write(alphabet[v >> 12 & 63]);
|
||||
if (padded) out.write('==');
|
||||
} else if (rest == 2) {
|
||||
final v = bytes[i] << 16 | bytes[i + 1] << 8;
|
||||
out
|
||||
..write(alphabet[v >> 18])
|
||||
..write(alphabet[v >> 12 & 63])
|
||||
..write(alphabet[v >> 6 & 63]);
|
||||
if (padded) out.write('=');
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
@ -0,0 +1,192 @@
|
||||
/// Bech32 (BIP 173) as the internal/bech32 package of filippo.io/age v1.3.2,
|
||||
/// which datekeys-go copies verbatim as codec/bech32: the encoding of the age
|
||||
/// X25519 identities (AGE-SECRET-KEY-1…) and recipients (age1…). An
|
||||
/// encoding, not cryptography. Like age, it accepts strings longer than the
|
||||
/// 90 characters of BIP 173.
|
||||
///
|
||||
/// Ported from internal/bech32 of filippo.io/age v1.3.2
|
||||
/// (https://github.com/FiloSottile/age), under its license:
|
||||
///
|
||||
/// Copyright (c) 2017 Takatoshi Nakagawa
|
||||
/// Copyright (c) 2019 The age Authors
|
||||
///
|
||||
/// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
/// copy of this software and associated documentation files (the
|
||||
/// "Software"), to deal in the Software without restriction, including
|
||||
/// without limitation the rights to use, copy, modify, merge, publish,
|
||||
/// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
/// permit persons to whom the Software is furnished to do so, subject to
|
||||
/// the following conditions:
|
||||
///
|
||||
/// The above copyright notice and this permission notice shall be included
|
||||
/// in all copies or substantial portions of the Software.
|
||||
///
|
||||
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
/// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
/// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
/// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
/// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
/// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
/// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
///
|
||||
/// Go strings are bytes: the positions in the error texts are offsets in the
|
||||
/// UTF-8 of the string, as in Go. The mixed-case check of a string to decode
|
||||
/// folds it with the Unicode case mapping of the platform, as Go does with
|
||||
/// its own tables; they differ only for the few characters whose case
|
||||
/// mapping is not one to one, which Bech32 rejects anyway, maybe with
|
||||
/// another text.
|
||||
///
|
||||
/// Internal: lib/datekeys.dart does not export it.
|
||||
library;
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'bytes.dart';
|
||||
|
||||
/// A string or data that Bech32 rejects, with the text of age's error.
|
||||
final class Bech32Exception implements Exception {
|
||||
/// An exception with age's [message].
|
||||
const Bech32Exception(this.message);
|
||||
|
||||
/// The text of age's error.
|
||||
final String message;
|
||||
|
||||
@override
|
||||
String toString() => message;
|
||||
}
|
||||
|
||||
const _charset = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
|
||||
const _generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
|
||||
|
||||
int _polymod(List<int> values) {
|
||||
var chk = 1;
|
||||
for (final v in values) {
|
||||
final top = chk >>> 25;
|
||||
chk = ((chk & 0x1ffffff) << 5) ^ v;
|
||||
for (var i = 0; i < 5; i++) {
|
||||
if ((top >>> i) & 1 == 1) chk ^= _generator[i];
|
||||
}
|
||||
}
|
||||
return chk;
|
||||
}
|
||||
|
||||
String _lowerAscii(String s) => String.fromCharCodes([
|
||||
for (final c in s.codeUnits) c >= 0x41 && c <= 0x5a ? c + 0x20 : c,
|
||||
]);
|
||||
|
||||
String _upperAscii(String s) => String.fromCharCodes([
|
||||
for (final c in s.codeUnits) c >= 0x61 && c <= 0x7a ? c - 0x20 : c,
|
||||
]);
|
||||
|
||||
List<int> _hrpExpand(String hrp) {
|
||||
final h = utf8Bytes(_lowerAscii(hrp));
|
||||
return [for (final c in h) c >> 5, 0, for (final c in h) c & 31];
|
||||
}
|
||||
|
||||
List<int> _convertBits(List<int> data, int from, int to, bool pad) {
|
||||
final out = <int>[];
|
||||
var acc = 0;
|
||||
var bits = 0;
|
||||
final maxv = (1 << to) - 1;
|
||||
for (var idx = 0; idx < data.length; idx++) {
|
||||
final value = data[idx];
|
||||
if (value >> from != 0) {
|
||||
throw Bech32Exception(
|
||||
'invalid data range: data[$idx]=$value (frombits=$from)',
|
||||
);
|
||||
}
|
||||
// Go keeps acc in a uint32; only its low bits are ever read.
|
||||
acc = ((acc << from) | value) & 0xffffff;
|
||||
bits += from;
|
||||
while (bits >= to) {
|
||||
bits -= to;
|
||||
out.add((acc >> bits) & maxv);
|
||||
}
|
||||
}
|
||||
if (pad) {
|
||||
if (bits > 0) out.add((acc << (to - bits)) & maxv);
|
||||
} else if (bits >= from) {
|
||||
throw const Bech32Exception('illegal zero padding');
|
||||
} else if ((acc << (to - bits)) & maxv != 0) {
|
||||
throw const Bech32Exception('non-zero padding');
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Encodes [hrp] and [data] in Bech32, as age's bech32.Encode: an uppercase
|
||||
/// HRP gives an uppercase string.
|
||||
String bech32Encode(String hrp, List<int> data) {
|
||||
final values = _convertBits(data, 8, 5, true);
|
||||
final h = utf8Bytes(hrp);
|
||||
if (h.isEmpty) throw Bech32Exception('invalid HRP: ${goQuote(h)}');
|
||||
for (var p = 0; p < h.length;) {
|
||||
final (c, size) = decodeRune(h, p);
|
||||
if (c < 33 || c > 126) {
|
||||
throw Bech32Exception('invalid HRP character: hrp[$p]=$c');
|
||||
}
|
||||
p += size;
|
||||
}
|
||||
if (_upperAscii(hrp) != hrp && _lowerAscii(hrp) != hrp) {
|
||||
throw Bech32Exception('mixed case HRP: ${goQuote(h)}');
|
||||
}
|
||||
final lower = _lowerAscii(hrp) == hrp;
|
||||
final lh = _lowerAscii(hrp);
|
||||
final mod = _polymod([..._hrpExpand(lh), ...values, 0, 0, 0, 0, 0, 0]) ^ 1;
|
||||
final out = StringBuffer(lh)..write('1');
|
||||
for (final v in values) {
|
||||
out.write(_charset[v]);
|
||||
}
|
||||
for (var p = 0; p < 6; p++) {
|
||||
out.write(_charset[(mod >>> (5 * (5 - p))) & 31]);
|
||||
}
|
||||
final s = out.toString();
|
||||
return lower ? s : _upperAscii(s);
|
||||
}
|
||||
|
||||
/// Decodes the Bech32 string [s], as age's bech32.Decode: the HRP keeps the
|
||||
/// case of the string.
|
||||
({String hrp, Uint8List data}) bech32Decode(String s) {
|
||||
// Go compares the string with its Unicode lower and upper case.
|
||||
if (s.toLowerCase() != s && s.toUpperCase() != s) {
|
||||
throw const Bech32Exception('mixed case');
|
||||
}
|
||||
final b = utf8Bytes(s);
|
||||
final pos = b.lastIndexOf(0x31);
|
||||
if (pos < 1 || pos + 7 > b.length) {
|
||||
throw Bech32Exception(
|
||||
"separator '1' at invalid position: pos=$pos, len=${b.length}",
|
||||
);
|
||||
}
|
||||
final hrp = Uint8List.sublistView(b, 0, pos);
|
||||
for (var p = 0; p < hrp.length;) {
|
||||
final (c, size) = decodeRune(hrp, p);
|
||||
if (c < 33 || c > 126) {
|
||||
throw Bech32Exception('invalid character human-readable part: s[$p]=$c');
|
||||
}
|
||||
p += size;
|
||||
}
|
||||
final rest = Uint8List.sublistView(b, pos + 1);
|
||||
final data = <int>[];
|
||||
for (var p = 0; p < rest.length;) {
|
||||
var (c, size) = decodeRune(rest, p);
|
||||
// Fold ASCII explicitly, as age does.
|
||||
if (c >= 0x41 && c <= 0x5a) c += 0x20;
|
||||
final d = c < 0x80 ? _charset.indexOf(String.fromCharCode(c)) : -1;
|
||||
if (d == -1) {
|
||||
throw Bech32Exception('invalid character data part: s[$p]=$c');
|
||||
}
|
||||
data.add(d);
|
||||
p += size;
|
||||
}
|
||||
if (data.length < 6) throw const Bech32Exception('data part too short');
|
||||
final hrpString = String.fromCharCodes(hrp);
|
||||
if (_polymod([..._hrpExpand(hrpString), ...data]) != 1) {
|
||||
throw const Bech32Exception('invalid checksum');
|
||||
}
|
||||
return (
|
||||
hrp: hrpString,
|
||||
data: Uint8List.fromList(
|
||||
_convertBits(data.sublist(0, data.length - 6), 5, 8, false),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,537 @@
|
||||
/// ChaCha20, Poly1305 and the AEAD ChaCha20-Poly1305 of RFC 8439, which age
|
||||
/// uses to wrap file keys and for its STREAM.
|
||||
///
|
||||
/// The arithmetic is exact on the VM and when compiled to JavaScript:
|
||||
/// - ChaCha20 adds two 32-bit words at a time and masks the sum, and its
|
||||
/// rotations are of 32-bit values, so that the 32-bit bit operators of the
|
||||
/// web give what the 64-bit ones of the VM give;
|
||||
/// - Poly1305 keeps its accumulator and r in ten limbs of 13 bits. A product
|
||||
/// of a limb of h (below 2^15) and one of 5·r (below 2^16) is below 2^31,
|
||||
/// and a sum of ten of them and a carry is below 2^35: far from 2^53, where
|
||||
/// a double stops being exact. The carries of those sums, which may pass
|
||||
/// 2^32, are taken with `~/` and the low bits with `&`, never with a shift
|
||||
/// of a value above 2^32, which the web would truncate.
|
||||
///
|
||||
/// The tag is compared in constant time. The rest is written without
|
||||
/// branches or indexes that depend on secrets, but neither the VM nor a
|
||||
/// JavaScript engine promises constant time.
|
||||
///
|
||||
/// Internal: lib/datekeys.dart does not export it.
|
||||
library;
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
const _mask32 = 0xffffffff;
|
||||
|
||||
/// The size of a ChaCha20 key.
|
||||
const chachaKeySize = 32;
|
||||
|
||||
/// The size of the nonce of ChaCha20-Poly1305 (RFC 8439).
|
||||
const chachaNonceSize = 12;
|
||||
|
||||
/// The size of a Poly1305 tag, the overhead of ChaCha20-Poly1305.
|
||||
const poly1305TagSize = 16;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ChaCha20
|
||||
|
||||
int _le32(List<int> b, int o) =>
|
||||
b[o] | b[o + 1] << 8 | b[o + 2] << 16 | (b[o + 3] << 24 & _mask32);
|
||||
|
||||
/// The ChaCha20 keystream of RFC 8439, 2.4: [key] (32 bytes), [nonce] (12
|
||||
/// bytes) and the block counter from [counter].
|
||||
final class ChaCha20 {
|
||||
/// The cipher of [key] and [nonce], from block [counter].
|
||||
ChaCha20(List<int> key, List<int> nonce, [int counter = 0]) {
|
||||
if (key.length != chachaKeySize) {
|
||||
throw ArgumentError.value(key.length, 'key', 'not 32 bytes');
|
||||
}
|
||||
if (nonce.length != chachaNonceSize) {
|
||||
throw ArgumentError.value(nonce.length, 'nonce', 'not 12 bytes');
|
||||
}
|
||||
if (counter < 0 || counter > _mask32) {
|
||||
throw RangeError.range(counter, 0, _mask32, 'counter');
|
||||
}
|
||||
_input[0] = 0x61707865;
|
||||
_input[1] = 0x3320646e;
|
||||
_input[2] = 0x79622d32;
|
||||
_input[3] = 0x6b206574;
|
||||
for (var i = 0; i < 8; i++) {
|
||||
_input[4 + i] = _le32(key, 4 * i);
|
||||
}
|
||||
_input[12] = counter;
|
||||
for (var i = 0; i < 3; i++) {
|
||||
_input[13 + i] = _le32(nonce, 4 * i);
|
||||
}
|
||||
}
|
||||
|
||||
final Uint32List _input = Uint32List(16);
|
||||
final Uint32List _x = Uint32List(16);
|
||||
final Uint8List _stream = Uint8List(64);
|
||||
int _used = 64;
|
||||
bool _overflow = false;
|
||||
|
||||
// The next 64 bytes of keystream into _stream, and the counter advanced.
|
||||
void _block() {
|
||||
// Go's chacha20 refuses a block past counter 2^32 - 1, which RFC 8439
|
||||
// leaves undefined.
|
||||
if (_overflow) throw StateError('chacha20: counter overflow');
|
||||
final s = _input;
|
||||
var x0 = s[0], x1 = s[1], x2 = s[2], x3 = s[3];
|
||||
var x4 = s[4], x5 = s[5], x6 = s[6], x7 = s[7];
|
||||
var x8 = s[8], x9 = s[9], x10 = s[10], x11 = s[11];
|
||||
var x12 = s[12], x13 = s[13], x14 = s[14], x15 = s[15];
|
||||
for (var i = 0; i < 10; i++) {
|
||||
// Column rounds.
|
||||
x0 = (x0 + x4) & _mask32;
|
||||
x12 ^= x0;
|
||||
x12 = (x12 << 16 & _mask32) | x12 >>> 16;
|
||||
x8 = (x8 + x12) & _mask32;
|
||||
x4 ^= x8;
|
||||
x4 = (x4 << 12 & _mask32) | x4 >>> 20;
|
||||
x0 = (x0 + x4) & _mask32;
|
||||
x12 ^= x0;
|
||||
x12 = (x12 << 8 & _mask32) | x12 >>> 24;
|
||||
x8 = (x8 + x12) & _mask32;
|
||||
x4 ^= x8;
|
||||
x4 = (x4 << 7 & _mask32) | x4 >>> 25;
|
||||
|
||||
x1 = (x1 + x5) & _mask32;
|
||||
x13 ^= x1;
|
||||
x13 = (x13 << 16 & _mask32) | x13 >>> 16;
|
||||
x9 = (x9 + x13) & _mask32;
|
||||
x5 ^= x9;
|
||||
x5 = (x5 << 12 & _mask32) | x5 >>> 20;
|
||||
x1 = (x1 + x5) & _mask32;
|
||||
x13 ^= x1;
|
||||
x13 = (x13 << 8 & _mask32) | x13 >>> 24;
|
||||
x9 = (x9 + x13) & _mask32;
|
||||
x5 ^= x9;
|
||||
x5 = (x5 << 7 & _mask32) | x5 >>> 25;
|
||||
|
||||
x2 = (x2 + x6) & _mask32;
|
||||
x14 ^= x2;
|
||||
x14 = (x14 << 16 & _mask32) | x14 >>> 16;
|
||||
x10 = (x10 + x14) & _mask32;
|
||||
x6 ^= x10;
|
||||
x6 = (x6 << 12 & _mask32) | x6 >>> 20;
|
||||
x2 = (x2 + x6) & _mask32;
|
||||
x14 ^= x2;
|
||||
x14 = (x14 << 8 & _mask32) | x14 >>> 24;
|
||||
x10 = (x10 + x14) & _mask32;
|
||||
x6 ^= x10;
|
||||
x6 = (x6 << 7 & _mask32) | x6 >>> 25;
|
||||
|
||||
x3 = (x3 + x7) & _mask32;
|
||||
x15 ^= x3;
|
||||
x15 = (x15 << 16 & _mask32) | x15 >>> 16;
|
||||
x11 = (x11 + x15) & _mask32;
|
||||
x7 ^= x11;
|
||||
x7 = (x7 << 12 & _mask32) | x7 >>> 20;
|
||||
x3 = (x3 + x7) & _mask32;
|
||||
x15 ^= x3;
|
||||
x15 = (x15 << 8 & _mask32) | x15 >>> 24;
|
||||
x11 = (x11 + x15) & _mask32;
|
||||
x7 ^= x11;
|
||||
x7 = (x7 << 7 & _mask32) | x7 >>> 25;
|
||||
|
||||
// Diagonal rounds.
|
||||
x0 = (x0 + x5) & _mask32;
|
||||
x15 ^= x0;
|
||||
x15 = (x15 << 16 & _mask32) | x15 >>> 16;
|
||||
x10 = (x10 + x15) & _mask32;
|
||||
x5 ^= x10;
|
||||
x5 = (x5 << 12 & _mask32) | x5 >>> 20;
|
||||
x0 = (x0 + x5) & _mask32;
|
||||
x15 ^= x0;
|
||||
x15 = (x15 << 8 & _mask32) | x15 >>> 24;
|
||||
x10 = (x10 + x15) & _mask32;
|
||||
x5 ^= x10;
|
||||
x5 = (x5 << 7 & _mask32) | x5 >>> 25;
|
||||
|
||||
x1 = (x1 + x6) & _mask32;
|
||||
x12 ^= x1;
|
||||
x12 = (x12 << 16 & _mask32) | x12 >>> 16;
|
||||
x11 = (x11 + x12) & _mask32;
|
||||
x6 ^= x11;
|
||||
x6 = (x6 << 12 & _mask32) | x6 >>> 20;
|
||||
x1 = (x1 + x6) & _mask32;
|
||||
x12 ^= x1;
|
||||
x12 = (x12 << 8 & _mask32) | x12 >>> 24;
|
||||
x11 = (x11 + x12) & _mask32;
|
||||
x6 ^= x11;
|
||||
x6 = (x6 << 7 & _mask32) | x6 >>> 25;
|
||||
|
||||
x2 = (x2 + x7) & _mask32;
|
||||
x13 ^= x2;
|
||||
x13 = (x13 << 16 & _mask32) | x13 >>> 16;
|
||||
x8 = (x8 + x13) & _mask32;
|
||||
x7 ^= x8;
|
||||
x7 = (x7 << 12 & _mask32) | x7 >>> 20;
|
||||
x2 = (x2 + x7) & _mask32;
|
||||
x13 ^= x2;
|
||||
x13 = (x13 << 8 & _mask32) | x13 >>> 24;
|
||||
x8 = (x8 + x13) & _mask32;
|
||||
x7 ^= x8;
|
||||
x7 = (x7 << 7 & _mask32) | x7 >>> 25;
|
||||
|
||||
x3 = (x3 + x4) & _mask32;
|
||||
x14 ^= x3;
|
||||
x14 = (x14 << 16 & _mask32) | x14 >>> 16;
|
||||
x9 = (x9 + x14) & _mask32;
|
||||
x4 ^= x9;
|
||||
x4 = (x4 << 12 & _mask32) | x4 >>> 20;
|
||||
x3 = (x3 + x4) & _mask32;
|
||||
x14 ^= x3;
|
||||
x14 = (x14 << 8 & _mask32) | x14 >>> 24;
|
||||
x9 = (x9 + x14) & _mask32;
|
||||
x4 ^= x9;
|
||||
x4 = (x4 << 7 & _mask32) | x4 >>> 25;
|
||||
}
|
||||
final x = _x;
|
||||
x[0] = x0 + s[0];
|
||||
x[1] = x1 + s[1];
|
||||
x[2] = x2 + s[2];
|
||||
x[3] = x3 + s[3];
|
||||
x[4] = x4 + s[4];
|
||||
x[5] = x5 + s[5];
|
||||
x[6] = x6 + s[6];
|
||||
x[7] = x7 + s[7];
|
||||
x[8] = x8 + s[8];
|
||||
x[9] = x9 + s[9];
|
||||
x[10] = x10 + s[10];
|
||||
x[11] = x11 + s[11];
|
||||
x[12] = x12 + s[12];
|
||||
x[13] = x13 + s[13];
|
||||
x[14] = x14 + s[14];
|
||||
x[15] = x15 + s[15];
|
||||
// A Uint32List keeps the low 32 bits of each sum, on the VM and on the
|
||||
// web alike.
|
||||
final out = _stream;
|
||||
for (var i = 0; i < 16; i++) {
|
||||
final v = x[i];
|
||||
out[4 * i] = v & 0xff;
|
||||
out[4 * i + 1] = v >>> 8 & 0xff;
|
||||
out[4 * i + 2] = v >>> 16 & 0xff;
|
||||
out[4 * i + 3] = v >>> 24;
|
||||
}
|
||||
if (s[12] == _mask32) {
|
||||
_overflow = true;
|
||||
} else {
|
||||
s[12] = s[12] + 1;
|
||||
}
|
||||
_used = 0;
|
||||
}
|
||||
|
||||
/// XORs the keystream into [data] from [start] to [end], in place.
|
||||
void xorInPlace(Uint8List data, [int start = 0, int? end]) {
|
||||
final stop = end ?? data.length;
|
||||
RangeError.checkValidRange(start, stop, data.length);
|
||||
for (var i = start; i < stop; i++) {
|
||||
if (_used == 64) _block();
|
||||
data[i] ^= _stream[_used++];
|
||||
}
|
||||
}
|
||||
|
||||
/// The next [n] bytes of keystream.
|
||||
Uint8List keystream(int n) {
|
||||
final out = Uint8List(n);
|
||||
xorInPlace(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Clears the key and the keystream.
|
||||
void wipe() {
|
||||
_input.fillRange(0, 16, 0);
|
||||
_x.fillRange(0, 16, 0);
|
||||
_stream.fillRange(0, 64, 0);
|
||||
_used = 64;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Poly1305
|
||||
|
||||
const _limbBits = 13;
|
||||
const _limbMask = 0x1fff;
|
||||
const _limbRadix = 0x2000;
|
||||
const _limbs = 10;
|
||||
|
||||
// The ten 13-bit limbs of the little-endian integer in pad[0..16]; pad has
|
||||
// at least 19 bytes, those above the integer zero.
|
||||
void _limbsOf(Uint8List pad, Int32List out) {
|
||||
for (var i = 0; i < _limbs; i++) {
|
||||
final bit = _limbBits * i;
|
||||
final byte = bit >>> 3;
|
||||
final v = pad[byte] | pad[byte + 1] << 8 | pad[byte + 2] << 16;
|
||||
out[i] = v >>> (bit & 7) & _limbMask;
|
||||
}
|
||||
}
|
||||
|
||||
/// Poly1305 (RFC 8439, 2.5) with a one-time key of 32 bytes: r and s.
|
||||
final class Poly1305 {
|
||||
/// The MAC of the one-time [key], 32 bytes.
|
||||
Poly1305(List<int> key) {
|
||||
if (key.length != 32) {
|
||||
throw ArgumentError.value(key.length, 'key', 'not 32 bytes');
|
||||
}
|
||||
final pad = _pad;
|
||||
for (var i = 0; i < 16; i++) {
|
||||
pad[i] = key[i];
|
||||
}
|
||||
// Clamping, RFC 8439 2.5.1.
|
||||
pad[3] &= 15;
|
||||
pad[7] &= 15;
|
||||
pad[11] &= 15;
|
||||
pad[15] &= 15;
|
||||
pad[4] &= 252;
|
||||
pad[8] &= 252;
|
||||
pad[12] &= 252;
|
||||
_limbsOf(pad, _r);
|
||||
for (var i = 0; i < 16; i++) {
|
||||
_s[i] = key[16 + i];
|
||||
}
|
||||
pad.fillRange(0, pad.length, 0);
|
||||
}
|
||||
|
||||
final Int32List _r = Int32List(_limbs);
|
||||
final Int32List _h = Int32List(_limbs);
|
||||
final Int32List _m = Int32List(_limbs);
|
||||
final Uint8List _s = Uint8List(16);
|
||||
final Uint8List _pad = Uint8List(20);
|
||||
int _buffered = 0;
|
||||
|
||||
/// Adds [data] to the message.
|
||||
void add(List<int> data, [int start = 0, int? end]) {
|
||||
final stop = end ?? data.length;
|
||||
RangeError.checkValidRange(start, stop, data.length);
|
||||
var i = start;
|
||||
final pad = _pad;
|
||||
if (_buffered > 0) {
|
||||
while (_buffered < 16 && i < stop) {
|
||||
pad[_buffered++] = data[i++];
|
||||
}
|
||||
if (_buffered < 16) return;
|
||||
_block(16);
|
||||
}
|
||||
for (; i + 16 <= stop; i += 16) {
|
||||
for (var k = 0; k < 16; k++) {
|
||||
pad[k] = data[i + k];
|
||||
}
|
||||
_block(16);
|
||||
}
|
||||
while (i < stop) {
|
||||
pad[_buffered++] = data[i++];
|
||||
}
|
||||
}
|
||||
|
||||
// h = (h + the block of the first n bytes of _pad, with the byte 1 after
|
||||
// them) · r mod 2^130 - 5.
|
||||
void _block(int n) {
|
||||
final pad = _pad;
|
||||
pad[n] = 1;
|
||||
pad.fillRange(n + 1, pad.length, 0);
|
||||
final h = _h;
|
||||
final r = _r;
|
||||
final m = _m;
|
||||
_limbsOf(pad, m);
|
||||
pad.fillRange(0, pad.length, 0);
|
||||
_buffered = 0;
|
||||
for (var i = 0; i < _limbs; i++) {
|
||||
h[i] += m[i];
|
||||
}
|
||||
// d_i = the sum over j of h_j · r_(i-j), where a limb past 2^130 comes
|
||||
// back multiplied by 5, as 2^130 = 5 mod p. Each d_i is below 2^35.
|
||||
var carry = 0;
|
||||
for (var i = 0; i < _limbs; i++) {
|
||||
var d = carry;
|
||||
for (var j = 0; j <= i; j++) {
|
||||
d += h[j] * r[i - j];
|
||||
}
|
||||
for (var j = i + 1; j < _limbs; j++) {
|
||||
d += h[j] * 5 * r[i - j + _limbs];
|
||||
}
|
||||
carry = d ~/ _limbRadix;
|
||||
m[i] = d & _limbMask;
|
||||
}
|
||||
// The carry out of limb 9 is a multiple of 2^130: it comes back · 5.
|
||||
final v = m[0] + carry * 5;
|
||||
h[0] = v & _limbMask;
|
||||
h[1] = m[1] + v ~/ _limbRadix;
|
||||
for (var i = 2; i < _limbs; i++) {
|
||||
h[i] = m[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// The 16-byte tag of the message. The object is wiped and cannot be used
|
||||
/// again.
|
||||
Uint8List finish() {
|
||||
if (_buffered > 0) _block(_buffered);
|
||||
final h = _h;
|
||||
// Two full carries: every limb below 2^13, but for h_1, which may reach
|
||||
// 2^13 by one, and h below 2^130 + 2^26.
|
||||
for (var round = 0; round < 2; round++) {
|
||||
var c = 0;
|
||||
for (var i = 0; i < _limbs; i++) {
|
||||
final v = h[i] + c;
|
||||
c = v ~/ _limbRadix;
|
||||
h[i] = v & _limbMask;
|
||||
}
|
||||
final v = h[0] + c * 5;
|
||||
h[0] = v & _limbMask;
|
||||
h[1] += v ~/ _limbRadix;
|
||||
}
|
||||
// g = h + 5 - 2^130. The carry out of limb 9 is 1 exactly when
|
||||
// h + 5 >= 2^130, that is h >= p, and then h mod p = g.
|
||||
final g = _m;
|
||||
var c = 5;
|
||||
for (var i = 0; i < _limbs; i++) {
|
||||
final v = h[i] + c;
|
||||
c = v ~/ _limbRadix;
|
||||
g[i] = v & _limbMask;
|
||||
}
|
||||
final keep = 1 - c;
|
||||
for (var i = 0; i < _limbs; i++) {
|
||||
h[i] = keep * h[i] + c * g[i];
|
||||
}
|
||||
// (h + s) mod 2^128, little-endian. The limbs are added, not ORed, so
|
||||
// that h_1 = 2^13 carries into the next one.
|
||||
final tag = Uint8List(16);
|
||||
var acc = 0;
|
||||
var bits = 0;
|
||||
var limb = 0;
|
||||
var carry = 0;
|
||||
for (var i = 0; i < 16; i++) {
|
||||
while (bits < 8) {
|
||||
acc += h[limb++] << bits;
|
||||
bits += _limbBits;
|
||||
}
|
||||
final v = (acc & 0xff) + _s[i] + carry;
|
||||
tag[i] = v & 0xff;
|
||||
carry = v >>> 8;
|
||||
acc >>>= 8;
|
||||
bits -= 8;
|
||||
}
|
||||
wipe();
|
||||
return tag;
|
||||
}
|
||||
|
||||
/// Clears the key and the state.
|
||||
void wipe() {
|
||||
_r.fillRange(0, _limbs, 0);
|
||||
_h.fillRange(0, _limbs, 0);
|
||||
_m.fillRange(0, _limbs, 0);
|
||||
_s.fillRange(0, 16, 0);
|
||||
_pad.fillRange(0, _pad.length, 0);
|
||||
_buffered = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// The Poly1305 tag of [message] under the one-time [key].
|
||||
Uint8List poly1305(List<int> key, List<int> message) =>
|
||||
(Poly1305(key)..add(message)).finish();
|
||||
|
||||
/// Whether [a] and [b] are equal, in a time that depends only on their
|
||||
/// lengths.
|
||||
bool constantTimeEquals(List<int> a, List<int> b) {
|
||||
if (a.length != b.length) return false;
|
||||
var d = 0;
|
||||
for (var i = 0; i < a.length; i++) {
|
||||
d |= a[i] ^ b[i];
|
||||
}
|
||||
return d == 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ChaCha20-Poly1305
|
||||
|
||||
final _zeros = Uint8List(16);
|
||||
|
||||
Uint8List _tag(
|
||||
ChaCha20 cipher,
|
||||
List<int> aad,
|
||||
Uint8List ciphertext,
|
||||
int start,
|
||||
int end,
|
||||
) =>
|
||||
// The one-time key is the first 32 bytes of block 0 (RFC 8439, 2.6); the
|
||||
// rest of that block is discarded, and the message starts at block 1.
|
||||
_tagWith(cipher.keystream(64), aad, ciphertext, start, end);
|
||||
|
||||
// An int below 2^53 as 8 little-endian bytes, without a 64-bit shift.
|
||||
Uint8List _le64(int v) {
|
||||
final out = Uint8List(8);
|
||||
var x = v;
|
||||
for (var i = 0; i < 8; i++) {
|
||||
out[i] = x & 0xff;
|
||||
x = x ~/ 256;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/// The AEAD ChaCha20-Poly1305 of RFC 8439, 2.8, as Go's chacha20poly1305:
|
||||
/// the ciphertext followed by the 16-byte tag.
|
||||
Uint8List chacha20Poly1305Seal(
|
||||
List<int> key,
|
||||
List<int> nonce,
|
||||
List<int> plaintext, [
|
||||
List<int> aad = const [],
|
||||
]) {
|
||||
final cipher = ChaCha20(key, nonce);
|
||||
final out = Uint8List(plaintext.length + poly1305TagSize);
|
||||
out.setRange(0, plaintext.length, plaintext);
|
||||
try {
|
||||
// Block 0 gives the one-time key; the message starts at block 1.
|
||||
final otk = cipher.keystream(64);
|
||||
cipher.xorInPlace(out, 0, plaintext.length);
|
||||
final tag = _tagWith(otk, aad, out, 0, plaintext.length);
|
||||
out.setRange(plaintext.length, out.length, tag);
|
||||
return out;
|
||||
} finally {
|
||||
cipher.wipe();
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens the [ciphertext] (with its tag) of ChaCha20-Poly1305: the plaintext,
|
||||
/// or null when the tag does not verify. The tag is compared in constant
|
||||
/// time, and nothing is decrypted before it verifies.
|
||||
Uint8List? chacha20Poly1305Open(
|
||||
List<int> key,
|
||||
List<int> nonce,
|
||||
Uint8List ciphertext, [
|
||||
List<int> aad = const [],
|
||||
]) {
|
||||
if (ciphertext.length < poly1305TagSize) return null;
|
||||
final n = ciphertext.length - poly1305TagSize;
|
||||
final cipher = ChaCha20(key, nonce);
|
||||
try {
|
||||
final want = _tag(cipher, aad, ciphertext, 0, n);
|
||||
final got = Uint8List.sublistView(ciphertext, n);
|
||||
if (!constantTimeEquals(want, got)) return null;
|
||||
final out = Uint8List.fromList(Uint8List.sublistView(ciphertext, 0, n));
|
||||
cipher.xorInPlace(out);
|
||||
return out;
|
||||
} finally {
|
||||
cipher.wipe();
|
||||
}
|
||||
}
|
||||
|
||||
Uint8List _tagWith(
|
||||
Uint8List otk,
|
||||
List<int> aad,
|
||||
Uint8List ciphertext,
|
||||
int start,
|
||||
int end,
|
||||
) {
|
||||
final mac = Poly1305(otk.sublist(0, 32));
|
||||
otk.fillRange(0, otk.length, 0);
|
||||
final n = end - start;
|
||||
mac
|
||||
..add(aad)
|
||||
..add(_zeros, 0, (16 - aad.length % 16) % 16)
|
||||
..add(ciphertext, start, end)
|
||||
..add(_zeros, 0, (16 - n % 16) % 16)
|
||||
..add(_le64(aad.length))
|
||||
..add(_le64(n));
|
||||
return mac.finish();
|
||||
}
|
||||
@ -0,0 +1,258 @@
|
||||
/// scrypt (RFC 7914), with Salsa20/8, BlockMix and ROMix as Go's
|
||||
/// golang.org/x/crypto/scrypt: the key derivation of the scrypt stanza of
|
||||
/// age, with which the file of an author key is encrypted (spec §29.12,
|
||||
/// logN = 16).
|
||||
///
|
||||
/// The words are 32 bits: sums of two words are masked and rotations are of
|
||||
/// 32-bit values, exact on the VM and when compiled to JavaScript. The
|
||||
/// memory is 128 · r · N bytes in one Uint32List: 64 MiB for logN = 16 and
|
||||
/// r = 8.
|
||||
///
|
||||
/// Internal: lib/datekeys.dart does not export it.
|
||||
library;
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'sha256.dart';
|
||||
|
||||
const _mask32 = 0xffffffff;
|
||||
|
||||
/// The error of Go's scrypt.Key for parameters it refuses.
|
||||
final class ScryptParameterException implements Exception {
|
||||
/// An exception with Go's [message].
|
||||
const ScryptParameterException(this.message);
|
||||
|
||||
/// The text of Go's error.
|
||||
final String message;
|
||||
|
||||
@override
|
||||
String toString() => message;
|
||||
}
|
||||
|
||||
/// scrypt of [password] and [salt] with cost [n], a power of two above 1,
|
||||
/// block size [r] and parallelism [p], [length] bytes: Go's scrypt.Key, with
|
||||
/// its checks and error texts. The p blocks are mixed one after the other.
|
||||
Uint8List scrypt(
|
||||
List<int> password,
|
||||
List<int> salt,
|
||||
int n,
|
||||
int r,
|
||||
int p,
|
||||
int length,
|
||||
) {
|
||||
if (n <= 1 || n & (n - 1) != 0) {
|
||||
throw const ScryptParameterException(
|
||||
'scrypt: N must be > 1 and a power of 2',
|
||||
);
|
||||
}
|
||||
if (r <= 0 || p <= 0) {
|
||||
throw const ScryptParameterException('scrypt: parameters must be > 0');
|
||||
}
|
||||
// Go's limits with maxInt = 2^53 - 1, the largest exact int of the web,
|
||||
// instead of 2^63 - 1: memory runs out long before either.
|
||||
const maxInt = 9007199254740991;
|
||||
if (r * p >= 1 << 30 ||
|
||||
r > maxInt ~/ 128 ~/ p ||
|
||||
r > maxInt ~/ 256 ||
|
||||
n > maxInt ~/ 128 ~/ r) {
|
||||
throw const ScryptParameterException('scrypt: parameters are too large');
|
||||
}
|
||||
final blockWords = 32 * r;
|
||||
final b = pbkdf2HmacSha256(password, salt, 1, p * 128 * r);
|
||||
final xy = Uint32List(2 * blockWords);
|
||||
final v = Uint32List(blockWords * n);
|
||||
final tmp = Uint32List(16);
|
||||
final x = Uint32List(16);
|
||||
try {
|
||||
for (var i = 0; i < p; i++) {
|
||||
_smix(b, i * 128 * r, r, n, v, xy, tmp, x);
|
||||
}
|
||||
return pbkdf2HmacSha256(password, b, 1, length);
|
||||
} finally {
|
||||
b.fillRange(0, b.length, 0);
|
||||
xy.fillRange(0, xy.length, 0);
|
||||
v.fillRange(0, v.length, 0);
|
||||
tmp.fillRange(0, 16, 0);
|
||||
x.fillRange(0, 16, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// ROMix of the block of b at offset, in place.
|
||||
void _smix(
|
||||
Uint8List b,
|
||||
int offset,
|
||||
int r,
|
||||
int n,
|
||||
Uint32List v,
|
||||
Uint32List xy,
|
||||
Uint32List tmp,
|
||||
Uint32List x,
|
||||
) {
|
||||
final words = 32 * r;
|
||||
for (var i = 0; i < words; i++) {
|
||||
final o = offset + 4 * i;
|
||||
xy[i] = b[o] | b[o + 1] << 8 | b[o + 2] << 16 | (b[o + 3] << 24 & _mask32);
|
||||
}
|
||||
// X is xy[0..words), Y is xy[words..2·words).
|
||||
for (var i = 0; i < n; i += 2) {
|
||||
v.setRange(i * words, (i + 1) * words, xy);
|
||||
_blockMix(tmp, x, xy, 0, xy, words, r);
|
||||
v.setRange((i + 1) * words, (i + 2) * words, xy, words);
|
||||
_blockMix(tmp, x, xy, words, xy, 0, r);
|
||||
}
|
||||
// Integerify: the first word of the last 64-byte block, below N ≤ 2^30, so
|
||||
// the second word of the little-endian integer never counts.
|
||||
final last = (2 * r - 1) * 16;
|
||||
for (var i = 0; i < n; i += 2) {
|
||||
var j = xy[last] & (n - 1);
|
||||
_xorBlock(xy, 0, v, j * words, words);
|
||||
_blockMix(tmp, x, xy, 0, xy, words, r);
|
||||
j = xy[words + last] & (n - 1);
|
||||
_xorBlock(xy, words, v, j * words, words);
|
||||
_blockMix(tmp, x, xy, words, xy, 0, r);
|
||||
}
|
||||
for (var i = 0; i < words; i++) {
|
||||
final w = xy[i];
|
||||
final o = offset + 4 * i;
|
||||
b[o] = w & 0xff;
|
||||
b[o + 1] = w >>> 8 & 0xff;
|
||||
b[o + 2] = w >>> 16 & 0xff;
|
||||
b[o + 3] = w >>> 24;
|
||||
}
|
||||
}
|
||||
|
||||
void _xorBlock(Uint32List dst, int d, Uint32List src, int s, int n) {
|
||||
for (var i = 0; i < n; i++) {
|
||||
dst[d + i] ^= src[s + i];
|
||||
}
|
||||
}
|
||||
|
||||
// BlockMix with Salsa20/8 of the 2r blocks of 16 words at in[inOff..], into
|
||||
// out[outOff..]: the even blocks first, then the odd ones, as RFC 7914 and
|
||||
// Go's blockMix.
|
||||
void _blockMix(
|
||||
Uint32List tmp,
|
||||
Uint32List x,
|
||||
Uint32List input,
|
||||
int inOff,
|
||||
Uint32List out,
|
||||
int outOff,
|
||||
int r,
|
||||
) {
|
||||
tmp.setRange(0, 16, input, inOff + (2 * r - 1) * 16);
|
||||
for (var i = 0; i < 2 * r; i += 2) {
|
||||
_salsaXor(tmp, x, input, inOff + i * 16, out, outOff + i * 8);
|
||||
_salsaXor(tmp, x, input, inOff + i * 16 + 16, out, outOff + i * 8 + r * 16);
|
||||
}
|
||||
}
|
||||
|
||||
// tmp = Salsa20/8(tmp ^ in[inOff..+16]), also written to out[outOff..+16].
|
||||
void _salsaXor(
|
||||
Uint32List tmp,
|
||||
Uint32List w,
|
||||
Uint32List input,
|
||||
int inOff,
|
||||
Uint32List out,
|
||||
int outOff,
|
||||
) {
|
||||
for (var i = 0; i < 16; i++) {
|
||||
w[i] = tmp[i] ^ input[inOff + i];
|
||||
}
|
||||
var x0 = w[0], x1 = w[1], x2 = w[2], x3 = w[3];
|
||||
var x4 = w[4], x5 = w[5], x6 = w[6], x7 = w[7];
|
||||
var x8 = w[8], x9 = w[9], x10 = w[10], x11 = w[11];
|
||||
var x12 = w[12], x13 = w[13], x14 = w[14], x15 = w[15];
|
||||
for (var i = 0; i < 8; i += 2) {
|
||||
// Columns.
|
||||
var u = (x0 + x12) & _mask32;
|
||||
x4 ^= (u << 7 & _mask32) | u >>> 25;
|
||||
u = (x4 + x0) & _mask32;
|
||||
x8 ^= (u << 9 & _mask32) | u >>> 23;
|
||||
u = (x8 + x4) & _mask32;
|
||||
x12 ^= (u << 13 & _mask32) | u >>> 19;
|
||||
u = (x12 + x8) & _mask32;
|
||||
x0 ^= (u << 18 & _mask32) | u >>> 14;
|
||||
|
||||
u = (x5 + x1) & _mask32;
|
||||
x9 ^= (u << 7 & _mask32) | u >>> 25;
|
||||
u = (x9 + x5) & _mask32;
|
||||
x13 ^= (u << 9 & _mask32) | u >>> 23;
|
||||
u = (x13 + x9) & _mask32;
|
||||
x1 ^= (u << 13 & _mask32) | u >>> 19;
|
||||
u = (x1 + x13) & _mask32;
|
||||
x5 ^= (u << 18 & _mask32) | u >>> 14;
|
||||
|
||||
u = (x10 + x6) & _mask32;
|
||||
x14 ^= (u << 7 & _mask32) | u >>> 25;
|
||||
u = (x14 + x10) & _mask32;
|
||||
x2 ^= (u << 9 & _mask32) | u >>> 23;
|
||||
u = (x2 + x14) & _mask32;
|
||||
x6 ^= (u << 13 & _mask32) | u >>> 19;
|
||||
u = (x6 + x2) & _mask32;
|
||||
x10 ^= (u << 18 & _mask32) | u >>> 14;
|
||||
|
||||
u = (x15 + x11) & _mask32;
|
||||
x3 ^= (u << 7 & _mask32) | u >>> 25;
|
||||
u = (x3 + x15) & _mask32;
|
||||
x7 ^= (u << 9 & _mask32) | u >>> 23;
|
||||
u = (x7 + x3) & _mask32;
|
||||
x11 ^= (u << 13 & _mask32) | u >>> 19;
|
||||
u = (x11 + x7) & _mask32;
|
||||
x15 ^= (u << 18 & _mask32) | u >>> 14;
|
||||
|
||||
// Rows.
|
||||
u = (x0 + x3) & _mask32;
|
||||
x1 ^= (u << 7 & _mask32) | u >>> 25;
|
||||
u = (x1 + x0) & _mask32;
|
||||
x2 ^= (u << 9 & _mask32) | u >>> 23;
|
||||
u = (x2 + x1) & _mask32;
|
||||
x3 ^= (u << 13 & _mask32) | u >>> 19;
|
||||
u = (x3 + x2) & _mask32;
|
||||
x0 ^= (u << 18 & _mask32) | u >>> 14;
|
||||
|
||||
u = (x5 + x4) & _mask32;
|
||||
x6 ^= (u << 7 & _mask32) | u >>> 25;
|
||||
u = (x6 + x5) & _mask32;
|
||||
x7 ^= (u << 9 & _mask32) | u >>> 23;
|
||||
u = (x7 + x6) & _mask32;
|
||||
x4 ^= (u << 13 & _mask32) | u >>> 19;
|
||||
u = (x4 + x7) & _mask32;
|
||||
x5 ^= (u << 18 & _mask32) | u >>> 14;
|
||||
|
||||
u = (x10 + x9) & _mask32;
|
||||
x11 ^= (u << 7 & _mask32) | u >>> 25;
|
||||
u = (x11 + x10) & _mask32;
|
||||
x8 ^= (u << 9 & _mask32) | u >>> 23;
|
||||
u = (x8 + x11) & _mask32;
|
||||
x9 ^= (u << 13 & _mask32) | u >>> 19;
|
||||
u = (x9 + x8) & _mask32;
|
||||
x10 ^= (u << 18 & _mask32) | u >>> 14;
|
||||
|
||||
u = (x15 + x14) & _mask32;
|
||||
x12 ^= (u << 7 & _mask32) | u >>> 25;
|
||||
u = (x12 + x15) & _mask32;
|
||||
x13 ^= (u << 9 & _mask32) | u >>> 23;
|
||||
u = (x13 + x12) & _mask32;
|
||||
x14 ^= (u << 13 & _mask32) | u >>> 19;
|
||||
u = (x14 + x13) & _mask32;
|
||||
x15 ^= (u << 18 & _mask32) | u >>> 14;
|
||||
}
|
||||
// A Uint32List keeps the low 32 bits of each sum.
|
||||
tmp[0] = x0 + w[0];
|
||||
tmp[1] = x1 + w[1];
|
||||
tmp[2] = x2 + w[2];
|
||||
tmp[3] = x3 + w[3];
|
||||
tmp[4] = x4 + w[4];
|
||||
tmp[5] = x5 + w[5];
|
||||
tmp[6] = x6 + w[6];
|
||||
tmp[7] = x7 + w[7];
|
||||
tmp[8] = x8 + w[8];
|
||||
tmp[9] = x9 + w[9];
|
||||
tmp[10] = x10 + w[10];
|
||||
tmp[11] = x11 + w[11];
|
||||
tmp[12] = x12 + w[12];
|
||||
tmp[13] = x13 + w[13];
|
||||
tmp[14] = x14 + w[14];
|
||||
tmp[15] = x15 + w[15];
|
||||
out.setRange(outOff, outOff + 16, tmp);
|
||||
}
|
||||
@ -0,0 +1,423 @@
|
||||
/// SHA-256 with a compression function of its own, and on it HMAC-SHA256,
|
||||
/// HKDF-SHA256 (RFC 5869) and PBKDF2-HMAC-SHA256 (RFC 8018).
|
||||
///
|
||||
/// package:crypto has SHA-256 and HMAC, but its HMAC computes the states of
|
||||
/// the two keys again for every message and allocates for every call. The
|
||||
/// word key of spec §38.1 runs PBKDF2 with 600 000 iterations, two HMACs per
|
||||
/// iteration: here the inner and outer states of the key are computed once,
|
||||
/// and each iteration is two compressions over 32-bit words in buffers that
|
||||
/// are reused, without bytes in between. The app still calls it in an
|
||||
/// Isolate (docs/PLAN_dart.md, «Rendimiento»).
|
||||
///
|
||||
/// The arithmetic is exact on the VM and when compiled to JavaScript: words
|
||||
/// are kept in 0..2^32-1, sums are of at most five words (below 2^35, exact
|
||||
/// in a double), and every shift, rotation and mask is of a 32-bit value, so
|
||||
/// that the 32-bit bit operators of the web give the same result as the
|
||||
/// 64-bit ones of the VM.
|
||||
///
|
||||
/// Internal: lib/datekeys.dart does not export it.
|
||||
library;
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
const _mask32 = 0xffffffff;
|
||||
|
||||
/// The size of a SHA-256 digest, of an HMAC-SHA256 tag and of a PRK of HKDF.
|
||||
const sha256Size = 32;
|
||||
|
||||
/// The size of a SHA-256 block.
|
||||
const sha256BlockSize = 64;
|
||||
|
||||
// The round constants of FIPS 180-4, 4.2.2.
|
||||
final Uint32List _k = Uint32List.fromList(const [
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, //
|
||||
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
||||
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
|
||||
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
|
||||
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
||||
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
|
||||
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
|
||||
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
||||
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
||||
]);
|
||||
|
||||
// The initial hash value of FIPS 180-4, 5.3.3.
|
||||
const _iv = [
|
||||
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, //
|
||||
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
||||
];
|
||||
|
||||
/// Compresses the 16 words of [w] (big-endian words of one block, w[0..15];
|
||||
/// w[16..63] are scratch) into the eight words of [state].
|
||||
void _compress(Uint32List state, Uint32List w) {
|
||||
for (var t = 16; t < 64; t++) {
|
||||
final x = w[t - 15];
|
||||
final y = w[t - 2];
|
||||
final s0 =
|
||||
((x >>> 7) | (x << 25) & _mask32) ^
|
||||
((x >>> 18) | (x << 14) & _mask32) ^
|
||||
(x >>> 3);
|
||||
final s1 =
|
||||
((y >>> 17) | (y << 15) & _mask32) ^
|
||||
((y >>> 19) | (y << 13) & _mask32) ^
|
||||
(y >>> 10);
|
||||
w[t] = (w[t - 16] + s0 + w[t - 7] + s1) & _mask32;
|
||||
}
|
||||
var a = state[0];
|
||||
var b = state[1];
|
||||
var c = state[2];
|
||||
var d = state[3];
|
||||
var e = state[4];
|
||||
var f = state[5];
|
||||
var g = state[6];
|
||||
var h = state[7];
|
||||
for (var t = 0; t < 64; t++) {
|
||||
final s1 =
|
||||
((e >>> 6) | (e << 26) & _mask32) ^
|
||||
((e >>> 11) | (e << 21) & _mask32) ^
|
||||
((e >>> 25) | (e << 7) & _mask32);
|
||||
// Ch(e, f, g) = (e & f) ^ (~e & g), with ~e as 32 bits on the VM too.
|
||||
final ch = (e & f) ^ ((e ^ _mask32) & g);
|
||||
final t1 = (h + s1 + ch + _k[t] + w[t]) & _mask32;
|
||||
final s0 =
|
||||
((a >>> 2) | (a << 30) & _mask32) ^
|
||||
((a >>> 13) | (a << 19) & _mask32) ^
|
||||
((a >>> 22) | (a << 10) & _mask32);
|
||||
final maj = (a & b) ^ (a & c) ^ (b & c);
|
||||
final t2 = (s0 + maj) & _mask32;
|
||||
h = g;
|
||||
g = f;
|
||||
f = e;
|
||||
e = (d + t1) & _mask32;
|
||||
d = c;
|
||||
c = b;
|
||||
b = a;
|
||||
a = (t1 + t2) & _mask32;
|
||||
}
|
||||
state[0] = (state[0] + a) & _mask32;
|
||||
state[1] = (state[1] + b) & _mask32;
|
||||
state[2] = (state[2] + c) & _mask32;
|
||||
state[3] = (state[3] + d) & _mask32;
|
||||
state[4] = (state[4] + e) & _mask32;
|
||||
state[5] = (state[5] + f) & _mask32;
|
||||
state[6] = (state[6] + g) & _mask32;
|
||||
state[7] = (state[7] + h) & _mask32;
|
||||
}
|
||||
|
||||
/// Loads the block of [bytes] at [offset] into w[0..15], big-endian.
|
||||
void _load(Uint32List w, List<int> bytes, int offset) {
|
||||
for (var i = 0; i < 16; i++) {
|
||||
final o = offset + 4 * i;
|
||||
w[i] =
|
||||
bytes[o] << 24 & _mask32 |
|
||||
bytes[o + 1] << 16 |
|
||||
bytes[o + 2] << 8 |
|
||||
bytes[o + 3];
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the eight words of [state] big-endian into [out] at [offset].
|
||||
void _store(Uint32List state, Uint8List out, int offset) {
|
||||
for (var i = 0; i < 8; i++) {
|
||||
final v = state[i];
|
||||
out[offset + 4 * i] = v >>> 24;
|
||||
out[offset + 4 * i + 1] = v >>> 16 & 0xff;
|
||||
out[offset + 4 * i + 2] = v >>> 8 & 0xff;
|
||||
out[offset + 4 * i + 3] = v & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
/// An incremental SHA-256 (FIPS 180-4). After [finish] it must not be used
|
||||
/// again.
|
||||
final class Sha256 {
|
||||
/// A hash of nothing yet.
|
||||
Sha256() : _state = Uint32List.fromList(_iv);
|
||||
|
||||
// A hash that continues from [state] after [length] bytes, all of them
|
||||
// whole blocks: the precomputed states of HMAC.
|
||||
Sha256._from(Uint32List state, this._length)
|
||||
: _state = Uint32List.fromList(state);
|
||||
|
||||
final Uint32List _state;
|
||||
final Uint32List _w = Uint32List(64);
|
||||
final Uint8List _block = Uint8List(sha256BlockSize);
|
||||
int _buffered = 0;
|
||||
// The number of bytes hashed: an int, exact up to 2^53 - 1, far beyond any
|
||||
// input of this library.
|
||||
int _length = 0;
|
||||
|
||||
/// Hashes [data], or its bytes from [start] to [end].
|
||||
void add(List<int> data, [int start = 0, int? end]) {
|
||||
final stop = end ?? data.length;
|
||||
RangeError.checkValidRange(start, stop, data.length);
|
||||
var i = start;
|
||||
_length += stop - start;
|
||||
if (_buffered > 0) {
|
||||
while (_buffered < sha256BlockSize && i < stop) {
|
||||
_block[_buffered++] = data[i++];
|
||||
}
|
||||
if (_buffered < sha256BlockSize) return;
|
||||
_load(_w, _block, 0);
|
||||
_compress(_state, _w);
|
||||
_buffered = 0;
|
||||
}
|
||||
for (; i + sha256BlockSize <= stop; i += sha256BlockSize) {
|
||||
_load(_w, data, i);
|
||||
_compress(_state, _w);
|
||||
}
|
||||
while (i < stop) {
|
||||
_block[_buffered++] = data[i++];
|
||||
}
|
||||
}
|
||||
|
||||
/// The digest of everything added: 32 bytes.
|
||||
Uint8List finish() {
|
||||
final out = Uint8List(sha256Size);
|
||||
finishInto(out, 0);
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Writes the digest into [out] at [offset].
|
||||
void finishInto(Uint8List out, int offset) {
|
||||
// The length in bits, as two 32-bit halves without a 64-bit shift.
|
||||
final bits = _length * 8;
|
||||
final high = bits ~/ 0x100000000;
|
||||
final low = bits - high * 0x100000000;
|
||||
_block[_buffered++] = 0x80;
|
||||
if (_buffered > 56) {
|
||||
_block.fillRange(_buffered, sha256BlockSize, 0);
|
||||
_load(_w, _block, 0);
|
||||
_compress(_state, _w);
|
||||
_buffered = 0;
|
||||
}
|
||||
_block.fillRange(_buffered, 56, 0);
|
||||
_load(_w, _block, 0);
|
||||
_w[14] = high;
|
||||
_w[15] = low;
|
||||
_compress(_state, _w);
|
||||
_store(_state, out, offset);
|
||||
_block.fillRange(0, sha256BlockSize, 0);
|
||||
_w.fillRange(0, 64, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// The SHA-256 digest of [data].
|
||||
Uint8List sha256(List<int> data) => (Sha256()..add(data)).finish();
|
||||
|
||||
/// HMAC-SHA256 (RFC 2104) with a key fixed once: the states after the
|
||||
/// blocks of the key XOR ipad and XOR opad are computed in the constructor,
|
||||
/// so that each tag costs only the compressions of the message and two of
|
||||
/// the padding. [wipe] clears them.
|
||||
final class HmacSha256 {
|
||||
/// The HMAC of [key], of any length: a key longer than a block is hashed
|
||||
/// first, as RFC 2104 says.
|
||||
HmacSha256(List<int> key) {
|
||||
final k = Uint8List(sha256BlockSize);
|
||||
if (key.length > sha256BlockSize) {
|
||||
k.setAll(0, sha256(key));
|
||||
} else {
|
||||
k.setAll(0, key);
|
||||
}
|
||||
final w = Uint32List(64);
|
||||
for (var i = 0; i < sha256BlockSize; i++) {
|
||||
k[i] ^= 0x36;
|
||||
}
|
||||
_load(w, k, 0);
|
||||
_inner.setAll(0, _iv);
|
||||
_compress(_inner, w);
|
||||
for (var i = 0; i < sha256BlockSize; i++) {
|
||||
k[i] ^= 0x36 ^ 0x5c;
|
||||
}
|
||||
_load(w, k, 0);
|
||||
_outer.setAll(0, _iv);
|
||||
_compress(_outer, w);
|
||||
k.fillRange(0, sha256BlockSize, 0);
|
||||
w.fillRange(0, 64, 0);
|
||||
}
|
||||
|
||||
final Uint32List _inner = Uint32List(8);
|
||||
final Uint32List _outer = Uint32List(8);
|
||||
|
||||
/// The tag of [message]: 32 bytes.
|
||||
Uint8List mac(List<int> message) {
|
||||
final h = Sha256._from(_inner, sha256BlockSize)..add(message);
|
||||
final inner = h.finish();
|
||||
final o = Sha256._from(_outer, sha256BlockSize)..add(inner);
|
||||
inner.fillRange(0, sha256Size, 0);
|
||||
return o.finish();
|
||||
}
|
||||
|
||||
/// The tag of the concatenation of [parts].
|
||||
Uint8List macAll(Iterable<List<int>> parts) {
|
||||
final h = Sha256._from(_inner, sha256BlockSize);
|
||||
for (final p in parts) {
|
||||
h.add(p);
|
||||
}
|
||||
final inner = h.finish();
|
||||
final o = Sha256._from(_outer, sha256BlockSize)..add(inner);
|
||||
inner.fillRange(0, sha256Size, 0);
|
||||
return o.finish();
|
||||
}
|
||||
|
||||
/// Clears the states of the key.
|
||||
void wipe() {
|
||||
_inner.fillRange(0, 8, 0);
|
||||
_outer.fillRange(0, 8, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// The HMAC-SHA256 of [message] under [key].
|
||||
Uint8List hmacSha256(List<int> key, List<int> message) {
|
||||
final h = HmacSha256(key);
|
||||
try {
|
||||
return h.mac(message);
|
||||
} finally {
|
||||
h.wipe();
|
||||
}
|
||||
}
|
||||
|
||||
/// HKDF-Extract of RFC 5869 with SHA-256: the PRK of [ikm]. An empty or null
|
||||
/// [salt] is the string of 32 zero bytes, as the RFC says and as Go's
|
||||
/// hkdf.Extract does.
|
||||
Uint8List hkdfExtract(List<int> ikm, List<int>? salt) {
|
||||
final s = salt == null || salt.isEmpty ? Uint8List(sha256Size) : salt;
|
||||
return hmacSha256(s, ikm);
|
||||
}
|
||||
|
||||
/// HKDF-Expand of RFC 5869 with SHA-256: [length] bytes, at most 255 · 32,
|
||||
/// from [prk] and [info].
|
||||
Uint8List hkdfExpand(List<int> prk, List<int> info, int length) {
|
||||
if (length < 0 || length > 255 * sha256Size) {
|
||||
throw RangeError.range(length, 0, 255 * sha256Size, 'length');
|
||||
}
|
||||
final h = HmacSha256(prk);
|
||||
final out = Uint8List(length);
|
||||
var t = Uint8List(0);
|
||||
try {
|
||||
for (var i = 1, o = 0; o < length; i++) {
|
||||
final next = h.macAll([
|
||||
t,
|
||||
info,
|
||||
[i],
|
||||
]);
|
||||
t.fillRange(0, t.length, 0);
|
||||
t = next;
|
||||
final n = length - o < sha256Size ? length - o : sha256Size;
|
||||
out.setRange(o, o + n, t);
|
||||
o += n;
|
||||
}
|
||||
} finally {
|
||||
t.fillRange(0, t.length, 0);
|
||||
h.wipe();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/// HKDF-SHA256 of RFC 5869, Extract then Expand, as Go's hkdf.New read for
|
||||
/// [length] bytes.
|
||||
Uint8List hkdfSha256(
|
||||
List<int> ikm,
|
||||
List<int>? salt,
|
||||
List<int> info,
|
||||
int length,
|
||||
) {
|
||||
final prk = hkdfExtract(ikm, salt);
|
||||
try {
|
||||
return hkdfExpand(prk, info, length);
|
||||
} finally {
|
||||
prk.fillRange(0, prk.length, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// PBKDF2 of RFC 8018 with HMAC-SHA256: [length] bytes of [password] and
|
||||
/// [salt] after [iterations] iterations, at least 1. Each iteration is two
|
||||
/// compressions from the precomputed states of the key, over words; spec
|
||||
/// §38.1 runs it with 600 000 iterations.
|
||||
Uint8List pbkdf2HmacSha256(
|
||||
List<int> password,
|
||||
List<int> salt,
|
||||
int iterations,
|
||||
int length,
|
||||
) {
|
||||
if (iterations < 1) {
|
||||
throw RangeError.range(iterations, 1, null, 'iterations');
|
||||
}
|
||||
if (length < 0) throw RangeError.range(length, 0, null, 'length');
|
||||
final h = HmacSha256(password);
|
||||
final out = Uint8List(length);
|
||||
// The block of the inner and of the outer hash of a 32-byte message after
|
||||
// the 64 bytes of the key: the message, 0x80, zeros and the length in bits,
|
||||
// (64 + 32) · 8 = 768.
|
||||
final w = Uint32List(64);
|
||||
final u = Uint32List(8);
|
||||
final acc = Uint32List(8);
|
||||
final state = Uint32List(8);
|
||||
final block = Uint8List(sha256Size);
|
||||
try {
|
||||
for (var i = 1, o = 0; o < length; i++) {
|
||||
// U1 = HMAC(P, S || INT(i)), through the general path.
|
||||
final u1 = h.macAll([
|
||||
salt,
|
||||
[i >>> 24 & 0xff, i >>> 16 & 0xff, i >>> 8 & 0xff, i & 0xff],
|
||||
]);
|
||||
_load16(u, u1);
|
||||
u1.fillRange(0, sha256Size, 0);
|
||||
acc.setAll(0, u);
|
||||
for (var j = 1; j < iterations; j++) {
|
||||
// The inner hash of U, then the outer hash of that.
|
||||
_hmacWords(h._inner, u, w, state);
|
||||
_hmacWords(h._outer, state, w, u);
|
||||
for (var k = 0; k < 8; k++) {
|
||||
acc[k] ^= u[k];
|
||||
}
|
||||
}
|
||||
_store(acc, block, 0);
|
||||
final n = length - o < sha256Size ? length - o : sha256Size;
|
||||
out.setRange(o, o + n, block);
|
||||
o += n;
|
||||
}
|
||||
} finally {
|
||||
h.wipe();
|
||||
w.fillRange(0, 64, 0);
|
||||
u.fillRange(0, 8, 0);
|
||||
acc.fillRange(0, 8, 0);
|
||||
state.fillRange(0, 8, 0);
|
||||
block.fillRange(0, sha256Size, 0);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// The eight big-endian words of a 32-byte digest.
|
||||
void _load16(Uint32List words, Uint8List digest) {
|
||||
for (var i = 0; i < 8; i++) {
|
||||
words[i] =
|
||||
digest[4 * i] << 24 & _mask32 |
|
||||
digest[4 * i + 1] << 16 |
|
||||
digest[4 * i + 2] << 8 |
|
||||
digest[4 * i + 3];
|
||||
}
|
||||
}
|
||||
|
||||
// out = the compression, from the precomputed state [from], of the block of
|
||||
// the eight words of [message] and the padding of a 96-byte input.
|
||||
void _hmacWords(
|
||||
Uint32List from,
|
||||
Uint32List message,
|
||||
Uint32List w,
|
||||
Uint32List out,
|
||||
) {
|
||||
for (var i = 0; i < 8; i++) {
|
||||
w[i] = message[i];
|
||||
}
|
||||
w[8] = 0x80000000;
|
||||
for (var i = 9; i < 15; i++) {
|
||||
w[i] = 0;
|
||||
}
|
||||
w[15] = 768;
|
||||
for (var i = 0; i < 8; i++) {
|
||||
out[i] = from[i];
|
||||
}
|
||||
_compress(out, w);
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
// The copy of test/vectors/primitives.json that primitives_test.dart reads,
|
||||
// a Dart constant for the tests compiled to JavaScript, is the JSON file
|
||||
// byte for byte: tool/gen_primitive_vectors.go writes both.
|
||||
@TestOn('vm')
|
||||
library;
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'vectors/primitives.g.dart';
|
||||
|
||||
void main() {
|
||||
test('primitives.g.dart holds primitives.json', () {
|
||||
final file = File('test/vectors/primitives.json').readAsStringSync();
|
||||
expect(primitivesJson, file);
|
||||
});
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue