Skip to content

Conversation

edwardneal
Copy link
Contributor

Description

I'm trying to improve query performance in AE scenarios, and part of this means a review of the column encryption providers. These were written prior to a number of cryptography improvements in .NET 5.0; as a result of these improvements, we can save a little memory.

Besides the performance improvements, each provider reproduces the same serialization logic. These primitives centralise that logic; they use the column master key parameters to perform encryption and decryption of a column encryption key with an RSA instance. The only thing that the three SqlColumnEncryptionKeyStoreProvider derivatives need to do is retrieve an RSA instance using the master key path.

I considered moving this logic into a protected method on SqlColumnEncryptionKeyStoreProvider, but a third-party provider isn't guaranteed to perform encryption/decryption of the CEK using an RSA instance (or indeed, on the same server - SqlColumnEncryptionAzureKeyVaultProvider passes that duty to Azure Key Vault.)

A subsequent PR will refactor the column encryption providers to use these primitives, and the benchmark results for this are below. To summarise: no significant variations in execution time, and around a 15% reduction in AlwaysEncrypted memory usage.

Benchmarks

SqlColumnEncryptionCertificateStoreProvider

Method Branch Mean Error StdDev Ratio Allocated Alloc Ratio
DecryptColumnEncryptionKey main 13.79 ms 0.256 ms 0.227 ms 1.00 18.17 KB 1.00
DecryptColumnEncryptionKey PR 13.16 ms 0.180 ms 0.150 ms 0.95 15.43 KB 0.85
EncryptColumnEncryptionKey main 12.59 ms 0.224 ms 0.375 ms 1.00 18.94 KB 1.00
EncryptColumnEncryptionKey PR 12.58 ms 0.246 ms 0.450 ms 1.00 16.01 KB 0.85

SqlColumnEncryptionCngProvider

Method Branch Mean Error StdDev Ratio Allocated Alloc Ratio
DecryptColumnEncryptionKey main 3.303 ms 0.0639 ms 0.1661 ms 1.00 2.51 KB 1.00
DecryptColumnEncryptionKey PR 3.285 ms 0.0648 ms 0.0842 ms 0.99 901 B 0.36
EncryptColumnEncryptionKey main 3.214 ms 0.0395 ms 0.0330 ms 1.00 3.11 KB 1.00
EncryptColumnEncryptionKey PR 3.332 ms 0.0645 ms 0.0603 ms 1.03 1.61 KB 0.52

SqlColumnEncryptionCspProvider

Method Branch Mean Error StdDev Ratio Allocated Alloc Ratio
DecryptColumnEncryptionKey main 2.775 ms 0.0411 ms 0.0520 ms 1.00 3.59 KB 1.00
DecryptColumnEncryptionKey PR 2.737 ms 0.0256 ms 0.0200 ms 0.99 3.01 KB 0.84
EncryptColumnEncryptionKey main 2.815 ms 0.0556 ms 0.0571 ms 1.00 4.01 KB 1.00
EncryptColumnEncryptionKey PR 2.847 ms 0.0464 ms 0.0434 ms 1.01 3.23 KB 0.81

Testing

These primitives aren't used anywhere yet, so there's no test coverage. The existing providers have this coverage though, so the new code will gain coverage from the follow-up PR. I have a local branch where the column encryption providers use this code, and almost all tests pass. The two failing tests are capitalisation errors in exception messages produced by the CSP provider, which will be fixed at the same time.

@edwardneal edwardneal requested a review from a team as a code owner August 9, 2025 15:52
@edwardneal edwardneal changed the title Performance | Introduce low-allocation AlwaysEncrypted primitives Performance | Introduce lower-allocation AlwaysEncrypted primitives Aug 9, 2025
Add a comment and a debug assertion to make it clear that the invariant lowercase value of masterKeyPath should be the same length as the original string.
@paulmedynski paulmedynski self-assigned this Aug 27, 2025
@paulmedynski
Copy link
Contributor

/azp run

Copy link

Azure Pipelines successfully started running 2 pipeline(s).

Copy link
Contributor

@paulmedynski paulmedynski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consolidation looks good overall, with some questions/suggestions.

@apoorvdeshmukh apoorvdeshmukh self-assigned this Aug 27, 2025
Add/updated XML documentation to the public APIs.
Add intermediary variable containing the signature size for clarity.
Use AlgorithmOffset rather than hardcoded offset in EncryptedColumnEncryptionKeyParameters.
Specifically define using block in ColumnMasterKeyMetadata.
class -> struct
@paulmedynski
Copy link
Contributor

/azp run

Copy link

Azure Pipelines successfully started running 2 pipeline(s).

Copy link
Contributor

@paulmedynski paulmedynski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the previous updates. Just a couple more and one question about disposal.

@edwardneal
Copy link
Contributor Author

Thanks. Responded, and merged following the merge of #3014.

Copy link
Contributor

@paulmedynski paulmedynski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updates! RSA.SignHash() will throw CryptographicException if "An error occurred creating the signature.", so need to document that one as well.

@edwardneal
Copy link
Contributor Author

I'd missed that - added to ColumnMasterKeyMetadata.Sign and EncryptedColumnEncryptionKeyParameters.Encrypt.

@paulmedynski
Copy link
Contributor

/azp run

Copy link

Azure Pipelines successfully started running 2 pipeline(s).

Copy link
Contributor

@benrr101 benrr101 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, looks quite good - one BIG question regarding padding schemes, but if you can answer that one, I'll take it right away

@paulmedynski
Copy link
Contributor

@edwardneal - A merge from main will increase the test timeout and should help with the CI failures. We also increased the Azure SQL resources to support more parallel PR CI runs to help avoid timeouts.

@edwardneal
Copy link
Contributor Author

Thanks @paulmedynski - I've just merged. Could you re-run CI please?

@paulmedynski
Copy link
Contributor

/azp run

Copy link

Azure Pipelines successfully started running 2 pipeline(s).

@edwardneal
Copy link
Contributor Author

Thanks; it looks like there are some lingering CI issues, this time with the NuGet feed.

@paulmedynski
Copy link
Contributor

/azp run

Copy link

Azure Pipelines successfully started running 2 pipeline(s).

Copy link

codecov bot commented Sep 10, 2025

Codecov Report

❌ Patch coverage is 6.66667% with 98 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.82%. Comparing base (cd3dbd1) to head (dc50d06).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
...ncrypted/EncryptedColumnEncryptionKeyParameters.cs 0.00% 67 Missing ⚠️
...lClient/AlwaysEncrypted/ColumnMasterKeyMetadata.cs 0.00% 28 Missing ⚠️
....SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs 50.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3554      +/-   ##
==========================================
- Coverage   65.48%   63.82%   -1.67%     
==========================================
  Files         275      270       -5     
  Lines       61518    61411     -107     
==========================================
- Hits        40288    39196    -1092     
- Misses      21230    22215     +985     
Flag Coverage Δ
addons ?
netcore 67.65% <9.09%> (+<0.01%) ⬆️
netfx 62.68% <8.13%> (-6.54%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@paulmedynski paulmedynski merged commit 3ddd756 into dotnet:main Sep 10, 2025
236 checks passed
@edwardneal edwardneal deleted the perf/alwaysencrypted-primitives branch September 10, 2025 20:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants