iMessage is the end-to-end encrypted messaging service built into Apple's devices. Its architecture is worth studying because it solves, at very large scale, the problems every secure messenger faces: how a sender finds the keys of every device a person owns, how a message reaches a phone that is asleep or offline, how large media is moved without pushing megabytes through a notification channel, and how to keep the server from reading anything.
This article is built from what Apple has published: the Apple Platform Security guide and Apple Security Research's description of the PQ3 protocol. Where Apple has not published a detail, such as how its storage or queues are built, the article says so and describes how you would build that part yourself. For the cryptographic ratchet that most secure messengers share, read the Signal Protocol article alongside this one.
The moving parts
Four services and the devices themselves make up the system.
- Devices. Every iPhone, iPad, Mac and Watch signed in to iMessage has its own key pairs. Private keys stay on the device; signing keys are generated in the Secure Enclave.
- The identity directory (IDS). Apple's Platform Security guide calls it the Apple Identity Service; the PQ3 post calls it the Identity Directory Service. It maps a handle, a phone number or email address, to the public keys and push addresses of every device registered for that handle.
- APNs. The Apple Push Notification service relays small encrypted payloads to devices and holds them for devices that are offline.
- iCloud storage. Large content, such as photos or long text, is encrypted on the sender's device and uploaded; only a reference and a key travel in the message.
The essential design decision is that the server is a router and a store for ciphertext, and the unit of delivery is a device, not a person. Everything else follows from that.
Registration and the key directory
When a device turns on iMessage it generates its key pairs and registers the public halves with IDS, together with its APNs address. Under PQ3, Apple describes each device registering a Kyber-1024 post-quantum key encapsulation public key and a classical P-256 elliptic-curve key agreement public key, both signed with ECDSA using a P-256 authentication key generated by the device's Secure Enclave.
To send, the sender's device asks IDS for every device registered to each recipient handle, and to the sender's own handle, so that the sender's other devices also get a copy. The directory is therefore the root of trust: if it returned an extra device key controlled by an attacker, the sender would encrypt to it. Apple's answer for users who face that threat is Contact Key Verification, introduced in iOS 17.2, which lets users compare verification codes and alerts them if an unexpected device appears. Treat it as an optional defence on top of directory trust, not as something every conversation uses.
If you build a similar system, the directory is the component to engineer most carefully. Cache lookups on the sender with short lifetimes, version each handle's device list so clients can detect change, sign entries, and consider an append-only transparency log so that a silently added key is publicly detectable.
Sending: one ciphertext per device
Apple's guide describes the original, pre-PQ3 send path. For each recipient device the sender generated a random 88-bit value, used it as an HMAC-SHA-256 key over the two parties' public keys and the plaintext, kept 40 bits of the result, and concatenated the two into a 128-bit key that encrypts the message with AES in counter mode. That per-message key was itself encrypted to the recipient device's public key, using RSA-OAEP or, from iOS 13, ECIES. The whole message was signed with the sender's ECDSA key. Communication with APNs uses a forward-secret TLS channel, so the transport layer adds a second envelope around ciphertext the server cannot read.
The point to carry away is the fan-out. A conversation between you (phone, laptop) and a friend (phone, tablet, watch) means one send produces four encrypted copies: three for the friend's devices and one for your laptop. A group of six people averaging three devices each produces about seventeen copies per message. That fan-out happens on the sending device, because only the sender holds the plaintext.
Payload size is the second constraint. Apple states that APNs can relay messages of only 4 or 16 KB, depending on the OS version. Anything larger goes through the attachment path.
PQ3: the current protocol
In 2024 Apple replaced the iMessage protocol with PQ3, rolled out with iOS 17.4, iPadOS 17.4, macOS 14.4 and watchOS 10.4. Apple calls it Level 3 security: post-quantum cryptography protects both initial key establishment and ongoing message exchange, and the conversation can automatically recover its security if a key is compromised.
Two ratchets run together. A classical elliptic-curve Diffie-Hellman ratchet advances on every message and adds only 32 bytes of overhead. A post-quantum ratchet using Kyber-768 rekeys on an adaptive schedule, about every 50 messages and at least once every 7 days; because each post-quantum rekey adds more than 2 KB to a message, doing it on every message would be wasteful. Messages are padded with the Padmé heuristic, at most 12 percent overhead, so that lengths leak less about content.
The design point here generalises: post-quantum keys are large, so the protocol spends them where they buy the most, at session setup and periodically, and relies on the cheap classical ratchet for per-message forward secrecy in between. The Signal Protocol makes a similar trade with PQXDH and its own ratchet design.
Attachments: upload once, key per message
For a photo, the sender generates a random 256-bit key, encrypts the file with AES in counter mode, and uploads the ciphertext to iCloud. The message itself then carries only the key, the location of the upload and a SHA-1 hash of the encrypted file, and that small message goes through the normal per-device encryption. Each recipient device downloads the blob, checks the hash and decrypts it.
This is the pattern to copy. The expensive object is stored once, however many devices receive it, and access to it is controlled by possession of a key delivered end to end. The storage service holds ciphertext, sees a download from each device, and cannot read the content. A worked example: a 3 MB video sent to a group of five people with three devices each is uploaded once (3 MB) and then downloaded fifteen times, while the per-device fan-out costs fifteen small encrypted messages of a few hundred bytes plus overhead. Without the indirection, the sender would encrypt and upload 45 MB.
Offline devices, multiple devices and history
Phones are offline often. Apple states that messages for offline devices are stored on Apple servers for up to 30 days. After that, a device that has been off the network loses messages it was never delivered, which is a deliberate bound on how much undelivered ciphertext the service holds.
Message history across devices is a separate feature, Messages in iCloud, which syncs conversations between a user's own devices with end-to-end encryption. Its protection interacts with iCloud Backup settings: Apple's documentation describes Advanced Data Protection as the setting that keeps the keys for backups out of Apple's reach, so whether history is recoverable by Apple depends on that choice. Check the current Platform Security guide before making claims about a specific configuration.
Defending the receiving device
End-to-end encryption protects messages from the server, not the recipient from the sender. An attacker who can send a message can send malformed images, documents or rich previews that the recipient's device parses automatically, without any tap. iMessage has been the target of such zero-click exploit chains.
Apple's response, starting with iOS 14, was BlastDoor: a tightly sandboxed service that parses untrusted message content before the rest of the system sees it. The architectural lesson applies to any messenger: treat every inbound payload as hostile, decode it in an isolated process with minimal privileges, and pass only a validated, simplified representation to the rest of the app.
How you would build the same shape
Apple does not publish its server design, so the following is a reference design for a system with iMessage's properties, not a description of Apple's. The sender does lookups, fan-out and encryption; the server stores ciphertext per device and pushes a wake-up.
# Sender side (device)
def send(conversation, plaintext, attachment=None):
if attachment:
k = random_bytes(32)
blob = aes_ctr_encrypt(k, attachment)
url = storage.upload(blob) # once, regardless of fan-out
plaintext = embed(plaintext, key=k, url=url, digest=sha256(blob))
devices = []
for handle in conversation.handles + [me.handle]:
devices += directory.lookup(handle, cache_ttl=300) # versioned, signed entries
devices = [d for d in devices if d.id != me.device_id]
msg_id = uuid4()
for d in devices:
session = sessions.get_or_establish(d) # ratchet state per device pair
ct = session.encrypt(pad(plaintext))
relay.enqueue(device=d.id, msg_id=msg_id, ciphertext=ct, sig=sign(ct))
# Server side (relay)
def enqueue(device, msg_id, ciphertext, sig):
queue[device].put_if_absent(msg_id, ciphertext, sig, expires=now() + days(30))
push.wake(device) # tiny notification, no content
def on_device_connect(device):
for m in queue[device].pending():
deliver(device, m) # device acks, then server deletesThe idempotency key msg_id lets the sender retry after a timeout without the recipient seeing duplicates. The per-device queue makes delivery state independent across devices, so a sleeping watch never blocks a phone. The 30-day expiry bounds storage. For the queueing and connection tiers in more depth, see message queues and designing a real-time chat system.
Failure modes and operations
| Failure | What users see | Mitigation |
|---|---|---|
| Stale directory cache | A new device misses messages; a removed device still receives them | Short cache lifetimes, device-list versions, refresh on decryption failure |
| Session state diverges | Messages that fail to decrypt on one device | Re-establish the session and ask the sender to resend for that device only |
| Device offline beyond retention | Gaps in history on that device | Sync history from the user's other devices |
| Attachment upload succeeds, message fails | Orphaned ciphertext in storage | Lifecycle rules that delete unreferenced blobs |
| Large group fan-out | Slow sends and battery cost on the sender | Cap group size; consider sender keys or MLS-style group keys |
| Malformed inbound content | Crashes or exploitation on receipt | Parse in a sandboxed process; fuzz every decoder |
Trade-offs
Sender-side fan-out keeps the server blind but makes send cost grow with the number of devices, which is why large groups are expensive in this design. Directory-based key distribution is seamless for users but concentrates trust in the directory, which verification features and transparency logs exist to check. The push relay limits payload size, which forces the attachment indirection but keeps notifications small and cheap. And the server still sees metadata, who sends to which device and when, even though it cannot read content. Every design in this family picks a point on these axes; iMessage chose seamlessness and multi-device convenience, with stronger checks available to people who need them.
What to do next
- Read the current Apple Platform Security guide sections on iMessage and the Apple Security Research PQ3 post; do not rely on older descriptions of the protocol.
- Draw your own messenger's architecture with the same five parts: devices, directory, relay, blob store and history sync, and mark which hold plaintext.
- Make the directory versioned and signed, and decide whether you will publish a transparency log.
- Design delivery as per-device queues with idempotent message ids, acknowledgements and a fixed retention window.
- Route large content through an encrypted blob store with per-message keys, and add lifecycle cleanup for orphaned blobs.
- Parse every inbound payload in a sandboxed process and fuzz the decoders.
- Compare your choices with notification system architecture for the push tier and with the Signal Protocol for the ratchet.