ecies

package module
v1.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 28, 2023 License: MIT Imports: 18 Imported by: 1

README

Introduction

This package is an implementation of the Elliptic Curve Integrated Encryption Scheme (ECIES), following the specification recommended in Victor Shoup's paper. We refer to this implementation as the "Shoup's version" of ECIES.

We noticed that while Botan and Bouncy Castle have implemented the Shoup's version of ECIES, we could not find a Golang package that implemented it. As a result, interactions of ECIES-encrypted data among ecosystems coded in different languages can be difficult. To address this, the Disney+Hotstar Security Team implemented the Shoup's version of ECIES in Golang and is contributing it back to the community.

According to the wiki, our implementation is compatible with the Botan and Bouncy Castle implementations.

The encryption flow is as follows:
  1. The sender generates an ephemeral key pair (ephemeral public key and ephemeral private key)
  2. The sender calculates a shared secret using the Key Agreement, getting ephemeral private key and receiver's public key as the input parameters
  3. The sender derives keys using the Key Derivation Function, getting the shared secret as the input parameters
  4. The sender retrieves two keys from the derived keys, one is for encryption and the other is for mac
  5. The sender calculates cipher message using the Symmetric Cipher, getting the plain message and encryption key as the input parameters.
  6. The sender calculates mac, getting the cipher message and mac key as the input parameters
  7. The sender sends the ephemeral public key, the cipher message, and the mac to the receiver.
The decryption flow is as follows:
  1. The receiver retrieves the ephemeral public key
  2. The receiver calculates a shared secret using the Key Agreement, getting ephemeral public key and its own private key as the input parameters
  3. The receiver derives keys using the Key Derivation Function, getting the shared secret as the input parameters
  4. The receiver retrieves two keys from the derived keys, one is for encryption and the other is for mac
  5. The receiver validates the mac, getting the cipher message and mac key as the input parameters
  6. The receiver calculates the plain message using the Symmetric Cipher, getting the cipher message and encryption key as the input parameters.

Quick Start


import (
	"fmt"
)

func example1() {
	k, err := GenerateKey()
	if err != nil {
		panic("failed to generate key pair")
	}
	privateKey := k
	publicKey := k.PublicKey

	serializedPrivateKey := HexEncode(SerializePrivateKey(privateKey))
	serializedPublicKey := HexEncode(SerializePublicKey(publicKey))
	fmt.Println("privateKey=" + serializedPrivateKey)
	fmt.Println("publicKey=" + serializedPublicKey)

	deserializedPrivateKey := DeserializePrivateKey(HexDecodeWithoutError(serializedPrivateKey))
	deserializedPublicKey, err := DeserializePublicKey(HexDecodeWithoutError(serializedPublicKey))
	if err != nil {
		panic("failed to deserialize key pair")
	}
	fmt.Println(deserializedPrivateKey.D.Cmp(privateKey.D) == 0)
	fmt.Println(deserializedPublicKey.X.Cmp(publicKey.X) == 0)
	fmt.Println(deserializedPublicKey.Y.Cmp(publicKey.Y) == 0)

	ecies := NewECIES()
	plainText := "hello world"
	encryptedTextBytes, err := ecies.Encrypt(publicKey, []byte(plainText))
	if err != nil {
		panic("failed to encrypt message")
	}
	decryptedTextBytes, err := ecies.Decrypt(privateKey, encryptedTextBytes)
	if err != nil {
		panic("failed to decrypt message")
	}
	fmt.Println(string(decryptedTextBytes))

}

Usage

There are four key parts in the ECIES. They are Key Agreement, Key Derivation Function, Symmetric Cipher, and MAC. In our implementation, we provide interface for each of them so that developers can have their own implementations. We can use this method ecies.NewCustomizedECIES to use our own component. We can also switch to different curve by set the variable CURVE

Also, we provide a default implementation using this method ecies.NewECIES , the following is the details.

Part Spec Comment
Elliptic Curve secp256r1 Also known as P256 or prime256v1.
Key Agreement ECDHBasicAgreement P1363 7.2.1 ECSVDP-DH
Key Derivation Function KDF2 with SHA256 IEEE P1363 or ISO 18033
MAC HmacSHA256
Symmetric Cipher AES-CBC-PKCS7Padding

