提交 6b23094c 编写于 作者: G Gustav Simonsson

Improve key store passphrase crypto

* Change MAC-then-Encrypt to Encrypt-then-MAC
* Change AES256 to AES128
* Use first 16 bytes of KDF derived key for AES and
  remaining 16 for MAC
上级 9918b6c8
...@@ -252,7 +252,7 @@ func aesCBCDecrypt(key []byte, cipherText []byte, iv []byte) (plainText []byte, ...@@ -252,7 +252,7 @@ func aesCBCDecrypt(key []byte, cipherText []byte, iv []byte) (plainText []byte,
decrypter.CryptBlocks(paddedPlainText, cipherText) decrypter.CryptBlocks(paddedPlainText, cipherText)
plainText = PKCS7Unpad(paddedPlainText) plainText = PKCS7Unpad(paddedPlainText)
if plainText == nil { if plainText == nil {
err = errors.New("Decryption failed: PKCS7Unpad failed after decryption") err = errors.New("Decryption failed: PKCS7Unpad failed after AES decryption")
} }
return plainText, err return plainText, err
} }
......
...@@ -48,6 +48,7 @@ type plainKeyJSON struct { ...@@ -48,6 +48,7 @@ type plainKeyJSON struct {
} }
type cipherJSON struct { type cipherJSON struct {
MAC []byte
Salt []byte Salt []byte
IV []byte IV []byte
CipherText []byte CipherText []byte
......
...@@ -28,24 +28,25 @@ the private key is encrypted and on disk uses another JSON encoding. ...@@ -28,24 +28,25 @@ the private key is encrypted and on disk uses another JSON encoding.
Cryptography: Cryptography:
1. Encryption key is scrypt derived key from user passphrase. Scrypt parameters 1. Encryption key is first 16 bytes of SHA3-256 of first 16 bytes of
scrypt derived key from user passphrase. Scrypt parameters
(work factors) [1][2] are defined as constants below. (work factors) [1][2] are defined as constants below.
2. Scrypt salt is 32 random bytes from CSPRNG. It is appended to ciphertext. 2. Scrypt salt is 32 random bytes from CSPRNG.
3. Checksum is SHA3 of the private key bytes. It's stored in plain next to ciphertext in key file.
4. Plaintext is concatenation of private key bytes and checksum. 3. MAC is SHA3-256 of concatenation of ciphertext and last 16 bytes of scrypt derived key.
5. Encryption algo is AES 256 CBC [3][4] 4. Plaintext is the EC private key bytes.
6. CBC IV is 16 random bytes from CSPRNG. It is appended to ciphertext. 5. Encryption algo is AES 128 CBC [3][4]
6. CBC IV is 16 random bytes from CSPRNG.
It's stored in plain next to ciphertext in key file.
7. Plaintext padding is PKCS #7 [5][6] 7. Plaintext padding is PKCS #7 [5][6]
Encoding: Encoding:
1. On disk, ciphertext, salt and IV are encoded in a nested JSON object. 1. On disk, the ciphertext, MAC, salt and IV are encoded in a nested JSON object.
cat a key file to see the structure. cat a key file to see the structure.
2. byte arrays are base64 JSON strings. 2. byte arrays are base64 JSON strings.
3. The EC private key bytes are in uncompressed form [7]. 3. The EC private key bytes are in uncompressed form [7].
They are a big-endian byte slice of the absolute value of D [8][9]. They are a big-endian byte slice of the absolute value of D [8][9].
4. The checksum is the last 32 bytes of the plaintext byte array and the
private key is the preceeding bytes.
References: References:
...@@ -124,21 +125,25 @@ func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) { ...@@ -124,21 +125,25 @@ func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) {
return err return err
} }
encryptKey := Sha3(derivedKey[:16])[:16]
keyBytes := FromECDSA(key.PrivateKey) keyBytes := FromECDSA(key.PrivateKey)
keyBytesHash := Sha3(keyBytes) toEncrypt := PKCS7Pad(keyBytes)
toEncrypt := PKCS7Pad(append(keyBytes, keyBytesHash...))
AES256Block, err := aes.NewCipher(derivedKey) AES128Block, err := aes.NewCipher(encryptKey)
if err != nil { if err != nil {
return err return err
} }
iv := randentropy.GetEntropyCSPRNG(aes.BlockSize) // 16 iv := randentropy.GetEntropyCSPRNG(aes.BlockSize) // 16
AES256CBCEncrypter := cipher.NewCBCEncrypter(AES256Block, iv) AES128CBCEncrypter := cipher.NewCBCEncrypter(AES128Block, iv)
cipherText := make([]byte, len(toEncrypt)) cipherText := make([]byte, len(toEncrypt))
AES256CBCEncrypter.CryptBlocks(cipherText, toEncrypt) AES128CBCEncrypter.CryptBlocks(cipherText, toEncrypt)
mac := Sha3(derivedKey[16:32], cipherText)
cipherStruct := cipherJSON{ cipherStruct := cipherJSON{
mac,
salt, salt,
iv, iv,
cipherText, cipherText,
...@@ -177,6 +182,7 @@ func DecryptKey(ks keyStorePassphrase, keyAddr []byte, auth string) (keyBytes [] ...@@ -177,6 +182,7 @@ func DecryptKey(ks keyStorePassphrase, keyAddr []byte, auth string) (keyBytes []
err = json.Unmarshal(fileContent, keyProtected) err = json.Unmarshal(fileContent, keyProtected)
keyId = keyProtected.Id keyId = keyProtected.Id
mac := keyProtected.Crypto.MAC
salt := keyProtected.Crypto.Salt salt := keyProtected.Crypto.Salt
iv := keyProtected.Crypto.IV iv := keyProtected.Crypto.IV
cipherText := keyProtected.Crypto.CipherText cipherText := keyProtected.Crypto.CipherText
...@@ -186,15 +192,16 @@ func DecryptKey(ks keyStorePassphrase, keyAddr []byte, auth string) (keyBytes [] ...@@ -186,15 +192,16 @@ func DecryptKey(ks keyStorePassphrase, keyAddr []byte, auth string) (keyBytes []
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
plainText, err := aesCBCDecrypt(derivedKey, cipherText, iv)
if err != nil { calculatedMAC := Sha3(derivedKey[16:32], cipherText)
if !bytes.Equal(calculatedMAC, mac) {
err = errors.New("Decryption failed: MAC mismatch")
return nil, nil, err return nil, nil, err
} }
keyBytes = plainText[:len(plainText)-32]
keyBytesHash := plainText[len(plainText)-32:] plainText, err := aesCBCDecrypt(Sha3(derivedKey[:16])[:16], cipherText, iv)
if !bytes.Equal(Sha3(keyBytes), keyBytesHash) { if err != nil {
err = errors.New("Decryption failed: checksum mismatch")
return nil, nil, err return nil, nil, err
} }
return keyBytes, keyId, err return plainText, keyId, err
} }
package crypto package crypto
import ( import (
"github.com/ethereum/go-ethereum/crypto/randentropy"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto/randentropy"
"reflect" "reflect"
"testing" "testing"
) )
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册