aboutsummaryrefslogblamecommitdiffstats
path: root/crypto/ecies/ecies.go
blob: 86a70261d28a50f9b90fd6250b18223ff3ab6578 (plain) (tree)




























                                                                         














                         





                                                                                            











                                                                 
                                                                     





















                                                                   
                                                                



































                                                                                                          



                                                   
         
 
                                                                 

                                                         
         




                                                
























































                                                                                

                                 
                         













































                                                                                            




                                                                            






















































































                                                                                        



                                         

























                                                                     
// Copyright (c) 2013 Kyle Isom <kyle@tyrfingr.is>
// Copyright (c) 2012 The Go Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//    * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//    * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package ecies

import (
    "crypto/cipher"
    "crypto/ecdsa"
    "crypto/elliptic"
    "crypto/hmac"
    "crypto/subtle"
    "fmt"
    "hash"
    "io"
    "math/big"
)

var (
    ErrImport                     = fmt.Errorf("ecies: failed to import key")
    ErrInvalidCurve               = fmt.Errorf("ecies: invalid elliptic curve")
    ErrInvalidParams              = fmt.Errorf("ecies: invalid ECIES parameters")
    ErrInvalidPublicKey           = fmt.Errorf("ecies: invalid public key")
    ErrSharedKeyIsPointAtInfinity = fmt.Errorf("ecies: shared key is point at infinity")
    ErrSharedKeyTooBig            = fmt.Errorf("ecies: shared key params are too big")
)

// PublicKey is a representation of an elliptic curve public key.
type PublicKey struct {
    X *big.Int
    Y *big.Int
    elliptic.Curve
    Params *ECIESParams
}

// Export an ECIES public key as an ECDSA public key.
func (pub *PublicKey) ExportECDSA() *ecdsa.PublicKey {
    return &ecdsa.PublicKey{Curve: pub.Curve, X: pub.X, Y: pub.Y}
}

// Import an ECDSA public key as an ECIES public key.
func ImportECDSAPublic(pub *ecdsa.PublicKey) *PublicKey {
    return &PublicKey{
        X:      pub.X,
        Y:      pub.Y,
        Curve:  pub.Curve,
        Params: ParamsFromCurve(pub.Curve),
    }
}

// PrivateKey is a representation of an elliptic curve private key.
type PrivateKey struct {
    PublicKey
    D *big.Int
}

// Export an ECIES private key as an ECDSA private key.
func (prv *PrivateKey) ExportECDSA() *ecdsa.PrivateKey {
    pub := &prv.PublicKey
    pubECDSA := pub.ExportECDSA()
    return &ecdsa.PrivateKey{PublicKey: *pubECDSA, D: prv.D}
}

// Import an ECDSA private key as an ECIES private key.
func ImportECDSA(prv *ecdsa.PrivateKey) *PrivateKey {
    pub := ImportECDSAPublic(&prv.PublicKey)
    return &PrivateKey{*pub, prv.D}
}

// Generate an elliptic curve public / private keypair. If params is nil,
// the recommended default paramters for the key will be chosen.
func GenerateKey(rand io.Reader, curve elliptic.Curve, params *ECIESParams) (prv *PrivateKey, err error) {
    pb, x, y, err := elliptic.GenerateKey(curve, rand)
    if err != nil {
        return
    }
    prv = new(PrivateKey)
    prv.PublicKey.X = x
    prv.PublicKey.Y = y
    prv.PublicKey.Curve = curve
    prv.D = new(big.Int).SetBytes(pb)
    if params == nil {
        params = ParamsFromCurve(curve)
    }
    prv.PublicKey.Params = params
    return
}

// MaxSharedKeyLength returns the maximum length of the shared key the
// public key can produce.
func MaxSharedKeyLength(pub *PublicKey) int {
    return (pub.Curve.Params().BitSize + 7) / 8
}

// ECDH key agreement method used to establish secret keys for encryption.
func (prv *PrivateKey) GenerateShared(pub *PublicKey, skLen, macLen int) (sk []byte, err error) {
    if prv.PublicKey.Curve != pub.Curve {
        return nil, ErrInvalidCurve
    }
    if skLen+macLen > MaxSharedKeyLength(pub) {
        return nil, ErrSharedKeyTooBig
    }

    x, _ := pub.Curve.ScalarMult(pub.X, pub.Y, prv.D.Bytes())
    if x == nil {
        return nil, ErrSharedKeyIsPointAtInfinity
    }

    sk = make([]byte, skLen+macLen)
    skBytes := x.Bytes()
    copy(sk[len(sk)-len(skBytes):], skBytes)
    return sk, nil
}

var (
    ErrKeyDataTooLong = fmt.Errorf("ecies: can't supply requested key data")
    ErrSharedTooLong  = fmt.Errorf("ecies: shared secret is too long")
    ErrInvalidMessage = fmt.Errorf("ecies: invalid message")
)

var (
    big2To32   = new(big.Int).Exp(big.NewInt(2), big.NewInt(32), nil)
    big2To32M1 = new(big.Int).Sub(big2To32, big.NewInt(1))
)

func incCounter(ctr []byte) {
    if ctr[3]++; ctr[3] != 0 {
        return
    } else if ctr[2]++; ctr[2] != 0 {
        return
    } else if ctr[1]++; ctr[1] != 0 {
        return
    } else if ctr[0]++; ctr[0] != 0 {
        return
    }
    return
}

