Created
May 11, 2019 11:46
-
-
Save gythialy/049b169528ae3ed2333d8db905a19c67 to your computer and use it in GitHub Desktop.
It is an ECIES Go implementation, a hybrid asymmetric–symmetric key encryption algorithm based on the Diffie–Hellman key exchange, specifically uses one of the following combinations: Mode P256 P-256 curve (FIPS 186-3, section D.2.3) AES-128 for symmetric encryption with the CTR (Counter) mode Poly1305 for message aunthentication (MAC with 32-bi…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package ecies | |
| import ( | |
| "crypto/aes" | |
| "crypto/cipher" | |
| "crypto/elliptic" | |
| "crypto/sha256" | |
| "crypto/sha512" | |
| "hash" | |
| "io" | |
| "math/big" | |
| "strconv" | |
| "golang.org/x/crypto/poly1305" | |
| ) | |
| // PublicKey is a structure for storing information relevant to the public key | |
| type PublicKey struct { | |
| X *big.Int | |
| Y *big.Int | |
| elliptic.Curve | |
| } | |
| // PrivateKey is a structure for storing information relevant to the private key | |
| type PrivateKey struct { | |
| PublicKey | |
| D *big.Int | |
| } | |
| // GenerateKey is the constructor for Private-Public key pair | |
| func GenerateKey(rand io.Reader, curve elliptic.Curve) *PrivateKey { | |
| privateBytes, x, y, err := elliptic.GenerateKey(curve, rand) | |
| check(err) | |
| public := PublicKey{X: x, Y: y, Curve: curve} | |
| private := PrivateKey{PublicKey: public, D: new(big.Int).SetBytes(privateBytes)} | |
| return &private | |
| } | |
| // DeriveShared is method to derive a shared secret | |
| func (private *PrivateKey) DeriveShared(public *PublicKey, keySize int) []byte { | |
| if private.PublicKey.Curve != public.Curve { | |
| panic("Curves don't match") | |
| } | |
| if 2*keySize > (public.Curve.Params().BitSize+7)/8 { | |
| panic("Shared key length is too long") | |
| } | |
| x, _ := public.Curve.ScalarMult(public.X, public.Y, private.D.Bytes()) | |
| if x == nil { | |
| panic("Scalar multiplication resulted in infinity") | |
| } | |
| shared := x.Bytes() | |
| return shared | |
| } | |
| // Key-Derivation Function | |
| func kdf(hash hash.Hash, shared, s1 []byte) []byte { | |
| hash.Write(shared) | |
| if s1 != nil { | |
| hash.Write(s1) | |
| } | |
| key := hash.Sum(nil) | |
| hash.Reset() | |
| return key | |
| } | |
| func sumTag(in, shared []byte, key *[32]byte) [16]byte { | |
| var out [16]byte | |
| poly1305.Sum(&out, append(in, shared...), key) | |
| return out | |
| } | |
| func verifyTag(mac *[16]byte, in, shared []byte, key *[32]byte) bool { | |
| return poly1305.Verify(mac, append(in, shared...), key) | |
| } | |
| func encryptSymmetric(rand io.Reader, in, key []byte) []byte { | |
| block, err := aes.NewCipher(key) | |
| check(err) | |
| nonce := getCryptoRandVec(rand, aes.BlockSize) | |
| cipher := cipher.NewCTR(block, nonce) | |
| out := make([]byte, len(in)) | |
| cipher.XORKeyStream(out, in) | |
| out = append(nonce, out...) | |
| return out | |
| } | |
| func decryptSymmetric(in, key []byte) []byte { | |
| block, err := aes.NewCipher(key) | |
| check(err) | |
| cipher := cipher.NewCTR(block, in[:aes.BlockSize]) | |
| out := make([]byte, len(in)-aes.BlockSize) | |
| cipher.XORKeyStream(out, in[aes.BlockSize:]) | |
| return out | |
| } | |
| // Encrypt is a function for encryption | |
| func Encrypt(rand io.Reader, public *PublicKey, in, s1, s2 []byte) []byte { | |
| private := GenerateKey(rand, public.Curve) | |
| curveName := public.Curve.Params().Name | |
| var hashFunc hash.Hash | |
| if curveName == "P-521" { | |
| hashFunc = sha512.New() | |
| } else { | |
| hashFunc = sha256.New() | |
| } | |
| keySize := hashFunc.Size() / 2 | |
| shared := private.DeriveShared(public, keySize) | |
| K := kdf(hashFunc, shared, s1) | |
| Ke := K[:keySize] | |
| Km := K[keySize:] | |
| if len(Km) < 32 { | |
| // Hash K_m so that it's 32 bytes long (required for Poly1305) | |
| hashFunc.Write(Km) | |
| Km = hashFunc.Sum(nil) | |
| hashFunc.Reset() | |
| } | |
| c := encryptSymmetric(rand, in, Ke) | |
| tag := sumTag(c, s2, to32ByteArray(Km)) | |
| R := elliptic.Marshal(public.Curve, private.PublicKey.X, private.PublicKey.Y) | |
| out := make([]byte, len(R)+len(c)+len(tag)) | |
| copy(out, R) | |
| copy(out[len(R):], c) | |
| copy(out[len(R)+len(c):], tag[:]) | |
| return out | |
| } | |
| // Decrypt is a function for decryption | |
| func Decrypt(private *PrivateKey, in, s1, s2 []byte) []byte { | |
| curveName := private.PublicKey.Curve.Params().Name | |
| var hashFunc hash.Hash | |
| if curveName == "P-521" { | |
| hashFunc = sha512.New() | |
| } else { | |
| hashFunc = sha256.New() | |
| } | |
| keySize := hashFunc.Size() / 2 | |
| var messageStart int | |
| macLen := poly1305.TagSize | |
| if in[0] == 2 || in[0] == 3 || in[0] == 4 { | |
| messageStart = (private.PublicKey.Curve.Params().BitSize + 7) / 4 | |
| if len(in) < (messageStart + macLen + 1) { | |
| panic("Invalid message") | |
| } | |
| } else { | |
| panic("Invalid public key") | |
| } | |
| if curveName == "P-521" { | |
| // P-521 curve is serialized into 133 bytes, above formula yields size of only 132, therefore we must add 1 | |
| // P-256 curve is serialized into 65 bytes, above formula yields correct result | |
| messageStart++ | |
| } | |
| messageEnd := len(in) - macLen | |
| R := new(PublicKey) | |
| R.Curve = private.PublicKey.Curve | |
| R.X, R.Y = elliptic.Unmarshal(R.Curve, in[:messageStart]) | |
| if R.X == nil { | |
| panic("Invalid public key. Maybe you didn't specify the right mode?") | |
| } | |
| if !R.Curve.IsOnCurve(R.X, R.Y) { | |
| panic("Invalid curve") | |
| } | |
| shared := private.DeriveShared(R, keySize) | |
| K := kdf(hashFunc, shared, s1) | |
| Ke := K[:keySize] | |
| Km := K[keySize:] | |
| if len(Km) < 32 { | |
| // Hash K_m so that it's 32 bytes long (required for Poly1305) | |
| hashFunc.Write(Km) | |
| Km = hashFunc.Sum(nil) | |
| hashFunc.Reset() | |
| } | |
| match := verifyTag(to16ByteArray(in[messageEnd:]), in[messageStart:messageEnd], s2, to32ByteArray(Km)) | |
| if !match { | |
| panic("Message tags don't match") | |
| } | |
| out := decryptSymmetric(in[messageStart:messageEnd], Ke) | |
| return out | |
| } | |
| func check(e error) { | |
| if e != nil { | |
| panic(e) | |
| } | |
| } | |
| func getCryptoRandVec(rand io.Reader, len int) []byte { | |
| out := make([]byte, len) | |
| _, err := io.ReadFull(rand, out) | |
| check(err) | |
| return out | |
| } | |
| func to32ByteArray(in []byte) *[32]byte { | |
| if len(in) != 32 { | |
| panic("Input array size does not match. Expected 32, but got " + strconv.Itoa(len(in))) | |
| } | |
| var out [32]byte | |
| for i := 0; i < 32; i++ { | |
| out[i] = in[i] | |
| } | |
| return &out | |
| } | |
| func to16ByteArray(in []byte) *[16]byte { | |
| if len(in) != 16 { | |
| panic("Input array size does not match. Expected 16, but got " + strconv.Itoa(len(in))) | |
| } | |
| var out [16]byte | |
| for i := 0; i < 16; i++ { | |
| out[i] = in[i] | |
| } | |
| return &out | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment