Merge branch 'denizt-srp' into master
diff --git a/lib/api.dart b/lib/api.dart
index 3f5865b..15b7736 100644
--- a/lib/api.dart
+++ b/lib/api.dart
@@ -36,21 +36,14 @@
 part 'src/api/parameters_with_salt_configuration.dart';
 part 'src/api/private_key.dart';
 part 'src/api/private_key_parameter.dart';
-
 part 'src/api/public_key.dart';
-
 part 'src/api/public_key_parameter.dart';
-
 part 'src/api/registry_factory_exception.dart';
-
 part 'src/api/secure_random.dart';
-
 part 'src/api/signature.dart';
-
 part 'src/api/signer.dart';
-
 part 'src/api/stream_cipher.dart';
-
+part "src/api/srp_client.dart";
+part "src/api/srp_server.dart";
 part 'src/api/aead_block_cipher.dart';
-
 part 'src/api/xof.dart';
diff --git a/lib/src/api/srp_client.dart b/lib/src/api/srp_client.dart
new file mode 100644
index 0000000..c29d539
--- /dev/null
+++ b/lib/src/api/srp_client.dart
@@ -0,0 +1,35 @@
+// See file LICENSE for more information.
+
+part of api;
+
+abstract class SRPClient {
+  ///Computes the client evidence message M1 using the previously received values.
+  ///To be called after calculating the secret S.
+  ///returns M1: the client side generated evidence message
+  ///throws Exception
+  BigInt? calculateClientEvidenceMessage();
+
+  ///Generates the secret S given the server's credentials
+  ///@param serverB The server's credentials
+  ///@return Client's verification message for the server
+  ///@throws Exception If server's credentials are invalid
+  ///
+  BigInt? calculateSecret(BigInt serverB);
+
+  /// Computes the final session key as a result of the SRP successful mutual authentication
+  /// To be called after verifying the server evidence message M2.
+  /// returns Key: the mutually authenticated symmetric session key
+  /// throws Exception
+  BigInt? calculateSessionKey();
+
+  /// Generates the client's credentials that are to be sent to the server.
+  /// @return The client's public value
+  BigInt? generateClientCredentials(
+      Uint8List salt, Uint8List identity, Uint8List password);
+
+  /// Authenticates the server evidence message M2 received and saves it only if correct.
+  /// [serverM2] the server side generated evidence message
+  /// return A boolean indicating if the server message M2 was the expected one.
+  /// throws Exception
+  bool verifyServerEvidenceMessage(BigInt serverM2);
+}
diff --git a/lib/src/api/srp_server.dart b/lib/src/api/srp_server.dart
new file mode 100644
index 0000000..ff5abad
--- /dev/null
+++ b/lib/src/api/srp_server.dart
@@ -0,0 +1,37 @@
+// See file LICENSE for more information.
+
+part of api;
+
+/// Implements the server side SRP-6a protocol. Note that this class is stateful, and therefore NOT threadsafe.
+/// This implementation of SRP is based on the optimized message sequence put forth by Thomas Wu in the paper
+/// "SRP-6: Improvements and Refinements to the Secure Remote Password Protocol, 2002"
+abstract class SRPServer {
+  /// Processes the client's credentials. If valid the shared secret is generated and returned.
+  /// @param clientA The client's credentials
+  /// @return A shared secret BigInt
+  /// @throws CryptoException If client's credentials are invalid
+  BigInt? calculateSecret(BigInt clientA);
+
+  /// Computes the final session key as a result of the SRP successful mutual authentication
+  /// To be called after calculating the server evidence message M2.
+  /// @return Key: the mutual authenticated symmetric session key
+  /// @throws CryptoException
+  BigInt? calculateSessionKey();
+
+  /// Generates the server's credentials that are to be sent to the client.
+  /// @return The server's public value
+  BigInt? generateServerCredentials();
+
+  /// Computes the server evidence message M2 using the previously verified values.
+  /// To be called after successfully verifying the client evidence message M1.
+  /// @return M2: the server side generated evidence message
+  /// @throws CryptoException
+  BigInt? calculateServerEvidenceMessage();
+
+  /// Authenticates the received client evidence message M1 and saves it only if correct.
+  /// To be called after calculating the secret S.
+  /// @param clientM1 the client side generated evidence message
+  /// @return A boolean indicating if the client message M1 was the expected one.
+  /// @throws CryptoException
+  bool verifyClientEvidenceMessage(BigInt clientM1);
+}
diff --git a/lib/src/registry/registration.dart b/lib/src/registry/registration.dart
index 8f71fcb..a89e756 100644
--- a/lib/src/registry/registration.dart
+++ b/lib/src/registry/registration.dart
@@ -94,6 +94,10 @@
 import 'package:pointycastle/signers/ecdsa_signer.dart';
 import 'package:pointycastle/signers/rsa_signer.dart';
 import 'package:pointycastle/src/registry/registry.dart';
