# Cryptographic Signatures with a TPM

In my previous [post](https://blog.ladeak.net/posts/crypto-signatures), I compared different signature algorithms listed primarily in [RFC 9421](https://www.rfc-editor.org/rfc/rfc9421.html), extended with `MLDsa44`. The focus of that comparison was performance rather than cryptographic strength. I ran the tests on multiple hardware configurations and operating systems, but performed the signing in software.

In this post, I use Windows and a TPM to sign data. The TPM protects the private key and performs the private key operation, while public key operations may be handled by the provider or operating system The test setup matches the previous tests as closely as possible.

**Message sizes:**

- A small message, roughly the size of a single JSON property value.
- A medium message, where the plaintext is roughly the size of a few properties (93 bytes).
- A 1 KB message.

**Execution:**

For the comparison, I run a .NET 11 application using [BenchmarkDotNet](https://benchmarkdotnet.org/) on Windows. The benchmark uses Windows specific .NET APIs to access the TPM. Here, I do not measure single-shot signing, because that would require creating a key in the TPM for each test execution. Instead, the same key is reused to sign multiple messages.

## Code

The key can be created in the TPM for signing with ECDSA using the P-256 curve:

```csharp
public CngKey? CreateTpmKey()
{
    var creationParams = new CngKeyCreationParameters
    {
        Provider = CngProvider.MicrosoftPlatformCryptoProvider,
        KeyUsage = CngKeyUsages.Signing,
        ExportPolicy = CngExportPolicies.None,
        KeyCreationOptions = CngKeyCreationOptions.OverwriteExistingKey
    };

    return CngKey.Create(
        algorithm: new CngAlgorithm("ECDSA_P256"),
        keyName: "TestTpmEcdsaKey",
        creationParams);
}
```

With this key, we can sign data using the `Cng` APIs in .NET:

```csharp
    using var serverEcdsa = new ECDsaCng(key);
    Span<byte> signature = stackalloc byte[serverEcdsa.GetMaxSignatureSize(DSASignatureFormat.IeeeP1363FixedFieldConcatenation)];
    int length = serverEcdsa.SignData(message, signature, HashAlgorithmName.SHA256);
```

Verifying the signature is very similar: use the `VerifyData` method of `ECDsaCng`.

## Results

For comparison, I also include the performance results for software based signatures. The key observations for the TPM backed approach are:

- Signature verification is slightly faster with the TPM backed key in these measurements.
- Message size has no significant impact on the mean execution time. This is expected because ECDSA hashes the message before signing or verification, and these messages are all small compared with the fixed overhead of the operation.
- The ECDSA curve has a large impact on the overall signing and verification time.
- Signing is significantly slower with the TPM backed key than with the software implementation.

The most striking result is the cost of signing. P-256 signing takes about 33 ms with the TPM-backed key, compared with about 0.11 ms in software: roughly 300 times slower in this benchmark. P-384 shows a similar pattern, at about 40 ms versus 0.20 ms, or roughly 200 times slower. Verification is much less expensive, the TPM-backed results are slightly faster than the software results for both curves.

These results should be interpreted as measurements of `ECDsaCng` using the Microsoft Platform Crypto Provider, not as proof that every part of verification executes inside the TPM.

### TPM ECDsa P256 - Windows

```
| Method       | MessageInput         | Mean         | Error      | StdDev     |
|------------- |--------------------- |-------------:|-----------:|-----------:|
| ECDSA_Sign   | High(...)ory. [1024] | 32,980.28 us | 649.098 us | 991.242 us |
| ECDSA_Verify | High(...)ory. [1024] |     84.44 us |   0.651 us |   0.577 us |
| ECDSA_Sign   | some secret value    | 33,021.76 us | 641.456 us | 940.237 us |
| ECDSA_Verify | some secret value    |     83.92 us |   0.689 us |   0.610 us |
| ECDSA_Sign   | some (...)value [93] | 33,016.29 us | 656.958 us | 983.305 us |
| ECDSA_Verify | some (...)value [93] |     84.49 us |   0.725 us |   0.678 us |
```

### Non TPM ECDsa P256 - Windows

```
| Method       | MessageInput         | Mean     | Error   | StdDev  |
|------------- |--------------------- |---------:|--------:|--------:|
| ECDSA_Sign   | High(...)ory. [1024] | 113.2 us | 0.63 us | 0.59 us |
| ECDSA_Verify | High(...)ory. [1024] | 105.7 us | 0.16 us | 0.15 us |
| ECDSA_Sign   | some secret value    | 111.2 us | 0.82 us | 0.77 us |
| ECDSA_Verify | some secret value    | 104.8 us | 0.21 us | 0.17 us |
| ECDSA_Sign   | some (...)value [93] | 111.4 us | 0.76 us | 0.71 us |
| ECDSA_Verify | some (...)value [93] | 104.8 us | 0.12 us | 0.10 us |
```

### TPM ECDsa P384 - Windows

```
| Method       | MessageInput         | Mean        | Error     | StdDev      |
|------------- |--------------------- |------------:|----------:|------------:|
| ECDSA_Sign   | High(...)ory. [1024] | 39,814.8 us | 807.94 us | 2,369.54 us |
| ECDSA_Verify | High(...)ory. [1024] |    181.3 us |   1.65 us |     1.46 us |
| ECDSA_Sign   | some secret value    | 40,822.9 us | 806.02 us | 1,230.88 us |
| ECDSA_Verify | some secret value    |    183.0 us |   1.28 us |     1.14 us |
| ECDSA_Sign   | some (...)value [93] | 41,230.8 us | 817.97 us | 2,280.17 us |
| ECDSA_Verify | some (...)value [93] |    181.7 us |   1.45 us |     1.29 us |
```

### Non TPM ECDsa P384 - Windows

```
| Method       | MessageInput         | Mean     | Error   | StdDev  |
|------------- |--------------------- |---------:|--------:|--------:|
| ECDSA_Sign   | High(...)ory. [1024] | 206.3 us | 0.58 us | 0.54 us |
| ECDSA_Verify | High(...)ory. [1024] | 213.6 us | 0.19 us | 0.17 us |
| ECDSA_Sign   | some secret value    | 202.4 us | 0.62 us | 0.58 us |
| ECDSA_Verify | some secret value    | 213.9 us | 0.19 us | 0.18 us |
| ECDSA_Sign   | some (...)value [93] | 201.9 us | 0.55 us | 0.52 us |
| ECDSA_Verify | some (...)value [93] | 212.4 us | 0.13 us | 0.12 us |
```
