Skip to content

Conversation

@ChrisMcKee
Copy link
Collaborator

Changes

Spans buffers and spans

Adds new methods that allow passing in a buffer

CreatePasswordHash(ReadOnlySpan<char> inputKey, ReadOnlySpan<char> salt,
        Span<char> outputBuffer, out int outputBufferWritten,
        HashType hashType = HashType.None,
        EnhancedHashDelegate enhancedHashKeyGen = null)

Adds in buffer zeroing when no longer needed to reduce time in memory.

Changed targeting and csproj defined constants to ensure net8 still gains the available support for span that came with the memory package and netstandard support. This could potentially start at 4.7.2 but the netstandard integration was iffy.

Validate input length (Breaking)

If the usage doesn't make use of the enhanced hash generation to retain entropy (aka the standard use) the library will throw when the input to hash length is greater than 72 chars.
This isn't standard behaviour in most bcrypt libraries where truncating silently is the default.
But I believe this is necessary, should always have been in place, and will force developers to decide deliberately to truncate input at 72 if they wish to retain existing behaviour.

Enhanced Hashing V3

  • Split 'enhanced' versions into separate classes
  • Swap func for delegate
  • Add span based calls

Along with switching the func to a bog standard delegate, splitting the enhanced versions into separate classes simplified maintaining the previous versions and adding in the new version.
The v3 enhanced hash adds in keyed HMAC which apart from protecting against theorical risks of shucking adds a further uniqueness to the produced key material.

Experimental SafeString support

Hate SafeString, it's filled with lies and was really only a useful feature in Windows. In an API it should never be touched but there are some cases in Windows apps where people still require it to tick a box so I've added a sort of basic implementation
This code requires unsafe enabled so will probably be disabled initially.

SecureEquals check

This basically needed to be made for the span handling but also allowed a few minor optimizations which mimic the code in the .net codebase.

Net 10

We basically try to tag against the lts releases; the code may be updated to target sts between releases to assertain if there is any performance improvement worth having but otherwise its lts.

Examples

The api uses the identity extension to enable bcrypt passwords; chucked in a maui to test performance on android as we've been asked before by people doing the hashing at the device level

Release Benchmarks

Separate project that tests previous nuget package versions; its mostly as a simple way to test for regressions in performance while the other benchmark project is for testing components in a more targeted way.

…gardless (just hiding the complexity)

feat: move full span compatible code into a separate file as its a pain to flip back and forth
feat: set Char64 MethodImplOptions.AggressiveInlining
feat: sort  benchmarks
chore: swap out NET8_0_OR_GREATER
chore: drop fwk from benchmark run
feat: EncodeBase64 stackalloc and return via `Buffer.Memmove(ref MemoryMarshal.GetArrayDataReference(destination), ref _reference, (uint)_length);` alloc (toarray > span)
         vs alloc to heap and return span; added benchmark
…ements)

Clone BCryptExtendedV2 to BCryptExtendedV2.Std as we can't use the newer span wiring
* Add experimental support for an extension to allow easy SecureString usage; I don't agree with the usage but its still a thing in UI apps
  * Net5+ BCryptSafeString tries to maintain as much of the call in memory as possible using newer features
  * Net Standard 2 version makes an attempt
  * Add tests; automatically covers both
