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  }