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.
Этот коммит содержится в:
Frank Denis
2015-06-24 16:29:45 +02:00
родитель 5bc0a19953
Коммит 34d688ca72

Просмотреть файл

@@ -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
}
block, err := aes.NewCipher(key)
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(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)