WebRTC Security

Encryption is mandatory in WebRTC. There is no plaintext mode. Every connection encrypts and authenticates its media and data before any payload crosses the wire. HTTP has http:// and https://; WebSockets have ws:// and wss://. WebRTC has only the secure form.

This article explains what that encryption protects, how the keys get established directly between peers, and the one thing it does not protect: trust in whoever delivered the connection details. The guarantees are strong and worth understanding precisely, because the failure mode is not weak crypto: it is misplaced trust in the channel that introduced the peers.

1. What encryption protects

A WebRTC session carries two kinds of payload, and each has its own cipher. Both ciphers draw their keys from a single handshake that runs directly between the two endpoints.

Layer Protocol What it encrypts What authenticates it Key source
Media SRTP / SRTCP Audio and video RTP packets, plus RTCP control DTLS-SRTP key export, tied to the DTLS handshake Derived from the DTLS master secret
Data SCTP over DTLS The data channel: game state, chat, files The DTLS session record layer The DTLS session itself
Transport identity DTLS certificate The handshake that produces all keys SHA-256 fingerprint carried in the SDP Self-signed cert generated per session
Path consent STUN (ICE) Nothing: it authorizes a destination Short-term ICE credentials (ufrag / pwd) Random per-session, exchanged in SDP

Read the table top to bottom and the architecture falls out. DTLS is the root: it runs once, directly between peers, and everything else hangs off it. The data channel rides inside the DTLS session. The media keys are exported from the DTLS master secret. The DTLS certificate is authenticated by a fingerprint that travelled in the SDP. ICE, at the bottom, does not encrypt anything: it decides which network address is allowed to receive traffic at all.

No server holds any of these keys. No server sits on the media or data path. The encryption is end-to-end in the strict sense: only the two endpoints can read the traffic. A signaling server moves SDP and ICE candidates between the peers to set the connection up, and then has no further role and no access to the payload.

Signaling server no lock, sees no payload SDP only: fingerprint, ICE ufrag/pwd, candidates Peer A Peer B ENCRYPTED PIPE SRTP (media) SCTP / DTLS (data) DTLS master secret

2. In depth

2.1 The DTLS handshake, step by step

DTLS is TLS adapted for datagram transport. It keeps TLS's cryptography and adds the machinery to survive UDP: packet loss, reordering, and the absence of a connection. WebRTC uses DTLS 1.2 or DTLS 1.3, negotiated during the handshake.

The handshake establishes a shared secret between the two peers without either trusting a certificate authority. Walk it in order.

Before the handshake: certificate generation. When a peer constructs an RTCPeerConnection, the browser generates a self-signed certificate. It is unique to the session unless you explicitly reuse one. The private key never leaves the browser process and is never written to the SDP. Only a hash of the public certificate (the fingerprint) is exposed.

Step 0: roles via a=setup. DTLS is client-server at the protocol level: one side sends ClientHello, the other responds. WebRTC is peer-to-peer, so the peers must agree on who plays client. They negotiate this in the SDP with the a=setup attribute:

  • a=setup:actpass: "I will be either; you choose." The offerer almost always sends this.
  • a=setup:active: "I will be the DTLS client and send ClientHello."
  • a=setup:passive: "I will be the DTLS server and wait."

The answerer reads the offerer's actpass and picks a concrete role, usually active, then the offerer becomes passive. The DTLS client role is independent of the ICE controlling role and independent of who created the offer. By the time ICE finds a path, both sides already know who sends ClientHello.

a=setup:actpass    # offer: offerer is flexible
a=setup:active     # answer: answerer will be DTLS client

Step 1: ClientHello. The active peer sends ClientHello over the ICE-selected path. It lists supported DTLS versions, cipher suites, and a random nonce. In DTLS, this exchange also runs a stateless cookie round-trip (HelloVerifyRequest) to blunt amplification attacks, since the handshake rides on spoofable UDP.

