From fba5d5bc0be84e9f635d5ab2d1895acb7f91f55b Mon Sep 17 00:00:00 2001 From: Jarek Kowalski Date: Wed, 28 Sep 2016 18:46:13 -0700 Subject: [PATCH] Cleaned up format.go to eliminate confusing crypto, removed non-HMAC option. Removed fold() operation in favor of truncation --- repo/format.go | 69 ++++++++++++++++------------------------- repo/repository_test.go | 23 ++------------ 2 files changed, 30 insertions(+), 62 deletions(-) diff --git a/repo/format.go b/repo/format.go index 54a2f0fee..7e25dc9b9 100644 --- a/repo/format.go +++ b/repo/format.go @@ -55,18 +55,16 @@ type ObjectFormatter interface { Decrypt(cipherText []byte, oid ObjectID) ([]byte, error) } +// digestFunction computes the digest (hash, optionally HMAC) of a given block of bytes. +type digestFunction func([]byte) []byte + // unencryptedFormat implements non-encrypted format. type unencryptedFormat struct { - hashCtor func() hash.Hash - fold int - secret []byte + digestFunc digestFunction } func (fi *unencryptedFormat) ComputeObjectID(data []byte) ObjectID { - h := hashContent(fi.hashCtor, data, fi.secret) - if fi.fold > 0 { - h = fold(h, fi.fold) - } + h := fi.digestFunc(data) return ObjectID{StorageBlock: hex.EncodeToString(h)} } @@ -86,14 +84,13 @@ func (fi *unencryptedFormat) Decrypt(cipherText []byte, oid ObjectID) ([]byte, e // convergentEncryptionFormat implements encrypted format where encryption key is derived from the data and HMAC secret and IV is constant. // By sharing HMAC secret alone, this allows multiple parties in posession of the same file to generate identical block IDs and encryption keys. type convergentEncryptionFormat struct { - hashCtor func() hash.Hash + digestFunc digestFunction createCipher func(key []byte) (cipher.Block, error) keyBytes int - secret []byte } func (fi *convergentEncryptionFormat) ComputeObjectID(data []byte) ObjectID { - h := hashContent(fi.hashCtor, data, fi.secret) + h := fi.digestFunc(data) p := len(h) - fi.keyBytes return ObjectID{StorageBlock: hex.EncodeToString(h[0:p]), EncryptionKey: h[p:]} @@ -110,20 +107,13 @@ func (fi *convergentEncryptionFormat) Decrypt(cipherText []byte, oid ObjectID) ( // syntheticIVEncryptionFormat implements encrypted format with single master AES key and StorageBlock==IV that's // derived from HMAC-SHA256(content, secret). type syntheticIVEncryptionFormat struct { + digestFunc digestFunction createCipher func(key []byte) (cipher.Block, error) aesKey []byte - hmacSecret []byte } func (fi *syntheticIVEncryptionFormat) ComputeObjectID(data []byte) ObjectID { - h := hashContent(sha256.New, data, fi.hmacSecret) - - // Fold the bits into 16 bytes required for the IV. - for i := aes.BlockSize; i < len(h); i++ { - h[i%aes.BlockSize] ^= h[i] - } - - h = h[0:aes.BlockSize] + h := fi.digestFunc(data) return ObjectID{StorageBlock: hex.EncodeToString(h)} } @@ -174,25 +164,25 @@ func decodeHexSuffix(s string, length int) ([]byte, error) { func init() { SupportedFormats = map[string]func(f *Format) (ObjectFormatter, error){ "TESTONLY_MD5": func(f *Format) (ObjectFormatter, error) { - return &unencryptedFormat{md5.New, 0, f.Secret}, nil + return &unencryptedFormat{computeHash(md5.New, md5.Size)}, nil }, "UNENCRYPTED_HMAC_SHA256": func(f *Format) (ObjectFormatter, error) { - return &unencryptedFormat{sha256.New, 0, f.Secret}, nil + return &unencryptedFormat{computeHMAC(sha256.New, f.Secret, sha256.Size)}, nil }, "UNENCRYPTED_HMAC_SHA256_128": func(f *Format) (ObjectFormatter, error) { - return &unencryptedFormat{sha256.New, 16, f.Secret}, nil + return &unencryptedFormat{computeHMAC(sha256.New, f.Secret, 16)}, nil }, "ENCRYPTED_HMAC_SHA512_384_AES256": func(f *Format) (ObjectFormatter, error) { - return &convergentEncryptionFormat{sha512.New384, aes.NewCipher, 32, f.Secret}, nil + return &convergentEncryptionFormat{computeHMAC(sha512.New384, f.Secret, sha512.Size384), aes.NewCipher, 32}, nil }, "ENCRYPTED_HMAC_SHA512_AES256": func(f *Format) (ObjectFormatter, error) { - return &convergentEncryptionFormat{sha512.New, aes.NewCipher, 32, f.Secret}, nil + return &convergentEncryptionFormat{computeHMAC(sha512.New, f.Secret, sha512.Size), aes.NewCipher, 32}, nil }, "ENCRYPTED_HMAC_SHA256_AES256_SIV": func(f *Format) (ObjectFormatter, error) { if len(f.MasterKey) < 32 { return nil, fmt.Errorf("master key is not set") } - return &syntheticIVEncryptionFormat{aes.NewCipher, f.MasterKey, f.Secret}, nil + return &syntheticIVEncryptionFormat{computeHMAC(sha256.New, f.Secret, aes.BlockSize), aes.NewCipher, f.MasterKey}, nil }, } } @@ -200,25 +190,20 @@ func init() { // DefaultObjectFormat is the format that should be used by default when creating new repositories. var DefaultObjectFormat = "ENCRYPTED_HMAC_SHA256_AES256_SIV" -func fold(b []byte, size int) []byte { - if len(b) == size { - return b +// computeHash returns a digestFunction that computes a hash of a given block of bytes and truncates results to the given size. +func computeHash(hf func() hash.Hash, truncate int) digestFunction { + return func(b []byte) []byte { + h := hf() + h.Write(b) + return h.Sum(nil)[0:truncate] } - - for i := size; i < len(b); i++ { - b[i%size] ^= b[i] - } - return b[0:size] } -func hashContent(hf func() hash.Hash, data []byte, secret []byte) []byte { - var h hash.Hash - - if len(secret) > 0 { - h = hmac.New(hf, secret) - } else { - h = hf() +// computeHMAC returns a digestFunction that computes HMAC(hash, secret) of a given block of bytes and truncates results to the given size. +func computeHMAC(hf func() hash.Hash, secret []byte, truncate int) digestFunction { + return func(b []byte) []byte { + h := hmac.New(hf, secret) + h.Write(b) + return h.Sum(nil)[0:truncate] } - h.Write(data) - return h.Sum(nil) } diff --git a/repo/repository_test.go b/repo/repository_test.go index 001287a83..f95c58e70 100644 --- a/repo/repository_test.go +++ b/repo/repository_test.go @@ -201,7 +201,6 @@ func TestHMAC(t *testing.T) { s := testFormat() s.ObjectFormat = "TESTONLY_MD5" - s.Secret = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25} repo, err := New(storagetesting.NewMapStorage(data), s) if err != nil { @@ -210,8 +209,8 @@ func TestHMAC(t *testing.T) { w := repo.NewWriter() w.Write(content) result, err := w.Result(false) - if result.String() != "D697eaf0aca3a3aea3a75164746ffaa79" { - t.Errorf("unexpected result: %v err: %v", result, err) + if result.String() != "D999732b72ceff665b3f7608411db66a4" { + t.Errorf("unexpected result: %v err: %v", result.String(), err) } } @@ -378,19 +377,11 @@ func TestFormats(t *testing.T) { format *Format oids map[string]ObjectID }{ - { - format: makeFormat("TESTONLY_MD5"), // MD5-HMAC with secret 'key' - oids: map[string]ObjectID{ - "": ObjectID{StorageBlock: "63530468a04e386459855da0063b6596"}, - "The quick brown fox jumps over the lazy dog": ObjectID{StorageBlock: "80070713463e7749b90c2dc24911e275"}, - }, - }, { format: &Format{ Version: 1, ObjectFormat: "TESTONLY_MD5", MaxBlockSize: 10000, - Secret: nil, // non-HMAC version }, oids: map[string]ObjectID{ "": ObjectID{StorageBlock: "d41d8cd98f00b204e9800998ecf8427e"}, @@ -399,14 +390,6 @@ func TestFormats(t *testing.T) { }, }, }, - { - format: makeFormat("TESTONLY_MD5"), - oids: map[string]ObjectID{ - "The quick brown fox jumps over the lazy dog": ObjectID{ - StorageBlock: "80070713463e7749b90c2dc24911e275", - }, - }, - }, { format: makeFormat("UNENCRYPTED_HMAC_SHA256"), oids: map[string]ObjectID{ @@ -419,7 +402,7 @@ func TestFormats(t *testing.T) { format: makeFormat("UNENCRYPTED_HMAC_SHA256_128"), oids: map[string]ObjectID{ "The quick brown fox jumps over the lazy dog": ObjectID{ - StorageBlock: "18f1da557915937d2675055a87758d9b", + StorageBlock: "f7bc83f430538424b13298e6aa6fb143", }, }, },