Technical Deep Dive

Technical

A detailed look at the cryptographic protocols that make identity verification possible — from key exchange to contacts encryption at rest.

Overview

It's Me is an identity verification system designed to combat deepfake impersonation attacks. The core insight is that while AI can perfectly replicate someone's voice and face, it cannot replicate a cryptographic secret that was exchanged between two people.

The system works by establishing a shared secret between two people when they exchange contact information. This secret is then used to generate synchronized, time-based verification codes that both parties can compare over any communication channel — phone, video, or messaging.

🛡️ Key Properties

Offline Verification: Verification codes are generated locally on-device. No internet required to verify someone.
Per-Contact Keys: Each relationship has unique cryptographic keys. Your code with Alice is different from your code with Bob.
Time-Based Codes: Codes rotate every 30 seconds, preventing replay attacks.
Device-Bound: Your identity and contacts encryption key are bound to your physical device via the iOS Keychain.
Encrypted at Rest: All contact data is encrypted with AES-256-GCM before being stored.

Threat Model

It's Me is designed to protect against impersonation attacks where an adversary attempts to convince you they are someone you know and trust.

Attacks We Defend Against

  • Voice cloning: AI-generated audio that perfectly mimics a known person's voice
  • Video deepfakes: Real-time or pre-recorded video impersonation
  • Caller ID spoofing: Falsified phone numbers or identities
  • Account compromise: Attackers using stolen accounts to message as someone else
  • Social engineering: Urgent requests exploiting trust relationships
  • Phone theft/cloning: Device binding detects when a contact's phone changes
⚠️ Important Limitation

This system cannot verify identity if the initial exchange was compromised (e.g., you added an impersonator thinking they were someone else). For in-person exchanges, the security model assumes trust at the point of physical contact establishment. For online exchanges, the SAS verification step provides an additional confirmation layer.

Identity & Key Generation

When you first set up It's Me, the app generates your cryptographic identity entirely on your device.

What Gets Created

  • Curve25519 Key Pair: A public/private key pair for Elliptic Curve Diffie-Hellman (ECDH) key agreement. The private key never leaves your device.
  • Fingerprint: The first 16 characters of the SHA-256 hash of your public key, used as a short identifier.
  • Contacts Encryption Key: A 256-bit AES key generated with SecRandomCopyBytes, used to encrypt all contact data at rest.
  • PIN Hash: Your access PIN is salted and hashed before storage. The plaintext PIN is never stored.

Storage

All identity material is stored in the iOS Keychain with the access level kSecAttrAccessibleWhenUnlockedThisDeviceOnly. This means:

  • Data is encrypted by the device's hardware at rest
  • Data is only accessible when the device is unlocked
  • Data does not transfer via iCloud Keychain or device backup
  • Data is permanently lost if the device is wiped without export
// Identity generation (on first launch) let privateKey = Curve25519.KeyAgreement.PrivateKey() let publicKey = privateKey.publicKey let fingerprint = SHA256(publicKey.rawRepresentation) .prefix(8).hex // e.g., "A1B2C3D4E5F6G7H8" // Contacts encryption key (256-bit random) var keyBytes = [UInt8](repeating: 0, count: 32) SecRandomCopyBytes(kSecRandomDefault, 32, &keyBytes) // Stored in Keychain — never leaves device except during export

Contact Exchange

It's Me supports three methods for establishing contacts. All methods result in both parties sharing a unique verification seed derived via Elliptic Curve Diffie-Hellman key agreement.

Method 1: QR Code (In-Person)

The highest-trust exchange method. Both parties are physically present and scan each other's QR codes.

// Each QR code contains: { "version": 2, "name": "Alice", "fingerprint": "A1B2C3D4E5F6G7H8", "ephemeralPublicKey": "base64-encoded-key", "deviceSecret": "6-digit-secret" }
QR Code Exchange Flow
Alice
Shows QR
Bob
Scans QR
Bob
Shows QR
Alice
Scans QR
Both
Shared Secret

Method 2: Bluetooth (In-Person)

Both parties are physically present and exchange keys over Bluetooth Low Energy (BLE). The exchange is encrypted end-to-end.

  • Devices discover each other via BLE advertising
  • An ephemeral ECDH key exchange is performed over the BLE channel
  • Contact data is encrypted with the derived shared secret before transmission
  • The BLE exchange is authenticated — both parties confirm they are exchanging with the intended device

Method 3: Online Exchange

