github.com/hashgraph/hedera-sdk-go/v2@v2.48.0/keystore.go (about) 1 package hedera 2 3 /*- 4 * 5 * Hedera Go SDK 6 * 7 * Copyright (C) 2020 - 2024 Hedera Hashgraph, LLC 8 * 9 * Licensed under the Apache License, Version 2.0 (the "License"); 10 * you may not use this file except in compliance with the License. 11 * You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, software 16 * distributed under the License is distributed on an "AS IS" BASIS, 17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 * See the License for the specific language governing permissions and 19 * limitations under the License. 20 * 21 */ 22 23 import ( 24 "crypto/aes" 25 cipher2 "crypto/cipher" 26 "crypto/hmac" 27 "crypto/rand" 28 "crypto/sha256" 29 "crypto/sha512" 30 "crypto/subtle" 31 "encoding/hex" 32 "encoding/json" 33 34 "io" 35 36 "golang.org/x/crypto/pbkdf2" 37 ) 38 39 type _Keystore struct { 40 Version uint8 `json:"version"` 41 Crypto _CryptoData `json:"crypto"` 42 } 43 44 // internal struct used for cipher parameters 45 type _CipherParams struct { 46 // hex-encoded initialization vector 47 IV string `json:"iv"` 48 } 49 50 // internal struct used for kdf parameters 51 type _KdfParams struct { 52 // derived key length 53 DKLength int `json:"dklength"` 54 // hex-encoded salt 55 Salt string `json:"salt"` 56 // iteration count 57 Count int `json:"c"` 58 // hash function 59 PRF string `json:"prf"` 60 } 61 62 // internal type used in _Keystore to represent the crypto data 63 type _CryptoData struct { 64 // hex-encoded ciphertext 65 CipherText string `json:"ciphertext"` 66 CipherParams _CipherParams `json:"cipherparams"` 67 // Cipher being used 68 Cipher string `json:"cipher"` 69 // key derivation function being used 70 KDF string `json:"kdf"` 71 // parameters for key derivation function 72 KDFParams _KdfParams `json:"kdfparams"` 73 // hex-encoded HMAC-SHA384 74 Mac string `json:"mac"` 75 } 76 77 const Aes128Ctr = "aes-128-ctr" 78 const HmacSha256 = "hmac-sha256" 79 80 // all values taken from https://github.com/ethereumjs/ethereumjs-wallet/blob/de3a92e752673ada1d78f95cf80bc56ae1f59775/src/index.ts#L25 81 const dkLen int = 32 82 const c int = 262144 83 const saltLen uint = 32 84 85 func _RandomBytes(n uint) ([]byte, error) { 86 // based on https://github.com/gophercon/2016-talks/tree/master/GeorgeTankersley-CryptoForGoDevelopers 87 b := make([]byte, n) 88 _, err := io.ReadFull(rand.Reader, b) 89 if err != nil { 90 return nil, err 91 } 92 93 return b, nil 94 } 95 96 func _NewKeystore(privateKey []byte, passphrase string) ([]byte, error) { 97 salt, err := _RandomBytes(saltLen) 98 if err != nil { 99 return nil, err 100 } 101 102 key := pbkdf2.Key([]byte(passphrase), salt, c, dkLen, sha256.New) 103 104 iv, err := _RandomBytes(16) 105 if err != nil { 106 return nil, err 107 } 108 109 // AES-128-CTR with the first half of the derived key and a random IV 110 block, err := aes.NewCipher(key[0:16]) 111 if err != nil { 112 return nil, err 113 } 114 115 cipher := cipher2.NewCTR(block, iv) 116 cipherText := make([]byte, len(privateKey)) 117 cipher.XORKeyStream(cipherText, privateKey) 118 119 h := hmac.New(sha512.New384, key[16:]) 120 121 if _, err = h.Write(cipherText); err != nil { 122 return nil, err 123 } 124 125 mac := h.Sum(nil) 126 127 keystore := _Keystore{ 128 Version: 1, 129 Crypto: _CryptoData{ 130 CipherText: hex.EncodeToString(cipherText), 131 CipherParams: _CipherParams{ 132 IV: hex.EncodeToString(iv), 133 }, 134 Cipher: Aes128Ctr, 135 KDF: "pbkdf2", 136 KDFParams: _KdfParams{ 137 DKLength: dkLen, 138 Salt: hex.EncodeToString(salt), 139 Count: c, 140 PRF: HmacSha256, 141 }, 142 Mac: hex.EncodeToString(mac), 143 }, 144 } 145 146 return json.Marshal(keystore) 147 } 148 149 func _ParseKeystore(keystoreBytes []byte, passphrase string) (PrivateKey, error) { 150 keyStore := _Keystore{} 151 152 err := json.Unmarshal(keystoreBytes, &keyStore) 153 154 if err != nil { 155 return PrivateKey{}, err 156 } 157 158 if keyStore.Version != 1 { 159 // todo: change to a switch and handle differently if future _Keystore versions are added 160 return PrivateKey{}, _NewErrBadKeyf("unsupported _Keystore version: %v", keyStore.Version) 161 } 162 163 if keyStore.Crypto.KDF != "pbkdf2" { 164 return PrivateKey{}, _NewErrBadKeyf("unsupported KDF: %v", keyStore.Crypto.KDF) 165 } 166 167 if keyStore.Crypto.Cipher != Aes128Ctr { 168 return PrivateKey{}, _NewErrBadKeyf("unsupported _Keystore cipher: %v", keyStore.Crypto.Cipher) 169 } 170 171 if keyStore.Crypto.KDFParams.PRF != HmacSha256 { 172 return PrivateKey{}, _NewErrBadKeyf( 173 "unsupported PRF: %v", 174 keyStore.Crypto.KDFParams.PRF) 175 } 176 177 salt, err := hex.DecodeString(keyStore.Crypto.KDFParams.Salt) 178 179 if err != nil { 180 return PrivateKey{}, err 181 } 182 183 iv, err := hex.DecodeString(keyStore.Crypto.CipherParams.IV) 184 185 if err != nil { 186 return PrivateKey{}, err 187 } 188 189 cipherBytes, err := hex.DecodeString(keyStore.Crypto.CipherText) 190 191 if err != nil { 192 return PrivateKey{}, err 193 } 194 195 key := pbkdf2.Key([]byte(passphrase), salt, keyStore.Crypto.KDFParams.Count, dkLen, sha256.New) 196 197 mac, err := hex.DecodeString(keyStore.Crypto.Mac) 198 199 if err != nil { 200 return PrivateKey{}, err 201 } 202 203 h := hmac.New(sha512.New384, key[16:]) 204 205 _, err = h.Write(cipherBytes) 206 207 if err != nil { 208 return PrivateKey{}, err 209 } 210 211 verifyMac := h.Sum(nil) 212 213 if subtle.ConstantTimeCompare(mac, verifyMac) == 0 { 214 return PrivateKey{}, _NewErrBadKeyf("hmac mismatch; passphrase is incorrect") 215 } 216 217 block, err := aes.NewCipher(key[:16]) 218 if err != nil { 219 return PrivateKey{}, err 220 } 221 222 decipher := cipher2.NewCTR(block, iv) 223 pkBytes := make([]byte, len(cipherBytes)) 224 225 decipher.XORKeyStream(pkBytes, cipherBytes) 226 227 return PrivateKeyFromBytesEd25519(pkBytes) 228 }