forked from cerc-io/plugeth
Add ImportPreSaleKey
* ImportPreSaleKey takes a KeyStore, a presale key JSON (e.g. file content) and a password string. It stores the key in the given key store. * Refactored common AES decryption and moved some functions to crypto.go
This commit is contained in:
parent
4df2e1ef5c
commit
1f8290ca44
104
crypto/crypto.go
104
crypto/crypto.go
@ -1,12 +1,19 @@
|
|||||||
package crypto
|
package crypto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"code.google.com/p/go-uuid/uuid"
|
||||||
|
"code.google.com/p/go.crypto/pbkdf2"
|
||||||
"code.google.com/p/go.crypto/ripemd160"
|
"code.google.com/p/go.crypto/ripemd160"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"github.com/ethereum/go-ethereum/crypto/secp256k1"
|
"github.com/ethereum/go-ethereum/crypto/secp256k1"
|
||||||
"github.com/ethereum/go-ethereum/crypto/sha3"
|
"github.com/ethereum/go-ethereum/crypto/sha3"
|
||||||
"github.com/ethereum/go-ethereum/ethutil"
|
"github.com/ethereum/go-ethereum/ethutil"
|
||||||
@ -113,3 +120,100 @@ func Decrypt(prv *ecdsa.PrivateKey, ct []byte) ([]byte, error) {
|
|||||||
key := ecies.ImportECDSA(prv)
|
key := ecies.ImportECDSA(prv)
|
||||||
return key.Decrypt(rand.Reader, ct, nil, nil)
|
return key.Decrypt(rand.Reader, ct, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// creates a Key and stores that in the given KeyStore by decrypting a presale key JSON
|
||||||
|
func ImportPreSaleKey(keyStore KeyStore2, keyJSON []byte, password string) (*Key, error) {
|
||||||
|
key, err := decryptPreSaleKey(keyJSON, password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
id := uuid.NewRandom()
|
||||||
|
key.Id = &id
|
||||||
|
err = keyStore.StoreKey(key, password)
|
||||||
|
return key, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error) {
|
||||||
|
preSaleKeyStruct := struct {
|
||||||
|
EncSeed string
|
||||||
|
EthAddr string
|
||||||
|
Email string
|
||||||
|
BtcAddr string
|
||||||
|
}{}
|
||||||
|
err = json.Unmarshal(fileContent, &preSaleKeyStruct)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
encSeedBytes, err := hex.DecodeString(preSaleKeyStruct.EncSeed)
|
||||||
|
iv := encSeedBytes[:16]
|
||||||
|
cipherText := encSeedBytes[16:]
|
||||||
|
/*
|
||||||
|
See https://github.com/ethereum/pyethsaletool
|
||||||
|
|
||||||
|
pyethsaletool generates the encryption key from password by
|
||||||
|
2000 rounds of PBKDF2 with HMAC-SHA-256 using password as salt (:().
|
||||||
|
16 byte key length within PBKDF2 and resulting key is used as AES key
|
||||||
|
*/
|
||||||
|
passBytes := []byte(password)
|
||||||
|
derivedKey := pbkdf2.Key(passBytes, passBytes, 2000, 16, sha256.New)
|
||||||
|
plainText, err := aes_cbc_decrypt(derivedKey, cipherText, iv)
|
||||||
|
ethPriv := Sha3(plainText)
|
||||||
|
ecKey := ToECDSA(ethPriv)
|
||||||
|
key = &Key{
|
||||||
|
Id: nil,
|
||||||
|
PrivateKey: ecKey,
|
||||||
|
}
|
||||||
|
derivedAddr := ethutil.Bytes2Hex(key.Address())
|
||||||
|
expectedAddr := preSaleKeyStruct.EthAddr
|
||||||
|
if derivedAddr != expectedAddr {
|
||||||
|
err = errors.New("decrypted addr not equal to expected addr")
|
||||||
|
}
|
||||||
|
return key, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func aes_cbc_decrypt(key []byte, cipherText []byte, iv []byte) (plainText []byte, err error) {
|
||||||
|
aesBlock, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return plainText, err
|
||||||
|
}
|
||||||
|
decrypter := cipher.NewCBCDecrypter(aesBlock, iv)
|
||||||
|
paddedPlainText := make([]byte, len(cipherText))
|
||||||
|
decrypter.CryptBlocks(paddedPlainText, cipherText)
|
||||||
|
plainText = PKCS7Unpad(paddedPlainText)
|
||||||
|
if plainText == nil {
|
||||||
|
err = errors.New("Decryption failed: PKCS7Unpad failed after decryption")
|
||||||
|
}
|
||||||
|
return plainText, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// From https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes
|
||||||
|
func PKCS7Pad(in []byte) []byte {
|
||||||
|
padding := 16 - (len(in) % 16)
|
||||||
|
if padding == 0 {
|
||||||
|
padding = 16
|
||||||
|
}
|
||||||
|
for i := 0; i < padding; i++ {
|
||||||
|
in = append(in, byte(padding))
|
||||||
|
}
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|
||||||
|
func PKCS7Unpad(in []byte) []byte {
|
||||||
|
if len(in) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
padding := in[len(in)-1]
|
||||||
|
if int(padding) > len(in) || padding > aes.BlockSize {
|
||||||
|
return nil
|
||||||
|
} else if padding == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := len(in) - 1; i > len(in)-int(padding)-1; i-- {
|
||||||
|
if in[i] != padding {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return in[:len(in)-int(padding)]
|
||||||
|
}
|
||||||
|
@ -57,7 +57,7 @@ type encryptedKeyJSON struct {
|
|||||||
|
|
||||||
func (k *Key) Address() []byte {
|
func (k *Key) Address() []byte {
|
||||||
pubBytes := FromECDSAPub(&k.PrivateKey.PublicKey)
|
pubBytes := FromECDSAPub(&k.PrivateKey.PublicKey)
|
||||||
return Sha3(pubBytes)[12:]
|
return Sha3(pubBytes[1:])[12:]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *Key) MarshalJSON() (j []byte, err error) {
|
func (k *Key) MarshalJSON() (j []byte, err error) {
|
||||||
@ -99,9 +99,10 @@ func NewKey(rand io.Reader) *Key {
|
|||||||
privateKeyMarshalled := elliptic.Marshal(S256(), x, y)
|
privateKeyMarshalled := elliptic.Marshal(S256(), x, y)
|
||||||
privateKeyECDSA := ToECDSA(privateKeyMarshalled)
|
privateKeyECDSA := ToECDSA(privateKeyMarshalled)
|
||||||
|
|
||||||
key := new(Key)
|
|
||||||
id := uuid.NewRandom()
|
id := uuid.NewRandom()
|
||||||
key.Id = &id
|
key := &Key{
|
||||||
key.PrivateKey = privateKeyECDSA
|
Id: &id,
|
||||||
|
PrivateKey: privateKeyECDSA,
|
||||||
|
}
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
@ -178,22 +178,10 @@ func DecryptKey(ks keyStorePassphrase, keyId *uuid.UUID, auth string) (keyBytes
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
plainText, err := aes_cbc_decrypt(derivedKey, cipherText, iv)
|
||||||
AES256Block, err := aes.NewCipher(derivedKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
AES256CBCDecrypter := cipher.NewCBCDecrypter(AES256Block, iv)
|
|
||||||
paddedPlainText := make([]byte, len(cipherText))
|
|
||||||
AES256CBCDecrypter.CryptBlocks(paddedPlainText, cipherText)
|
|
||||||
|
|
||||||
plainText := PKCS7Unpad(paddedPlainText)
|
|
||||||
if plainText == nil {
|
|
||||||
err = errors.New("Decryption failed: PKCS7Unpad failed after decryption")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
keyBytes = plainText[:len(plainText)-32]
|
keyBytes = plainText[:len(plainText)-32]
|
||||||
keyBytesHash := plainText[len(plainText)-32:]
|
keyBytesHash := plainText[len(plainText)-32:]
|
||||||
if !bytes.Equal(Sha3(keyBytes), keyBytesHash) {
|
if !bytes.Equal(Sha3(keyBytes), keyBytesHash) {
|
||||||
@ -211,35 +199,3 @@ func getEntropyCSPRNG(n int) []byte {
|
|||||||
}
|
}
|
||||||
return mainBuff
|
return mainBuff
|
||||||
}
|
}
|
||||||
|
|
||||||
// From https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes
|
|
||||||
func PKCS7Pad(in []byte) []byte {
|
|
||||||
padding := 16 - (len(in) % 16)
|
|
||||||
if padding == 0 {
|
|
||||||
padding = 16
|
|
||||||
}
|
|
||||||
for i := 0; i < padding; i++ {
|
|
||||||
in = append(in, byte(padding))
|
|
||||||
}
|
|
||||||
return in
|
|
||||||
}
|
|
||||||
|
|
||||||
func PKCS7Unpad(in []byte) []byte {
|
|
||||||
if len(in) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
padding := in[len(in)-1]
|
|
||||||
if int(padding) > len(in) || padding > aes.BlockSize {
|
|
||||||
return nil
|
|
||||||
} else if padding == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := len(in) - 1; i > len(in)-int(padding)-1; i-- {
|
|
||||||
if in[i] != padding {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return in[:len(in)-int(padding)]
|
|
||||||
}
|
|
||||||
|
@ -83,3 +83,16 @@ func TestKeyStorePassphraseDecryptionFail(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImportPreSaleKey(t *testing.T) {
|
||||||
|
// file content of a presale key file generated with:
|
||||||
|
// python pyethsaletool.py genwallet
|
||||||
|
// with password "foo"
|
||||||
|
fileContent := "{\"encseed\": \"26d87f5f2bf9835f9a47eefae571bc09f9107bb13d54ff12a4ec095d01f83897494cf34f7bed2ed34126ecba9db7b62de56c9d7cd136520a0427bfb11b8954ba7ac39b90d4650d3448e31185affcd74226a68f1e94b1108e6e0a4a91cdd83eba\", \"ethaddr\": \"d4584b5f6229b7be90727b0fc8c6b91bb427821f\", \"email\": \"gustav.simonsson@gmail.com\", \"btcaddr\": \"1EVknXyFC68kKNLkh6YnKzW41svSRoaAcx\"}"
|
||||||
|
ks := NewKeyStorePassphrase(DefaultDataDir())
|
||||||
|
pass := "foo"
|
||||||
|
_, err := ImportPreSaleKey(ks, []byte(fileContent), pass)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user