Hardcoded cryptographic key
ID |
dart.cryptography.hardcoded_cryptographic_key |
Severity |
critical |
Remediation Complexity |
medium |
Remediation Risk |
medium |
Remediation Effort |
medium |
Resource |
Cryptography |
Language |
Dart |
Description
A cryptographic key is embedded directly in the application source as a string or byte array literal. The Dart obfuscator does NOT obfuscate string literals, so the key is recoverable from the shipped APK / IPA.
Rationale
Cossack Labs' review of Flutter security explicitly notes that Dart’s
obfuscator (flutter build --obfuscate) preserves string literals
intact — they are extractable from the Dart snapshot with
analyze_snapshot or a hex editor. A hardcoded key therefore has the
same trust level as a key published on GitHub: anyone with the binary
has it.
Once an attacker has the symmetric key, they can decrypt all data the app ever produced with it (including ciphertext exfiltrated from backups, sync services, telemetry, or device images), forge ciphertext that the app will accept as authentic, and impersonate the user against any backend that trusts that key. For asymmetric private keys, the consequence is signature forgery — the attacker can sign anything the legitimate app could.
The following code illustrates a vulnerable pattern detected by this rule:
class HardcodedKeys {
void encryptKeys() {
// VULNERABLE: Hardcoded cryptographic key
final k1 = Key.fromUtf8("my 32 length key................");
// VULNERABLE: Hardcoded cryptographic key
final k2 = Key.fromBase64("aGVsbG93b3JsZGhlbGxvd29ybGQxMjM0");
// VULNERABLE: Hardcoded cryptographic key
final k3 = Key.fromLength(32);
// VULNERABLE: Hardcoded cryptographic key
final k4 = Key(Uint8List.fromList([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10]));
print('$k1 $k2 $k3 $k4');
}
}
Remediation
Move the key out of the bundle entirely:
-
Per-user secrets: derive at runtime from a user-supplied passphrase using a memory-hard KDF (Argon2id / scrypt / PBKDF2 >= 600 000 iterations).
-
Per-device secrets: store in
flutter_secure_storage(backed by Keychain on iOS, EncryptedSharedPreferences / Keystore on Android). -
Server-shared secrets: fetch from an authenticated backend after sign-in; never bundle them.
-
Public keys (for pinning, signature verification): hardcoding the public half is acceptable; only private / symmetric keys must be protected.
// Vulnerable
final key = enc.Key.fromUtf8('DVFA_STATIC_KEY_8899001122334455');
final p = KeyParameter(utf8.encode('hardcoded-key-123'));
// Fixed
const storage = FlutterSecureStorage();
final stored = await storage.read(key: 'app_key');
final key = enc.Key(base64Decode(stored!));
References
-
OWASP Top 10 2025 - A04 : Cryptographic Failures.
-
Cossack Labs — Flutter application security considerations — Documents that the Dart obfuscator does not obfuscate strings
-
Talsec — OWASP Mobile Top 10 for Flutter M1 — Mastering credential security in Flutter (M1 — Improper Credential Usage)
-
package:flutter_secure_storage — Keychain / Keystore-backed key storage — the standard alternative to bundling secrets