Forward secrecy limits the damage from later compromise of a long-term authentication key. For a correctly authenticated ephemeral Diffie-Hellman exchange, that key alone does not reveal earlier session secrets. The claim assumes those ephemeral secrets were not also captured. It does not make a compromised endpoint, key log, or recorded plaintext safe.
Two kinds of keys do different jobs
In ECDHE, each side generates an ephemeral private key and sends its public share. Combining a local private key with the peer’s public share produces the same shared secret at both ends. An observer sees public shares, not the private values needed to reproduce that computation under the algorithm’s security assumptions.
TLS does not simply use the raw ECDH output as an application encryption key. Its key schedule derives distinct handshake and application traffic secrets, binds them to the handshake context, and separates directions and purposes. Fresh exchange material is one ingredient in that larger protocol, not a substitute for the protocol.
A certificate binds an identity to a public key under a trust policy. The corresponding private key signs handshake material; the certificate object does not itself sign anything. In TLS 1.3, CertificateVerify authenticates the handshake transcript in certificate-based handshakes. Without authentication, an active intermediary could replace public shares and establish separate secrets with each side.
An RSA or Ed25519 authentication key can coexist with an ephemeral exchange; the signature algorithm and key-exchange group are separate choices. TLS 1.3 also defines PSK-only resumption, which lacks DHE’s forward-secrecy property, while PSK-with-DHE adds fresh exchange material. Optional early data has weaker guarantees still. Do not infer forward secrecy for every traffic epoch solely from a TLS version or certificate label.
When it breaks
Reusing an exchange private key over long periods weakens the intended compromise boundary. Poor randomness, exposed ephemeral values, crash dumps, or TLS key logging can expose the very secrets that the guarantee assumes remain unavailable. Later disclosure of a traffic secret is a different threat from later disclosure of the certificate’s signing key.
Endpoint compromise during a session can reveal plaintext directly. A stolen authentication key can also support impersonation of future connections when an attacker can intercept them. Forward secrecy protects a particular past-traffic scenario; it does not revoke credentials or repair ongoing compromise.
Erasure is an implementation concern, not a line of reassuring prose. Setting a Go variable to nil is not proof that all copies disappeared from memory, swap, or diagnostics. Keep secrets out of logs and manage credential compromise separately from claims about recorded traffic.
Check it quickly
Save this as ecdhe_probe.go and run go run ecdhe_probe.go with Go 1.23 or newer. It uses standard-library X25519 and Ed25519 operations entirely in memory. The signing public key represents an already trusted identity for this exercise. The signed message is explicitly not a TLS transcript; this is a primitive-level experiment, not a new TLS implementation.
package main
import (
"bytes"
"crypto/ecdh"
"crypto/ed25519"
"crypto/rand"
"fmt"
"runtime"
)
func must(err error) {
if err != nil {
panic(err)
}
}
func check(ok bool, message string) {
if !ok {
panic(message)
}
}
func main() {
identity, signingKey, err := ed25519.GenerateKey(rand.Reader)
must(err)
curve := ecdh.X25519()
var previous []byte
for round := 1; round <= 2; round++ {
client, err := curve.GenerateKey(rand.Reader)
must(err)
server, err := curve.GenerateKey(rand.Reader)
must(err)
message := []byte("ECDH learning probe; not a TLS transcript\x00")
message = append(message, client.PublicKey().Bytes()...)
message = append(message, server.PublicKey().Bytes()...)
signature := ed25519.Sign(signingKey, message)
check(ed25519.Verify(identity, message, signature), "trusted signature must verify")
altered := append([]byte(nil), message...)
altered[len(altered)-1] ^= 1
check(!ed25519.Verify(identity, altered, signature), "altered share must fail authentication")
clientSecret, err := client.ECDH(server.PublicKey())
must(err)
serverSecret, err := server.ECDH(client.PublicKey())
must(err)
check(bytes.Equal(clientSecret, serverSecret), "peers must agree")
if previous != nil {
check(!bytes.Equal(previous, clientSecret), "fresh exchange must produce a different observed secret")
}
previous = clientSecret
fmt.Println("round", round, "agrees; altered share fails signature verification")
}
_, err = curve.NewPublicKey(make([]byte, 31))
check(err != nil, "wrong-length X25519 key must fail parsing")
zero, err := curve.NewPublicKey(make([]byte, 32))
must(err)
client, err := curve.GenerateKey(rand.Reader)
must(err)
_, err = client.ECDH(zero)
check(err != nil, "all-zero shared result must be rejected")
fmt.Println("two fresh exchanges differ with one signing identity; malformed and low-order inputs rejected")
fmt.Println(runtime.Version())
}
Each round produces matching secrets at both participants, and the two fresh rounds produce different observed secrets while retaining one signing identity. Changing a byte of the signed public-share message fails signature verification. A thirty-one-byte X25519 public key fails parsing; a length-correct all-zero input parses but fails ECDH’s all-zero-result check.
All assertions passed with Go 1.27.0 on macOS. Only result labels are printed. The example deliberately retains the previous shared secret for comparison, so it demonstrates no secure-erasure property. It also does not prove cryptographic hardness by exhausting possible keys.
The experiment isolates agreement, freshness, authentication, and invalid-input handling. It does not exercise a real TLS transcript, certificate chain, traffic-key derivation, packet encryption, resumption, or production key disposal. Use a maintained TLS implementation for those duties and verify the actual negotiated mode; assembling these primitives is not sufficient to build a secure transport.
Further reading
Sources
- RFC 8446: TLS 1.3, CertificateVerify, the key schedule, PSK modes, and forward-secrecy boundaries, checked 2026-09-09.
- Go crypto/ecdh, X25519 agreement and invalid public-key behavior, checked 2026-09-09.
- Go crypto/ed25519, signing and verification operations, checked 2026-09-09.