I've created a basic implementation of ECIES (Elliptic Curve Integrated Encryption Scheme) based on http://www.secg.org/sec1-v2.pdf section 5.1.
/// <summary>
/// Simple implementation of ECIES (Elliptic Curve Integrated Encryption Scheme) based on http://www.secg.org/sec1-v2.pdf, section 5.1
/// The KDF, cipher and HMAC are fixed as ANSI-X9.63-KDF, AES-256-CBC and HMAC–SHA-256–256 respectively
/// Note this implementation does not use the optional SharedInfo1 & SharedInfo2 parameters
/// </summary>
public static class Ecies
{
/// <summary>
/// Based on http://www.secg.org/sec1-v2.pdf, section 5.1.3
/// Encrypt data using ECIES (Elliptic Curve Integrated Encryption Scheme)
/// </summary>
/// <param name="recipientPubKey">Public key of the recipient</param>
/// <param name="m">𝑀, the message to be encrypted</param>
/// <returns>(R̄, 𝐸𝑀, 𝐷̄), the elliptic curve parameters, encrypted message and HMAC</returns>
public static (byte[] rBar, byte[] em, byte[] d) Encrypt(ECDiffieHellmanPublicKey recipientPubKey, byte[] m)
{
var curve = recipientPubKey.ExportParameters().Curve;
// Generate an ephemeral keypair on the correct curve
using (var ephemeral = ECDiffieHellman.Create(curve))
{
// R̄ (rBar) contains the parameters to be used for encryption/decryption operations
var ephemPublicParams = ephemeral.ExportParameters(false);
var pointLen = ephemPublicParams.Q.X.Length;
byte[] rBar = new byte[pointLen * 2 + 1];
rBar[0] = 0x04;
Buffer.BlockCopy(ephemPublicParams.Q.X, 0, rBar, 1, pointLen);
Buffer.BlockCopy(ephemPublicParams.Q.Y, 0, rBar, 1 + pointLen, pointLen);
// Use ANSI-X9.63-KDF to derive the encryption key, 𝐸𝐾
var ek = ephemeral.DeriveKeyFromHash(recipientPubKey, HashAlgorithmName.SHA256, null, new byte[] {0, 0, 0, 1});
// Use ANSI-X9.63-KDF to derive the HMAC key, 𝑀𝐾
var mk = ephemeral.DeriveKeyFromHash(recipientPubKey, HashAlgorithmName.SHA256, null, new byte[] {0, 0, 0, 2});
// The ciphertext, 𝐸𝑀
byte[] em;
// Use AES-256-CBC to encrypt the message
// Note we use an empty IV - this is OK, as the key is never reused
using (var aes = Aes.Create())
using (var encryptor = aes.CreateEncryptor(ek, new byte[16]))
{
if (!encryptor.CanTransformMultipleBlocks)
throw new InvalidOperationException();
em = encryptor.TransformFinalBlock(m, 0, m.Length);
}
// Use HMAC–SHA-256–256 to compute 𝐷, HMAC of the ciphertext
byte[] d;
using (HMAC hmac = new HMACSHA256(mk))
{
d = hmac.ComputeHash(em);
}
return (rBar, em, d);
}
}
/// <summary>
/// Based on http://www.secg.org/sec1-v2.pdf, section 5.1.4
/// Encrypt data using ECIES (Elliptic Curve Integrated Encryption Scheme)
/// </summary>
/// <param name="recipient">Recipient of the message</param>
/// <param name="rBar">R̄, elliptic curve parameters to be used for decryption</param>
/// <param name="em">𝐸𝑀, the ciphertext to be decrypted</param>
/// <param name="d">𝐷, HMAC of the ciphertext</param>
/// <returns>𝑀, the decrypted message</returns>
public static byte[] Decrypt(ECDiffieHellman recipient, byte[] rBar, byte[] em, byte[] d)
{
// Convert R̄ to an elliptic curve point R=(xR, yR)
var r = new ECParameters
{
Curve = recipient.ExportParameters(false).Curve,
Q =
{
X = rBar.Skip(1).Take(32).ToArray(),
Y = rBar.Skip(33).Take(32).ToArray(),
}
};
r.Validate();
// 𝑀, the plaintext
byte[] m;
using (var senderEcdh = ECDiffieHellman.Create(r))
{
// Use ANSI-X9.63-KDF to derive the encryption key, 𝐸𝐾
var ek = recipient.DeriveKeyFromHash(senderEcdh.PublicKey, HashAlgorithmName.SHA256, null, new byte[] {0, 0, 0, 1});
// Use ANSI-X9.63-KDF to derive the HMAC key, 𝑀𝐾
var mk = recipient.DeriveKeyFromHash(senderEcdh.PublicKey, HashAlgorithmName.SHA256, null, new byte[] {0, 0, 0, 2});
// Use HMAC–SHA-256–256 to verify that the HMAC matches 𝐷
using (HMAC verify = new HMACSHA256(mk))
{
if (!verify.ComputeHash(em).SequenceEqual(d))
throw new CryptographicException("Invalid HMAC");
}
// Use AES-256-CBC to decrypt the message
using (var aes = Aes.Create())
using (var encryptor = aes.CreateDecryptor(ek, new byte[16]))
{
if (!encryptor.CanTransformMultipleBlocks)
throw new InvalidOperationException();
m = encryptor.TransformFinalBlock(em, 0, em.Length);
}
}
return m;
}
}
I tested it using:
var alice = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256);
var bob = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256);
var encrypted = Ecies.Encrypt(bob.PublicKey, Encoding.UTF8.GetBytes(message));
var decrypted = Ecies.Decrypt(bob, encrypted.rBar, encrypted.em, encrypted.d);
var result = Encoding.UTF8.GetString(decrypted);
I'm able to encrypt/decrypt messages as expected. I also tried using ECC certificates, getting the ECDH object as so, and it worked as expected:
using (var ecdsa = cert.GetECDsaPrivateKey())
{
return ECDiffieHellman.Create(ecdsa.ExportParameters(true));
}
So, seemingly all good! But crypto implementations are fraught with danger, and the spec I linked to was hard reading, so more eyes on it would be very welcome - does the implementation look correct?
Also, how might the API change if different KDF functions, cipher functions and HMAC functions were to be supported?
EDIT Created an updated version as a gist, based on feedback.