How Do You Encrypt With Elliptic Curves?

Can you encrypt with elliptic curve methods? Ans: Not quite. Unlike RSA, you can’t just encrypt and decrypt using elliptic curves, and…

How Do You Encrypt With Elliptic Curves?

Can you encrypt with elliptic curve methods? Ans: Not quite. Unlike RSA, you can’t just encrypt and decrypt using elliptic curves, and actually use an offline Diffie-Hellman method with symmetric key encryption.

With ECC (Elliptic Curve Cryptography), we have an opportunity to use both the power of public-key encryption with the speed and security of symmetric key encryption. And, so we slowly move to the best practice for encryption, where there’s an increasing consensus around:

  • Public key encryption key exchange: ECDH (P256), ECDH (P384), ECDH (P521), X25519 and X448.
  • Public key encryption: RSA and ECIES.
  • Hashing method for key derivation (HKDF): SHA256, SHA384 and SHA512.
  • Symmetric key: 128-bit AES GCM and 256-bit AES GCM.

All of the above methods are compatible with most systems. For this, Bob and Alice could pick an elliptic curve to define their key pair and then use a given hashing method to derive an encryption key. This is normally achieved with HKDF (HMAC Key Derivation Function). For the actual encryption, we can use symmetric-key encryption, as this is the most efficient and much faster than public key encryption. Overall, with this, there is a general move towards using AEAD (Authenticated Encryption with Additional Data). A typical mode for this is GCM.

So let’s build a hybrid encryption method with C#.

The basics

Now, let’s say that Bob will send an encrypted message to Alice. Alice will then generate a key pair (a public key and a private key). She then sends her public key to Bob, and he then uses this to derive a symmetric key for the encryption (S). He then encrypts the message using K and with AES GCM. Bob receives the cipher © and a value of R. From R, she can then derive S from her private key. With this key, she can decrypt the cipher text to derive the plaintext message.

To achieve this in RSA, Alice would create a random symmetric key (KA), and then encrypt it with the public key of Bob, and then encrypt a file with KA. She would then send the encrypted key (Epk(KA)) to Bob, and the encrypted file, and then he would decrypt the encrypted key to reveal KA, and can then decrypt the file:

But, elliptic curve methods do not naturally encrypt data, so let’s see how this is done with these methods.

Theory

In this method, Alice generates a random private key (dA) and then takes a point on an elliptic curve (G) and determines her public key (QA):

QA=dA×G

G and QA are thus points on an elliptic curve. Alice then sends QA to Bob. Next, Bob will generate:

R=r×G

S=r×QA

and where r is a random number (nonce) generated by Bob. The symmetric key (S) is then used to encrypt a message.

Alice will then receive the encrypted message — along with R. She is then able to determine the same encryption key with:

S=dA×R

which is:

S=dA×(r×G)

S=r×(dA×G)

S=r×QA

and which is the same as the key that Bob generated. Once she has S, she now has the symmetric key that has encrypted the file, and can then decrypt it. The method is illustrated here:

Coding

First we create a folder named “bc_ecies”, and then go into that folder.We can create a Dotnet console project for .NET 8.0 with:

dotnet new console --framework net8.0

Next some code [here]:

