Skip to content
iOS

Skill: AES Encryption

Overview

AES is the standard symmetric cipher for encrypting data at rest. On iOS, use Apple CryptoKit with AES-GCM, which provides both confidentiality and integrity (authenticated encryption). Never roll your own crypto, never use ECB mode, never reuse a nonce/IV, and store keys in the Keychain or derive them from the Secure Enclave. For most "encrypt this blob" needs, AES-GCM with a per-message random nonce is the right default.

Use Cases

  • Encrypting cached sensitive data, local files, or a local database blob.
  • Protecting exported/backed-up data.
  • Wrapping data before storing outside the Keychain.

Best Practices

  • Use AES-GCM (authenticated). Generate a fresh random nonce per encryption.
  • Generate keys with SymmetricKey(size: .bits256); store in Keychain or use Secure Enclave.
  • Derive keys from passwords with a KDF (HKDF/PBKDF2) — never use a raw password as a key.
  • Verify the authentication tag on decryption (CryptoKit does this; handle the thrown error).
  • Keep crypto in a small, tested module with known-answer tests.

Anti-Patterns

  • ❌ Custom/"XOR" encryption or unauthenticated modes (ECB/CBC without a MAC).
  • ❌ Reusing a nonce/IV across messages (catastrophic for GCM).
  • ❌ Hardcoding keys in source or storing them next to the ciphertext.
  • ❌ Using a user password directly as an AES key.
  • ❌ Ignoring decryption (tag verification) failures.

Checklist

  • [ ] AES-GCM via CryptoKit; fresh random nonce per message.
  • [ ] 256-bit keys stored in Keychain / Secure Enclave.
  • [ ] Password-derived keys use a KDF with a salt.
  • [ ] Auth tag verified; decryption errors handled.
  • [ ] Crypto isolated and covered by known-answer tests.

Swift Examples

swift
import CryptoKit

enum Crypto {
    static func newKey() -> SymmetricKey { SymmetricKey(size: .bits256) }

    static func encrypt(_ plaintext: Data, with key: SymmetricKey) throws -> Data {
        let sealed = try AES.GCM.seal(plaintext, using: key)   // random nonce per call
        guard let combined = sealed.combined else { throw CryptoError.encryptionFailed }
        return combined                                        // nonce + ciphertext + tag
    }

    static func decrypt(_ combined: Data, with key: SymmetricKey) throws -> Data {
        let box = try AES.GCM.SealedBox(combined: combined)
        return try AES.GCM.open(box, using: key)               // throws if tag invalid
    }

    /// Derive a key from a password using HKDF + a stored random salt.
    static func deriveKey(password: String, salt: Data) -> SymmetricKey {
        let ikm = SymmetricKey(data: Data(password.utf8))
        return HKDF<SHA256>.deriveKey(inputKeyMaterial: ikm, salt: salt, outputByteCount: 32)
    }
}

enum CryptoError: Error { case encryptionFailed }

Common Interview Questions

  • Why AES-GCM over AES-CBC/ECB?
  • What happens if you reuse a GCM nonce?
  • Why must you not use a password directly as a key?
  • Where do you store the encryption key on iOS?
  • What does the authentication tag protect against?

AI Implementation Notes

  • Default to CryptoKit AES-GCM with a per-message random nonce; never ECB or homegrown crypto.
  • Store keys in Keychain/Secure Enclave; derive password keys via HKDF/PBKDF2 with a salt.
  • Generate known-answer tests for the crypto module.
  • Related: keychain.md, ../../../standards/security_standards.md.