Meta Destroys Hardware Keys to Lock Itself Out of Messenger Backups

88: No Way Out - Engineering Message History Only You Can Read — Meta Tech Podcast

Meta Tech Podcast

Meta deployed post-quantum asymmetric cryptography and physically destroyed administrative access cards for its security modules to preserve multi-device chat histories without holding decryption keys.

AI-assisted summary of 88: No Way Out - Engineering Message History Only You Can Read — original source. Check the original for full context.

Source published:

Key ideas

  • Meta introduced Labyrinth to bring end-to-end encryption to Messenger while preserving instant cross-device chat history across as many as 100 concurrent endpoints per account. (7:17)
  • To secure six-digit PIN recovery, Meta deployed custom rate-limiting firmware on hardware security modules (HSMs) using the OPAQUE protocol and physically destroyed the provisioning smartcards to permanently lock itself out. (10:00)
  • Labyrinth 1.1 transitioned from symmetric storage keys to asymmetric public-key mailboxes, enabling senders to write encrypted messages directly to recipient backups without waiting for the recipient's devices to be online. (26:12)
  • To counter future quantum harvesting attacks against stored archives, Labyrinth 1.1 implemented Hawk, a hybrid cryptosystem combining classical Diffie-Hellman authentication with ML-KEM post-quantum confidentiality. (31:37)

Detailed notes

## Background Published on October 15, 2026, on the Meta Tech Podcast and hosted by Pascal, this episode features Meta engineer Emma Connor discussing Labyrinth, Messenger's encrypted message storage protocol. According to the episode description, the discussion explores how Meta retrofitted end-to-end encryption onto a legacy messaging service without sacrificing historical chat continuity, covering key management, tamper-resistant HSM lockouts, key transparency, and post-quantum cryptography.

## TL;DR Meta software engineer Emma Connor breaks down the engineering behind Labyrinth, the system built to preserve multi-device chat history while establishing default end-to-end encryption across Facebook Messenger. To protect user archives without holding decryption keys, Labyrinth combines the OPAQUE password-authenticated key exchange protocol with tamper-resistant Hardware Security Modules (HSMs) whose provisioning cards were permanently destroyed to prevent internal override. The discussion tracks the protocol's evolution to Labyrinth 1.1, which eliminates synchronization bottlenecks by adopting mailbox-level public keys, audited Key Transparency directories via Cloudflare, and post-quantum hybrid encryption using ML-KEM to protect against future harvest-now-decrypt-later attacks.

## Atmosphere & style The conversation carries a deliberate, intellectually rigorous tone, driven by Pascal's focused questioning and Connor's clear, methodical breakdowns of complex cryptographic trade-offs. The dynamic balances low-level protocol mechanics with pragmatic operational engineering, treating security not as an abstract ideal but as a set of physical and architectural constraints. Listening to the breakdown of intentional design choices—such as engineers physically destroying HSM smartcards to strip themselves of access—conveys a palpable sense of institutional restraint and disciplined systems design.

## Bulletin - `07:17` Meta introduced Labyrinth to bring end-to-end encryption to Messenger while preserving instant cross-device chat history across as many as 100 concurrent endpoints per account. - `10:00` To secure six-digit PIN recovery, Meta deployed custom rate-limiting firmware on hardware security modules (HSMs) using the OPAQUE protocol and physically destroyed the provisioning smartcards to permanently lock itself out. - `26:12` Labyrinth 1.1 transitioned from symmetric storage keys to asymmetric public-key mailboxes, enabling senders to write encrypted messages directly to recipient backups without waiting for the recipient's devices to be online. - `31:37` To counter future quantum harvesting attacks against stored archives, Labyrinth 1.1 implemented Hawk, a hybrid cryptosystem combining classical Diffie-Hellman authentication with ML-KEM post-quantum confidentiality.

## Key takeaways - `00:00` Retrofitting E2EE onto Messenger required matching user expectations of instantaneous server-side message history across new browsers and devices without Meta retaining access to keys. - `05:00` Unlike WhatsApp's model of local transfers and third-party cloud backups, Messenger users expect continuous access across secondary devices left unused for long periods. - `07:17` Labyrinth decouples raw encrypted ciphertext stored on Meta servers from decryption keys, which reside strictly on client endpoints during daily operation. - `10:00` PIN recovery relies on the OPAQUE password-authenticated key exchange protocol, ensuring neither Meta nor the HSM ever learns the raw PIN or the derived backup key. - `10:00` Meta physically destroyed the smartcards required to flash firmware onto its rate-limiting HSMs, preventing internal engineers or attackers from bypassing PIN guess limits. - `15:00` The One-Time Code recovery mechanism authenticates a public-key secure channel across active sessions rather than acting as a direct authorization secret, backed by local client rate limiting. - `20:00` Messenger avoids O(N^2) connection complexity across up to 100 registered endpoints by addressing messages to single user-level mailbox keys rather than fanning out to every individual device key. - `23:25` Post-revocation confidentiality is maintained through epoch keys generated from fresh entropy and shared only among remaining trusted devices, preventing revoked devices from reading subsequent messages. - `26:12` Labyrinth 1.0 required recipient devices to come online to decrypt and re-upload messages to the backup archive, causing synchronization gaps on secondary devices. - `26:12` Labyrinth 1.1 resolves synchronization delays by allowing senders to encrypt directly to a recipient's mailbox public key on the server. - `30:00` To prevent server manipulation of mailbox public keys, Meta implemented Key Transparency using an append-only verifiable ledger signed periodically by external auditors like Cloudflare. - `31:37` Because message archives are preserved long term, Labyrinth adopted the Hawk hybrid cryptosystem with ML-KEM to protect stored ciphertext against future 'store now, decrypt later' quantum decryption.

## Detailed walkthrough ### `00:00` Introduction and the Paradox of Retrofitting E2EE

Host Pascal opens the episode by returning from a month-long recharge leave, joking about the corporate luxury problem of sitting on a mountain of accrued paid time off with only a few months left in the year to take it. Shifting to technical topics, he points out a fundamental truth about cryptography: encrypting data so thoroughly that no one—including the author—can ever read it again is simple. The true engineering nightmare begins when you try to retroactively introduce end-to-end encryption into an existing platform. While WhatsApp adopted end-to-end encryption early enough to set user expectations around local device backups, Messenger operated for over a decade under a completely different paradigm. Users spent years signing into Messenger from arbitrary computers, or pulling an old phone out of a drawer after 18 months, expecting their entire message history to be waiting for them. Retrofitting encryption meant honoring those unyielding habits while explicitly depriving Meta of the ability to unlock the data.

Before introducing the guest, Pascal highlights an article on the Meta Engineering blog about Network Time Security (NTS). Meta’s public time service has implemented authenticated time packets, allowing connected devices to verify that timestamp data genuinely originates from Meta without tampering. As Pascal notes, the Network Time Protocol (NTP) has operated in an unauthenticated state since 1985, despite being the bedrock against which certificate expirations, cryptographic handshakes, and token lifetimes are validated. The deployed NTS implementation keeps no per-client state, and its protocol client and server codebases are open source. Pascal then introduces Emma Connor, a research scientist at Meta focusing on encrypted infrastructure, who joined the company in August 2021 immediately after defending her PhD. Emma previously spent several years working part-time on Cisco’s security initiatives group, concentrating on penetration testing and exploit development.

- Emma’s team at Meta is responsible for encrypted messaging infrastructure (principally end-to-end encryption for Facebook Messenger). - The team also builds federated analytics, combining differential privacy techniques and trusted execution environments (TEEs) to collect aggregated product telemetry without exposing individual user data to Meta. - Pascal notes that the delay between Meta’s announcement of end-to-end encryption for Messenger and its full production rollout gave rise to public skepticism and conspiracy theories, when the reality was simply that solving cross-device encrypted message history without breaking legacy behavior is extraordinarily complex.

`05:00` User Mental Models: WhatsApp vs. Messenger

The core user experience problem is simple to state but technically brutal: if a user drops their phone into a lake and buys a replacement, they expect all their past conversations to appear as soon as they log back in. In a classic client-server model, the data center simply serves the stored chat archive upon authentication. In an end-to-end encrypted architecture, Meta cannot hold the keys to decrypt or read those chats, creating an apparent paradox when provisioning a fresh device that possesses no existing cryptographic state. Emma explains that while migrating users to an encrypted architecture inevitably forces some new mental models, the primary objective was to minimize the delta between the old and new user experience.

Emma and Pascal contrast Messenger's engineering challenge with WhatsApp’s architecture. Because WhatsApp introduced encryption earlier in its lifespan, its community was accustomed to device-to-device transfers and local operating system backups (such as iCloud or Google Drive). Migrating between devices on WhatsApp typically involves an explicit, high-friction, and time-consuming sync process that works best when both the old and new hardware are present. Messenger users, by contrast, expect to walk into an internet cafe, open a clean web browser, authenticate, and have their entire conversation timeline stream in virtually instantaneously. As Emma observes:

> “Part of the problem we’re trying to solve is making that new mental model as small as possible compared to the existing mental model.”

`07:17` Introducing Labyrinth and the Modular Key Dilemma

To bridge this gap without compromising the cryptographic boundary, Meta engineered an encrypted storage system called **Labyrinth**. Emma explains that the name was originally coined by John Milliken. The moniker reflected an early architectural concept where the storage topology formed a hidden structure that made it hard for an adversary (or Meta) to even locate where specific message records lived. While that particular structural obscurity is a historical factoid that no longer applies to the production deployment, the name Labyrinth stuck.

When Pascal brings up the central question of where the cryptographic keys actually reside, Emma clarifies that during normal day-to-day operation, active decryption keys reside strictly on the user’s local endpoints—they are never stored with Meta. To survive hardware loss or phone upgrades, Labyrinth relies on backup keys stored across segregated locations designed around modular recovery flows:

> “During daily use, the keys live only on your device, but we have different ways to store backup keys in different locations so that if you lose your device or something happens, you can get back in.”

```mermaid flowchart TD accTitle: Labyrinth Key Storage Architecture Overview accDescr: High-level separation between the user's local device, the untrusted Meta storage layer, and the external secure recovery mechanisms.

subgraph Client["User Device (Active Endpoint)"] LocalKeys["Active Session & Decryption Keys"] LocalCache["Decrypted Local Message Cache"] LocalKeys --> LocalCache end

subgraph MetaInfra["Meta Infrastructure (Untrusted)"] BlobStore["Labyrinth Encrypted Message Blobs\n(Meta cannot read content)"] end

subgraph RecoveryTier["Modular Recovery Options (External / Isolated)"] BackupKeys["Encrypted Backup Master Keys\n(Hardware Security Modules / Third-party)"] end

Client -- "Reads/Writes Ciphertext" --> BlobStore Client -. "Bootstraps via Passcode/PIN".-> RecoveryTier RecoveryTier -. "Yields Master Key (On Auth Only)".-> Client ```

- Messenger’s architecture decouples raw encrypted message blobs (stored by Meta in ciphertext form) from the keys required to decode them. - Because everyday operational keys never hit Meta’s server fleet, system recovery must bootstrap from an external mechanism or a specialized rate-limited credential like a PIN. - Modularity was an explicit architectural requirement from day one, allowing the team to introduce new recovery backends (such as cloud drive escrows, hardware keys, or specialized hardware security modules) without rebuilding the core message sync engine.

`10:00` Securing the Six-Digit PIN with OPAQUE and Locked-Down HSMs

When designing authentication options for end-to-end encrypted message backups at scale, the primary method deployed to users is a simple six-digit numerical PIN. While a six-digit PIN provides a very small key space that would typically be trivial for an attacker to brute-force, Meta mitigates this attack vector by strictly rate-limiting guess attempts inside tamper-resistant hardware. The long encryption keys guarding the user's backups are stored in a form that only hardware security modules (HSMs) residing in Meta’s data centers can decrypt, and the HSMs are cryptographically blocked from doing so without the user's PIN.

Rather than sending the raw PIN over the wire, the system relies on an asymmetric password-authenticated key exchange protocol called OPAQUE, which Meta has implemented and open-sourced as a Rust crate. The device derives a cryptographic token from the PIN locally—ensuring the raw code never leaves the client—and exchanges a few challenge-response rounds with the HSM. Through this exchange, the user device reconstructs the master backup decryption key. Emma Connor summarizes the mutual privacy guarantees established between the client and the data center: “And as a result of this exchange the device is able to reconstruct the key to the backup whereas the HSM never sees your PIN and it never sees the key to your backup either.” An eavesdropper monitoring the network packets cannot deduce the PIN without breaking an underlying cryptographically hard problem.

```mermaid flowchart TD accTitle: OPAQUE Protocol with HSM Rate Limiting accDescr: Flow diagram showing how a user device derives a token from a PIN and exchanges rounds with an HSM to unlock backups without revealing the PIN or key. subgraph Client["User Device"] PIN["User Enters 6-Digit PIN"] Derive["Derive Cryptographic Token (PIN never leaves device)"] Reconstruct["Reconstruct Master Backup Key"] end subgraph Server["Data Center Hardware Security Module (HSM)"] HSMCode["Tamper-Resistant HSM Firmware (Rate-Limiting Enforced)"] DecKey["Store Encrypted Backup Keys"] end PIN --> Derive Derive <-->|"OPAQUE Multi-Round Exchange"| HSMCode HSMCode -.->|"Decrypts intermediate secret only with valid PIN exchange"| DecKey Derive --> Reconstruct ```

To explain how the rate limiting cannot simply be overridden by Meta engineers or an attacker in the data center, Emma points to the analogy of an iPhone's Secure Enclave, which locks users out after successive wrong passcode attempts and resists extraction even against well-funded laboratory chip disassembly. Meta uses HSMs with similar physical tamper resistance. During initial infrastructure setup, engineers flash the rate-limiting firmware onto the units and then physically burn their bridges: “So we deploy the code to the HSM when we are provisioning this system, but we actually destroy the cards that allow us to load code onto the HSMs after the first deployment.” By destroying the deployment smartcards, Meta ensures that nobody—not even internal engineering teams—can alter the rate-limiting rules or extract the underlying keys.

`15:00` One-Time Code Recovery and Cross-Device PAKE Authentication

For situations where a user forgets their six-digit backup PIN, Labyrinth provides alternative recovery paths. The most common alternative is the One-Time Code (OTC) flow, which can be initiated if the user still has an active, authenticated session on another device. In this flow, the user signs in on a secondary device, requests a code, and a six-digit code appears on their existing logged-in device. Typing that code into the new device restores access to the encrypted backup without ever requiring the original PIN.

Despite appearing to the user as a simple PIN, the one-time code functions under the hood as an authentication mechanism for a cryptographic secure channel rather than an access key itself. Emma explains: “The one-time code is actually not, under the hood, it actually doesn't work like a pin in the sense that it's granting access to anything. It's actually just used to authenticate the secure channel.” The two endpoints perform a key exchange—akin to standard Diffie-Hellman, RSA public-key schemes, or post-quantum algorithms like ML-KEM—to establish an encrypted connection. To prevent man-in-the-middle attacks where an adversary completes separate key exchanges with each side, both devices generate a six-digit code derived from hash functions calculated over the exchange parameters and public keys. Because the initiating device already computes the expected hash value independently, entering the code proves that a human in the physical world is viewing both screens simultaneously and verifying the connection. Because servers might be untrusted or compromised, the client devices must enforce their own rate limiting locally to ensure an attacker cannot brute-force the one-time code.

This architecture must also scale to Messenger's permissive multi-device environment. While WhatsApp historically maintained a strict cap of around four linked devices, Messenger supports a vastly wider footprint:

- Emma notes that the Messenger device limit is “something like 200” concurrent endpoints. - The high threshold is intentional so users can leave a secondary tablet or an old phone sitting inactive in a drawer for a year or two, power it back on, and instantly have their entire message history intact without being abruptly signed out. - Pascal confirms this user experience firsthand, recalling turning on a spare phone retrieved from a drawer after an extended absence and immediately receiving Messenger notifications, whereas other modern messaging apps would have evicted the session. src/utils/text_sanitizer.py

# Fallback: remove remaining backslashes that don't look like standard formatting # Keep \\, \n, \t, etc. text = re.sub(r'\\(?![\\nrt*_{}\[\]()#+\-.!|`])', '', text)

return text import re

def sanitize_markdown(text: str) -> str: """ Sanitize text to fix over-escaped characters often produced by LLMs, such as escaped punctuation inside words (e.g. Meta\'s, WhatsApp\'s, aren\'t). """ if not text: return text

# Fix escaped apostrophes/single quotes inside words: Meta\'s -> Meta's, isn\'t -> isn't text = re.sub(r'(\w)\\\'(\w)', r"\1'\2", text) # Fix escaped single quotes generally if not needed: \' -> ' text = re.sub(r"\\'", "'", text)

# Fix escaped double quotes: \" -> " text = re.sub(r'\\"', '"', text)

# Fix escaped dashes/hyphens: \- -> - text = re.sub(r'\\-', '-', text)

# Fix escaped colons: \: -> : text = re.sub(r'\\:', ':', text)

# Fix escaped underscores when they shouldn't be (e.g. in words, or keep standard) # Be careful not to break valid markdown escapes like \* or \_ if intentional, # but LLMs often escape parentheses, brackets, etc. unnecessarily. # E.g. \( -> (, \) -> ), \[ -> [, \] -> ] # Keep markdown link syntax and brackets intact, but remove escaping on plain parens # If LLM wrote \(something\) instead of (something) text = re.sub(r'\\\(', '(', text) text = re.sub(r'\\\)', ')', text)

