Hardcoded cryptographic salt
ID |
dart.cryptography.hardcoded_cryptographic_salt |
Severity |
critical |
Remediation Complexity |
medium |
Remediation Risk |
medium |
Remediation Effort |
medium |
Resource |
Cryptography |
Language |
Dart |
Description
A cryptographic salt is hardcoded as a literal byte array or string and passed to a key-derivation function (PBKDF2, Argon2, scrypt). A constant salt defeats the purpose of the KDF: every user with the same password derives the same key, enabling precomputed-rainbow-table attacks.
Rationale
Salts serve two purposes: (1) ensure that two users with the same password do not produce the same derived key, defeating cross-user table lookup attacks; and (2) make precomputed attack tables (rainbow-tables) infeasible by expanding the input space beyond what an attacker can practically precompute.
A constant salt eliminates both properties: an attacker who steals a
database of (salt, derived_hash) pairs only has to brute-force the
password set ONCE for the entire user base, and can build a rainbow
table for that single salt offline. For the same memory/CPU budget,
this turns a per-user one-week brute-force into a one-shot lookup.
In Dart this appears as a literal byte array passed to
Pbkdf2Parameters(saltBytes, iterations, len) in pointycastle or as
nonce: const […] / nonce: utf8.encode("static") on
package:cryptography’s `Pbkdf2.deriveKeyFromPassword.
The following code illustrates a vulnerable pattern detected by this rule:
class HardcodedSalts {
void pcStaticSalt() {
// VULNERABLE: Hardcoded cryptographic salt
final p1 = Pbkdf2Parameters(Uint8List.fromList([1, 2, 3, 4, 5, 6, 7, 8]), 1000, 32);
// VULNERABLE: Hardcoded cryptographic salt
final p2 = Pbkdf2Parameters(utf8.encode("static-salt"), 1000, 32);
print('$p1 $p2');
}
}
Remediation
Generate a fresh random salt per user / per derivation using a CSPRNG (typically 16 bytes). Store the salt alongside the derived hash — salts are not secret, just unique.
// Vulnerable
final p = Pbkdf2Parameters(utf8.encode('mysalt'), 100000, 32);
// Fixed
final saltBytes = Uint8List(16);
Random.secure().nextBytes(saltBytes); // unique random salt
final p = Pbkdf2Parameters(saltBytes, 600000, 32);
// store (saltBytes, derived) together for later verification
References
-
OWASP Top 10 2025 - A04 : Cryptographic Failures.
-
OWASP Password Storage Cheat Sheet — Salting, iteration counts, and KDF selection guidance
-
package:pointycastle (pub.dev) —
Pbkdf2Parametersaccepts the salt as first arg — pass a CSPRNG-generated buffer