Benchmark

In this part, we are giving some benchmarks for different elliptic curves, they are P256P384 and P521. Refer to here to get more details about these curves.

Test Machine configuration:
  • CPU: Apple M1 Pro
  • Memory: 16 GB
  • OS: macOS Ventura 13.0.1
Benchmark Results
  • P256 is also known as secp256r1, the benchmark of which is as below.
Message Length (Byte) Encryption Time (ns/op) Decryption Time (ns/op)
128 57043 44975
256 57432 45092
512 56926 44992
1024 59012 45314
2048 59083 46418
4096 62557 48598
8192 69278 52830
  • P384 is also known as secp384r1, the benchmark of which is as below.
Message Length (Byte) Encryption Time (ns/op) Decryption Time (ns/op)
128 998769 500437
256 1005223 502177
512 1012345 498896
1024 1007236 498630
2048 1020338 508600
4096 1014529 509956
8192 1034656 521036
  • P521 is also known as secp521r1, the benchmark of which is as below.
Message Length (Byte) Encryption Time (ns/op) Decryption Time (ns/op)
128 3101959 1507083
256 3042652 1529502
512 3075750 1550932
1024 3140785 1545537
2048 3146527 1584484
4096 3130114 1565796
8192 3163863 1561788
  • For comparing with P256, we gave the benchmark of RSA3072 (RSA3072 has the same security level as P256).
Message Length (Byte) Encryption Time (ns/op) Decryption Time (ns/op)
128 71317 2747494
256 90165 3709487

Compatibility

So far, we have verified the compatibility with C++ Botan and Java BouncyCastle. Let's take the BouncyCastle as an example.

  • BouncyCastle
<dependency>
   <groupId>org.bouncycastle</groupId>
   <artifactId>bcpkix-jdk18on</artifactId>
   <version>1.71</version>
</dependency>

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.ECGenParameterSpec;
import javax.crypto.Cipher;
import org.bouncycastle.crypto.agreement.ECDHBasicAgreement;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.engines.IESEngine;
import org.bouncycastle.crypto.generators.KDF2BytesGenerator;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PKCS7Padding;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.util.DigestFactory;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.IESCipher;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.IESParameterSpec;
import org.bouncycastle.util.BigIntegers;
import org.bouncycastle.util.encoders.Hex;

public class BouncyCastleECIES {

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    private static final String EC_ALGORITHM_NAME = "EC";
    private static final String CURVE = "secp256r1";

    private static final IESParameterSpec IES_PARAMETER_SPEC_FOR_AES_CBC =
        new IESParameterSpec(null, null, 128, 128, null);

    public static byte[] encrypt(byte[] plainText, BCECPublicKey publicKey) throws Exception {
        IESCipher cipher = getIESCipher();
        cipher.engineInit(Cipher.ENCRYPT_MODE, publicKey, IES_PARAMETER_SPEC_FOR_AES_CBC, new SecureRandom());
        return cipher.engineDoFinal(plainText, 0, plainText.length);
    }

    public static byte[] decrypt(byte[] cipherText, BCECPrivateKey privateKey) throws Exception {
        IESCipher cipher = getIESCipher();
        cipher.engineInit(Cipher.DECRYPT_MODE, privateKey, IES_PARAMETER_SPEC_FOR_AES_CBC, new SecureRandom());
        return cipher.engineDoFinal(cipherText, 0, cipherText.length);
    }


