-
Notifications
You must be signed in to change notification settings - Fork 6
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
Fix bug in second hash calculation #141
Conversation
Codecov ReportPatch coverage has no change and project coverage change:
📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more Additional details and impacted files@@ Coverage Diff @@
## main #141 +/- ##
==========================================
- Coverage 60.14% 60.11% -0.04%
==========================================
Files 20 20
Lines 3450 3452 +2
==========================================
Hits 2075 2075
- Misses 1079 1081 +2
Partials 296 296
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. ☔ View full report at Codecov. |
store/dhash/dhash.go
Outdated
@@ -64,7 +67,7 @@ func EncryptAES(payload, passphrase []byte) ([]byte, []byte, error) { | |||
// Create initialization vector (nonse) to be used during encryption | |||
// Nonce is derived from the mulithash (passpharase) so that encrypted payloads | |||
// for the same multihash can be compared to each other without having to decrypt | |||
nonce := SHA256(passphrase, nil)[:nonceLen] | |||
nonce := SHA256(append(noncePrefix, passphrase...), nil)[:nonceLen] |
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.
AFAIT this means that two different peer will use the same nonce (since it is just a salted hash of the multihash). Assuming this is true:
This is not OK, because then an attacker can use multiple different provider records from multiple different nodes using the same nonce to recover the derived key, which is the multihash. (the actual attack relies on cipher textes being different, here the cipher text should be the peer id)
If this what the spec says, the spec needs to be updated.
You can search: aes gcm duplicated nonce
this is a well known attack.
Other symetric encryptions constructions supports using duplicated nonce with minimal issue (attackers can match duplicates without revealing their content, either based on blocks of the stream without feedback, or until the first duplicate is found if there is feedback).
We are not using this here tho, and this is unsafe.
cc @guillaumemichel we had brainstorming together around this, I was maybe not clear at the end but this was assuming the nonce was salted with the cipher text and we don't mind duplicates being matched. not 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.
Responding to:
We derive nonse from the passphrase so that we can deterministically produce the same result of
hash(mh) -> EncPeerID
given the same multihash / peerID.
You need to salt this by the peer id then:
nonce := SHA256(append(append(binary.AppendUvarint(noncePrefix, uint64(len(payload))), payload...)), passphrase...))
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 for pointing this out - will push a fix in shortly
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.
Btw I can't comment on untouched code, but you should really make:
func SHA256(dest []byte, payloads ...[]byte) []byte {
h := sha256.New()
for _, payload := range payloads {
h.Write(payload)
}
return h.Sum(dest)
}
This will allow to rewrite:
nonce := SHA256(append(append(binary.AppendUint64(noncePrefix, uint64(len(payload))), payload...)), passphrase...))
into the much more readable:
nonce := SHA256(nil, noncePrefix, binary.Uvarint(uint64(len(payload))), payload, passphrase)
Co-authored-by: Andrew Gillis <[email protected]>
nonce := SHA256(passphrase, nil)[:nonceLen] | ||
// Nonce is derived from the passphrase concatenated with the payload so that the encrypted payloads | ||
// for the same multihash can be compared to each other without having to decrypt them, as it's not possible. | ||
payloadLen := make([]byte, 8) |
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.
Concerning the payloadLen, the exact format isn't defined yet in ipfs/specs#373.
Would it make more sense to use a constant byte array (e.g make([]byte, 8)
) or to use an unsigned varint. As we mostly encrypt peerids, that are quite short, we don't always need a big number for the payload len. A varint allows to use less bytes to describe the payload len, and the maximal payload len can also be very large if needed.
I think that I am more in favor of an unsigned varint format for the payload len, as it gives more agility. WDYT?
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 don't always need a big number for the payload len
These values are hashed over, so payloadLen
isn't going to be a part of the encrypted value, i.e. see line 79: nonce := sha256Multiple(nil, noncePrefix, payloadLen, payload, passphrase)[:nonceLen]
.
Does that make sense?
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.
Alright, all good! 👍🏻
// for the same multihash can be compared to each other without having to decrypt them, as it's not possible. | ||
payloadLen := make([]byte, 8) | ||
binary.LittleEndian.PutUint64(payloadLen, uint64(len(payload))) | ||
nonce := sha256Multiple(nil, noncePrefix, payloadLen, payload, passphrase)[:nonceLen] | ||
|
||
// Create cypher and seal the data | ||
block, err := aes.NewCipher(derivedKey) |
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 format for Encrypted PeerID/Metadata was recently updated in the DHT Spec. The format should be [encryption_varint
, payload_len
, nonce
, encrypted_payload
]. See ipfs/specs#373
The encryption varint for aes-gcm-256
is 0x8040
and the multicodec is 0x2000
(See multiformats/multicodec#314 (comment))
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.
Great point! As IPNI doesn't have anything stored after EncPeerID
the encryption_varint
and payload_len
can be appended on retrieval.
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.
Perfect, just wanted to make sure we are on the same page 😄
Co-authored-by: Guillaume Michel - guissou <[email protected]>
Co-authored-by: Guillaume Michel - guissou <[email protected]>
Co-authored-by: Guillaume Michel - guissou <[email protected]>
Co-authored-by: Guillaume Michel - guissou <[email protected]>
Co-authored-by: Guillaume Michel - guissou <[email protected]>
Co-authored-by: Guillaume Michel - guissou <[email protected]>
Co-authored-by: Guillaume Michel - guissou <[email protected]>
Co-authored-by: Guillaume Michel - guissou <[email protected]>
passphrase || payload
instead of justpassphrase
to avoid attacks on gcm with repeated nonces (example).