Description
Background and motivation
When the need to use the shared secret (agreement) arises, we use our wrapper of the CNG API on Windows (10). We use reflection on non-Windows platforms because the method DeriveSecretAgreement in EcDiffieHellmanOpenSsl class is declared private.
private byte[]? DeriveSecretAgreement(ECDiffieHellmanPublicKey otherPartyPublicKey, IncrementalHash? hasher)
As always, the code that uses reflection depends on the private implementation of the class, which we do not control, so the code is fragile and error-prone. The code without obvious optimizations might look like the following code. We would rather prefer to use public and supported API than this "hack".
private static CoreECDHAsymmetricAlgorithm TryCreateFrom(object asymmetric, bool failOnError, bool ownsAlgorithm)
{
#if STD21
var inner = asymmetric as ECDiffieHellman;
if (inner == null)
#else
var inner = asymmetric as AsymmetricAlgorithm;
if (inner == null || !_ecdhType.IsInstanceOfType(inner))
#endif
{
if (failOnError) throw new CryptographicException("ECDiffieHellman algorithm expected.");
return null;
}
if (Environment.Version.Major >= 7)
{
FieldInfo wrappedField = inner.GetType().GetField("_wrapped", BindingFlags.Instance | BindingFlags.NonPublic);
if (wrappedField != null)
{
#if STD21
var wrapped = wrappedField.GetValue(inner) as ECDiffieHellman;
if (wrapped != null)
#else
var wrapped = wrappedField.GetValue(inner) as AsymmetricAlgorithm;
if (_ecdhType.IsInstanceOfType(wrapped))
#endif
{
inner = wrapped;
}
}
}
#if STD21
MethodInfo deriveSecretAgreementMethod = inner.GetType().GetMethod("DeriveSecretAgreement", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(ECDiffieHellmanPublicKey), typeof(IncrementalHash) }, null);
if (deriveSecretAgreementMethod != null && deriveSecretAgreementMethod.ReturnType != typeof(byte[]))
{
deriveSecretAgreementMethod = null;
}
#else
MethodInfo deriveSecretAgreementMethod = inner.GetType().GetMethod("DeriveSecretAgreement", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { _ecdhPublicKeyType, typeof(IncrementalHash) }, null);
if (deriveSecretAgreementMethod == null || deriveSecretAgreementMethod.ReturnType != typeof(byte[]))
{
if (failOnError) throw new CryptographicException("Unsupported instance of ECDiffieHellman algorithm.");
return null;
}
#endif
//CoreECDHAsymmetricAlgorithm is our class that derives shared secret.
return new CoreECDHAsymmetricAlgorithm(inner, deriveSecretAgreementMethod, ownsAlgorithm);
}
API Proposal
namespace System.Security.Cryptography
{
public partial class ECDiffieHellman : ECAlgorithm
{
public virtual byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPartyPublicKey);
}
}
API Usage
//Create ECDH instance for Bob
using var bobEcdh = ECDiffieHellman.Create();
//Create ECDH instance for Alice
using var aliceEcdh = ECDiffieHellman.Create();
//Generate keys
bobEcdh.GenerateKey(eccCurve);
aliceEcdh.GenerateKey(eccCurve);
//Obtain shared secret
var sharedSecret = bobEcdh.DeriveSecretAgreement(aliceEcdh.PublicKey);
Alternative Designs
Add public method DeriveSecretAgreement only to specific ECDH classes (EcDiffieHellmanOpenSsl , ECDiffieHellmanAndroid.cs?, iOS?).
Better method name?
namespace System.Security.Cryptography;
public abstract partial class ECDiffieHellman : ECAlgorithm
{
public virtual byte[] DeriveSharedSecret(ECDiffieHellmanPublicKey otherPartyPublicKey)
{
throw DerivedClassMustOverride();
}
}
Risks
Shared raw secret (Truncate method in CNG BCryptDeriveKey) is available only on Windows 10?