github.com/jonasnick/go-ethereum@v0.7.12-0.20150216215225-22176f05d387/crypto/key_store_passphrase.go (about) 1 /* 2 This file is part of go-ethereum 3 4 go-ethereum is free software: you can redistribute it and/or modify 5 it under the terms of the GNU Lesser General Public License as published by 6 the Free Software Foundation, either version 3 of the License, or 7 (at your option) any later version. 8 9 go-ethereum is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 GNU General Public License for more details. 13 14 You should have received a copy of the GNU Lesser General Public License 15 along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 /** 18 * @authors 19 * Gustav Simonsson <gustav.simonsson@gmail.com> 20 * @date 2015 21 * 22 */ 23 /* 24 25 This key store behaves as KeyStorePlain with the difference that 26 the private key is encrypted and on disk uses another JSON encoding. 27 28 Cryptography: 29 30 1. Encryption key is scrypt derived key from user passphrase. Scrypt parameters 31 (work factors) [1][2] are defined as constants below. 32 2. Scrypt salt is 32 random bytes from CSPRNG. It is appended to ciphertext. 33 3. Checksum is SHA3 of the private key bytes. 34 4. Plaintext is concatenation of private key bytes and checksum. 35 5. Encryption algo is AES 256 CBC [3][4] 36 6. CBC IV is 16 random bytes from CSPRNG. It is appended to ciphertext. 37 7. Plaintext padding is PKCS #7 [5][6] 38 39 Encoding: 40 41 1. On disk, ciphertext, salt and IV are encoded in a nested JSON object. 42 cat a key file to see the structure. 43 2. byte arrays are base64 JSON strings. 44 3. The EC private key bytes are in uncompressed form [7]. 45 They are a big-endian byte slice of the absolute value of D [8][9]. 46 4. The checksum is the last 32 bytes of the plaintext byte array and the 47 private key is the preceeding bytes. 48 49 References: 50 51 1. http://www.tarsnap.com/scrypt/scrypt-slides.pdf 52 2. http://stackoverflow.com/questions/11126315/what-are-optimal-scrypt-work-factors 53 3. http://en.wikipedia.org/wiki/Advanced_Encryption_Standard 54 4. http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher-block_chaining_.28CBC.29 55 5. https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes 56 6. http://tools.ietf.org/html/rfc2315 57 7. http://bitcoin.stackexchange.com/questions/3059/what-is-a-compressed-bitcoin-key 58 8. http://golang.org/pkg/crypto/ecdsa/#PrivateKey 59 9. https://golang.org/pkg/math/big/#Int.Bytes 60 61 */ 62 63 package crypto 64 65 import ( 66 "bytes" 67 "code.google.com/p/go-uuid/uuid" 68 "code.google.com/p/go.crypto/scrypt" 69 "crypto/aes" 70 "crypto/cipher" 71 "encoding/hex" 72 "encoding/json" 73 "errors" 74 "github.com/jonasnick/go-ethereum/crypto/randentropy" 75 "io" 76 "os" 77 "path" 78 ) 79 80 const ( 81 // 2^18 / 8 / 1 uses 256MB memory and approx 1s CPU time on a modern CPU. 82 scryptN = 1 << 18 83 scryptr = 8 84 scryptp = 1 85 scryptdkLen = 32 86 ) 87 88 type keyStorePassphrase struct { 89 keysDirPath string 90 } 91 92 func NewKeyStorePassphrase(path string) KeyStore2 { 93 return &keyStorePassphrase{path} 94 } 95 96 func (ks keyStorePassphrase) GenerateNewKey(rand io.Reader, auth string) (key *Key, err error) { 97 return GenerateNewKeyDefault(ks, rand, auth) 98 } 99 100 func (ks keyStorePassphrase) GetKey(keyAddr []byte, auth string) (key *Key, err error) { 101 keyBytes, keyId, err := DecryptKey(ks, keyAddr, auth) 102 if err != nil { 103 return nil, err 104 } 105 key = &Key{ 106 Id: uuid.UUID(keyId), 107 Address: keyAddr, 108 PrivateKey: ToECDSA(keyBytes), 109 } 110 return key, err 111 } 112 113 func (ks keyStorePassphrase) GetKeyAddresses() (addresses [][]byte, err error) { 114 return GetKeyAddresses(ks.keysDirPath) 115 } 116 117 func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) { 118 authArray := []byte(auth) 119 salt := randentropy.GetEntropyMixed(32) 120 derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptr, scryptp, scryptdkLen) 121 if err != nil { 122 return err 123 } 124 125 keyBytes := FromECDSA(key.PrivateKey) 126 keyBytesHash := Sha3(keyBytes) 127 toEncrypt := PKCS7Pad(append(keyBytes, keyBytesHash...)) 128 129 AES256Block, err := aes.NewCipher(derivedKey) 130 if err != nil { 131 return err 132 } 133 134 iv := randentropy.GetEntropyMixed(aes.BlockSize) // 16 135 AES256CBCEncrypter := cipher.NewCBCEncrypter(AES256Block, iv) 136 cipherText := make([]byte, len(toEncrypt)) 137 AES256CBCEncrypter.CryptBlocks(cipherText, toEncrypt) 138 139 cipherStruct := cipherJSON{ 140 salt, 141 iv, 142 cipherText, 143 } 144 keyStruct := encryptedKeyJSON{ 145 key.Id, 146 key.Address, 147 cipherStruct, 148 } 149 keyJSON, err := json.Marshal(keyStruct) 150 if err != nil { 151 return err 152 } 153 154 return WriteKeyFile(key.Address, ks.keysDirPath, keyJSON) 155 } 156 157 func (ks keyStorePassphrase) DeleteKey(keyAddr []byte, auth string) (err error) { 158 // only delete if correct passphrase is given 159 _, _, err = DecryptKey(ks, keyAddr, auth) 160 if err != nil { 161 return err 162 } 163 164 keyDirPath := path.Join(ks.keysDirPath, hex.EncodeToString(keyAddr)) 165 return os.RemoveAll(keyDirPath) 166 } 167 168 func DecryptKey(ks keyStorePassphrase, keyAddr []byte, auth string) (keyBytes []byte, keyId []byte, err error) { 169 fileContent, err := GetKeyFile(ks.keysDirPath, keyAddr) 170 if err != nil { 171 return nil, nil, err 172 } 173 174 keyProtected := new(encryptedKeyJSON) 175 err = json.Unmarshal(fileContent, keyProtected) 176 177 keyId = keyProtected.Id 178 salt := keyProtected.Crypto.Salt 179 iv := keyProtected.Crypto.IV 180 cipherText := keyProtected.Crypto.CipherText 181 182 authArray := []byte(auth) 183 derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptr, scryptp, scryptdkLen) 184 if err != nil { 185 return nil, nil, err 186 } 187 plainText, err := aesCBCDecrypt(derivedKey, cipherText, iv) 188 if err != nil { 189 return nil, nil, err 190 } 191 keyBytes = plainText[:len(plainText)-32] 192 keyBytesHash := plainText[len(plainText)-32:] 193 if !bytes.Equal(Sha3(keyBytes), keyBytesHash) { 194 err = errors.New("Decryption failed: checksum mismatch") 195 return nil, nil, err 196 } 197 return keyBytes, keyId, err 198 }