    public static KeyPair generateKeyPair() throws Exception {
        KeyPairGenerator keyGenerator =
            KeyPairGenerator.getInstance(EC_ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
        keyGenerator.initialize(new ECGenParameterSpec(CURVE));
        KeyPair keyPair = keyGenerator.generateKeyPair();
        return keyPair;
    }

    public static String serializePrivateKey(BCECPrivateKey privateKey) {
        byte[] bytes = BigIntegers.asUnsignedByteArray(privateKey.getD());
        return Hex.toHexString(bytes);
    }

    public static String serializePublicKey(BCECPublicKey publicKey) throws Exception {
        byte[] bytes = publicKey.getQ().getEncoded(false);
        return Hex.toHexString(bytes);
    }

    private static IESCipher getIESCipher() {
        IESCipher cipher = new IESCipher(
            new IESEngine(
                new ECDHBasicAgreement(),
                new KDF2BytesGenerator(new SHA256Digest()),
                new HMac(DigestFactory.createSHA256()),
                new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()), new PKCS7Padding())), 0);
        return cipher;
    }

    // we can test the compatibility using the output data
    public static void main(String[] args) throws Exception {
        String msg="hello_ECIES";
        KeyPair keyPair = generateKeyPair();
        BCECPublicKey publicKey = (BCECPublicKey) keyPair.getPublic();
        BCECPrivateKey privateKey = (BCECPrivateKey) keyPair.getPrivate();
        System.out.println("privateKeyString: "+serializePrivateKey(privateKey));
        System.out.println("publicKeyString: "+serializePublicKey(publicKey));
        byte[] encData = encrypt(msg.getBytes(), publicKey);
        System.out.println("encMessage: "+Hex.toHexString(encData));
        byte[] plainData = decrypt(encData, privateKey);
        System.out.println(new String(plainData));
    }
}

By the code above, we output the privateKeyString and the encMessage. We can decrypt using the Go implementation below.

package main

import (
	"fmt"
	"github.com/hotstar/ecies"
)

