twonly-app-dependencies/hashlib/lib/src/algorithms/blake2/blake2b_32bit.dart
2025-12-07 16:10:41 +01:00

354 lines
10 KiB
Dart

// Copyright (c) 2023, Sudipto Chandra
// All rights reserved. Check LICENSE file for details.
import 'dart:typed_data';
import 'package:hashlib/src/core/block_hash.dart';
import 'package:hashlib/src/core/mac_base.dart';
/*
| BLAKE2b |
--------------+------------------+
Bits in word | w = 64 |
Rounds in F | r = 12 |
Block bytes | bb = 128 |
Hash bytes | 1 <= nn <= 64 |
Key bytes | 0 <= kk <= 64 |
Input bytes | 0 <= ll < 2**128 |
--------------+------------------+
G Rotation | (R1, R2, R3, R4) |
constants = | (32, 24, 16, 63) |
--------------+------------------+
*/
const int _mask32 = 0xFFFFFFFF;
const int _r1 = 32;
const int _r2 = 24;
const int _r3 = 16;
const int _r4 = 63;
const _seed = [
0xF3BCC908, 0x6A09E667, //
0x84CAA73B, 0xBB67AE85,
0xFE94F82B, 0x3C6EF372,
0x5F1D36F1, 0xA54FF53A,
0xADE682D1, 0x510E527F,
0x2B3E6C1F, 0x9B05688C,
0xFB41BD6B, 0x1F83D9AB,
0x137E2179, 0x5BE0CD19,
];
// This is used to contruct sigma2 map. Kept for future reference.
// const _sigma = [
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], // round 0
// [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], // round 1
// [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], // round 2
// [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], // round 3
// [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], // round 4
// [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], // round 5
// [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], // round 6
// [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], // round 7
// [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], // round 8
// [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], // round 9
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], // round 10
// [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], // round 11
// ];
// _sigma.map((e) => e.map((e) => e << 1).toList()).toList();
const _sigma2 = [
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30], // round 0
[28, 20, 8, 16, 18, 30, 26, 12, 2, 24, 0, 4, 22, 14, 10, 6], // round 1
[22, 16, 24, 0, 10, 4, 30, 26, 20, 28, 6, 12, 14, 2, 18, 8], // round 2
[14, 18, 6, 2, 26, 24, 22, 28, 4, 12, 10, 20, 8, 0, 30, 16], // round 3
[18, 0, 10, 14, 4, 8, 20, 30, 28, 2, 22, 24, 12, 16, 6, 26], // round 4
[4, 24, 12, 20, 0, 22, 16, 6, 8, 26, 14, 10, 30, 28, 2, 18], // round 5
[24, 10, 2, 30, 28, 26, 8, 20, 0, 14, 12, 6, 18, 4, 16, 22], // round 6
[26, 22, 14, 28, 24, 2, 6, 18, 10, 0, 30, 8, 16, 12, 4, 20], // round 7
[12, 30, 28, 18, 22, 6, 0, 16, 24, 4, 26, 14, 2, 8, 20, 10], // round 8
[20, 4, 16, 8, 14, 12, 2, 10, 30, 22, 18, 28, 6, 24, 26, 0], // round 9
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30], // round 10
[28, 20, 8, 16, 18, 30, 26, 12, 2, 24, 0, 4, 22, 14, 10, 6], // round 11
];
const int _w0 = 0;
const int _w1 = _w0 + 2;
const int _w2 = _w1 + 2;
const int _w3 = _w2 + 2;
const int _w4 = _w3 + 2;
const int _w5 = _w4 + 2;
const int _w6 = _w5 + 2;
const int _w7 = _w6 + 2;
const int _w8 = _w7 + 2;
const int _w9 = _w8 + 2;
const int _w10 = _w9 + 2;
const int _w11 = _w10 + 2;
const int _w12 = _w11 + 2;
const int _w13 = _w12 + 2;
const int _w14 = _w13 + 2;
const int _w15 = _w14 + 2;
/// Implementation is derived from [RFC-7693][rfc] document for
/// "The BLAKE2 Cryptographic Hash and Message Authentication Code (MAC)".
///
/// For reference, the official [blake2][blake2] implementation was followed.
///
/// Note that blake2b uses 64-bit operations.
///
/// [rfc]: https://www.ietf.org/rfc/rfc7693.html
/// [blake2]: https://github.com/BLAKE2/BLAKE2/blob/master/ref/blake2b-ref.c
class Blake2bHash extends BlockHashSink implements MACSinkBase {
final List<int>? key;
late int _s0, _s1, _s2, _s3, _s4, _s5, _s6, _s7;
late int _s8, _s9, _s10, _s11, _s12, _s13, _s14, _s15;
final Uint32List _var = Uint32List(_w15 + 2);
final Uint32List state = Uint32List(_seed.length);
@override
final int hashLength;
@override
final int derivedKeyLength;
/// For internal use only.
Blake2bHash(
int digestSize, {
this.key,
List<int>? salt,
List<int>? aad,
}) : hashLength = digestSize,
derivedKeyLength = digestSize,
super(1024 >>> 3) {
if (digestSize < 1 || digestSize > 64) {
throw ArgumentError('The digest size must be between 1 and 64');
}
// Parameter block from the seed
_s0 = _seed[0] ^ 0x01010000 ^ hashLength;
_s1 = _seed[1];
_s2 = _seed[2];
_s3 = _seed[3];
_s4 = _seed[4];
_s5 = _seed[5];
_s6 = _seed[6];
_s7 = _seed[7];
_s8 = _seed[8];
_s9 = _seed[9];
_s10 = _seed[10];
_s11 = _seed[11];
_s12 = _seed[12];
_s13 = _seed[13];
_s14 = _seed[14];
_s15 = _seed[15];
if (key != null && key!.isNotEmpty) {
if (key!.length > 64) {
throw ArgumentError('The key should not be greater than 64 bytes');
}
// Add key length to parameter
_s0 ^= key!.length << 8;
}
if (salt != null && salt.isNotEmpty) {
if (salt.length != 16) {
throw ArgumentError('The valid length of salt is 16 bytes');
}
for (int i = 0, p = 0; i < 4; i++, p += 8) {
_s8 ^= (salt[i] & 0xFF) << p;
}
for (int i = 4, p = 0; i < 8; i++, p += 8) {
_s9 ^= (salt[i] & 0xFF) << p;
}
for (int i = 8, p = 0; i < 12; i++, p += 8) {
_s10 ^= (salt[i] & 0xFF) << p;
}
for (int i = 12, p = 0; i < 16; i++, p += 8) {
_s11 ^= (salt[i] & 0xFF) << p;
}
}
if (aad != null && aad.isNotEmpty) {
if (aad.length != 16) {
throw ArgumentError('The valid length of personalization is 16 bytes');
}
for (int i = 0, p = 0; i < 4; i++, p += 8) {
_s12 ^= (aad[i] & 0xFF) << p;
}
for (int i = 4, p = 0; i < 8; i++, p += 8) {
_s13 ^= (aad[i] & 0xFF) << p;
}
for (int i = 8, p = 0; i < 12; i++, p += 8) {
_s14 ^= (aad[i] & 0xFF) << p;
}
for (int i = 12, p = 0; i < 16; i++, p += 8) {
_s15 ^= (aad[i] & 0xFF) << p;
}
}
reset();
}
@override
void reset() {
super.reset();
state[0] = _s0;
state[1] = _s1;
state[2] = _s2;
state[3] = _s3;
state[4] = _s4;
state[5] = _s5;
state[6] = _s6;
state[7] = _s7;
state[8] = _s8;
state[9] = _s9;
state[10] = _s10;
state[11] = _s11;
state[12] = _s12;
state[13] = _s13;
state[14] = _s14;
state[15] = _s15;
// If the key is present, the first block is the key padded with zeroes
if (key != null) {
int i;
for (i = 0; i < key!.length; ++i) {
buffer[i] = key![i];
}
for (; i < blockLength; ++i) {
buffer[i] = 0;
}
messageLength = pos = blockLength;
}
}
@override
void $process(List<int> chunk, int start, int end) {
for (; start < end; start++, pos++, messageLength++) {
if (pos == blockLength) {
$update();
pos = 0;
}
buffer[pos] = chunk[start];
}
}
/// `v[k] = (v[i] << (64 - n)) | (v[i] >>> n)`
static void _rotr(int n, List<int> v, int i, int k) {
var a = v[i + 1];
var b = v[i];
if (n == 32) {
v[k + 1] = b;
v[k] = a;
} else if (n < 32) {
v[k + 1] = (b << (32 - n)) | (a >>> n);
v[k] = (a << (32 - n)) | (b >>> n);
} else {
v[k + 1] = (a << (64 - n)) | (b >>> (n - 32));
v[k] = (b << (64 - n)) | (a >>> (n - 32));
}
}
/// `v[k] = v[i] ^ v[j]`
static void _xor(List<int> v, int i, int j, int k) {
v[k] = v[i] ^ v[j];
v[k + 1] = v[i + 1] ^ v[j + 1];
}
/// `v[k] = v[i] + v[j]`
static void _add2(List<int> v, int i, int j, int k) {
var t = v[i] + v[j];
v[k] = t;
v[k + 1] = (v[i + 1] + v[j + 1]) + (v[k] < t ? 1 : 0);
}
/// `v[k] = v[i] + v[j] + n[x]`
static void _add3(List<int> v, int i, int j, List<int> n, int x, int k) {
var t = v[i] + v[j];
v[k] = t;
v[k + 1] = v[i + 1] + v[j + 1] + (v[k] < t ? 1 : 0);
t = v[k] + n[x];
v[k] += n[x];
v[k + 1] += n[x + 1] + (v[k] < t ? 1 : 0);
}
// The G function for mixing
static void _mix(
List<int> v,
int a,
int b,
int c,
int d,
List<int> m,
int x,
int y,
) {
// v[a] = (v[a] + v[b] + x);
_add3(v, a, b, m, x, a);
// v[d] = _rotr(v[d] ^ v[a], _r1);
_xor(v, d, a, d);
_rotr(_r1, v, d, d);
// v[c] = (v[c] + v[d]);
_add2(v, c, d, c);
// v[b] = _rotr(v[b] ^ v[c], _r2);
_xor(v, b, c, b);
_rotr(_r2, v, b, b);
// v[a] = (v[a] + v[b] + y);
_add3(v, a, b, m, y, a);
// v[d] = _rotr(v[d] ^ v[a], _r3);
_xor(v, d, a, d);
_rotr(_r3, v, d, d);
// v[c] = (v[c] + v[d]);
_add2(v, c, d, c);
// v[b] = _rotr(v[b] ^ v[c], _r4);
_xor(v, b, c, b);
_rotr(_r4, v, b, b);
}
@override
void $update([List<int>? block, int offset = 0, bool last = false]) {
// Copy state and seed
for (int i = 0; i < 16; ++i) {
_var[i] = state[i];
_var[_w8 + i] = _seed[i];
}
_var[_w12] ^= messageLength & _mask32;
_var[_w12 + 1] ^= (messageLength >>> 32) & _mask32;
if (last) {
_var[_w14] ^= _mask32;
_var[_w14 + 1] ^= _mask32;
}
// Cryptographic mixing
for (int i = 0; i < 12; i++) {
var s = _sigma2[i];
_mix(_var, _w0, _w4, _w8, _w12, sbuffer, s[0], s[1]);
_mix(_var, _w1, _w5, _w9, _w13, sbuffer, s[2], s[3]);
_mix(_var, _w2, _w6, _w10, _w14, sbuffer, s[4], s[5]);
_mix(_var, _w3, _w7, _w11, _w15, sbuffer, s[6], s[7]);
_mix(_var, _w0, _w5, _w10, _w15, sbuffer, s[8], s[9]);
_mix(_var, _w1, _w6, _w11, _w12, sbuffer, s[10], s[11]);
_mix(_var, _w2, _w7, _w8, _w13, sbuffer, s[12], s[13]);
_mix(_var, _w3, _w4, _w9, _w14, sbuffer, s[14], s[15]);
}
// Build new state
for (int i = 0; i < 16; ++i) {
state[i] ^= _var[i] ^ _var[_w8 + i];
}
}
@override
Uint8List $finalize() {
// Fill remaining buffer to put the message length at the end
for (; pos < blockLength; pos++) {
buffer[pos] = 0;
}
// Update with the final block
$update(buffer, 0, true);
// Convert the state to 8-bit byte array
return Uint8List.view(state.buffer).sublist(0, hashLength);
}
}