
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, thisdemonstrates atiming 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:
- BoringSSL private key loading is not constant time Billy Brumley (Oct 13)
- Re: BoringSSL private key loading is not constant time Jeffrey Walton (Oct 13)
- Re: BoringSSL private key loading is not constant time Peter Gutmann (Oct 13)
- Re: BoringSSL private key loading is not constant time Alex Gaynor (Oct 14)
- Re: BoringSSL private key loading is not constant time Peter Gutmann (Oct 14)
- Re: BoringSSL private key loading is not constant time Demi Marie Obenour (Oct 14)
- Re: BoringSSL private key loading is not constant time Billy Brumley (Oct 14)
- Re: BoringSSL private key loading is not constant time Billy Brumley (Oct 14)
- Re: BoringSSL private key loading is not constant time David Benjamin (Oct 14)
- Re: BoringSSL private key loading is not constant time Hanno Böck (Oct 14)
- Re: BoringSSL private key loading is not constant time Alex Gaynor (Oct 14)
- Re: BoringSSL private key loading is not constant time Peter Gutmann (Oct 13)
- Re: BoringSSL private key loading is not constant time Billy Brumley (Oct 14)
- Re: BoringSSL private key loading is not constant time Jacob Bachmeyer (Oct 14)
- Re: BoringSSL private key loading is not constant time Jeffrey Walton (Oct 13)