---
# === IDENTITY ===
id: software/security/mobile-app-security/2026
canonical_question: "What is the mobile app security checklist (iOS and Android)?"
aliases:
  - "mobile application security best practices"
  - "OWASP MASVS checklist"
  - "iOS app security checklist"
  - "Android app security checklist"
  - "mobile app penetration testing checklist"
  - "secure mobile development guide"
  - "OWASP mobile top 10 prevention"
  - "mobile app hardening iOS Android"
entity_type: software_reference
domain: software > security > Mobile App Security
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === VERIFICATION ===
last_verified: 2026-02-27
confidence: 0.93
version: 1.0
first_published: 2026-02-27

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "OWASP MASVS v2.0 restructured categories and removed L1/L2/R levels (2023); Android EncryptedSharedPreferences deprecated in favor of direct Keystore usage (2024); Apple App Attest API became standard for device attestation (2022)"
  next_review: 2026-08-26
  change_sensitivity: medium

# === CONSTRAINTS ===
constraints:
  - "NEVER store secrets (API keys, tokens, passwords) in plaintext -- use iOS Keychain or Android Keystore"
  - "ALWAYS enforce TLS 1.2+ for all network communication -- no cleartext HTTP traffic"
  - "NEVER disable certificate validation in production builds -- test-only flags must be removed"
  - "Biometric authentication MUST have a server-side fallback -- device-only auth is bypassable on rooted/jailbroken devices"
  - "Code obfuscation is defense-in-depth, NOT a security boundary -- never rely on it as sole protection"
  - "Root/jailbreak detection MUST be combined with runtime integrity checks -- detection alone is trivially bypassed"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Need web application security (XSS, CSRF, SQL injection)"
    use_instead: "software/security/xss-prevention/2026"
  - condition: "Need API security specifically (OAuth, rate limiting, JWT)"
    use_instead: "software/patterns/jwt-implementation/2026"
  - condition: "Need backend server hardening, not mobile client"
    use_instead: "software/security/server-hardening/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: "platform"
    question: "Which mobile platform are you targeting?"
    type: choice
    options: ["iOS", "Android", "Both (cross-platform)"]
  - key: "risk_level"
    question: "What is the sensitivity of data handled by the app?"
    type: choice
    options: ["Low (public data)", "Medium (user accounts, PII)", "High (financial, health, government)"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/security/mobile-app-security/2026"
suggested_citation: "Source: knowledgelib.io -- AI Knowledge Library (verified 2026-02-27)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "software/security/xss-prevention/2026"
      label: "XSS Prevention Guide"
    - id: "software/patterns/jwt-implementation/2026"
      label: "JWT Implementation Guide"
  solves: []
  alternative_to: []
  often_confused_with:
    - id: "software/security/api-security/2026"
      label: "API Security Best Practices"

# === SOURCES (7 authoritative sources) ===
# Types: official_docs, technical_blog, rfc_spec, academic_paper, community_resource, industry_report
# Reliability: high, moderate_high, moderate, moderate_low, low, authoritative
sources:
  - id: src1
    title: "OWASP Mobile Application Security Verification Standard (MASVS)"
    author: OWASP Foundation
    url: https://mas.owasp.org/MASVS/
    type: community_resource
    published: 2023-12-01
    reliability: authoritative
  - id: src2
    title: "OWASP Mobile Application Security Testing Guide (MASTG)"
    author: OWASP Foundation
    url: https://mas.owasp.org/MASTG/
    type: community_resource
    published: 2024-06-01
    reliability: authoritative
  - id: src3
    title: "Apple Platform Security Guide"
    author: Apple
    url: https://support.apple.com/guide/security/keychain-data-protection-secb0694df1a/web
    type: official_docs
    published: 2026-01-01
    reliability: authoritative
  - id: src4
    title: "Network Security Configuration"
    author: Android Developers
    url: https://developer.android.com/privacy-and-security/security-config
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
  - id: src5
    title: "OWASP Mobile Application Security Explained"
    author: NowSecure
    url: https://www.nowsecure.com/blog/2026/01/21/owasp-mobile-application-security-explained-how-to-put-masvs-mastg-and-maswe-into-practice/
    type: technical_blog
    published: 2026-01-21
    reliability: high
  - id: src6
    title: "Android App Security in 2026: Real-World Cybersecurity Practices"
    author: Software Developer (Medium)
    url: https://medium.com/@ssshubham660/android-app-security-in-2026-real-world-cybersecurity-practices-beyond-https-095c7761660c
    type: technical_blog
    published: 2026-01-15
    reliability: moderate_high
  - id: src7
    title: "Top 15 Mobile Application Security Best Practices in 2025"
    author: Cybersecurity ASEE
    url: https://cybersecurity.asee.io/blog/top-mobile-application-security-best-practices/
    type: technical_blog
    published: 2025-09-01
    reliability: high
---

# Mobile App Security: iOS & Android Security Checklist

## TL;DR

- **Bottom line**: Follow the OWASP MASVS v2.0 eight-category checklist (Storage, Crypto, Auth, Network, Platform, Code, Resilience, Privacy) with platform-specific implementations using iOS Keychain/Android Keystore for secrets, TLS 1.2+ with certificate pinning for networking, and layered runtime protection.
- **Key tool/command**: `OWASP MASVS v2.0` checklist + `MobSF` (Mobile Security Framework) for automated scanning.
- **Watch out for**: Storing sensitive data in plaintext SharedPreferences (Android) or UserDefaults (iOS) -- the #1 finding in mobile penetration tests.
- **Works with**: iOS 15+ (Swift 5.5+), Android API 23+ (Kotlin 1.8+). OWASP MASVS v2.0 is platform-agnostic.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- NEVER store secrets (API keys, tokens, passwords) in plaintext -- use iOS Keychain or Android Keystore
- ALWAYS enforce TLS 1.2+ for all network communication -- no cleartext HTTP traffic
- NEVER disable certificate validation in production builds -- test-only flags must be removed
- Biometric authentication MUST have a server-side fallback -- device-only auth is bypassable on rooted/jailbroken devices
- Code obfuscation is defense-in-depth, NOT a security boundary -- never rely on it as sole protection
- Root/jailbreak detection MUST be combined with runtime integrity checks -- detection alone is trivially bypassed

## Quick Reference

**OWASP MASVS v2.0 Security Checklist:**

| # | Category | Key Controls | iOS Implementation | Android Implementation |
|---|---|---|---|---|
| 1 | MASVS-STORAGE | Secure sensitive data at rest; no secrets in logs/backups/clipboard | Keychain with `kSecAttrAccessibleWhenUnlockedThisDeviceOnly`; Data Protection API | Android Keystore; EncryptedFile; exclude from backups via `android:allowBackup="false"` |
| 2 | MASVS-CRYPTO | Use approved algorithms; secure key management; no hardcoded keys | CommonCrypto/CryptoKit with AES-GCM; Secure Enclave for key storage | Android Keystore with `setUserAuthenticationRequired(true)`; AES/GCM/NoPadding |
| 3 | MASVS-AUTH | Strong authentication; session management; biometric with fallback | LAContext with `evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics)`; server-side token validation | BiometricPrompt with `setNegativeButtonText` for fallback; server-side session binding |
| 4 | MASVS-NETWORK | TLS 1.2+ enforced; certificate pinning; no cleartext traffic | App Transport Security (ATS) enforced by default; URLSession delegate for pinning | Network Security Config XML; `cleartextTrafficPermitted="false"`; pin-set with backup pins |
| 5 | MASVS-PLATFORM | Secure IPC; WebView hardening; input validation from deep links | Universal Links validation; `WKWebView` (never UIWebView); URL scheme input sanitization | Intent filter validation; `WebView.setJavaScriptEnabled(false)` by default; deep link validation |
| 6 | MASVS-CODE | Dependency management; no debug code in production; input validation | Strip debug symbols; disable logging in release; dependency audit with `swift package audit` | ProGuard/R8 obfuscation; `BuildConfig.DEBUG` checks; dependency scanning with `dependencyCheck` |
| 7 | MASVS-RESILIENCE | Anti-tampering; anti-debugging; integrity verification | App Attest API (DeviceCheck framework); `ptrace` detection; code signing validation | Play Integrity API (replaces SafetyNet); signature verification; `Debug.isDebuggerConnected()` checks |
| 8 | MASVS-PRIVACY | Data minimization; consent management; anonymization | ATT (App Tracking Transparency) framework; Privacy Manifest; on-device processing | Privacy Sandbox APIs; runtime permissions; `PURPOSE_` declarations in manifest |

## Decision Tree

```
START: What is your app's risk profile?
├── Handles financial/health/government data?
│   ├── YES → Implement ALL 8 MASVS categories + certificate pinning + RASP + App Attest/Play Integrity
│   └── NO ↓
├── Handles user credentials or PII?
│   ├── YES → Implement MASVS-STORAGE + CRYPTO + AUTH + NETWORK + PLATFORM + CODE (skip RESILIENCE if low-risk)
│   └── NO ↓
├── Public data only, no user accounts?
│   ├── YES → Implement MASVS-NETWORK (TLS) + PLATFORM (WebView safety) + CODE (dependency management)
│   └── NO ↓
├── Which platform?
│   ├── iOS → Use Keychain + ATS + App Attest + CryptoKit
│   ├── Android → Use Keystore + Network Security Config + Play Integrity + Jetpack Security
│   └── Cross-platform → Apply platform-specific implementations on each side; DO NOT share crypto code
└── DEFAULT → Implement at minimum: MASVS-STORAGE + MASVS-NETWORK + MASVS-CODE
```

## Step-by-Step Guide

### 1. Secure local data storage

Store all sensitive data (tokens, credentials, PII) in platform-provided secure storage. Never use plaintext files, SharedPreferences, or UserDefaults for secrets. [src1]

**iOS (Swift) -- Keychain Services:**

```swift
import Security

func saveToKeychain(key: String, data: Data) -> OSStatus {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: key,
        kSecValueData as String: data,
        kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
    ]
    SecItemDelete(query as CFDictionary)  // Remove existing item
    return SecItemAdd(query as CFDictionary, nil)
}

func loadFromKeychain(key: String) -> Data? {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: key,
        kSecReturnData as String: true,
        kSecMatchLimit as String: kSecMatchLimitOne
    ]
    var result: AnyObject?
    let status = SecItemCopyMatching(query as CFDictionary, &result)
    return status == errSecSuccess ? result as? Data : nil
}
```

**Android (Kotlin) -- Keystore + EncryptedFile:**

```kotlin
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey

// Create or retrieve the master key from Android Keystore
val masterKey = MasterKey.Builder(context)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
    .build()

// Create encrypted shared preferences
val securePrefs = EncryptedSharedPreferences.create(
    context,
    "secure_prefs",
    masterKey,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

// Store sensitive data
securePrefs.edit().putString("auth_token", token).apply()
```

**Verify**: Run `MobSF` static analysis -- no findings under "Insecure Data Storage" category.

### 2. Implement certificate pinning

Pin your server's public key (SPKI hash) to prevent man-in-the-middle attacks even with compromised CAs. Always include a backup pin for key rotation. [src4]

**Android -- Network Security Config:**

```xml
<?xml version="1.0" encoding="utf-8"?>
<!-- res/xml/network_security_config.xml -->
<network-security-config>
    <base-config cleartextTrafficPermitted="false" />
    <domain-config>
        <domain includeSubdomains="true">api.example.com</domain>
        <pin-set expiration="2027-01-01">
            <!-- Primary pin (SHA-256 of SPKI) -->
            <pin digest="SHA-256">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</pin>
            <!-- Backup pin (generate next cert's key ahead of time) -->
            <pin digest="SHA-256">BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=</pin>
        </pin-set>
    </domain-config>
</network-security-config>
```

Add to `AndroidManifest.xml`:

```xml
<application android:networkSecurityConfig="@xml/network_security_config" ...>
```

**iOS (Swift) -- URLSession delegate pinning:**

```swift
class PinnedSessionDelegate: NSObject, URLSessionDelegate {
    // SHA-256 hash of your server's SPKI (Subject Public Key Info)
    let pinnedKeyHash = "your-base64-encoded-sha256-hash"

    func urlSession(
        _ session: URLSession,
        didReceive challenge: URLAuthenticationChallenge,
        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
    ) {
        guard let serverTrust = challenge.protectionSpace.serverTrust,
              let certificate = SecTrustCopyCertificateChain(serverTrust)
                  as? [SecCertificate],
              let serverKey = SecCertificateCopyKey(certificate[0]) else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }

        let serverKeyData = SecKeyCopyExternalRepresentation(serverKey, nil)! as Data
        let serverKeyHash = sha256(data: serverKeyData)

        if serverKeyHash == pinnedKeyHash {
            completionHandler(.useCredential,
                URLCredential(trust: serverTrust))
        } else {
            completionHandler(.cancelAuthenticationChallenge, nil)
        }
    }
}
```

**Verify**: Use a proxy tool (Charles/mitmproxy) -- requests should fail when proxy's cert is injected.

### 3. Implement biometric authentication with server binding

Biometric auth must be tied to a server-side session, not just a local gate. A jailbroken device can bypass local-only biometric checks. [src3]

```swift
// iOS: LAContext biometric + Keychain-bound key
import LocalAuthentication

let context = LAContext()
var error: NSError?

if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
    context.evaluatePolicy(
        .deviceOwnerAuthenticationWithBiometrics,
        localizedReason: "Authenticate to access your account"
    ) { success, error in
        if success {
            // Retrieve server token from Keychain (protected by biometric)
            // The Keychain item was stored with kSecAccessControlBiometryCurrentSet
            let token = loadFromKeychain(key: "auth_token")
            // Send token to server for validation
        }
    }
}
```

**Verify**: Attempt to access protected resources without biometric -- should be denied both locally and server-side.

### 4. Harden WebViews

WebViews are a major attack surface. Disable JavaScript unless required, validate all URLs loaded, and never expose native bridges to untrusted content. [src2]

```kotlin
// Android: Secure WebView configuration
val webView = WebView(context)
webView.settings.apply {
    javaScriptEnabled = false          // Enable ONLY if required
    allowFileAccess = false            // Prevent file:// access
    allowContentAccess = false         // Prevent content:// access
    domStorageEnabled = false          // Disable unless needed
    setGeolocationEnabled(false)       // Disable location
}

// Validate URLs before loading
webView.webViewClient = object : WebViewClient() {
    override fun shouldOverrideUrlLoading(
        view: WebView, request: WebResourceRequest
    ): Boolean {
        val url = request.url
        val allowedHosts = setOf("example.com", "api.example.com")
        return if (url.host !in allowedHosts) {
            true  // Block navigation to untrusted hosts
        } else false
    }
}
```

**Verify**: Attempt to load `javascript:alert(1)` or `file:///etc/passwd` in the WebView -- both should be blocked.

### 5. Enable runtime integrity checks

Detect tampering, debugging, and compromised environments. Use platform attestation APIs for server-side verification. [src7]

**Android -- Play Integrity API:**

```kotlin
import com.google.android.play.core.integrity.IntegrityManagerFactory

val integrityManager = IntegrityManagerFactory.create(context)
val integrityTokenRequest = IntegrityTokenRequest.builder()
    .setNonce(generateNonce())  // Server-generated nonce
    .build()

integrityManager.requestIntegrityToken(integrityTokenRequest)
    .addOnSuccessListener { response ->
        val token = response.integrityToken()
        // Send token to YOUR server for verification
        // Server decrypts and checks: deviceRecognitionVerdict,
        // appRecognitionVerdict, accountDetails
    }
    .addOnFailureListener { e ->
        // Handle integrity check failure
    }
```

**iOS -- App Attest:**

```swift
import DeviceCheck

let service = DCAppAttestService.shared
if service.isSupported {
    service.generateKey { keyId, error in
        guard let keyId = keyId else { return }
        // Store keyId, then attest it with Apple's servers
        let clientDataHash = Data(SHA256.hash(data: challenge))
        service.attestKey(keyId, clientDataHash: clientDataHash) { attestation, error in
            // Send attestation to your server for verification
        }
    }
}
```

**Verify**: Run app on a rooted/jailbroken device or emulator -- integrity check should fail and app should restrict functionality.

## Code Examples

### Swift: Secure Keychain Storage with Access Control

```swift
import Security
import LocalAuthentication

// Input:  Secret string to store securely
// Output: OSStatus indicating success/failure

func storeSecretWithBiometric(key: String, secret: String) -> OSStatus {
    guard let data = secret.data(using: .utf8) else { return errSecParam }

    let access = SecAccessControlCreateWithFlags(
        nil,
        kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
        .biometryCurrentSet,  // Requires current biometric enrollment
        nil
    )!

    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: key,
        kSecValueData as String: data,
        kSecAttrAccessControl as String: access
    ]
    SecItemDelete(query as CFDictionary)
    return SecItemAdd(query as CFDictionary, nil)
}
```

### Kotlin: Android Keystore Direct Key Generation

> Full script: [kotlin-android-keystore-direct-key-generation.txt](scripts/kotlin-android-keystore-direct-key-generation.txt) (37 lines)

```kotlin
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
# ... (see full script)
```

## Anti-Patterns

### Wrong: Storing tokens in SharedPreferences/UserDefaults

```kotlin
// BAD -- plaintext storage readable on rooted devices
val prefs = context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
prefs.edit().putString("auth_token", "eyJhbGciOiJIUzI1NiIs...").apply()
// Any app with root access can read /data/data/com.app/shared_prefs/app_prefs.xml
```

### Correct: Use EncryptedSharedPreferences or Keystore

```kotlin
// GOOD -- encrypted at rest with Android Keystore-backed key
val masterKey = MasterKey.Builder(context)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build()
val securePrefs = EncryptedSharedPreferences.create(
    context, "secure_prefs", masterKey,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
securePrefs.edit().putString("auth_token", token).apply()
```

### Wrong: Disabling SSL verification for testing (left in production)

```swift
// BAD -- disables ALL certificate validation
class InsecureDelegate: NSObject, URLSessionDelegate {
    func urlSession(_ session: URLSession,
        didReceive challenge: URLAuthenticationChallenge,
        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        // NEVER do this in production
        completionHandler(.useCredential,
            URLCredential(trust: challenge.protectionSpace.serverTrust!))
    }
}
```

### Correct: Implement proper certificate pinning

```swift
// GOOD -- validates server certificate against pinned key hash
class PinnedDelegate: NSObject, URLSessionDelegate {
    func urlSession(_ session: URLSession,
        didReceive challenge: URLAuthenticationChallenge,
        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        guard let trust = challenge.protectionSpace.serverTrust,
              SecTrustEvaluateWithError(trust, nil),
              validatePin(trust: trust) else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }
        completionHandler(.useCredential, URLCredential(trust: trust))
    }
}
```

### Wrong: Logging sensitive data

```kotlin
// BAD -- tokens and PII visible in logcat
Log.d("Auth", "User token: $authToken")
Log.d("Payment", "Card number: ${card.number}")
// adb logcat reveals these on any connected device
```

### Correct: Conditional logging with sanitization

```kotlin
// GOOD -- no sensitive data in logs; disabled in release
if (BuildConfig.DEBUG) {
    Log.d("Auth", "Token received: [REDACTED]")
}
// For release builds, use ProGuard to strip all Log calls:
// -assumenosideeffects class android.util.Log { *; }
```

### Wrong: Hardcoding API keys in source code

```swift
// BAD -- extractable via strings command on the binary
let apiKey = "sk_live_abc123xyz789"
let request = URLRequest(url: URL(string: "https://api.example.com")!)
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
```

### Correct: Retrieve keys from secure storage at runtime

```swift
// GOOD -- key stored in Keychain, never in source code
guard let apiKeyData = loadFromKeychain(key: "api_key"),
      let apiKey = String(data: apiKeyData, encoding: .utf8) else {
    fatalError("API key not provisioned")
}
// Key is provisioned during app onboarding or via MDM
```

## Common Pitfalls

- **Backup exposure**: Android auto-backup includes SharedPreferences by default. Fix: Set `android:allowBackup="false"` or use `android:fullBackupContent` to exclude sensitive files. [src1]
- **Clipboard data leakage**: Sensitive data copied to clipboard is accessible to all apps. Fix: Use `UIPasteboard.general.setItems([], options: [.localOnly: true, .expirationDate: Date().addingTimeInterval(60)])` on iOS; on Android, mark fields as `android:importantForAutofill="no"` and use `ClipboardManager` with expiry. [src2]
- **Screenshot/screen recording exposure**: iOS and Android capture app screens for task switcher. Fix: Add a blur overlay in `applicationWillResignActive` (iOS) or set `FLAG_SECURE` on the window (Android). [src2]
- **Insecure deep link handling**: Unvalidated deep links allow injection attacks. Fix: Validate all URI parameters, use Universal Links (iOS) / App Links (Android) with domain verification. [src1]
- **Certificate pinning without backup pins**: App becomes unusable after server certificate rotation. Fix: Always include at least 2 pins (current + next rotation key) with an expiration date. [src4]
- **Ignoring Keychain accessibility classes**: Using `kSecAttrAccessibleAlways` makes Keychain data available even when device is locked. Fix: Use `kSecAttrAccessibleWhenUnlockedThisDeviceOnly` or `kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly` for sensitive data. [src3]
- **Hardcoded symmetric keys in app binary**: Easily extractable with `strings` or Hopper. Fix: Generate keys in Keystore/Keychain or derive them from server-provided material via HKDF. [src6]
- **Missing ProGuard/R8 rules**: Default configuration may not obfuscate security-critical classes. Fix: Verify obfuscation output with `apktool d app.apk` and check that class/method names are obfuscated. [src7]

## Diagnostic Commands

```bash
# Static analysis with MobSF (Docker)
docker run -it --rm -p 8000:8000 opensecurity/mobile-security-framework-mobsf:latest

# Extract SPKI hash for certificate pinning
openssl s_client -connect api.example.com:443 2>/dev/null | \
  openssl x509 -pubkey -noout | \
  openssl pkey -pubin -outform DER | \
  openssl dgst -sha256 -binary | \
  openssl enc -base64

# Check Android APK for plaintext secrets
apktool d app.apk -o app_decoded
grep -rn "api_key\|secret\|password\|token" app_decoded/

# Check Android APK for cleartext traffic
grep -rn "cleartextTrafficPermitted" app_decoded/

# Verify iOS app binary for hardcoded strings
strings MyApp.app/MyApp | grep -i "key\|secret\|password\|token"

# Check Android obfuscation effectiveness
jadx -d output app.apk
grep -rn "class.*Activity\|class.*Fragment" output/

# Run OWASP dependency check on Android project
./gradlew dependencyCheckAnalyze

# Verify network security config is applied
adb shell dumpsys package com.example.app | grep networkSecurityConfig
```

## Version History & Compatibility

| Standard/Tool | Version | Status | Key Changes |
|---|---|---|---|
| OWASP MASVS | v2.1.0 | Current (2024) | Added MASVS-PRIVACY category; removed L1/L2/R levels; introduced MAS Testing Profiles |
| OWASP MASVS | v1.5.0 | Deprecated | Three verification levels (L1, L2, R); MSTG companion |
| Android Jetpack Security | 1.1.0-alpha06 | Current | EncryptedSharedPreferences + EncryptedFile |
| Play Integrity API | 1.3 | Current (2025) | Replaces SafetyNet Attestation (deprecated 2024) |
| Apple App Attest | iOS 14+ | Current | Device attestation; replaces DeviceCheck for assertions |
| Apple ATS | iOS 9+ | Enforced | TLS 1.2+ required by default; exceptions need justification |
| CryptoKit | iOS 13+ | Current | Modern Swift crypto API (AES-GCM, SHA-256, P-256) |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| App handles user credentials, PII, or payment data | Building a static content app with no user data | Basic HTTPS enforcement only |
| App communicates with backend APIs | App is entirely offline with no network calls | Focus on MASVS-STORAGE only |
| App processes financial or health data | Building an internal prototype/demo | Simplified security for development |
| App is distributed via public app stores | App is only used on managed/MDM devices | Enterprise MDM security policies |
| Regulatory compliance required (GDPR, HIPAA, PCI DSS) | No regulatory requirements apply | Risk-based subset of MASVS |

## Important Caveats

- OWASP MASVS v2.0+ no longer defines verification "levels" (L1/L2/R) -- instead use MAS Testing Profiles to determine which controls apply to your risk level
- Android EncryptedSharedPreferences requires API 23+ (Android 6.0) and the AndroidX Security library; it has been deprecated in favor of direct Keystore usage but remains functional
- Certificate pinning can lock users out of your app if pins expire and the app is not updated -- always set pin expiration dates and include backup pins
- Root/jailbreak detection is an arms race -- determined attackers can bypass any detection method; it should be one layer in a defense-in-depth strategy, not the sole protection
- iOS Keychain items may persist across app reinstalls unless `kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly` is used -- this can be a privacy concern
- Cross-platform frameworks (React Native, Flutter) add additional attack surface through their bridge layers -- apply native security controls on each platform side

## Related Units
<!-- Generated from related_kos frontmatter -->

- [XSS Prevention Guide](/software/security/xss-prevention/2026)
- [JWT Implementation Guide](/software/patterns/jwt-implementation/2026)
- [API Security Best Practices](/software/security/api-security/2026)
