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.
This commit is contained in:
Kyle
2014-01-09 19:16:02 -07:00
parent c0d375d4bf
commit 689f0e6fdc
2 changed files with 177 additions and 0 deletions

141
ecdh/ecdh.go Normal file
View File

@@ -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
}

36
ecdh/ecdh_test.go Normal file
View File

@@ -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.")
}
}