From c0d375d4bfa3fc856458bbab55c5722d79cd35a7 Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 9 Jan 2014 06:53:48 -0700 Subject: [PATCH 1/9] Begin adding ECDSA support. --- passvault/passvault.go | 75 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 69 insertions(+), 6 deletions(-) diff --git a/passvault/passvault.go b/passvault/passvault.go index d6028d1..cabd687 100644 --- a/passvault/passvault.go +++ b/passvault/passvault.go @@ -11,9 +11,12 @@ import ( "code.google.com/p/go.crypto/scrypt" "crypto/aes" "crypto/cipher" + "crypto/ecdsa" + "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/sha1" + "crypto/x509" "encoding/binary" "encoding/json" "errors" @@ -62,6 +65,11 @@ type PasswordRecord struct { RSAPrimeQIV []byte RSAPublic rsa.PublicKey } + ECKey struct { + ECPriv []byte + ECPrivIV []byte + ECPublic ecdsa.PublicKey + } Admin bool } @@ -135,9 +143,28 @@ func encryptRSARecord(newRec *PasswordRecord, rsaPriv *rsa.PrivateKey, passKey [ return } +// encryptECCRecord takes an ECDSA private key and encrypts it with +// a password key. +func encryptECCRecord(newRec *PasswordRecord, ecPriv *ecdsa.PrivateKey, passKey []byte) (err error) { + ecX509, err := x509.MarshalECPrivateKey(ecPriv) + if err != nil { + return + } + + if newRec.ECKey.ECPrivIV, err = makeRandom(16); err != nil { + return + } + + paddedX509 := padding.AddPadding(ecX509) + if newRec.ECKey.ECPriv, err = encryptCBC(paddedX509, newRec.ECKey.ECPrivIV, passKey); err != nil { + return + } + return +} + // createPasswordRec creates a new record from a username and password func createPasswordRec(password string, admin bool) (newRec PasswordRecord, err error) { - newRec.Type = RSARecord + newRec.Type = ECCRecord if newRec.PasswordSalt, err = makeRandom(16); err != nil { return @@ -157,17 +184,17 @@ func createPasswordRec(password string, admin bool) (newRec PasswordRecord, err } // generate a key pair - rsaPriv, err := rsa.GenerateKey(rand.Reader, 2048) + ecPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return } // encrypt RSA key with password key - if err = encryptRSARecord(&newRec, rsaPriv, passKey); err != nil { + if err = encryptECCRecord(&newRec, ecPriv, passKey); err != nil { return } - newRec.RSAKey.RSAPublic = rsaPriv.PublicKey + newRec.ECKey.ECPublic = ecPriv.PublicKey // encrypt AES key with password key aesKey, err := makeRandom(16) @@ -528,7 +555,7 @@ func (pr PasswordRecord) GetKeyAES(password string) (key []byte, err error) { return decryptECB(pr.AESKey, passKey) } -// GetKeyAES returns the RSA public key of the record. +// GetKeyRSAPub returns the RSA public key of the record. func (pr PasswordRecord) GetKeyRSAPub() (out *rsa.PublicKey, err error) { if pr.Type != RSARecord { return out, errors.New("Invalid function for record type") @@ -536,7 +563,43 @@ func (pr PasswordRecord) GetKeyRSAPub() (out *rsa.PublicKey, err error) { return &pr.RSAKey.RSAPublic, err } -// GetKeyAES returns the RSA private key of the record given the correct password. +// GetKeyECCPub returns the ECDSA public key out of the record. +func (pr PasswordRecord) GetKeyECCPub() (out *ecdsa.PublicKey, err error) { + if pr.Type != ECCRecord { + return out, errors.New("Invalid function for record type") + } + return &pr.ECKey.ECPublic, err +} + +// GetKeyECC returns the ECDSA private key of the record given the correct password. +func (pr PasswordRecord) GetKeyECC(password string) (key *ecdsa.PrivateKey, err error) { + if pr.Type != ECCRecord { + return key, errors.New("Invalid function for record type") + } + + err = pr.ValidatePassword(password) + if err != nil { + return + } + + passKey, err := derivePasswordKey(password, pr.KeySalt) + if err != nil { + return + } + + x509Padded, err := decryptCBC(pr.ECKey.ECPriv, pr.ECKey.ECPrivIV, passKey) + if err != nil { + return + } + + ecX509, err := padding.RemovePadding(x509Padded) + if err != nil { + return + } + return x509.ParseECPrivateKey(ecX509) +} + +// GetKeyRSA returns the RSA private key of the record given the correct password. func (pr PasswordRecord) GetKeyRSA(password string) (key rsa.PrivateKey, err error) { if pr.Type != RSARecord { return key, errors.New("Invalid function for record type") From 689f0e6fdc7dc5113c2b66c1ff51cd9d628b31a4 Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 9 Jan 2014 19:16:02 -0700 Subject: [PATCH 2/9] Add ECDH package. The package encrypts data using ECDHE with AES-128-CBC-HMAC-SHA1; this matches the other components. The curve used is P256 to match the use of AES-128. The Go ECDSA package is used; no signatures are done, but it presents usable PublicKey and PrivateKey types that are useful for this system. --- ecdh/ecdh.go | 141 ++++++++++++++++++++++++++++++++++++++++++++++ ecdh/ecdh_test.go | 36 ++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 ecdh/ecdh.go create mode 100644 ecdh/ecdh_test.go diff --git a/ecdh/ecdh.go b/ecdh/ecdh.go new file mode 100644 index 0000000..2264293 --- /dev/null +++ b/ecdh/ecdh.go @@ -0,0 +1,141 @@ +// Package ecdh encrypts and decrypts data using elliptic curve keys. Data +// is encrypted with AES-128-CBC with HMAC-SHA1 message tags using +// ECDHE to generate a shared key. The P256 curve is chosen in +// keeping with the use of AES-128 for encryption. +package ecdh + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/hmac" + "crypto/rand" + "crypto/sha1" + "errors" + "github.com/cloudflare/redoctober/padding" +) + +var Curve = elliptic.P256 + +func zero(in []byte) { + inLen := len(in) + for i := 0; i < inLen; i++ { + in[i] ^= in[i] + } +} + +// Encrypt secures and authenticates its input using the public key +// using ECDHE with AES-128-CBC-HMAC-SHA1. +func Encrypt(pub ecdsa.PublicKey, in []byte) (out []byte, err error) { + ephemeral, err := ecdsa.GenerateKey(Curve(), rand.Reader) + if err != nil { + return + } + x, _ := pub.Curve.ScalarMult(pub.X, pub.Y, ephemeral.D.Bytes()) + if x == nil { + return nil, errors.New("Failed to generate encryption key") + } + shared := x.Bytes() + iv, err := makeRandom(16) + if err != nil { + return + } + + paddedIn := padding.AddPadding(in) + ct, err := encryptCBC(paddedIn, iv, shared[:16]) + if err != nil { + return + } + + ephPub := elliptic.Marshal(pub.Curve, ephemeral.PublicKey.X, ephemeral.PublicKey.Y) + out = make([]byte, 1+len(ephPub)+16) + out[0] = byte(len(ephPub)) + copy(out[1:], ephPub) + copy(out[1+len(ephPub):], iv) + out = append(out, ct...) + + h := hmac.New(sha1.New, shared[16:]) + h.Write(iv) + h.Write(ct) + out = h.Sum(out) + return +} + +// Decrypt authentications and recovers the original message from +// its input using the private key and the ephemeral key included in +// the message. +func Decrypt(priv *ecdsa.PrivateKey, in []byte) (out []byte, err error) { + ephLen := int(in[0]) + ephPub := in[1 : 1+ephLen] + ct := in[1+ephLen:] + if len(ct) < (sha1.Size + aes.BlockSize) { + return nil, errors.New("Invalid ciphertext") + } + + x, y := elliptic.Unmarshal(Curve(), ephPub) + if x == nil { + return nil, errors.New("Invalid public key") + } + + x, _ = priv.Curve.ScalarMult(x, y, priv.D.Bytes()) + if x == nil { + return nil, errors.New("Failed to generate encryption key") + } + shared := x.Bytes() + + tagStart := len(ct) - sha1.Size + h := hmac.New(sha1.New, shared[16:]) + h.Write(ct[:tagStart]) + mac := h.Sum(nil) + if !hmac.Equal(mac, ct[tagStart:]) { + return nil, errors.New("Invalid MAC") + } + + paddedOut, err := decryptCBC(ct[aes.BlockSize:tagStart], ct[:aes.BlockSize], shared[:16]) + if err != nil { + return + } + out, err = padding.RemovePadding(paddedOut) + return +} + +// Utility functions copied from passvault. These handle encryption +// and decryption of data using AES-128-CBC. + +// decryptCBC decrypt bytes using a key and IV with AES in CBC mode. +func decryptCBC(data, iv, key []byte) (decryptedData []byte, err error) { + aesCrypt, err := aes.NewCipher(key) + if err != nil { + return + } + ivBytes := append([]byte{}, iv...) + + decryptedData = make([]byte, len(data)) + aesCBC := cipher.NewCBCDecrypter(aesCrypt, ivBytes) + aesCBC.CryptBlocks(decryptedData, data) + + return +} + +// encryptCBC encrypt data using a key and IV with AES in CBC mode. +func encryptCBC(data, iv, key []byte) (encryptedData []byte, err error) { + aesCrypt, err := aes.NewCipher(key) + if err != nil { + return + } + ivBytes := append([]byte{}, iv...) + + encryptedData = make([]byte, len(data)) + aesCBC := cipher.NewCBCEncrypter(aesCrypt, ivBytes) + aesCBC.CryptBlocks(encryptedData, data) + + return +} + +// makeRandom is a helper that makes a new buffer full of random data +func makeRandom(length int) ([]byte, error) { + bytes := make([]byte, length) + _, err := rand.Read(bytes) + return bytes, err +} diff --git a/ecdh/ecdh_test.go b/ecdh/ecdh_test.go new file mode 100644 index 0000000..0efbd28 --- /dev/null +++ b/ecdh/ecdh_test.go @@ -0,0 +1,36 @@ +package ecdh + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "testing" +) + +var testKey *ecdsa.PrivateKey + +func TestGenerateKey(t *testing.T) { + var err error + testKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("%v", err) + } +} + +func TestCrypt(t *testing.T) { + message := []byte("One ping only, please.") + out, err := Encrypt(testKey.PublicKey, message) + if err != nil { + t.Fatalf("%v", err) + } + + out, err = Decrypt(testKey, out) + if err != nil { + t.Fatalf("%v", err) + } + + if !bytes.Equal(out, message) { + t.Fatal("Decryption return different plaintext than original message.") + } +} From 5ea44c0ffb7a584edebeb6702549a83ba25cff15 Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 9 Jan 2014 19:18:16 -0700 Subject: [PATCH 3/9] Add ECC support to passvault. This adds code to handle ECC records; the RSA code remains intact. While old password records are not affected, new records use ECC. Due to the use of public keys to encrypt a KEK that is then used to encrypt data, there is no visible change to end users. A user with an RSA key can interact and share a secret with a user who has an ECC key. The bulk of this commit simply adds cases for handling EC records. --- passvault/passvault.go | 65 +++++++++++++++++++++++++++++-------- passvault/passvault_test.go | 28 ++++++++++++++++ 2 files changed, 80 insertions(+), 13 deletions(-) diff --git a/passvault/passvault.go b/passvault/passvault.go index cabd687..e655daf 100644 --- a/passvault/passvault.go +++ b/passvault/passvault.go @@ -20,6 +20,7 @@ import ( "encoding/binary" "encoding/json" "errors" + "github.com/cloudflare/redoctober/ecdh" "github.com/cloudflare/redoctober/padding" "io/ioutil" "math/big" @@ -34,6 +35,8 @@ const ( ECCRecord = "ECC" ) +var DefaultRecordType = ECCRecord + // Constants for scrypt const ( KEYLENGTH = 16 // 16-byte output from scrypt @@ -164,7 +167,7 @@ func encryptECCRecord(newRec *PasswordRecord, ecPriv *ecdsa.PrivateKey, passKey // createPasswordRec creates a new record from a username and password func createPasswordRec(password string, admin bool) (newRec PasswordRecord, err error) { - newRec.Type = ECCRecord + newRec.Type = DefaultRecordType if newRec.PasswordSalt, err = makeRandom(16); err != nil { return @@ -184,18 +187,31 @@ func createPasswordRec(password string, admin bool) (newRec PasswordRecord, err } // generate a key pair - ecPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - return + switch DefaultRecordType { + case RSARecord: + var rsaPriv *rsa.PrivateKey + rsaPriv, err = rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return + } + // encrypt RSA key with password key + if err = encryptRSARecord(&newRec, rsaPriv, passKey); err != nil { + return + } + newRec.RSAKey.RSAPublic = rsaPriv.PublicKey + case ECCRecord: + var ecPriv *ecdsa.PrivateKey + ecPriv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return + } + // encrypt ECDSA key with password key + if err = encryptECCRecord(&newRec, ecPriv, passKey); err != nil { + return + } + newRec.ECKey.ECPublic = ecPriv.PublicKey } - // encrypt RSA key with password key - if err = encryptECCRecord(&newRec, ecPriv, passKey); err != nil { - return - } - - newRec.ECKey.ECPublic = ecPriv.PublicKey - // encrypt AES key with password key aesKey, err := makeRandom(16) if err != nil { @@ -331,6 +347,11 @@ func InitFromDisk(path string) error { return formatErr } } + if rec.Type == ECCRecord { + if len(rec.ECKey.ECPriv) == 0 { + return formatErr + } + } } // If the Version field is 0 then it indicates that nothing was @@ -388,6 +409,7 @@ func ChangePassword(name, password, newPassword string) (err error) { // decrypt key var key []byte var rsaKey rsa.PrivateKey + var ecKey *ecdsa.PrivateKey if pr.Type == AESRecord { key, err = pr.GetKeyAES(password) if err != nil { @@ -398,6 +420,11 @@ func ChangePassword(name, password, newPassword string) (err error) { if err != nil { return } + } else if pr.Type == ECCRecord { + ecKey, err = pr.GetKeyECC(password) + if err != nil { + return + } } else { err = errors.New("Unkown record type") return @@ -431,6 +458,12 @@ func ChangePassword(name, password, newPassword string) (err error) { if err != nil { return } + } else if pr.Type == ECCRecord { + // encrypt ECDSA key with password key + err = encryptECCRecord(&pr, ecKey, newPassKey) + if err != nil { + return + } } else { err = errors.New("Unkown record type") return @@ -531,9 +564,15 @@ func (pr PasswordRecord) GetType() string { return pr.Type } -// EncryptKey encrypts a 16-byte key with the RSA key of the record. +// EncryptKey encrypts a 16-byte key with the RSA or EC key of the record. func (pr PasswordRecord) EncryptKey(in []byte) (out []byte, err error) { - return rsa.EncryptOAEP(sha1.New(), rand.Reader, &pr.RSAKey.RSAPublic, in, nil) + if pr.Type == RSARecord { + return rsa.EncryptOAEP(sha1.New(), rand.Reader, &pr.RSAKey.RSAPublic, in, nil) + } else if pr.Type == ECCRecord { + return ecdh.Encrypt(pr.ECKey.ECPublic, in) + } else { + return nil, errors.New("Invalid function for record type") + } } // GetKeyAES returns the 16-byte key of the record. diff --git a/passvault/passvault_test.go b/passvault/passvault_test.go index 6ceba9b..575e781 100644 --- a/passvault/passvault_test.go +++ b/passvault/passvault_test.go @@ -9,6 +9,8 @@ import ( ) func TestRSAEncryptDecrypt(t *testing.T) { + oldDefaultRecordType := DefaultRecordType + DefaultRecordType = RSARecord myRec, err := createPasswordRec("mypasswordisweak", true) if err != nil { t.Fatalf("Error creating record") @@ -33,4 +35,30 @@ func TestRSAEncryptDecrypt(t *testing.T) { if err != nil { t.Fatalf("Error validating RSA key") } + DefaultRecordType = oldDefaultRecordType +} + +func TestECCEncryptDecrypt(t *testing.T) { + oldDefaultRecordType := DefaultRecordType + DefaultRecordType = ECCRecord + myRec, err := createPasswordRec("mypasswordisweak", true) + if err != nil { + t.Fatalf("Error creating record") + } + + _, err = myRec.GetKeyECCPub() + if err != nil { + t.Fatalf("Error extracting EC pub") + } + + _, err = myRec.GetKeyECC("mypasswordiswrong") + if err == nil { + t.Fatalf("Incorrect password did not fail") + } + + _, err = myRec.GetKeyECC("mypasswordisweak") + if err != nil { + t.Fatalf("Error decrypting EC key") + } + DefaultRecordType = oldDefaultRecordType } From db0b10e671115e5ac5de911f982b922a32dc5e22 Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 9 Jan 2014 19:20:06 -0700 Subject: [PATCH 4/9] Add ECC support to cryptor. There are a few changes made here: * Comments have been updated to reflect the presence of ECC records. * Variables named rsa* have been renamed pub* to reflect the general use of public key cryptography, instead of RSA specifically. * An if statement for RSA keys now handles both RSA and ECC keys. --- cryptor/cryptor.go | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/cryptor/cryptor.go b/cryptor/cryptor.go index 3a5558e..707f9ab 100644 --- a/cryptor/cryptor.go +++ b/cryptor/cryptor.go @@ -33,7 +33,7 @@ type MultiWrappedKey struct { } // SingleWrappedKey is a structure containing a 16-byte key encrypted -// by an RSA key. +// by an RSA or EC key. type SingleWrappedKey struct { Key []byte aesKey []byte @@ -60,7 +60,7 @@ func makeRandom(length int) (bytes []byte, err error) { // encryptKey encrypts data with the key associated with name inner, // then name outer -func encryptKey(nameInner, nameOuter string, clearKey []byte, rsaKeys map[string]SingleWrappedKey) (out MultiWrappedKey, err error) { +func encryptKey(nameInner, nameOuter string, clearKey []byte, pubKeys map[string]SingleWrappedKey) (out MultiWrappedKey, err error) { out.Name = []string{nameOuter, nameInner} recInner, ok := passvault.GetRecord(nameInner) @@ -85,19 +85,18 @@ func encryptKey(nameInner, nameOuter string, clearKey []byte, rsaKeys map[string var overrideOuter SingleWrappedKey // For AES records, use the live user key - // For RSA records, use the public key from the passvault + // For RSA and ECC records, use the public key from the passvault switch recInner.Type { - case passvault.RSARecord: - if overrideInner, ok = rsaKeys[nameInner]; !ok { + case passvault.RSARecord, passvault.ECCRecord: + if overrideInner, ok = pubKeys[nameInner]; !ok { err = errors.New("Missing user in file") return } - if overrideOuter, ok = rsaKeys[nameOuter]; !ok { + if overrideOuter, ok = pubKeys[nameOuter]; !ok { err = errors.New("Missing user in file") return } - case passvault.AESRecord: break @@ -119,7 +118,7 @@ func encryptKey(nameInner, nameOuter string, clearKey []byte, rsaKeys map[string } // unwrapKey decrypts first key in keys whose encryption keys are in keycache -func unwrapKey(keys []MultiWrappedKey, rsaKeys map[string]SingleWrappedKey) (unwrappedKey []byte, err error) { +func unwrapKey(keys []MultiWrappedKey, pubKeys map[string]SingleWrappedKey) (unwrappedKey []byte, err error) { var ( keyFound error fullMatch bool = false @@ -133,9 +132,9 @@ func unwrapKey(keys []MultiWrappedKey, rsaKeys map[string]SingleWrappedKey) (unw tmpKeyValue := mwKey.Key for _, mwName := range mwKey.Name { - rsaEncrypted := rsaKeys[mwName] + pubEncrypted := pubKeys[mwName] // if this is null, it's an AES encrypted key - if tmpKeyValue, keyFound = keycache.DecryptKey(tmpKeyValue, mwName, rsaEncrypted.Key); keyFound != nil { + if tmpKeyValue, keyFound = keycache.DecryptKey(tmpKeyValue, mwName, pubEncrypted.Key); keyFound != nil { break } } @@ -294,7 +293,7 @@ func Encrypt(in []byte, names []string, min int) (resp []byte, err error) { return } - if rec.GetType() == passvault.RSARecord { + if rec.GetType() == passvault.RSARecord || rec.GetType() == passvault.ECCRecord { // only wrap key with RSA key if found if singleWrappedKey.aesKey, err = makeRandom(16); err != nil { return nil, err From 4e4ac735ef4ba2db0a32e8dbe8cbdc47cfb35c45 Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 9 Jan 2014 19:22:09 -0700 Subject: [PATCH 5/9] Add ECCRecord support to keycache. This commit adds ECC cases to functions checking for RSA records. Additionally, the rsaEncryptedKey variable is changed to pubEncryptedKey to reflect a general use of public key crypto. --- keycache/keycache.go | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/keycache/keycache.go b/keycache/keycache.go index 39c842a..1c26eab 100644 --- a/keycache/keycache.go +++ b/keycache/keycache.go @@ -7,10 +7,12 @@ package keycache import ( "crypto/aes" + "crypto/ecdsa" "crypto/rand" "crypto/rsa" "crypto/sha1" "errors" + "github.com/cloudflare/redoctober/ecdh" "github.com/cloudflare/redoctober/passvault" "log" "time" @@ -28,6 +30,7 @@ type ActiveUser struct { aesKey []byte rsaKey rsa.PrivateKey + eccKey *ecdsa.PrivateKey } // matchUser returns the matching active user if present @@ -93,6 +96,8 @@ func AddKeyFromRecord(record passvault.PasswordRecord, name string, password str current.aesKey, err = record.GetKeyAES(password) case passvault.RSARecord: current.rsaKey, err = record.GetKeyRSA(password) + case passvault.ECCRecord: + current.eccKey, err = record.GetKeyECC(password) default: err = errors.New("Unknown record type") } @@ -113,7 +118,8 @@ func AddKeyFromRecord(record passvault.PasswordRecord, name string, password str // EncryptKey encrypts a 16 byte key using the cached key corresponding to name. // For AES keys, use the cached key. -// For RSA keys, the cache is not necessary use the override key instead. +// For RSA and EC keys, the cache is not necessary; use the override +// key instead. func EncryptKey(in []byte, name string, override []byte) (out []byte, err error) { Refresh() @@ -150,9 +156,10 @@ func EncryptKey(in []byte, name string, override []byte) (out []byte, err error) // DecryptKey decrypts a 16 byte key using the key corresponding to the name parameter // for AES keys, the cached AES key is used directly to decrypt in -// for RSA keys, the cached RSA key is used to decrypt the rsaEncryptedKey -// which is then used to decrypt the input buffer. -func DecryptKey(in []byte, name string, rsaEncryptedKey []byte) (out []byte, err error) { +// for RSA and EC keys, the cached RSA/EC key is used to decrypt +// the pubEncryptedKey which is then used to decrypt the input +// buffer. +func DecryptKey(in []byte, name string, pubEncryptedKey []byte) (out []byte, err error) { Refresh() decryptKey, ok := matchUser(name) @@ -168,12 +175,18 @@ func DecryptKey(in []byte, name string, rsaEncryptedKey []byte) (out []byte, err aesKey = decryptKey.aesKey case passvault.RSARecord: - // extract the aes key from the rsaEncryptedKey - aesKey, err = rsa.DecryptOAEP(sha1.New(), rand.Reader, &decryptKey.rsaKey, rsaEncryptedKey, nil) + // extract the aes key from the pubEncryptedKey + aesKey, err = rsa.DecryptOAEP(sha1.New(), rand.Reader, &decryptKey.rsaKey, pubEncryptedKey, nil) if err != nil { return out, err } + case passvault.ECCRecord: + // extract the aes key from the pubEncryptedKey + aesKey, err = ecdh.Decrypt(decryptKey.eccKey, pubEncryptedKey) + if err != nil { + return out, err + } default: return nil, errors.New("unknown type") } From 46b93b7fba4c9b9427899c69d5b06be2500e6cf3 Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 9 Jan 2014 19:23:44 -0700 Subject: [PATCH 6/9] Update core_test to check for DefaultRecordType. In the passvault, the default type of public key cryptography is selected via a variable. The core test has been changed where it relies on RSA specifically to check for the default record type. --- core/core_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/core_test.go b/core/core_test.go index 02332c7..922b91e 100644 --- a/core/core_test.go +++ b/core/core_test.go @@ -7,9 +7,9 @@ package core import ( "bytes" "encoding/json" - "os" "github.com/cloudflare/redoctober/keycache" "github.com/cloudflare/redoctober/passvault" + "os" "testing" ) @@ -102,7 +102,7 @@ func TestSummary(t *testing.T) { if data.Admin != true { t.Fatalf("Error in summary of account, record incorrect") } - if data.Type != passvault.RSARecord { + if data.Type != passvault.DefaultRecordType { t.Fatalf("Error in summary of account, record incorrect") } @@ -138,7 +138,7 @@ func TestSummary(t *testing.T) { if data.Admin != true { t.Fatalf("Error in summary of account, record missing") } - if data.Type != passvault.RSARecord { + if data.Type != passvault.DefaultRecordType { t.Fatalf("Error in summary of account, record missing") } @@ -149,7 +149,7 @@ func TestSummary(t *testing.T) { if data.Admin != false { t.Fatalf("Error in summary of account, record missing") } - if data.Type != passvault.RSARecord { + if data.Type != passvault.DefaultRecordType { t.Fatalf("Error in summary of account, record missing") } @@ -160,7 +160,7 @@ func TestSummary(t *testing.T) { if dataLive.Admin != false { t.Fatalf("Error in summary of account, record missing") } - if dataLive.Type != passvault.RSARecord { + if dataLive.Type != passvault.DefaultRecordType { t.Fatalf("Error in summary of account, record missing") } From a696c85bba6f84e5d0e0c263eddd5c886d400446 Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 10 Jan 2014 03:09:55 -0700 Subject: [PATCH 7/9] Cleanups based on feedback from pull request. --- passvault/passvault.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/passvault/passvault.go b/passvault/passvault.go index e655daf..7994d94 100644 --- a/passvault/passvault.go +++ b/passvault/passvault.go @@ -159,9 +159,7 @@ func encryptECCRecord(newRec *PasswordRecord, ecPriv *ecdsa.PrivateKey, passKey } paddedX509 := padding.AddPadding(ecX509) - if newRec.ECKey.ECPriv, err = encryptCBC(paddedX509, newRec.ECKey.ECPrivIV, passKey); err != nil { - return - } + newRec.ECKey.ECPriv, err = encryptCBC(paddedX509, newRec.ECKey.ECPrivIV, passKey) return } @@ -616,8 +614,7 @@ func (pr PasswordRecord) GetKeyECC(password string) (key *ecdsa.PrivateKey, err return key, errors.New("Invalid function for record type") } - err = pr.ValidatePassword(password) - if err != nil { + if err = pr.ValidatePassword(password); err != nil { return } From 1916f385ed63c99c7912c895f3fb4b8057a12ece Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 10 Jan 2014 03:58:43 -0700 Subject: [PATCH 8/9] Move {En,De}cryptCBC and MakeRandom to symcrypt. The symcrypt package now contains common secret-key code that is redefined in a number of packages. --- cryptor/cryptor.go | 15 ++------ ecdh/ecdh.go | 48 ++---------------------- passvault/passvault.go | 77 ++++++++++----------------------------- symcrypt/symcrypt.go | 46 +++++++++++++++++++++++ symcrypt/symcrypt_test.go | 39 ++++++++++++++++++++ 5 files changed, 113 insertions(+), 112 deletions(-) create mode 100644 symcrypt/symcrypt.go create mode 100644 symcrypt/symcrypt_test.go diff --git a/cryptor/cryptor.go b/cryptor/cryptor.go index 707f9ab..8d260d8 100644 --- a/cryptor/cryptor.go +++ b/cryptor/cryptor.go @@ -9,13 +9,13 @@ import ( "crypto/aes" "crypto/cipher" "crypto/hmac" - "crypto/rand" "crypto/sha1" "encoding/json" "errors" "github.com/cloudflare/redoctober/keycache" "github.com/cloudflare/redoctober/padding" "github.com/cloudflare/redoctober/passvault" + "github.com/cloudflare/redoctober/symcrypt" "sort" "strconv" ) @@ -51,13 +51,6 @@ type EncryptedData struct { Signature []byte } -// makeRandom is a helper to make new buffer full of random data -func makeRandom(length int) (bytes []byte, err error) { - bytes = make([]byte, length) - _, err = rand.Read(bytes) - return -} - // encryptKey encrypts data with the key associated with name inner, // then name outer func encryptKey(nameInner, nameOuter string, clearKey []byte, pubKeys map[string]SingleWrappedKey) (out MultiWrappedKey, err error) { @@ -265,7 +258,7 @@ func Encrypt(in []byte, names []string, min int) (resp []byte, err error) { } // Generate random IV and encryption key - ivBytes, err := makeRandom(16) + ivBytes, err := symcrypt.MakeRandom(16) if err != nil { return } @@ -274,7 +267,7 @@ func Encrypt(in []byte, names []string, min int) (resp []byte, err error) { // encrypted.IV encrypted.IV = append([]byte{}, ivBytes...) - clearKey, err := makeRandom(16) + clearKey, err := symcrypt.MakeRandom(16) if err != nil { return } @@ -295,7 +288,7 @@ func Encrypt(in []byte, names []string, min int) (resp []byte, err error) { if rec.GetType() == passvault.RSARecord || rec.GetType() == passvault.ECCRecord { // only wrap key with RSA key if found - if singleWrappedKey.aesKey, err = makeRandom(16); err != nil { + if singleWrappedKey.aesKey, err = symcrypt.MakeRandom(16); err != nil { return nil, err } diff --git a/ecdh/ecdh.go b/ecdh/ecdh.go index 2264293..b05d6da 100644 --- a/ecdh/ecdh.go +++ b/ecdh/ecdh.go @@ -6,7 +6,6 @@ package ecdh import ( "crypto/aes" - "crypto/cipher" "crypto/ecdsa" "crypto/elliptic" "crypto/hmac" @@ -14,6 +13,7 @@ import ( "crypto/sha1" "errors" "github.com/cloudflare/redoctober/padding" + "github.com/cloudflare/redoctober/symcrypt" ) var Curve = elliptic.P256 @@ -37,13 +37,13 @@ func Encrypt(pub ecdsa.PublicKey, in []byte) (out []byte, err error) { return nil, errors.New("Failed to generate encryption key") } shared := x.Bytes() - iv, err := makeRandom(16) + iv, err := symcrypt.MakeRandom(16) if err != nil { return } paddedIn := padding.AddPadding(in) - ct, err := encryptCBC(paddedIn, iv, shared[:16]) + ct, err := symcrypt.EncryptCBC(paddedIn, iv, shared[:16]) if err != nil { return } @@ -92,50 +92,10 @@ func Decrypt(priv *ecdsa.PrivateKey, in []byte) (out []byte, err error) { return nil, errors.New("Invalid MAC") } - paddedOut, err := decryptCBC(ct[aes.BlockSize:tagStart], ct[:aes.BlockSize], shared[:16]) + paddedOut, err := symcrypt.DecryptCBC(ct[aes.BlockSize:tagStart], ct[:aes.BlockSize], shared[:16]) if err != nil { return } out, err = padding.RemovePadding(paddedOut) return } - -// Utility functions copied from passvault. These handle encryption -// and decryption of data using AES-128-CBC. - -// decryptCBC decrypt bytes using a key and IV with AES in CBC mode. -func decryptCBC(data, iv, key []byte) (decryptedData []byte, err error) { - aesCrypt, err := aes.NewCipher(key) - if err != nil { - return - } - ivBytes := append([]byte{}, iv...) - - decryptedData = make([]byte, len(data)) - aesCBC := cipher.NewCBCDecrypter(aesCrypt, ivBytes) - aesCBC.CryptBlocks(decryptedData, data) - - return -} - -// encryptCBC encrypt data using a key and IV with AES in CBC mode. -func encryptCBC(data, iv, key []byte) (encryptedData []byte, err error) { - aesCrypt, err := aes.NewCipher(key) - if err != nil { - return - } - ivBytes := append([]byte{}, iv...) - - encryptedData = make([]byte, len(data)) - aesCBC := cipher.NewCBCEncrypter(aesCrypt, ivBytes) - aesCBC.CryptBlocks(encryptedData, data) - - return -} - -// makeRandom is a helper that makes a new buffer full of random data -func makeRandom(length int) ([]byte, error) { - bytes := make([]byte, length) - _, err := rand.Read(bytes) - return bytes, err -} diff --git a/passvault/passvault.go b/passvault/passvault.go index 7994d94..fa9b7cd 100644 --- a/passvault/passvault.go +++ b/passvault/passvault.go @@ -10,7 +10,6 @@ import ( "bytes" "code.google.com/p/go.crypto/scrypt" "crypto/aes" - "crypto/cipher" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" @@ -22,6 +21,7 @@ import ( "errors" "github.com/cloudflare/redoctober/ecdh" "github.com/cloudflare/redoctober/padding" + "github.com/cloudflare/redoctober/symcrypt" "io/ioutil" "math/big" mrand "math/rand" @@ -97,7 +97,7 @@ type Summary struct { func init() { // seed math.random from crypto.random - seedBytes, _ := makeRandom(8) + seedBytes, _ := symcrypt.MakeRandom(8) seedBuf := bytes.NewBuffer(seedBytes) n64, _ := binary.ReadVarint(seedBuf) mrand.Seed(n64) @@ -109,40 +109,33 @@ func hashPassword(password string, salt []byte) ([]byte, error) { return scrypt.Key([]byte(password), salt, N, R, P, KEYLENGTH) } -// makeRandom is a helper that makes a new buffer full of random data -func makeRandom(length int) ([]byte, error) { - bytes := make([]byte, length) - _, err := rand.Read(bytes) - return bytes, err -} - // encryptRSARecord takes an RSA private key and encrypts it with // a password key func encryptRSARecord(newRec *PasswordRecord, rsaPriv *rsa.PrivateKey, passKey []byte) (err error) { - if newRec.RSAKey.RSAExpIV, err = makeRandom(16); err != nil { + if newRec.RSAKey.RSAExpIV, err = symcrypt.MakeRandom(16); err != nil { return } paddedExponent := padding.AddPadding(rsaPriv.D.Bytes()) - if newRec.RSAKey.RSAExp, err = encryptCBC(paddedExponent, newRec.RSAKey.RSAExpIV, passKey); err != nil { + if newRec.RSAKey.RSAExp, err = symcrypt.EncryptCBC(paddedExponent, newRec.RSAKey.RSAExpIV, passKey); err != nil { return } - if newRec.RSAKey.RSAPrimePIV, err = makeRandom(16); err != nil { + if newRec.RSAKey.RSAPrimePIV, err = symcrypt.MakeRandom(16); err != nil { return } paddedPrimeP := padding.AddPadding(rsaPriv.Primes[0].Bytes()) - if newRec.RSAKey.RSAPrimeP, err = encryptCBC(paddedPrimeP, newRec.RSAKey.RSAPrimePIV, passKey); err != nil { + if newRec.RSAKey.RSAPrimeP, err = symcrypt.EncryptCBC(paddedPrimeP, newRec.RSAKey.RSAPrimePIV, passKey); err != nil { return } - if newRec.RSAKey.RSAPrimeQIV, err = makeRandom(16); err != nil { + if newRec.RSAKey.RSAPrimeQIV, err = symcrypt.MakeRandom(16); err != nil { return } paddedPrimeQ := padding.AddPadding(rsaPriv.Primes[1].Bytes()) - newRec.RSAKey.RSAPrimeQ, err = encryptCBC(paddedPrimeQ, newRec.RSAKey.RSAPrimeQIV, passKey) + newRec.RSAKey.RSAPrimeQ, err = symcrypt.EncryptCBC(paddedPrimeQ, newRec.RSAKey.RSAPrimeQIV, passKey) return } @@ -154,12 +147,12 @@ func encryptECCRecord(newRec *PasswordRecord, ecPriv *ecdsa.PrivateKey, passKey return } - if newRec.ECKey.ECPrivIV, err = makeRandom(16); err != nil { + if newRec.ECKey.ECPrivIV, err = symcrypt.MakeRandom(16); err != nil { return } paddedX509 := padding.AddPadding(ecX509) - newRec.ECKey.ECPriv, err = encryptCBC(paddedX509, newRec.ECKey.ECPrivIV, passKey) + newRec.ECKey.ECPriv, err = symcrypt.EncryptCBC(paddedX509, newRec.ECKey.ECPrivIV, passKey) return } @@ -167,7 +160,7 @@ func encryptECCRecord(newRec *PasswordRecord, ecPriv *ecdsa.PrivateKey, passKey func createPasswordRec(password string, admin bool) (newRec PasswordRecord, err error) { newRec.Type = DefaultRecordType - if newRec.PasswordSalt, err = makeRandom(16); err != nil { + if newRec.PasswordSalt, err = symcrypt.MakeRandom(16); err != nil { return } @@ -175,7 +168,7 @@ func createPasswordRec(password string, admin bool) (newRec PasswordRecord, err return } - if newRec.KeySalt, err = makeRandom(16); err != nil { + if newRec.KeySalt, err = symcrypt.MakeRandom(16); err != nil { return } @@ -211,7 +204,7 @@ func createPasswordRec(password string, admin bool) (newRec PasswordRecord, err } // encrypt AES key with password key - aesKey, err := makeRandom(16) + aesKey, err := symcrypt.MakeRandom(16) if err != nil { return } @@ -257,36 +250,6 @@ func encryptECB(data, key []byte) (encryptedData []byte, err error) { return } -// decryptCBC decrypt bytes using a key and IV with AES in CBC mode. -func decryptCBC(data, iv, key []byte) (decryptedData []byte, err error) { - aesCrypt, err := aes.NewCipher(key) - if err != nil { - return - } - ivBytes := append([]byte{}, iv...) - - decryptedData = make([]byte, len(data)) - aesCBC := cipher.NewCBCDecrypter(aesCrypt, ivBytes) - aesCBC.CryptBlocks(decryptedData, data) - - return -} - -// encryptCBC encrypt data using a key and IV with AES in CBC mode. -func encryptCBC(data, iv, key []byte) (encryptedData []byte, err error) { - aesCrypt, err := aes.NewCipher(key) - if err != nil { - return - } - ivBytes := append([]byte{}, iv...) - - encryptedData = make([]byte, len(data)) - aesCBC := cipher.NewCBCEncrypter(aesCrypt, ivBytes) - aesCBC.CryptBlocks(encryptedData, data) - - return -} - // InitFromDisk reads the record from disk and initialize global context. func InitFromDisk(path string) error { jsonDiskRecord, err := ioutil.ReadFile(path) @@ -358,7 +321,7 @@ func InitFromDisk(path string) error { if records.Version == 0 { records.Version = DEFAULT_VERSION records.VaultId = int(mrand.Int31()) - records.HmacKey, err = makeRandom(16) + records.HmacKey, err = symcrypt.MakeRandom(16) if err != nil { return err } @@ -429,14 +392,14 @@ func ChangePassword(name, password, newPassword string) (err error) { } // add the password salt and hash - if pr.PasswordSalt, err = makeRandom(16); err != nil { + if pr.PasswordSalt, err = symcrypt.MakeRandom(16); err != nil { return } if pr.HashedPassword, err = hashPassword(newPassword, pr.PasswordSalt); err != nil { return } - if pr.KeySalt, err = makeRandom(16); err != nil { + if pr.KeySalt, err = symcrypt.MakeRandom(16); err != nil { return } newPassKey, err := derivePasswordKey(newPassword, pr.KeySalt) @@ -623,7 +586,7 @@ func (pr PasswordRecord) GetKeyECC(password string) (key *ecdsa.PrivateKey, err return } - x509Padded, err := decryptCBC(pr.ECKey.ECPriv, pr.ECKey.ECPrivIV, passKey) + x509Padded, err := symcrypt.DecryptCBC(pr.ECKey.ECPriv, pr.ECKey.ECPrivIV, passKey) if err != nil { return } @@ -651,7 +614,7 @@ func (pr PasswordRecord) GetKeyRSA(password string) (key rsa.PrivateKey, err err return } - rsaExponentPadded, err := decryptCBC(pr.RSAKey.RSAExp, pr.RSAKey.RSAExpIV, passKey) + rsaExponentPadded, err := symcrypt.DecryptCBC(pr.RSAKey.RSAExp, pr.RSAKey.RSAExpIV, passKey) if err != nil { return } @@ -660,7 +623,7 @@ func (pr PasswordRecord) GetKeyRSA(password string) (key rsa.PrivateKey, err err return } - rsaPrimePPadded, err := decryptCBC(pr.RSAKey.RSAPrimeP, pr.RSAKey.RSAPrimePIV, passKey) + rsaPrimePPadded, err := symcrypt.DecryptCBC(pr.RSAKey.RSAPrimeP, pr.RSAKey.RSAPrimePIV, passKey) if err != nil { return } @@ -669,7 +632,7 @@ func (pr PasswordRecord) GetKeyRSA(password string) (key rsa.PrivateKey, err err return } - rsaPrimeQPadded, err := decryptCBC(pr.RSAKey.RSAPrimeQ, pr.RSAKey.RSAPrimeQIV, passKey) + rsaPrimeQPadded, err := symcrypt.DecryptCBC(pr.RSAKey.RSAPrimeQ, pr.RSAKey.RSAPrimeQIV, passKey) if err != nil { return } diff --git a/symcrypt/symcrypt.go b/symcrypt/symcrypt.go new file mode 100644 index 0000000..3b3552d --- /dev/null +++ b/symcrypt/symcrypt.go @@ -0,0 +1,46 @@ +// Package symcrypt contains common symmetric encryption functions. +package symcrypt + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "io" +) + +// DecryptCBC decrypt bytes using a key and IV with AES in CBC mode. +func DecryptCBC(data, iv, key []byte) (decryptedData []byte, err error) { + aesCrypt, err := aes.NewCipher(key) + if err != nil { + return + } + ivBytes := append([]byte{}, iv...) + + decryptedData = make([]byte, len(data)) + aesCBC := cipher.NewCBCDecrypter(aesCrypt, ivBytes) + aesCBC.CryptBlocks(decryptedData, data) + + return +} + +// EncryptCBC encrypt data using a key and IV with AES in CBC mode. +func EncryptCBC(data, iv, key []byte) (encryptedData []byte, err error) { + aesCrypt, err := aes.NewCipher(key) + if err != nil { + return + } + ivBytes := append([]byte{}, iv...) + + encryptedData = make([]byte, len(data)) + aesCBC := cipher.NewCBCEncrypter(aesCrypt, ivBytes) + aesCBC.CryptBlocks(encryptedData, data) + + return +} + +// MakeRandom is a helper that makes a new buffer full of random data. +func MakeRandom(length int) ([]byte, error) { + bytes := make([]byte, length) + _, err := io.ReadFull(rand.Reader, bytes) + return bytes, err +} diff --git a/symcrypt/symcrypt_test.go b/symcrypt/symcrypt_test.go new file mode 100644 index 0000000..0a76a3a --- /dev/null +++ b/symcrypt/symcrypt_test.go @@ -0,0 +1,39 @@ +package symcrypt + +import ( + "bytes" + "github.com/cloudflare/redoctober/padding" + "testing" +) + +func TestCrypt(t *testing.T) { + msg := []byte("One ping only, please.") + padMsg := padding.AddPadding(msg) + + key, err := MakeRandom(16) + if err != nil { + t.Fatalf("%v", err) + } + + iv, err := MakeRandom(16) + if err != nil { + t.Fatalf("%v", err) + } + + out, err := EncryptCBC(padMsg, iv, key) + if err != nil { + t.Fatalf("%v", err) + } + + out, err = DecryptCBC(out, iv, key) + if err != nil { + t.Fatalf("%v", err) + } + + unpadOut, err := padding.RemovePadding(out) + if err != nil { + t.Fatalf("%v", err) + } else if !bytes.Equal(unpadOut, msg) { + t.Fatal("Decrypted message doesn't match original plaintext.") + } +} From de5b1817c782f0f3459a8b7b257d576e6fc5d22c Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 10 Jan 2014 16:27:14 -0700 Subject: [PATCH 9/9] SHA256 the ECDH shared key This ensures the shared key is a more uniform distribution than just taking the bytes from the shared X. --- ecdh/ecdh.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ecdh/ecdh.go b/ecdh/ecdh.go index b05d6da..5b2c06c 100644 --- a/ecdh/ecdh.go +++ b/ecdh/ecdh.go @@ -11,6 +11,7 @@ import ( "crypto/hmac" "crypto/rand" "crypto/sha1" + "crypto/sha256" "errors" "github.com/cloudflare/redoctober/padding" "github.com/cloudflare/redoctober/symcrypt" @@ -36,7 +37,7 @@ func Encrypt(pub ecdsa.PublicKey, in []byte) (out []byte, err error) { if x == nil { return nil, errors.New("Failed to generate encryption key") } - shared := x.Bytes() + shared := sha256.Sum256(x.Bytes()) iv, err := symcrypt.MakeRandom(16) if err != nil { return @@ -82,7 +83,7 @@ func Decrypt(priv *ecdsa.PrivateKey, in []byte) (out []byte, err error) { if x == nil { return nil, errors.New("Failed to generate encryption key") } - shared := x.Bytes() + shared := sha256.Sum256(x.Bytes()) tagStart := len(ct) - sha1.Size h := hmac.New(sha1.New, shared[16:])