From 34d688ca72d8ef13b83987cc3b3d9b93ab989d9f Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Wed, 24 Jun 2015 16:29:45 +0200 Subject: SQL store: use authenticated encryption Data were encrypted using AES-CFB, with a properly randomized IV, but without any authenticators. This allows the data to be tampered with, without being noticed by the application. This diff slightly changes the encryption/decryption functions in sql_store.go to add a HMAC-SHA256 authenticator to encrypted messages. Two keys are derived from AtRestEncryptKey: the first half of SHA512(AtRestEncryptKey) for the block cipher and the second half for the MAC. This can be changed to a KDF if needed. The decryption function also checks that base64 decoding actually worked, and that the ciphertext is long enough to include the IV and the MAC. Unfortunately, it breaks backward compatibility. But if such a change has to be made, it has to be made early. --- store/sql_store.go | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) (limited to 'store') diff --git a/store/sql_store.go b/store/sql_store.go index a2deea6ba..bef8b4867 100644 --- a/store/sql_store.go +++ b/store/sql_store.go @@ -7,6 +7,9 @@ import ( l4g "code.google.com/p/log4go" "crypto/aes" "crypto/cipher" + "crypto/hmac" + "crypto/sha256" + "crypto/sha512" crand "crypto/rand" dbsql "database/sql" "encoding/base64" @@ -327,20 +330,26 @@ func encrypt(key []byte, text string) (string, error) { } plaintext := []byte(text) + skey := sha512.Sum512(key) + ekey, akey := skey[:32], skey[32:] - block, err := aes.NewCipher(key) + block, err := aes.NewCipher(ekey) if err != nil { return "", err } - ciphertext := make([]byte, aes.BlockSize+len(plaintext)) + macfn := hmac.New(sha256.New, akey) + ciphertext := make([]byte, aes.BlockSize+macfn.Size()+len(plaintext)) iv := ciphertext[:aes.BlockSize] if _, err := io.ReadFull(crand.Reader, iv); err != nil { return "", err } stream := cipher.NewCFBEncrypter(block, iv) - stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext) + stream.XORKeyStream(ciphertext[aes.BlockSize+macfn.Size():], plaintext) + macfn.Write(ciphertext[aes.BlockSize+macfn.Size():]) + mac := macfn.Sum(nil) + copy(ciphertext[aes.BlockSize:aes.BlockSize+macfn.Size()], mac) return base64.URLEncoding.EncodeToString(ciphertext), nil } @@ -351,9 +360,26 @@ func decrypt(key []byte, cryptoText string) (string, error) { return "{}", nil } - ciphertext, _ := base64.URLEncoding.DecodeString(cryptoText) + ciphertext, err := base64.URLEncoding.DecodeString(cryptoText) + if err != nil { + return "", err + } + + skey := sha512.Sum512(key) + ekey, akey := skey[:32], skey[32:] + macfn := hmac.New(sha256.New, akey) + if len(ciphertext) < aes.BlockSize+macfn.Size() { + return "", errors.New("short ciphertext") + } + + macfn.Write(ciphertext[aes.BlockSize+macfn.Size():]) + expectedMac := macfn.Sum(nil) + mac := ciphertext[aes.BlockSize:aes.BlockSize+macfn.Size()] + if hmac.Equal(expectedMac, mac) != true { + return "", errors.New("Incorrect MAC for the given ciphertext") + } - block, err := aes.NewCipher(key) + block, err := aes.NewCipher(ekey) if err != nil { return "", err } @@ -362,7 +388,7 @@ func decrypt(key []byte, cryptoText string) (string, error) { return "", errors.New("ciphertext too short") } iv := ciphertext[:aes.BlockSize] - ciphertext = ciphertext[aes.BlockSize:] + ciphertext = ciphertext[aes.BlockSize+macfn.Size():] stream := cipher.NewCFBDecrypter(block, iv) -- cgit v1.2.3-1-g7c22