# Fix escaped periods at line starts or inside text: \. -> . text = re.sub(r'\\\.', '.', text)

# Fix escaped ampersands: \& -> & text = re.sub(r'\\&', '&', text) src/parsers/youtube.py import re from typing import Optional, Tuple from youtube_transcript_api import YouTubeTranscriptApi from youtube_transcript_api._errors import NoTranscriptFound, TranscriptsDisabled import yt_dlp from src.parsers.base import BaseParser from src.utils.logger import logger

class YouTubeParser(BaseParser): @classmethod def can_handle(cls, url: str) -> bool: youtube_regex = ( r'(https?://)?(www\.)?' '(youtube|youtu|youtube-nocookie)\.(com|be)/' '(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})' ) return bool(re.match(youtube_regex, url))

def _extract_video_id(self, url: str) -> Optional[str]: patterns = [ r'(?:v=|\/)([0-9A-Za-z_-]{11}).*', r'(?:embed\/|v\/|watch\?v=|youtu\.be\/|\/v\/|^)([0-9A-Za-z_-]{11})' ] for pattern in patterns: match = re.search(pattern, url) if match: return match.group(1) return None

def _extract_metadata_via_ytdlp(self, url: str) -> Tuple[Optional[str], Optional[str], Optional[str], Optional[float], Optional[str], Optional[str]]: ydl_opts = { 'skip_download': True, 'quiet': True, 'no_warnings': True, } try: with yt_dlp.YoutubeDL(ydl_opts) as ydl: info = ydl.extract_info(url, download=False) title = info.get('title') channel = info.get('uploader') or info.get('channel') upload_date = info.get('upload_date') # YYYYMMDD duration = info.get('duration') # seconds (float/int) description = info.get('description') webpage_url = info.get('webpage_url') or url

# Format upload_date if available if upload_date and len(upload_date) == 8: upload_date = f"{upload_date[:4]}-{upload_date[4:6]}-{upload_date[6:]}"

return title, channel, upload_date, float(duration) if duration else None, webpage_url, description except Exception as e: logger.warning(f"Could not extract metadata via yt-dlp: {e}") return None, None, None, None, None, None

def parse(self, url: str) -> Tuple[str, dict]: video_id = self._extract_video_id(url) if not video_id: raise ValueError(f"Could not extract YouTube video ID from URL: {url}")

logger.info(f"Extracting metadata for YouTube video: {video_id}") title, channel, upload_date, duration, webpage_url, description = self._extract_metadata_via_ytdlp(url)

logger.info(f"Fetching transcript for YouTube video: {video_id}") try: # Try fetching transcript directly transcript_list = YouTubeTranscriptApi.list_transcripts(video_id)

# Prefer manually created transcripts, fall back to generated try: transcript = transcript_list.find_manually_created_transcript(['en', 'ru', 'es', 'de', 'fr']) except NoTranscriptFound: transcript = transcript_list.find_generated_transcript(['en', 'ru', 'es', 'de', 'fr'])

data = transcript.fetch() except Exception as e: logger.warning(f"youtube-transcript-api failed ({e}), attempting fallback via yt-dlp...") # Fallback to yt-dlp subtitles ydl_opts = { 'skip_download': True, 'writesubtitles': True, 'writeautomaticsub': True, 'subtitleslangs': ['en', 'ru', 'es', 'de', 'fr'], 'quiet': True, } with yt_dlp.YoutubeDL(ydl_opts) as ydl: info = ydl.extract_info(url, download=False) subtitles = info.get('subtitles') or info.get('automatic_captions') if not subtitles: raise RuntimeError("No transcript or subtitles found for this video.")

# Pick the first available language from our preferred list or whatever is there lang = next((l for l in ['en', 'ru', 'es', 'de', 'fr'] if l in subtitles), list(subtitles.keys())[0]) sub_formats = subtitles[lang] json3_sub = next((s['url'] for s in sub_formats if s.get('ext') == 'json3'), None)

if json3_sub: import requests resp = requests.get(json3_sub) resp.raise_for_status() sub_data = resp.json() data = [] for event in sub_data.get('events', []): if 'segs' in event: text = "".join(s.get('utf8', '') for s in event['segs']).strip() if text and text!= '\n': start = event.get('tStartMs', 0) / 1000.0 duration_s = event.get('dDurationMs', 0) / 1000.0 data.append({'start': start, 'duration': duration_s, 'text': text}) else: raise RuntimeError("Could not find suitable transcript format in subtitles.")

# Format transcript with timestamps formatted_lines = [] for entry in data: start = entry.get('start', 0) text = entry.get('text', '').replace('\n', ' ').strip() if not text: continue

# Convert seconds to [HH:MM:SS] hours = int(start // 3600) minutes = int((start % 3600) // 60) seconds = int(start % 60) timestamp = f"[{hours:02d}:{minutes:02d}:{seconds:02d}]"

formatted_lines.append(f"{timestamp} {text}")

transcript_text = "\n".join(formatted_lines)

metadata = { "title": title or f"YouTube Video ({video_id})", "channel": channel or "Unknown Channel", "published_date": upload_date or "Unknown Date", "duration": duration, "source_url": webpage_url or url, "platform": "YouTube", "video_id": video_id, "description": description or "" }

return transcript_text, metadata src/main.py import argparse import sys import os from dotenv import load_dotenv

from src.parsers.youtube import YouTubeParser from src.parsers.rss_podcast import RSSPodcastParser from src.parsers.audio_transcriber import AudioTranscriberParser from src.summarizer.pipeline import SummarizerPipeline from src.utils.logger import logger

def parse_args(): parser = argparse.ArgumentParser( description="Extract transcript and generate a detailed walkthrough of YouTube videos or Podcasts." ) parser.add_argument( "url", type=str, help="URL of the YouTube video, RSS feed, audio file (mp3), or direct podcast link." ) parser.add_argument( "-o", "--output", type=str, default=None, help="Path to the output Markdown file (default: auto-generated based on title in outputs/)." ) parser.add_argument( "--whisper-model", type=str, default=None, help="Whisper model size for audio transcription if required (e.g., tiny, base, small, medium, large)." ) parser.add_argument( "--gemini-model", type=str, default="gemini-2.5-pro", help="Google Gemini model to use for summarization (default: gemini-2.5-pro)." ) return parser.parse_args()

def main(): load_dotenv() args = parse_args()

url = args.url logger.info(f"Processing URL: {url}")

# 1. Select appropriate parser parser = None if YouTubeParser.can_handle(url): logger.info("Selected parser: YouTubeParser") parser = YouTubeParser() elif RSSPodcastParser.can_handle(url): logger.info("Selected parser: RSSPodcastParser") parser = RSSPodcastParser() elif AudioTranscriberParser.can_handle(url): logger.info("Selected parser: AudioTranscriberParser") parser = AudioTranscriberParser(model_size=args.whisper_model or "base") else: logger.error("No suitable parser found for this URL.") sys.exit(1)

try: transcript, metadata = parser.parse(url) except Exception as e: logger.error(f"Failed to extract transcript: {e}", exc_info=True) sys.exit(1)

if not transcript or not transcript.strip(): logger.error("Extracted transcript is empty. Aborting.") sys.exit(1)

logger.info(f"Extracted transcript ({len(transcript)} chars). Starting summarization pipeline...")

# 2. Run summarizer pipeline try: pipeline = SummarizerPipeline(model_name=args.gemini_model) walkthrough_md = pipeline.run(transcript, metadata) except Exception as e: logger.error(f"Summarization pipeline failed: {e}", exc_info=True) sys.exit(1)

# 3. Save output output_path = args.output if not output_path: os.makedirs("outputs", exist_ok=True) # Clean title for filename clean_title = "".join(c for c in metadata.get('title', 'summary') if c.isalnum() or c in (' ', '-', '_')).rstrip() clean_title = clean_title.replace(' ', '_').lower()[:60] output_path = os.path.join("outputs", f"{clean_title}.md")

os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_ok=True) with open(output_path, "w", encoding="utf-8") as f: f.write(walkthrough_md)

logger.info(f"Success! Detailed walkthrough saved to: {output_path} ")

if __name__ == "__main__": main() src/summarizer/pipeline.py

for entry in entries: if entry.startswith('['): time_part = entry.split(']')[0] + ']' name_part = entry.split(']')[-1].strip() speaker_name_map[time_part] = name_part elif ':' in entry: parts = entry.split(':', 1) speaker_name_map[parts[0].strip()] = parts[1].strip() logger.info(f"Extracted speaker profiles: {len(speaker_name_map)} speakers found.") except Exception as e: logger.warning(f"Speaker identification step failed (non-critical): {e}")

# Step 2: Split into chunks chunks = chunk_transcript_by_time(transcript, chunk_duration_minutes=self.chunk_duration_minutes) total_parts = len(chunks) logger.info(f"Transcript divided into {total_parts} part(s) of ~{self.chunk_duration_minutes} min each.")

# Step 3: Process each chunk sub_walkthroughs = [] for i, chunk_text in enumerate(chunks, 1): logger.info(f"Generating walkthrough for part {i} of {total_parts}...")

prompt = self._build_chunk_prompt( part_num=i, total_parts=total_parts, chunk_transcript=chunk_text, metadata=metadata, speaker_map=speaker_name_map )

resp = self._call_model_with_backoff(prompt) cleaned_text = sanitize_markdown(resp.text.strip()) sub_walkthroughs.append(cleaned_text)

# Step 4: Generate Executive Summary & Synthesis logger.info("Generating executive summary and synthesis...") combined_text = "\n\n".join(sub_walkthroughs) synthesis_prompt = self._build_synthesis_prompt(combined_text, metadata) synth_resp = self._call_model_with_backoff(synthesis_prompt) exec_summary = sanitize_markdown(synth_resp.text.strip())

# Step 5: Format Final Document final_doc = self._assemble_final_document(metadata, exec_summary, sub_walkthroughs) return final_doc

def _call_model_with_backoff(self, prompt: str, max_retries: int = 5) -> any: delay = 5.0 for attempt in range(1, max_retries + 1): try: response = self.client.models.generate_content( model=self.model_name, contents=prompt ) return response except Exception as e: logger.warning(f"Gemini API call failed (attempt {attempt}/{max_retries}): {e}") if attempt == max_retries: raise time.sleep(delay) delay *= 2

def _build_speaker_id_prompt(self, transcript_sample: str, metadata: dict) -> str: return f"""You are analyzing a podcast or video transcript to identify the names and roles of the speakers.

METADATA: Title: {metadata.get('title', 'Unknown')} Channel/Host: {metadata.get('channel', 'Unknown')} Description: {metadata.get('description', '')[:1000]}

TRANSCRIPT EXCERPT: {transcript_sample}

TASK: Identify who is speaking. Look at introductions, greetings, self-references, host opening lines, and names mentioned. Return a bullet list formatted exactly like: - [Speaker Name]: [Brief Role / Description]

If you cannot identify names with certainty, list the likely roles (e.g. Host, Guest, Co-host). Keep it brief."""

def _build_chunk_prompt(self, part_num: int, total_parts: int, chunk_transcript: str, metadata: dict, speaker_map: dict) -> str: # Format page background desc = metadata.get('description', '').strip() dur_str = "" if metadata.get('duration'): dur_m = int(metadata['duration'] // 60) dur_s = int(metadata['duration'] % 60) dur_str = f"{dur_m:02d}:{dur_s:02d}"

speakers_info = "" if speaker_map: speakers_info = "\nKnown speakers in this episode:\n" + "\n".join(f"- {k}: {v}" for k, v in speaker_map.items())

background = f"""- Title: {metadata.get('title', 'Unknown')} - Platform: {metadata.get('platform', 'Unknown')} - Channel / author: {metadata.get('channel', 'Unknown')} - Published: {metadata.get('published_date', 'Unknown')} - Duration: {dur_str or 'Unknown'} - Source URL: {metadata.get('source_url', '')} - Show notes / description: {desc[:3000]} {speakers_info}"""

prompt = f"""## Page background (title, channel, and description — use it to fix transcription errors) {background}

Below is part {part_num} of {total_parts} of a transcript. Each line is prefixed with its [HH:MM:SS] timestamp. It is auto-generated (speech-to-text), so expect recognition errors.

Write a detailed, faithful walkthrough of THIS part as Markdown: - The transcript may mis-hear words. Using the page background above and common knowledge, silently correct obvious transcription errors — especially names, companies, products, places, and technical terms — so they are correct and consistent. Do NOT change the actual meaning or invent content; when unsure, keep the original. - Break it into one or more subsections, each starting with a heading of the form `### [HH:MM:SS] <short descriptive heading>` using the timestamp where that topic begins. - Under each heading, write 1-3 paragraphs of flowing prose that capture what is said in depth: the specific details, numbers, names, and examples, AND the mood and the way it is discussed (stories, jokes, debates, emotional beats). - After the prose, optionally add `- ` bullets for extra concrete data points, lists, or facts that deserve to stand alone. - When a visual architecture or flow is important, use a fenced Mermaid diagram beginning with ` ```mermaid ` and a simple `flowchart LR` or `flowchart TD`. Keep node IDs short, put human-readable labels in quoted brackets such as `A["Meeting transcript"]`, and keep each edge on its own line. Use an `accTitle:` line for accessibility. If you add an accessibility description, the only valid directive is `accDescr:` (never `accDescription:`). If an edge label contains parentheses or brackets, wrap that label in double quotes, for example `A -->|"step (one)"| B`. Keep the layout compact enough to fit in one viewport: group related stages into labeled subgraphs and avoid a single uninterrupted row or column longer than four nodes. Never draw boxes, arrows, or tables with ASCII characters in a plain code fence. For genuinely tabular comparisons, use a standard Markdown table instead. If neither format can express the source cleanly, explain it in prose. - Quote the speakers directly. When a speaker states a view, judgement, prediction, or lesson, quote the WHOLE sentence verbatim inside “quotation marks” and say who said it, rather than clipping a 2-4 word fragment of it. In every subsection where someone argues a point, include at least one such full-sentence quote, choosing the line where they put it most sharply. Only the speakers' actual words go inside quotation marks; never put your own paraphrase or summary inside them. - IMPORTANT: Put a [HH:MM:SS] timestamp ONLY on the section headings. Do NOT add timestamps inside the prose sentences or bullets — the prose must read cleanly with no inline timestamps. Start a new `###` subsection when a clearly distinct new topic begins (roughly every 1-3 minutes), rather than timestamping sentences. - Be thorough. Do not skip substantive content. Do NOT write an overall title or any preamble/conclusion about the whole video — only cover this part. - LANGUAGE: Write all text fields in the SAME language as the transcript/source.

TRANSCRIPT PART: {chunk_transcript}""" return prompt

def _build_synthesis_prompt(self, combined_subsections: str, metadata: dict) -> str: return f"""You are given the complete set of detailed section walkthroughs for the episode "{metadata.get('title')}".

Write an Executive Overview and Synthesis to precede the walkthrough: 1. Executive Summary: 2-3 substantive paragraphs outlining the core thesis, the main narrative arc, and the most important conclusions of the episode. 2. Key Takeaways & Milestones: 4-7 bullet points capturing the most critical lessons, technical breakthroughs, metrics, or takeaways discussed.

Respond in the SAME language as the content. Do not include a document title header (like # Title), as that will be added automatically.

DETAILED SECTIONS CONTENT: {combined_subsections[:30000]}"""

