Skip to main content
Code Review

Return to Question

Added gist of updated version
Source Link

EDIT Created an updated version as a gist , based on feedback.

EDIT Created an updated version as a gist , based on feedback.

Source Link

Validate implementation of ECIES

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">M, the message to be encrypted</param>
 /// <returns>(R̄, EM, D̄), 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, EK
 var ek = ephemeral.DeriveKeyFromHash(recipientPubKey, HashAlgorithmName.SHA256, null, new byte[] {0, 0, 0, 1});
 // Use ANSI-X9.63-KDF to derive the HMAC key, MK
 var mk = ephemeral.DeriveKeyFromHash(recipientPubKey, HashAlgorithmName.SHA256, null, new byte[] {0, 0, 0, 2});
 // The ciphertext, EM
 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 D, 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">EM, the ciphertext to be decrypted</param>
 /// <param name="d">D, HMAC of the ciphertext</param>
 /// <returns>M, 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();
 
 // M, the plaintext
 byte[] m;
 using (var senderEcdh = ECDiffieHellman.Create(r))
 {
 // Use ANSI-X9.63-KDF to derive the encryption key, EK
 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, MK
 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 D
 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?

lang-cs

AltStyle によって変換されたページ (->オリジナル) /