167 lines
5 KiB
Dart
167 lines
5 KiB
Dart
library src.srp_util;
|
|
|
|
import 'dart:math' as math;
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:pointycastle/pointycastle.dart';
|
|
|
|
class SRP6Util {
|
|
static final _byteMask = BigInt.from(0xff);
|
|
|
|
static BigInt calculateK(Digest digest, BigInt N, BigInt g) {
|
|
return hashPaddedPair(digest, N, N, g);
|
|
}
|
|
|
|
static BigInt calculateU(Digest digest, BigInt N, BigInt? A, BigInt? B) {
|
|
return hashPaddedPair(digest, N, A, B);
|
|
}
|
|
|
|
static BigInt calculateX(Digest digest, BigInt N, Uint8List salt,
|
|
Uint8List identity, Uint8List password) {
|
|
var output = Uint8List(digest.digestSize);
|
|
|
|
digest.update(identity, 0, identity.length);
|
|
digest.updateByte(':'.codeUnitAt(0));
|
|
digest.update(password, 0, password.length);
|
|
digest.doFinal(output, 0);
|
|
|
|
digest.update(salt, 0, salt.length);
|
|
digest.update(output, 0, output.length);
|
|
digest.doFinal(output, 0);
|
|
|
|
return decodeBigInt(output);
|
|
}
|
|
|
|
/// Decode a BigInt from bytes in big-endian encoding.
|
|
static BigInt decodeBigInt(List<int> bytes) {
|
|
var result = BigInt.from(0);
|
|
for (var i = 0; i < bytes.length; i++) {
|
|
result += BigInt.from(bytes[bytes.length - i - 1]) << (8 * i);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// Encode a BigInt into bytes using big-endian encoding.
|
|
static Uint8List encodeBigInt(BigInt number) {
|
|
// Not handling negative numbers. Decide how you want to do that.
|
|
var size = (number.bitLength + 7) >> 3;
|
|
var result = Uint8List(size);
|
|
for (var i = 0; i < size; i++) {
|
|
result[size - i - 1] = (number & _byteMask).toInt();
|
|
number = number >> 8;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static BigInt? generatePrivateValue(
|
|
Digest digest, BigInt N, BigInt g, SecureRandom random) {
|
|
var minBits = math.min(256, N.bitLength ~/ 2);
|
|
var min = BigInt.one << (minBits - 1);
|
|
var max = N - BigInt.one;
|
|
|
|
BigInt result;
|
|
do {
|
|
result = random.nextBigInteger(minBits);
|
|
} while (result > max || result < min);
|
|
return result;
|
|
}
|
|
|
|
static BigInt validatePublicValue(BigInt N, BigInt val) {
|
|
val = val % N;
|
|
|
|
// Check that val % N != 0
|
|
if (val == BigInt.zero) {
|
|
throw Exception('Invalid public value: 0');
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
/// Computes the client evidence message (M1) according to the standard routine:
|
|
/// M1 = H( A | B | S )
|
|
/// [digest] The Digest used as the hashing function H
|
|
/// [N] Modulus used to get the pad length
|
|
/// [A] The public client value
|
|
/// [B] The public server value
|
|
/// [S] The secret calculated by both sides
|
|
/// [M1] The calculated client evidence message
|
|
static BigInt calculateM1(
|
|
Digest digest, BigInt N, BigInt? A, BigInt? B, BigInt? S) {
|
|
return hashPaddedTriplet(digest, N, A, B, S);
|
|
}
|
|
|
|
/// Computes the server evidence message (M2) according to the standard routine:
|
|
/// M2 = H( A | M1 | S )
|
|
/// [digest] The Digest used as the hashing function H
|
|
/// [N] Modulus used to get the pad length
|
|
/// [A] The public client value
|
|
/// [M1] The client evidence message
|
|
/// [S] The secret calculated by both sides
|
|
/// @return M2 The calculated server evidence message
|
|
static BigInt calculateM2(
|
|
Digest digest, BigInt N, BigInt? A, BigInt? M1, BigInt? S) {
|
|
return hashPaddedTriplet(digest, N, A, M1, S);
|
|
}
|
|
|
|
/// Computes the final Key according to the standard routine: Key = H(S)
|
|
/// [digest] The Digest used as the hashing function H
|
|
/// [N] Modulus used to get the pad length
|
|
/// [S] The secret calculated by both sides
|
|
/// @return the final Key value.
|
|
static BigInt calculateKey(Digest digest, BigInt N, BigInt? S) {
|
|
var padLength = (N.bitLength + 7) ~/ 8;
|
|
var S0 = getPadded(S!, padLength);
|
|
digest.update(S0, 0, S0.length);
|
|
|
|
var output = Uint8List(digest.digestSize);
|
|
digest.doFinal(output, 0);
|
|
return decodeBigInt(output);
|
|
}
|
|
|
|
static BigInt hashPaddedTriplet(
|
|
Digest digest, BigInt N, BigInt? n1, BigInt? n2, BigInt? n3) {
|
|
var padLength = (N.bitLength + 7) ~/ 8;
|
|
|
|
var n1Bytes = getPadded(n1!, padLength);
|
|
var n2Bytes = getPadded(n2!, padLength);
|
|
var n3Bytes = getPadded(n3!, padLength);
|
|
|
|
digest.update(n1Bytes, 0, n1Bytes.length);
|
|
digest.update(n2Bytes, 0, n2Bytes.length);
|
|
digest.update(n3Bytes, 0, n3Bytes.length);
|
|
|
|
var output = Uint8List(digest.digestSize);
|
|
digest.doFinal(output, 0);
|
|
|
|
return decodeBigInt(output);
|
|
}
|
|
|
|
static Uint8List getPadded(BigInt n, int length) {
|
|
var bs = encodeBigInt(n);
|
|
if (bs.length < length) {
|
|
var tmp = Uint8List(length);
|
|
var start = length - bs.length;
|
|
for (var i = 0; start < length; i++, start++) {
|
|
tmp[start] = bs[i];
|
|
}
|
|
bs = tmp;
|
|
}
|
|
return bs;
|
|
}
|
|
|
|
static BigInt hashPaddedPair(
|
|
Digest digest, BigInt N, BigInt? n1, BigInt? n2) {
|
|
var padLength = (N.bitLength + 7) ~/ 8;
|
|
|
|
var n1Bytes = getPadded(n1!, padLength);
|
|
var n2Bytes = getPadded(n2!, padLength);
|
|
|
|
digest.update(n1Bytes, 0, n1Bytes.length);
|
|
digest.update(n2Bytes, 0, n2Bytes.length);
|
|
|
|
var output = Uint8List(digest.digestSize);
|
|
digest.doFinal(output, 0);
|
|
|
|
return decodeBigInt(output);
|
|
}
|
|
}
|