def _assemble_final_document(self, metadata: dict, exec_summary: str, subsections: List[str]) -> str: dur_str = "" if metadata.get('duration'): dur_m = int(metadata['duration'] // 60) dur_s = int(metadata['duration'] % 60) dur_str = f"{dur_m:02d}:{dur_s:02d}"

header_lines = [ f"# {metadata.get('title', 'Detailed Episode Walkthrough')}\n", f"**Channel / Host:** {metadata.get('channel', 'Unknown')} ", f"**Platform:** {metadata.get('platform', 'Unknown')} ", f"**Published Date:** {metadata.get('published_date', 'Unknown')} ", f"**Duration:** {dur_str or 'Unknown'} ", f"**Source URL:** [{metadata.get('source_url', 'Link')}]({metadata.get('source_url', '#')})\n", "---\n", "## Executive Overview & Synthesis\n", exec_summary, "\n---\n", "## Detailed Walkthrough\n" ]

walkthrough_content = "\n\n".join(subsections) return "\n".join(header_lines) + "\n" + walkthrough_content + "\n" import os import time from typing import List, Tuple, Optional from google import genai from src.utils.logger import logger from src.utils.time_chunker import chunk_transcript_by_time from src.utils.text_sanitizer import sanitize_markdown

class SummarizerPipeline: def __init__(self, model_name: str = "gemini-2.5-pro", chunk_duration_minutes: int = 10): self.model_name = model_name self.chunk_duration_minutes = chunk_duration_minutes

api_key = os.getenv("GEMINI_API_KEY") if not api_key: raise ValueError("GEMINI_API_KEY environment variable is missing.")

self.client = genai.Client(api_key=api_key)

def run(self, transcript: str, metadata: dict) -> str: # Step 1: Detect speakers from metadata and initial transcript lines logger.info("Detecting speakers and background context...") speaker_name_map = {} try: head_transcript = "\n".join(transcript.splitlines()[:60]) speaker_prompt = self._build_speaker_id_prompt(head_transcript, metadata) resp = self._call_model_with_backoff(speaker_prompt) if resp and resp.text: entries = [line.strip('- *') for line in resp.text.splitlines() if line.strip().startswith(('-', '*'))] src/parsers/audio_transcriber.py import os import tempfile import urllib.request from typing import Tuple from src.parsers.base import BaseParser from src.utils.logger import logger

class AudioTranscriberParser(BaseParser): def __init__(self, model_size: str = "base"): self.model_size = model_size

@classmethod def can_handle(cls, url: str) -> bool: # Handles direct mp3, m4a, wav, ogg, or local files audio_extensions = ('.mp3', '.m4a', '.wav', '.ogg', '.aac', '.flac') return any(url.lower().split('?')[0].endswith(ext) for ext in audio_extensions) or os.path.isfile(url)

def parse(self, url: str) -> Tuple[str, dict]: temp_audio_path = None if os.path.isfile(url): audio_path = url filename = os.path.basename(url) else: logger.info(f"Downloading audio file from: {url}") with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f: temp_audio_path = f.name

# Simple download with urllib req = urllib.request.Request( url, headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'} ) with urllib.request.urlopen(req) as response, open(temp_audio_path, 'wb') as out_file: out_file.write(response.read()) audio_path = temp_audio_path filename = os.path.basename(url.split('?')[0])

try: logger.info(f"Transcribing audio with faster-whisper (model={self.model_size})...") from faster_whisper import WhisperModel

model = WhisperModel(self.model_size, device="auto", compute_type="auto") segments, info = model.transcribe(audio_path, beam_size=5)

formatted_lines = [] for segment in segments: start = segment.start text = segment.text.strip() if not text: continue

hours = int(start // 3600) minutes = int((start % 3600) // 60) seconds = int(start % 60) timestamp = f"[{hours:02d}:{minutes:02d}:{seconds:02d}]" formatted_lines.append(f"{timestamp} {text}")

transcript_text = "\n".join(formatted_lines) metadata = { "title": filename, "channel": "Unknown Channel", "published_date": "Unknown Date", "duration": info.duration if hasattr(info, 'duration') else None, "source_url": url, "platform": "Audio File", "language": info.language if hasattr(info, 'language') else "unknown" } return transcript_text, metadata

finally: if temp_audio_path and os.path.exists(temp_audio_path): os.remove(temp_audio_path) tests/test_audio_transcriber.py

def test_audio_transcriber_can_handle_invalid(): assert not AudioTranscriberParser.can_handle("https://example.com/article.html") assert not AudioTranscriberParser.can_handle("https://www.youtube.com/watch?v=123") import pytest from src.parsers.audio_transcriber import AudioTranscriberParser

def test_audio_transcriber_can_handle(): assert AudioTranscriberParser.can_handle("https://example.com/audio.mp3") assert AudioTranscriberParser.can_handle("https://example.com/audio.mp3?dest-id=123") assert AudioTranscriberParser.can_handle("https://example.com/podcast.m4a") assert AudioTranscriberParser.can_handle("https://example.com/podcast.wav") assert AudioTranscriberParser.can_handle("https://example.com/podcast.ogg")

# Test local file handling import tempfile with tempfile.NamedTemporaryFile(suffix=".mp3") as tmp: assert AudioTranscriberParser.can_handle(tmp.name) tests/test_youtube.py import pytest from src.parsers.youtube import YouTubeParser

def test_youtube_can_handle(): urls = [ "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "https://youtu.be/dQw4w9WgXcQ", "https://youtube.com/embed/dQw4w9WgXcQ", "http://www.youtube.com/watch?v=dQw4w9WgXcQ&t=10s" ] for url in urls: assert YouTubeParser.can_handle(url) is True

def test_youtube_can_handle_invalid(): urls = [ "https://vimeo.com/123456", "https://example.com", "https://podcasts.apple.com/us/podcast/id1234" ] for url in urls: assert YouTubeParser.can_handle(url) is False

def test_extract_video_id(): parser = YouTubeParser() assert parser._extract_video_id("https://www.youtube.com/watch?v=dQw4w9WgXcQ") == "dQw4w9WgXcQ" assert parser._extract_video_id("https://youtu.be/dQw4w9WgXcQ") == "dQw4w9WgXcQ" assert parser._extract_video_id("https://www.youtube.com/watch?v=dQw4w9WgXcQ&feature=share") == "dQw4w9WgXcQ"

def test_extract_video_id_invalid(): parser = YouTubeParser() assert parser._extract_video_id("https://example.com") is None src/utils/time_chunker.py import re from typing import List

def parse_timestamp_seconds(timestamp_str: str) -> float: """Parses [HH:MM:SS] or [MM:SS] into total seconds.""" clean = timestamp_str.strip('[]') parts = list(map(int, clean.split(':'))) if len(parts) == 3: return parts[0] * 3600 + parts[1] * 60 + parts[2] elif len(parts) == 2: return parts[0] * 60 + parts[1] return 0.0

def chunk_transcript_by_time(transcript: str, chunk_duration_minutes: int = 10) -> List[str]: """ Chunks a timestamped transcript by time into segments of approximately `chunk_duration_minutes` (e.g. 10 minutes). Ensures that chunks split cleanly at timestamps. """ lines = transcript.splitlines() if not lines: return []

target_chunk_seconds = chunk_duration_minutes * 60 chunks = [] current_chunk = []

current_chunk_start_sec = 0.0

# Regex for [HH:MM:SS] or [MM:SS] at the start of a line ts_pattern = re.compile(r'^\[(\d{1,2}:\d{2}(?::\d{2})?)\]')

for line in lines: match = ts_pattern.match(line) if match: current_sec = parse_timestamp_seconds(match.group(1))

# If this is the very first timestamp, set the baseline start if not current_chunk: current_chunk_start_sec = current_sec

# Check if duration has exceeded the target chunk duration if (current_sec - current_chunk_start_sec) >= target_chunk_seconds and len(current_chunk) > 10: chunks.append("\n".join(current_chunk)) current_chunk = [] current_chunk_start_sec = current_sec

current_chunk.append(line)

if current_chunk: chunks.append("\n".join(current_chunk))

return chunks tests/test_text_sanitizer.py

def test_sanitize_markdown_quotes(): raw = 'He said, \\"Hello world!\\"' expected = 'He said, "Hello world!"' assert sanitize_markdown(raw) == expected

def test_sanitize_markdown_escaped_parens(): raw = 'This is an example \\(with parens\\).' expected = 'This is an example (with parens).' assert sanitize_markdown(raw) == expected

def test_sanitize_markdown_escaped_dashes_and_dots(): raw = 'item \\- one\\. item \\- two\\.' expected = 'item - one. item - two.' assert sanitize_markdown(raw) == expected

def test_sanitize_empty_and_none(): assert sanitize_markdown("") == "" assert sanitize_markdown(None) is None from src.utils.text_sanitizer import sanitize_markdown

def test_sanitize_markdown_apostrophe(): raw = "Meta\\'s team works on WhatsApp\\'s encryption and didn\\'t give up." expected = "Meta's team works on WhatsApp's encryption and didn't give up." assert sanitize_markdown(raw) == expec ted src/parsers/rss_podcast.py

if not matching_entry: raise ValueError(f"Could not find matching episode in feed: {feed_url} for url: {url}")

title = matching_entry.get('title', 'Unknown Episode') author = matching_entry.get('author') or feed.feed.get('author') or feed.feed.get('title') published = matching_entry.get('published') or matching_entry.get('updated') description = matching_entry.get('summary') or matching_entry.get('description') or ''

# Strip HTML tags from description if present clean_desc = re.sub(r'<[^>]+>', ' ', description).strip() clean_desc = re.sub(r'\s+', ' ', clean_desc)

metadata = { "title": title, "channel": author or "Unknown Podcast", "published_date": published or "Unknown Date", "source_url": url, "platform": "Podcast / RSS", "description": clean_desc }

# Check for transcript tag (podcast namespace standard: <podcast:transcript url="..." type="..." />) transcript_url = None for tag in matching_entry.get('podcast_transcript', []): transcript_url = tag.get('url') break if not transcript_url and 'podcast_transcripts' in matching_entry: transcript_url = matching_entry['podcast_transcripts'][0].get('url')

if transcript_url: logger.info(f"Found dedicated transcript URL: {transcript_url}") return self._fetch_and_parse_transcript_file(transcript_url, metadata)

# If no transcript URL, find audio enclosure and transcribe audio_url = None for enc in matching_entry.get('enclosures', []): if 'audio' in enc.get('type', '') or enc.get('href', '').split('?')[0].endswith(('.mp3', '.m4a')): audio_url = enc.get('href') break

if not audio_url: raise RuntimeError(f"No transcript or audio enclosure found for episode: {title}")

logger.info(f"No direct transcript found in RSS feed. Transcribing audio file: {audio_url}") transcriber = AudioTranscriberParser() transcript_text, audio_meta = transcriber.parse(audio_url)

metadata["duration"] = audio_meta.get("duration") return transcript_text, metadata

def _fetch_and_parse_transcript_file(self, transcript_url: str, metadata: dict) -> Tuple[str, dict]: resp = requests.get(transcript_url) resp.raise_for_status() content = resp.text

# Check format (JSON, SRT, VTT, or plain text) lines = [] if transcript_url.endswith('.json') or content.strip().startswith('{'): try: import json data = json.loads(content) segments = data.get('segments') or data.get('results', {}).get('segments', []) for seg in segments: start = seg.get('start', 0) text = seg.get('text', '').strip() hours = int(start // 3600) minutes = int((start % 3600) // 60) seconds = int(start % 60) timestamp = f"[{hours:02d}:{minutes:02d}:{seconds:02d}]" lines.append(f"{timestamp} {text}") return "\n".join(lines), metadata except Exception as e: logger.warning(f"Failed parsing JSON transcript: {e}")

# Fallback to lines parsing for SRT/VTT/Text # Simple extraction of timestamps # Matches 00:00:00 or 00:00 for line in content.splitlines(): clean = line.strip() if not clean or clean.isdigit(): continue lines.append(clean)

return "\n".join(lines), metadata import feedparser import requests import re from typing import Tuple, Optional from src.parsers.base import BaseParser from src.parsers.audio_transcriber import AudioTranscriberParser from src.utils.logger import logger

class RSSPodcastParser(BaseParser): @classmethod def can_handle(cls, url: str) -> bool: # Check if URL ends with xml, rss, or feed, or contains podcast domains lower = url.lower() return ( lower.endswith(('.xml', '.rss', '/feed', '/rss')) or 'feeds.' in lower or 'feed.' in lower or 'anchor.fm' in lower or 'podcasts.apple.com' in lower or 'libsyn.com' in lower )

def parse(self, url: str) -> Tuple[str, dict]: logger.info(f"Parsing RSS / Podcast feed or episode: {url}")

# If it's an Apple podcast or specific episode link, we might parse the feed or resolve redirect feed_url = url feed = feedparser.parse(feed_url)

if not feed.entries: raise ValueError(f"Could not parse RSS feed or found 0 entries at {url}")

# Select the latest episode or find the episode matching the URL matching_entry = None for entry in feed.entries: # Check enclosure or link enclosures = [e.href for e in entry.get('enclosures', []) if 'href' in e] if url in enclosures or url == entry.get('link'): matching_entry = entry break

# If no specific episode matched (e.g. user provided whole feed URL), take the latest entry if not matching_entry: matching_entry = feed.entries[0] logger.info(f"No specific episode matched URL. Defaulting to latest episode: '{matching_entry.get('title')}'") src/parsers/base.py

@abstractmethod def parse(self, url: str) -> Tuple[str, dict]: """ Parses the URL and returns: 1. transcript: Text with formatted [HH:MM:SS] timestamps on each line/segment. 2. metadata: Dictionary with title, channel/author, published_date, duration, description, etc. """ pass from abc import ABC, abstractmethod from typing import Tuple, Optional

class BaseParser(ABC): @classmethod @abstractmethod def can_handle(cls, url: str) -> bool: """Returns True if this parser can handle the provided URL.""" pass

`20:00` Scaling End-to-End Encryption and "Rolling Your Own Crypto"

When designing end-to-end encrypted messaging systems, the sheer volume of active user endpoints alters the underlying mathematics significantly. In simple systems where each participant possesses only a few hardware units—such as a thread among two or three people with three or four devices each, totaling 12 to 20 endpoints—encrypting directly to each device endpoint is relatively straightforward. However, Emma Connor points out that some real-world Messenger accounts have up to 100 registered devices. In large group chats, this multiplier can cause the total number of connected endpoints to skyrocket into hundreds or thousands.

Off-the-shelf protocols like standard Signal face severe scaling hurdles under these constraints. Pascal notes that group conversations can escalate quickly, evoking WhatsApp's pairing mechanisms. Emma confirms that while some systems employ sender keys to achieve an order of $O(N)$ keys for $N$ users, the pure Signal protocol can require an order of $O(N^2)$ connections. For 100 endpoints, that translates to approximately 10,000 distinct connections. To bypass this combinatorial explosion, Meta's Labyrinth 1.1 architecture shifts to a single "mailbox-level public key" per user. When two individuals communicate in a thread, the message payload is encrypted strictly between the two user-level public keys rather than fanning out across every single individual device key.

This structural divergence meant existing off-the-shelf options were insufficient, forcing the engineering team to construct their own protocols. While the standard industry mantra warns never to roll your own crypto, Emma clarifies their stance: > “Well, not the primitives, but at least the packaging of it. Right. So that's what we did. And I know that there of course the classic warning of never roll your own crypto but somebody has to And when we do we do it with cryptographic experts We do it with input from external researchers and community feedback and transparency”

Because Meta houses extensive internal security expertise, the Labyrinth team worked alongside specialists across the company and the broader cryptography community: - Internal Meta chat channels populated by end-to-end encryption specialists, veterans of WhatsApp's end-to-end rollout, and engineers who previously worked with the Electronic Frontier Foundation (EFF). - Collaboration with external and industry figures, including past work with Moxie Marlinspike. - Direct work alongside cryptographic experts like Shai Halevi (who authored widely adopted internet standards for encryption algorithms) and Kevin Lewi. - Formal security reviews and consultations conducted with independent researchers from the external privacy and security community.

---

`23:25` Device Revocation Guarantees and Epoch Keys

Having extensive multi-device support introduces the reality that devices will eventually be misplaced, stolen, or compromised. When a user revokes access for an old or missing hardware unit, the backup system must provide strict cryptographic boundaries rather than relying on server-side promises. Emma explains that the security guarantee specifically targets post-revocation confidentiality: > “If you revoke a device, that means the keys that exist on that device should no longer be able to decrypt any messages that are sent after that point.”

The system explicitly does not offer backwards revocation; it does not rewrite history or retroactively re-encrypt historical messages stored in earlier backups. However, the forward guarantee holds under adversarial conditions: even if a revoked device ends up in the possession of an attacker who is also a Meta insider with unrestricted access to Meta data centers, that actor still cannot read any messages transmitted after the revocation cutoff.

```mermaid flowchart TD accTitle: Device Revocation and Epoch Key Derivation Flow accDescr: An active device generates new entropy, encrypts it to valid devices, and isolates the revoked device from deriving the new epoch key.

subgraph ActiveDevices["User's Trusted Devices"] D1["Active Device A"] D2["Active Device B"] end

subgraph RevokedGroup["Untrusted Hardware"] D3["Revoked Device C (Lost/Stolen)"] end

D1 -->|"1. Generates new entropy"| D1 D1 -->|"2. Derives"| K1["Epoch Key N+1"] D1 -->|"3. Encrypts entropy to Device B's public key"| D2 D2 -->|"4. Derives identical"| K1

D1 -.->|"Blocked / Excluded from key share"| D3 D3 -.->|"Cannot derive key (Cannot decrypt post-revocation data)"| K1 ```

To enforce this boundary without re-encrypting the archive, Labyrinth relies on "epoch keys": - **Epoch Definition:** An epoch represents a discrete window of time defining the exact cohort of authorized devices permitted to read messages. - **Entropy Generation:** When an authorized device triggers a revocation, it generates a fresh pool of entropy rather than sharing a finalized key directly. - **End-to-End Key Exchange:** The originating device encrypts this entropy specifically to the individual public-private key pairs of the remaining, trusted devices via an end-to-end encrypted channel. - **Isolation:** Because the revoked device never receives this encrypted entropy, it is mathematically incapable of deriving the subsequent epoch key used for encrypting new incoming traffic.

---

`26:12` Architectural Shift: Labyrinth 1.0 vs. Labyrinth 1.1

The major operational upgrade in Labyrinth 1.1 revolves around an inversion of the backup pipeline. In the legacy Labyrinth 1.0 architecture, users depended entirely on their own active client hardware to maintain server archives:

1. **Sender Encryption:** Alice encrypted a message payload directly addressed to Bob's specific hardware endpoint. 2. **Download & Decryption:** Bob's phone had to connect to the network, receive the payload, and decrypt it. 3. **Re-encryption for Archive:** Bob's phone re-encrypted the decrypted message content into an alternative format explicitly compatible with the Labyrinth encrypted storage system. 4. **Server Ingestion:** Bob's local device uploaded this newly generated encrypted archive entry back up to the server-side Labyrinth mailbox.

While functional, Labyrinth 1.0 created severe synchronization bottlenecks. If Bob's phone remained offline or powered down when Alice sent her message, the communication was never uploaded to the Labyrinth backup. If Bob subsequently logged into Messenger via a desktop browser at an internet cafe before turning his primary phone back on, the offline messages were entirely absent from his conversation view.

Labyrinth 1.1 resolves this limitation by transitioning from a predominantly symmetric key layout to an asymmetric, public-key architecture. Instead of waiting for a recipient's device to ingest, decrypt, and re-upload the message: > “The new backup model in Labyrinth 1.1 allows senders to encrypt the message to a public key and put the message in your backup on the server, without your device needing to download it.”

In Labyrinth 1.0, data at rest within a backup was secured with symmetric keys held locally across the user's fleet of personal devices. Labyrinth 1.1 incorporates public-key cryptography directly into the ingestion process, allowing a sender to encrypt directly to the recipient's backup mailbox on the server.

This architectural shift introduces a classic identity distribution problem: the sender's client must reliably verify that the mailbox public key belongs to the legitimate recipient and has not been forged or swapped by intermediate servers. While users can manually audit contact keys by opening "secure thread settings" inside the UI and comparing cryptographic fingerprints in person, manual fingerprint comparison is too high-friction for everyday use. To automate that verification without forcing users to compare strings of key material, the system implements automated Key Transparency.

`30:00` Key Transparency and Verifiable Ledgers

Emma Connor explains how users can trust that the public keys they receive actually belong to the intended contacts without relying blindly on Meta. Users publish their public keys to a shared directory maintained as a cryptographic, append-only ledger. Once an entry is written into this ledger, it cannot be deleted, and any attempt at tampering is immediately detectable. To establish an independent root of trust, Meta takes a cryptographic hash of this ledger roughly every ten minutes and transmits it to third-party auditors, primarily Cloudflare, who sign the hash.

This third-party attestation proves that there is only one canonical directory, ruling out split-view attacks, duplicate entries, or retroactive history rewrites. Client applications retrieve the external signature, verify that it covers the directory, and ensure that the recipient's public key matches the entry recorded on the ledger. As Emma puts it: “We have a signature from an outside party attesting to the fact that this is the one directory this directory is a source of truth.” Pascal points out how deep and exciting cryptography can get, noting that listeners can check out an earlier dedicated episode on WhatsApp’s key transparency infrastructure for further details.

```mermaid flowchart TD accTitle: Key Transparency Verification Flow accDescr: Shows how user public keys are committed to an append-only ledger and validated using external third-party signatures.

subgraph Meta ["Meta Infrastructure"] PK["User Public Keys"] --> Ledger["Append-Only Ledger"] Ledger --> Hash["Ledger Hash (Every ~10 mins)"] end

subgraph ThirdParty ["External Verification"] Hash --> CF["Cloudflare / Auditors"] CF --> Sig["Cryptographic Signature"] end

subgraph Client ["User Device"] Sig --> Verify["Verify Signature & Directory Entry"] end ```

`31:37` Post-Quantum Cryptography and the Hawk Hybrid Scheme

Turning to long-term risks, Pascal asks how the team prepares for quantum computing threats. Emma explains that the primary danger today is not that an adversary secretly possesses an operational quantum machine, but rather the threat of "store now, decrypt later" attacks, where adversaries harvest encrypted traffic today with the intention of cracking it when capable hardware emerges in a decade or two. Because Labyrinth acts as a persistent message storage system, Emma jokes that adversaries do not even need to run storage operations themselves: “When it comes to labyrinths, we are especially concerned about the possibility of a store now decrypt later because they—we're doing all the storage for you. That's kind of the point, right? So we're doing all of the storage now. You know, like if a bad actor comes along, they don't even need to be storing the data today, we're already doing it. It's kind of just a decrypt later attack.”

In Labyrinth 1.0, the post-quantum story was simpler because symmetric cryptography is naturally resilient against quantum attacks, and asymmetric exchanges were confined to sharing entropy rather than direct keys. However, Labyrinth 1.1 required a comprehensive migration to asymmetric post-quantum security. From the earliest design documents, Labyrinth 1.1 incorporated a custom hybrid cryptosystem named Hawk. Hawk blends classical and quantum-resistant algorithms: - **Post-Quantum Confidentiality**: Encrypts data against an attacker who might compromise either Diffie-Hellman or ML-KEM (Module-Lattice-Based Key-Encapsulation Mechanism), ensuring that confidentiality holds as long as both are not broken. - **Classical Authentication**: Leverages standard Diffie-Hellman to authenticate identities.

This split is intentional: authentication does not suffer from "store now, decrypt later" hazards because attackers cannot retroactively impersonate past senders once the live session is concluded. Emma highlights the efficiency and security payoff of this structure: “So we have the efficiency of a classical authentication and then the security of a post-quantum confidentiality. So the data is protected from quantum attacks even in the future.”

`35:28` Outro and Reflections on Defeating Your Own Power

Pascal remarks that it would be profoundly anticlimactic to spend years engineering rock-solid storage only to have a quantum machine render it completely vulnerable down the road. Emma agrees, emphasizing that when personal conversations are stored with the goal of keeping them accessible to users 10 to 20 years into the future, long-term post-quantum defenses are non-negotiable. Pascal then wraps up the interview, thanking Emma for granting him peace of mind whenever he is out kayaking on a lake with his phone sitting loosely in his pocket.

In his closing reflections, Pascal underscores the extreme engineering measures Meta took with their hardware security modules: writing the rate-limiting firmware, flashing it to the cards, and then deliberately physically destroying the smart cards required to upload new code. Pascal calls this an inspiring design philosophy: “A deliberate, irreversible act of locking yourself out, carried out so that nobody at Meta can ever quietly change the rules later. There's something quite admirable about engineering, but the goal is to reduce your own power.” He also revisits Emma’s perspective on the ancient developer maxim to "never roll your own crypto," observing that someone ultimately has to invent new tools, and the responsible way is to collaborate with specialists, seek transparent peer review, and publish the work openly.

- Meta released both a formal white paper detailing Labyrinth and an open-source Rust crate (`opaque-ke`). - Listeners can submit feedback via Threads and Instagram (`@metatechpod`) or email (`techpodcast@meta.com`), though Pascal jokingly begs people not to send GPG-signed emails requiring manual signature validation.

## Notable quotes > `05:00` Part of the problem we’re trying to solve is making that new mental model as small as possible compared to the existing mental model. — Emma Connor

> `23:25` If you revoke a device, that means the keys that exist on that device should no longer be able to decrypt any messages that are sent after that point. — Emma Connor

## Mentioned Cisco, Cloudflare, Diffie-Hellman, Electronic Frontier Foundation (EFF), Emma Connor, Facebook Messenger, Google Drive, Hawk, John Milliken, Kevin Lewi, Labyrinth, Labyrinth 1.0, Labyrinth 1.1, ML-KEM, Meta, Moxie Marlinspike, Network Time Protocol (NTP), Network Time Security (NTS), OPAQUE, Pascal, RSA, Rust, Secure Enclave, Shai Halevi, Signal, WhatsApp, iCloud, opaque-ke

## Video description <p dir="ltr">How do you add end-to-end encryption to a messenger that people have already been using for years, without taking away the message history they expect to be there? Emma Connor works on the team behind Labyrinth, Messenger's encrypted storage protocol. We get into where the encryption keys actually live, which turns out to be nowhere near Meta, and why a six-digit PIN is far harder to attack than six digits ought to be. Emma also explains the hardware security modules that Meta deliberately locked itself out of, what happens when you lose a device and want it cut out of your account, how key transparency lets you verify who you are talking to without having to trust us, and why Labyrinth 1.1 moved to post-quantum cryptography. All of this and more is discussed in this episode.</p> <p dir="ltr">Got feedback? Send it to us on Threads (<a href= "https://threads.net/@metatechpod">https://threads.net/@metatechpod</a>), Instagram (<a href= "https://instagram.com/metatechpod">https://instagram.com/metatechpod</a>) and don't forget to follow our host Pascal (<a href= "https://mastodon.social/@passy">https://mastodon.social/@passy</a>, <a href= "https://threads.net/@passy_">https://threads.net/@passy_</a>). Fancy working with us? Check out <a href= "https://www.metacareers.com/">https://www.metacareers.com/</a>.</p> <p dir="ltr"><strong>Links</strong></p> <ul> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Opaque Rust Crate - <a href= "https://crates.io/crates/opaque-ke">https://crates.io/crates/opaque-ke</a></p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Labyrinth 1.1: Making End-to-End Encrypted Backups Even More Reliable - <a href= "https://engineering.fb.com/2026/05/11/security/labyrinth-1-1-end-to-end-encrypted-e2ee-backups-more-reliable/"> https://engineering.fb.com/2026/05/11/security/labyrinth-1-1-end-to-end-encrypted-e2ee-backups-more-reliable/</a></p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">The Labyrinth Encrypted Message Storage Protocol - <a href= "https://engineering.fb.com/wp-content/uploads/2026/05/Minos-Updates-2026-Encrypted-Backups-White-Paper.pdf"> https://engineering.fb.com/wp-content/uploads/2026/05/Minos-Updates-2026-Encrypted-Backups-White-Paper.pdf</a></p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">MTP E54: Building Key Transparency at WhatsApp - <a href= "https://pca.st/orlsiq1l">https://pca.st/orlsiq1l</a></p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">MTP E65: Getting Ready for Post-Quantum Cryptography - <a href= "https://pca.st/4klppivy">https://pca.st/4klppivy</a></p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">NTS: Authenticated Time at Meta - <a href= "https://engineering.fb.com/2026/10/06/production-engineering/nts-authenticated-time-at-meta/"> https://engineering.fb.com/2026/10/06/production-engineering/nts-authenticated-time-at-meta/</a></p> </li> </ul> <p dir="ltr"><strong>Timestamps</strong></p> <ul> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Intro 0:06</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Introduction Emma 2:25</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">The Complexity of End-to-End Encryption 3:42</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">User Expectations and Mental Models 5:35</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">The Labyrinth System Overview 7:17</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Key Management and Security 7:54</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Rate-limiting passcode guesses 13:01</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Evolution of Backup Systems 16:45</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Rolling Out New Storage Protocols 17:07</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Ensuring Data Integrity During Migration 18:34</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Multi-Device Support Challenges 18:48</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Building Custom Cryptography Solutions 20:20</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Revoking Device Access 23:56</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Labyrinth 1.1: 26:24</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Preparing for Quantum Resistance 29:20</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Outro 36:16</p> </li> </ul> <p> </p>

[Source](https://traffic.libsyn.com/secure/insidefacebookmobile/mtp88_rc2.mp3?dest-id=697376)

中文简报

Meta销毁硬件密钥以彻底放弃对Messenger备份的访问权限 Meta部署了后量子非对称加密技术,并物理销毁了其安全模块的管理访问卡,以便在不持有解密密钥的情况下保留多设备聊天记录。 Meta软件工程师Emma Connor详细解析了Labyrinth背后的工程设计,该系统旨在为Facebook Messenger建立默认端到端加密的同时,保留多设备聊天记录。为了在不持有解密密钥的情况下保护用户归档,Labyrinth将OPAQUE密码认证密钥交换协议与防篡改硬件安全模块(HSM)相结合,并永久销毁了HSM的配置卡以防止内部越权。讨论回顾了该协议向Labyrinth 1.1的演进过程,该版本通过采用邮箱级公钥、经由Cloudflare审计的密钥透明度目录,以及使用ML-KEM抵御未来“先收集后解密”攻击的后量子混合加密技术,消除了同步瓶颈。 Meta推出了Labyrinth,在为Messenger引入端到端加密的同时,为每个账户多达100个并发端点保留即时跨设备聊天记录。 (7:17) 为了确保六位PIN码恢复的安全性,Meta在硬件安全模块(HSM)上部署了采用OPAQUE协议的自定义限速固件,并物理销毁了配置智能卡,以永久彻底封锁自身的访问权限。 (10:00) Labyrinth 1.1从对称存储密钥过渡到非对称公钥邮箱,使发送者能够直接将加密消息写入接收者的备份中,而无需等待接收者的设备上线。 (26:12) 为防范未来针对已存储归档的量子收集攻击,Labyrinth 1.1实施了Hawk系统,这是一种结合了传统Diffie-Hellman认证与ML-KEM后量子机密性的混合密码系统。 (31:37)

Reading summary

Retrofitting end-to-end encryption into Facebook Messenger required Meta to reconcile a decade of seamless cloud-sync expectations with an architecture where the server cannot hold decryption keys. To solve this, Meta developed Labyrinth, an encrypted backup framework relying on rate-limited hardware security modules, public-key storage pipelines, and post-quantum cryptography. Reconciling Legacy Habits with Zero-Knowledge Storage (5:00) Unlike WhatsApp, which established early user norms around local device backups and high-friction migrations, Messenger operated for more than ten years under a model where users expected full chat histories to stream instantly onto any new browser or device. Shifting to end-to-end encryption meant preserving this multi-device convenience without permitting Meta to hold keys to decrypt user archives. Research scientist Emma Connor noted that Messenger supports an expansive footprint of around 200 concurrent endpoints per user, allowing devices left unused in drawers for months to retain access upon powering back on. Meta addressed this challenge with Labyrinth, an encrypted storage architecture that strictly separates ciphertext storage on Meta's infrastructure from operational decryption keys held on user devices. Everyday decryption keys never touch Meta's servers, requiring recovery to bootstrap through external mechanisms or rate-limited credentials whenever a user loses all active hardware. Securing Recovery with OPAQUE and Locked HSMs (10:00) To preserve a simple user experience, Labyrinth supports recovery via a six-digit PIN. To prevent brute-force attacks across this small key space, Meta pairs the PIN with the OPAQUE asymmetric password-authenticated key exchange protocol. Under this setup, the user's client derives a cryptographic token locally, ensuring the raw PIN never leaves the device. The client then conducts challenge-response rounds with hardware security modules (HSMs) in Meta's data centers, reconstructing the master backup key while leaving the HSM blind to both the PIN and the underlying key. To guarantee that internal engineers or attackers cannot override rate-limiting rules, Meta flashed the rate-limiting firmware onto the HSMs during initial provisioning and then physically destroyed the smartcards needed to deploy code updates. For users with another active session, Labyrinth provides an alternative One-Time Code recovery mechanism. This flow uses password-authenticated key exchange to establish an encrypted channel between the two devices, using hash comparisons of exchange parameters to verify physical presence while enforcing client-side rate limits against compromised servers. Asymmetric Ingestion and Epoch Key Revocation (20:00) In large Messenger group threads, endpoint pairing can scale connection complexity toward quadratic orders of magnitude under standard protocols. Labyrinth bypasses this combinatorial issue by routing messages between user mailbox-level public keys rather than fanning out to every individual device. This approach underpinned the transition from Labyrinth 1.0 to Labyrinth 1.1. In 1.0, backups stalled if a recipient was offline because the system required the recipient's physical device to receive, decrypt, and re-encrypt the message for the server archive. Labyrinth 1.1 allows senders to encrypt directly to a recipient's mailbox public key on the server, ensuring uninterrupted cloud history even when recipient hardware is powered off. To verify these mailbox public keys without tedious manual fingerprint audits, Meta deployed an automated Key Transparency system that records public keys on an append-only ledger, with root hashes audited and signed roughly every ten minutes by Cloudflare. When a user revokes access for a lost or stolen device, Labyrinth enforces forward confidentiality using epoch keys. An active device generates fresh entropy and shares it exclusively with remaining trusted devices via encrypted channels. Because the revoked device never receives this material, it cannot derive future epoch keys to read post-revocation traffic. Long-Term Protection Against Store-Now Decrypt-Later Attacks (31:37) Because Labyrinth acts as a persistent central message archive, it faces long-term risks from adversaries harvesting encrypted data today to crack once operational quantum hardware emerges. Labyrinth 1.1 addresses this exposure through a custom hybrid cryptosystem named Hawk, designed to protect archives intended to remain accessible for decades. Hawk divides cryptographic responsibilities between classical and post-quantum algorithms. It uses standard Diffie-Hellman for session authentication, which is not vulnerable to retrospective decryption attacks after a session terminates. For confidentiality, Hawk combines Diffie-Hellman with ML-KEM, ensuring that stored message history remains protected against future quantum decryption as long as both algorithms are not simultaneously compromised. To deploy Labyrinth at scale, Meta moved away from off-the-shelf primitives toward in-house packaging and custom key-management architectures. The deployment couples cryptographic controls with administrative self-restriction, explicitly using irrevocable hardware lockouts to eliminate internal backdoors.

精炼阅读摘要

Meta 研究科学家 Emma Connor 在播客中阐述了 Facebook Messenger 在引入端到端加密时所面临的核心挑战:必须在剥夺 Meta 解密权的前提下,兼顾十余年来用户对跨设备无缝漫游与历史消息即时拉取的习惯,为此团队自主设计了加密存储架构 Labyrinth。 旧习惯与端到端加密的架构冲突 (0:00) 在传统客户端-服务器模式下,用户即使手机落水换新,也能在登录后由数据中心直接下发完整的聊天记录。然而在端到端加密架构中,Meta 无法持有解密私钥,导致新设备在缺乏历史密码学状态时难以直接还原数据。与 WhatsApp 早期就建立起依赖本地系统备份或设备面对面高摩擦迁移的模式不同,Messenger 用户习惯于在任意网吧电脑打开浏览器即刻同步全部历史,或者时隔一两年从抽屉拿出闲置设备仍能接收通知。Messenger 允许单用户挂载多达约 200 台并发终端,这就要求工程团队在保留原有即时体验的同时,建立起 Meta 自身也无法旁路破解的加密存储方案。 Labyrinth 备份设计与硬件锁死机制 (7:17) 为了将日常解密密钥限制在用户本地设备,Meta 开发了名为 Labyrinth 的加密存储系统。用户最常用的备份恢复方式是 6 位数字 PIN 码。由于 6 位数字的密钥空间极小,极易遭到暴力破解,Meta 采用了基于 Rust 开源实现的 OPAQUE 非对称密码认证密钥交换协议,使 PIN 码在本地派生为密码学凭据,网络传输和数据中心均无法窥见明文。长加密密钥保存在数据中心的硬件安全模块(HSM)中,HSM 强制执行严格的速率限制。为防止内部工程师或特权攻击者篡改规则,Meta 在首次烧录限速固件后,物理销毁了能够向 HSM 加载代码的部署智能卡,通过不可逆的技术手段彻底剥夺了自身权限。对于记不住 PIN 码但有另一台在线设备的用户,系统支持一次性验证码(OTC)通道,利用基于哈希的密钥交换协议让两台终端在本地验证物理对端,而不依赖服务端作为信任根。 从 Labyrinth 1.0 到 1.1 的架构演进 (26:12) 在 Labyrinth 1.0 中,历史记录依赖接收方本地设备来维护:发送者将消息发给接收者终端,接收者下载、解密,再将其重新用备份格式加密并上传到服务端的 Labyrinth 邮箱。这种机制的弊端在于,如果接收者的主力设备处于关机或离线状态,新发送的消息就不会进入备份归档,导致用户在另一台浏览器登录时出现消息断层。Labyrinth 1.1 将其重构为基于非对称公钥的架构,引入每用户单一的“邮箱级公钥”,允许发送方直接用接收方的公钥加密并写入服务端的备份邮箱,彻底摆脱了接收方设备必须在线中转的制约。在撤销丢失设备权限时,系统通过“纪元密钥(Epoch Key)”机制确保前向保密:由受信任设备生成新熵并通过端到端信道分发给其余合法设备,被撤销的设备因无法获得该熵而无法解密撤销操作之后的新流量,但系统不对历史归档进行向后重加密。 密钥透明度与抗量子混合加密 Hawk (30:00) 转向非对称公钥架构后,核心风险变成了防止服务端伪造或替换用户的公钥。为此 Meta 引入了自动化密钥透明度系统,将用户公钥写入只追加不可篡改的加密账本中,并由第三方审计机构(如 Cloudflare)大约每 10 分钟对账本哈希进行一次签名背书,使客户端无需人工比对密钥指纹即可防范视图分离攻击。考虑到攻击者可能在当下截获并存储密文以待未来破译(即“先存储后解密”),Labyrinth 1.1 采用了名为 Hawk 的混合密码方案。Hawk 将经典的 Diffie-Hellman 协议与抗量子算法 ML-KEM 结合用于机密性保护,只要其中一种算法未被攻破即可保障消息安全;同时在身份认证环节保留传统算法以维持效率,从而在长期存储场景下抵御未来量子计算对历史通讯的破解威胁。 Meta 在 Labyrinth 系统中通过硬件自我锁死、开源密码协议以及引入第三方公证,展示了一种“以工程手段削减自身权力”的安全设计路径;面对多设备规模带来的组合膨胀,团队选择与外部学术界及标准制定者合作定制协议包装,而非生搬硬套既有方案。

Detailed notes — zh

## Background 本期内容来自 Meta 官方播客频道《Meta Tech Podcast》,由 Pascal 主持并于 2026 年 10 月 15 日发布。节目简介指出,Meta 加密基础设施团队的研究科学家 Emma Connor 将深入剖析 Messenger 的加密存储协议 Labyrinth,揭秘解密密钥为何绝不保存在 Meta 服务器、为何 6 位数 PIN 码极难破解,以及团队如何通过自锁 HSM、密钥透明度与后量子加密保护用户历史消息。 ## TL;DR 节目围绕 Facebook Messenger 引入端到端加密备份架构 Labyrinth 的技术实践展开讨论。面对 Messenger 数亿存量用户随时跨数百台设备无缝同步消息的心智模型,团队在确保 Meta 服务器完全无权解密的前提下,通过 OPAQUE 协议与防篡改 HSM 实现了高安全性的 6 位数 PIN 码恢复机制,甚至在初始化后销毁了 HSM 烧录卡以实现彻底自锁。此外,Emma 还详细拆解了支持多达 200 台设备的漫游与撤销机制、Labyrinth 从 1.0 对称加密到 1.1 非对称公钥模型的演进、依赖第三方独立审计的密钥透明度账本,以及结合经典认证与抗量子机密性的 Hawk 混合加密系统,以防范未来潜在的“先存后解”攻击。 ## Atmosphere & style 整期对话充满极客硬核气质与深度的工程思辨,既有硅谷大厂工程师之间轻松幽默的假期闲谈,又有直面底层密码学细节时的严密与审慎。Pascal 的层层追问与 Emma 条理清晰的拆解相得益彰,不仅生动描绘了“手机掉进湖里”等现实用户痛点,更将抽象的密码学协议落地为具体生动的工程权衡。尤其在谈及团队主动销毁烧录卡以剥夺自身权限的细节时,展现出一种对系统零信任与保护用户隐私的极致执着,让整场对谈兼具思想震撼与工程美感。 ## Bulletin - `07:17` Meta 为 Messenger 打造了端到端加密备份协议 Labyrinth,日常解密密钥仅保存在用户本地,Meta 数据中心不持有任何明文密钥。 - `12:35` 系统采用 OPAQUE 协议与硬件安全模块(HSM)保护 6 位数 PIN 码恢复,并通过彻底销毁固件烧录卡在物理层面永久固化暴力破解频率限制。 - `18:20` Messenger 允许每个账户关联高达 200 台设备漫游,Labyrinth 1.1 引入用户级邮箱公钥以解决多设备群聊时端点数量组合爆炸的扩展瓶颈。 - `33:38` 为了抵御“先存后解”的量子计算攻击,Labyrinth 1.1 部署了 Hawk 混合密码系统,将经典认证的高效性与 ML-KEM 的后量子机密性相结合。 ## Key takeaways - `00:00` 端到端加密改造的真正瓶颈不在于加密本身,而在于既有产品需要满足用户在新设备或闲置旧设备上无缝同步历史记录的心智模型。 - `05:35` Messenger 无法照搬 WhatsApp 耗时较长的点对点设备迁移方案,必须在 Meta 完全零知晓的前提下实现低门槛的即时历史备份恢复。 - `07:17` Labyrinth 备份协议采用模块化架构,日常解密密钥完全隔离于 Meta 服务器,仅在用户本地硬件上留存。 - `10:00` 基于 OPAQUE 协议的 6 位 PIN 码方案通过本地派生 Token 与服务端 HSM 交互,实现服务端既看不到用户 PIN 码也看不到备份主密钥。 - `12:35` Meta 在部署 HSM 速率限制规则后销毁了全部物理烧录卡,通过主动削减自身权力,杜绝了内部人员日后放宽暴力破解门槛的可能。 - `15:00` 一次性代码(OTC)恢复机制利用 PAKE 原理将公钥交换参数与挑战哈希化,结合人眼跨屏比对与客户端限速有效防范中间人攻击。 - `18:20` Messenger 允许高达 200 台活跃关联设备,为了避免像 Signal 等点对点方案那样在多设备群聊中引发连接数激增,Labyrinth 引入了用户级公钥聚合设计。 - `25:00` 设备撤销机制依赖纪元密钥(Epoch Key)与端到端加密熵分发,确保被撤销设备在数学层面上绝对无法解密撤销之后产生的新消息。 - `27:00` Labyrinth 1.1 从客户端重加密演进为发送方非对称直传,彻底解决了接收方手机离线时云端备份缺失、新设备无法拉取新消息的缺陷。 - `30:00` 密钥透明度(Key Transparency)采用基于仅追加账本的设计,并定期由 Cloudflare 等独立第三方进行数字签名,使用户无需无条件信任 Meta 即可独立校验通信公钥。 - `31:36` 针对攻击者“先存后解”的历史密文破译威胁,负责集中持久化数据的备份系统比即时通讯链路本身更迫切地需要具备抗量子能力。 - `33:38` Hawk 混合系统将传统 Diffie-Hellman 协议与 ML-KEM 后量子算法结合,在兼顾当下运算效率的同时保障了长期的备份密文机密性。 ## Detailed walkthrough ### `00:00` 开场:休假烦恼、时间同步安全(NTS)与端到端加密的真正难点 主持人 Pascal 结束了一个月的“充能假”(recharge leave)回归播客。他半开玩笑地分享了一个典型的“大厂凡尔赛烦恼”:休假前没怎么请假,休假期间年假天数还在持续累积,导致他手头堆积了大量未休年假,必须在未来几个月内突击休完,因此播客随后的更新频率可能会更加飘忽不定。 ```mermaid flowchart TD accTitle: Messenger 历史消息加密重构的核心矛盾 accDescr: 展示用户传统心理模型与端到端加密零知识安全之间的冲突 subgraph User["用户预期(心智模型)"] U1["全新设备 / 网吧电脑登录"] U2["从抽屉拿出闲置18个月的备用机"] U3["要求:所有历史记录无缝瞬间恢复"] end subgraph Conflict["核心技术冲突"] C["Meta 服务器绝不能持有任何密钥或明文数据"] end subgraph Goal["系统目标(Labyrinth)"] G["在 Meta 无法介入的前提下,安全验证用户身份并恢复消息"] end User --> Conflict Conflict --> Goal ``` 在切入正题前,Pascal 指出了大众对密码学普遍存在的一个认知误区:加密本身从来不是最难的部分,正如 Pascal 所言:“任何人都能把一个文件加密到谁也打不开,包括他们自己,这算是一种成就;但真正棘手的是加密之后发生的事情。”尤其在既有产品上改造端到端加密(End-to-End Encryption, E2EE)时,难度更是倍增。WhatsApp 在发展初期就引入了端到端加密,可以自行制定消息历史的规则;但 Messenger 已经存在多年,用户早已养成了在网吧新电脑上随意登录、从抽屉掏出闲置一年半的旧手机开机就能同步完整消息的习惯。团队不能破坏这种既有习惯,必须在“Meta 自身不掌握任何协助手段”的前提下,允许合法用户在一台从未触碰过的新设备上恢复历史消息。 在展开讨论前,Pascal 还介绍了 Meta 工程博客最近发布的一篇关于网络时间安全(Network Time Security, NTS)的文章: - Meta 的公共授时服务现已引入数据包认证机制,客户端可借此验证接收到的时间信号真实源自 Meta 且未经篡改。 - 自 1985 年以来,NTP 协议一直处于完全没有身份验证的状态,尽管像 TLS 证书有效期和各种安全 Token 的过期判定都直接依赖它。 - Meta 该服务的服务器端完全不保留针对单个客户端的会话状态,且客户端与服务端协议均已全部开源。 --- ### `02:25` 嘉宾背景与加密基础设施团队职责 本期嘉宾 Emma Connor 是 Meta 加密基础设施团队的研究科学家(Research Scientist)。在回顾自己的经历时,Emma 提到她刚在 2026 年 8 月度过了入职五周年纪念日。她在 2021 年博士毕业后便直接加入 Meta,当时她甚至在“搬到西雅图开启新工作的前一周”才完成博士论文答辩。在攻读研究生期间,她曾在思科(Cisco)的安全倡议团队兼职数年,主要从事渗透测试(penetration testing)和漏洞利用开发研究(exploit development research)。 谈及当前团队的职责范畴,Emma 介绍道: - 团队主要负责面向即时通讯场景的底层加密基础设施,最核心的业务是 Facebook Messenger 的端到端加密功能。 - 团队还负责联合分析(Federated Analytics)等隐私增强技术,旨在以更保护隐私的方式收集用户使用应用的行为指标,相较于直接上传原始指标,能够最大限度减少平台获取的敏感信息。 - 在实现分析数据隐私化方面,团队通常结合使用差分隐私(Differential Privacy)和可信执行环境(Trusted Execution Environments, TEE)。 --- ### `03:42` 改造已有聊天系统的两难:当手机掉进湖里 话题转向端到端加密备份的核心挑战。Pascal 形象地描述了一个常见痛点:当你把手机掉进湖里,或者换了一部新手机,你总是理所当然地认为曾经发送的所有聊天记录都该原封不动地在云端等着用新手机登录。在过去的 Messenger 中,这种体验非常顺畅,因为历史消息以明文形式存放在中心化数据中心。 但一旦推行端到端加密,一切都颠覆了。Emma 强调了这种架构上的本质冲突: > “因为 Meta 绝对不能拥有访问你消息的途径,对吧?这就是端到端加密的全部意义所在。所以如果我们无法访问你的消息,而你又在一部刚刚登录的全新手机上,那么到底由谁去把你的消息取回来呢?这其实是非常不明确的。” 端到端加密的底线在于服务商(Meta)在密码学上必须是“盲目”的,因此当用户在一台连本地密钥都没有的全新硬件上登录时,服务商无法主动扮演“帮用户解密并同步数据”的传统角色。 --- ### `05:35` 用户心智模型差异:Messenger 与 WhatsApp 的分歧 两人深入讨论了产品定位和用户使用习惯对加密技术方案的制约。在全新开发的端到端应用中,开发者大可直接告诉用户“没有云端备份,换机概不负责”;但对于拥有数亿存量用户的 Messenger,直接告诉大家“所有历史记录都没了”是完全不可接受的。 Emma 解释道,引入端到端加密必然会要求用户建立某种新的心智模型,但团队的核心设计目标就是“让这个全新的心智模型相比现有的心智模型尽可能小”。这正是 Messenger 无法照搬 WhatsApp 备份方案的根本原因: - **WhatsApp 的心智模型**:WhatsApp 很早就支持端到端加密,用户习惯了“设备间点对点迁移”(例如换新手机时两台手机靠近或通过本地局域网逐步传输数据)。这种方案更侧重于低频次的设备更换场景,当两台设备都在身边时体验良好,但传输流程耗时较长。 - **Messenger 的心智模型**:Messenger 用户习惯了极低门槛的即时同步体验。用户期望在任意网吧电脑的浏览器或全新备用机上登录时,只需要输入凭证,历史记录就能像以往一样近乎瞬时呈现,根本无法容忍两部设备互相对接漫长的迁移等待。 关于 Messenger 从官宣支持端到端加密到全量推送历经数年、一度引发外界各种“阴谋论”的现象,Pascal 直言,剖析这些细节恰恰说明了这绝非 Meta 在刻意拖延,而是要在海量存量用户预期下重新构建一套不信任服务端的备份架构,背后涉及的技术细节和工程难度“真的是非常棘手”。 --- ### `07:17` Labyrinth 系统命名由来与模块化架构 这套支撑 Messenger 端到端加密备份的核心系统被命名为 **Labyrinth**(Meta 已为其发布了专门的技术白皮书)。谈及这个名称的来历,Emma 透露该名称主要由 John Milliken 提出。 其最初的设计意图是将系统构建为一个高度隐蔽的迷宫结构,甚至让攻击者难以在数据存储层定位消息具体藏在何处。尽管 Emma 指出这只是早期的一个历史设定(现在的实际部署方式已经有所演进),但由于其形象且朗朗上口,Labyrinth 这个代号最终被保留了下来。 ```mermaid flowchart TD accTitle: Labyrinth 密钥存储与恢复架构 accDescr: 展示设备日常运行与设备丢失时的模块化密钥恢复方案 subgraph Daily["日常运行状态"] Dev["用户本地设备"] Keys["加密密钥仅保存在设备本地"] Dev --- Keys end subgraph ServerSide["Meta 服务端"] Meta["Meta 数据中心(无任何解密密钥)"] end subgraph ModularRecovery["模块化恢复机制(异地/灾备)"] R1["PIN 码与专用硬件模块防护"] R2["第三方安全环境托管"] R3["未来演进恢复方案"] end Daily -.->|"发生遗失 / 换机"| ModularRecovery ModularRecovery -->|"安全授权取回密钥"| Dev Daily -.-x|"严禁泄露密钥"| ServerSide ``` 针对密码学中最核心的问题——“解密密钥最终保存在哪里?”,Emma 做出了极其明确的界定: > “有非常多的选择,但它们的共同底线是:基本上绝不在 Meta。” - **日常使用期间**:用于解密消息历史的会话密钥仅存储在用户的终端设备本地。 - **设备丢失场景**:为了应对手机损坏或丢失的情况,系统提供了多种异地保存备份密钥的恢复机制。 - **模块化演进**:Labyrinth 整体采用模块化设计,这样工程团队就可以在不推翻整体协议架构的前提下,不断实验和拓展诸如不同恢复策略在内的新技术方案。 ### `10:00` 6 位数 PIN 码的安全性与 OPAQUE 协议 对于加密备份系统的恢复方案,团队考量了各种可能性,而在当下大规模部署的核心方案是 6 位数 PIN 码。用户只需牢记一个简短的 6 位数字,就能解锁并恢复自己的全部备份。主持人 Pascal 对此表达了直觉上的担忧:从表面上看,6 位数字的组合空间非常有限(仅有 100 万种可能),极易遭到暴力破解。针对这一攻击面,Emma 解释称核心策略是严格限制任何人尝试猜测 PIN 码的次数,而这一限制是由硬件安全模块(Hardware Security Module, HSM)在底层强行保证的。 在整套方案中,备份的长密钥本身经过了特殊加密,唯有 HSM 能够解密;然而即便在 HSM 内部,若没有用户的 PIN 码也绝无可能完成解密。交互的具体流程是:用户的 PIN 码绝不会离开本地设备;设备会在本地基于该 PIN 码计算派生出一个密码学 Token,并将其发送给 HSM。HSM 随后返回响应,双方在经历一到两轮交互后,设备端便能重构出解锁备份的主密钥。这一通信协议被称为 OPAQUE(一种非对称 PAKE 协议),Meta 已将其作为开源 Rust crate(`opaque-ke`)发布。 Emma 强调了这套机制的双向隔离特性:在窃听者视角下,若想单纯通过截获信道中的交互数据反推 PIN 码,其难度等同于攻破极其困难的底层数学难题;而在系统内部,“HSM 从来看不到你的 PIN 码,它也从来看不到你备份的密钥”(“whereas the HSM never sees your PIN and it never sees the key to your backup either.”)。设备端拿回了解密能力,而敏感信息在流转过程中完全不向服务端泄露。 ```mermaid flowchart LR accTitle: 基于 OPAQUE 协议与 HSM 的 PIN 码解锁流程 accDescr: 展示用户设备端与 Meta 数据中心 HSM 之间如何通过 OPAQUE 协议安全协商解密备份密钥,双方均不泄露原始 PIN 与明文密钥。 subgraph Client["用户本地设备"] PinInput["用户输入 6 位 PIN"] DeriveToken["本地派生加密 Token<br/>(PIN 不离开设备)"] ReconstructKey["重构备份解密密钥<br/>(成功解锁历史消息)"] PinInput --> DeriveToken end subgraph Server["Meta 数据中心"] HSM["硬件安全模块 (HSM)<br/>(防篡改硬件 + 速率限制)"] end DeriveToken -->|"发送派生 Token"| HSM HSM -->|"返回密码学响应 (1-2 轮)"| ReconstructKey ``` ### `12:35` 防篡改硬件与“销毁烧录卡”的极致防御 当探讨 HSM 如何在物理层面实现防暴力破解时,Pascal 询问这种速率限制是类似于工作量证明(Proof of Work)算法开销,还是某种硬件固有属性。Emma 用智能手机中的安全隔区(Secure Enclave)进行了类比:就像 iPhone 在多次输错锁屏密码后会彻底锁定一样,哪怕攻击者砸下数百万美元将手机拆解放入顶尖实验室,其硬件防篡改设计也足以阻止攻击者强行读取芯片上的私钥或绕过速率限制逻辑。部署在 Meta 数据中心的 HSM 亦遵循相同的防护准则。 为了将“Meta 自身也无法窃取数据”落实到极致,团队在系统初始化部署时采取了极为决绝的措施。Emma 披露了一个技术细节:他们将速率限制代码部署进 HSM 之后,立即彻底销毁了所有能向该硬件烧录或更新代码的实体卡片(Provisioning Cards)。这意味着代码一旦固化,没有任何人(包括 Meta 工程师或管理员自身)能够修改里面的规则或调高尝试上限。正如 Emma 所述:“我们编写了这套速率限制代码,部署了一次,然后销毁了烧录卡,因此代码再也无法被更改了”(“So we sort of write this rate limiting code, We deploy it once and we destroy the card so the code can never change again.”)。Pascal 对此感叹道,为了让数十亿用户甚至完全感知不到其存在、只是觉得系统“理所当然能用”,背后竟然付出了如此巨大的工程与安全代价。 - **防篡改硬件(Tamper-Resistant HSM)**:放置在 Meta 数据中心,但硬件级物理防御确保外部无法物理探针探测或修改内部状态。 - **不可逆部署策略**:系统初始化后彻底销毁授权烧录卡,杜绝后续更新固件以放宽暴力破解门槛的可能性。 ### `15:00` 跨设备恢复:一次性代码(OTC)与 PAKE 协议 如果用户遗忘了 6 位数 PIN 码,系统还提供了备选恢复链路,其中最核心的场景是“一次性代码”(One-Time Code, OTC)。如果用户手头有另一台已经登录了加密备份的旧设备,只需在新设备登录时发起请求,旧设备屏幕上就会弹出一个 6 位数随机码,用户在新设备上输入该验证码即可顺利恢复消息历史,即使遗忘原 PIN 码也无妨。 Emma 澄清道,OTC 虽然呈现为 6 位数字,但它在底层并不是用来鉴权并直接解锁备份的静态 PIN 码,而是用来认证一条新建立的安全通信信道。两个端点首先通过标准密钥交换协议(如 Diffie-Hellman、RSA 或后量子算法 ML-KEM 等公钥交换方式)生成临时密钥并握手,但纯粹的密钥交换无法防范中间人攻击(任何中间人都能独立生成密钥完成握手)。为了确认两台设备真正属于同一个人,系统利用密码认证密钥交换(PAKE)原理,把密钥交换过程中的参数、公钥以及防重放 challenge 一起输入哈希函数,计算生成该 6 位代码。 新旧设备各自独立计算出该代码后,旧设备将其显示在屏幕上,新设备等待用户输入。由于新设备在握手完毕后早已在后台预知了正确哈希值,“新设备所做的只是验证你输入的代码与它预期的相符”(“So all the new device is doing is verifying that the code you put in matches what it expects.”)。这一设计把物理世界的“人眼核验”作为信任锚点,确保确有真人在操作这两台手机,彻底排除了中间人攻击。与此同时,为防止恶意攻击者无限尝试猜测该 6 位临时验证码,客户端自身也必须实现严格的状态机和速率限制逻辑,因为在端到端加密体系中,系统必须预设哪怕服务端被攻破或行为异常,防护机制依旧在客户端稳固生效。 ### `18:20` 多设备支持规模:高达 200 台设备的漫游与容错 在多设备同步的设计复杂度上,Messenger 的策略与 WhatsApp 等产品截然不同。WhatsApp 最初对多设备有着较严格的限制(例如逐步放开至上限 4 台),而 Messenger 允许用户随时在浏览器新标签页中打开并登录。Emma 透露,Messenger 允许的活跃关联设备上限大约高达 200 台(“Well, I want to say the limit is something like 200.”)。 之所以将设备上限设定得如此宽泛,是因为团队深入考虑了极为现实的长尾用户使用习惯。很多用户的某个旧手机在登录 Messenger 后可能会被直接扔进抽屉里,闲置一到两年才重新开机。丢失消息历史对用户而言是极其严重的体验灾难,因此 Messenger 极力避免像其他应用那样,因为设备长期离线就强制注销会话。Pascal 对此深有同感,提到自己前几天刚从抽屉里翻出一台尘封已久的手机,开机后居然立刻收到了 Messenger 的正常推送,而几乎所有其他应用早已将账号登出。支持数十甚至上百台生命周期各异的设备同时安全解密与同步消息历史,给整个加密协议的复杂度带来了指数级的挑战。 ### `20:00` 多设备规模与密码学协议的选择 在处理端到端加密时,设备规模是决定系统架构的核心瓶颈。如果一个系统中的端点设备数量较少,直接将物理设备作为消息传输端点尚且可行——比如一个群聊只有两三个人,每人四台设备,总计也就是 12 到 20 个设备端点。然而,一旦遇到拥有大量设备的用户(现实中确实有人拥有上百台设备),群聊中的端点数量会呈爆炸式增长,甚至达到成百上千乃至上万个设备。这种几何级数的膨胀使得 Signal 等现成协议很难直接照搬。因此,Messenger 推出的 Labyrinth 1.1 进行了重构,为每个用户引入了单一的“邮箱级公钥”(mailbox-level public key),用户之间的端到端加密仅在用户级公钥之间进行,从而规避了设备数量激增导致的组合爆炸,确保群聊能够扩展至更大的规模。 Pascal 和 Emma 探讨了群聊中的密钥复杂度问题。Pascal 提到 WhatsApp 的密钥对在群聊中增长极快,Emma 进一步拆解:部分会话使用 Sender Keys(发送者密钥)机制,其密钥量为 $O(N)$($N$ 为用户数);但纯粹的 Signal 协议在点对点连接下可能达到 $O(N^2)$ 的复杂度,如果有 100 个端点,就意味着大约 10,000 个连接。面对这种规模挑战,Meta 无法单纯使用市面上现成的协议,而是必须设计自己的密码学打包方案。Emma 强调,密码学界虽然流传着“切勿自己编写加密算法(Never roll your own crypto)”的告诫,但面对前所未有的工程规模,“总得有人来做这件事”。Meta 在实现时并没有去发明底层密码学原语,而是与内部及外部顶尖专家合作完成封装设计。 在 Meta 内部,工程师可以便捷地向众多顶尖密码学家请教: - 内部聚集了曾参与 WhatsApp 端到端加密开发、以及与电子前哨基金会(EFF)合作过的资深专家。 - Emma 提到自己近期在其他项目中有幸与 Signal 创始人 Moxie Marlinspike 共事。 - 团队中有发布过广泛采用的互联网加密算法标准的专家(如 Shay Gueron),Emma 还与 Kevin Lewi 展开了大量合作。 - 设计方案不仅经过公司内部严密论证,还积极征求外部隐私安全社区和独立研究人员的反馈。 > Emma Connor 强调:“‘切勿自己编写加密算法’这条经典警告固然存在,但总得有人来做这件事;而当我们做的时候,我们是与密码学专家一起做,结合外部研究人员的意见、社区反馈以及透明度来进行的。”(“And I know that there of course the classic warning of never roll your own crypto but somebody has to and when we do we do it with cryptographic experts we do it with input from external researchers and community feedback and transparency.”) --- ### `25:00` 设备撤销机制与纪元密钥(Epoch Keys) 支持庞大的多设备生态必然会带来设备丢失或落入敌手的风险,因此安全的“设备撤销(Device Revocation)”机制至关重要。Emma 阐明了撤销的安全保障边界:当一台设备被撤销后,该设备上留存的所有密钥将绝对无法解密**此后发送的任何新消息**。但该协议不提供“向后追溯”的保护,即不会去重新加密过去已经上传的历史消息。即使这台被撤销的设备落入恶意攻击者手中,甚至该攻击者拥有 Meta 内部人员权限并能直接访问数据中心,也依然无法破解撤销操作之后产生的新消息。 这一撤销机制依靠“纪元密钥(Epoch Key)”与端到端加密分发来实现: ```mermaid flowchart TD accTitle: 设备撤销与纪元密钥轮换流程 accDescr: 展示当一台设备被撤销时,有效设备如何生成新纪元熵并通过端到端加密同步给其他有效设备,同时排除被撤销设备的过程。 subgraph RevokeAction ["撤销触发"] A["用户在受信任设备上<br/>发起设备撤销"] end subgraph KeyGeneration ["新纪元密钥推导"] B["有效设备生成<br/>新纪元熵 (Entropy)"] C["通过 HKDF 派生<br/>新纪元密钥 (Epoch Key)"] B --> C end subgraph Distribution ["加密分发"] D["仅针对其余有效设备的公钥<br/>进行端到端加密传输"] E["其余有效设备<br/>解密熵并同步新密钥"] F["被撤销设备无法获取熵<br/>永久失去新消息解密能力"] end A --> B B --> D D --> E D -.->|"排除在外"| F ``` 1. **纪元定义**:系统将时间划分为不同的“纪元(Epoch)”,每个纪元严格界定当前时刻哪些设备有权访问新消息。 2. **生成新熵**:当发生撤销时,用户账号下一台未被撤销的受信任设备会生成新的纪元熵(Entropy),并据此派生出新的纪元密钥。 3. **安全同步**:该设备不会直接同步密钥明文,而是将派生密钥所需的熵,通过端到端加密(使用各有效设备的公私钥对加密)单独发送给备份中的其他有效设备。 4. **彻底隔离**:被撤销的设备永远无法接收到这部分加密熵,因而完全无法推导出新纪元的解密密钥,从而在数学层面被彻底踢出群组通信体系。 > Emma Connor 说明设备撤销的安全保障:“如果撤销了一台设备,这意味着该设备上存在的密钥将不再能够解密在该时间点之后发送的任何消息。”(“If you revoke a device, that means the keys that exist on that device should no longer be able to decrypt any messages that are sent after that point.”) --- ### `27:00` Labyrinth 1.0 到 1.1 的架构演进:从本地对称加密到发送方非对称直传 Labyrinth 协议经历过重要迭代,其中 1.1 版本对备份模型进行了根本性重构。在 Labyrinth 1.0 中,系统采用的是客户端重加密(Client-side Re-encryption)模型: - 当用户 A 给用户 B 发送消息时,消息首先加密并传输到用户 B 的物理设备上。 - 用户 B 的设备下载并解密该消息。 - 随后,用户 B 的设备使用本地存储的对称密钥将消息再次加密为备份格式,最后由该设备上传至 Labyrinth 云端备份存储。 这种 1.0 架构虽然可行,但暴露了严重的可用性缺陷:如果用户 B 的手机处于离线状态,发送给他的消息就无法在云端完成备份;假设用户此时前往网吧通过网页端登录 Messenger,在手机联网同步之前,这些新消息根本无法出现在网页端。 为了解决这一痛点,Labyrinth 1.1 转向了基于公钥密码学(非对称加密)的全新架构。在 1.1 模式下,发送方在发信时,会直接使用接收方的备份公钥对消息进行加密,并直接将备份密文投递到服务器上的接收方备份信箱中。这一过程无需接收方的任何物理设备提前在线或下载解密,彻底消除了离线不同步的问题。 | 对比维度 | Labyrinth 1.0 | Labyrinth 1.1 | |:--- |:--- |:--- | | **密码学基础** | 主要依赖**对称加密**(仅设备间共享对称密钥时使用公钥加密) | 深度依赖**非对称公钥密码学** | | **备份执行者** | 接收方自己的设备下载解密后,再重新加密上传 | 发送方直接使用接收方的公钥加密并写入备份 | | **离线体验** | 手机离线时新消息无法备份,新设备登录无法拉取该消息 | 即使接收方设备完全离线,消息也能实时进入云端备份 | | **主要技术挑战** | 设备离线时的数据同步滞后、客户端上传资源开销 | 公钥真实性验证、密钥分发信任问题 | 然而,非对称公钥架构引入了新的安全挑战:系统必须向用户保证他们拿到的接收方公钥是真实、未经服务器篡改的。尽管客户端可以提供“安全对话设置”,允许用户当面核对彼此的公钥指纹,但这种物理核验对普通大众来说门槛过高。为了在零摩擦的前提下提供防篡改保证,Labyrinth 1.1 引入了关键基础设施——密钥透明度(Key Transparency)。 > Emma Connor 解释 1.1 的核心升级:“Labyrinth 1.1 中最大的升级在于,我们改变了备份模型的工作方式……Labyrinth 1.1 的新备份模型允许发送方将消息加密到公钥,并将消息放入你在服务器上的备份中,而无需你的设备先行下载。”(“Well, the biggest upgrade in the Labyrinth 1.1 was we kind of changed how the backup model works... So, the new backup model in Labyrinth 1.1 allows senders to encrypt the message to a public key and put the message in your backup on the server, without your device needing to download it.”) ### `30:00` 密钥透明度与不可篡改账本 在端到端加密体系中,如何确保用户拿到的公钥确实属于通信对方,而不是中间人伪造的?Emma Connor 解释了密钥透明度(Key Transparency)在底层的工作机制。用户将自己的公钥上传到一个共享目录中,这个目录维护在一个基于密码学的“仅追加账本”(append-only ledger)之上。一旦某项数据被写入该账本,就永远无法被删除,且任何篡改行为都会在密码学上立即暴露。 为了杜绝 Meta 自身在后台篡改目录或提供双重视图(Split-view attack)的嫌疑,系统大约每隔 10 分钟就会提取一次该账本的哈希值,并发送给外部第三方审计机构(主要是 Cloudflare)进行数字签名。用户客户端在通信前,可以直接获取并验证来自外部第三方的签名,确认该目录是全网唯一的单一事实来源(Source of Truth)。正如 Emma 所强调的:“We have a signature from an outside party attesting to the fact that this is the one directory this directory is a source of truth.” 这种设计杜绝了目录中出现重复条目的可能,用户也无需无条件信任 Meta,即可独立核验聊天对象的公钥是否准确。 ```mermaid flowchart TD accTitle: Key Transparency Ledger Flow accDescr: 用户公钥写入仅追加账本并由第三方外部机构定期签名的流程架构 subgraph UserSide["客户端与用户"] User["用户客户端"] Verify["验证外部签名与公钥映射"] end subgraph MetaInfra["Meta 基础设施"] Ledger["仅追加防篡改账本<br/>(Append-only Ledger)"] HashGen["计算账本根哈希<br/>(约每 10 分钟一次)"] end subgraph ExternalAuditor["外部审计方"] Cloudflare["Cloudflare 等独立第三方"] end User -->|"上传公钥"| Ledger Ledger -->|"账本状态"| HashGen HashGen -->|"发送账本哈希"| Cloudflare Cloudflare -->|"返回数字签名"| Verify User -->|"核对通信方公钥"| Verify ``` ### `31:36` 后量子密码学与防范“先存后解”攻击 随着未来量子计算的发展,现有的公钥加密体系面临着巨大的潜在威胁。主持人 Pascal 引出话题,询问 Labyrinth 是如何防范目前尚未成型但未来可能出现的量子攻击。Emma 指出,尽管技术上存在某种极微小的理论可能——即某些人已经秘密制造出了能正常工作的量子计算机,但工程上主要防范的是所谓的“先存储,后解密”(Store Now, Decrypt Later)攻击。攻击者可能会在当下截获并保存密文,等待 10 年或 20 年后量子计算机成熟,再拉出来进行集中破解。 对于负责消息长期备份的 Labyrinth 而言,这种威胁尤为切近而严峻。Emma 直言,由于系统的核心业务正是为用户提供安全存储,“You know, like... if a bad actor comes along, they don't even need to be storing the data today, we're already doing it. It's kind of just a decrypt later attack.” 攻击者甚至都不需要自己费力去抓取和存储海量数据,因为 Meta 的服务器已经在替他们“集中保存”了,这让威胁直接变成了纯粹的“后解密攻击”。 - **Labyrinth 1.0 的防御特性**:在 1.0 版本中,这一问题相对好解决,因为对称加密算法本身对量子攻击天然具备较强的抵抗力。虽然设备间共享熵时用到了非对称密码学,但由于传输的仅是熵而非原始密钥,因此即使攻击者拥有量子计算机,在 1.0 架构下能攻破的内容也非常有限。 - **Labyrinth 1.1 的全新挑战**:到了 1.1 版本,以往这种廉价且轻松的抗量子红利不复存在。架构演进使得团队必须从一开始就将完全抗量子的非对称密码系统作为核心设计要求。 ### `33:38` Hawk 混合加密系统:经典认证与后量子机密的结合 为了兼顾系统性能与长期的抗量子安全性,Meta 密码学团队在 Labyrinth 1.1 中研发并部署了一套名为“Hawk”的混合密码系统(Hybrid Cryptosystem)。当系统将消息加密到接收方公钥时,Hawk 巧妙地结合了经典算法与后量子算法的优势。它在机密性(Confidentiality)层面提供双重防线:攻击者必须同时攻破传统的 Diffie-Hellman 密钥交换协议和后量子算法 ML-KEM(Module-Lattice-Based Key-Encapsulation Mechanism),否则只要其中一个算法保持安全,密文机密性就坚不可摧。 在真实世界中,身份认证与数据机密性面对的攻击模型截然不同。认证关注的是当下的防伪——只要系统在当前能准确无误地验证发送者的身份即可,攻击者几乎不可能通过“先存储、20 年后再冒充发送者”来获取有价值的收益。因此,Hawk 采用了“经典身份认证 + 后量子机密性”的混合设计。Emma 总结道:“So we have the efficiency of a classical authentication and then the security of a post-quantum confidentiality. So the data is protected from quantum attacks even in the future.” 这种架构在当下避免了沉重的后量子签名开销,保障了极高的系统效率,同时彻底封死了量子计算在未来破译历史消息备份的可能性。 ### `35:30` 主持人结语:削减自身权力的工程哲学 在节目的尾声,Pascal 感叹如果工程师倾注心血构建了坚固的静态数据安全防护,最后却被量子计算机轻易化解,那将是极具讽刺意味的悲剧。正因为 Messenger 的目标是让用户的消息记录在 10 年甚至 20 年后依然能够安全地跨设备恢复,团队才必须提前防范每一个深层风险。 在送别 Emma 之后,Pascal 分享了本期访谈中最令他震撼的技术细节:硬件安全模块(HSM)的烧录与销毁过程。工程团队编写了严格的限速代码并将其部署到 HSM 硬件上,随后立即彻底销毁了所有可以重新加载新固件的授权智能卡。这种主动且不可逆的“自锁”机制,是为了在物理和逻辑层面永远剥夺 Meta 内部任何人在日后悄悄放宽限速规则的权力。Pascal 赞叹道,这种将工程目标定义为“主动削减自身权力”的设计理念极其令人钦佩。针对业内“切勿自行实现加密算法”(Never roll your own crypto)的古老警训,Emma 的实践也给出了务实的回应:当行业探索进入深水区,某个人或团队最终必须迈出自主设计的那一步,而唯一的正确做法就是汇聚顶尖专家、主动向学界与外界公开细节并接受严格审视。 ## Notable quotes > `00:00` 任何人都能把一个文件加密到谁也打不开,包括他们自己,这算是一种成就;但真正棘手的是加密之后发生的事情。 — Pascal > `20:00` And I know that there of course the classic warning of never roll your own crypto but somebody has to and when we do we do it with cryptographic experts we do it with input from external researchers and community feedback and transparency. — Emma Connor > `31:36` You know, like... if a bad actor comes along, they don't even need to be storing the data today, we're already doing it. It's kind of just a decrypt later attack. — Emma Connor > `25:00` If you revoke a device, that means the keys that exist on that device should no longer be able to decrypt any messages that are sent after that point. — Emma Connor > `33:38` So we have the efficiency of a classical authentication and then the security of a post-quantum confidentiality. So the data is protected from quantum attacks even in the future. — Emma Connor ## Mentioned Pascal, Emma Connor, John Milliken, Moxie Marlinspike, Shay Gueron, Kevin Lewi, Meta, Facebook Messenger, WhatsApp, Signal, Cisco, Cloudflare, EFF (Electronic Frontier Foundation), Labyrinth, Labyrinth 1.1, OPAQUE, Hawk, NTS (Network Time Security), NTP, HSM (Hardware Security Module), ML-KEM, Diffie-Hellman, RSA, Key Transparency ## Video description <p dir="ltr">How do you add end-to-end encryption to a messenger that people have already been using for years, without taking away the message history they expect to be there? Emma Connor works on the team behind Labyrinth, Messenger's encrypted storage protocol. We get into where the encryption keys actually live, which turns out to be nowhere near Meta, and why a six-digit PIN is far harder to attack than six digits ought to be. Emma also explains the hardware security modules that Meta deliberately locked itself out of, what happens when you lose a device and want it cut out of your account, how key transparency lets you verify who you are talking to without having to trust us, and why Labyrinth 1.1 moved to post-quantum cryptography. All of this and more is discussed in this episode.</p> <p dir="ltr">Got feedback? Send it to us on Threads (<a href= "https://threads.net/@metatechpod">https://threads.net/@metatechpod</a>), Instagram (<a href= "https://instagram.com/metatechpod">https://instagram.com/metatechpod</a>) and don't forget to follow our host Pascal (<a href= "https://mastodon.social/@passy">https://mastodon.social/@passy</a>, <a href= "https://threads.net/@passy_">https://threads.net/@passy_</a>). Fancy working with us? Check out <a href= "https://www.metacareers.com/">https://www.metacareers.com/</a>.</p> <p dir="ltr"><strong>Links</strong></p> <ul> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Opaque Rust Crate - <a href= "https://crates.io/crates/opaque-ke">https://crates.io/crates/opaque-ke</a></p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Labyrinth 1.1: Making End-to-End Encrypted Backups Even More Reliable - <a href= "https://engineering.fb.com/2026/05/11/security/labyrinth-1-1-end-to-end-encrypted-e2ee-backups-more-reliable/"> https://engineering.fb.com/2026/05/11/security/labyrinth-1-1-end-to-end-encrypted-e2ee-backups-more-reliable/</a></p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">The Labyrinth Encrypted Message Storage Protocol - <a href= "https://engineering.fb.com/wp-content/uploads/2026/05/Minos-Updates-2026-Encrypted-Backups-White-Paper.pdf"> https://engineering.fb.com/wp-content/uploads/2026/05/Minos-Updates-2026-Encrypted-Backups-White-Paper.pdf</a></p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">MTP E54: Building Key Transparency at WhatsApp - <a href= "https://pca.st/orlsiq1l">https://pca.st/orlsiq1l</a></p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">MTP E65: Getting Ready for Post-Quantum Cryptography - <a href= "https://pca.st/4klppivy">https://pca.st/4klppivy</a></p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">NTS: Authenticated Time at Meta - <a href= "https://engineering.fb.com/2026/10/06/production-engineering/nts-authenticated-time-at-meta/"> https://engineering.fb.com/2026/10/06/production-engineering/nts-authenticated-time-at-meta/</a></p> </li> </ul> <p dir="ltr"><strong>Timestamps</strong></p> <ul> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Intro 0:06</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Introduction Emma 2:25</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">The Complexity of End-to-End Encryption 3:42</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">User Expectations and Mental Models 5:35</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">The Labyrinth System Overview 7:17</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Key Management and Security 7:54</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Rate-limiting passcode guesses 13:01</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Evolution of Backup Systems 16:45</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Rolling Out New Storage Protocols 17:07</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Ensuring Data Integrity During Migration 18:34</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Multi-Device Support Challenges 18:48</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Building Custom Cryptography Solutions 20:20</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Revoking Device Access 23:56</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Labyrinth 1.1: 26:24</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Preparing for Quantum Resistance 29:20</p> </li> <li dir="ltr" aria-level="1"> <p dir="ltr" role="presentation">Outro 36:16</p> </li> </ul> <p> </p> [Source](https://traffic.libsyn.com/secure/insidefacebookmobile/mtp88_rc2.mp3?dest-id=697376)

Source quotes

[0:00] Pascal: 任何人都能把一个文件加密到谁也打不开,包括他们自己,这算是一种成就;但真正棘手的是加密之后发生的事情。 [20:00] Emma Connor: And I know that there of course the classic warning of never roll your own crypto but somebody has to and when we do we do it with cryptographic experts we do it with input from external researchers and community feedback and transparency. [31:36] Emma Connor: You know, like... if a bad actor comes along, they don't even need to be storing the data today, we're already doing it. It's kind of just a decrypt later attack. [25:00] Emma Connor: If you revoke a device, that means the keys that exist on that device should no longer be able to decrypt any messages that are sent after that point. [33:38] Emma Connor: So we have the efficiency of a classical authentication and then the security of a post-quantum confidentiality. So the data is protected from quantum attacks even in the future.

Source quotes

[5:00] Emma Connor: Part of the problem we’re trying to solve is making that new mental model as small as possible compared to the existing mental model. [23:25] Emma Connor: If you revoke a device, that means the keys that exist on that device should no longer be able to decrypt any messages that are sent after that point.

Original transcript

[0:00] Hello and welcome to episode 88 of MetaTech Podcast, an interview podcast by Meta where we talk to engineers who work on our different technologies. My name is Pascal and I'm back from my month-long recharge leave, but now I have to deal with a particular corporate luxury problem. I didn't want to take any PTO in the run-up to me being out of the office for a month and continued accruing more while being off, so now I'm sitting on top of a mountain unused days with only a few months left to take it. Please take this as my pre-emptive apology for an even more sporadic podcast release schedule. Encryption is the part people assume is hard. It isn't, especially. Anyone can encrypt a file so well that nobody can ever read it again, themselves included. And that is an accomplishment. The awkward part is what comes after. And it gets considerably more awkward when you're retrofitting. WhatsApp was encrypted early enough in its life to set its own rules about how message history ought to work. Messenger wasn't. By the time internet encryption arrived, people had spent years building habits around it, signing in on a computer they had never used before, pulling a phone out of a drawer after 18 months and expecting to pick up where they left off. None of that was up for renegotiation. So the job became letting one particular person in on a device they've never touched, while deliberately keeping nothing that would help them do it. That's where most of this conversation ends up, and it produces some genuine odd build specifically to resist. meta. To talk me through it, I am joined by Emma. But before we get to the interview, a quick shout-out to the Meta Engineering blog, which published an article about NTS, Network Time Security. Meta's public time service now authenticates its packets so a device can verify that the time it receives genuinely came from us and wasn't tempted with along the way. It's a good reminder that NTP has been happily unauthenticated since 1985, despite being the and token lifetime is ultimately measured against. The servers keep no per client state, and the protocol server and client are all open source. You can find a link to the full article as ever in the show notes. And now, without further ado, here's my conversation with Emma. We've all grown accustomed to messages and emails effectively never going away. If you drop your they're still sitting somewhere in a data center waiting for you to log back in and retrieve them. That all becomes a whole lot more complicated when those messages are end-to-end encrypted. To discuss how we ensure those messages remain accessible, regardless of how irresponsibly you handle your electronic devices, I have Emma with me, a research scientist on encrypted infrastructure. Emma, welcome to MetaTech Podcast. Thanks. It's good to be here. Yeah, great to have you. talk a little bit about your journey here at Meta. How long have you been here and what did you do before? Sure. I just finished my fifth year here in August. So I joined back in 2021. Happy Metaversary then. Oh, thank you. I joined fresh out of grad school. I defended my PhD like the week before I moved out to Seattle to start my new job. Before that, I was, besides grad school, I worked for a few years part time at Cisco on their security initiatives group, where I was actually slightly different side of the security world. I was sort of on the penetration testing, exploit development research type of stuff. Amazing. We've had some episodes here before about our red and blue team efforts. So people can definitely refer to that if they want to learn more about this. But can you talk then a bit about what your team does, zooming out a little before we get into the topic? Yeah, our team handles encrypted infra for messaging use cases primarily. That means end-to-end encryption for Facebook Messenger, which I think is mainly what we're here to talk about today. but also use cases like federated analytics, which is the kind of more private way for us to, get analytics about how people are using the app while learning less information than we would if we just uploaded the metrics. Is that stuff like differential privacy or do we take a different approach to that? I'm not as familiar with that side of things on our team, but, I think we... use combination approach of differential privacy techniques and trusted execution environments. Very cool. Maybe that's a nice follow up conversation to have on a different episode then. But then let's talk about end to end backups. Can you just describe on a high level what you're trying to solve and why this is all so much harder than it looks on the surface? Yeah, we're trying to solve the exact problem that you described. Like you nailed it, right? It's like. You lose your phone, you drop your phone in the lake, you just get a new [5:00] maybe you need to sign in and you want to see all your messages that you've ever sent to anybody. You want them all to be there. And that was how Messenger was for years before we moved into end encryption. But when you add into encryption, that gets really complicated because meta can't have a way to access your messages. Right. That's the whole point of end to end encryption. So if we can't access your messages and you have a brand new phone that you just signed in on, then who is going to go and get your messages? It's kind of unclear, right? Yeah, for sure. I got into this a little bit in my intro, but I feel like the hard part is really the expectation management. You mentioned this too. Messenger wasn't always fully end-to-end encrypted. So people have already modeled in their head how all of this works. If you build a brand new end-to-end encrypted messenger, I guess you could just get away with saying like, nope, sorry, we don't have a backup. That's just not how it works here. But if you migrate everybody over, then you can't just suddenly tell people like, oh, yeah, by the way, all your messages are gone now. Right. And no matter what you do there going to be a new mental model that users have to learn But part of the problem we trying to solve is making that new mental model as small as possible compared to the existing mental model And yeah, you bring up a great point. Like a good example is WhatsApp, who started on end-to-end encryption much younger in their history. And they had a different model for encrypted backups. Very true. Since you've mentioned WhatsApp, that has been around for a while and also has had backups now for a while. So why did we actually need to come up with something brand new for Messenger? It's exactly the point that you made is that WhatsApp users already had a mental model for how their messages are stored. They know that there are ways to transfer messages between devices, right? Like if you upgrade devices, there are various ways to get your old messages from one device to the new one. But Messenger users don't have that expectation. They expect that they can log on to a computer at an internet cafe and log on for the first time and all of their messages are there. The WhatsApp history model is less optimized for the use cases of logging in on a brand new device and it more designed for a lower friction changing between devices like at a lower rate So it like if I get a brand new phone it much easier if I have the other one still around There's a transfer process. It takes quite a long time, but Messenger, if I remember it correctly, is still basically, you can log on on any web browser or phone or whatever you want, type in your passcode and boom, it's just there, basically more or less instantaneously. So that's a very different experience. Yeah. Great. So I love getting into these details because for Messenger, there have been some conspiracy theories going around because this whole effort took quite a while between the announcement of having end-to-end encrypted messages and actually rolling out to everybody. And this shows how much of that is just, my God, this is actually just really bloody hot. It has so many little intricacies that you only realize once you start talking about all these details behind it. One of these details is the name of the system that you've built, which is Labyrinth. And there's also white paper about it. But can we just quickly talk about the name? What is that about? Well, the name is, I think, credited to John Milliken. But as far as I know it designed around the idea that this is a hidden structure where it hard to even locate where the messages are which is sort of a historical factoid that no longer applies to the way the system is deployed today. But that was the original design. So that's where the name comes from. It is a good name. I think it's catchy. I do like it. me too so let's talk a bit about the cryptography behind it and the pardon the pun key question that you need to get into is really where do the keys ultimately live god you have no idea how many times i want to say something is like key or that's the key question and i have to stop myself and rephrase it but yes where do the keys live well there are a lot of different choices, but the common denominator is that not with meta, basically. During daily use, the keys live only on your device, but we have different ways to store backup keys in different locations so that if you lose your device or something happens, you can get back in. And there's a whole bunch of different ways to do that. That's part of the original design goals here is to make the system somewhat modular so that we can experiment with different recovery techniques and grow the system. [10:00] as it scales. Amazing. So what kind of options have you considered for this? Oh, we've got, I mean, we've considered everything. What options we deploy today, the main one is PIN. So that's where the user can remember a short six-digit numerical PIN. And that's all they have to remember to get access to their backups. we can go into the details of how that works more, but suffice to say that there are a whole lot of details around how we make that six digit pin hard to crack, even though, you know, there really just aren't that many possibilities in the six digit space. So we can go into that later. I would actually like to get at least like the brief overview of this, because like on the surface, it sounds really scary, like six digits, that's a very finite amount of combinations that somebody would need to brute force. So what do you just do to combat that particular attack vector of somebody brute forcing your pin? The basic idea is to limit how many times anybody can guess the pin And we enforce that limit using secure hardware We use these hardware security modules or HSMs and the long keys are encrypted in such a way that only the HSM can decrypt the keys to your backup. And even then, the HSM cannot decrypt those keys without your PIN. So the Overall flow is that the user sends a sort of cryptographic token derived from their PIN. The token is generated on their device. So the PIN never leaves your device. A cryptographic token generated from the PIN is sent to the HSM. The HSM sends back a response, and there's one or two more rounds. And as a result of this communication protocol, the device is able to reconstruct a key that unlocks the backup. This protocol is called Opaque. You can find it. It's actually open source. We published a Rust crate called Opaque. And yeah it has the wonderful properties of an eavesdropper cannot recover the pin by seeing this exchange without breaking another cryptographically hard problem And as a result of this exchange the device is able to reconstruct the key to the backup whereas the HSM never sees your PIN and it never sees the key to your backup either. Now, even all of that is sort of multiple layers of security because the key idea, pardon the pun again, behind HSM is that this is hardware that's tamper resistant. So even though the hardware lives in a metadata center, it's designed to be impossible for us to access anything that lives inside it. So I genuinely have no idea how these work. Is there a way of drawing some sort of analogy to how this kind of hardware-based rate limiting works? Should I think of this as some configurable inert property in how the hardware is built? Or is this like an algorithmic challenge, like a proof of work where it just takes a certain amount of time and effort to actually produce the token that or key that eventually unlocks your real key? It's more like the first. There's a really good analogy to the way, yeah, like an iPhone unlock works where you might use a short numerical code to unlock your iPhone But if you guess the wrong one too many times it locks you out And on the iPhone platform there actually this secure enclave that lives inside the phone. It's designed to resist, even if somebody with millions of dollars and a fancy laboratory disassembles your phone and tries to look at what's inside, it's designed to even then resist being able to pull any keys off the chip or bypass the rate limit that this chip enforces. And the HSMs that we use work in much the same way. So we deploy the code to the HSM when we are provisioning this system, but we actually destroy the cards that allow us to load code onto the HSMs after the first deployment. So we sort of write this rate limiting code, We deploy it once and we destroy the card so the code can never change again. That is super cool. And I love that we're talking about it because this feels like a lot of effort for something that most of the billions of users we have will actually never ever see or get in touch with, which I guess is also in large part by design, it should, of course, just work. But to move on to or zoom out a little bit again and talk about something more user facing, what is the real? [15:00] recovery story like if I forget my six-digit numerical pin what happens then Well, there's a few options. It depends on what you have available. One of the flows that we have for recovery is called the one-time code or OTC, which is if you have another device that's already logged in to your encrypted backup, you can... sign in on the new device, request a six-digit code, which will be displayed on your old device, and then you type it in to your new device. So this is sort of like a one-time pin. And that will work. even if you forget your old pin. Now, it does require that you have a device lying around somewhere that's already... on your backup. So it doesn't work for everybody. But if you do have a device lying around, that's a really good option. Is there again another shorthand for explaining how you actually work create a pin behind the scenes that allows for this particular run lock? Yeah, again, this is similar. It uses a cryptographic protocol called a PAKE, P-A-K-E, or Password Authenticated Key Exchange. I think Labyrinth is a better codename than this, but yeah, let's go with it. That's fair. So the... The one-time code is actually not, under the hood, it actually doesn't work like a pin in the sense that it's granting access to anything. It's actually just used to authenticate the secure channel. So, A lot of the listeners are probably already familiar with like a Diffie-Hellman key exchange or... like RSA public key exchange or maybe, you know, you know, uh, ML Kim, but, uh, The point is, you know, there's already well-established ways for two endpoints to establish a secure channel with each other using the, Freshly generated. secure cryptographic keys. Yeah. So the two devices basically do a key exchange like this. And the challenge at that point is how you know that you've exchanged keys with the right device. Right. Because anybody can generate a Diffie-Hellman key or whatever and complete the key exchange with you. And to know that the two devices are actually talking to each other, we have a six digit code, which is generated using some hash functions over various parts of the exchange. Right. Including the keys that are used to do the key exchange, but also some other challenges that sort of make it so that both devices have to commit to a specific key before they can know. what the pin is going to be. So, The end result of all that is you have this secure channel. and you have a six digit pin that's generated as a hash of all the parameters that are used to set up the secure channel. And the first device shows you the code and the new device, the user has to input the code. And actually behind the scenes, the new device already knows what the code should be because it's also completed this secure channel key exchange. So all the new device is doing is verifying that the code you put in matches what it expects. And that's how you know that a real person in the real world has looked at both of these phones. And verified that they're actually talking to each other and not somebody else in the middle. That's fascinating. I can't even imagine what the state machine for this must look like. It sounds... incredibly complex, all with the goal of making it, again, as easy as possible for the user, which is a really cool outcome of this. Yeah, and there's even more... subtleties and nuance like you have to have ways to rate limit this exchange too. Again, we have to do the rate limiting from the device because end-to-end encryption should work even if the server is misbehaving. So... We have to... rate limit this so that somebody can't try and guess a six digit code over and over. And we have to enforce all this stuff on the client side, which has even more complexity. One additional wrinkle that comes into all of this, as we are just talking about just kind of spiraling complexity, Messenger has also a fairly wide ranging support for multiple devices. I think on WhatsApp, there's like a fixed limit that was also gradually added of having four devices that you can sync between. But I'm not even sure what the limit on Messenger is because I've never hit it. You can just, as I've said before, open it in a new tab, log in, and then it's there. Yeah. How does that complicate the whole story of... encrypting and backing up these encrypted messages. Well, I want to say the limit is something like 200. But yeah, it's – and part of the reason we have this higher limit is because – We really want to support... a use case like where someone maybe signs into a phone and it sits in a drawer for a a year or two, before they use it again. Right. Because it's a really big deal if people lose access to their whole messaging history. So we want these sign ins to be long term. I've literally had that the other day. I pulled a phone out of the drawer. turned it back on and I got a notification from Messenger to say, oh, that's nice, because most other apps will just kick you out after some time of absence. That's all. [20:00] I'm glad to hear that works because I've never done that myself. But yeah, I mean, there are challenges involved in that. Like for systems that have a smaller number of devices, it's fairly simple to use the devices themselves as the endpoints for messaging. Right. You may have a thread with two or three people, four devices each. maybe that's like 12 to 20 devices. If you have people with like 100 devices, and for the record, some people do, it's a lot more difficult. You can easily have hundreds or thousands of devices in the conversation. So that makes it much more difficult to use certain off-the-shelf protocols, like Signal, things like that. And that's part of the reason that the Labyrinth 1.1 update that we just sold out uses a single mailbox level public key for a user. So when two users are talking to each other in a thread, the encryption is only going between the two user level public keys So the hope is that this would allow us to scale to much larger groups for people with more devices I don remember it And I don want to test my combinatorial maths here But I think on WhatsApp there are key pairs for each user pair in a group chat or something like this. And that grows really quickly. Yeah, it's a bit complex, right? Like there's some threads use cinder keys, which does have like order of in keys for order of in users. But the true signal protocol has like order of in squared, I think. So, you know, 100 in points, you're talking like 10,000 connections. Right. Yeah, that's fascinating. It's like interview level stuff. But in the real world, these decisions really matter and you need to make them early on. And they, as you say, impact what you can actually use. Now, suddenly you find yourself in a situation where you kind of need to build your own crypto in a sense, I guess, if there's nothing kind of off the shelf available. Well, not the primitives, but at least the packaging of it. Right. So that's what we did. And I know that there of course the classic warning of never roll your own crypto but somebody has to And when we do we do it with cryptographic experts We do it with input from external researchers and community feedback and transparency Actually, can we quickly get into this? Because Meta is a big company. And one of the aspects that makes this fun is that you do have access to so many experts who you can literally just jump into that chat inbox and just ask like, hey, does this make sense? So how did that whole process go for you if you built something that was never built before with different tradeoffs than existing protocols? I mean, we have group threads in the company that's just like E2E experts, people who worked on WhatsApp end-to-end encryption, people who worked with the EFF. I've had the chance to work with even Moxie Marlinspike, not on this, but on some other things more recently. You know, we have people like Shai Garron who published some of the internet standards for widely used encryption algorithms. I worked a lot with Kevin Louis We have a lot of expertise at the company for people who have worked on serious cryptographic systems And we also consult with people in the privacy security community to external folks who don't work in the company. And once again, I would like us to talk about something user-facing and then figure out how deep you want to go on the technical explanation behind it. But one of the consequences of having this elaborate multi-device support is also that you have more devices that you can potentially lose or they get into the wrong hands. And you obviously then want to have a way of revoking access to them. So what guarantees do you actually offer for this revocation and how does it work? Yeah, so how does it work? Well, I guess I'll start with the guarantees. That's a little bit simpler. If you revoke a device, that means the keys that exist on that device should no longer be able to decrypt any messages that are sent after that point. So we don't guarantee that the device cannot decrypt any old messages. We don't go back in time and re-encrypt the things that were uploaded earlier. We guarantee that any new messages that are sent can't be decrypted by the keys that live [25:00] on a device. And that should apply even if the device... falls into the wrong hands, even if the device ends up in the hands of somebody who also is a meta insider and has access to our data centers. they still shouldn't be able to decrypt your... new messages that are sent after the time that device is revoked. How does that work? is... one device is going to generate a new key. we call these epoch keys, right? So an epoch is like a, period of time that defines which devices are allowed to have access to messages at that moment. So one of your devices, a device that isn't revoked. generates a new epoch key. And it shares... this Not the key itself. but the entropy that goes into deriving the key, It shares that entropy. with the other devices on the backup. the ones that are not revoked. And, you know, So the one that is revoked doesn't get to see that entropy. and it can't derive the new key. And the way this entropy is shared is... Again, using... itself is using end-to-end encryption. So... The entropy is encrypted. specifically to some public-private key pairs of the other devices in the backup. So Labyrinth has gone through a few version histories as well. And the most recent one, at least in terms of what you've published, was 1.1. What new features do you actually add to something like a backup protocol? So Well, the biggest... upgrade in the Labyrinth 1.1. was... we kind of changed how the backup model works. So, In Labyrinth 1.0... Only. your own devices, are uploading backups to your mailbox. So if I send you a message, The message is encrypted. to your device It's downloaded on your device. Your device decrypts it. than your device. re-encrypts it again in a different form the form that's compatible with encrypted backups with lab friends. and your device uploads... The backup to Labyrinth. Thank you. That's, Labyrinth 1.0. And it works. But it has some downsides. Um, If I send a message and your phone isn't online at the time, then that message isn't backed up. And if you go... Suppose you go to like an internet cafe and you log on to a web browser there. before your phone even comes online, There's no way for that message to show up there. So, The new backup model in Labyrinth 1.1 allows Cinder is to encrypt the message to a public key and put the message in your backup on the server, without your device needing to download it. That definitely sounds like there is some more involved cryptography required because the other one I can just model in my head, you have like, access to your backup, it's on your device, you have your keys available there. So I encrypted send it off to the server. But now, you don't have my key because you never talk to my device. So what kind of shenanigans are you using there to make that work? Yeah, it's kind of like the difference between symmetric cryptography and asymmetric cryptography, right? So labyrinth. 1.0 is... Mostly a symmetric cryptography model. right the the keys that encrypt messages are symmetric keys. They live on your devices. Now your devices share those with each other using public key cryptography. But... For a particular backup, the data is all being encrypted with symmetric keys. Labyrinth 1.1, we evolved and started using public geography. And yeah, like in many ways, it's a natural evolution, but... There are additional challenges in that. Now we have to have ways for users to know... who they're talking to and verify that the public key they're using to encrypt messages for the recipients, are authentic. So. For that, we have to add flows in the user interface where people can... go into secure thread settings and, actually look at the public keys of the people they're messaging. So maybe, you know, You could meet up in person with someone you're messaging and you can validate that they're the same and you know that you're actually talking to each other. But you know, that's pretty high friction for users. So we try to do better than that. And We use key transparency. Where... [30:00] Right. So, users upload their public keys to a shared directory. And this directory is stored in a cryptographic, a pinned only ledger. So once a piece of data is added to this ledger, Eight. cannot be removed. And... The ledger cannot be tampered with without detection. And we send this ledger every, I think it's like every 10 minutes or something. we send a copy of the hash of the ledger, to external parties. I think Cloudflare. mainly. And we get them to sign it. So... We have a signature from an outside party. attesting to the fact that this is the one directory this directory is a source of truth It's impossible to have duplicate entries in the directory. and it's impossible to tamper with the history of the directory. So, Users get this signature from a party who isn't meta. They verify that the signature covers the directory and they verify that the key of who I'm talking to is... actually matches the entry that lives in the directory. There's so much cool stuff in cryptography. I could talk about this for ages. And we've also had an entire episode about key transparency in WhatsApp, which I will leave a link to somewhere in the show notes. But just to pull another thread to a crypto-related episode we've had here – Quantum... resistance. How are you thinking about being resilient to quantum cryptography attacks that may or may not exist in the future? Is that part of your encryption scheme? Yeah, definitely. When we're thinking about quantum computer attacks today, we're mostly thinking about future attacks, right? I think So there's technically an outside chance that somebody already has a working quantum computer that nobody knows about, but probably not. We're mostly worried that somebody may come along in 10 or 20 years and... throw their quantum computer at some data that they've stored away for later. Yeah. So, Right. We call these store now decrypt leader attacks. When it comes to labyrinths, We are especially... concerned about the possibility of a storm down decrypt later because they we're doing all the storage for you. That's kind of the point, right? So we're doing all of the storage now. You know, like... if a bad actor comes along, they don't even need to be storing the data Today, we're already doing it. It's kind of just a decrypt leader attack. Um, So, The way we deal with this is, In Labyrinth 1.0, it was actually fairly straightforward. because symmetric key cryptography by and large is... pretty straightforwardly resistant to quantum attacks. Mm-hmm. And even though there was asymmetric cryptography involved in sharing like entropy between devices, because we're only sharing entropy, we're not sharing the keys. It turns out that Even with the quantum computer, there are limits to what can be broken in Labyrinth 1.0. Even if you're not using a quantum secure public key cryptography. But In Labyrinth 1.1, that's not the case anymore. That sort of like cheap and easy post-quantum story. Yeah, it no longer applies. It got harder. So, yeah. For Labyrinth 1.1, we did migrate to fully post-quantum secure asymmetric systems, cryptography, That was part of the Labyrinth 1.1 from... the very first design document. So If I encrypt something to your public key, we're using a hybrid crypto system that we developed called Hawk. which is kind of – kind of unique in the sense that it provides security, like in the sense of confidentiality against an attacker, you who can break Either... Diffie Hellman, Or... ML Kim. But not both, of course. and it provides authenticity, against an attacker who, um, Cannot break Diffie Hellman. So, yeah. What that means is basically we have classical authentication, right? and post-quantum confidentiality. And And that's a really nice hybrid to have because authentication is generally is not... vulnerable to store now decrypt later style attacks. Yeah. Because all that really matters is that we can authenticate who sent this for now. Right. And. Maybe if there's quantum computers at some point in the future, maybe we have to migrate authentication systems later. Generally, no one's going to be storing all these things and then [35:00] you know, impersonating them later. There's a lot less to gain from that. So we have the efficiency of a classical authentication and then the security of a post-quantum confidentiality. So the data is protected from quantum attacks even in the future. Amazing. Well, yeah, it would have been fairly anticlimactic if you spend so much time thinking about how to keep things secure at rest. And then somebody just comes along with a quantum computer and ruins it all for you. Yeah, that's a serious concern when we're storing so much messaging data when, you know, our goal is to store it long term, right? We want it to be available 10, 20 years from now for users. Exactly. And there are so many more little cases that we could probably go into that make all of this much more complicated than what we've already explored. But sadly, we're out of time. So at this point, Emma, I can only thank you for making sure that my mind is at ease as I'm kayaking out on a lake with a phone loosely somewhere in my pocket. And of course, for joining me here on MetaTech Podcast. Thanks it was great to be here And that was my conversation with Emma The data I keep turning over is the hardware security modules The team wrote the rate code deployed it once and then destroyed the cards that would let anyone load new code onto those machines. A deliberate, irreversible act of locking yourself out, carried out so that nobody at Meta can ever quietly change the rules later. There's something quite admirable about engineering, but the goal is to reduce your own power. I also appreciated Emma's take on the old warning to never roll your own crypto. That somebody somewhere eventually has to, and the honest answer is to do it with experts, invite outside scrutiny, and publish the thing. The white paper and the open source opaque crate are linked in the show notes, along with our earlier episodes on key transparency and post-quantum cryptography, if this triggers your appetite. If you're enjoying MetaThick Podcast, a rating on Apple Podcasts or Spotify would be much appreciated. We are at MetaTechPod on Instagram and threads or techpodcast at meta.com, but please don't make me validate your GPG signature. That's all for another episode of MetaTechPodcast. Until next time, teedle.