For contacts who are not physically present, the app supports an asynchronous online exchange via an invite code system.

Online Exchange Flow
Alice
Creates Invite
Server
Stores E2EE Blob
Bob
Accepts Invite
Both
SAS Verification

The online exchange works as follows:

  1. Alice creates an invite, which generates a random invite code (8 characters) and a random encryption code (8 characters).
  2. Alice's contact payload (name, fingerprint, public key, device secret) is encrypted with AES-256-GCM using a key derived from the encryption code via HKDF-SHA256.
  3. The encrypted payload is uploaded to the server. The server stores only the ciphertext — it never sees the encryption code and cannot decrypt the payload.
  4. Alice shares the invite code and encryption code with Bob through a separate channel (text, email, etc.).
  5. Bob enters the invite code and encryption code in the app, which downloads Alice's encrypted payload and decrypts it locally.
  6. Bob's encrypted payload is uploaded for Alice to retrieve.
  7. Both parties now have each other's contact data and can derive a shared verification seed via ECDH.
// Online exchange encryption let encryptionCode = "K7X2M9PL" // shared out-of-band // Derive encryption key from code using HKDF let key = HKDF<SHA256>.deriveKey( inputKeyMaterial: SymmetricKey(data: encryptionCode.utf8), salt: "itsme-online-exchange-v1", info: "payload-encryption", outputByteCount: 32 ) // Encrypt payload with AES-GCM + AAD let sealed = AES.GCM.seal( payloadJSON, using: key, authenticating: "itsme-online-payload-v1" )

SAS Verification for Online Contacts

Because online exchanges don't have the physical presence guarantee of in-person exchanges, the app requires a Short Authentication String (SAS) confirmation step. After the exchange completes:

  1. Both parties see the same derived SAS code (based on the shared secret)
  2. They verbally confirm the SAS code over a phone or video call
  3. Once confirmed, the contact is marked as verified

This prevents man-in-the-middle attacks where an attacker might intercept and replace payloads during the online exchange.

Shared Secret Derivation (All Methods)

Regardless of exchange method, the shared verification seed is derived identically:

// ECDH key agreement let sharedSecret = try myPrivateKey.sharedSecretFromKeyAgreement( with: theirPublicKey ) // Sort fingerprints to ensure both sides derive the same seed let sortedFingerprints = [myFingerprint, theirFingerprint].sorted() // Derive verification seed using HKDF let verificationSeed = sharedSecret.hkdfDerivedSymmetricKey( using: SHA256.self, salt: sortedFingerprints.joined(), info: "itsme-verification-seed-v1", outputByteCount: 32 )
🔑 Why Sort Fingerprints?

Both parties must derive the exact same seed from the exact same inputs. By sorting fingerprints alphabetically before using them as the HKDF salt, we ensure both Alice and Bob produce identical output regardless of who initiated the exchange.

Interactive Verification

Verification is an interactive, multi-step process designed to prove both parties hold the same shared secret while defending against replay and pre-recording attacks.

Time Window Synchronization

When verification begins, the app waits until the next unused 30-second time window starts. This is calculated as floor(unixTimestamp / 30). The app tracks which time windows have been used for each contact, ensuring a window is never reused. This prevents an attacker from replaying a previously observed verification session.

The wait also ensures that both parties generate codes at the same moment, eliminating the possibility that an attacker pre-computed or pre-recorded a valid code before the session began.

Session Confirmation

Both devices display a session ID derived from the locked time window and device secrets. This is read aloud to confirm both devices are in sync before proceeding with word selection.

Five-Word Selection

Each person is shown five NATO phonetic words, derived deterministically from the shared secret, the time window, and the selector's fingerprint. Because each person's fingerprint is different, each person sees a different set of five words.

// Generate five unique words per selector let optionsKey = HKDF<SHA256>.deriveKey( inputKeyMaterial: SymmetricKey(data: seed), salt: windowData + selectorFingerprint, info: "itsme-word-options-v1" + secretsData, outputByteCount: 32 ) let mac = HMAC<SHA256>.authenticationCode( for: "word-options", using: optionsKey ) // Extract 5 unique NATO indices from HMAC bytes // Words are sorted alphabetically for display

The verification alternates who goes first — which person selects first changes each session. The first person picks one of their five words and says it aloud. Both parties tap that word in their app. Then the second person picks from their own five words, says it aloud, and both tap it.

Why Two Selections Matter

