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  }