// NIST SP 800-56 Concatenation Key Derivation Function (see section 5.8.1).
func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) (k []byte, err error) {
    if s1 == nil {
        s1 = make([]byte, 0)
    }

    reps := ((kdLen + 7) * 8) / (hash.BlockSize() * 8)
    if big.NewInt(int64(reps)).Cmp(big2To32M1) > 0 {
        fmt.Println(big2To32M1)
        return nil, ErrKeyDataTooLong
    }

    counter := []byte{0, 0, 0, 1}
    k = make([]byte, 0)

    for i := 0; i <= reps; i++ {
        hash.Write(counter)
        hash.Write(z)
        hash.Write(s1)
        k = append(k, hash.Sum(nil)...)
        hash.Reset()
        incCounter(counter)
    }

    k = k[:kdLen]
    return
}

// messageTag computes the MAC of a message (called the tag) as per
// SEC 1, 3.5.
func messageTag(hash func() hash.Hash, km, msg, shared []byte) []byte {
    mac := hmac.New(hash, km)
    mac.Write(msg)
    mac.Write(shared)
    tag := mac.Sum(nil)
    return tag
}

// Generate an initialisation vector for CTR mode.
func generateIV(params *ECIESParams, rand io.Reader) (iv []byte, err error) {
    iv = make([]byte, params.BlockSize)
    _, err = io.ReadFull(rand, iv)
    return
}

// symEncrypt carries out CTR encryption using the block cipher specified in the
// parameters.
func symEncrypt(rand io.Reader, params *ECIESParams, key, m []byte) (ct []byte, err error) {
    c, err := params.Cipher(key)
    if err != nil {
        return
    }

    iv, err := generateIV(params, rand)
    if err != nil {
        return
    }
    ctr := cipher.NewCTR(c, iv)

    ct = make([]byte, len(m)+params.BlockSize)
    copy(ct, iv)
    ctr.XORKeyStream(ct[params.BlockSize:], m)
    return
}

// symDecrypt carries out CTR decryption using the block cipher specified in
// the parameters
func symDecrypt(rand io.Reader, params *ECIESParams, key, ct []byte) (m []byte, err error) {
    c, err := params.Cipher(key)
    if err != nil {
        return
    }

    ctr := cipher.NewCTR(c, ct[:params.BlockSize])

    m = make([]byte, len(ct)-params.BlockSize)
    ctr.XORKeyStream(m, ct[params.BlockSize:])
    return
}

// Encrypt encrypts a message using ECIES as specified in SEC 1, 5.1.
//
// s1 and s2 contain shared information that is not part of the resulting
// ciphertext. s1 is fed into key derivation, s2 is fed into the MAC. If the
// shared information parameters aren't being used, they should be nil.
func Encrypt(rand io.Reader, pub *PublicKey, m, s1, s2 []byte) (ct []byte, err error) {
    params := pub.Params
    if params == nil {
        if params = ParamsFromCurve(pub.Curve); params == nil {
            err = ErrUnsupportedECIESParameters
            return
        }
    }
    R, err := GenerateKey(rand, pub.Curve, params)
    if err != nil {
        return
    }

    hash := params.Hash()
    z, err := R.GenerateShared(pub, params.KeyLen, params.KeyLen)
    if err != nil {
        return
    }
    K, err := concatKDF(hash, z, s1, params.KeyLen+params.KeyLen)
    if err != nil {
        return
    }
    Ke := K[:params.KeyLen]
    Km := K[params.KeyLen:]
    hash.Write(Km)
    Km = hash.Sum(nil)
    hash.Reset()

    em, err := symEncrypt(rand, params, Ke, m)
    if err != nil || len(em) <= params.BlockSize {
        return
    }

    d := messageTag(params.Hash, Km, em, s2)

    Rb := elliptic.Marshal(pub.Curve, R.PublicKey.X, R.PublicKey.Y)
    ct = make([]byte, len(Rb)+len(em)+len(d))
    copy(ct, Rb)
    copy(ct[len(Rb):], em)
    copy(ct[len(Rb)+len(em):], d)
    return
}

// Decrypt decrypts an ECIES ciphertext.
func (prv *PrivateKey) Decrypt(rand io.Reader, c, s1, s2 []byte) (m []byte, err error) {
    if c == nil || len(c) == 0 {
        err = ErrInvalidMessage
        return
    }
    params := prv.PublicKey.Params
    if params == nil {
        if params = ParamsFromCurve(prv.PublicKey.Curve); params == nil {
            err = ErrUnsupportedECIESParameters
            return
        }
    }
    hash := params.Hash()

    var (
        rLen   int
        hLen   int = hash.Size()
        mStart int
        mEnd   int
    )

    switch c[0] {
    case 2, 3, 4:
        rLen = ((prv.PublicKey.Curve.Params().BitSize + 7) / 4)
        if len(c) < (rLen + hLen + 1) {
            err = ErrInvalidMessage
            return
        }
    default:
        err = ErrInvalidPublicKey
        return
    }

    mStart = rLen
    mEnd = len(c) - hLen

    R := new(PublicKey)
    R.Curve = prv.PublicKey.Curve
    R.X, R.Y = elliptic.Unmarshal(R.Curve, c[:rLen])
    if R.X == nil {
        err = ErrInvalidPublicKey
        return
    }
    if !R.Curve.IsOnCurve(R.X, R.Y) {
        err = ErrInvalidCurve
        return
    }

    z, err := prv.GenerateShared(R, params.KeyLen, params.KeyLen)
    if err != nil {
        return
    }

    K, err := concatKDF(hash, z, s1, params.KeyLen+params.KeyLen)
    if err != nil {
        return
    }

    Ke := K[:params.KeyLen]
    Km := K[params.KeyLen:]
    hash.Write(Km)
    Km = hash.Sum(nil)
    hash.Reset()

    d := messageTag(params.Hash, Km, c[mStart:mEnd], s2)
    if subtle.ConstantTimeCompare(c[mEnd:], d) != 1 {
        err = ErrInvalidMessage
        return
    }

    m, err = symDecrypt(rand, params, Ke, c[mStart:mEnd])
    return
}