oss-sec mailing list archives

Re: BoringSSL private key loading is not constant time


From: David Benjamin <davidben () google com>
Date: Tue, 14 Oct 2025 13:08:26 -0400

Hi all,

I don’t think BoringSSL has ever claimed *no* side channels. That’s quite a
tall order! We do try to mitigate cache and timing side channels w.r.t.
cryptographic secrets, under the so-called “constant-time” model. (BearSSL
has an excellent write-up of how that works at [0].) But, as the volume of
this list shows, I think *any* software project would have a hard time
claiming to have *no* bugs with respect to *whatever* correctness goals
they have!

Regardless, constant-time is certainly important to us, and reports of
side-channel leaks are quite welcome. If folks have something for us to
look at, email or a bug using the Chromium security bug process[1] (mention
that it’s for BoringSSL) are good ways to reach project members.

As for the report here, thanks to Billy for taking a look at our private
key parser! The minimum bit width of an EC scalar (as opposed to the width
of the group order), indeed should be confidential, and BoringSSL aims not
to leak it. But there’s a bit more to the story: This appears to be a
misunderstanding of the ECPrivateKey format, not a leak in the parser. The
leaky inputs were actually not valid ECPrivateKeys and cannot, under the
constant-time model, be processed without leaking this information. It’s a
historical quirk that they’re accepted at all.

I’ll take a moment to give some details for any readers who (like me!) find
these sorts of things interesting:

In an ECPrivateKey, the privateKey field is an OCTET STRING that is
supposed to have a fixed length relative to the EC group. Per RFC 5915[2]:

  o  privateKey is the private key.  It is an octet string of length
      ceiling (log2(n)/8) (where n is the order of the curve) obtained
      from the unsigned integer via the Integer-to-Octet-String-
      Primitive (I2OSP) defined in [RFC3447].

This means all private keys of, say, P-384 should have 48 bytes. The
encoding of a P-384 scalar with value 1 should be the 48-byte string {0, 0,
..., 0, 0, 1}, not the one-byte string {1}.

This is good, because it makes constant-time processing possible. Encoding
and decoding can convert to/from the fixed-width byte representation and
some fixed-width in-memory representation (e.g. six 64-bit words),
performing fixed-width, constant-time operations end-to-end from
generation, to encoding, to decoding, to signing. For these operations,
using this encoding, BoringSSL aims to be constant-time, and we’re not
aware of any leaks. (But reports of bugs are very welcome!)

Unfortunately, some old software did not quite get this right:

1. When decoding, they accepted all sizes of inputs
2. When encoding, they truncated leading zeros, thus leaking the magnitude
of the private key

We’re not aware of any current software doing this but, since there may
still be malformed keys in the wild, BoringSSL still accepts these
truncated inputs. When importing in BoringSSL, we load it into our
fixed-width in-memory representation, effectively putting the missing zeros
back. (Fixed-with representations are necessary for ECDSA or ECDH itself to
be constant-time.)

With respect to the constant-time model, that import process indeed leaks
the byte length of the privateKey field, but this is actually unavoidable.
The encoding itself already leaked the length. The byte strings themselves
weren’t the same size and the constant-time model assumes the trace of
memory accesses (often visible to cache-timing attacks) is leaked. That
means merely constructing a buffer to pass into the library leaks the
length of the buffer.

This means private key formats must have secret-independent lengths. The
privateKey field, by spec, achieves this, but these malformed, truncated
privateKey fields do not. If one passes a truncated privateKey field to any
decoder, leaking the byte length is unavoidable. Rather, it is up to the
encoder to follow the spec, which will give a fixed-width,
secret-independent byte length that can be safely leaked.

The inputs in the test harness use this leaky, truncated encoding. One can
see this in how the test cases have different sizes. The issue is that
“randme.py” calls the Python hex() function on an integer, which returns
the minimal hex encoding. Something like theint.to_bytes(48, "big").hex()
would have constructed the correct, fixed-width private key representation
for P-384.

Ideally, decoders would all reject these invalid inputs, so it would be
immediately apparent when encoders get this wrong, but the environment of
existing private keys makes doing so a compatibility risk.

Nonetheless, the attention is much appreciated. Thanks again to Billy for
taking the time to look!

David

[0] https://www.bearssl.org/constanttime.html
[1] https://www.chromium.org/Home/chromium-security/reporting-security-bugs/
[2] https://www.rfc-editor.org/rfc/rfc5915.html#section-3

On Tue, Oct 14, 2025 at 10:26 AM Alex Gaynor <alex.gaynor () gmail com> wrote:

I missed this talk at the OpenSSL Conference last week. And I don't
know what _precise_ claims the BoringSSL folks have made.

But it seems to me any claim like "there are no timing side-channels"
has to have an implicit "relevant to a threat model". It's _surely_
the case that many functions in any library exhibit timing
variability, but if this can't be used to leak anything confidential,
it's not really an attack of note. In this case, as I understand it,
the only thing that's alleged to be leaked is the length of a key,
which already wasn't confidential.

Alex

On Mon, Oct 13, 2025 at 11:07 PM Peter Gutmann
<pgut001 () cs auckland ac nz> wrote:

Jeffrey Walton <noloader () gmail com> writes:

What does the attacker learn besides the key length?  Isn't that mostly
public information, like the TLS options used during cipher suite
negotiation?

It's a proof-of-concept from a very entertaining talk at the OpenSSL
conference, "Constant-Time BIGNUM Is Bollocks".  The BoringSSL folks had
claimed there were no timing side-channels in their code, this
demonstrates a
timing side-channel.

Admittedly not a terribly useful one :-).

Peter.



--
All that is necessary for evil to succeed is for good people to do nothing.


Current thread: