dsrp 0.5.4 copy "dsrp: ^0.5.4" to clipboard
dsrp: ^0.5.4 copied to clipboard

Secure Remote Password (SRP-6a) for zero-knowledge authentication without transmitting password-equivalents.

example/dsrp.dart

import 'package:dsrp/dsrp.dart';
import 'package:logging/logging.dart';

/// Demonstrates the major steps of SRP using dsrp.
void main() async {
  // Configure logging.
  Logger.root.level = Level.ALL;
  Logger.root.onRecord.listen((record) {
    print('${record.loggerName} ${record.level.name}: ${record.message}');
  });
  final log = Logger('srp.Example');

  //////////////////////////////
  ///// Registration.
  //////////////////////////////
  log.info('///// USER REGISTRATION BEGINS /////');

  // 0 (optional). User requests from server basic SRP primitives (e.g., safe
  // prime and generator).
  // 
  // Alternatively, user and server could hardcode them beforehand, or the user
  // could dictate the primitives to the server.
  //
  // User and/or server should verify SRP primitives received are secure.
  // 
  // In production, you should generate custom safe primes using
  // scripts/generate_safe_primes.py to reduce vulnerability to pre-computed
  // attacks on standard primes.
  log.info('(optional) User requested SRP primitives from server.');
  final safePrime = BigInt.parse('EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE48E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B297BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9AFD5138FE8376435B9FC61D2FC0EB06E3', radix: 16);
  final generator = BigInt.from(2);
  final expectedSafePrimeBitLength = 1024;

  log.info('User is verifying safe prime and generator (this may take a few seconds)...');
  verifySafePrime(safePrime, expectedSafePrimeBitLength);
  log.info('✓ Safe prime is valid');

  verifyGenerator(generator, safePrime);
  log.info('✓ Generator is valid');

  // 1. User generates a salted verification key based on user ID and password.

  // SECURITY: Convert strings to bytes early for secure memory handling.
  final String userId = "fakeuserid";
  String password = "fakepassword";
  final userIdBytes = userId.utf8Bytes;
  final passwordBytes = password.utf8Bytes;
  // Setting sensitive string variables to null or empty string allows sensitive
  // string data to be garbage collected. Do the same for userId if it is
  // sensitive.
  password = ""; 

  final saltedVerificationKey = await User.createSaltedVerificationKeyFromBytes(
    userIdBytes: userIdBytes,
    passwordBytes: passwordBytes,
    generator: generator,
    safePrime: safePrime,
  );

  // SECURITY: Zero out credential bytes immediately after use.
  userIdBytes.overwriteWithZeros();
  passwordBytes.overwriteWithZeros();

  // 2. The salted verification key is sent to the server, along with user ID,
  // to register the user for later authentication.
  log.info('User began registration by creating a salted verification key and sending it to the server.');
  verifySalt(saltedVerificationKey.salt);
  log.info('Server stored salted verification key and its salt for future authentication.');

  // SECURITY: Erase the verification key after sending to server.
  // (In production, only erase after confirming successful transmission.)
  // saltedVerificationKey.erase();

  log.info('///// USER REGISTRATION COMPLETE /////');


  //////////////////////////////
  ///// Authentication.
  //////////////////////////////
  // 1. To initiate login, the user requests a challenge from the server by
  // sending the user ID. The server retrieves the salted verification key in
  // order to create the challenge.
  log.info('///// AUTHENTICATION BEGINS /////');
  log.info('User began authentication by requesting a challenge from the server.');
  final server = Server(
    userId: userId,
    salt: saltedVerificationKey.salt,
    verifierKey: saltedVerificationKey.key,
    generator: generator,
    safePrime: safePrime
  );
  final challenge = await server.createChallenge();
  log.info('Server created a challenge and sent it to the user.');

  // 2. The user processes the challenge to generate a session key and its
  // verifiers.

  // SECURITY: Re-create credential bytes for authentication (originals were zeroed
  // during registration).
  final userIdBytesForAuth = userId.utf8Bytes;
  String passwordForAuth = 'fakepassword';
  final passwordBytesForAuth = passwordForAuth.utf8Bytes;
  passwordForAuth = ''; // Allows for GC.

  final user = await User.fromUserCredsBytesAndChallenge(
    userIdBytes: userIdBytesForAuth,
    passwordBytes: passwordBytesForAuth,
    challenge: challenge,
  );

  // SECURITY: Zero out credentials and challenge after use.
  userIdBytesForAuth.overwriteWithZeros();
  passwordBytesForAuth.overwriteWithZeros();
  challenge.erase();

  final userSessionVerifiers = user.getUserSessionVerifiers();

  // 3. The user-derived verifiers are sent to the server.
  log.info('User generated a session key and sent its verifiers to the server.');

  // 4. The server verifies the user session key and responds with a session-key
  // encrypted message containing its own verifier.
  // Throws an exception if verification fails.
  // At this point all message bodies between user and server should be
  // encrypted using the session key (e.g., user.sessionKey,
  // server.sessionKey). This supplements but does not replace TLS and
  // other transport layer encryption. If at any point a message body cannot be
  // decrypted, the message should be dropped.
  final serverSessionKeyVerifier = await server.verifySession(
    ephemeralUserPublicKey: userSessionVerifiers.ephemeralUserPublicKey,
    userSessionKeyVerifier: userSessionVerifiers.sessionKeyVerifier);
  log.info('Server verified session and generated a session verifier, sent it to the user.');
  userSessionVerifiers.erase();

  // 5. The user verifies the server session key. Throws an exception if
  // verification fails.
  try {
    await user.verifySession(serverSessionKeyVerifier);
    log.info('✓ Server verified successfully');
  } on AuthenticationFailure catch (e) {
    log.severe('❌ Server verification failed: $e');
    log.severe('Authentication aborted - possible man-in-the-middle attack');
    return;
  }

  // 6. User and server are now mutually authenticated and can continue using
  // the shared SRP session key to encrypt messages for this user session.
  log.info('User verified session and now SRP-encrypted communication can begin.');
  log.info('Session keys match: ${user.sessionKey.length} bytes');

  // NOTE: Keep user.sessionKey and server.sessionKey for encrypted
  // communication. Only zero them out when the session ends.

  log.info('///// AUTHENTICATION COMPLETE /////');
}
1
likes
150
points
184
downloads

Publisher

verified publisherfictiontoreality.dev

Weekly Downloads

Secure Remote Password (SRP-6a) for zero-knowledge authentication without transmitting password-equivalents.

Repository
View/report issues

Topics

#security #cryptography #authentication #srp #password

Documentation

API reference

License

Apache-2.0 (license)

Dependencies

cryptography, logging

More

Packages that depend on dsrp