From 6cf6981ed076d43514e86fa7e7a56c6bad1da583 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 10 Dec 2014 00:00:52 +0100 Subject: init --- .gitignore | 24 +++ LICENSE | 28 +++ README | 94 ++++++++++ asn1.go | 556 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ecies.go | 326 ++++++++++++++++++++++++++++++++++ ecies_test.go | 489 +++++++++++++++++++++++++++++++++++++++++++++++++++ params.go | 187 ++++++++++++++++++++ 7 files changed, 1704 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README create mode 100644 asn1.go create mode 100644 ecies.go create mode 100644 ecies_test.go create mode 100644 params.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..802b6744a --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe + +*~ diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..e1ed19a27 --- /dev/null +++ b/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2013 Kyle Isom +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. diff --git a/README b/README new file mode 100644 index 000000000..7ccf27a27 --- /dev/null +++ b/README @@ -0,0 +1,94 @@ +# NOTE + +This implementation is direct fork of Kylom's implementation. I claim no authorship over this code apart from some minor modifications. +Please be aware this code **has not yet been reviewed**. + +ecies implements the Elliptic Curve Integrated Encryption Scheme. + +The package is designed to be compliant with the appropriate NIST +standards, and therefore doesn't support the full SEC 1 algorithm set. + + +STATUS: + +ecies should be ready for use. The ASN.1 support is only complete so +far as to supported the listed algorithms before. + + +CAVEATS + +1. CMAC support is currently not present. + + +SUPPORTED ALGORITHMS + + SYMMETRIC CIPHERS HASH FUNCTIONS + AES128 SHA-1 + AES192 SHA-224 + AES256 SHA-256 + SHA-384 + ELLIPTIC CURVE SHA-512 + P256 + P384 KEY DERIVATION FUNCTION + P521 NIST SP 800-65a Concatenation KDF + +Curve P224 isn't supported because it does not provide a minimum security +level of AES128 with HMAC-SHA1. According to NIST SP 800-57, the security +level of P224 is 112 bits of security. Symmetric ciphers use CTR-mode; +message tags are computed using HMAC- function. + + +CURVE SELECTION + +According to NIST SP 800-57, the following curves should be selected: + + +----------------+-------+ + | SYMMETRIC SIZE | CURVE | + +----------------+-------+ + | 128-bit | P256 | + +----------------+-------+ + | 192-bit | P384 | + +----------------+-------+ + | 256-bit | P521 | + +----------------+-------+ + + +TODO + +1. Look at serialising the parameters with the SEC 1 ASN.1 module. +2. Validate ASN.1 formats with SEC 1. + + +TEST VECTORS + +The only test vectors I've found so far date from 1993, predating AES +and including only 163-bit curves. Therefore, there are no published +test vectors to compare to. + + +LICENSE + +ecies is released under the same license as the Go source code. See the +LICENSE file for details. + + +REFERENCES + +* SEC (Standard for Efficient Cryptography) 1, version 2.0: Elliptic + Curve Cryptography; Certicom, May 2009. + http://www.secg.org/download/aid-780/sec1-v2.pdf +* GEC (Guidelines for Efficient Cryptography) 2, version 0.3: Test + Vectors for SEC 1; Certicom, September 1999. + http://www.secg.org/collateral/gec2.pdf +* NIST SP 800-56a: Recommendation for Pair-Wise Key Establishment Schemes + Using Discrete Logarithm Cryptography. National Institute of Standards + and Technology, May 2007. + http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf +* Suite B Implementer’s Guide to NIST SP 800-56A. National Security + Agency, July 28, 2009. + http://www.nsa.gov/ia/_files/SuiteB_Implementer_G-113808.pdf +* NIST SP 800-57: Recommendation for Key Management – Part 1: General + (Revision 3). National Institute of Standards and Technology, July + 2012. + http://csrc.nist.gov/publications/nistpubs/800-57/sp800-57_part1_rev3_general.pdf + diff --git a/asn1.go b/asn1.go new file mode 100644 index 000000000..3ef194ea0 --- /dev/null +++ b/asn1.go @@ -0,0 +1,556 @@ +package ecies + +import ( + "bytes" + "crypto" + "crypto/elliptic" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "encoding/asn1" + "encoding/pem" + "fmt" + "hash" + "math/big" +) + +var ( + secgScheme = []int{1, 3, 132, 1} + shaScheme = []int{2, 16, 840, 1, 101, 3, 4, 2} + ansiX962Scheme = []int{1, 2, 840, 10045} + x963Scheme = []int{1, 2, 840, 63, 0} +) + +var ErrInvalidPrivateKey = fmt.Errorf("ecies: invalid private key") + +func doScheme(base, v []int) asn1.ObjectIdentifier { + var oidInts asn1.ObjectIdentifier + oidInts = append(oidInts, base...) + return append(oidInts, v...) +} + +// curve OID code taken from crypto/x509, including +// - oidNameCurve* +// - namedCurveFromOID +// - oidFromNamedCurve +// RFC 5480, 2.1.1.1. Named Curve +// +// secp224r1 OBJECT IDENTIFIER ::= { +// iso(1) identified-organization(3) certicom(132) curve(0) 33 } +// +// secp256r1 OBJECT IDENTIFIER ::= { +// iso(1) member-body(2) us(840) ansi-X9-62(10045) curves(3) +// prime(1) 7 } +// +// secp384r1 OBJECT IDENTIFIER ::= { +// iso(1) identified-organization(3) certicom(132) curve(0) 34 } +// +// secp521r1 OBJECT IDENTIFIER ::= { +// iso(1) identified-organization(3) certicom(132) curve(0) 35 } +// +// NB: secp256r1 is equivalent to prime256v1 +type secgNamedCurve asn1.ObjectIdentifier + +var ( + secgNamedCurveP224 = secgNamedCurve{1, 3, 132, 0, 33} + secgNamedCurveP256 = secgNamedCurve{1, 2, 840, 10045, 3, 1, 7} + secgNamedCurveP384 = secgNamedCurve{1, 3, 132, 0, 34} + secgNamedCurveP521 = secgNamedCurve{1, 3, 132, 0, 35} + rawCurveP224 = []byte{6, 5, 4, 3, 1, 2, 9, 4, 0, 3, 3} + rawCurveP256 = []byte{6, 8, 4, 2, 1, 3, 4, 7, 2, 2, 0, 6, 6, 1, 3, 1, 7} + rawCurveP384 = []byte{6, 5, 4, 3, 1, 2, 9, 4, 0, 3, 4} + rawCurveP521 = []byte{6, 5, 4, 3, 1, 2, 9, 4, 0, 3, 5} +) + +func rawCurve(curve elliptic.Curve) []byte { + switch curve { + case elliptic.P224(): + return rawCurveP224 + case elliptic.P256(): + return rawCurveP256 + case elliptic.P384(): + return rawCurveP384 + case elliptic.P521(): + return rawCurveP521 + default: + return nil + } +} + +func (curve secgNamedCurve) Equal(curve2 secgNamedCurve) bool { + if len(curve) != len(curve2) { + return false + } + for i, _ := range curve { + if curve[i] != curve2[i] { + return false + } + } + return true +} + +func namedCurveFromOID(curve secgNamedCurve) elliptic.Curve { + switch { + case curve.Equal(secgNamedCurveP224): + return elliptic.P224() + case curve.Equal(secgNamedCurveP256): + return elliptic.P256() + case curve.Equal(secgNamedCurveP384): + return elliptic.P384() + case curve.Equal(secgNamedCurveP521): + return elliptic.P521() + } + return nil +} + +func oidFromNamedCurve(curve elliptic.Curve) (secgNamedCurve, bool) { + switch curve { + case elliptic.P224(): + return secgNamedCurveP224, true + case elliptic.P256(): + return secgNamedCurveP256, true + case elliptic.P384(): + return secgNamedCurveP384, true + case elliptic.P521(): + return secgNamedCurveP521, true + } + + return nil, false +} + +// asnAlgorithmIdentifier represents the ASN.1 structure of the same name. See RFC +// 5280, section 4.1.1.2. +type asnAlgorithmIdentifier struct { + Algorithm asn1.ObjectIdentifier + Parameters asn1.RawValue `asn1:"optional"` +} + +func (a asnAlgorithmIdentifier) Cmp(b asnAlgorithmIdentifier) bool { + if len(a.Algorithm) != len(b.Algorithm) { + return false + } + for i, _ := range a.Algorithm { + if a.Algorithm[i] != b.Algorithm[i] { + return false + } + } + return true +} + +type asnHashFunction asnAlgorithmIdentifier + +var ( + oidSHA1 = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26} + oidSHA224 = doScheme(shaScheme, []int{4}) + oidSHA256 = doScheme(shaScheme, []int{1}) + oidSHA384 = doScheme(shaScheme, []int{2}) + oidSHA512 = doScheme(shaScheme, []int{3}) +) + +func hashFromOID(oid asn1.ObjectIdentifier) func() hash.Hash { + switch { + case oid.Equal(oidSHA1): + return sha1.New + case oid.Equal(oidSHA224): + return sha256.New224 + case oid.Equal(oidSHA256): + return sha256.New + case oid.Equal(oidSHA384): + return sha512.New384 + case oid.Equal(oidSHA512): + return sha512.New + } + return nil +} + +func oidFromHash(hash crypto.Hash) (asn1.ObjectIdentifier, bool) { + switch hash { + case crypto.SHA1: + return oidSHA1, true + case crypto.SHA224: + return oidSHA224, true + case crypto.SHA256: + return oidSHA256, true + case crypto.SHA384: + return oidSHA384, true + case crypto.SHA512: + return oidSHA512, true + default: + return nil, false + } +} + +var ( + asnAlgoSHA1 = asnHashFunction{ + Algorithm: oidSHA1, + } + asnAlgoSHA224 = asnHashFunction{ + Algorithm: oidSHA224, + } + asnAlgoSHA256 = asnHashFunction{ + Algorithm: oidSHA256, + } + asnAlgoSHA384 = asnHashFunction{ + Algorithm: oidSHA384, + } + asnAlgoSHA512 = asnHashFunction{ + Algorithm: oidSHA512, + } +) + +// type ASNasnSubjectPublicKeyInfo struct { +// +// } +// + +type asnSubjectPublicKeyInfo struct { + Algorithm asn1.ObjectIdentifier + PublicKey asn1.BitString + Supplements ecpksSupplements `asn1:"optional"` +} + +type asnECPKAlgorithms struct { + Type asn1.ObjectIdentifier +} + +var idPublicKeyType = doScheme(ansiX962Scheme, []int{2}) +var idEcPublicKey = doScheme(idPublicKeyType, []int{1}) +var idEcPublicKeySupplemented = doScheme(idPublicKeyType, []int{0}) + +func curveToRaw(curve elliptic.Curve) (rv asn1.RawValue, ok bool) { + switch curve { + case elliptic.P224(), elliptic.P256(), elliptic.P384(), elliptic.P521(): + raw := rawCurve(curve) + return asn1.RawValue{ + Tag: 30, + Bytes: raw[2:], + FullBytes: raw, + }, true + default: + return rv, false + } +} + +func asnECPublicKeyType(curve elliptic.Curve) (algo asnAlgorithmIdentifier, ok bool) { + raw, ok := curveToRaw(curve) + if !ok { + return + } else { + return asnAlgorithmIdentifier{Algorithm: idEcPublicKey, + Parameters: raw}, true + } +} + +type asnECPrivKeyVer int + +var asnECPrivKeyVer1 asnECPrivKeyVer = 1 + +type asnPrivateKey struct { + Version asnECPrivKeyVer + Private []byte + Curve secgNamedCurve `asn1:"optional"` + Public asn1.BitString +} + +var asnECDH = doScheme(secgScheme, []int{12}) + +type asnECDHAlgorithm asnAlgorithmIdentifier + +var ( + dhSinglePass_stdDH_sha1kdf = asnECDHAlgorithm{ + Algorithm: doScheme(x963Scheme, []int{2}), + } + dhSinglePass_stdDH_sha256kdf = asnECDHAlgorithm{ + Algorithm: doScheme(secgScheme, []int{11, 1}), + } + dhSinglePass_stdDH_sha384kdf = asnECDHAlgorithm{ + Algorithm: doScheme(secgScheme, []int{11, 2}), + } + dhSinglePass_stdDH_sha224kdf = asnECDHAlgorithm{ + Algorithm: doScheme(secgScheme, []int{11, 0}), + } + dhSinglePass_stdDH_sha512kdf = asnECDHAlgorithm{ + Algorithm: doScheme(secgScheme, []int{11, 3}), + } +) + +func (a asnECDHAlgorithm) Cmp(b asnECDHAlgorithm) bool { + if len(a.Algorithm) != len(b.Algorithm) { + return false + } + for i, _ := range a.Algorithm { + if a.Algorithm[i] != b.Algorithm[i] { + return false + } + } + return true +} + +// asnNISTConcatenation is the only supported KDF at this time. +type asnKeyDerivationFunction asnAlgorithmIdentifier + +var asnNISTConcatenationKDF = asnKeyDerivationFunction{ + Algorithm: doScheme(secgScheme, []int{17, 1}), +} + +func (a asnKeyDerivationFunction) Cmp(b asnKeyDerivationFunction) bool { + if len(a.Algorithm) != len(b.Algorithm) { + return false + } + for i, _ := range a.Algorithm { + if a.Algorithm[i] != b.Algorithm[i] { + return false + } + } + return true +} + +var eciesRecommendedParameters = doScheme(secgScheme, []int{7}) +var eciesSpecifiedParameters = doScheme(secgScheme, []int{8}) + +type asnECIESParameters struct { + KDF asnKeyDerivationFunction `asn1:"optional"` + Sym asnSymmetricEncryption `asn1:"optional"` + MAC asnMessageAuthenticationCode `asn1:"optional"` +} + +type asnSymmetricEncryption asnAlgorithmIdentifier + +var ( + aes128CTRinECIES = asnSymmetricEncryption{ + Algorithm: doScheme(secgScheme, []int{21, 0}), + } + aes192CTRinECIES = asnSymmetricEncryption{ + Algorithm: doScheme(secgScheme, []int{21, 1}), + } + aes256CTRinECIES = asnSymmetricEncryption{ + Algorithm: doScheme(secgScheme, []int{21, 2}), + } +) + +func (a asnSymmetricEncryption) Cmp(b asnSymmetricEncryption) bool { + if len(a.Algorithm) != len(b.Algorithm) { + return false + } + for i, _ := range a.Algorithm { + if a.Algorithm[i] != b.Algorithm[i] { + return false + } + } + return true +} + +type asnMessageAuthenticationCode asnAlgorithmIdentifier + +var ( + hmacFull = asnMessageAuthenticationCode{ + Algorithm: doScheme(secgScheme, []int{22}), + } +) + +func (a asnMessageAuthenticationCode) Cmp(b asnMessageAuthenticationCode) bool { + if len(a.Algorithm) != len(b.Algorithm) { + return false + } + for i, _ := range a.Algorithm { + if a.Algorithm[i] != b.Algorithm[i] { + return false + } + } + return true +} + +type ecpksSupplements struct { + ECDomain secgNamedCurve + ECCAlgorithms eccAlgorithmSet +} + +type eccAlgorithmSet struct { + ECDH asnECDHAlgorithm `asn1:"optional"` + ECIES asnECIESParameters `asn1:"optional"` +} + +func marshalSubjectPublicKeyInfo(pub *PublicKey) (subj asnSubjectPublicKeyInfo, err error) { + subj.Algorithm = idEcPublicKeySupplemented + curve, ok := oidFromNamedCurve(pub.Curve) + if !ok { + err = ErrInvalidPublicKey + return + } + subj.Supplements.ECDomain = curve + if pub.Params != nil { + subj.Supplements.ECCAlgorithms.ECDH = paramsToASNECDH(pub.Params) + subj.Supplements.ECCAlgorithms.ECIES = paramsToASNECIES(pub.Params) + } + pubkey := elliptic.Marshal(pub.Curve, pub.X, pub.Y) + subj.PublicKey = asn1.BitString{ + BitLength: len(pubkey) * 8, + Bytes: pubkey, + } + return +} + +// Encode a public key to DER format. +func MarshalPublic(pub *PublicKey) ([]byte, error) { + subj, err := marshalSubjectPublicKeyInfo(pub) + if err != nil { + return nil, err + } + return asn1.Marshal(subj) +} + +// Decode a DER-encoded public key. +func UnmarshalPublic(in []byte) (pub *PublicKey, err error) { + var subj asnSubjectPublicKeyInfo + + if _, err = asn1.Unmarshal(in, &subj); err != nil { + return + } + if !subj.Algorithm.Equal(idEcPublicKeySupplemented) { + err = ErrInvalidPublicKey + return + } + pub = new(PublicKey) + pub.Curve = namedCurveFromOID(subj.Supplements.ECDomain) + x, y := elliptic.Unmarshal(pub.Curve, subj.PublicKey.Bytes) + if x == nil { + err = ErrInvalidPublicKey + return + } + pub.X = x + pub.Y = y + pub.Params = new(ECIESParams) + asnECIEStoParams(subj.Supplements.ECCAlgorithms.ECIES, pub.Params) + asnECDHtoParams(subj.Supplements.ECCAlgorithms.ECDH, pub.Params) + if pub.Params == nil { + if pub.Params = ParamsFromCurve(pub.Curve); pub.Params == nil { + err = ErrInvalidPublicKey + } + } + return +} + +func marshalPrivateKey(prv *PrivateKey) (ecprv asnPrivateKey, err error) { + ecprv.Version = asnECPrivKeyVer1 + ecprv.Private = prv.D.Bytes() + + var ok bool + ecprv.Curve, ok = oidFromNamedCurve(prv.PublicKey.Curve) + if !ok { + err = ErrInvalidPrivateKey + return + } + + var pub []byte + if pub, err = MarshalPublic(&prv.PublicKey); err != nil { + return + } else { + ecprv.Public = asn1.BitString{ + BitLength: len(pub) * 8, + Bytes: pub, + } + } + return +} + +// Encode a private key to DER format. +func MarshalPrivate(prv *PrivateKey) ([]byte, error) { + ecprv, err := marshalPrivateKey(prv) + if err != nil { + return nil, err + } + return asn1.Marshal(ecprv) +} + +// Decode a private key from a DER-encoded format. +func UnmarshalPrivate(in []byte) (prv *PrivateKey, err error) { + var ecprv asnPrivateKey + + if _, err = asn1.Unmarshal(in, &ecprv); err != nil { + return + } else if ecprv.Version != asnECPrivKeyVer1 { + err = ErrInvalidPrivateKey + return + } + + privateCurve := namedCurveFromOID(ecprv.Curve) + if privateCurve == nil { + err = ErrInvalidPrivateKey + return + } + + prv = new(PrivateKey) + prv.D = new(big.Int).SetBytes(ecprv.Private) + + if pub, err := UnmarshalPublic(ecprv.Public.Bytes); err != nil { + return nil, err + } else { + prv.PublicKey = *pub + } + + return +} + +// Export a public key to PEM format. +func ExportPublicPEM(pub *PublicKey) (out []byte, err error) { + der, err := MarshalPublic(pub) + if err != nil { + return + } + + var block pem.Block + block.Type = "ELLIPTIC CURVE PUBLIC KEY" + block.Bytes = der + + buf := new(bytes.Buffer) + err = pem.Encode(buf, &block) + if err != nil { + return + } else { + out = buf.Bytes() + } + return +} + +// Export a private key to PEM format. +func ExportPrivatePEM(prv *PrivateKey) (out []byte, err error) { + der, err := MarshalPrivate(prv) + if err != nil { + return + } + + var block pem.Block + block.Type = "ELLIPTIC CURVE PRIVATE KEY" + block.Bytes = der + + buf := new(bytes.Buffer) + err = pem.Encode(buf, &block) + if err != nil { + return + } else { + out = buf.Bytes() + } + return +} + +// Import a PEM-encoded public key. +func ImportPublicPEM(in []byte) (pub *PublicKey, err error) { + p, _ := pem.Decode(in) + if p == nil || p.Type != "ELLIPTIC CURVE PUBLIC KEY" { + return nil, ErrInvalidPublicKey + } + + pub, err = UnmarshalPublic(p.Bytes) + return +} + +// Import a PEM-encoded private key. +func ImportPrivatePEM(in []byte) (prv *PrivateKey, err error) { + p, _ := pem.Decode(in) + if p == nil || p.Type != "ELLIPTIC CURVE PRIVATE KEY" { + return nil, ErrInvalidPrivateKey + } + + prv, err = UnmarshalPrivate(p.Bytes) + return +} diff --git a/ecies.go b/ecies.go new file mode 100644 index 000000000..0e2403d47 --- /dev/null +++ b/ecies.go @@ -0,0 +1,326 @@ +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") + ErrSharedKeyTooBig = fmt.Errorf("ecies: shared key is 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{pub.Curve, pub.X, 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{*pubECDSA, 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 { + err = ErrInvalidCurve + return + } + x, _ := pub.Curve.ScalarMult(pub.X, pub.Y, prv.D.Bytes()) + if x == nil || (x.BitLen()+7)/8 < (skLen+macLen) { + err = ErrSharedKeyTooBig + return + } + sk = x.Bytes()[:skLen+macLen] + return +} + +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 { + if shared == nil { + shared = make([]byte, 0) + } + mac := hmac.New(hash, km) + mac.Write(msg) + 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. 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 + } + + 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 +} diff --git a/ecies_test.go b/ecies_test.go new file mode 100644 index 000000000..943e4488e --- /dev/null +++ b/ecies_test.go @@ -0,0 +1,489 @@ +package ecies + +import ( + "bytes" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "flag" + "fmt" + "io/ioutil" + "testing" +) + +var dumpEnc bool + +func init() { + flDump := flag.Bool("dump", false, "write encrypted test message to file") + flag.Parse() + dumpEnc = *flDump +} + +// Ensure the KDF generates appropriately sized keys. +func TestKDF(t *testing.T) { + msg := []byte("Hello, world") + h := sha256.New() + + k, err := concatKDF(h, msg, nil, 64) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + if len(k) != 64 { + fmt.Printf("KDF: generated key is the wrong size (%d instead of 64\n", + len(k)) + t.FailNow() + } +} + +var skLen int +var ErrBadSharedKeys = fmt.Errorf("ecies: shared keys don't match") + +// cmpParams compares a set of ECIES parameters. We assume, as per the +// docs, that AES is the only supported symmetric encryption algorithm. +func cmpParams(p1, p2 *ECIESParams) bool { + if p1.hashAlgo != p2.hashAlgo { + return false + } else if p1.KeyLen != p2.KeyLen { + return false + } else if p1.BlockSize != p2.BlockSize { + return false + } + return true +} + +// cmpPublic returns true if the two public keys represent the same pojnt. +func cmpPublic(pub1, pub2 PublicKey) bool { + if pub1.X == nil || pub1.Y == nil { + fmt.Println(ErrInvalidPublicKey.Error()) + return false + } + if pub2.X == nil || pub2.Y == nil { + fmt.Println(ErrInvalidPublicKey.Error()) + return false + } + pub1Out := elliptic.Marshal(pub1.Curve, pub1.X, pub1.Y) + pub2Out := elliptic.Marshal(pub2.Curve, pub2.X, pub2.Y) + + return bytes.Equal(pub1Out, pub2Out) +} + +// cmpPrivate returns true if the two private keys are the same. +func cmpPrivate(prv1, prv2 *PrivateKey) bool { + if prv1 == nil || prv1.D == nil { + return false + } else if prv2 == nil || prv2.D == nil { + return false + } else if prv1.D.Cmp(prv2.D) != 0 { + return false + } else { + return cmpPublic(prv1.PublicKey, prv2.PublicKey) + } +} + +// Validate the ECDH component. +func TestSharedKey(t *testing.T) { + prv1, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + skLen = MaxSharedKeyLength(&prv1.PublicKey) / 2 + + prv2, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + sk1, err := prv1.GenerateShared(&prv2.PublicKey, skLen, skLen) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + sk2, err := prv2.GenerateShared(&prv1.PublicKey, skLen, skLen) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + if !bytes.Equal(sk1, sk2) { + fmt.Println(ErrBadSharedKeys.Error()) + t.FailNow() + } +} + +// Verify that the key generation code fails when too much key data is +// requested. +func TestTooBigSharedKey(t *testing.T) { + prv1, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + prv2, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + _, err = prv1.GenerateShared(&prv2.PublicKey, skLen*2, skLen*2) + if err != ErrSharedKeyTooBig { + fmt.Println("ecdh: shared key should be too large for curve") + t.FailNow() + } + + _, err = prv2.GenerateShared(&prv1.PublicKey, skLen*2, skLen*2) + if err != ErrSharedKeyTooBig { + fmt.Println("ecdh: shared key should be too large for curve") + t.FailNow() + } +} + +// Ensure a public key can be successfully marshalled and unmarshalled, and +// that the decoded key is the same as the original. +func TestMarshalPublic(t *testing.T) { + prv, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + out, err := MarshalPublic(&prv.PublicKey) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + pub, err := UnmarshalPublic(out) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + if !cmpPublic(prv.PublicKey, *pub) { + fmt.Println("ecies: failed to unmarshal public key") + t.FailNow() + } +} + +// Ensure that a private key can be encoded into DER format, and that +// the resulting key is properly parsed back into a public key. +func TestMarshalPrivate(t *testing.T) { + prv, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + out, err := MarshalPrivate(prv) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + if dumpEnc { + ioutil.WriteFile("test.out", out, 0644) + } + + prv2, err := UnmarshalPrivate(out) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + if !cmpPrivate(prv, prv2) { + fmt.Println("ecdh: private key import failed") + t.FailNow() + } +} + +// Ensure that a private key can be successfully encoded to PEM format, and +// the resulting key is properly parsed back in. +func TestPrivatePEM(t *testing.T) { + prv, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + out, err := ExportPrivatePEM(prv) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + if dumpEnc { + ioutil.WriteFile("test.key", out, 0644) + } + + prv2, err := ImportPrivatePEM(out) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } else if !cmpPrivate(prv, prv2) { + fmt.Println("ecdh: import from PEM failed") + t.FailNow() + } +} + +// Ensure that a public key can be successfully encoded to PEM format, and +// the resulting key is properly parsed back in. +func TestPublicPEM(t *testing.T) { + prv, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + out, err := ExportPublicPEM(&prv.PublicKey) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + if dumpEnc { + ioutil.WriteFile("test.pem", out, 0644) + } + + pub2, err := ImportPublicPEM(out) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } else if !cmpPublic(prv.PublicKey, *pub2) { + fmt.Println("ecdh: import from PEM failed") + t.FailNow() + } +} + +// Benchmark the generation of P256 keys. +func BenchmarkGenerateKeyP256(b *testing.B) { + for i := 0; i < b.N; i++ { + if _, err := GenerateKey(rand.Reader, elliptic.P256(), nil); err != nil { + fmt.Println(err.Error()) + b.FailNow() + } + } +} + +// Benchmark the generation of P256 shared keys. +func BenchmarkGenSharedKeyP256(b *testing.B) { + prv, err := GenerateKey(rand.Reader, elliptic.P256(), nil) + if err != nil { + fmt.Println(err.Error()) + b.FailNow() + } + + for i := 0; i < b.N; i++ { + _, err := prv.GenerateShared(&prv.PublicKey, skLen, skLen) + if err != nil { + fmt.Println(err.Error()) + b.FailNow() + } + } +} + +// Verify that an encrypted message can be successfully decrypted. +func TestEncryptDecrypt(t *testing.T) { + prv1, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + prv2, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + message := []byte("Hello, world.") + ct, err := Encrypt(rand.Reader, &prv2.PublicKey, message, nil, nil) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + pt, err := prv2.Decrypt(rand.Reader, ct, nil, nil) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + if !bytes.Equal(pt, message) { + fmt.Println("ecies: plaintext doesn't match message") + t.FailNow() + } + + _, err = prv1.Decrypt(rand.Reader, ct, nil, nil) + if err == nil { + fmt.Println("ecies: encryption should not have succeeded") + t.FailNow() + } +} + +// TestMarshalEncryption validates the encode/decode produces a valid +// ECIES encryption key. +func TestMarshalEncryption(t *testing.T) { + prv1, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + out, err := MarshalPrivate(prv1) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + prv2, err := UnmarshalPrivate(out) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + message := []byte("Hello, world.") + ct, err := Encrypt(rand.Reader, &prv2.PublicKey, message, nil, nil) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + pt, err := prv2.Decrypt(rand.Reader, ct, nil, nil) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + if !bytes.Equal(pt, message) { + fmt.Println("ecies: plaintext doesn't match message") + t.FailNow() + } + + _, err = prv1.Decrypt(rand.Reader, ct, nil, nil) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + +} + +type testCase struct { + Curve elliptic.Curve + Name string + Expected bool +} + +var testCases = []testCase{ + testCase{ + Curve: elliptic.P224(), + Name: "P224", + Expected: false, + }, + testCase{ + Curve: elliptic.P256(), + Name: "P256", + Expected: true, + }, + testCase{ + Curve: elliptic.P384(), + Name: "P384", + Expected: true, + }, + testCase{ + Curve: elliptic.P521(), + Name: "P521", + Expected: true, + }, +} + +// Test parameter selection for each curve, and that P224 fails automatic +// parameter selection (see README for a discussion of P224). Ensures that +// selecting a set of parameters automatically for the given curve works. +func TestParamSelection(t *testing.T) { + for _, c := range testCases { + testParamSelection(t, c) + } +} + +func testParamSelection(t *testing.T, c testCase) { + params := ParamsFromCurve(c.Curve) + if params == nil && c.Expected { + fmt.Printf("%s (%s)\n", ErrInvalidParams.Error(), c.Name) + t.FailNow() + } else if params != nil && !c.Expected { + fmt.Printf("ecies: parameters should be invalid (%s)\n", + c.Name) + t.FailNow() + } + + prv1, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + fmt.Printf("%s (%s)\n", err.Error(), c.Name) + t.FailNow() + } + + prv2, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + fmt.Printf("%s (%s)\n", err.Error(), c.Name) + t.FailNow() + } + + message := []byte("Hello, world.") + ct, err := Encrypt(rand.Reader, &prv2.PublicKey, message, nil, nil) + if err != nil { + fmt.Printf("%s (%s)\n", err.Error(), c.Name) + t.FailNow() + } + + pt, err := prv2.Decrypt(rand.Reader, ct, nil, nil) + if err != nil { + fmt.Printf("%s (%s)\n", err.Error(), c.Name) + t.FailNow() + } + + if !bytes.Equal(pt, message) { + fmt.Printf("ecies: plaintext doesn't match message (%s)\n", + c.Name) + t.FailNow() + } + + _, err = prv1.Decrypt(rand.Reader, ct, nil, nil) + if err == nil { + fmt.Printf("ecies: encryption should not have succeeded (%s)\n", + c.Name) + t.FailNow() + } + +} + +// Ensure that the basic public key validation in the decryption operation +// works. +func TestBasicKeyValidation(t *testing.T) { + badBytes := []byte{0, 1, 5, 6, 7, 8, 9} + + prv, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + message := []byte("Hello, world.") + ct, err := Encrypt(rand.Reader, &prv.PublicKey, message, nil, nil) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + for _, b := range badBytes { + ct[0] = b + _, err := prv.Decrypt(rand.Reader, ct, nil, nil) + if err != ErrInvalidPublicKey { + fmt.Println("ecies: validated an invalid key") + t.FailNow() + } + } +} diff --git a/params.go b/params.go new file mode 100644 index 000000000..b968c7c17 --- /dev/null +++ b/params.go @@ -0,0 +1,187 @@ +package ecies + +// This file contains parameters for ECIES encryption, specifying the +// symmetric encryption and HMAC parameters. + +import ( + "crypto" + "crypto/aes" + "crypto/cipher" + "crypto/elliptic" + "crypto/sha256" + "crypto/sha512" + "fmt" + "hash" +) + +// The default curve for this package is the NIST P256 curve, which +// provides security equivalent to AES-128. +var DefaultCurve = elliptic.P256() + +var ( + ErrUnsupportedECDHAlgorithm = fmt.Errorf("ecies: unsupported ECDH algorithm") + ErrUnsupportedECIESParameters = fmt.Errorf("ecies: unsupported ECIES parameters") +) + +type ECIESParams struct { + Hash func() hash.Hash // hash function + hashAlgo crypto.Hash + Cipher func([]byte) (cipher.Block, error) // symmetric cipher + BlockSize int // block size of symmetric cipher + KeyLen int // length of symmetric key +} + +// Standard ECIES parameters: +// * ECIES using AES128 and HMAC-SHA-256-16 +// * ECIES using AES256 and HMAC-SHA-256-32 +// * ECIES using AES256 and HMAC-SHA-384-48 +// * ECIES using AES256 and HMAC-SHA-512-64 +var ( + ECIES_AES128_SHA256 *ECIESParams + ECIES_AES256_SHA256 *ECIESParams + ECIES_AES256_SHA384 *ECIESParams + ECIES_AES256_SHA512 *ECIESParams +) + +func init() { + ECIES_AES128_SHA256 = &ECIESParams{ + Hash: sha256.New, + hashAlgo: crypto.SHA256, + Cipher: aes.NewCipher, + BlockSize: aes.BlockSize, + KeyLen: 16, + } + + ECIES_AES256_SHA256 = &ECIESParams{ + Hash: sha256.New, + hashAlgo: crypto.SHA256, + Cipher: aes.NewCipher, + BlockSize: aes.BlockSize, + KeyLen: 32, + } + + ECIES_AES256_SHA384 = &ECIESParams{ + Hash: sha512.New384, + hashAlgo: crypto.SHA384, + Cipher: aes.NewCipher, + BlockSize: aes.BlockSize, + KeyLen: 32, + } + + ECIES_AES256_SHA512 = &ECIESParams{ + Hash: sha512.New, + hashAlgo: crypto.SHA512, + Cipher: aes.NewCipher, + BlockSize: aes.BlockSize, + KeyLen: 32, + } +} + +var paramsFromCurve = map[elliptic.Curve]*ECIESParams{ + elliptic.P256(): ECIES_AES128_SHA256, + elliptic.P384(): ECIES_AES256_SHA384, + elliptic.P521(): ECIES_AES256_SHA512, +} + +func AddParamsForCurve(curve elliptic.Curve, params *ECIESParams) { + paramsFromCurve[curve] = params +} + +// ParamsFromCurve selects parameters optimal for the selected elliptic curve. +// Only the curves P256, P384, and P512 are supported. +func ParamsFromCurve(curve elliptic.Curve) (params *ECIESParams) { + return paramsFromCurve[curve] + + /* + switch curve { + case elliptic.P256(): + return ECIES_AES128_SHA256 + case elliptic.P384(): + return ECIES_AES256_SHA384 + case elliptic.P521(): + return ECIES_AES256_SHA512 + default: + return nil + } + */ +} + +// ASN.1 encode the ECIES parameters relevant to the encryption operations. +func paramsToASNECIES(params *ECIESParams) (asnParams asnECIESParameters) { + if nil == params { + return + } + asnParams.KDF = asnNISTConcatenationKDF + asnParams.MAC = hmacFull + switch params.KeyLen { + case 16: + asnParams.Sym = aes128CTRinECIES + case 24: + asnParams.Sym = aes192CTRinECIES + case 32: + asnParams.Sym = aes256CTRinECIES + } + return +} + +// ASN.1 encode the ECIES parameters relevant to ECDH. +func paramsToASNECDH(params *ECIESParams) (algo asnECDHAlgorithm) { + switch params.hashAlgo { + case crypto.SHA224: + algo = dhSinglePass_stdDH_sha224kdf + case crypto.SHA256: + algo = dhSinglePass_stdDH_sha256kdf + case crypto.SHA384: + algo = dhSinglePass_stdDH_sha384kdf + case crypto.SHA512: + algo = dhSinglePass_stdDH_sha512kdf + } + return +} + +// ASN.1 decode the ECIES parameters relevant to the encryption stage. +func asnECIEStoParams(asnParams asnECIESParameters, params *ECIESParams) { + if !asnParams.KDF.Cmp(asnNISTConcatenationKDF) { + params = nil + return + } else if !asnParams.MAC.Cmp(hmacFull) { + params = nil + return + } + + switch { + case asnParams.Sym.Cmp(aes128CTRinECIES): + params.KeyLen = 16 + params.BlockSize = 16 + params.Cipher = aes.NewCipher + case asnParams.Sym.Cmp(aes192CTRinECIES): + params.KeyLen = 24 + params.BlockSize = 16 + params.Cipher = aes.NewCipher + case asnParams.Sym.Cmp(aes256CTRinECIES): + params.KeyLen = 32 + params.BlockSize = 16 + params.Cipher = aes.NewCipher + default: + params = nil + } +} + +// ASN.1 decode the ECIES parameters relevant to ECDH. +func asnECDHtoParams(asnParams asnECDHAlgorithm, params *ECIESParams) { + if asnParams.Cmp(dhSinglePass_stdDH_sha224kdf) { + params.hashAlgo = crypto.SHA224 + params.Hash = sha256.New224 + } else if asnParams.Cmp(dhSinglePass_stdDH_sha256kdf) { + params.hashAlgo = crypto.SHA256 + params.Hash = sha256.New + } else if asnParams.Cmp(dhSinglePass_stdDH_sha384kdf) { + params.hashAlgo = crypto.SHA384 + params.Hash = sha512.New384 + } else if asnParams.Cmp(dhSinglePass_stdDH_sha512kdf) { + params.hashAlgo = crypto.SHA512 + params.Hash = sha512.New + } else { + params = nil + } +} -- cgit From ce796dcdbf3d29181a60eb8c734ef5050a5306c4 Mon Sep 17 00:00:00 2001 From: Gustav Simonsson Date: Thu, 11 Dec 2014 13:25:01 +0100 Subject: Update reference pdf links --- README | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README b/README index 7ccf27a27..2650c7b9f 100644 --- a/README +++ b/README @@ -76,10 +76,10 @@ REFERENCES * SEC (Standard for Efficient Cryptography) 1, version 2.0: Elliptic Curve Cryptography; Certicom, May 2009. - http://www.secg.org/download/aid-780/sec1-v2.pdf + http://www.secg.org/sec1-v2.pdf * GEC (Guidelines for Efficient Cryptography) 2, version 0.3: Test Vectors for SEC 1; Certicom, September 1999. - http://www.secg.org/collateral/gec2.pdf + http://read.pudn.com/downloads168/doc/772358/TestVectorsforSEC%201-gec2.pdf * NIST SP 800-56a: Recommendation for Pair-Wise Key Establishment Schemes Using Discrete Logarithm Cryptography. National Institute of Standards and Technology, May 2007. -- cgit From 52a46e61f948d9c5f4a4e993bc1870bd79a19b56 Mon Sep 17 00:00:00 2001 From: Gustav Simonsson Date: Wed, 11 Feb 2015 20:03:52 +0100 Subject: Correct ECIES shared key length check * Ensure the ECIES shared key is padded with zero bytes if it's smaller than the requested key length. * Split the ECIES shared key error into two; one for when the generated key is too big for the params and one for when it's nil (point of infinity returned by the curve scalar multiplication). --- ecies.go | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/ecies.go b/ecies.go index 0e2403d47..18952fc0b 100644 --- a/ecies.go +++ b/ecies.go @@ -13,11 +13,12 @@ import ( ) 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") - ErrSharedKeyTooBig = fmt.Errorf("ecies: shared key is too big") + 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. @@ -90,16 +91,20 @@ func MaxSharedKeyLength(pub *PublicKey) int { // 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 { - err = ErrInvalidCurve - return + 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 || (x.BitLen()+7)/8 < (skLen+macLen) { - err = ErrSharedKeyTooBig - return + if x == nil { + return nil, ErrSharedKeyIsPointAtInfinity } - sk = x.Bytes()[:skLen+macLen] - return + + sk = make([]byte, skLen+macLen) + skBytes := x.Bytes() + copy(sk[len(sk)-len(skBytes):], skBytes) + return sk, nil } var ( -- cgit From 5136fc9ab71f77b0741c52b312ca9fdbfb5240c3 Mon Sep 17 00:00:00 2001 From: Gustav Simonsson Date: Thu, 12 Feb 2015 02:31:00 +0100 Subject: Fix ECIES params nil bug * Change ECIES params init function to static var as it does not have state; fixes TestMarshalencryption. --- params.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/params.go b/params.go index b968c7c17..fd1ceedd0 100644 --- a/params.go +++ b/params.go @@ -36,14 +36,8 @@ type ECIESParams struct { // * ECIES using AES256 and HMAC-SHA-256-32 // * ECIES using AES256 and HMAC-SHA-384-48 // * ECIES using AES256 and HMAC-SHA-512-64 -var ( - ECIES_AES128_SHA256 *ECIESParams - ECIES_AES256_SHA256 *ECIESParams - ECIES_AES256_SHA384 *ECIESParams - ECIES_AES256_SHA512 *ECIESParams -) -func init() { +var ( ECIES_AES128_SHA256 = &ECIESParams{ Hash: sha256.New, hashAlgo: crypto.SHA256, @@ -75,7 +69,7 @@ func init() { BlockSize: aes.BlockSize, KeyLen: 32, } -} +) var paramsFromCurve = map[elliptic.Curve]*ECIESParams{ elliptic.P256(): ECIES_AES128_SHA256, -- cgit