[TLS] Encrypting record headers: practical for TLS 1.3 after all?

Bryan A Ford <brynosaurus@gmail.com> Fri, 27 November 2015 14:35 UTC

Return-Path: <brynosaurus@gmail.com>
X-Original-To: tls@ietfa.amsl.com
Delivered-To: tls@ietfa.amsl.com
Received: from localhost (ietfa.amsl.com []) by ietfa.amsl.com (Postfix) with ESMTP id 5BA901B2BA9 for <tls@ietfa.amsl.com>; Fri, 27 Nov 2015 06:35:32 -0800 (PST)
X-Virus-Scanned: amavisd-new at amsl.com
X-Spam-Flag: NO
X-Spam-Score: -1.4
X-Spam-Status: No, score=-1.4 tagged_above=-999 required=5 tests=[BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, FREEMAIL_FROM=0.001, J_CHICKENPOX_22=0.6, SPF_PASS=-0.001] autolearn=no
Received: from mail.ietf.org ([]) by localhost (ietfa.amsl.com []) (amavisd-new, port 10024) with ESMTP id uTf4r-X8H_lg for <tls@ietfa.amsl.com>; Fri, 27 Nov 2015 06:35:30 -0800 (PST)
Received: from mail-wm0-x235.google.com (mail-wm0-x235.google.com [IPv6:2a00:1450:400c:c09::235]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by ietfa.amsl.com (Postfix) with ESMTPS id 2F0AE1B2B93 for <tls@ietf.org>; Fri, 27 Nov 2015 06:35:30 -0800 (PST)
Received: by wmuu63 with SMTP id u63so57811570wmu.0 for <tls@ietf.org>; Fri, 27 Nov 2015 06:35:28 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=to:from:subject:message-id:date:user-agent:mime-version :content-type; bh=F4oo9QnNNnIbr1Laa2+UIkgDRZ8EJL3tUb4/VH1W6qw=; b=BZBYt7yiSi1wimHw0yZ8fKMgJjGtNbf7cOSNNoZbDP+hv6hvXjkDeNG6Vgf+jiCiu0 /P3RprBe9+eu5AfkxB3n5M+LM/8ulxVYluQV4+eVA7WFClwriskFMK9Yu4GQm3iNFdCE YNwKy9OXA15f0jeL2wFDcpwcvmx5b0pkB8FuS7GnpeYM6wLdViQLQ+4/fi/1uCldBlH7 KczSlcpfDNuXthqi8m1Jpp81IsKY2aCs60KojX2EGQr+b8KvLXhB1AHjroy9d9Hd/sLx ob2nyc+m/7+5EELufxQJvgrWfdeiG5W71PkBEO52qzlIgrCs3X2LnS3gDSKWYsWv6DWL MZrw==
X-Received: by with SMTP id cp5mr56424176wjb.163.1448634928765; Fri, 27 Nov 2015 06:35:28 -0800 (PST)
Received: from proz.dclient.lsne.ch (85-218-12-53.dclient.lsne.ch. []) by smtp.gmail.com with ESMTPSA id lx4sm33066211wjb.5.2015. for <tls@ietf.org> (version=TLSv1/SSLv3 cipher=OTHER); Fri, 27 Nov 2015 06:35:27 -0800 (PST)
To: "tls@ietf.org" <tls@ietf.org>
From: Bryan A Ford <brynosaurus@gmail.com>
X-Enigmail-Draft-Status: N1110
Message-ID: <56586A2F.1070703@gmail.com>
Date: Fri, 27 Nov 2015 15:35:27 +0100
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Thunderbird/38.3.0
MIME-Version: 1.0
Content-Type: multipart/signed; protocol="application/pkcs7-signature"; micalg="sha-256"; boundary="------------ms030208000305020508010207"
Archived-At: <http://mailarchive.ietf.org/arch/msg/tls/G0klpfGp_4sUCByaQcSjOZ_PtMU>
Subject: [TLS] Encrypting record headers: practical for TLS 1.3 after all?
X-BeenThere: tls@ietf.org
X-Mailman-Version: 2.1.15
Precedence: list
List-Id: "This is the mailing list for the Transport Layer Security working group of the IETF." <tls.ietf.org>
List-Unsubscribe: <https://www.ietf.org/mailman/options/tls>, <mailto:tls-request@ietf.org?subject=unsubscribe>
List-Archive: <https://mailarchive.ietf.org/arch/browse/tls/>
List-Post: <mailto:tls@ietf.org>
List-Help: <mailto:tls-request@ietf.org?subject=help>
List-Subscribe: <https://www.ietf.org/mailman/listinfo/tls>, <mailto:tls-request@ietf.org?subject=subscribe>
X-List-Received-Date: Fri, 27 Nov 2015 14:35:32 -0000

The idea of encrypting TLS record headers has come up before, the most
important purpose being to hide record lengths and boundaries and make
fingerprinting and traffic analysis harder.  I had convinced myself that
goal this would be "too hard" to accomplish in TLS 1.3, but after
further thought I'm not so sure.  So I would like to request comment on
one approach that strikes me as a practical and requires only a rather
minor change to the current spec.

The quick summary:

* To encrypt a record, we first AEAD-encrypt the record's payload,
protecting the header fields via the additional_data, exactly as
currently specified.  But then we XOR-encrypt the 5-byte TLS header just
before transmission, using a (separate) stream cipher indexed by a nonce
that depends on record sequence number and *_write_iv, in exactly the
same way the AEAD is already nonce-indexed.

* To decrypt a record, we simply do the reverse: first use the stream
cipher with the appropriate nonce to XOR-decrypt the 5-byte TLS header,
then sanity-check it as usual to determine its length, read the rest of
the record, and submit it to AEAD for decryption and full integrity
checking as before.

That's it, in a nutshell.  Two likely concerns immediately arise,
discussed below, but feel free to TL;DR the rest if you don't share
these concerns.


Concern #1: What if an active attacker messes with the TLS header,
especially the length field, since stream ciphers don't protect
integrity?  The simple answer is that *exactly* the same thing happens
as now: the AEAD decryption attempt fails, because the
(stream-decrypted) header is AEAD-protected as additional_data.  Nothing
is gained or lost.

SSH, which did something like this, ran into trouble with attackers
being able to twiddle the record length field to make the record length
look big, causing the receiver to try to receive a very large record,
and hence appear to the user to hang, instead of immediately detecting
the modification and terminating the connection.  But there are three
mitigating factors here: (1) TLS is not usually used for interactive
terminal traffic like SSH is; (2) TLS's 2-byte record length field
imposes a pretty reasonable upper-bound on the maximum size an attacker
could maliciously make a record appear to be; and (3) if this risk of
length-twiddling is at all a problem in this proposed encrypted-header
protocol, then it's already a problem for the current TLS 1.3 spec
without encrypted headers, because active attackers can twiddle the bits
of a cleartext length field just as easily (and even be *certain* they
are making the length appear large!).  So I can't see any way this
length-twiddling vulnerability becomes any worse, and maybe it gets a
bit better (because the attacker can no longer be entirely certain
whether he's setting a 1 bit to 0 or a 0 bit to 1).


Concern #2: Do we want to have to go to the trouble of adding a stream
cipher to every TLS 1.3-compatible ciphersuite?  Answer: maybe not, but
we don't necessarily need to.  We could instead just specify a generic
method of using the ciphersuite's main AEAD as a stream cipher for
header encryption/decryption purposes.

The conceptually simplest approach I can think of: In the specification
of how AEAD nonces are generated (section 5.2.2 of
draft-ietf-tls-tls13-07), reserve the least-significant bit of the
record sequence number, so that sequence numbers increment by 2 rather
than 1 each record.  Thus, we get two unique nonces per record from the
same set of symmetric keys.  We first use the nonce with a '0'
least-significant bit to perform the regular AEAD-encryption of the
record with the header info as additional_data.

Then for the same record we use the nonce with a '1' least-significant
to AEAD-encrypt a sequence of five zero bytes ("\0\0\0\0\0"), and use
the first five bytes of result as the cipherstream to XOR the 5-byte TLS
header with before transmitting.  The AEAD will of course uselessly
append some kind of authenticator to this ciphertext that we won't end
up using, but that's OK.  The receiver will just use AEAD-encrypt
(again) on the same five-zero-byte message to reproduce the 5
cipherstream bytes with which to decrypt the TLS header.  Thus, senders
perform two AEAD-encrypts per record, and receivers do one AEAD-encrypt
and one AEAD-decrypt per record.

This approach seems pretty conceptually clean and simple, but has the
performance downside that we always need to invoke the AEAD twice per
record rather than once, which might be (a bit) costly especially when
records are small.  So a simple refinement is to amortize this cost
across records: e.g., once every 256 records (every sequence number
ending in 0x00) we AEAD-encrypt a sequence of 5*256=1280 zero bytes, and
the result in 5-byte chunks as the cipherstream with which to encrypt
and decrypt 256 consecutive TLS record headers.  Thus, we're only adding
one additional AEAD-encryption of a "normal-packet-sized" 1280-byte blob
once every 256 records, which seems likely to be a pretty
inconsequential performance cost.