Added ECDH and encryption/decryption support

This commit is contained in:
Ishbir Singh 2015-04-09 18:13:35 -04:00
parent 007bee5ec8
commit 58f29ad939
6 changed files with 489 additions and 1 deletions

View file

@ -47,6 +47,16 @@ $ go get github.com/btcsuite/btcd/btcec
Demonstrates verifying a secp256k1 signature against a public key that is Demonstrates verifying a secp256k1 signature against a public key that is
first parsed from raw bytes. The signature is also parsed from raw bytes. first parsed from raw bytes. The signature is also parsed from raw bytes.
* [Encryption]
(http://godoc.org/github.com/btcsuite/btcd/btcec#example-package--EncryptMessage)
Demonstrates encrypting a message for a public key that is first parsed from
raw bytes, then decrypting it using the corresponding private key.
* [Decryption]
(http://godoc.org/github.com/btcsuite/btcd/btcec#example-package--DecryptMessage)
Demonstrates decrypting a message using a private key that is first parsed
from raw bytes.
## GPG Verification Key ## GPG Verification Key
All official release tags are signed by Conformal so users can ensure the code All official release tags are signed by Conformal so users can ensure the code

216
btcec/ciphering.go Normal file
View file

@ -0,0 +1,216 @@
// Copyright (c) 2015 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcec
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"errors"
"io"
)
var (
// ErrInvalidMAC occurs when Message Authentication Check (MAC) fails
// during decryption. This happens because of either invalid private key or
// corrupt ciphertext.
ErrInvalidMAC = errors.New("invalid mac hash")
// errInputTooShort occurs when the input ciphertext to the Decrypt
// function is less than 134 bytes long.
errInputTooShort = errors.New("ciphertext too short")
// errUnsupportedCurve occurs when the first two bytes of the encrypted
// text aren't 0x02CA (= 712 = secp256k1, from OpenSSL).
errUnsupportedCurve = errors.New("unsupported curve")
errInvalidXLength = errors.New("invalid X length, must be 32")
errInvalidYLength = errors.New("invalid Y length, must be 32")
errInvalidPadding = errors.New("invalid PKCS#7 padding")
// 0x02CA = 714
ciphCurveBytes = [2]byte{0x02, 0xCA}
// 0x20 = 32
ciphCoordLength = [2]byte{0x00, 0x20}
)
// GenerateSharedSecret generates a shared secret based on a private key and a
// private key using Diffie-Hellman key exchange (ECDH) (RFC 4753).
// RFC5903 Section 9 states we should only return x.
func GenerateSharedSecret(privkey *PrivateKey, pubkey *PublicKey) []byte {
x, _ := pubkey.Curve.ScalarMult(pubkey.X, pubkey.Y, privkey.D.Bytes())
return x.Bytes()
}
// Encrypt encrypts data for the target public key using AES-256-CBC. It also
// generates a private key (the pubkey of which is also in the output). The only
// supported curve is secp256k1. The `structure' that it encodes everything into
// is:
//
// struct {
// // Initialization Vector used for AES-256-CBC
// IV [16]byte
// // Public Key: curve(2) + len_of_pubkeyX(2) + pubkeyX +
// // len_of_pubkeyY(2) + pubkeyY (curve = 714)
// PublicKey [70]byte
// // Cipher text
// Data []byte
// // HMAC-SHA-256 Message Authentication Code
// HMAC [32]byte
// }
//
// The primary aim is to ensure byte compatibility with Pyelliptic. Additionaly,
// refer to section 5.8.1 of ANSI X9.63 for rationale on this format.
func Encrypt(pubkey *PublicKey, in []byte) ([]byte, error) {
ephemeral, err := NewPrivateKey(S256())
if err != nil {
return nil, err
}
ecdhKey := GenerateSharedSecret(ephemeral, pubkey)
derivedKey := sha512.Sum512(ecdhKey)
keyE := derivedKey[:32]
keyM := derivedKey[32:]
paddedIn := addPKCSPadding(in)
// IV + Curve params/X/Y + padded plaintext/ciphertext + HMAC-256
out := make([]byte, aes.BlockSize+70+len(paddedIn)+sha256.Size)
iv := out[:aes.BlockSize]
if _, err = io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
// start writing public key
pb := ephemeral.PubKey().SerializeUncompressed()
offset := aes.BlockSize
// curve and X length
copy(out[offset:offset+4], append(ciphCurveBytes[:], ciphCoordLength[:]...))
offset += 4
// X
copy(out[offset:offset+32], pb[1:33])
offset += 32
// Y length
copy(out[offset:offset+2], ciphCoordLength[:])
offset += 2
// Y
copy(out[offset:offset+32], pb[33:])
offset += 32
// start encryption
block, err := aes.NewCipher(keyE)
if err != nil {
return nil, err
}
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(out[offset:len(out)-sha256.Size], paddedIn)
// start HMAC-SHA-256
hm := hmac.New(sha256.New, keyM)
hm.Write(out[:len(out)-sha256.Size]) // everything is hashed
copy(out[len(out)-sha256.Size:], hm.Sum(nil)) // write checksum
return out, nil
}
// Decrypt decrypts data that was encrypted using the Encrypt function.
func Decrypt(priv *PrivateKey, in []byte) ([]byte, error) {
// IV + Curve params/X/Y + 1 block + HMAC-256
if len(in) < aes.BlockSize+70+aes.BlockSize+sha256.Size {
return nil, errInputTooShort
}
// read iv
iv := in[:aes.BlockSize]
offset := aes.BlockSize
// start reading pubkey
if !bytes.Equal(in[offset:offset+2], ciphCurveBytes[:]) {
return nil, errUnsupportedCurve
}
offset += 2
if !bytes.Equal(in[offset:offset+2], ciphCoordLength[:]) {
return nil, errInvalidXLength
}
offset += 2
xBytes := in[offset : offset+32]
offset += 32
if !bytes.Equal(in[offset:offset+2], ciphCoordLength[:]) {
return nil, errInvalidYLength
}
offset += 2
yBytes := in[offset : offset+32]
offset += 32
pb := make([]byte, 65)
pb[0] = byte(0x04) // uncompressed
copy(pb[1:33], xBytes)
copy(pb[33:], yBytes)
// check if (X, Y) lies on the curve and create a Pubkey if it does
pubkey, err := ParsePubKey(pb, S256())
if err != nil {
return nil, err
}
// check for cipher text length
if (len(in)-aes.BlockSize-offset-sha256.Size)%aes.BlockSize != 0 {
return nil, errInvalidPadding // not padded to 16 bytes
}
// read hmac
messageMAC := in[len(in)-sha256.Size:]
// generate shared secret
ecdhKey := GenerateSharedSecret(priv, pubkey)
derivedKey := sha512.Sum512(ecdhKey)
keyE := derivedKey[:32]
keyM := derivedKey[32:]
// verify mac
hm := hmac.New(sha256.New, keyM)
hm.Write(in[:len(in)-sha256.Size]) // everything is hashed
expectedMAC := hm.Sum(nil)
if !bytes.Equal(messageMAC, expectedMAC) {
return nil, ErrInvalidMAC
}
// start decryption
block, err := aes.NewCipher(keyE)
if err != nil {
return nil, err
}
mode := cipher.NewCBCDecrypter(block, iv)
// same length as ciphertext
plaintext := make([]byte, len(in)-offset-sha256.Size)
mode.CryptBlocks(plaintext, in[offset:len(in)-sha256.Size])
return removePKCSPadding(plaintext)
}
// Implement PKCS#7 padding with block size of 16 (AES block size).
// addPKCSPadding adds padding to a block of data
func addPKCSPadding(src []byte) []byte {
padding := aes.BlockSize - len(src)%aes.BlockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(src, padtext...)
}
// removePKCSPadding removes padding from data that was added with addPKCSPadding
func removePKCSPadding(src []byte) ([]byte, error) {
length := len(src)
padLength := int(src[length-1])
if padLength > aes.BlockSize || length < aes.BlockSize {
return nil, errInvalidPadding
}
return src[:length-padLength], nil
}

176
btcec/ciphering_test.go Normal file
View file

@ -0,0 +1,176 @@
// Copyright (c) 2015 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcec_test
import (
"bytes"
"encoding/hex"
"testing"
"github.com/btcsuite/btcd/btcec"
)
func TestGenerateSharedSecret(t *testing.T) {
privKey1, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
t.Errorf("private key generation error: %s", err)
return
}
privKey2, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
t.Errorf("private key generation error: %s", err)
return
}
secret1 := btcec.GenerateSharedSecret(privKey1, privKey2.PubKey())
secret2 := btcec.GenerateSharedSecret(privKey2, privKey1.PubKey())
if !bytes.Equal(secret1, secret2) {
t.Errorf("ECDH failed, secrets mismatch - first: %x, second: %x",
secret1, secret2)
}
}
// Test 1: Encryption and decryption
func TestCipheringBasic(t *testing.T) {
privkey, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
t.Fatal("failed to generate private key")
}
in := []byte("Hey there dude. How are you doing? This is a test.")
out, err := btcec.Encrypt(privkey.PubKey(), in)
if err != nil {
t.Fatal("failed to encrypt:", err)
}
dec, err := btcec.Decrypt(privkey, out)
if err != nil {
t.Fatal("failed to decrypt:", err)
}
if !bytes.Equal(in, dec) {
t.Error("decrypted data doesn't match original")
}
}
// Test 2: Byte compatibility with Pyelliptic
func TestCiphering(t *testing.T) {
pb, _ := hex.DecodeString("fe38240982f313ae5afb3e904fb8215fb11af1200592b" +
"fca26c96c4738e4bf8f")
privkey, _ := btcec.PrivKeyFromBytes(btcec.S256(), pb)
in := []byte("This is just a test.")
out, _ := hex.DecodeString("b0d66e5adaa5ed4e2f0ca68e17b8f2fc02ca002009e3" +
"3487e7fa4ab505cf34d98f131be7bd258391588ca7804acb30251e71a04e0020ecf" +
"df0f84608f8add82d7353af780fbb28868c713b7813eb4d4e61f7b75d7534dd9856" +
"9b0ba77cf14348fcff80fee10e11981f1b4be372d93923e9178972f69937ec850ed" +
"6c3f11ff572ddd5b2bedf9f9c0b327c54da02a28fcdce1f8369ffec")
dec, err := btcec.Decrypt(privkey, out)
if err != nil {
t.Fatal("failed to decrypt:", err)
}
if !bytes.Equal(in, dec) {
t.Error("decrypted data doesn't match original")
}
}
func TestCipheringErrors(t *testing.T) {
privkey, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
t.Fatal("failed to generate private key")
}
tests1 := []struct {
ciphertext []byte // input ciphertext
}{
{bytes.Repeat([]byte{0x00}, 133)}, // errInputTooShort
{bytes.Repeat([]byte{0x00}, 134)}, // errUnsupportedCurve
{bytes.Repeat([]byte{0x02, 0xCA}, 134)}, // errInvalidXLength
{bytes.Repeat([]byte{0x02, 0xCA, 0x00, 0x20}, 134)}, // errInvalidYLength
{[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // IV
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0xCA, 0x00, 0x20, // curve and X length
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // X
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x20, // Y length
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Y
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ciphertext
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // MAC
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}}, // invalid pubkey
{[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // IV
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0xCA, 0x00, 0x20, // curve and X length
0x11, 0x5C, 0x42, 0xE7, 0x57, 0xB2, 0xEF, 0xB7, // X
0x67, 0x1C, 0x57, 0x85, 0x30, 0xEC, 0x19, 0x1A,
0x13, 0x59, 0x38, 0x1E, 0x6A, 0x71, 0x12, 0x7A,
0x9D, 0x37, 0xC4, 0x86, 0xFD, 0x30, 0xDA, 0xE5,
0x00, 0x20, // Y length
0x7E, 0x76, 0xDC, 0x58, 0xF6, 0x93, 0xBD, 0x7E, // Y
0x70, 0x10, 0x35, 0x8C, 0xE6, 0xB1, 0x65, 0xE4,
0x83, 0xA2, 0x92, 0x10, 0x10, 0xDB, 0x67, 0xAC,
0x11, 0xB1, 0xB5, 0x1B, 0x65, 0x19, 0x53, 0xD2,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ciphertext
// padding not aligned to 16 bytes
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // MAC
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}}, // errInvalidPadding
{[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // IV
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0xCA, 0x00, 0x20, // curve and X length
0x11, 0x5C, 0x42, 0xE7, 0x57, 0xB2, 0xEF, 0xB7, // X
0x67, 0x1C, 0x57, 0x85, 0x30, 0xEC, 0x19, 0x1A,
0x13, 0x59, 0x38, 0x1E, 0x6A, 0x71, 0x12, 0x7A,
0x9D, 0x37, 0xC4, 0x86, 0xFD, 0x30, 0xDA, 0xE5,
0x00, 0x20, // Y length
0x7E, 0x76, 0xDC, 0x58, 0xF6, 0x93, 0xBD, 0x7E, // Y
0x70, 0x10, 0x35, 0x8C, 0xE6, 0xB1, 0x65, 0xE4,
0x83, 0xA2, 0x92, 0x10, 0x10, 0xDB, 0x67, 0xAC,
0x11, 0xB1, 0xB5, 0x1B, 0x65, 0x19, 0x53, 0xD2,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ciphertext
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // MAC
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}}, // ErrInvalidMAC
}
for i, test := range tests1 {
_, err = btcec.Decrypt(privkey, test.ciphertext)
if err == nil {
t.Errorf("Decrypt #%d did not get error", i)
}
}
// test error from removePKCSPadding
tests2 := []struct {
in []byte // input data
}{
{bytes.Repeat([]byte{0x11}, 17)},
{bytes.Repeat([]byte{0x07}, 15)},
}
for i, test := range tests2 {
_, err = btcec.TstRemovePKCSPadding(test.in)
if err == nil {
t.Errorf("removePKCSPadding #%d did not get error", i)
}
}
}