Step 2: ServerHello and certificate. The passive peer replies with ServerHello (its chosen cipher suite and its own random nonce), then its Certificate: the actual self-signed certificate, not the fingerprint. With ECDHE cipher suites it also sends its key-share parameters. DTLS 1.3 compresses these into fewer flights, but the information exchanged is the same.

Step 3: client certificate and key exchange. The active peer sends its own Certificate, its key-share, and the messages that finalize the ephemeral Diffie-Hellman exchange. Both peers now hold the inputs to compute the same shared secret.

Step 4: key derivation. Each side runs the agreed key-derivation function over the ECDHE shared secret and the two random nonces to produce the master secret, then expands that into the working keys: encryption keys, MAC keys, and IVs for each direction. The keys are ephemeral: derived from the per-handshake ECDHE exchange, not from the long-term certificate. This is what gives the session forward secrecy.

Step 5: Finished and verification. Each peer sends a Finished message containing a MAC over the entire handshake transcript. If the transcripts match, neither side's messages were altered in flight. The DTLS session is now established and the record layer is live.

Step 6: fingerprint check. This is the security-critical step, and it is easy to overlook because it happens implicitly. Each peer hashes the certificate it received in steps 2/3 and compares that hash to the a=fingerprint value it read from the remote SDP. A mismatch aborts the connection immediately. The certificate is self-signed and proves nothing on its own: anyone can mint one. Trust comes entirely from this comparison: the live certificate must match the fingerprint that arrived through signaling.

Active peerDTLS client Passive peerDTLS server ClientHello HelloVerifyRequest (cookie) ClientHello + cookie ServerHello, Certificate, ServerKeyExchange, ServerHelloDone Certificate, ClientKeyExchange, Finished Finished Each side hashes the received Certificate and compares it to a=fingerprint from the SDP. Mismatch = abort.

DTLS 1.3 differences. DTLS 1.3 keeps the same trust model but reshapes the wire exchange. It removes the static-RSA and other non-forward-secret key exchanges, so every session is forward-secret by construction. It merges flights, so the handshake completes in fewer round trips: the certificate and key-share arrive together, and the separate ServerKeyExchange/ServerHelloDone messages of 1.2 disappear. It encrypts more of the handshake itself, including the certificates, so an on-path observer learns less about the peers. The negotiated version appears nowhere in the SDP; the peers settle it in ClientHello/ServerHello. From the application's point of view nothing changes: you still set a local description, exchange SDP, and read the same a=fingerprint line. The fingerprint check in step 6 is identical.

Certificate lifetime and rotation. A generated certificate has an expiry. By default the browser picks a short validity window and rotates automatically: the certificate is per-RTCPeerConnection and effectively per-session. You can pin one with generateCertificate() and reuse it across connections, which keeps the fingerprint stable; that is useful only if you have an out-of-band way to publish and verify that stable fingerprint, since a constant fingerprint is itself a tracking identifier. For most peer-to-peer use, let the browser generate a fresh certificate per session and rely on the signaling channel to deliver the matching fingerprint.

2.2 How the SDP fingerprint binds signaling identity to the media path

The fingerprint is one line in the SDP:

a=fingerprint:sha-256 8C:2F:1A:9E:...:4D
a=setup:actpass

It is a SHA-256 hash of the peer's certificate. It is small, it is deterministic, and it is the entire basis of peer authentication in WebRTC. Trace the binding:

  1. The signaling channel delivers the SDP, and therefore the fingerprint, from one peer to the other.
  2. The DTLS handshake delivers the actual certificate, directly, over the media path.
  3. Step 6 above checks that the certificate hashes to the fingerprint.

So the fingerprint is a commitment made on the signaling channel, and the DTLS handshake is the proof that the peer on the media path is the one who made that commitment. This is how a channel that carries no media nonetheless authenticates the media path. If the fingerprint you received belongs to your intended peer, then the only party who can complete the DTLS handshake to your satisfaction is that peer, the one holding the matching private key.

The whole guarantee reduces to a single question: did you receive the right fingerprint?

2.3 The tampered-SDP MITM: concrete attack and defense

