key_store_passphrase.go 5.63 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*
	This file is part of go-ethereum

	go-ethereum is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	go-ethereum is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with go-ethereum.  If not, see <http://www.gnu.org/licenses/>.
*/
/**
 * @authors
 * 	Gustav Simonsson <gustav.simonsson@gmail.com>
 * @date 2015
 *
 */
23

24 25
/*

Gustav Simonsson's avatar
Gustav Simonsson committed
26 27
This key store behaves as KeyStorePlain with the difference that
the private key is encrypted and on disk uses another JSON encoding.
28 29 30 31 32 33 34 35 36 37 38 39 40 41

Cryptography:

1. Encryption key is scrypt derived key from user passphrase. Scrypt parameters
   (work factors) [1][2] are defined as constants below.
2. Scrypt salt is 32 random bytes from CSPRNG. It is appended to ciphertext.
3. Checksum is SHA3 of the private key bytes.
4. Plaintext is concatenation of private key bytes and checksum.
5. Encryption algo is AES 256 CBC [3][4]
6. CBC IV is 16 random bytes from CSPRNG. It is appended to ciphertext.
7. Plaintext padding is PKCS #7 [5][6]

Encoding:

Gustav Simonsson's avatar
Gustav Simonsson committed
42
1. On disk, ciphertext, salt and IV are encoded in a nested JSON object.
43
   cat a key file to see the structure.
Gustav Simonsson's avatar
Gustav Simonsson committed
44
2. byte arrays are base64 JSON strings.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
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].
4. The checksum is the last 32 bytes of the plaintext byte array and the
   private key is the preceeding bytes.

References:

1. http://www.tarsnap.com/scrypt/scrypt-slides.pdf
2. http://stackoverflow.com/questions/11126315/what-are-optimal-scrypt-work-factors
3. http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
4. http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher-block_chaining_.28CBC.29
5. https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes
6. http://tools.ietf.org/html/rfc2315
7. http://bitcoin.stackexchange.com/questions/3059/what-is-a-compressed-bitcoin-key
8. http://golang.org/pkg/crypto/ecdsa/#PrivateKey
9. https://golang.org/pkg/math/big/#Int.Bytes

*/

package crypto

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
70
	"encoding/hex"
71 72
	"encoding/json"
	"errors"
73
	"io"
74 75
	"os"
	"path"
76 77 78 79

	"code.google.com/p/go-uuid/uuid"
	"github.com/ethereum/go-ethereum/crypto/randentropy"
	"golang.org/x/crypto/scrypt"
80 81
)

82 83 84 85 86 87 88
const (
	// 2^18 / 8 / 1 uses 256MB memory and approx 1s CPU time on a modern CPU.
	scryptN     = 1 << 18
	scryptr     = 8
	scryptp     = 1
	scryptdkLen = 32
)
89

90
type keyStorePassphrase struct {
91 92 93
	keysDirPath string
}

94
func NewKeyStorePassphrase(path string) KeyStore2 {
95
	return &keyStorePassphrase{path}
96 97
}

98 99
func (ks keyStorePassphrase) GenerateNewKey(rand io.Reader, auth string) (key *Key, err error) {
	return GenerateNewKeyDefault(ks, rand, auth)
100 101
}

102 103
func (ks keyStorePassphrase) GetKey(keyAddr []byte, auth string) (key *Key, err error) {
	keyBytes, keyId, err := DecryptKey(ks, keyAddr, auth)
104 105 106
	if err != nil {
		return nil, err
	}
107
	key = &Key{
108 109
		Id:         uuid.UUID(keyId),
		Address:    keyAddr,
110
		PrivateKey: ToECDSA(keyBytes),
111
	}
112
	return key, err
113 114
}

115 116 117 118
func (ks keyStorePassphrase) GetKeyAddresses() (addresses [][]byte, err error) {
	return GetKeyAddresses(ks.keysDirPath)
}

119
func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) {
120
	authArray := []byte(auth)
121
	salt := randentropy.GetEntropyMixed(32)
122 123
	derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptr, scryptp, scryptdkLen)
	if err != nil {
124
		return err
125 126 127 128 129 130 131 132
	}

	keyBytes := FromECDSA(key.PrivateKey)
	keyBytesHash := Sha3(keyBytes)
	toEncrypt := PKCS7Pad(append(keyBytes, keyBytesHash...))

	AES256Block, err := aes.NewCipher(derivedKey)
	if err != nil {
133
		return err
134 135
	}

136
	iv := randentropy.GetEntropyMixed(aes.BlockSize) // 16
137 138 139 140
	AES256CBCEncrypter := cipher.NewCBCEncrypter(AES256Block, iv)
	cipherText := make([]byte, len(toEncrypt))
	AES256CBCEncrypter.CryptBlocks(cipherText, toEncrypt)

141 142 143 144
	cipherStruct := cipherJSON{
		salt,
		iv,
		cipherText,
145
	}
146
	keyStruct := encryptedKeyJSON{
147 148
		key.Id,
		key.Address,
149 150 151 152
		cipherStruct,
	}
	keyJSON, err := json.Marshal(keyStruct)
	if err != nil {
153
		return err
154 155
	}

156
	return WriteKeyFile(key.Address, ks.keysDirPath, keyJSON)
157 158
}

159
func (ks keyStorePassphrase) DeleteKey(keyAddr []byte, auth string) (err error) {
160
	// only delete if correct passphrase is given
161
	_, _, err = DecryptKey(ks, keyAddr, auth)
162
	if err != nil {
163
		return err
164 165
	}

166
	keyDirPath := path.Join(ks.keysDirPath, hex.EncodeToString(keyAddr))
167
	return os.RemoveAll(keyDirPath)
168 169
}

170 171
func DecryptKey(ks keyStorePassphrase, keyAddr []byte, auth string) (keyBytes []byte, keyId []byte, err error) {
	fileContent, err := GetKeyFile(ks.keysDirPath, keyAddr)
172
	if err != nil {
173
		return nil, nil, err
174 175
	}

176
	keyProtected := new(encryptedKeyJSON)
177 178
	err = json.Unmarshal(fileContent, keyProtected)

179
	keyId = keyProtected.Id
180 181 182
	salt := keyProtected.Crypto.Salt
	iv := keyProtected.Crypto.IV
	cipherText := keyProtected.Crypto.CipherText
183 184 185 186

	authArray := []byte(auth)
	derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptr, scryptp, scryptdkLen)
	if err != nil {
187
		return nil, nil, err
188
	}
189
	plainText, err := aesCBCDecrypt(derivedKey, cipherText, iv)
190
	if err != nil {
191
		return nil, nil, err
192 193 194 195 196
	}
	keyBytes = plainText[:len(plainText)-32]
	keyBytesHash := plainText[len(plainText)-32:]
	if !bytes.Equal(Sha3(keyBytes), keyBytesHash) {
		err = errors.New("Decryption failed: checksum mismatch")
197
		return nil, nil, err
198
	}
199
	return keyBytes, keyId, err
200
}