* Tidy SecureEquals; no need for null check on ReadOnlySpan as it returns default; add variable for length. This is correct vs the .net version which uses SUB instead of XOR
* Zero out input bytes when we're done with them as its our buffer.
* Change fwk to use EnhancedHashDelegate instead of func; keeps the definition in one place
* Update SonarAnalyzer
* Add beginning of doc
…ow me to simplify the existing benchmarks project
Split out functionality from base into core as it makes my life easier working out which junk is still needed in netfwk standard and core
and will also make deleting things like fwk easier later on
```

BenchmarkDotNet v0.15.6, Windows 11 (10.0.26100.6899/24H2/2024Update/HudsonValley)
AMD Ryzen 9 9900X 4.40GHz, 1 CPU, 24 logical and 12 physical cores
.NET SDK 10.0.100
  [Host]     : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v4
  Job-XJDVJN : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v4
  Job-QQRERM : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v4
  Job-TDICCM : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v4
  Job-DYUUZC : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v4
  Job-JIERNF : .NET 8.0.22 (8.0.22, 8.0.2225.52707), X64 RyuJIT x86-64-v4
  Job-QBQHGG : .NET 8.0.22 (8.0.22, 8.0.2225.52707), X64 RyuJIT x86-64-v4
  Job-NOZKUG : .NET 8.0.22 (8.0.22, 8.0.2225.52707), X64 RyuJIT x86-64-v4
  Job-RDWRBK : .NET 8.0.22 (8.0.22, 8.0.2225.52707), X64 RyuJIT x86-64-v4
  Job-RDXXZX : .NET Framework 4.8.1 (4.8.9310.0), X64 RyuJIT VectorSize=256
  Job-RLXPNS : .NET Framework 4.8.1 (4.8.9310.0), X64 RyuJIT VectorSize=256
  Job-XEJGGT : .NET Framework 4.8.1 (4.8.9310.0), X64 RyuJIT VectorSize=256
  Job-URTIUT : .NET Framework 4.8.1 (4.8.9310.0), X64 RyuJIT VectorSize=256
  Job-TUHZZR : .NET Framework 4.8.1 (4.8.9310.0), X64 RyuJIT VectorSize=256
  Job-BXPPFA : .NET Framework 4.8.1 (4.8.9310.0), X64 RyuJIT VectorSize=256
  Job-BQPXRF : .NET Framework 4.8.1 (4.8.9310.0), X64 RyuJIT VectorSize=256
  Job-RIVHIS : .NET Framework 4.8.1 (4.8.9310.0), X64 RyuJIT VectorSize=256
  Job-OVEKXS : .NET Framework 4.8.1 (4.8.9310.0), X64 RyuJIT VectorSize=256
  Job-ULWAAK : .NET Framework 4.8.1 (4.8.9310.0), X64 RyuJIT VectorSize=256

Server=True

```
| Method           | Runtime              | Arguments                                     | key                  | salt                 | hash                 | Mean      | Error    | StdDev   | Rank | Allocated |
|----------------- |--------------------- |---------------------------------------------- |--------------------- |--------------------- |--------------------- |----------:|---------:|---------:|-----:|----------:|
| **TestHashValidate** | **.NET 10.0**            | **/p:BCryptVersion=2.1.4**                        | **~!@#$(...)NBFRD [34]** | **$2a$1(...)rOvHe [29]** | **$2a$1(...)JYlfS [60]** |  **40.43 ms** | **0.689 ms** | **0.644 ms** |    **1** |  **69.76 KB** |
| TestHashValidate | .NET 10.0            | /p:BCryptVersion=3.5.0                        | ~!@#$(...)NBFRD [34] | $2a$1(...)rOvHe [29] | $2a$1(...)JYlfS [60] |  40.40 ms | 0.396 ms | 0.370 ms |    1 |   5.01 KB |
| TestHashValidate | .NET 10.0            | /p:BCryptVersion=4.0.3                        | ~!@#$(...)NBFRD [34] | $2a$1(...)rOvHe [29] | $2a$1(...)JYlfS [60] |  40.64 ms | 0.620 ms | 0.580 ms |    1 |   5.01 KB |
| TestHashValidate | .NET 10.0            | /p:BCryptVersion=5.0.0-prerelease.g5300270033 | ~!@#$(...)NBFRD [34] | $2a$1(...)rOvHe [29] | $2a$1(...)JYlfS [60] |  40.15 ms | 0.278 ms | 0.260 ms |    1 |   4.45 KB |
| TestHashValidate | .NET 8.0             | /p:BCryptVersion=2.1.4                        | ~!@#$(...)NBFRD [34] | $2a$1(...)rOvHe [29] | $2a$1(...)JYlfS [60] |  39.95 ms | 0.228 ms | 0.213 ms |    1 |  69.76 KB |
| TestHashValidate | .NET 8.0             | /p:BCryptVersion=3.5.0                        | ~!@#$(...)NBFRD [34] | $2a$1(...)rOvHe [29] | $2a$1(...)JYlfS [60] |  40.42 ms | 0.471 ms | 0.441 ms |    1 |   5.01 KB |
| TestHashValidate | .NET 8.0             | /p:BCryptVersion=4.0.3                        | ~!@#$(...)NBFRD [34] | $2a$1(...)rOvHe [29] | $2a$1(...)JYlfS [60] |  40.50 ms | 0.432 ms | 0.404 ms |    1 |   5.01 KB |
| TestHashValidate | .NET 8.0             | /p:BCryptVersion=5.0.0-prerelease.g5300270033 | ~!@#$(...)NBFRD [34] | $2a$1(...)rOvHe [29] | $2a$1(...)JYlfS [60] |  40.19 ms | 0.292 ms | 0.273 ms |    1 |   4.45 KB |
| TestHashValidate | .NET Framework 4.6.2 | /p:BCryptVersion=2.0.0                        | ~!@#$(...)NBFRD [34] | $2a$1(...)rOvHe [29] | $2a$1(...)JYlfS [60] |  42.33 ms | 0.481 ms | 0.427 ms |    1 |     70 KB |
| TestHashValidate | .NET Framework 4.6.2 | /p:BCryptVersion=2.1.4                        | ~!@#$(...)NBFRD [34] | $2a$1(...)rOvHe [29] | $2a$1(...)JYlfS [60] |  42.26 ms | 0.336 ms | 0.297 ms |    1 |  69.66 KB |
| TestHashValidate | .NET Framework 4.6.2 | /p:BCryptVersion=3.5.0                        | ~!@#$(...)NBFRD [34] | $2a$1(...)rOvHe [29] | $2a$1(...)JYlfS [60] |  42.39 ms | 0.428 ms | 0.401 ms |    1 |  69.33 KB |
| TestHashValidate | .NET Framework 4.6.2 | /p:BCryptVersion=4.0.3                        | ~!@#$(...)NBFRD [34] | $2a$1(...)rOvHe [29] | $2a$1(...)JYlfS [60] |  42.50 ms | 0.373 ms | 0.349 ms |    1 |  69.33 KB |
| TestHashValidate | .NET Framework 4.6.2 | /p:BCryptVersion=5.0.0-prerelease.g5300270033 | ~!@#$(...)NBFRD [34] | $2a$1(...)rOvHe [29] | $2a$1(...)JYlfS [60] |  42.56 ms | 0.557 ms | 0.521 ms |    1 |  68.99 KB |
| TestHashValidate | .NET Framework 4.8.1 | /p:BCryptVersion=2.0.0                        | ~!@#$(...)NBFRD [34] | $2a$1(...)rOvHe [29] | $2a$1(...)JYlfS [60] |  43.54 ms | 0.845 ms | 0.940 ms |    1 |     70 KB |
| TestHashValidate | .NET Framework 4.8.1 | /p:BCryptVersion=2.1.4                        | ~!@#$(...)NBFRD [34] | $2a$1(...)rOvHe [29] | $2a$1(...)JYlfS [60] |  44.21 ms | 0.512 ms | 0.478 ms |    1 |     70 KB |
| TestHashValidate | .NET Framework 4.8.1 | /p:BCryptVersion=3.5.0                        | ~!@#$(...)NBFRD [34] | $2a$1(...)rOvHe [29] | $2a$1(...)JYlfS [60] |  44.64 ms | 0.865 ms | 0.997 ms |    1 |   4.67 KB |
| TestHashValidate | .NET Framework 4.8.1 | /p:BCryptVersion=4.0.3                        | ~!@#$(...)NBFRD [34] | $2a$1(...)rOvHe [29] | $2a$1(...)JYlfS [60] |  42.64 ms | 0.314 ms | 0.293 ms |    1 |   4.67 KB |
| TestHashValidate | .NET Framework 4.8.1 | /p:BCryptVersion=5.0.0-prerelease.g5300270033 | ~!@#$(...)NBFRD [34] | $2a$1(...)rOvHe [29] | $2a$1(...)JYlfS [60] |  42.59 ms | 0.292 ms | 0.259 ms |    1 |   4.67 KB |
| **TestHashValidate** | **.NET 10.0**            | **/p:BCryptVersion=2.1.4**                        | **~!@#$(...)NBFRD [34]** | **$2a$1(...)nkrPO [29]** | **$2a$1(...)eyhgC [60]** | **160.46 ms** | **1.088 ms** | **0.964 ms** |    **2** | **261.76 KB** |
| TestHashValidate | .NET 10.0            | /p:BCryptVersion=3.5.0                        | ~!@#$(...)NBFRD [34] | $2a$1(...)nkrPO [29] | $2a$1(...)eyhgC [60] | 160.22 ms | 0.962 ms | 0.900 ms |    2 |   5.01 KB |
| TestHashValidate | .NET 10.0            | /p:BCryptVersion=4.0.3                        | ~!@#$(...)NBFRD [34] | $2a$1(...)nkrPO [29] | $2a$1(...)eyhgC [60] | 160.37 ms | 0.645 ms | 0.572 ms |    2 |   5.01 KB |
| TestHashValidate | .NET 10.0            | /p:BCryptVersion=5.0.0-prerelease.g5300270033 | ~!@#$(...)NBFRD [34] | $2a$1(...)nkrPO [29] | $2a$1(...)eyhgC [60] | 160.95 ms | 1.437 ms | 1.274 ms |    2 |   4.45 KB |
| TestHashValidate | .NET 8.0             | /p:BCryptVersion=2.1.4                        | ~!@#$(...)NBFRD [34] | $2a$1(...)nkrPO [29] | $2a$1(...)eyhgC [60] | 162.36 ms | 3.214 ms | 3.439 ms |    2 | 261.76 KB |
| TestHashValidate | .NET 8.0             | /p:BCryptVersion=3.5.0                        | ~!@#$(...)NBFRD [34] | $2a$1(...)nkrPO [29] | $2a$1(...)eyhgC [60] | 160.75 ms | 0.941 ms | 0.786 ms |    2 |   5.01 KB |
| TestHashValidate | .NET 8.0             | /p:BCryptVersion=4.0.3                        | ~!@#$(...)NBFRD [34] | $2a$1(...)nkrPO [29] | $2a$1(...)eyhgC [60] | 160.83 ms | 1.753 ms | 1.554 ms |    2 |   5.01 KB |
| TestHashValidate | .NET 8.0             | /p:BCryptVersion=5.0.0-prerelease.g5300270033 | ~!@#$(...)NBFRD [34] | $2a$1(...)nkrPO [29] | $2a$1(...)eyhgC [60] | 160.47 ms | 1.143 ms | 0.955 ms |    2 |   4.45 KB |
| TestHashValidate | .NET Framework 4.6.2 | /p:BCryptVersion=2.0.0                        | ~!@#$(...)NBFRD [34] | $2a$1(...)nkrPO [29] | $2a$1(...)eyhgC [60] | 170.28 ms | 2.024 ms | 1.893 ms |    2 | 261.33 KB |
| TestHashValidate | .NET Framework 4.6.2 | /p:BCryptVersion=2.1.4                        | ~!@#$(...)NBFRD [34] | $2a$1(...)nkrPO [29] | $2a$1(...)eyhgC [60] | 169.68 ms | 1.650 ms | 1.543 ms |    2 | 261.33 KB |
| TestHashValidate | .NET Framework 4.6.2 | /p:BCryptVersion=3.5.0                        | ~!@#$(...)NBFRD [34] | $2a$1(...)nkrPO [29] | $2a$1(...)eyhgC [60] | 168.60 ms | 1.189 ms | 1.112 ms |    2 | 262.21 KB |
| TestHashValidate | .NET Framework 4.6.2 | /p:BCryptVersion=4.0.3                        | ~!@#$(...)NBFRD [34] | $2a$1(...)nkrPO [29] | $2a$1(...)eyhgC [60] | 169.15 ms | 1.267 ms | 1.185 ms |    2 | 261.33 KB |
| TestHashValidate | .NET Framework 4.6.2 | /p:BCryptVersion=5.0.0-prerelease.g5300270033 | ~!@#$(...)NBFRD [34] | $2a$1(...)nkrPO [29] | $2a$1(...)eyhgC [60] | 169.73 ms | 2.048 ms | 1.916 ms |    2 | 261.33 KB |
| TestHashValidate | .NET Framework 4.8.1 | /p:BCryptVersion=2.0.0                        | ~!@#$(...)NBFRD [34] | $2a$1(...)nkrPO [29] | $2a$1(...)eyhgC [60] | 169.54 ms | 2.082 ms | 1.948 ms |    2 | 261.33 KB |
| TestHashValidate | .NET Framework 4.8.1 | /p:BCryptVersion=2.1.4                        | ~!@#$(...)NBFRD [34] | $2a$1(...)nkrPO [29] | $2a$1(...)eyhgC [60] | 169.22 ms | 1.691 ms | 1.499 ms |    2 | 261.33 KB |
| TestHashValidate | .NET Framework 4.8.1 | /p:BCryptVersion=3.5.0                        | ~!@#$(...)NBFRD [34] | $2a$1(...)nkrPO [29] | $2a$1(...)eyhgC [60] | 169.25 ms | 1.372 ms | 1.217 ms |    2 |   5.33 KB |
| TestHashValidate | .NET Framework 4.8.1 | /p:BCryptVersion=4.0.3                        | ~!@#$(...)NBFRD [34] | $2a$1(...)nkrPO [29] | $2a$1(...)eyhgC [60] | 172.02 ms | 3.346 ms | 3.436 ms |    2 |   5.33 KB |
| TestHashValidate | .NET Framework 4.8.1 | /p:BCryptVersion=5.0.0-prerelease.g5300270033 | ~!@#$(...)NBFRD [34] | $2a$1(...)nkrPO [29] | $2a$1(...)eyhgC [60] | 173.11 ms | 3.154 ms | 3.375 ms |    2 |   5.33 KB |
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.

2 participants