namespace ECIES
{
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto.Generators;
using System.Text;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.X509;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto.EC;
using Org.BouncyCastle.Crypto.Agreement;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Macs;
class Program
{
static void Main(string[] args)
{
string str="Hello";
var curvename="secp256k1";

if (args.Length >0) str=args[0];
if (args.Length >1) curvename=args[1];

try {

ECKeyPairGenerator gen = new ECKeyPairGenerator();
SecureRandom secureRandom = new SecureRandom();
X9ECParameters ecps = CustomNamedCurves.GetByName(curvename);
ECDomainParameters ecDomainParameters = new ECDomainParameters(ecps.Curve, ecps.G, ecps.N, ecps.H, ecps.GetSeed());
ECKeyGenerationParameters ecKeyGenerationParameters = new ECKeyGenerationParameters(ecDomainParameters, secureRandom);
gen.Init(ecKeyGenerationParameters);
var Bob= gen.GenerateKeyPair();
var Alice= gen.GenerateKeyPair();
var d = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
var e = new byte[] { 8, 7, 6, 5, 4, 3, 2, 1 };
var p = new IesWithCipherParameters(d, e, 64, 128);
// Encrypt
var cryptEng = new IesEngine(new ECDHBasicAgreement(),new Kdf2BytesGenerator(new Sha256Digest()), new HMac(new Sha256Digest()));
cryptEng.Init(true, Bob.Private, Alice.Public, p);
byte[] data = Encoding.ASCII.GetBytes(str);
byte[] enc = cryptEng.ProcessBlock(data,0, data.Length);
// Decrypt
var decryptEng = new IesEngine(new ECDHBasicAgreement(),new Kdf2BytesGenerator(new Sha256Digest()), new HMac(new Sha256Digest()));

decryptEng.Init(true, Bob.Private, Alice.Public, p);
byte[] plain = decryptEng.ProcessBlock(enc,0,data.Length);
var p1=new byte[data.Length];
Buffer.BlockCopy(plain, 0, p1, 0, data.Length);
Console.WriteLine("Plain: {0} ",str);
Console.WriteLine("Cipher: {0} [{1}]",Convert.ToHexString(enc),Convert.ToBase64String(enc));
Console.WriteLine("Decrypted: {0}" ,System.Text.Encoding.UTF8.GetString(p1));
Console.WriteLine("\n== Curve: {0} ",curvename);
Console.WriteLine("\n==Alice keys == ");
var privateKey = (ECPrivateKeyParameters) Alice.Private;
var publicKey = (ECPublicKeyParameters) Alice.Public;

Console.WriteLine("\n == Private key === ");
Console.WriteLine(" == D ==={0} ",privateKey.D.ToString());
Console.WriteLine(" == Public key === ");
Console.WriteLine(" == Q_x ==={0} ",publicKey.Q.XCoord);
Console.WriteLine(" == Q_t ==={0} ",publicKey.Q.YCoord);
Console.WriteLine("\n==Bob keys == ");
privateKey = (ECPrivateKeyParameters) Bob.Private;
publicKey = (ECPublicKeyParameters) Bob.Public;
Console.WriteLine("\n == Private key === ");
Console.WriteLine(" == D ==={0} ",privateKey.D.ToString());
Console.WriteLine(" == Public key === ");
Console.WriteLine(" == Q_x ==={0} ",publicKey.Q.XCoord);
Console.WriteLine(" == Q_t ==={0} ",publicKey.Q.YCoord);

Console.WriteLine("\n\nCurve details: G={0}, N={1}, H={2}", ecps.G, ecps.N, ecps.H);
Console.WriteLine("A={0}\nB={1}\nField size={2}",ecps.Curve.A,ecps.Curve.B,ecps.Curve.FieldSize);

} catch (Exception e) {
Console.WriteLine("Error: {0}",e.Message);
}
}
}
}

A sample run for the NIST P-256 curve [here]:

Plain: Hello 
Cipher: 043EEBA057E58520398686865689A5AAA16DF19A10B5A2F8D2FB5C907664076B26C1B84C84 [BD7roFflhSA5hoaGVomlqqFt8ZoQtaL40vtckHZkB2smwbhMhA==]
Decrypted: Hello
== Curve: P-256
==Alice keys ==
== Private key ===
== D ===20095745737963775254115792097819341569898246674422301374031596614518891314351
== Public key ===
== Q_x ===b3354e3c202077fb7e7d9aa75c0f7e47581fecb64620337c67f05c300c3fbb46
== Q_t ===a03a646e1eda0d777acdc09041b18080d2259ba1e80b560412d8a4b52a00f2d2
==Bob keys ==
== Private key ===
== D ===77334438983487146498203856769750122534227946737471030687758663600349319254712
== Public key ===
== Q_x ===81a3d0b70c66f5fc4f16dabd4a9e3076b04a354c14ae9af1fbc6b3137206d48b
== Q_t ===b5bbbb78c3ee099fb50941ba471ca2d14004e2e496bc5440bec4ff6d504091c1

Curve details: G=(6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296,4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5,1), N=115792089210356248762697446949407573529996955224135760342422259061068512044369, H=1
A=ffffffff00000001000000000000000000000000fffffffffffffffffffffffc
B=5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b
Field size=256

and for secp256k1 [here]:

Plain: Hello 
Cipher: CCE4EB9006114D4A0D14D5511A445A5127D4E367ACB32EC418F19A3C5F91CC6BA7BF824A91 [zOTrkAYRTUoNFNVRGkRaUSfU42essy7EGPGaPF+RzGunv4JKkQ==]
Decrypted: Hello
== Curve: secp256k1
==Alice keys ==
== Private key ===
== D ===21780821407748287735342541217844678722456676556478844752016708485856950985750
== Public key ===
== Q_x ===503d543f2bbecd23796d739ea8c93420778d1eda09031ca36576b3e4b4966087
== Q_t ===df6f232e75960d8b61c5500ab26b2b731565c3e1b8ef7dea8241831a55b746c5
==Bob keys ==
== Private key ===
== D ===22532829012982123891324214196055684259095217236742723653314344066250408526487
== Public key ===
== Q_x ===705089c082409f926bb6d5ae8c318c328b9f6b8e5ee383223410e2e7b54981b0
== Q_t ===96a8cf11ac102040df5a099e9f7b0c25d0027cb9ba3c2141d13d3ae27b96704f

Curve details: G=(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8,1), N=115792089237316195423570985008687907852837564279074904382605163141518161494337, H=1
A=0
B=7
Field size=256

Conclusion

While RSA is still the most common form of public key encryption, ECIES does provide the power of public key and the speed of symmetric key. But, of course, RSA can be used with symmetric key encryption, and where we encrypt the symmetric key to be used with the public key. And, so, RSA is typically used for encrypt our encryption keys.