View file

@ -86,3 +86,83 @@ func Example_verifySignature() {
// Output: // Output:
// Signature Verified? true // Signature Verified? true
} }
// This example demonstrates encrypting a message for a public key that is first
// parsed from raw bytes, then decrypting it using the corresponding private key.
func Example_encryptMessage() {
// Decode the hex-encoded pubkey of the recipient.
pubKeyBytes, err := hex.DecodeString("04115c42e757b2efb7671c578530ec191a1" +
"359381e6a71127a9d37c486fd30dae57e76dc58f693bd7e7010358ce6b165e483a29" +
"21010db67ac11b1b51b651953d2") // uncompressed pubkey
if err != nil {
fmt.Println(err)
return
}
pubKey, err := btcec.ParsePubKey(pubKeyBytes, btcec.S256())
if err != nil {
fmt.Println(err)
return
}
// Encrypt a message decryptable by the private key corresponding to pubKey
message := "test message"
ciphertext, err := btcec.Encrypt(pubKey, []byte(message))
if err != nil {
fmt.Println(err)
return
}
// Decode the hex-encoded private key.
pkBytes, err := hex.DecodeString("a11b0a4e1a132305652ee7a8eb7848f6ad" +
"5ea381e3ce20a2c086a2e388230811")
if err != nil {
fmt.Println(err)
return
}
// note that we already have corresponding pubKey
privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), pkBytes)
// Try decrypting and verify if it's the same message.
plaintext, err := btcec.Decrypt(privKey, ciphertext)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(plaintext))
// Output:
// test message
}
// This example demonstrates decrypting a message using a private key that is
// first parsed from raw bytes.
func Example_decryptMessage() {
// Decode the hex-encoded private key.
pkBytes, err := hex.DecodeString("a11b0a4e1a132305652ee7a8eb7848f6ad" +
"5ea381e3ce20a2c086a2e388230811")
if err != nil {
fmt.Println(err)
return
}
privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), pkBytes)
ciphertext, err := hex.DecodeString("35f644fbfb208bc71e57684c3c8b437402ca" +
"002047a2f1b38aa1a8f1d5121778378414f708fe13ebf7b4a7bb74407288c1958969" +
"00207cf4ac6057406e40f79961c973309a892732ae7a74ee96cd89823913b8b8d650" +
"a44166dc61ea1c419d47077b748a9c06b8d57af72deb2819d98a9d503efc59fc8307" +
"d14174f8b83354fac3ff56075162")
// Try decrypting the message.
plaintext, err := btcec.Decrypt(privKey, ciphertext)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(plaintext))
// Output:
// test message
}

View file

@ -74,3 +74,9 @@ func NewFieldVal() *fieldVal {
func TstNonceRFC6979(privkey *big.Int, hash []byte) *big.Int { func TstNonceRFC6979(privkey *big.Int, hash []byte) *big.Int {
return nonceRFC6979(privkey, hash) return nonceRFC6979(privkey, hash)
} }
// TstRemovePKCSPadding makes the internal removePKCSPadding function available
// to the test package.
func TstRemovePKCSPadding(src []byte) ([]byte, error) {
return removePKCSPadding(src)
}

View file

@ -107,7 +107,7 @@ func ParsePubKey(pubKeyStr []byte, curve *KoblitzCurve) (key *PublicKey, err err
return nil, fmt.Errorf("pubkey Y parameter is >= to P") return nil, fmt.Errorf("pubkey Y parameter is >= to P")
} }
if !pubkey.Curve.IsOnCurve(pubkey.X, pubkey.Y) { if !pubkey.Curve.IsOnCurve(pubkey.X, pubkey.Y) {
return nil, fmt.Errorf("pubkey isn't on secp265k1 curve") return nil, fmt.Errorf("pubkey isn't on secp256k1 curve")
} }
return &pubkey, nil return &pubkey, nil
} }