Irbisa · Cheatsheet

Security, Privacy & Keychain

Data protection classes, ATS and TLS, certificate pinning, biometrics, jailbreak detection, secrets in the build, permissions, ATT and location

Middle Developer · 20 questions

  1. 01

    What is the Keychain on iOS, how is it protected, and what kinds of items can it hold?

    The Keychain is an encrypted database of small secrets that the system keeps for you: apps reach it only through the SecItem API, and every item is encrypted with keys protected by the device passcode and the Secure Enclave.

  2. 02

    Can you hide an API key in Info.plist or an .xcconfig file? Where should a secret the app relies on actually live?

    No. Everything in the .ipa ships to every user: Info.plist is a readable file in the bundle and string constants sit in the binary, so the key is extracted in minutes.

  3. 03

    How do you add Face ID / Touch ID to an app with LocalAuthentication?

    Create an LAContext, call canEvaluatePolicy to check that biometrics are available and enrolled, then call evaluatePolicy with a reason string.

    import LocalAuthentication // Info.plist needs NSFaceIDUsageDescription, or Face ID requests failfunc unlockWithBiometrics() async -> Bool {  let context = LAContext()  context.localizedFallbackTitle = "Enter Password"…
  4. 04

    How do runtime permissions work on iOS: what happens if NSCameraUsageDescription is missing, and what can the app do after the user taps Don't Allow?

    The app declares a purpose string in Info.plist, and the system asks the user once, on the first request.

    import AVFoundationimport UIKit enum CameraAccess { case granted, denied, restricted } // Info.plist: NSCameraUsageDescription = "Scan the QR code on your parcel"…
  5. 05

    How does HTTPS protect a request: what does the TLS handshake agree on, and what does the app check in the server's certificate?

    TLS gives a request encryption, integrity and the server's identity.

  6. 06

    What are the Data Protection classes for files, which one does a file get by default, and when would you change it?

    Each file is encrypted with a key from a class that decides when it can be read: complete only while unlocked, completeUnlessOpen keeps already open files usable after lock, completeUntilFirstUserAuthentication (the default) after the first unlock since boot, none always.

    // A file that must be unreadable while the phone is lockedfunc saveStatement(_ pdf: Data, to url: URL) throws {  try pdf.write(to: url, options: [.atomic, .completeFileProtection])} // On a directory: new files created inside inherit the class…
  7. 07

    Find the bug: users get signed out at random, usually when the app syncs in the background and the phone is locked.

    func saveToken(_ token: Data) {  SecItemAdd([kSecClass: kSecClassGenericPassword, kSecAttrService: "auth", kSecValueData: token,              kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked] as CFDictionary, nil)}func readToken() -> String? {  var result: CFTypeRef?  let query = [kSecClass: kSecClassGenericPassword, kSecAttrService: "auth", kSecReturnData: true] as CFDictionary  guard SecItemCopyMatching(query, &result) == errSecSuccess, let data = result as? Data else { return nil }  return String(decoding: data, as: UTF8.self)}// Runs from a silent push and a BGAppRefreshTask, usually with the phone locked:func backgroundSync() async {  guard let token = readToken() else { return session.logOut() }  await api.sync(token: token)}

    The token is saved as kSecAttrAccessibleWhenUnlocked, so a background sync on a locked phone gets errSecInteractionNotAllowed.

  8. 08

    An https request fails with -1200 and -9802 in the log, yet the same URL loads in a browser. What does ATS demand that the server lacks, and how do you check?

    ATS rejects the TLS handshake because the server is below Apple's minimum: TLS 1.2 or later, a forward-secret ECDHE cipher, and a certificate signed with SHA-256 on an RSA key of 2048+ bits or an ECC key of 256+ bits.

  9. 09

    Find the bug: pinning works in the test, but a proxy with a user-installed CA can still read the app's traffic.

    final class PinningDelegate: NSObject, URLSessionDelegate {  let pinnedKeyHash: Data   // SHA-256 of the server's public key  init(pinnedKeyHash: Data) { self.pinnedKeyHash = pinnedKeyHash }   func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,                  completionHandler: @escaping @Sendable (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {    guard let trust = challenge.protectionSpace.serverTrust,          SecTrustEvaluateWithError(trust, nil),          let leaf = (SecTrustCopyCertificateChain(trust) as? [SecCertificate])?.first,          let key = SecCertificateCopyKey(leaf),          let raw = SecKeyCopyExternalRepresentation(key, nil) as Data?,          Data(SHA256.hash(data: raw)) == pinnedKeyHash    else {      completionHandler(.performDefaultHandling, nil)   // pin mismatch: use system trust      return    }    completionHandler(.useCredential, URLCredential(trust: trust))  }}

    On a pin mismatch the delegate falls back to .performDefaultHandling, so any certificate that a trusted CA signed is accepted and pinning protects nothing.

  10. 10

    How do you hash, encrypt and authenticate data with CryptoKit, and where does the encryption key live?

    SHA256.hash gives a fingerprint, AES.GCM.seal (or ChaChaPoly) encrypts and detects tampering in one step, and HMAC proves who produced a message.

    import CryptoKit // Hash: a fingerprint for integrity checks, not for storing passwordslet digest = SHA256.hash(data: Data("report.pdf contents".utf8))let hex = digest.map { String(format: "%02x", $0) }.joined()…
  11. 11

    How does privacy work in Logger and os_log, and what must never reach the device log?

    The unified log redacts interpolated values you do not mark .public: strings and objects appear as <private>, while numbers and booleans are public by default.

    import os let log = Logger(subsystem: "com.example.bank", category: "auth") func didSignIn(userID: Int, email: String, plan: String) {  log.info("Signed in user \(userID)")                           // numbers: public by default…
  12. 12

    How do you keep sensitive screens out of the app-switcher snapshot and out of screen recordings on iOS?

    iOS has no flag that blocks screenshots, so you cover the content yourself: show a privacy overlay as soon as the scene becomes inactive, before the system takes the app-switcher snapshot, and hide sensitive views while sceneCaptureState is .active.

    import SwiftUI struct BankRoot: View {  @Environment(\.scenePhase) private var phase   var body: some View {…
  13. 13

    What is App Tracking Transparency, when may you show its prompt, and what happens to the IDFA and ad attribution when the user declines?

    ATT is the permission an app needs before it tracks the user across other companies' apps and websites, which includes reading the IDFA.

    import AppTrackingTransparencyimport AdSupportimport UIKit // Info.plist: NSUserTrackingUsageDescription says what the user gets in return@MainActor…
  14. 14

    What is the difference between When In Use and Always location access, and what does turning off Precise Location change for your code?

    When In Use delivers locations while the app is in use; Always also lets the system relaunch the app for geofences, visits and significant changes.

    import CoreLocation @MainActorfinal class LocationGate: NSObject, CLLocationManagerDelegate {  private let manager = CLLocationManager()…
  15. 15

    This jailbreak check ships in a banking app. What does it catch, how is it bypassed, and what should the app do instead of exit(0)?

    @MainActor func isJailbroken() -> Bool {  let paths = ["/Applications/Cydia.app", "/bin/bash", "/usr/sbin/sshd", "/etc/apt"]  if paths.contains(where: { FileManager.default.fileExists(atPath: $0) }) { return true }  if UIApplication.shared.canOpenURL(URL(string: "cydia://")!) { return true }  do {    try "x".write(toFile: "/private/jb.txt", atomically: true, encoding: .utf8)    return true   // the sandbox should have refused this write  } catch {    return false  }} // application(_:didFinishLaunchingWithOptions:)// if isJailbroken() { exit(0) }

    It catches only an old rootful jailbreak with Cydia on an unmodified copy of the app.

  16. 16

    What is the Secure Enclave, and how do you create and use a key in it with CryptoKit?

    The Secure Enclave is a separate coprocessor that generates and uses keys without ever revealing them to iOS.

    import CryptoKitimport Security enum EnclaveError: Error { case unavailable } func makeDeviceKey() throws -> SecureEnclave.P256.Signing.PrivateKey {…
  17. 17

    What do .userPresence, .biometryAny and .biometryCurrentSet change for a Keychain item, and what happens to it when the user enrolls a new face?

    They set what the system demands before it decrypts the item: .userPresence accepts biometrics or the passcode, .biometryAny any enrolled face or finger, .biometryCurrentSet only the set enrolled when the item was saved; after a new enrollment that item can never be read again.

    import LocalAuthenticationimport Security func saveRefreshToken(_ token: Data) -> OSStatus {  let access = SecAccessControlCreateWithFlags(    nil, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, .biometryCurrentSet, nil)!…
  18. 18

    You pin the server's public key. How do you rotate that key without locking out users on old app versions?

    Ship the next key's pin before you need it: every release pins the current key and a pre-generated backup, so the server can switch to the backup and old builds keep connecting.

    <key>NSAppTransportSecurity</key><dict>  <key>NSPinnedDomains</key>  <dict>    <key>api.example.com</key>    <dict>…
  19. 19

    What are DeviceCheck and App Attest, and what does each one let your server verify?

    Both let your server ask Apple about the caller.

    import CryptoKitimport DeviceCheck enum AttestError: Error { case unsupported } // Once per install: create a key and let Apple vouch for it…
  20. 20

    How would you design the app lock for a banking app with a 6-digit PIN, Face ID and auto-lock after a minute in the background?

    Keep the real credential behind device-bound Secure Enclave keys and make the server the judge: Face ID unlocks a key through SecAccessControl, the PIN is checked by the server with an attempt limit, and the session is a short-lived token kept only in memory.