The DTLS handshake authenticates the peer link against the fingerprint in the SDP. WebRTC defines no security for the channel that carries the SDP. If an attacker controls that channel, the authentication still "succeeds", against the wrong fingerprint.

The attack. Alice and Bob connect through a signaling server. Mallory controls or sits in front of that server.

  1. Alice creates an offer. Her SDP contains her fingerprint F_alice and her ICE candidates.
  2. The offer passes through Mallory. Mallory rewrites the SDP: she replaces F_alice with her own F_mallory_a, and replaces Alice's candidates with addresses Mallory controls.
  3. Bob receives the doctored offer. He creates an answer with his fingerprint F_bob. Mallory intercepts it, swaps in F_mallory_b, and substitutes her own candidates.
  4. Alice completes a DTLS handshake, with Mallory. The certificate Mallory presents hashes to F_mallory_a, which is exactly what Alice's SDP told her to expect. Verification passes.
  5. Bob completes a DTLS handshake, also with Mallory. Same outcome.
  6. Mallory now holds two fully valid, encrypted, authenticated sessions. She decrypts everything from Alice, reads or alters it, re-encrypts it for Bob, and relays. Both endpoints see a green, authenticated WebRTC connection.

Nothing in WebRTC detects this. Each handshake is cryptographically perfect. The encryption did its job: it encrypted the link to the peer it authenticated. The peer it authenticated was Mallory.

The defense. The fingerprint must reach the far peer with integrity. Options, strongest first:

  • Authenticate and encrypt the signaling channel. Serve signaling over HTTPS/WSS, authenticate the participants, and ensure the server cannot be silently MITM'd. If Mallory cannot rewrite the SDP, she cannot swap the fingerprint, and the original attack collapses. This is the baseline and it is non-negotiable for any real deployment.
  • Verify the fingerprint out of band. Compare a short string derived from both fingerprints over a channel the attacker does not control: read it aloud on an existing trusted call, scan a QR code in person, or confirm through a separate authenticated app. If both peers agree on the fingerprints, no relay is possible.
  • Bind identity cryptographically. An identity provider can assert "this fingerprint belongs to this user" with a signature the peer verifies. This moves trust from the signaling server to the identity provider, which is only an improvement if the identity provider is more trustworthy.

The takeaway: WebRTC secures the link completely and trusts whoever introduced the peers. Securing the signaling channel is not optional hardening: it is the precondition for every other guarantee in this article.

Two valid DTLS sessions. F_mallory_a to Alice, F_mallory_b to Bob. Both fingerprints were swapped in the SDP. Alice Mallory Bob DTLS DTLS Both peers see an authenticated connection, to Mallory.

2.4 SRTP: key derivation and what it protects

Media tracks do not travel as raw RTP. They travel as SRTP (Secure RTP). SRTP encrypts each packet's payload and authenticates the packet, including its header, so an attacker can neither read the media nor forge or replay packets undetected. RTCP (the control and statistics channel) is protected in the same way as SRTCP.

The keys come from the DTLS handshake through the DTLS-SRTP profile:

  1. The peers negotiate an SRTP protection profile during the DTLS handshake (an extension lists the supported profiles, such as AES-128 in GCM or counter mode with an HMAC-SHA1 authentication tag).
  2. After the handshake, both sides run the TLS key-export function over the DTLS master secret to produce SRTP keying material: encryption keys, salts, and authentication keys, one set per direction.
  3. The media engine takes that keying material and encrypts outgoing RTP, decrypts incoming RTP.

No SRTP key ever appears in the SDP, and there is no separate key-exchange round trip on the media path. The single DTLS handshake authenticates the peer and seeds the media cipher in one operation.

SRTP encrypts per packet with low overhead, which matters because media is high-volume and latency-sensitive. A dropped or reordered packet decrypts independently of its neighbors: there is no stream state to lose. The cipher derives a per-packet keystream from the master key, the packet's SSRC, and a sequence index, so reordering does not corrupt later packets.

