import 'dart:convert'; import 'dart:math'; import 'package:cryptography/cryptography.dart'; class PasswordHelper { static const int _saltLength = 16; static const int _hashLength = 32; // 19 MiB, laut aktueller cryptography-Doku ein sinnvoller Startwert. static const int _memory = 19 * 1024; static const int _iterations = 2; static const int _parallelism = 1; static final Argon2id _algorithm = Argon2id( memory: _memory, iterations: _iterations, parallelism: _parallelism, hashLength: _hashLength, ); /// Erstellt einen neuen Passwort-Hash inklusive zufälligem Salt. /// /// Rückgabeformat: /// argon2id$memory$iterations$parallelism$saltBase64$hashBase64 static Future hashPassword(String password) async { final salt = _generateSalt(_saltLength); final secretKey = await _algorithm.deriveKeyFromPassword( password: password, nonce: salt, ); final hashBytes = await secretKey.extractBytes(); final saltBase64 = base64UrlEncode(salt); final hashBase64 = base64UrlEncode(hashBytes); return 'argon2id' '\$$_memory' '\$$_iterations' '\$$_parallelism' '\$$saltBase64' '\$$hashBase64'; } /// Prüft ein Klartext-Passwort gegen den gespeicherten Hash. static Future verifyPassword( String password, String storedHash, ) async { try { final parts = storedHash.split('\$'); if (parts.length != 6) { return false; } if (parts[0] != 'argon2id') { return false; } final memory = int.parse(parts[1]); final iterations = int.parse(parts[2]); final parallelism = int.parse(parts[3]); final salt = base64Url.decode(parts[4]); final expectedHash = base64Url.decode(parts[5]); final algorithm = Argon2id( memory: memory, iterations: iterations, parallelism: parallelism, hashLength: expectedHash.length, ); final secretKey = await algorithm.deriveKeyFromPassword( password: password, nonce: salt, ); final calculatedHash = await secretKey.extractBytes(); return _constantTimeEquals( calculatedHash, expectedHash, ); } catch (_) { return false; } } /// Erzeugt kryptographisch zufälligen Salt. static List _generateSalt(int length) { final random = Random.secure(); return List.generate( length, (_) => random.nextInt(256), ); } /// Vergleich ohne vorzeitigen Abbruch. static bool _constantTimeEquals( List a, List b, ) { if (a.length != b.length) { return false; } var difference = 0; for (var i = 0; i < a.length; i++) { difference |= a[i] ^ b[i]; } return difference == 0; } }