-
Notifications
You must be signed in to change notification settings - Fork 281
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Obfuscate shared secret when printing #396
Obfuscate shared secret when printing #396
Conversation
d12edb6
to
de3527f
Compare
I'm confused as to why CI is failing? This PR does not make material changes to the 256 byte array so why are |
de3527f
to
04726d9
Compare
src/ecdh.rs
Outdated
#[inline] | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
let mut slice = [0u8; BUF_SIZE * 2]; | ||
let hex = to_hex(&self.secret, &mut slice).expect("fixed-size hex serializer failed"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This serializes the full buffer, which can be longer than the actual secret.
Maybe DisplaySecret
should wrap the SharedSecret
type and use its Deref
impl to get the full secret?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think storing reference is better, see below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that this PR is being done on top of #402, these comments are resolved I believe.
@@ -91,7 +91,7 @@ pub struct DisplaySecret { | |||
impl fmt::Debug for DisplaySecret { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated but I think this should be called just Display
- same as std::path::Display
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to leave this as is for now, can re-visit if you feel strongly.
src/ecdh.rs
Outdated
/// Formats the explicit byte value of the secret as a little-endian hexadecimal string using the | ||
/// provided formatter. | ||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
pub struct DisplaySecret { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Name could be Display
but ecdh::Display
is weird. ecdh::shared_secret::Display
would look better but since secret is the only type here the additional module looks weird. IDK what's best.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above, I'll leave this for now. Thanks.
Converting to draft until #402 is merged or closed since 402 will effect how this PR (and review suggestions) are implemented. |
Cool, thanks. That explains the mysterious (to me) Rust 1.29 CI fail. |
JFYI previous work on a similar matter: #312 |
04726d9
to
6788d4b
Compare
Yep, I tying in with what you did there! Thanks |
f7b229e
to
8535fa1
Compare
Strange that nightly and stable CI runs pass but not the others (its a rustdoc test that fails, should not be effected by the toolchain)? |
@@ -35,7 +36,7 @@ macro_rules! impl_display_secret { | |||
|
|||
hasher.write(DEBUG_HASH_TAG); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the test fails because DefaultHasher::new()
above returns hasher seeded with a different random seed each time. Which also means this implementation is actually wrong if we want to allow comparing different secrets.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting, thanks for debugging it for me. So shared_secret_point
is non-trivial to use then. I'll put some more work into it tomorrow. Cheers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On further thought, I don't get that, the same data hashed multiple times should give the same output irrespective of the hashing implementation. I haven't looked at the code yet, I was just pondering your message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Edit: something doesn't seem right about this, DefaultHasher::new
is guaranteed to return same hasher and the implementation uses fixed seed 0, 0
.
Original message:
std
hasher is one intended for HashMap
and implements DoS protection by randomizing among other things. It produces same hash for same input for same instances of the hasher but since we call new
on each debug we're randomizing it.
if we want to allow comparing different secrets
I meant humans comparing, not computers here. Only the Debug
implementation is wrong.
Unless we want to implement our own hasher or store DefaultHasher
in static variable I suggest to just remove the hash when compiled without bitcoin_hashes
.
src/secret.rs
Outdated
/// let key = secp256k1::ONE_KEY; | ||
/// | ||
/// // Normal display hides value. | ||
/// assert_eq!("SecretKey(#2518682f7819fb2d)", format!("{:?}", key)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see now, this is actually hard coded. The test is wrong because std
doesn't guarantee any particular hash function or value. I suggest to test it with bitcoin_hashes
only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think std
should guarantee that the shared secrets don't change, and it is a breaking change if they do.
Having said this, we shouldn't guarantee the debug output of anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They don't guarantee it in case a better hash function is available so it can be switched. Notice that it's specifically called DefaultHasher
- there was SipHasher
before. I believe that's fine, if people want a specific hash function they should just explicitly use it (as a crate or NIH).
Agree on not guaranteeing debug output.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Happened to be the same hasher version that produced the expected output. So different versions of Rust produce different hashes - as documented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left an example of debug output in the rustdocs of SecretKey
, removed the assertion. Removed all mention of debug output for the other two secrets (KeyPair
and SharedSecret
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They don't guarantee it in case a better hash function is available so it can be switched. Notice that it's specifically called
DefaultHasher
- there wasSipHasher
before. I believe that's fine, if people want a specific hash function they should just explicitly use it (as a crate or NIH).
I really don't like this. The implication that if users try to share a secret, and one user has a slightly different version of rust-secp than the other, that they will be unable to communicate because their crypto doesn't work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's non-issue because we only use this for debug output which must not be relied on. Creation of SharedSecret
still uses sha256.
5db4115
to
85fc336
Compare
src/key.rs
Outdated
#[inline] | ||
pub fn serialize_secret(&self) -> [u8; constants::SECRET_KEY_SIZE] { | ||
pub fn into_secret(&self) -> [u8; constants::SECRET_KEY_SIZE] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In 2ccd54b:
I don't know about this. into_secret
I would expect to produce a SecretKey
or maybe a SharedSecret
, not a byte array. A byte array I'd expect to get from something whose name was serialization-related.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something bothered me about this and now I see what. secret.into_secret()
is weird. Even worse it takes &self
, which shouldn't use into_
prefix. I think the name should stay or _secret
should be removed. Or perhaps call it to_bytes()
to remove the impression that there's costly serialization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice. I'd be happy with to_bytes
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The system works, to_bytes
it is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Funny side note, while doing this change originally I looked up that naming table (again), and managed to read it incorrectly and write the commit log based on this :) Naming things really is hard.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to write our own, non-confusing version. :)
85fc336
to
ff39f28
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK ff39f28
This is a breaking change because it removes a bunch of the SharedSecret
API. But I also contend that it's a breaking change because it changes the sharedsecret hash function.
@apoelstra did you confuse it with the other PR? |
My comment refers to 41dc861 which I think is only in this PR. |
Ah, no -- the first three commits are from an old version of #402, which is where the breaking changes come in. |
My bad, this often happens to me, at first splitting the PRs seems like a good idea but if/as they grow it becomes entangled. And rebasing these was a PITA :) |
@tcharding is your intent here to build on #402? |
ea6a378
to
ea20bbb
Compare
Builds on top of the correct current tip of #402 now, thanks for noticing. |
ea20bbb
to
79c5a1e
Compare
5603d71 Limit SharedSecret to 32 byte buffer (Tobin Harding) d5eeb09 Use more intuitive local var numbering (Tobin Harding) 834f63c Separate new_with_hash into public function (Tobin Harding) Pull request description: Currently `SharedSecret` provides a way to get a shared secret using SHA256 _as well as_ a way to use a custom hash function to get the shared secret. Internally `SharedSecret` uses a 256 byte buffer, this is a tad wasteful. We would like to keep the current functionality but reduce memory usage. - Patch 1: Pulls the `new_with_hash` logic out into a standalone public function that just returns the 64 bytes representing the x,y co-ordinates of the computed shared secret point. Callers are then responsible for hashing this point to get the shared secret (idea by @Kixunil, thanks). - Patch 2: Does trivial refactor - Patch 3: Uses a 32 byte buffer internally for `SharedSecret`. This is basically a revert of the work @elichai did to add the custom hashing logic. @elichai please holla if you are not happy with me walking all over this code :) ### Note to reviewers Secret obfuscation is done on top of this in #396, they could be reviewed in order if this work is of interest to you. ACKs for top commit: apoelstra: ACK 5603d71 Tree-SHA512: 48982a4a6a700a111e4c1d5d21d62503d34f433d8cb303d11ff018d2f2be2467fa806107018db16b6d0fcc5ff1a0325dd5790c62c47831c7cd2141a1b6f9467d
#402 is merged. Can you rebase this? (It actually looks like the tip of 402 is still different from the commit in this PR) |
Hashing the debug output for secrets can be done with `bitcoin_hashes` not just `std`. Mention this in the obfuscated string output when neither are available.
In array initialisation we use magic number 64, this is the secret bytes length multiplied by 2. Please note; we still use the magic number 32, left as such because it is used in various ways and its not immediately clear that using a single const would be any more descriptive. Use `SECRET_KEY_SIZE * 2` instead of magic number 64.
The identifier `i` is predominantly used for indexing an array but we are using it as a place holder for the iterated value of an array that is then printed. The identifier `byte` is more descriptive. Done in preparation for adding similar code to the `ecdh` module.
79c5a1e
to
47c9adf
Compare
It is hard to get good help :) |
src/key.rs
Outdated
@@ -212,9 +212,9 @@ impl SecretKey { | |||
SecretKey(sk) | |||
} | |||
|
|||
/// Serializes the secret key as byte value. | |||
/// Gets the secret key as a byte value. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Returns" would sound more natural to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, use as suggested.
src/key.rs
Outdated
#[inline] | ||
pub fn serialize_secret(&self) -> [u8; constants::SECRET_KEY_SIZE] { | ||
pub fn to_bytes(&self) -> [u8; constants::SECRET_KEY_SIZE] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is key pair I think it'd be clearer to name this method secret_bytes()
(Was also thinking of to_
prefix but that usually isn't used for getters.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Weak ACK -- I think it would be clearer to label this method with secret
somehow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too trigger happy with the the to_
conversions huh :) Use secret_bytes
as suggested. Cheers.
The `serialize_secret` method is a getter method, it does not do any serialisation. However we use the method on secret keys and key types so in order for the name to be uniform use the descriptive name `secret_bytes`. Rename `serialize_secret` to be `secret_bytes`.
Improve rustdocs on `display_secret` by doing: - Minor improvements to the rustdocs to aid readability in the editor. - Do not guarantee (`assert_eq!`) debug output
Currently printing the `SharedSecret` using `Display` or `Debug` prints the real secret, this is sub-optimal. We have a solution for other secrets in the project where printing is obfuscated and we provide a `display_secret` method for explicitly printing. Mirror the logic for other secrets and obfuscate the `SharedSecret` when printing.
47c9adf
to
cf6badf
Compare
Implemented suggestions and force push. The only changes to the PR are the two suggested (docs fix and method name). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK cf6badf
Currently printing the
SharedSecret
usingDisplay
orDebug
prints the real secret, this is sub-optimal. We have a solution for other secrets in the project where printing is obfuscated and we provide adisplay_secret
method for explicitly printing.Mirror the logic for other secrets and obfuscate the
SharedSecret
when printing.This is the final change needed to:
Resolve: #226