mirror of
https://github.com/cloudflare/redoctober.git
synced 2026-06-06 23:12:59 +00:00
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:
141
ecdh/ecdh.go
Normal file
141
ecdh/ecdh.go
Normal 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
36
ecdh/ecdh_test.go
Normal 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.")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user