Both word selections are combined into the final code derivation. This means the resulting verification code depends on live, unpredictable input from both parties. An impersonator would need to know the shared secret, the current time window, and correctly predict both word selections to produce a matching code — and the words aren't revealed until each person freely chooses one.

🎲 Combined Selection Space

Each person selects from 5 words out of 26 NATO words. With both parties selecting independently, the combined space is 5 × 5 = 25 possible code outcomes per session. But an attacker doesn't know the five-word sets (which are derived from the secret they don't have), so they can't narrow the possibilities. They would need the shared secret to even see the correct word options.

Final Code Generation

After both selections, the final verification codes are generated:

// Both word selections feed into key derivation let selections = "\(word1):\(word2)" let interactiveKey = HKDF<SHA256>.deriveKey( inputKeyMaterial: SymmetricKey(data: seed), salt: windowData + selections, info: "itsme-interactive-code-v1" + secretsData, outputByteCount: 32 ) // Generate directional codes from the interactive key let myCode = generateCodeFromKey(key: interactiveKey, fingerprint: myFingerprint) let theirCode = generateCodeFromKey(key: interactiveKey, fingerprint: theirFingerprint)

Each party sees two codes: "Your Code" (derived with their own fingerprint) and "Their Code" (derived with the contact's fingerprint). When Alice reads her "Your Code" to Bob, it should match what Bob sees as "Their Code" for Alice — and vice versa.

Interactive Verification Flow
Wait
Fresh Window
Session
Confirm ID
Person A
Picks Word
Person B
Picks Word
Both
Compare Codes

Anti-Replay Properties

  • Fresh time window: Codes can't be pre-recorded because the session waits for an unused window to begin
  • Window tracking: Each time window can only be used once per contact, preventing replay of observed sessions
  • Interactive selections: Both parties contribute unpredictable input, so codes can't be computed in advance
  • Alternating order: Who selects first changes each session, preventing pattern-based attacks

NATO Phonetic Alphabet

Words are chosen from the NATO phonetic alphabet for clarity over voice channels:

ALFA, BRAVO, CHARLIE, DELTA, ECHO, FOXTROT, GOLF, HOTEL, INDIA, JULIET, KILO, LIMA, MIKE, NOVEMBER, OSCAR, PAPA, QUEBEC, ROMEO, SIERRA, TANGO, UNIFORM, VICTOR, WHISKEY, XRAY, YANKEE, ZULU

Device Binding & Phone Change Detection

Each contact exchange includes a device secret — a random 6-digit code stored in the iOS Keychain. This secret is unique per contact and serves as a second factor tied to the physical device.

How It Works

  • During exchange, each party shares their device secret alongside their public key
  • Device secrets are stored in the Keychain with kSecAttrAccessibleWhenUnlockedThisDeviceOnly
  • Because the Keychain doesn't transfer via iCloud backup, device secrets are lost when switching phones
  • If someone switches phones without doing a proper identity export, their device secret will be missing or different

Detection

When verifying, the app checks whether the contact's device secret matches what was originally exchanged. If it doesn't match, the contact is shown in a "degraded" state with a warning that their phone may have changed. This helps detect:

  • Phone theft or cloning attempts
  • Unauthorized device transfers
  • SIM swapping attacks (where the attacker sets up on a new device)
⚠️ Not Always Malicious

A degraded contact doesn't necessarily mean compromise. It can also occur when someone legitimately upgrades their phone without using the export feature. The app prompts you to re-verify with the person to confirm.

Contacts Encryption at Rest

All contact data is encrypted before being written to device storage, protecting it even if the device storage is compromised.

Encryption Scheme

// Encrypt contacts before saving let contactsJSON = try JSONEncoder().encode(contacts) let encryptionKey = KeychainService.loadContactsEncryptionKey() let sealed = try AES.GCM.seal(contactsJSON, using: encryptionKey) // Store nonce + ciphertext + tag as single blob let combined = sealed.nonce + sealed.ciphertext + sealed.tag UserDefaults.set(combined, forKey: "contacts_encrypted")

Key Management

  • The encryption key is a 256-bit AES key generated with SecRandomCopyBytes
  • Stored in the iOS Keychain with kSecAttrAccessibleWhenUnlockedThisDeviceOnly
  • The key does not back up to iCloud — it is bound to the physical device
  • During device transfer, the key is included in the PIN-encrypted identity export

What This Means

If your device is backed up to iCloud, the contacts data is included in the backup — but as AES-256-GCM ciphertext. Without the encryption key (which is in the Keychain and doesn't back up), the data is unreadable. On a restored device, the app detects that it has encrypted data but no key, and prompts you to import your identity from your old phone.

Device Transfer

When switching to a new phone, your cryptographic identity and contacts encryption key need to be transferred. The app provides a secure export/import flow.

Export (Old Phone)

  1. Go to Settings → Export Identity
  2. Create a PIN (4+ digits) to protect the export
  3. A QR code is generated containing your encrypted identity

What's in the Export

// Export payload (version 3) { "version": 3, "name": "Alice", "fingerprint": "A1B2C3D4E5F6G7H8", "privateKey": "base64-encoded", "contactsEncryptionKey": "base64-encoded" } // Encrypted with AES-GCM, key derived from PIN let keyMaterial = pinData + "itsme-export-v1" let encryptionKey = SHA256(keyMaterial) let encrypted = AES.GCM.seal(exportJSON, using: encryptionKey)

Import (New Phone)

  1. Install It's Me on your new phone
  2. The app detects encrypted contacts (from iCloud backup) and prompts for import
  3. Scan the export QR code from your old phone
  4. Enter your PIN to decrypt and restore your identity and contacts encryption key
  5. Contacts are now decryptable and your identity is restored

Wrong Identity Detection

If you scan an export QR from a different identity (not the one that encrypted the contacts on this device), the app detects the mismatch — the imported encryption key won't be able to decrypt the stored contacts. You'll be warned and given the option to cancel or proceed (which will clear the unreadable contacts).

Device Transfer Flow
Old Phone
Export + PIN
QR Code
AES-GCM
New Phone
Scan + PIN
Result
Identity + Key Restored
⚠️ Don't Lose Your PIN

If you forget your export PIN, you'll need to create a new identity and re-add all contacts. There is no recovery mechanism by design — this protects against unauthorized identity theft.

Security Analysis

Why Deepfakes Can't Bypass This

An attacker using AI to impersonate someone faces an impossible challenge: they need to produce a matching verification code, but the code is derived from a shared secret they don't have access to. Even if they perfectly clone the victim's voice and face, they cannot produce the correct code.

Attack Result
Attacker clones voice and asks for code ❌ Attacker can't provide matching code back
Attacker intercepts network traffic ❌ Codes are not transmitted; derived locally
Attacker records and replays old codes ❌ Codes expire after 30 seconds
Attacker intercepts online exchange ❌ Payloads are E2EE; SAS verification catches MITM
Attacker steals victim's phone ⚠️ Requires Face ID / PIN to access app
Attacker clones victim's phone ⚠️ Device binding detects the change
Attacker accesses iCloud backup ❌ Contacts encrypted; key not in backup

Algorithms & Standards

Component Algorithm Standard
Identity Key Pair Curve25519 RFC 7748
Key Agreement ECDH (X25519) RFC 7748
Key Derivation HKDF-SHA256 RFC 5869
Verification Codes HMAC-SHA256 RFC 2104
Contacts Encryption AES-256-GCM NIST SP 800-38D
Online Exchange Encryption AES-256-GCM + HKDF NIST SP 800-38D / RFC 5869
Export Encryption AES-256-GCM NIST SP 800-38D
API Authentication HMAC-SHA256 RFC 2104
Key Storage iOS Keychain Apple Security Framework
Biometric Auth Face ID / Touch ID LocalAuthentication
💻 Open Design

The protocol is intentionally simple and uses well-established cryptographic primitives from Apple's CryptoKit framework. Security comes from the mathematical properties of these algorithms, not from obscurity.

Server Architecture

The server component is minimal by design. It serves only as a temporary relay for online contact exchanges.

What the Server Sees

  • SHA-256 hashes of user fingerprints (not the fingerprints themselves)
  • Encrypted payloads that it cannot decrypt (the encryption key is never sent to the server)
  • Timestamps of when invites are created and accepted
  • IP addresses (as part of normal HTTP communication)

What the Server Cannot Do

  • Read any user's name or identity
  • Decrypt any payload (the encryption code is shared between users out-of-band)
  • Determine who is communicating with whom (only hashed fingerprints are visible)
  • Generate or predict verification codes
  • Tamper with payloads (AES-GCM provides authentication — any modification is detected)

Data Lifecycle

All invite data is automatically deleted after 24 hours or upon successful exchange, whichever comes first. The server stores no long-term user data.