Skip to content

Commit

Permalink
Merge pull request #4 from corverroos/corver/iteroption
Browse files Browse the repository at this point in the history
Add cipher cost option
  • Loading branch information
mcdee committed Sep 21, 2022
2 parents 8afc48d + bfaf103 commit b122c68
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 12 deletions.
10 changes: 4 additions & 6 deletions encrypt.go
Expand Up @@ -28,14 +28,12 @@ import (

const (
// Scrypt parameters
scryptN = 262144
scryptr = 8
scryptp = 1
scryptKeyLen = 32

// PBKDF2 parameters
pbkdf2KeyLen = 32
pbkdf2c = 262144
pbkdf2PRF = "hmac-sha256"
)

Expand All @@ -57,9 +55,9 @@ func (e *Encryptor) Encrypt(secret []byte, passphrase string) (map[string]interf
var err error
switch e.cipher {
case "scrypt":
decryptionKey, err = scrypt.Key(normedPassphrase, salt, scryptN, scryptr, scryptp, scryptKeyLen)
decryptionKey, err = scrypt.Key(normedPassphrase, salt, e.cost, scryptr, scryptp, scryptKeyLen)
case "pbkdf2":
decryptionKey = pbkdf2.Key(normedPassphrase, salt, pbkdf2c, pbkdf2KeyLen, sha256.New)
decryptionKey = pbkdf2.Key(normedPassphrase, salt, e.cost, pbkdf2KeyLen, sha256.New)
default:
return nil, fmt.Errorf("unknown cipher %q", e.cipher)
}
Expand Down Expand Up @@ -98,7 +96,7 @@ func (e *Encryptor) Encrypt(secret []byte, passphrase string) (map[string]interf
Function: "scrypt",
Params: &ksKDFParams{
DKLen: scryptKeyLen,
N: scryptN,
N: e.cost,
P: scryptp,
R: scryptr,
Salt: hex.EncodeToString(salt),
Expand All @@ -110,7 +108,7 @@ func (e *Encryptor) Encrypt(secret []byte, passphrase string) (map[string]interf
Function: "pbkdf2",
Params: &ksKDFParams{
DKLen: pbkdf2KeyLen,
C: pbkdf2c,
C: e.cost,
PRF: pbkdf2PRF,
Salt: hex.EncodeToString(salt),
},
Expand Down
36 changes: 33 additions & 3 deletions encrypt_test.go
Expand Up @@ -26,6 +26,7 @@ func TestEncrypt(t *testing.T) {
tests := []struct {
name string
cipher string
costPower uint
secret []byte
passphrase string
err error
Expand Down Expand Up @@ -57,8 +58,9 @@ func TestEncrypt(t *testing.T) {
err: errors.New(`unknown cipher "unknown"`),
},
{
name: "Good",
cipher: "scrypt",
name: "Good",
cipher: "pbkdf2",
costPower: 10,
secret: []byte{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
Expand All @@ -67,11 +69,33 @@ func TestEncrypt(t *testing.T) {
},
passphrase: "wallet passphrase",
},
{
name: "LowCostScrypt",
cipher: "scrypt",
costPower: 10,
secret: []byte(""),
passphrase: "",
},
{
name: "LowCostPBKDF2",
cipher: "pbkdf2",
costPower: 10,
secret: []byte(""),
passphrase: "",
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
encryptor := keystorev4.New(keystorev4.WithCipher(test.cipher))
var options []keystorev4.Option
if test.cipher != "" {
options = append(options, keystorev4.WithCipher(test.cipher))
}
if test.costPower != 0 {
options = append(options, keystorev4.WithCost(t, test.costPower))
}

encryptor := keystorev4.New(options...)
_, err := encryptor.Encrypt(test.secret, test.passphrase)
if test.err != nil {
require.NotNil(t, err)
Expand All @@ -82,3 +106,9 @@ func TestEncrypt(t *testing.T) {
})
}
}

func TestWithCostPanic(t *testing.T) {
require.Panics(t, func() {
keystorev4.WithCost(nil, 1)
})
}
24 changes: 22 additions & 2 deletions encryptor.go
Expand Up @@ -12,9 +12,12 @@

package keystorev4

import "testing"

// Encryptor is an encryptor that follows the Ethereum keystore V4 specification.
type Encryptor struct {
cipher string
cost int
}

type ksKDFParams struct {
Expand Down Expand Up @@ -61,7 +64,8 @@ const (

// options are the options for the keystore encryptor.
type options struct {
cipher string
cipher string
costPower uint
}

// Option gives options to New
Expand All @@ -82,19 +86,35 @@ func WithCipher(cipher string) Option {
})
}

// WithCost sets the cipher key cost for the encryptor to 2^power overriding
// the default value of 18 (ie. 2^18=262144). Higher values increases the
// cost of an exhaustive search but makes encoding and decoding proportionally slower.
// This should only be in testing as it affects security. It panics if t is nil.
func WithCost(t *testing.T, costPower uint) Option {
if t == nil {
panic("nil testing parameter")
}
return optionFunc(func(o *options) {
o.costPower = costPower
})
}

// New creates a new keystore V4 encryptor.
// This takes the following options:
// - cipher: the cipher to use when encrypting the secret, can be either "pbkdf2" (default) or "scrypt"
// - costPower: the cipher key cost to use as power of 2, default is 18 (ie. 2^18).
func New(opts ...Option) *Encryptor {
options := options{
cipher: "pbkdf2",
cipher: "pbkdf2",
costPower: 18,
}
for _, o := range opts {
o.apply(&options)
}

return &Encryptor{
cipher: options.cipher,
cost: 1 << options.costPower,
}
}

Expand Down
16 changes: 15 additions & 1 deletion encryptor_test.go
Expand Up @@ -34,6 +34,7 @@ func TestRoundTrip(t *testing.T) {
input string
passphrase string
secret []byte
options []keystorev4.Option
err error
}{
{
Expand All @@ -55,11 +56,24 @@ func TestRoundTrip(t *testing.T) {
passphrase: "π”±π”’π”°π”±π”­π”žπ”°π”°π”΄π”¬π”―π”‘πŸ”‘",
secret: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0xd6, 0x68, 0x9c, 0x08, 0x5a, 0xe1, 0x65, 0x83, 0x1e, 0x93, 0x4f, 0xf7, 0x63, 0xae, 0x46, 0xa2, 0xa6, 0xc1, 0x72, 0xb3, 0xf1, 0xb6, 0x0a, 0x8c, 0xe2, 0x6f},
},
{
name: "LowCostScrypt",
input: `{"checksum":{"function":"sha256","message":"e4c3c7171f8ff54478868dbf1648dac50c6f38dafe5d9c8dd9f312b812f7fc44","params":{}},"cipher":{"function":"aes-128-ctr","message":"a71e9211932429462d3f6b032a800452651d0cf4517cc0f28c65be57df78f675","params":{"iv":"f68ce93072d0b6c6ca8afbc9b002cd89"}},"kdf":{"function":"scrypt","message":"","params":{"dklen":32,"n":1024,"p":1,"r":8,"salt":"316bd15fbca6d44e543f91762b66a29d2e3f590a9f7a42b9eff1dec48df0075f"}}}`,
passphrase: "π”±π”’π”°π”±π”­π”žπ”°π”°π”΄π”¬π”―π”‘πŸ”‘",
secret: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0xd6, 0x68, 0x9c, 0x08, 0x5a, 0xe1, 0x65, 0x83, 0x1e, 0x93, 0x4f, 0xf7, 0x63, 0xae, 0x46, 0xa2, 0xa6, 0xc1, 0x72, 0xb3, 0xf1, 0xb6, 0x0a, 0x8c, 0xe2, 0x6f},
options: []keystorev4.Option{keystorev4.WithCipher("scrypt"), keystorev4.WithCost(t, 10)},
}, {
name: "LowCostPBKDF2",
input: `{"checksum":{"function":"sha256","message":"57dbef6061fe5832064da342e92cac95917ff6d928d278919e3ddb7ae89c05c7","params":{}},"cipher":{"function":"aes-128-ctr","message":"cfe1132d3f0fe4f59d38f5eef01b80be32517448fa65dd0476171324cc3ab5fc","params":{"iv":"5975ccd92bc36290f082f134ec4c52bd"}},"kdf":{"function":"pbkdf2","message":"","params":{"c":1024,"dklen":32,"prf":"hmac-sha256","salt":"700ca70794d861f8f35d733e83c67431c893aa8e83b0dc43b6abd62edf9df0d1"}}}`,
passphrase: "π”±π”’π”°π”±π”­π”žπ”°π”°π”΄π”¬π”―π”‘πŸ”‘",
secret: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0xd6, 0x68, 0x9c, 0x08, 0x5a, 0xe1, 0x65, 0x83, 0x1e, 0x93, 0x4f, 0xf7, 0x63, 0xae, 0x46, 0xa2, 0xa6, 0xc1, 0x72, 0xb3, 0xf1, 0xb6, 0x0a, 0x8c, 0xe2, 0x6f},
options: []keystorev4.Option{keystorev4.WithCost(t, 10)},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
encryptor := keystorev4.New()
encryptor := keystorev4.New(test.options...)
input := make(map[string]interface{})
err := json.Unmarshal([]byte(test.input), &input)
require.Nil(t, err)
Expand Down

0 comments on commit b122c68

Please sign in to comment.