+import 'package:pointycastle/srp/srp6_client.dart';
+import 'package:pointycastle/srp/srp6_server.dart';
+import 'package:pointycastle/srp/srp6_standard_groups.dart';
+import 'package:pointycastle/srp/srp6_verifier_generator.dart';
 import 'package:pointycastle/stream/chacha20poly1305.dart';
 import 'package:pointycastle/stream/chacha7539.dart';
 import 'package:pointycastle/stream/ctr.dart';
diff --git a/lib/srp/srp6_client.dart b/lib/srp/srp6_client.dart
new file mode 100644
index 0000000..a65164d
--- /dev/null
+++ b/lib/srp/srp6_client.dart
@@ -0,0 +1,104 @@
+library impl.srp_client;
+
+import 'dart:typed_data';
+
+import 'package:pointycastle/srp/srp6_standard_groups.dart';
+import 'package:pointycastle/srp/srp6_util.dart';
+import 'package:pointycastle/api.dart';
+
+class SRP6Client implements SRPClient {
+  late BigInt N;
+  late BigInt g;
+
+  BigInt? a;
+  BigInt? A;
+
+  BigInt? B;
+
+  BigInt? x;
+  BigInt? u;
+  BigInt? S;
+
+  BigInt? M1;
+  BigInt? M2;
+  BigInt? Key;
+
+  Digest digest;
+  SecureRandom random;
+  SRP6GroupParameters group;
+
+  SRP6Client(
+      {required this.group, required this.digest, required this.random}) {
+    g = group.g;
+    N = group.N;
+  }
+
+  @override
+  BigInt? calculateClientEvidenceMessage() {
+    // Verify pre-requirements
+    if (A == null || B == null || S == null) {
+      throw Exception(
+          'Impossible to compute M1: some data are missing from the previous operations (A,B,S)');
+    }
+    // compute the client evidence message 'M1'
+    M1 = SRP6Util.calculateM1(digest, N, A, B, S);
+    return M1;
+  }
+
+  ///S = (B - kg^x) ^ (a + ux)
+  BigInt? calculateS() {
+    var k = SRP6Util.calculateK(digest, N, g);
+    var exp = (u! * x!) + a!;
+    var tmp = g.modPow(x!, N) * (k % N);
+
+    return (B! - (tmp % (N))).modPow(exp, N);
+  }
+
+  @override
+  BigInt? calculateSecret(BigInt serverB) {
+    B = SRP6Util.validatePublicValue(N, serverB);
+    u = SRP6Util.calculateU(digest, N, A, B);
+    S = calculateS();
+    return S;
+  }
+
+  @override
+  BigInt? calculateSessionKey() {
+    // Verify pre-requirements (here we enforce a previous calculation of M1 and M2)
+    if (S == null || M1 == null || M2 == null) {
+      throw Exception(
+          'Impossible to compute Key: some data are missing from the previous operations (S,M1,M2)');
+    }
+    Key = SRP6Util.calculateKey(digest, N, S!);
+    return Key;
+  }
+
+  @override
+  BigInt? generateClientCredentials(
+      Uint8List salt, Uint8List identity, Uint8List password) {
+    x = SRP6Util.calculateX(digest, N, salt, identity, password);
+    a = selectPrivateValue();
+    A = g.modPow(a!, N);
+    return A;
+  }
+
+  BigInt? selectPrivateValue() {
+    return SRP6Util.generatePrivateValue(digest, N, g, random);
+  }
+
+  @override
+  bool verifyServerEvidenceMessage(BigInt serverM2) {
+    // Verify pre-requirements
+    if (A == null || M1 == null || S == null) {
+      throw Exception('Impossible to compute and verify M2: '
+          'some data are missing from the previous operations (A,M1,S)');
+    }
+    // Compute the own server evidence message 'M2'
+    var computedM2 = SRP6Util.calculateM2(digest, N, A, M1, S);
+    if (computedM2.compareTo(serverM2) == 0) {
+      M2 = serverM2;
+      return true;
+    }
+    return false;
+  }
+}
diff --git a/lib/srp/srp6_server.dart b/lib/srp/srp6_server.dart
new file mode 100644
index 0000000..47ec256
--- /dev/null
+++ b/lib/srp/srp6_server.dart
@@ -0,0 +1,101 @@
+library impl.srp_server;
+
+import 'package:pointycastle/srp/srp6_standard_groups.dart';
+import 'package:pointycastle/srp/srp6_util.dart';
+import 'package:pointycastle/api.dart';
+
+class SRP6Server implements SRPServer {
+  late BigInt N;
+  late BigInt g;
+
+  BigInt v;
+  SecureRandom random;
+  Digest digest;
+
+  BigInt? A;
+
+  BigInt? b;
+  BigInt? B;
+
+  BigInt? u;
+  BigInt? S;
+  BigInt? M1;
+  BigInt? M2;
+  BigInt? Key;
+
+  SRP6Server(
+      {required SRP6GroupParameters group,
+      required this.v,
+      required this.digest,
+      required this.random}) {
+    g = group.g;
+    N = group.N;
+  }
+
+  @override
+  BigInt? calculateSecret(BigInt clientA) {
+    A = SRP6Util.validatePublicValue(N, clientA);
+    u = SRP6Util.calculateU(digest, N, A, B);
+    S = _calculateS();
+
+    return S;
+  }
+
+  @override
+  BigInt? calculateServerEvidenceMessage() {
+    // Verify pre-requirements
+    if (A == null || M1 == null || S == null) {
+      throw Exception(
+          'Impossible to compute M2: some data are missing from the previous operations (A,M1,S)');
+    }
+
+    // Compute the server evidence message 'M2'
+    M2 = SRP6Util.calculateM2(digest, N, A!, M1!, S!);
+    return M2;
+  }
+
+  @override
+  BigInt? calculateSessionKey() {
+    // Verify pre-requirements
+    if (S == null || M1 == null || M2 == null) {
+      throw Exception(
+          'Impossible to compute Key: some data are missing from the previous operations (S,M1,M2)');
+    }
+    Key = SRP6Util.calculateKey(digest, N, S);
+    return Key;
+  }
+
+  @override
+  BigInt? generateServerCredentials() {
+    var k = SRP6Util.calculateK(digest, N, g);
+    b = selectPrivateValue();
+    B = ((k * v + g.modPow(b!, N)) % N);
+
+    return B;
+  }
+
+  BigInt? selectPrivateValue() {
+    return SRP6Util.generatePrivateValue(digest, N, g, random);
+  }
+
+  @override
+  bool verifyClientEvidenceMessage(BigInt clientM1) {
+    // Verify pre-requirements
+    if (A == null || B == null || S == null) {
+      throw Exception(
+          'Impossible to compute and verify M1: some data are missing from the previous operations (A,B,S)');
+    }
+
+    // Compute the own client evidence message 'M1'
+    var computedM1 = SRP6Util.calculateM1(digest, N, A, B, S);
+    if (computedM1.compareTo(clientM1) == 0) {
+      M1 = clientM1;
+      return true;
+    }
+    return false;
+  }
+
+  BigInt _calculateS() {
+    return ((v.modPow(u!, N) * A!) % N).modPow(b!, N);
+  }
+}
diff --git a/lib/srp/srp6_standard_groups.dart b/lib/srp/srp6_standard_groups.dart
new file mode 100644
index 0000000..2ae92ae
--- /dev/null
+++ b/lib/srp/srp6_standard_groups.dart
@@ -0,0 +1,176 @@
+library src.srp_standard_groups;
+
+class SRP6GroupParameters {
+  BigInt N, g;
+
+  SRP6GroupParameters({required this.N, required this.g});
+}
+
+class SRP6StandardGroups {
+  static final String rfc5054_1024_N =
+      'EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C'
+      '9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE4'
+      '8E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B29'
+      '7BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9A'
+      'FD5138FE8376435B9FC61D2FC0EB06E3';
+
+  static final String rfc5054_1024_g = '02';
+
+  /*
+  * * RFC 5054
+  * */
+  static final SRP6GroupParameters rfc5054_1024 =
+      fromNG(rfc5054_1024_N, rfc5054_1024_g);
+  static final String rfc5054_1536_N =
+      '9DEF3CAFB939277AB1F12A8617A47BBBDBA51DF499AC4C80BEEEA961'
+      '4B19CC4D5F4F5F556E27CBDE51C6A94BE4607A291558903BA0D0F843'
+      '80B655BB9A22E8DCDF028A7CEC67F0D08134B1C8B97989149B609E0B'
+      'E3BAB63D47548381DBC5B1FC764E3F4B53DD9DA1158BFD3E2B9C8CF5'
+      '6EDF019539349627DB2FD53D24B7C48665772E437D6C7F8CE442734A'
+      'F7CCB7AE837C264AE3A9BEB87F8A2FE9B8B5292E5A021FFF5E91479E'
+      '8CE7A28C2442C6F315180F93499A234DCF76E3FED135F9BB';
+  static final String rfc5054_1536_g = '02';
+
+  static final SRP6GroupParameters rfc5054_1536 =
+      fromNG(rfc5054_1536_N, rfc5054_1536_g);
+  static final String rfc5054_2048_N =
+      'AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC319294'
+      '3DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310D'
+      'CD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FB'
+      'D5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF74'
+      '7359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A'
+      '436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D'
+      '5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E73'
+      '03CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB6'
+      '94B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F'
+      '9E4AFF73';
+  static final String rfc5054_2048_g = '02';
+
+  static final SRP6GroupParameters rfc5054_2048 =
+      fromNG(rfc5054_2048_N, rfc5054_2048_g);
+  static final String rfc5054_3072_N =
+      'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08'
+      '8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B'
+      '302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9'
+      'A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6'
+      '49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8'
+      'FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D'
+      '670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C'
+      '180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718'
+      '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D'
+      '04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D'
+      'B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226'
+      '1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C'
+      'BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC'
+      'E0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF';
+  static final String rfc5054_3072_g = '05';
+
+  static final SRP6GroupParameters rfc5054_3072 =
+      fromNG(rfc5054_3072_N, rfc5054_3072_g);
+  static final String rfc5054_4096_N =
+      'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08'
+      '8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B'
+      '302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9'
+      'A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6'
+      '49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8'
+      'FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D'
+      '670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C'
+      '180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718'
+      '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D'
+      '04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D'
+      'B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226'
+      '1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C'
+      'BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC'
+      'E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26'
+      '99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB'
+      '04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2'
+      '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127'
+      'D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199'
+      'FFFFFFFFFFFFFFFF';
+  static final String rfc5054_4096_g = '05';
+
+  static final SRP6GroupParameters rfc5054_4096 =
+      fromNG(rfc5054_4096_N, rfc5054_4096_g);
+  static final String rfc5054_6144_N =
+      'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08'
+      '8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B'
+      '302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9'
+      'A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6'
+      '49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8'
+      'FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D'
+      '670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C'
+      '180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718'
+      '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D'
+      '04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D'
+      'B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226'
+      '1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C'
+      'BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC'
+      'E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26'
+      '99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB'
+      '04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2'
+      '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127'
+      'D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492'
+      '36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406'
+      'AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918'
+      'DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151'
+      '2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03'
+      'F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F'
+      'BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA'
+      'CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B'
+      'B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632'
+      '387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E'
+      '6DCC4024FFFFFFFFFFFFFFFF';
+  static final String rfc5054_6144_g = '05';
+
+  static final SRP6GroupParameters rfc5054_6144 =
+      fromNG(rfc5054_6144_N, rfc5054_6144_g);
+  static final String rfc5054_8192_N =
+      'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08'
+      '8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B'
+      '302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9'
+      'A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6'
+      '49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8'
+      'FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D'
+      '670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C'
+      '180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718'
+      '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D'
+      '04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D'
+      'B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226'
+      '1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C'
+      'BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC'
+      'E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26'
+      '99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB'
+      '04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2'
+      '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127'
+      'D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492'
+      '36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406'
+      'AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918'
+      'DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151'
+      '2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03'
+      'F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F'
+      'BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA'
+      'CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B'
+      'B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632'
+      '387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E'
+      '6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA'
+      '3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C'
+      '5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9'
+      '22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC886'
+      '2F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6'
+      '6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC5'
+      '0846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268'
+      '359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6'
+      'FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E71'
+      '60C980DD98EDD3DFFFFFFFFFFFFFFFFF';
+  static final String rfc5054_8192_g = '13';
+
+  static final SRP6GroupParameters rfc5054_8192 =
+      fromNG(rfc5054_8192_N, rfc5054_8192_g);
+  static BigInt fromHex(String hex) {
+    return BigInt.parse(hex, radix: 16);
+  }
+
+  static SRP6GroupParameters fromNG(String hexN, String hexG) {
+    return SRP6GroupParameters(N: fromHex(hexN), g: fromHex(hexG));
+  }
+}
diff --git a/lib/srp/srp6_util.dart b/lib/srp/srp6_util.dart
new file mode 100644
index 0000000..5733a75
--- /dev/null
+++ b/lib/srp/srp6_util.dart
@@ -0,0 +1,166 @@
+library src.srp_util;
+
+import 'dart:typed_data';
+import 'package:pointycastle/pointycastle.dart';
+import 'dart:math' as math;
+
+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;
+
+    var 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 _S = getPadded(S!, padLength);
+    digest.update(_S, 0, _S.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);
+  }
+}
diff --git a/lib/srp/srp6_verifier_generator.dart b/lib/srp/srp6_verifier_generator.dart
new file mode 100644
index 0000000..d7fa558
--- /dev/null
+++ b/lib/srp/srp6_verifier_generator.dart
@@ -0,0 +1,32 @@
+library src.srp_verifier_generator;
+
+import 'dart:typed_data';
+
+import 'package:pointycastle/srp/srp6_standard_groups.dart';
+import 'package:pointycastle/srp/srp6_util.dart';
+import 'package:pointycastle/pointycastle.dart';
+
+/// Generates new SRP verifier for user
+class SRP6VerifierGenerator {
+  late BigInt N;
+  late BigInt g;
+  Digest digest;
+
+  SRP6VerifierGenerator(
+      {required SRP6GroupParameters group, required this.digest}) {
+    N = group.N;
+    g = group.g;
+  }
+
+  /// Creates a new SRP verifier
+  /// [salt] The salt to use, generally should be large and random
+  /// [identity] The user's identifying information (eg. username)
+  /// [password] The user's password
+  /// returns A new verifier for use in future SRP authentication
+  BigInt generateVerifier(
+      Uint8List salt, Uint8List identity, Uint8List password) {
+    var x = SRP6Util.calculateX(digest, N, salt, identity, password);
+
+    return g.modPow(x, N);
+  }
+}
diff --git a/test/all_tests_web.dart b/test/all_tests_web.dart
index 2ee374c..a5c8fec 100644
--- a/test/all_tests_web.dart
+++ b/test/all_tests_web.dart
@@ -55,6 +55,7 @@
 import 'signers/ecdsa_signer_test.dart' as ecdsa_signer_test;
 import 'signers/pss_signer_test.dart' as pss_signer_test;
 import 'signers/rsa_signer_test.dart' as rsa_signer_test;
