Skip to content

MASTG-BEST-0065: Implementing Storage Integrity Checks on iOS

Implement storage integrity checks in iOS apps to detect unauthorized modifications to data stored on the device (for example, in the Keychain, UserDefaults/NSUserDefaults, files, or databases). These checks raise the cost for attackers who try to tamper with the app's data, especially on jailbroken devices, through backups, or by directly manipulating the app's data directory. For detecting modifications to the app's own executable code, see Implementing Source Code Integrity Checks on iOS.

Compute an HMAC over any data you store on the device before writing it, and verify the HMAC before reading. Use a key that is stored in the iOS Keychain with a strict accessibility setting (e.g., kSecAttrAccessibleWhenUnlockedThisDeviceOnly) so it cannot be extracted from a backup or transferred to another device.

Apple's CryptoKit provides a modern, Swift-friendly API:

import CryptoKit
import Security

func hmac(for data: Data, key: SymmetricKey) -> Data {
    let mac = HMAC<SHA256>.authenticationCode(for: data, using: key)
    return Data(mac)
}

func verify(data: Data, mac: Data, key: SymmetricKey) -> Bool {
    let expected = HMAC<SHA256>.authenticationCode(for: data, using: key)
    return Data(expected) == mac
}

Alternatively, use CCHmac from CommonCrypto for Objective-C or mixed codebases.

If you also encrypt the data, follow the Encrypt-then-MAC pattern: encrypt first, then compute the HMAC over the ciphertext.

Warning

File storage integrity checks are bypassable if the attacker can extract the HMAC key from the Keychain (possible on a jailbroken device) or intercept the verification logic. Treat these as a defense-in-depth control rather than a standalone guarantee.

Tests

MASTG-TEST-0387: References to Storage Integrity Check APIs