Back to Cheat Sheets

🔏 Insufficient CryptographyMobile 2016

OWASP Mobile Top 10 2016 - M5

HIGH RISK

📋 What Is It?

Insufficient Cryptography is cryptography that is present but ineffective. The app encrypts - it simply does it in a way that an attacker can undo: a broken algorithm, an unauthenticated mode, a hardcoded key, a fixed IV, a home-made cipher, or a correct algorithm whose key sits in the same binary as the data it protects.

On mobile the killer detail is key management. The device belongs to the attacker, so any key compiled into the app, derived from a constant, or stored beside the ciphertext is recoverable. Encryption only moves the problem: the security of the data becomes exactly the security of the key.

M5 OWASP Rank 2016
Keys The Real Problem
False Sense Of Safety

⚠️ Common Exploits

  • Hardcoded Keys: the key extracted from strings or bytecode in minutes
  • Broken Primitives: MD5, SHA-1, DES, RC4 still in use
  • ECB Mode: identical plaintext blocks produce identical ciphertext
  • Static or Zero IV: reuse destroys the security of CBC and CTR modes
  • No Authentication: unauthenticated ciphertext can be tampered with
  • Bad Randomness: Random or a timestamp seeding key material
  • Home-Made Crypto: XOR, Base64, or custom "obfuscation" mistaken for encryption
  • Weak Password Hashing: unsalted or single-round hashes for local secrets

🔴 Attack Flow

1. Decompile the app and locate the crypto calls
↓
2. Identify algorithm, mode, IV handling, and where the key comes from
↓
3. Recover the key from constants, resources, or a runtime hook
↓
4. Decrypt the stored data - or forge ciphertext where nothing is authenticated
↓
5. IMPACT: "Encrypted" data read and modified at will!

❌ Vulnerable Code

// Bad: hardcoded key, ECB mode, no authentication val key = SecretKeySpec("MySuperSecretKey".toByteArray(), "AES") ← in the binary val cipher = Cipher.getInstance("AES/ECB/PKCS5Padding") ← patterns survive cipher.init(Cipher.ENCRYPT_MODE, key) // Bad: fixed IV - reuse leaks relationships between messages val iv = IvParameterSpec(ByteArray(16)) ← all zeroes, every time // Bad: obsolete primitives MessageDigest.getInstance("MD5") ← collisions are practical Cipher.getInstance("DES/CBC/PKCS5Padding") ← 56-bit key // Bad: predictable "randomness" for key material val token = Random(System.currentTimeMillis()).nextLong() // Bad: encoding is not encryption fun protect(s: String) = Base64.encode(s.toByteArray()) ← reversible by anyone

✅ Secure Code

// Good: hardware-backed key that never leaves the Keystore val kg = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") kg.init(KeyGenParameterSpec.Builder("data_key", PURPOSE_ENCRYPT or PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setUserAuthenticationRequired(true) .build()) // Good: authenticated encryption with a fresh random IV per message val cipher = Cipher.getInstance("AES/GCM/NoPadding") cipher.init(Cipher.ENCRYPT_MODE, kg.generateKey()) // GCM IV generated by the provider val iv = cipher.iv // store alongside the ciphertext // Good: CSPRNG for anything security-relevant val nonce = ByteArray(32).also { SecureRandom().nextBytes(it) } // Good: modern hashing and key derivation MessageDigest.getInstance("SHA-256") val spec = PBEKeySpec(pin, salt, 210_000, 256) // or Argon2id // Good (iOS): CryptoKit AES-GCM with a Secure Enclave key let sealed = try AES.GCM.seal(plaintext, using: key)

✓ Prevention Checklist

  • Use platform crypto libraries - never write your own primitive
  • Prefer authenticated encryption: AES-GCM or ChaCha20-Poly1305
  • Generate and store keys in the Keystore/Keychain or Secure Enclave
  • Never hardcode a key, IV, or salt in code, resources, or config
  • Use a fresh random IV/nonce for every message; never reuse one with a key
  • Use a CSPRNG (SecureRandom) for all key and nonce material
  • Use SHA-256+ for hashing and Argon2id/PBKDF2 with high iteration counts for passwords
  • Retire MD5, SHA-1, DES, 3DES, RC4, and ECB mode
  • Bind keys to user authentication where the data warrants it
  • Plan key rotation and invalidation (logout, biometric re-enrolment, device loss)
  • Remember: encryption on a device the attacker owns is only as strong as the key store

🔍 Detection & Tools

Analysis:

MobSF jadx / Ghidra Frida (crypto hooks) semgrep crypto rules

Libraries:

Tink Jetpack Security CryptoKit libsodium

How to Test:

  • Grep the decompiled app for ECB, MD5, DES, and key-like constants
  • Hook Cipher.init at runtime and print the key and IV
  • Encrypt the same plaintext twice - identical ciphertext means ECB or a fixed IV
  • Flip a ciphertext byte; if it still decrypts, there is no authentication
  • Confirm keys are Keystore-resident and non-exportable

🌍 Real-World

These are documented incident classes - recurring patterns rather than single CVEs.

  • Hardcoded AES keys: extracted from the binary, turning "encrypted" local databases into plaintext for anyone with the app.
  • ECB-encrypted content: structure visible through the ciphertext, and repeated blocks that reveal or allow reordering of data.
  • Unauthenticated ciphertext: attackers flip bits to alter decrypted values, changing prices, roles, or flags.
  • Predictable tokens: session or reset values seeded from the clock, reproduced offline by an attacker.
  • Base64 as "encryption": data marked confidential that is simply encoded and trivially readable.

📌 Quick Tips

  • DO NOT hardcode keys, IVs, or salts
  • DO NOT use ECB, MD5, SHA-1, DES, or RC4
  • DO NOT invent your own cipher
  • DO use AES-GCM with Keystore-held keys
  • DO use a fresh random IV per message
  • DO derive local keys with Argon2id/PBKDF2

📜 Compliance

Related Standards:

  • OWASP Mobile Top 10 2016 - M5
  • Maps to M10:2024 Insufficient Cryptography
  • OWASP MASVS CRYPTO-1 … CRYPTO-4
  • CWE-327 Use of a Broken or Risky Cryptographic Algorithm
  • CWE-321 Use of Hard-coded Cryptographic Key
  • CWE-330 Use of Insufficiently Random Values
  • NIST SP 800-175B / FIPS 140-3