What SRTP authenticates, and replay protection. SRTP appends an authentication tag computed over the encrypted payload and the RTP header. The receiver recomputes the tag and drops any packet that fails, so an attacker cannot forge a packet, flip bits in transit, or rewrite the header (marker bit, timestamp, sequence number) without detection. The header stays in the clear for routing, but it is covered by the tag, so it is integrity-protected even though it is not confidential. On top of that, the receiver maintains a sliding replay window keyed on the sequence index and rejects any packet whose index it has already seen. A captured packet replayed later is discarded. GCM profiles fold encryption and authentication into a single AEAD operation; the older profile pairs AES-CTR encryption with a separate HMAC-SHA1 tag. Either way, confidentiality and integrity both hold.

Rekeying and ICE restart. The SRTP keys live as long as the DTLS session. If the network path changes (a peer moves networks, or connectivity is lost and recovered), ICE can restart and renegotiate candidates. An ICE restart by itself does not rekey DTLS; the existing DTLS session and its exported SRTP keys continue over the new path. A full renegotiation that replaces the certificate triggers a new DTLS handshake and therefore new SRTP keys. The fingerprint check runs again on any new handshake, so the binding from §2.2 is re-established every time keys are renewed. There is no window where media flows under keys that were never authenticated against a fingerprint.

2.5 The encrypted SCTP data channel

The data channel runs SCTP over DTLS. SCTP provides the channel semantics: multiple independent streams, ordered or unordered delivery, fully reliable or partially reliable. DTLS provides the encryption: every SCTP packet is a payload inside the established DTLS session's record layer.

There is no second handshake and no separate key for data. The data channel uses the same DTLS session that authenticated the peer. Every message you send through RTCDataChannel.send() is encrypted by that session and authenticated against the same certificate the fingerprint committed to.

This is the path that peer-to-peer applications rely on. Gameplay traffic (moves, state snapshots, chat, file chunks) rides the encrypted SCTP-over-DTLS channel and never touches a server. The signaling code in src/salon/ only moves SDP and candidates around to bring the channel up; once RTCDataChannel.readyState reaches open, the server has no role and no access to the contents.

Why DTLS wraps SCTP rather than the reverse. SCTP gives the channel its reliability and ordering semantics, but SCTP has no encryption of its own. Putting SCTP inside the DTLS record layer means every byte of channel data inherits the confidentiality, integrity, and replay protection of the established session, and a single handshake covers both media and data. The data channel and the media tracks share one RTCDtlsTransport and one certificate per peer; you are not running two separate trust relationships. A message you push through send() is fragmented by SCTP, sealed by DTLS, and reassembled on the far side inside the same authenticated session. The server that delivered the SDP cannot read it, replay it, or alter it.

The reliability mode you choose does not change the security. An unreliable, unordered channel for fast game state and a reliable, ordered channel for chat are both encrypted by the same DTLS session. Partial reliability trades retransmission for latency; it does not trade away confidentiality or integrity.

2.6 Consent freshness: STUN keepalives

Encryption stops eavesdropping. It does not, on its own, stop a peer from being aimed at a third party. Without a check, WebRTC could be coerced into flooding an arbitrary IP with packets, a denial-of-service amplifier driven by a malicious page.

ICE prevents this with consent freshness, defined in the ICE specification.

  • Before sending, a peer must complete a STUN connectivity check to the remote address. The check is a STUN binding request authenticated with the short-term ICE credentials (ice-ufrag / ice-pwd) that were exchanged in the SDP. A successful response proves the remote endpoint exists, is reachable, and holds the matching ICE password: it actually wants this traffic.
  • During the session, the peer keeps proving consent. It sends periodic STUN binding requests as keepalives, roughly every few seconds, and expects responses. The standard requires renewing consent at least every 30 seconds.
  • If consent lapses (the remote stops responding within the timeout), the sender stops transmitting on that path.

The attack this stops: a page convinces a browser to send a high-bandwidth media stream toward a victim's IP. Without consent freshness, the browser would happily fire packets at any address in the candidate list. With it, the browser first demands a STUN response carrying the correct ICE password. A victim who never agreed to the session cannot produce that response, so no traffic flows. The ICE credentials, exchanged only in the authenticated SDP, are what make the consent unforgeable.

