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

Configuration

This detector does not need any configuration.

References

© 2026 Xygeni. All rights reserved