Use of weak password hashing
ID |
dart.cryptography.weak_password_hash |
Severity |
critical |
Remediation Complexity |
medium |
Remediation Risk |
medium |
Remediation Effort |
medium |
Resource |
Cryptography |
Language |
Dart |
Description
Passwords are hashed with a fast hash (MD5 / SHA-1 / SHA-256 directly) or with PBKDF2 / Argon2 configured below the OWASP-recommended cost parameters. Fast hashes fall to GPU / ASIC brute-force at billions of guesses per second.
Rationale
A general-purpose hash like SHA-256 is designed to be fast — that is exactly the property that makes it inappropriate for password storage. Off-the-shelf GPUs hash multiple billions of SHA-256 candidates per second; an ASIC bank rented for a few dollars an hour reaches hundreds of billions per second. The entire English-language dictionary plus common mutations is exhausted in seconds.
Modern password hashing uses a MEMORY-HARD or DELIBERATELY-SLOW KDF:
-
Argon2id: winner of the Password Hashing Competition; memory-hard so GPU/ASIC attacks degrade to RAM-limited throughput.
-
scrypt: also memory-hard; widely deployed.
-
bcrypt: deliberately slow via Blowfish-based key schedule.
-
PBKDF2-HMAC-SHA-256: acceptable fallback if Argon2/scrypt/bcrypt unavailable, with >= 600 000 iterations (OWASP 2023).
Direct application of MD5 / SHA-1 / SHA-256 to a password — or PBKDF2 with the historical 1 000 - 10 000 iteration count — produces a hash that is recoverable to plaintext in practical time for any password not at the very tail of the entropy distribution.
The following code illustrates a vulnerable pattern detected by this rule:
class WeakPasswordHashes {
void fastHashOverPassword(String password, String pwd, String passphrase) {
// VULNERABLE: Use of weak password hashing
final h1 = md5.convert(utf8.encode(password));
// VULNERABLE: Use of weak password hashing
final h2 = sha1.convert(utf8.encode(password));
// VULNERABLE: Use of weak password hashing
final h3 = sha256.convert(utf8.encode(password));
// VULNERABLE: Use of weak password hashing
final h4 = sha384.convert(utf8.encode(pwd));
// VULNERABLE: Use of weak password hashing
final h5 = sha512.convert(utf8.encode(passphrase));
// VULNERABLE: Use of weak password hashing
final h6 = crypto.sha256.convert(utf8.encode(password));
print('$h1 $h2 $h3 $h4 $h5 $h6');
}
Future<void> cryptographyLowIterations(SecretKey secret) async {
final pbkdf2 = Pbkdf2(macAlgorithm: Hmac.sha256(), iterations: 1000, bits: 256);
final pbkdf2b = Pbkdf2(macAlgorithm: Hmac.sha256(), iterations: 100000, bits: 256);
final out = await pbkdf2.deriveKey(secretKey: secret, nonce: <int>[1, 2, 3]);
final outb = await pbkdf2b.deriveKey(secretKey: secret, nonce: <int>[1, 2, 3]);
print('$out $outb');
}
}
Remediation
Replace the fast-hash call with a memory-hard KDF. If you must stick with PBKDF2, raise the iteration count to at least 600 000 (PBKDF2- HMAC-SHA-256) or 210 000 (PBKDF2-HMAC-SHA-512) per OWASP 2023.
import 'package:cryptography/cryptography.dart';
// Vulnerable
final h = sha256.convert(utf8.encode(password));
final p = Pbkdf2Parameters(salt, 1000, 32);
// Fixed (Argon2id, recommended)
final argon2 = Argon2id(
memory: 19456, // 19 MiB
iterations: 2,
parallelism: 1,
hashLength: 32,
);
final hash = await argon2.deriveKeyFromPassword(
password: password,
nonce: saltBytes, // random per user
);
// Or PBKDF2 at OWASP-recommended iteration count
final pbkdf2 = Pbkdf2(
macAlgorithm: Hmac.sha256(),
iterations: 600000,
bits: 256,
);
References
-
OWASP Top 10 2025 - A04 : Cryptographic Failures.
-
OWASP Password Storage Cheat Sheet — Current Argon2id / scrypt / bcrypt / PBKDF2 cost parameters
-
PHC — Password Hashing Competition — Argon2 winner announcement and design rationale
-
package:cryptography (pub.dev) — Provides
Argon2id,Pbkdf2, andHmacconstructors