runtime: ECDsa.VerifyData Performance Slow on Mac

Hi, I have got performance issue when use ECDsa.VerifyData() on Mac, it’s almost 10 times slower than on windows, which cause my app running very slow on Mac.
The runtime is dotnet core 3.1. Here my test code:

private static X9ECParameters _ecParams  = NistNamedCurves.GetByName("P-256");
private static ECDomainParameters _domainParameters = new ECDomainParameters(_ecParams.Curve, _ecParams.G, _ecParams.N, _ecParams.H, _ecParams.GetSeed());
private static Org.BouncyCastle.Math.EC.ECCurve _curve = _ecParams.Curve;



static void Main(string[] args)
{

	var publicKey =
			"77d28f103c37eb03d62decd4fdbda01dd69fea878325bc1bebc5074a876455eb9d2f4e719ba0a0838df3b07479ed2179358f711fe9d004b693c62922e95772d0"
				.HexToBytes();
	var signature =
			"61516a23f99962b4417c87d26592b52060b19c5b7481a2318c665af540ae721c69611c073c6e34a358343e9ad43b4966ce0f9a8914c5e77f2cb3fe28ae2bf4d0"
				.HexToBytes();
	var message =
			"38900d0000000000bd3bde894f6ddb7e5ae89e7d7c1eef5c271875c5a2bd6b75faba07c8c8423902e8c4d7a4ebf3542d7d8eee65a996bb45211e546bd995ae5b1d099a90464886981b9e479971010000214e0000bf1740f6c1e1f913d2f307b29b8082551ba9a5c5"
				.HexToBytes();


	var v = VerifyData(message, signature, publicKey);
	var v2 = VerifyDataBouncy(message, signature, publicKey);

	Console.WriteLine($"{v},{v2}");

	int count = 1000;

	var sw = new Stopwatch();
	sw.Start();
	for (int i = 0; i < count ; i++)
	{
		VerifyData(message, signature, publicKey);
	}
	sw.Stop();

	Console.WriteLine($"VerifyData : {sw.Elapsed.TotalSeconds} s");
	sw.Restart();
	for (int i = 0; i < count; i++)
	{
		VerifyDataBouncy(message, signature, publicKey);
	}
	sw.Stop();

	Console.WriteLine($"VerifyDataBouncy : {sw.Elapsed.TotalSeconds} s");
	Console.ReadLine();
}



static bool VerifyData(ReadOnlySpan<byte> message, ReadOnlySpan<byte> signature, ReadOnlySpan<byte> pubkey)
{
	using var ecdsa = ECDsa.Create(new ECParameters
	{
		Curve = ECCurve.NamedCurves.nistP256,
		Q = new ECPoint
		{
			X = pubkey[..32].ToArray(),
			Y = pubkey[32..].ToArray()
		}
	});
	return ecdsa.VerifyData(message, signature, HashAlgorithmName.SHA256);
}



static bool VerifyDataBouncy(byte[] message, byte[] signature, byte[] pubkey)
{
	BigInteger x = new BigInteger(1, pubkey.Take(32).ToArray());
	BigInteger y = new BigInteger(1, pubkey.Skip(32).ToArray());

	var derSignature = new DerSequence(
			// first 32 bytes is "r" number
			new DerInteger(new BigInteger(1, signature.Take(32).ToArray())),
			// last 32 bytes is "s" number
			new DerInteger(new BigInteger(1, signature.Skip(32).ToArray())))
		.GetDerEncoded();
	Org.BouncyCastle.Math.EC.ECPoint q = _curve.CreatePoint(x, y);

	ECPublicKeyParameters pubkeyParam = new ECPublicKeyParameters(q, _domainParameters);

	var verifier = SignerUtilities.GetSigner("SHA-256withECDSA");
	verifier.Init(false, pubkeyParam);
	verifier.BlockUpdate(message, 0, message.Length);
	return verifier.VerifySignature(derSignature);
}

on Win10: win10

on Mac: Mac

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 26 (24 by maintainers)

Most upvoted comments

That would be easy to solve with carefully injecting kSecUseDataProtectionKeychain attribute during the key generation.

I can take a look at this since you are likely busy with iOS things. Unfortunately this attribute is new in macOS 10.15, but we can continue to use the CSSM path for 10.14.

believe this may be worth revisiting with benchmarks on main branch

I can do that much at least, I still have the benchmarks. Thanks for getting all that code in to a mergeable state.

Cool, I’m up for the experiment, deferring to the existing function when we need to (DSA, RSA+MD5)

@bartonjs both. Can use kSecKeyAlgorithmECDSASignatureDigestX962SHA256 for digests and kSecKeyAlgorithmECDSASignatureMessageX962SHA256 for messages.

There are similar options for RSA, (sign and encrypt).

@Ashuaidehao

Interesting, it appears that CryptoKit has a bit of a “start up” time. If I run SecurityTransforms and CryptoKit in a loop 1000 times, the performance difference is very noticeable.

image

CryptoKit is quite fast.


I don’t know how feasible it is to use CryptoKit for this off the top of my head. There are a number of barriers with using CryptoKit.

The biggest one is the lack of a C-like API that .NET can call. .NET can work with SecurityTransforms because it does offer a C API. Since the native parts of .NET are written in C, thus SecurityTransforms is easy enough to use.

CrytpoKit does not have a C API - it offers a Swift-only API/ABI that .NET doesn’t understand. In order to use it, there would need to be a Swift -> Objective-C -> C indirection. This was explored while researching AES-GCM but it’s only been mildly explored so far.

The other issue is that CryptoKit is 10.15+ on macOS. I can’t say with any certainty but I suspect .NET 5 will continue to support at least 10.14 as well.


The CryptoKit times are encouraging though that CoreCrypto (Apple’s truly low-level implementation of cryptographic functions that powers CommonCrypto and CryptoKit) is fast, and that something in SecurityTransforms is holding it back. I will continue to investigate other possibilities of SecurityTransforms - perhaps there is a way to get it closer to CryptoKit’s performance.