The credentials also defend the connectivity checks themselves. Each STUN binding request carries a message-integrity attribute computed with the remote ice-pwd, and each response carries one computed with the local ice-pwd. An off-path attacker who guesses or spoofs an address cannot forge a valid check without the password, and the password only ever appeared in the SDP. So an attacker who cannot read the signaling channel cannot inject a candidate that the peer will accept, cannot answer a connectivity check on a victim's behalf, and cannot keep a coerced flow alive past the next consent interval. This is another place where signaling-channel secrecy underwrites a runtime guarantee: leak the ice-pwd and the consent mechanism weakens.

Sender Remote peer STUN binding request response STUN binding request response ~5s: MESSAGE-INTEGRITY (ice-pwd) STUN binding request no response consent timeout (>30s) → sender stops Media flows only while the remote keeps answering authenticated checks.

2.7 The permission and secure-context model

Capturing a camera, microphone, or screen requires explicit user consent, enforced by the browser, not the application.

  • getUserMedia() prompts for camera and microphone. The prompt is per-origin. The grant is revocable at any time, and the browser shows an active-capture indicator while a track is live. The page requests constraints (resolution, frame rate, device), but the browser, not the page, decides whether to prompt, reuse a prior grant, or deny. A page cannot enumerate device labels until a grant exists, which blocks fingerprinting the device list before consent.
  • getDisplayMedia() prompts for screen, window, or tab sharing. The browser always presents the picker; a page cannot pre-select or silently choose what to capture, and it cannot persist this grant: every call shows the picker again. The user decides exactly what surface is shared, and the captured surface is marked so the user can see and revoke it. A page asking for the screen cannot quietly receive the camera instead; the surface type is what the user selected.

Both APIs require a secure context. The page must be served over HTTPS, or be localhost for development. On plain HTTP the capture APIs are simply absent: navigator.mediaDevices is undefined. This requirement is independent of the DTLS/SRTP encryption WebRTC applies to the connection: it protects the page origin that requests the capture. A compromised or impersonated origin would otherwise be able to request the camera under a trusted name.

// Throws if not a secure context, or if the user denies the prompt.
const stream = await navigator.mediaDevices.getUserMedia({
  audio: true,
  video: true,
});

// Always shows the OS/browser picker; the page cannot choose the surface.
const screen = await navigator.mediaDevices.getDisplayMedia({ video: true });

The practical consequence: a real WebRTC deployment needs HTTPS for the page itself, on top of the encryption WebRTC handles internally. Two separate requirements, both mandatory.

2.8 IP-address privacy and the mDNS candidate mitigation

WebRTC has a privacy cost that encryption does not address: ICE candidates contain IP addresses. To find a path, ICE gathers candidates of several types:

  • Host candidates: your local interface addresses, such as 192.168.1.42.
  • Server-reflexive candidates: your public address as seen by a STUN server.
  • Relayed candidates: a TURN relay's address (not used here; this project is STUN-only).

These candidates go into the SDP and travel to the other peer, and to anything observing the signaling channel. That can reveal more than intended. A page can read host candidates to map your local subnet, count your network interfaces, or fingerprint your setup. Server-reflexive candidates expose your public IP, in some configurations even when a VPN is active, because the local interface may still be enumerated.

The mDNS mitigation addresses the host-candidate leak specifically. Instead of publishing a raw private address, the browser:

  1. Generates a random hostname ending in .local, such as 9b2c4f1a-...-e1.local.
  2. Registers that name on the local network over multicast DNS, mapping it to the real private IP.
  3. Puts the .local name in the candidate instead of the IP.
# Without mDNS:
a=candidate:1 1 udp 2122260223 192.168.1.42 51000 typ host
# With mDNS:
a=candidate:1 1 udp 2122260223 9b2c4f1a-...-e1.local 51000 typ host

