| 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); |
| } |
| } |