func main() {
	privateKeyString := "" 
	encMessage := ""

	privateKey := ecies.DeserializePrivateKey(ecies.HexDecodeWithoutError(privateKeyString))
	cipher := ecies.NewECIES()
	palinMessage, _ := cipher.Decrypt(privateKey, ecies.HexDecodeWithoutError(encMessage))
	fmt.Println(string(palinMessage))
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ClearCurve

func ClearCurve()

ClearCurve clear your own elliptic curve, instead the default curve

func GetCurve

func GetCurve() elliptic.Curve

func GetECPointByteLength

func GetECPointByteLength() int

GetECPointByteLength Get the length of bytes of a point on the curve

func HexDecode

func HexDecode(dataInHex string) ([]byte, error)

func HexDecodeWithoutError

func HexDecodeWithoutError(dataInHex string) []byte

func HexEncode

func HexEncode(data []byte) string

func Hmac

func Hmac(hmacHash crypto.Hash, data []byte, secret []byte, otherDatas ...[]byte) []byte

func HmacSha256

func HmacSha256(data []byte, secret []byte, otherDatas ...[]byte) []byte

func SerializePrivateKey

func SerializePrivateKey(privateKey *PrivateKey) []byte

SerializePrivateKey Serialize the D bytes of the private key

func SerializePublicKey

func SerializePublicKey(publicKey *PublicKey) []byte

SerializePublicKey Serialize the X,Y bytes of the public key

func SerializePublicKeyToCoordinate

func SerializePublicKeyToCoordinate(publicKey *PublicKey) (string, string)

SerializePublicKeyToCoordinate Serialize the X,Y coordinate string

func SetCurve

func SetCurve(c elliptic.Curve)

SetCurve set your own elliptic curve

Types

type AesCbcPkcs7Cipher

type AesCbcPkcs7Cipher struct {
	// contains filtered or unexported fields
}

func NewAesCbcPkcs7Cipher

func NewAesCbcPkcs7Cipher() *AesCbcPkcs7Cipher

func (*AesCbcPkcs7Cipher) Decrypt

func (aesCbcPkcs7Cipher *AesCbcPkcs7Cipher) Decrypt(encMsg []byte, key []byte) ([]byte, error)

func (*AesCbcPkcs7Cipher) Encrypt

func (aesCbcPkcs7Cipher *AesCbcPkcs7Cipher) Encrypt(msg []byte, key []byte) ([]byte, error)

type AesGcmCipher

type AesGcmCipher struct {
	// contains filtered or unexported fields
}

func NewAesGcmCipher

func NewAesGcmCipher() *AesGcmCipher

func (*AesGcmCipher) Decrypt

func (aesGcmCipher *AesGcmCipher) Decrypt(encMsg []byte, key []byte) ([]byte, error)

func (*AesGcmCipher) Encrypt

func (aesGcmCipher *AesGcmCipher) Encrypt(msg []byte, key []byte) ([]byte, error)

type ECIES

type ECIES struct {
	// contains filtered or unexported fields
}

func NewCustomizedECIES

func NewCustomizedECIES(ka KeyAgreement, cipher SymmetricCipher, kdf KeyDerivationFunction, hmacHash crypto.Hash, encKeyByteSize int, macKeyByteSize int) *ECIES

NewCustomizedECIES create an ECIES instance with customized algorithms

func NewECIES

func NewECIES() *ECIES

NewECIES create an ECIES instance with default algorithms

func (*ECIES) Decrypt

func (ecies *ECIES) Decrypt(privateKey *PrivateKey, msg []byte) ([]byte, error)

Decrypt decrypts a passed message with a receiver private key, returns plaintext or decryption error

func (*ECIES) Encrypt

func (ecies *ECIES) Encrypt(pubkey *PublicKey, msg []byte) ([]byte, error)

Encrypt encrypts a passed message with a receiver public key, returns ciphertext or encryption error

type EcsvdpDhKeyAgreement

type EcsvdpDhKeyAgreement struct {
}

func NewEcsvdpDhKeyAgreement

func NewEcsvdpDhKeyAgreement() *EcsvdpDhKeyAgreement

func (*EcsvdpDhKeyAgreement) CalculateAgreement

func (ka *EcsvdpDhKeyAgreement) CalculateAgreement(privateKey *PrivateKey, anotherPublicKey *PublicKey) ([]byte, error)

CalculateAgreement calculate a key following 1363 7.2.1 ECSVDP-DH

type KeyAgreement

type KeyAgreement interface {
	CalculateAgreement(privateKey *PrivateKey, anotherPublicKey *PublicKey) ([]byte, error)
}

KeyAgreement the key agreement that is used in the ECIES

type KeyDerivationFunction

type KeyDerivationFunction interface {
	GenerateKeyBytes(secret []byte, iv []byte, kenBytesLength int) ([]byte, error)
}

KeyDerivationFunction the key derivation function that is used in the ECIES

type KeyDerivationFunction1

type KeyDerivationFunction1 struct {
	// contains filtered or unexported fields
}

func NewKeyDerivationFunction1

func NewKeyDerivationFunction1(inputHash crypto.Hash) *KeyDerivationFunction1

func (KeyDerivationFunction1) GenerateKeyBytes

func (keyDerivationFunction1 KeyDerivationFunction1) GenerateKeyBytes(secret []byte, iv []byte, kenBytesLength int) ([]byte, error)

type KeyDerivationFunction2

type KeyDerivationFunction2 struct {
	// contains filtered or unexported fields
}

func NewKeyDerivationFunction2

func NewKeyDerivationFunction2(inputHash crypto.Hash) *KeyDerivationFunction2

func (KeyDerivationFunction2) GenerateKeyBytes

func (keyDerivationFunction2 KeyDerivationFunction2) GenerateKeyBytes(secret []byte, iv []byte, kenBytesLength int) ([]byte, error)

type PrivateKey

type PrivateKey struct {
	*PublicKey
	D *big.Int
}

func DeserializePrivateKey

func DeserializePrivateKey(dBytes []byte) *PrivateKey

DeserializePrivateKey Deserialize from the D bytes

func GenerateKey

func GenerateKey() (*PrivateKey, error)

GenerateKey generate an ECC key pair

type PublicKey

type PublicKey struct {
	elliptic.Curve
	X *big.Int
	Y *big.Int
}

func DeserializePublicKey

func DeserializePublicKey(pointBytes []byte) (*PublicKey, error)

DeserializePublicKey Deserialize from the X,Y bytes

func DeserializePublicKeyFromCoordinate

func DeserializePublicKeyFromCoordinate(xCoordinate, yCoordinate string) (*PublicKey, error)

DeserializePublicKeyFromCoordinate Deserialize from the X,Y coordinate string

type SymmetricCipher

type SymmetricCipher interface {
	Encrypt(msg []byte, key []byte) ([]byte, error)
	Decrypt(encMsg []byte, key []byte) ([]byte, error)
}

SymmetricCipher the symmetric cipher that is used in the ECIES

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL