Skip to content

Commit 70300fb

Browse files
ischasnygammazeroguillaumemichel
authored
Fix bug in second hash calculation (#141)
Fix bug in second hash calculation Fix a bug in how second hash is calculated, aligned magic values with the DHT spec, fix nonce to be derived from (passphrase || payload) instead of just passphrase to avoid attacks on gcm with repeated nonces. --------- Co-authored-by: Andrew Gillis <[email protected]> Co-authored-by: Guillaume Michel - guissou <[email protected]>
1 parent 9aef865 commit 70300fb

File tree

4 files changed

+120
-21
lines changed

4 files changed

+120
-21
lines changed

go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ require (
1818
github.com/libp2p/go-libp2p v0.23.2
1919
github.com/multiformats/go-multihash v0.2.1
2020
github.com/multiformats/go-varint v0.0.7
21+
github.com/stretchr/testify v1.8.1
2122
go.opencensus.io v0.23.0
2223
golang.org/x/crypto v0.3.0
2324
lukechampine.com/blake3 v1.1.7
@@ -30,6 +31,7 @@ require (
3031
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f // indirect
3132
github.com/cockroachdb/redact v1.0.8 // indirect
3233
github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 // indirect
34+
github.com/davecgh/go-spew v1.1.1 // indirect
3335
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
3436
github.com/gammazero/deque v0.2.0 // indirect
3537
github.com/gogo/protobuf v1.3.2 // indirect
@@ -50,11 +52,13 @@ require (
5052
github.com/multiformats/go-multibase v0.1.1 // indirect
5153
github.com/multiformats/go-multicodec v0.7.0 // indirect
5254
github.com/pkg/errors v0.9.1 // indirect
55+
github.com/pmezard/go-difflib v1.0.0 // indirect
5356
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect
5457
github.com/spaolacci/murmur3 v1.1.0 // indirect
5558
go.uber.org/atomic v1.10.0 // indirect
5659
go.uber.org/multierr v1.8.0 // indirect
5760
go.uber.org/zap v1.24.0 // indirect
5861
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
5962
golang.org/x/sys v0.3.0 // indirect
63+
gopkg.in/yaml.v3 v3.0.1 // indirect
6064
)

go.sum

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,12 +251,17 @@ github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb6
251251
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
252252
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
253253
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
254+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
255+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
254256
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
255257
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
256258
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
257259
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
258260
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
261+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
262+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
259263
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
264+
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
260265
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
261266
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
262267
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
@@ -408,6 +413,7 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
408413
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
409414
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
410415
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
416+
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
411417
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
412418
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
413419
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
@@ -421,6 +427,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
421427
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
422428
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
423429
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
430+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
424431
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
425432
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
426433
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=

store/dhash/dhash.go

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"crypto/aes"
66
"crypto/cipher"
77
"crypto/sha256"
8+
"encoding/binary"
89

910
"github.com/libp2p/go-libp2p/core/peer"
1011
"github.com/multiformats/go-multihash"
@@ -13,30 +14,39 @@ import (
1314
const (
1415
// nonceLen defines length of the nonce to use for AESGCM encryption
1516
nonceLen = 12
16-
// keysize defines the size of multihash key
17-
keysize = 32
17+
)
18+
19+
var (
20+
// secondHashPrefix is a prefix that a mulithash is prepended with when calculating a second hash
21+
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")
22+
// deriveKeyPrefix is a prefix that a multihash is prepended with when deriving an encryption key
23+
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")
24+
// noncePrefix is a prefix that a multihash is prepended with when calculating a nonce
25+
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")
1826
)
1927

2028
// SecondSHA returns SHA256 over the payload
2129
func SHA256(payload, dest []byte) []byte {
30+
return sha256Multiple(dest, payload)
31+
}
32+
33+
func sha256Multiple(dest []byte, payloads ...[]byte) []byte {
2234
h := sha256.New()
23-
h.Write(payload)
35+
for _, payload := range payloads {
36+
h.Write(payload)
37+
}
2438
return h.Sum(dest)
2539
}
2640

2741
// SecondMultihash calculates SHA256 over the multihash and wraps it into another multihash with DBL_SHA256 codec
2842
func SecondMultihash(mh multihash.Multihash) (multihash.Multihash, error) {
29-
prefix := []byte("CR_DOUBLEHASH")
30-
mh, err := multihash.Sum(append(prefix, mh...), multihash.DBL_SHA2_256, keysize)
31-
if err != nil {
32-
return nil, err
33-
}
34-
return mh, nil
43+
digest := SHA256(append(secondHashPrefix, mh...), nil)
44+
return multihash.Encode(digest, multihash.DBL_SHA2_256)
3545
}
3646

3747
// deriveKey derives encryptioin key from the passphrase using SHA256
3848
func deriveKey(passphrase []byte) []byte {
39-
return SHA256(append([]byte("AESGCM"), passphrase...), nil)
49+
return SHA256(append(deriveKeyPrefix, passphrase...), nil)
4050
}
4151

4252
// DecryptAES decrypts AES payload using the nonce and the passphrase
@@ -62,9 +72,11 @@ func EncryptAES(payload, passphrase []byte) ([]byte, []byte, error) {
6272
derivedKey := deriveKey([]byte(passphrase))
6373

6474
// Create initialization vector (nonse) to be used during encryption
65-
// Nonce is derived from the mulithash (passpharase) so that encrypted payloads
66-
// for the same multihash can be compared to each other without having to decrypt
67-
nonce := SHA256(passphrase, nil)[:nonceLen]
75+
// Nonce is derived from the passphrase concatenated with the payload so that the encrypted payloads
76+
// for the same multihash can be compared to each other without having to decrypt them, as it's not possible.
77+
payloadLen := make([]byte, 8)
78+
binary.LittleEndian.PutUint64(payloadLen, uint64(len(payload)))
79+
nonce := sha256Multiple(nil, noncePrefix, payloadLen, payload, passphrase)[:nonceLen]
6880

6981
// Create cypher and seal the data
7082
block, err := aes.NewCipher(derivedKey)
@@ -83,13 +95,13 @@ func EncryptAES(payload, passphrase []byte) ([]byte, []byte, error) {
8395
}
8496

8597
// DecryptValueKey decrypts the value key using the passphrase
86-
func DecryptValueKey(valKey, passphrase []byte) ([]byte, error) {
87-
return DecryptAES(valKey[:nonceLen], valKey[nonceLen:], passphrase)
98+
func DecryptValueKey(valKey, mh multihash.Multihash) ([]byte, error) {
99+
return DecryptAES(valKey[:nonceLen], valKey[nonceLen:], mh)
88100
}
89101

90102
// EncryptValueKey encrypts raw value key using the passpharse
91-
func EncryptValueKey(valKey, passphrase []byte) ([]byte, error) {
92-
nonce, encValKey, err := EncryptAES(valKey, passphrase)
103+
func EncryptValueKey(valKey, mh multihash.Multihash) ([]byte, error) {
104+
nonce, encValKey, err := EncryptAES(valKey, mh)
93105
if err != nil {
94106
return nil, err
95107
}
@@ -98,13 +110,13 @@ func EncryptValueKey(valKey, passphrase []byte) ([]byte, error) {
98110
}
99111

100112
// DecryptMetadata decrypts metdata using the provided passphrase
101-
func DecryptMetadata(encMetadata, passphrase []byte) ([]byte, error) {
102-
return DecryptAES(encMetadata[:nonceLen], encMetadata[nonceLen:], passphrase)
113+
func DecryptMetadata(encMetadata, valueKey []byte) ([]byte, error) {
114+
return DecryptAES(encMetadata[:nonceLen], encMetadata[nonceLen:], valueKey)
103115
}
104116

105117
// EncryptMetadata encrypts metadata using the provided passphrase
106-
func EncryptMetadata(metadata, passphrase []byte) ([]byte, error) {
107-
nonce, encValKey, err := EncryptAES(metadata, passphrase)
118+
func EncryptMetadata(metadata, valueKey []byte) ([]byte, error) {
119+
nonce, encValKey, err := EncryptAES(metadata, valueKey)
108120
if err != nil {
109121
return nil, err
110122
}

store/dhash/dhash_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package dhash
2+
3+
import (
4+
"bytes"
5+
"crypto/sha256"
6+
"math/rand"
7+
"testing"
8+
9+
"github.com/ipni/go-indexer-core/store/test"
10+
"github.com/multiformats/go-multihash"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func TestSalt(t *testing.T) {
15+
salt_len := 64
16+
require.Equal(t, len(secondHashPrefix), salt_len)
17+
require.Equal(t, len(deriveKeyPrefix), salt_len)
18+
require.Equal(t, len(noncePrefix), salt_len)
19+
}
20+
21+
func TestEncryptSameValueWithTheSameMultihashShouldProduceTheSameOutput(t *testing.T) {
22+
rng := rand.New(rand.NewSource(1413))
23+
payload := make([]byte, 256)
24+
_, err := rng.Read(payload)
25+
if err != nil {
26+
panic(err)
27+
}
28+
passphrase := make([]byte, 32)
29+
_, err = rng.Read(passphrase)
30+
require.NoError(t, err)
31+
32+
nonce1, encrypted1, err := EncryptAES(payload, passphrase)
33+
require.NoError(t, err)
34+
35+
nonce2, encrypted2, err := EncryptAES(payload, passphrase)
36+
require.NoError(t, err)
37+
38+
require.True(t, bytes.Equal(nonce1, nonce2))
39+
require.True(t, bytes.Equal(encrypted1, encrypted2))
40+
}
41+
42+
func TestCanDecryptEncryptedValue(t *testing.T) {
43+
rng := rand.New(rand.NewSource(1413))
44+
payload := make([]byte, 256)
45+
_, err := rng.Read(payload)
46+
if err != nil {
47+
panic(err)
48+
}
49+
passphrase := make([]byte, 32)
50+
_, err = rng.Read(passphrase)
51+
require.NoError(t, err)
52+
53+
nonce, encrypted, err := EncryptAES(payload, passphrase)
54+
require.NoError(t, err)
55+
56+
decrypted, err := DecryptAES(nonce, encrypted, passphrase)
57+
require.NoError(t, err)
58+
59+
require.True(t, bytes.Equal(payload, decrypted))
60+
}
61+
62+
func TestSecondMultihash(t *testing.T) {
63+
mh := test.RandomMultihashes(1)[0]
64+
smh, err := SecondMultihash(mh)
65+
require.NoError(t, err)
66+
67+
h := sha256.New()
68+
h.Write(append(secondHashPrefix, mh...))
69+
digest := h.Sum(nil)
70+
71+
decoded, err := multihash.Decode(smh)
72+
require.NoError(t, err)
73+
74+
require.Equal(t, uint64(multihash.DBL_SHA2_256), decoded.Code)
75+
require.Equal(t, digest, decoded.Digest)
76+
}

0 commit comments

Comments
 (0)