When the remote peer receives the .local candidate, it resolves the name over multicast DNS on its own local segment during the connectivity check. If the peers share a network, resolution succeeds and the direct path works. The website's JavaScript, however, only ever sees the opaque .local string: it never learns your real internal IP. The private address stays on the local link where it belongs.

The public IP is harder to hide. STUN exists specifically to discover it, and the remote peer needs a reachable public address to connect. mDNS does not mask server-reflexive candidates. Reducing public-IP exposure means restricting candidate gathering at the application or policy level (for example, browser policies that limit candidate types), which trades connectivity for privacy. See ICE and NAT traversal for how candidates are gathered and why they carry these addresses.

2.9 What STUN-only means for privacy

This project uses public STUN servers and no TURN relay. STUN only tells a peer its own public address; it never carries media or data. That keeps the privacy surface small: the STUN server learns that you queried it from some IP, but it never sees your traffic, because there is no traffic for it to see: the payload path is strictly peer-to-peer over the encrypted channel.

A TURN relay, by contrast, forwards the actual encrypted media and data when a direct path cannot be found. DTLS still protects the payload end-to-end, so the relay cannot read it. But the relay does observe traffic timing, volume, packet sizes, and both endpoints' relayed addresses, metadata that a STUN-only setup never exposes to any server. Staying STUN-only removes that observer entirely. The cost is connectivity: on the most restrictive symmetric-NAT and firewall configurations, a direct path may not exist, and without a relay the connection fails. That tradeoff is deliberate here: peer-to-peer or nothing.

2.10 Inspecting the security state in code

You can read the local certificate and its fingerprint, and watch the DTLS transport come up.

// Generate and reuse a certificate explicitly (optional).
const cert = await RTCPeerConnection.generateCertificate({
  name: 'ECDSA',
  namedCurve: 'P-256',
});
const pc = new RTCPeerConnection({ certificates: [cert] });

// The fingerprint that will land in the SDP.
await pc.setLocalDescription(await pc.createOffer());
const fingerprintLines = pc.localDescription.sdp
  .split('\r\n')
  .filter((l) => l.startsWith('a=fingerprint'));
// a=fingerprint:sha-256 8C:2F:1A:...:4D

// The negotiated DTLS role, from the SDP.
const setupLine = pc.localDescription.sdp
  .split('\r\n')
  .find((l) => l.startsWith('a=setup'));
// a=setup:actpass
// Watch the DTLS transport state on the underlying sender transport.
const sender = pc.getSenders()[0];
const dtls = sender?.transport;        // RTCDtlsTransport
if (dtls) {
  dtls.addEventListener('statechange', () => {
    console.log('DTLS state:', dtls.state); // new -> connecting -> connected
  });
  // The remote certificate(s) actually presented in the handshake.
  // Compare against the remote SDP fingerprint to confirm the binding.
  const remoteCerts = dtls.getRemoteCertificates(); // ArrayBuffer[]
}

The RTCDtlsTransport.state transitions from new to connecting to connected, then closed or failed. getRemoteCertificates() returns the DER-encoded certificates the remote peer actually sent, the raw material the browser already hashed and checked against the fingerprint in RTCSessionDescription.

getStats() exposes the negotiated parameters after connection, which is the practical way to confirm what cipher and which candidate pair are actually in use:

const stats = await pc.getStats();
for (const report of stats.values()) {
  if (report.type === 'transport') {
    // The DTLS version, cipher, and SRTP profile actually negotiated.
    console.log(report.dtlsVersion, report.dtlsCipher, report.srtpCipher);
    console.log('local cert id:', report.localCertificateId);
    console.log('remote cert id:', report.remoteCertificateId);
  }
  if (report.type === 'candidate-pair' && report.state === 'succeeded') {
    // The address pair that won. Inspect to confirm a direct (host/srflx)
    // path and the absence of any relay.
    console.log('selected pair:', report.localCandidateId, report.remoteCandidateId);
  }
}