+import "srp/srp_test.dart" as srp_test;
 import 'src/ufixnum_test.dart' as ufixnum_test;
 import 'src/utils_test.dart' as utils_test;
 import 'stream/chacha20_test.dart' as chacha20_test;
@@ -115,6 +116,7 @@
   pss_signer_test.main();
   ufixnum_test.main();
   utils_test.main();
+  srp_test.main();
   salsa20_test.main();
   chacha20_test.main();
   chacha20poly1305_test.main();
diff --git a/test/srp/srp_test.dart b/test/srp/srp_test.dart
new file mode 100644
index 0000000..6b4ebe5
--- /dev/null
+++ b/test/srp/srp_test.dart
@@ -0,0 +1,299 @@
+library test.srp_test;
+
+import 'dart:typed_data';
+import 'package:pointycastle/srp/srp6_client.dart';
+import 'package:pointycastle/srp/srp6_server.dart';
+import 'package:pointycastle/srp/srp6_standard_groups.dart';
+import 'package:pointycastle/srp/srp6_util.dart';
+import 'package:pointycastle/srp/srp6_verifier_generator.dart';
+
+import 'package:convert/convert.dart';
+import 'package:pointycastle/pointycastle.dart';
+import 'package:test/test.dart';
+
+void main() {
+  group('SRP', () {
+    group('rfc5054:', () {
+      test('rfc5054AppendixBTestVectors', () {
+        var I = Uint8List.fromList('alice'.codeUnits);
+        var P = Uint8List.fromList('password123'.codeUnits);
+        var s =
+            Uint8List.fromList(hex.decode('BEB25379D1A8581EB5A727673A2441EE'));
+        var N = SRP6StandardGroups.rfc5054_1024.N;
+        var g = SRP6StandardGroups.rfc5054_1024.g;
+
+        var expect_k =
+            BigInt.parse('7556AA045AEF2CDD07ABAF0F665C3E818913186F', radix: 16);
+        var expect_x =
+            BigInt.parse('94B7555AABE9127CC58CCF4993DB6CF84D16C124', radix: 16);
+        var expect_v = BigInt.parse(
+            '7E273DE8696FFC4F4E337D05B4B375BEB0DDE1569E8FA00A9886D812'
+            '9BADA1F1822223CA1A605B530E379BA4729FDC59F105B4787E5186F5'
+            'C671085A1447B52A48CF1970B4FB6F8400BBF4CEBFBB168152E08AB5'
+            'EA53D15C1AFF87B2B9DA6E04E058AD51CC72BFC9033B564E26480D78'
+            'E955A5E29E7AB245DB2BE315E2099AFB',
+            radix: 16);
+        var expect_A = BigInt.parse(
+            '61D5E490F6F1B79547B0704C436F523DD0E560F0C64115BB72557EC4'
+            '4352E8903211C04692272D8B2D1A5358A2CF1B6E0BFCF99F921530EC'
+            '8E39356179EAE45E42BA92AEACED825171E1E8B9AF6D9C03E1327F44'
+            'BE087EF06530E69F66615261EEF54073CA11CF5858F0EDFDFE15EFEA'
+            'B349EF5D76988A3672FAC47B0769447B',
+            radix: 16);
+        var expect_B = BigInt.parse(
+            'BD0C61512C692C0CB6D041FA01BB152D4916A1E77AF46AE105393011'
+            'BAF38964DC46A0670DD125B95A981652236F99D9B681CBF87837EC99'
+            '6C6DA04453728610D0C6DDB58B318885D7D82C7F8DEB75CE7BD4FBAA'
+            '37089E6F9C6059F388838E7A00030B331EB76840910440B1B27AAEAE'
+            'EB4012B7D7665238A8E3FB004B117B58',
+            radix: 16);
+        var expect_u =
+            BigInt.parse('CE38B9593487DA98554ED47D70A7AE5F462EF019', radix: 16);
+        var expect_S = BigInt.parse(
+            'B0DC82BABCF30674AE450C0287745E7990A3381F63B387AAF271A10D'
+            '233861E359B48220F7C4693C9AE12B0A6F67809F0876E2D013800D6C'
+            '41BB59B6D5979B5C00A172B4A2A5903A0BDCAF8A709585EB2AFAFA8F'
+            '3499B200210DCC1F10EB33943CD67FC88A2F39A4BE5BEC4EC0A3212D'
+            'C346D7E474B29EDE8A469FFECA686E5A',
+            radix: 16);
+
+        var k = SRP6Util.calculateK(Digest('SHA-1'), N, g);
+        if (k.compareTo(expect_k) != 0) {
+          fail("wrong value of 'k', expected $expect_k got $k");
+        }
+
+        var x = SRP6Util.calculateX(Digest('SHA-1'), N, s, I, P);
+        if (x.compareTo(expect_x) != 0) {
+          fail("wrong value of 'x'");
+        }
+
+        var gen = SRP6VerifierGenerator(
+            group: SRP6StandardGroups.rfc5054_1024, digest: Digest('SHA-1'));
+        var v = gen.generateVerifier(s, I, P);
+        if (v.compareTo(expect_v) != 0) {
+          fail("wrong value of 'v'");
+        }
+
+        var client = TestSRP6Client(
+            group: SRP6StandardGroups.rfc5054_1024,
+            digest: Digest('SHA-1'),
+            random: random);
+
+        var A = client.generateClientCredentials(s, I, P);
+        if (A!.compareTo(expect_A) != 0) {
+          fail("wrong value of 'A'");
+        }
+
+        var server = TestSRP6Server(
+            group: SRP6StandardGroups.rfc5054_1024,
+            v: v,
+            digest: Digest('SHA-1'),
+            random: random);
+
+        var B = server.generateServerCredentials();
+        if (B!.compareTo(expect_B) != 0) {
+          fail("wrong value of 'B', expected $expect_B got $B");
+        }
+
+        var u = SRP6Util.calculateU(Digest('SHA-1'), N, A, B);
+        if (u.compareTo(expect_u) != 0) {
+          fail("wrong value of 'u'");
+        }
+
+        var clientS = client.calculateSecret(B);
+        if (clientS!.compareTo(expect_S) != 0) {
+          fail("wrong value of 'S' (client)");
+        }
+
+        var serverS = server.calculateSecret(A);
+        if (serverS!.compareTo(expect_S) != 0) {
+          fail("wrong value of 'S' (server)");
+        }
+      });
+    });
+
+    group('mutual verification:', () {
+      test('testMutualVerificationWith1024', () {
+        var I = Uint8List.fromList('username'.codeUnits);
+        var P = Uint8List.fromList('password'.codeUnits);
+        final key = Uint8List.fromList('keywithsixteenth'.codeUnits);
+        final keyParam = KeyParameter(key);
+        final params = ParametersWithIV(keyParam, Uint8List(16));
+
+        random.seed(params);
+        var s = random.nextBytes(16);
+
+        var gen = SRP6VerifierGenerator(
+            group: SRP6StandardGroups.rfc5054_1024, digest: Digest('SHA-256'));
+        var v = gen.generateVerifier(s, I, P);
+
+        var client = SRP6Client(
+            group: SRP6StandardGroups.rfc5054_1024,
+            digest: Digest('SHA-256'),
+            random: random);
+
+        var server = SRP6Server(
+            group: SRP6StandardGroups.rfc5054_1024,
+            v: v,
+            digest: Digest('SHA-256'),
+            random: random);
+
+        var A = client.generateClientCredentials(s, I, P);
+        var B = server.generateServerCredentials();
+
+        var clientS = client.calculateSecret(B!);
+        var serverS = server.calculateSecret(A!);
+
+        if (clientS!.compareTo(serverS!) != 0) {
+          fail(
+              'SRP agreement failed - client/server calculated different secrets');
+        }
+      });
+
+      test('testMutualVerificationWith2048', () {
+        var I = Uint8List.fromList('username'.codeUnits);
+        var P = Uint8List.fromList('password'.codeUnits);
+        final key = Uint8List.fromList('keywithsixteenth'.codeUnits);
+        final keyParam = KeyParameter(key);
+        final params = ParametersWithIV(keyParam, Uint8List(16));
+
+        random.seed(params);
+        var s = random.nextBytes(16);
+
+        var gen = SRP6VerifierGenerator(
+            group: SRP6StandardGroups.rfc5054_2048, digest: Digest('SHA-256'));
+        var v = gen.generateVerifier(s, I, P);
+
+        var client = SRP6Client(
+            group: SRP6StandardGroups.rfc5054_2048,
+            digest: Digest('SHA-256'),
+            random: random);
+
+        var server = SRP6Server(
+            group: SRP6StandardGroups.rfc5054_2048,
+            v: v,
+            digest: Digest('SHA-256'),
+            random: random);
+
+        var A = client.generateClientCredentials(s, I, P);
+        var B = server.generateServerCredentials();
+
+        var clientS = client.calculateSecret(B!);
+        var serverS = server.calculateSecret(A!);
+
+        if (clientS!.compareTo(serverS!) != 0) {
+          fail(
+              'SRP agreement failed - client/server calculated different secrets');
+        }
+        try {
+          server.verifyClientEvidenceMessage(
+              client.calculateClientEvidenceMessage()!);
+        } catch (e) {
+          fail("Evidence messages don't match");
+        }
+      });
+    });
+
+    group('client catches bad paramaters:', () {
+      test('testClientCatchesBadB', () {
+        var I = Uint8List.fromList('username'.codeUnits);
+        var P = Uint8List.fromList('password'.codeUnits);
+        final key = Uint8List.fromList('keywithsixteenth'.codeUnits);
+        final keyParam = KeyParameter(key);
+        final params = ParametersWithIV(keyParam, Uint8List(16));
+
+        random.seed(params);
+        var s = random.nextBytes(16);
+
+        var standardsGroup = SRP6StandardGroups.rfc5054_1024;
+        var client = SRP6Client(
+            group: standardsGroup, digest: Digest('SHA-256'), random: random);
+
+        client.generateClientCredentials(s, I, P);
+
+        try {
+          client.calculateSecret(BigInt.zero);
+          fail("Client failed to detect invalid value for 'B'");
+        } catch (e) {
+          // Expected
+        }
+
+        try {
+          client.calculateSecret(standardsGroup.N);
+          fail("Client failed to detect invalid value for 'B'");
+        } catch (e) {
+          // Expected
+        }
+      });
+
+      test('testServerCatchesBadA', () {
+        var I = Uint8List.fromList('username'.codeUnits);
+        var P = Uint8List.fromList('password'.codeUnits);
+        final key = Uint8List.fromList('keywithsixteenth'.codeUnits);
+        final keyParam = KeyParameter(key);
+        final params = ParametersWithIV(keyParam, Uint8List(16));
+
+        random.seed(params);
+        var s = random.nextBytes(16);
+
+        var standardsGroup = SRP6StandardGroups.rfc5054_1024;
+        var gen = SRP6VerifierGenerator(
+            digest: Digest('SHA-256'), group: standardsGroup);
+        var v = gen.generateVerifier(s, I, P);
+
+        var server = SRP6Server(
+            digest: Digest('SHA-256'),
+            group: standardsGroup,
+            v: v,
+            random: random);
+        server.generateServerCredentials();
+
+        try {
+          server.calculateSecret(BigInt.zero);
+          fail("Client failed to detect invalid value for 'A'");
+        } catch (e) {
+          // Expected
+        }
+
+        try {
+          server.calculateSecret(standardsGroup.N);
+          fail("Client failed to detect invalid value for 'A'");
+        } catch (e) {
+          // Expected
+        }
+      });
+    });
+  });
+}
+
+final random = SecureRandom('AES/CTR/AUTO-SEED-PRNG');
+
+class TestSRP6Client extends SRP6Client {
+  @override
+  var a = BigInt.parse(
+      '60975527035CF2AD1989806F0407210BC81EDC04E2762A56AFD529DDDA2D4393',
+      radix: 16);
+
+  TestSRP6Client({required SRP6GroupParameters group, digest, random})
+      : super(group: group, digest: digest, random: random);
+
+  @override
+  BigInt? selectPrivateValue() {
+    return a;
+  }
+}
+
+class TestSRP6Server extends SRP6Server {
+  @override
+  var b = BigInt.parse(
+      'E487CB59D31AC550471E81F00F6928E01DDA08E974A004F49E61F5D105284D20',
+      radix: 16);
+
+  TestSRP6Server({required SRP6GroupParameters group, v, digest, random})
+      : super(group: group, v: v, digest: digest, random: random);
+
+  @override
+  BigInt? selectPrivateValue() {
+    return b;
+  }
+}
diff --git a/tool/Makefile b/tool/Makefile
index 3955301..e6163f2 100644
--- a/tool/Makefile
+++ b/tool/Makefile
@@ -13,7 +13,7 @@
 	@echo "RUNNING TESTS"
 	@echo "********************************************************************************"
 
-	dart ../test/all_tests.dart
+	dart ../test/all_tests_web.dart
 
 doc: depends
 	@echo "********************************************************************************"