2 This key store behaves as KeyStorePlain with the difference that
3 the private key is encrypted and on disk uses another JSON encoding.
19 "github.com/pborman/uuid"
20 "github.com/vapor/crypto"
21 "github.com/vapor/crypto/ed25519/chainkd"
22 "github.com/vapor/crypto/randentropy"
23 "golang.org/x/crypto/pbkdf2"
24 "golang.org/x/crypto/scrypt"
28 keyHeaderKDF = "scrypt"
30 // StandardScryptN n,r,p = 2^18, 8, 1 uses 256MB memory and approx 1s CPU time on a modern CPU.
31 StandardScryptN = 1 << 18
32 // StandardScryptP fit above
35 // LightScryptN n,r,p = 2^12, 8, 6 uses 4MB memory and approx 100ms CPU time on a modern CPU.
36 LightScryptN = 1 << 12
37 //LightScryptP fit above
44 type keyStorePassphrase struct {
50 func (ks keyStorePassphrase) GetKey(alias string, filename, auth string) (*XKey, error) {
51 // Load the key from the keystore and decrypt its contents
52 keyjson, err := ioutil.ReadFile(filename)
56 key, err := DecryptKey(keyjson, auth)
60 // Make sure we're really operating on the requested key (no swap attacks)
61 if key.Alias != alias {
62 return nil, fmt.Errorf("key content mismatch: have account %x, want %x", key.Alias, alias)
67 func (ks keyStorePassphrase) StoreKey(filename string, key *XKey, auth string) error {
68 keyjson, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP)
72 return writeKeyFile(filename, keyjson)
75 func (ks keyStorePassphrase) JoinPath(filename string) string {
76 if filepath.IsAbs(filename) {
79 return filepath.Join(ks.keysDirPath, filename)
82 // EncryptKey encrypts a key using the specified scrypt parameters into a json
83 // blob that can be decrypted later on.
84 func EncryptKey(key *XKey, auth string, scryptN, scryptP int) ([]byte, error) {
85 authArray := []byte(auth)
86 salt := randentropy.GetEntropyCSPRNG(32)
87 derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptR, scryptP, scryptDKLen)
91 encryptKey := derivedKey[:16]
92 keyBytes := key.XPrv[:]
94 iv := randentropy.GetEntropyCSPRNG(aes.BlockSize) // 16
95 cipherText, err := aesCTRXOR(encryptKey, keyBytes, iv)
99 mac := crypto.Sha256(derivedKey[16:32], cipherText)
100 scryptParamsJSON := make(map[string]interface{}, 5)
101 scryptParamsJSON["n"] = scryptN
102 scryptParamsJSON["r"] = scryptR
103 scryptParamsJSON["p"] = scryptP
104 scryptParamsJSON["dklen"] = scryptDKLen
105 scryptParamsJSON["salt"] = hex.EncodeToString(salt)
107 cipherParamsJSON := cipherparamsJSON{
108 IV: hex.EncodeToString(iv),
110 cryptoStruct := cryptoJSON{
111 Cipher: "aes-128-ctr",
112 CipherText: hex.EncodeToString(cipherText),
113 CipherParams: cipherParamsJSON,
115 KDFParams: scryptParamsJSON,
116 MAC: hex.EncodeToString(mac),
118 encryptedKeyJSON := encryptedKeyJSON{
124 hex.EncodeToString(key.XPub[:]),
126 return json.Marshal(encryptedKeyJSON)
129 // DecryptKey decrypts a key from a json blob, returning the private key itself.
130 func DecryptKey(keyjson []byte, auth string) (*XKey, error) {
131 // Parse the json into a simple map to fetch the key version
132 m := make(map[string]interface{})
133 if err := json.Unmarshal(keyjson, &m); err != nil {
136 // Depending on the version try to parse one way or another
138 keyBytes, keyID []byte
141 k := new(encryptedKeyJSON)
142 if err := json.Unmarshal(keyjson, k); err != nil {
146 keyBytes, keyID, err = decryptKey(k, auth)
147 // Handle any decryption errors and return the key
151 var xprv chainkd.XPrv
152 copy(xprv[:], keyBytes[:])
155 //key := crypto.ToECDSA(keyBytes)
157 ID: uuid.UUID(keyID),
165 func decryptKey(keyProtected *encryptedKeyJSON, auth string) (keyBytes []byte, keyID []byte, err error) {
166 if keyProtected.Version != version {
167 return nil, nil, fmt.Errorf("Version not supported: %v", keyProtected.Version)
170 if keyProtected.Type != keytype {
171 return nil, nil, fmt.Errorf("Key type not supported: %v", keyProtected.Type)
174 if keyProtected.Crypto.Cipher != "aes-128-ctr" {
175 return nil, nil, fmt.Errorf("Cipher not supported: %v", keyProtected.Crypto.Cipher)
178 keyID = uuid.Parse(keyProtected.ID)
179 mac, err := hex.DecodeString(keyProtected.Crypto.MAC)
184 iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV)
189 cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText)
194 derivedKey, err := getKDFKey(keyProtected.Crypto, auth)
199 calculatedMAC := crypto.Sha256(derivedKey[16:32], cipherText)
201 if !bytes.Equal(calculatedMAC, mac) {
202 return nil, nil, ErrDecrypt
205 plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
209 return plainText, keyID, err
212 func getKDFKey(cryptoJSON cryptoJSON, auth string) ([]byte, error) {
213 authArray := []byte(auth)
214 salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string))
218 dkLen := ensureInt(cryptoJSON.KDFParams["dklen"])
220 if cryptoJSON.KDF == "scrypt" {
221 n := ensureInt(cryptoJSON.KDFParams["n"])
222 r := ensureInt(cryptoJSON.KDFParams["r"])
223 p := ensureInt(cryptoJSON.KDFParams["p"])
224 return scrypt.Key(authArray, salt, n, r, p, dkLen)
226 } else if cryptoJSON.KDF == "pbkdf2" {
227 c := ensureInt(cryptoJSON.KDFParams["c"])
228 prf := cryptoJSON.KDFParams["prf"].(string)
229 if prf != "hmac-sha256" {
230 return nil, fmt.Errorf("Unsupported PBKDF2 PRF: %s", prf)
232 key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New)
236 return nil, fmt.Errorf("Unsupported KDF: %s", cryptoJSON.KDF)
239 // TODO: can we do without this when unmarshalling dynamic JSON?
240 // why do integers in KDF params end up as float64 and not int after
242 func ensureInt(x interface{}) int {
245 res = int(x.(float64))
250 func aesCTRXOR(key, inText, iv []byte) ([]byte, error) {
251 // AES-128 is selected due to size of encryptKey.
252 aesBlock, err := aes.NewCipher(key)
256 stream := cipher.NewCTR(aesBlock, iv)
257 outText := make([]byte, len(inText))
258 stream.XORKeyStream(outText, inText)
262 func aesCBCDecrypt(key, cipherText, iv []byte) ([]byte, error) {
263 aesBlock, err := aes.NewCipher(key)
267 decrypter := cipher.NewCBCDecrypter(aesBlock, iv)
268 paddedPlaintext := make([]byte, len(cipherText))
269 decrypter.CryptBlocks(paddedPlaintext, cipherText)
270 plaintext := pkcs7Unpad(paddedPlaintext)
271 if plaintext == nil {
272 return nil, ErrDecrypt
274 return plaintext, err
277 // From https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes
278 func pkcs7Unpad(in []byte) []byte {
283 padding := in[len(in)-1]
284 if int(padding) > len(in) || padding > aes.BlockSize {
286 } else if padding == 0 {
290 for i := len(in) - 1; i > len(in)-int(padding)-1; i-- {
291 if in[i] != padding {
295 return in[:len(in)-int(padding)]