A transport report carries dtlsVersion, dtlsCipher, and srtpCipher, plus the certificate-stat IDs you can cross-reference with certificate reports to read the fingerprints the session settled on. The selected candidate-pair tells you which path won and, by candidate type, whether the connection is direct.

Perfect forward secrecy

The DTLS handshake derives session keys from an ephemeral ECDHE exchange, not from the long-term certificate alone. Recording the encrypted traffic and stealing a peer's certificate later does not decrypt past sessions: the ECDHE private values are discarded when the session ends. Each connection is cryptographically independent.

2.11 Threats, and where each is handled

It helps to lay out the threats side by side and name the mechanism that addresses each, and the ones WebRTC leaves to you.

Threat Handled by Where it lives Residual risk
Eavesdropping on media SRTP encryption Media path None, if the right peer was authenticated
Eavesdropping on data DTLS over SCTP Data path None, same caveat
Tampering / forged packets SRTP auth tag, DTLS record MAC Media + data path None
Replay of captured packets SRTP replay window, DTLS sequence Media + data path None
Connecting to an impostor peer Fingerprint check in DTLS handshake Media path vs SDP Only as good as the fingerprint you received
MITM via tampered SDP Nothing in WebRTC Signaling channel Yours to handle: secure signaling
DoS amplification at a victim Consent freshness (authenticated STUN) ICE None, while credentials stay secret
Forged connectivity checks ICE ufrag/pwd message integrity ICE Leaks if the SDP leaks
Unauthorized camera/mic/screen Permission prompt, secure context Browser User can be socially engineered to grant
Local IP disclosure to a page mDNS .local candidates Candidate gathering Public IP still exposed by srflx
Metadata exposure to a relay STUN-only, no TURN Topology choice Connectivity loss on hard NATs

Read the "Residual risk" column. Almost every entry resolves to "none", except the two that depend on the signaling channel. That is the shape of WebRTC security: the peer link is closed; the introduction is not.

3. Recap and the threat that remains

What WebRTC guarantees once a connection is up:

  • Media is encrypted and authenticated with SRTP, keyed from the DTLS handshake.
  • The data channel is encrypted with DTLS over SCTP.
  • Keys are negotiated directly between peers via ephemeral ECDHE, with forward secrecy.
  • The peer you handshake with is the one whose certificate matches the fingerprint you received.
  • Traffic flows only to endpoints that prove ongoing consent through authenticated STUN checks.
  • Camera, microphone, and screen capture require an explicit, revocable user grant in a secure context.

What WebRTC does not guarantee: that the fingerprint you received is the right one.

The DTLS handshake authenticates the peer link against the fingerprint in the SDP. The SDP arrives through the signaling channel, and WebRTC defines no security for signaling. An attacker who controls that channel can substitute their own fingerprint and candidates, complete a valid DTLS handshake with each side, and relay between them. Both peers see an encrypted, authenticated connection. They are authenticated to the attacker. The encryption is sound; the trust depends entirely on the integrity of SDP delivery.

Actionable guidance:

  • Secure the signaling channel first. Serve it over HTTPS/WSS and authenticate the participants. A tampered SDP defeats every guarantee above, and the signaling server is the real MITM vantage point, not the media path.
  • Treat the fingerprint as the thing to protect in transit. If you can verify it out of band (a short authentication string, a shared code read aloud, a QR scan, a trusted identity provider), do so. That closes the relay attack even against a hostile signaling server.
  • Serve the page over HTTPS. Capture APIs require a secure context, and a compromised page origin compromises everything downstream of it.
  • Keep gameplay peer-to-peer. Nothing in the payload path should transit a server. The encryption is end-to-end only as long as the endpoints are the real peers.
  • Prefer mDNS-masked candidates and STUN-only paths where the application allows it, to keep IP and metadata exposure minimal.

WebRTC secures the link between two peers completely. It trusts whoever introduced them. Secure that introduction, and the rest of the stack holds.

See the underlying protocols for how DTLS and SRTP fit together, the signaling handshake for how SDP moves, and ICE and NAT traversal for why candidates expose IP addresses.