-
Notifications
You must be signed in to change notification settings - Fork 6
Fix bug in second hash calculation #141
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
Changes from 13 commits
fc6b7af
211c6ea
d52a261
97b536b
f71ac58
8397bed
3c83f4e
b6de4a3
bf7b779
f46ab32
db78586
b96a4f7
3318d7d
5fbbb28
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ import ( | |
"crypto/aes" | ||
"crypto/cipher" | ||
"crypto/sha256" | ||
"encoding/binary" | ||
|
||
"github.com/libp2p/go-libp2p/core/peer" | ||
"github.com/multiformats/go-multihash" | ||
|
@@ -13,30 +14,39 @@ import ( | |
const ( | ||
// nonceLen defines length of the nonce to use for AESGCM encryption | ||
nonceLen = 12 | ||
// keysize defines the size of multihash key | ||
keysize = 32 | ||
) | ||
|
||
var ( | ||
// secondHashPrefix is a prefix that a mulithash is prepended with when calculating a second hash | ||
secondHashPrefix = []byte("CR_DOUBLEHASH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") | ||
// deriveKeyPrefix is a prefix that a multihash is prepended with when deriving an encryption key | ||
deriveKeyPrefix = []byte("CR_ENCRYPTIONKEY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") | ||
// noncePrefix is a prefix that a multihash is prepended with when calculating a nonce | ||
noncePrefix = []byte("CR_NONCE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") | ||
) | ||
|
||
// SecondSHA returns SHA256 over the payload | ||
func SHA256(payload, dest []byte) []byte { | ||
return sha256Multiple(dest, payload) | ||
} | ||
|
||
func sha256Multiple(dest []byte, payloads ...[]byte) []byte { | ||
h := sha256.New() | ||
h.Write(payload) | ||
for _, payload := range payloads { | ||
h.Write(payload) | ||
} | ||
return h.Sum(dest) | ||
} | ||
|
||
// SecondMultihash calculates SHA256 over the multihash and wraps it into another multihash with DBL_SHA256 codec | ||
func SecondMultihash(mh multihash.Multihash) (multihash.Multihash, error) { | ||
prefix := []byte("CR_DOUBLEHASH") | ||
mh, err := multihash.Sum(append(prefix, mh...), multihash.DBL_SHA2_256, keysize) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return mh, nil | ||
digest := SHA256(append(secondHashPrefix, mh...), nil) | ||
return multihash.Encode(digest, multihash.DBL_SHA2_256) | ||
} | ||
|
||
// deriveKey derives encryptioin key from the passphrase using SHA256 | ||
func deriveKey(passphrase []byte) []byte { | ||
return SHA256(append([]byte("AESGCM"), passphrase...), nil) | ||
return SHA256(append(deriveKeyPrefix, passphrase...), nil) | ||
} | ||
|
||
// DecryptAES decrypts AES payload using the nonce and the passphrase | ||
|
@@ -62,9 +72,11 @@ func EncryptAES(payload, passphrase []byte) ([]byte, []byte, error) { | |
derivedKey := deriveKey([]byte(passphrase)) | ||
|
||
// 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 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 commentThe 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 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 commentThe reason will be displayed to describe this comment to others. Learn more.
These values are hashed over, so Does that make sense? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alright, all good! 👍🏻 |
||
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 commentThe 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 [ The encryption varint for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great point! As IPNI doesn't have anything stored after There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perfect, just wanted to make sure we are on the same page 😄 |
||
|
@@ -83,13 +95,13 @@ func EncryptAES(payload, passphrase []byte) ([]byte, []byte, error) { | |
} | ||
|
||
// DecryptValueKey decrypts the value key using the passphrase | ||
func DecryptValueKey(valKey, passphrase []byte) ([]byte, error) { | ||
return DecryptAES(valKey[:nonceLen], valKey[nonceLen:], passphrase) | ||
func DecryptValueKey(valKey, mh multihash.Multihash) ([]byte, error) { | ||
return DecryptAES(valKey[:nonceLen], valKey[nonceLen:], mh) | ||
} | ||
|
||
// EncryptValueKey encrypts raw value key using the passpharse | ||
func EncryptValueKey(valKey, passphrase []byte) ([]byte, error) { | ||
nonce, encValKey, err := EncryptAES(valKey, passphrase) | ||
func EncryptValueKey(valKey, mh multihash.Multihash) ([]byte, error) { | ||
nonce, encValKey, err := EncryptAES(valKey, mh) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
@@ -98,13 +110,13 @@ func EncryptValueKey(valKey, passphrase []byte) ([]byte, error) { | |
} | ||
|
||
// DecryptMetadata decrypts metdata using the provided passphrase | ||
func DecryptMetadata(encMetadata, passphrase []byte) ([]byte, error) { | ||
return DecryptAES(encMetadata[:nonceLen], encMetadata[nonceLen:], passphrase) | ||
func DecryptMetadata(encMetadata, valueKey []byte) ([]byte, error) { | ||
return DecryptAES(encMetadata[:nonceLen], encMetadata[nonceLen:], valueKey) | ||
} | ||
|
||
// EncryptMetadata encrypts metadata using the provided passphrase | ||
func EncryptMetadata(metadata, passphrase []byte) ([]byte, error) { | ||
nonce, encValKey, err := EncryptAES(metadata, passphrase) | ||
func EncryptMetadata(metadata, valueKey []byte) ([]byte, error) { | ||
nonce, encValKey, err := EncryptAES(metadata, valueKey) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package dhash | ||
|
||
import ( | ||
"bytes" | ||
"crypto/sha256" | ||
"math/rand" | ||
"testing" | ||
|
||
"github.com/ipni/go-indexer-core/store/test" | ||
"github.com/multiformats/go-multihash" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
ischasny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
func TestSalt(t *testing.T) { | ||
salt_len := 64 | ||
require.Equal(t, len(secondHashPrefix), salt_len) | ||
require.Equal(t, len(deriveKeyPrefix), salt_len) | ||
require.Equal(t, len(noncePrefix), salt_len) | ||
} | ||
|
||
func TestEncryptSameValueWithTheSameMultihashShouldProduceTheSameOutput(t *testing.T) { | ||
rng := rand.New(rand.NewSource(1413)) | ||
payload := make([]byte, 256) | ||
_, err := rng.Read(payload) | ||
if err != nil { | ||
panic(err) | ||
} | ||
passphrase := make([]byte, 32) | ||
_, err = rng.Read(passphrase) | ||
require.NoError(t, err) | ||
|
||
nonce1, encrypted1, err := EncryptAES(payload, passphrase) | ||
require.NoError(t, err) | ||
|
||
nonce2, encrypted2, err := EncryptAES(payload, passphrase) | ||
require.NoError(t, err) | ||
|
||
require.True(t, bytes.Equal(nonce1, nonce2)) | ||
require.True(t, bytes.Equal(encrypted1, encrypted2)) | ||
} | ||
|
||
func TestCanDecryptEncryptedValue(t *testing.T) { | ||
rng := rand.New(rand.NewSource(1413)) | ||
payload := make([]byte, 256) | ||
_, err := rng.Read(payload) | ||
if err != nil { | ||
panic(err) | ||
} | ||
passphrase := make([]byte, 32) | ||
_, err = rng.Read(passphrase) | ||
require.NoError(t, err) | ||
|
||
nonce, encrypted, err := EncryptAES(payload, passphrase) | ||
require.NoError(t, err) | ||
|
||
decrypted, err := DecryptAES(nonce, encrypted, passphrase) | ||
require.NoError(t, err) | ||
|
||
require.True(t, bytes.Equal(payload, decrypted)) | ||
} | ||
|
||
func TestSecondMultihash(t *testing.T) { | ||
secondHashPrefix := []byte("CR_DOUBLEHASH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") | ||
|
||
mh := test.RandomMultihashes(1)[0] | ||
smh, err := SecondMultihash(mh) | ||
require.NoError(t, err) | ||
|
||
h := sha256.New() | ||
h.Write(append(secondHashPrefix, mh...)) | ||
digest := h.Sum(nil) | ||
|
||
decoded, err := multihash.Decode(smh) | ||
require.NoError(t, err) | ||
|
||
require.Equal(t, uint64(multihash.DBL_SHA2_256), decoded.Code) | ||
require.Equal(t, digest, decoded.Digest) | ||
} |
Uh oh!
There was an error while loading. Please reload this page.