diff --git a/.codacy.yml b/.codacy.yml new file mode 100644 index 0000000..2ed27d2 --- /dev/null +++ b/.codacy.yml @@ -0,0 +1,10 @@ +exclude_paths: + - ".github/**" + - ".config/**" + - "coverage/**" + - "vendor/**" + - "docs/**" + - "examples/**" + - "assets/**" + - "tests/**" + - "benchmarks/**" diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..c94b373 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,27 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-coverage": { + "version": "18.1.0", + "commands": [ + "dotnet-coverage" + ], + "rollForward": false + }, + "dotnet-stryker": { + "version": "4.12.0", + "commands": [ + "dotnet-stryker" + ], + "rollForward": false + }, + "docfx": { + "version": "2.78.4", + "commands": [ + "docfx" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig index a6e5446..c20e19f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -79,5 +79,4 @@ csharp_new_line_before_finally = true csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_members_in_anonymous_types = true - -file_header_template = /*\nThe MIT License (MIT)\nCopyright (c) 2006 Damien Miller djm@mindrot.org (jBCrypt)\nCopyright (c) 2013 Ryan D. Emerle (.Net port)\nCopyright (c) 2016/2025 Chris McKee (.Net-core port / patches / new features)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files\n(the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,\nmerge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished\nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\nIN THE SOFTWARE.\n*/ \ No newline at end of file +file_header_template = /*\nThe MIT License (MIT)\nCopyright (c) 2006 Damien Miller djm@mindrot.org (jBCrypt)\nCopyright (c) 2013 Ryan D. Emerle (.Net port)\nCopyright (c) 2016 Chris McKee (.Net-core port / patches / new features)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files\n(the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,\nmerge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished\nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\nIN THE SOFTWARE.\n*/ \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..a6eb98e --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @chrismckee \ No newline at end of file diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 0000000..8c1458c --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,6 @@ +paths: + - 'src' +paths-ignore: + - '**/examples/**' + - '**/benchmarks/**' + - '**/tests/**' diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 97cc51f..5a60d04 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,12 +9,8 @@ updates: update-types: ["version-update:semver-major"] - dependency-name: "System.Runtime.Caching" update-types: ["version-update:semver-major"] - reviewers: - - "chrismckee" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" - reviewers: - - "chrismckee" diff --git a/.github/workflows/ci-build-pr.yml b/.github/workflows/ci-build-pr.yml new file mode 100644 index 0000000..b225489 --- /dev/null +++ b/.github/workflows/ci-build-pr.yml @@ -0,0 +1,106 @@ +name: CI PR Build + +on: + workflow_dispatch: + push: + branches: + - 'main' + paths-ignore: + - 'docs/**' + - '.github/**' + pull_request: + branches: + - 'main' + paths-ignore: + - 'docs/**' + - '.github/**' + + +permissions: + contents: read + issues: read + pull-requests: write + checks: write + +env: + DOTNET_NOLOGO: true + DOTNET_GENERATE_ASPNET_CERTIFICATE: false + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + +jobs: + build: + name: Build and Test + runs-on: ubuntu-24.04 + steps: + - name: 'Harden Runner' + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 + with: + egress-policy: audit + + - name: 'Checkout' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 # avoid shallow clone so nbgv can do its work. + + - name: 'Setup .NET SDK' + uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0 + with: + dotnet-version: 10.x + + - name: 'Restore external dependencies' + run: dotnet restore + + - name: 'Build' + id: build + run: dotnet build --configuration Debug --no-restore + + - name: 'Test' + id: test + run: dotnet run --configuration Release --coverage --coverage-output-format cobertura --report-github --project tests/UnitTests/BCrypt.Net.UnitTests.csproj + + - name: 'Generate Coverage Reports' + uses: danielpalme/ReportGenerator-GitHub-Action@c4c5175a441c6603ec614f5084386dabe0e2295b # v5.4.12 + with: + reports: "tests/**/*.cobertura.xml" + targetdir: "${{ github.workspace }}" + reporttypes: "Cobertura" + verbosity: "Info" + title: "Code Coverage" + tag: "${{ github.run_number }}_${{ github.run_id }}" + toolpath: "reportgeneratortool" + license: ${{ secrets.REPORT_GENERATOR_LICENSE }} + + - name: Publish Code Coverage Report + uses: irongut/CodeCoverageSummary@51cc3a756ddcd398d447c044c02cb6aa83fdae95 # v1.3.0 + with: + filename: "Cobertura.xml" + badge: true + fail_below_min: false # just informative for now + format: markdown + hide_branch_rate: false + hide_complexity: false + indicators: true + output: both + thresholds: "10 30" + + - name: Upload Code Coverage Results + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: coverage-results + path: | + ${{ github.workspace }}/Cobertura.xml + ${{ github.workspace }}/code-coverage-results.md + retention-days: 5 + + - name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action@afb2984f4d89672b2f9d9c13ae23d53779671984 # v2.19.0 + if: always() + with: + files: "tests/**/TestResults.xml" + + - name: Upload Test Artifacts + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: test-results + path: "tests/**/TestResults.xml" + retention-days: 5 diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 7df381f..d92aadf 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -8,9 +8,7 @@ on: paths-ignore: - 'docs/**' - '.github/**' - pull_request: - branches: - - 'main' + permissions: contents: read @@ -29,19 +27,19 @@ jobs: runs-on: ubuntu-24.04 steps: - name: 'Harden Runner' - uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - name: 'Checkout' - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 # avoid shallow clone so nbgv can do its work. - name: 'Setup .NET SDK' - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 + uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0 with: - dotnet-version: 9.0.x + dotnet-version: 10.0.x - name: 'Restore external dependencies' run: dotnet restore @@ -56,14 +54,11 @@ jobs: name: build-artifacts path: | src/**/BCrypt.*.nupkg - src/**/BCrypt.*.dll - src/**/BCrypt.*.deps.json - src/**/BCrypt.*.xml retention-days: 5 - name: 'Test' id: test - run: dotnet test --restore --collect:"XPlat Code Coverage" --logger junit --configuration Debug + run: dotnet run --configuration Release --coverage --coverage-output-format cobertura --report-github --project tests/UnitTests/BCrypt.Net.UnitTests.csproj - name: 'Create test summary' uses: test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86 # v2.4 @@ -73,9 +68,9 @@ jobs: if: always() - name: 'Generate Coverage Reports' - uses: danielpalme/ReportGenerator-GitHub-Action@cc137d2b561c02b63ae869ffbe8f68af9d904bf4 # 5.4.6 + uses: danielpalme/ReportGenerator-GitHub-Action@c4c5175a441c6603ec614f5084386dabe0e2295b # v5.4.12 with: - reports: "tests/**/coverage.cobertura.xml" + reports: "tests/**/*.cobertura.xml" targetdir: "${{ github.workspace }}" reporttypes: "Cobertura" verbosity: "Info" diff --git a/.github/workflows/ci-manual-build-test-sign.yml b/.github/workflows/ci-manual-build-test-sign.yml deleted file mode 100644 index 6557bbc..0000000 --- a/.github/workflows/ci-manual-build-test-sign.yml +++ /dev/null @@ -1,121 +0,0 @@ -name: Manual Build, Test, Sign, Publish -on: - workflow_dispatch: - inputs: - public_release: - description: 'Public Release' - type: boolean - required: true - default: true - perform_sign: - description: 'Sign' - type: boolean - required: true - default: true - perform_publish: - description: 'nuget publish' - type: boolean - required: true - default: false - -env: - DOTNET_NOLOGO: true - DOTNET_GENERATE_ASPNET_CERTIFICATE: false - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true - DOTNET_CLI_TELEMETRY_OPTOUT: true - nupkgDirectory: ${{ github.workspace}}/dists - -jobs: - build: - permissions: - contents: read - - name: Build release - runs-on: ubuntu-latest - steps: - - name: Harden Runner - uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 - with: - egress-policy: audit - - - name: 'Checkout repository' - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - fetch-depth: 0 # avoid shallow clone so nbgv can do its work. - - - name: 'Setup .NET SDK' - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 - with: - dotnet-version: 9.0.x - - - name: 'Build' - run: dotnet build --configuration Release --property:PublicRelease=${{ inputs.public_release }} - - - name: 'Test' - run: dotnet test --configuration Release --no-restore --no-build --property:PublicRelease=${{ inputs.public_release }} tests/UnitTests/BCrypt.Net.UnitTests.csproj - - - name: 'Pack release' - run: dotnet pack --configuration Release --no-restore --no-build --output ${{ env.nupkgDirectory }} --property:PublicRelease=${{ inputs.public_release }} - - - name: 'List artifact directory' - shell: pwsh - run: > - Get-ChildItem -Path ${{ env.nupkgDirectory }} -Recurse -Force - - - name: 'Extract SBOMs' - shell: pwsh - run: > - Get-ChildItem -Path ${{ env.nupkgDirectory }} -Filter *.nupkg -Force | ForEach-Object { - Expand-Archive $_.FullName "$($_.DirectoryName)/$($_.Basename)" -Force - Copy-Item "$($_.DirectoryName)/$($_.Basename)/_manifest/spdx_2.2/manifest.spdx.json" -Destination "${{ env.nupkgDirectory }}/$($_.Basename).spdx.json" - Copy-Item "$($_.DirectoryName)/$($_.Basename)/_manifest/spdx_2.2/manifest.spdx.json.sha256" -Destination "${{ env.nupkgDirectory }}/$($_.Basename).spdx.json.sha256" - Remove-Item "$($_.DirectoryName)/$($_.Basename)" -Force -Recurse } - - - name: 'List artifact directory' - shell: pwsh - run: > - Get-ChildItem -Path ${{ env.nupkgDirectory }} -Recurse -Force - - - name: Upload unsigned nupkgs - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: build-artifacts - path: ${{ env.nupkgDirectory }}/* - retention-days: 7 - - # publish: - # name: Publish to nuget - # needs: sign - # runs-on: ubuntu-latest - # if: ${{ inputs.perform_publish }} - # environment: release - # permissions: - # id-token: write - # steps: - # - name: 'Harden Runner' - # uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 - # with: - # egress-policy: audit - - # - name: 'Setup .NET SDK' - # uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 - - # - name: 'Gather nupkgs from signing output' - # uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 - # with: - # name: signed-artifacts - # path : ${{ env.nupkgDirectory }} - - # - name: List assets to be published - # shell: pwsh - # run: > - # Get-ChildItem -Path ${{ env.nupkgDirectory }} -Filter *.nupkg -Recurse -Force - - # # Use --skip-duplicate to prevent errors if a package with the same version already exists. - # # This allows a retry of a failed workflow, already published packages will be skipped without error. - # - name: Publish NuGet package - # shell: pwsh - # run: > - # foreach($file in (Get-ChildItem "${{ env.nupkgDirectory }}" -Recurse -Filter *.nupkg)) { - # dotnet nuget push $file --api-key "${{ secrets.NUGET_APIKEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate - # } diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 98a4b83..68a140a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -12,6 +12,7 @@ on: - 'assets/*' - 'examples/*' - 'dists/*' + - 'benchmarks/*' pull_request: # The branches below must be a subset of the branches above branches: @@ -23,6 +24,7 @@ on: - 'assets/*' - 'examples/*' - 'dists/*' + - 'benchmarks/*' schedule: - cron: '25 4 * * 2' @@ -49,25 +51,27 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - name: 'Checkout repository' - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 # avoid shallow clone so nbgv can do its work. - name: 'Setup .NET SDK' - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 + uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0 with: - dotnet-version: 9.0.x + dotnet-version: 10.x # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3.28.2 + uses: github/codeql-action/init@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3 + with: languages: ${{ matrix.language }} + config-file: ./.github/codeql/codeql-config.yml # We can't use autobuild because we want to restrict the build to just src folder solutions # and avoid triggering deterministic builds and git commit based versioning @@ -75,4 +79,5 @@ jobs: - run: dotnet build --configuration CodeQL /p:UseSharedCompilation=false /t:rebuild - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3.28.2 + uses: github/codeql-action/analyze@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3 + diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index f3ab11f..5020768 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -14,12 +14,12 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Harden Runner - uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - name: 'Checkout Repository' - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: 'Dependency Review' - uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0 + uses: actions/dependency-review-action@595b5aeba73380359d98a5e087f648dbb0edce1b # v4.7.3 diff --git a/.github/workflows/devskim.yml b/.github/workflows/devskim.yml index 3e31856..a3e7267 100644 --- a/.github/workflows/devskim.yml +++ b/.github/workflows/devskim.yml @@ -17,12 +17,16 @@ jobs: dependency-review: runs-on: ubuntu-24.04 steps: - - name: Harden Runner - uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + - name: 'Harden Runner' + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - - uses: actions/checkout@v4 - - uses: microsoft/DevSkim-Action@v1 - - uses: github/codeql-action/upload-sarif@v3 + + - name: 'Checkout' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 # avoid shallow clone so nbgv can do its work. + - uses: microsoft/DevSkim-Action@4b5047945a44163b94642a1cecc0d93a3f428cc6 #v1.0.16 + - uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 #v4.32.3 with: sarif_file: devskim-results.sarif \ No newline at end of file diff --git a/.github/workflows/generate-publish-docs.yml b/.github/workflows/generate-publish-docs.yml index 971a58e..a60a895 100644 --- a/.github/workflows/generate-publish-docs.yml +++ b/.github/workflows/generate-publish-docs.yml @@ -30,25 +30,25 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - name: 'Checkout' - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 # avoid shallow clone so nbgv can do its work. - name: 'Setup .NET SDK' - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 + uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0 with: - dotnet-version: 9.0.x + dotnet-version: 10.0.x - run: dotnet tool update -g docfx - run: docfx ./docs/docfx.json - name: Upload Pages Artifact - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1 + uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0 with: # Upload entire repository path: './docs/_site' diff --git a/.github/workflows/infersharp.yml b/.github/workflows/infersharp.yml new file mode 100644 index 0000000..ff5326e --- /dev/null +++ b/.github/workflows/infersharp.yml @@ -0,0 +1,66 @@ +name: Infer# + +on: + workflow_dispatch: + push: + branches: + - main + paths-ignore: + - '**.md' + - 'docs/**' + - '.github/**' + pull_request: + branches: + - main + paths-ignore: + - '**.md' + - 'docs/**' + - '.github/**' + +permissions: + contents: read + security-events: write + +env: + DOTNET_NOLOGO: true + DOTNET_GENERATE_ASPNET_CERTIFICATE: false + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + +jobs: + infersharp: + name: Infer# Analysis + runs-on: ubuntu-24.04 + steps: + - name: Harden Runner + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0 + with: + dotnet-version: 8.x + + - name: Setup .NET SDK + uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0 + with: + dotnet-version: 10.x + + - name: Build + run: dotnet build src/BCrypt.Net/BCrypt.Net.csproj --configuration Release --framework net8.0 + + - name: Run Infer# + uses: microsoft/infersharpaction@b749060de518f410f92c87d37d2366e5e9d7c5fc #v1.5 + id: runinfersharp + with: + binary-path: src/BCrypt.Net/bin/Release/net8.0 + + - name: Upload SARIF to GitHub Security + uses: github/codeql-action/upload-sarif@d3678e237b9c32a6c9bffb3315c335f976f3549f # v3.30.2 + with: + sarif_file: infer-out/report.sarif diff --git a/.github/workflows/markdown-link-check.yml b/.github/workflows/markdown-link-check.yml deleted file mode 100644 index 0eec6ce..0000000 --- a/.github/workflows/markdown-link-check.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Markdown Links Check - -on: - workflow_dispatch: - push: - branches: - - 'main' - paths: - - '**.md' - pull_request: - branches: - - main - paths: - - '**.md' - #schedule: - # Run every-day at 9:00 AM (See https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html#tag_20_25_07) - #- cron: "0 9 * * *" -permissions: - contents: read - -jobs: - markdown-link-check: - runs-on: ubuntu-24.04 - steps: - - name: Harden Runner - uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 - with: - egress-policy: audit - - - name: 'Checkout Repository' - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - - name: 'Check for dead links in markdown files' - uses: gaurav-nelson/github-action-markdown-link-check@1b916f2cf6c36510a6059943104e3c42ce6c16bc # 1.0.16 - with: - use-quiet-mode: 'yes' - use-verbose-mode: 'no' diff --git a/.github/workflows/upload-coverage-report.yml b/.github/workflows/upload-coverage-report.yml index c887166..83216aa 100644 --- a/.github/workflows/upload-coverage-report.yml +++ b/.github/workflows/upload-coverage-report.yml @@ -18,7 +18,7 @@ jobs: github.event.workflow_run.conclusion == 'success' steps: - name: Harden Runner - uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit @@ -28,7 +28,7 @@ jobs: name: coverage-results - name: Add Code Coverage PR Comment - uses: marocchino/sticky-pull-request-comment@67d0dec7b07ed060a405f9b2a64b8ab319fdd7db # v2.9.2 + uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4 if: github.event_name == 'pull_request' with: recreate: true diff --git a/.gitignore b/.gitignore index 94e51cc..6525e58 100644 --- a/.gitignore +++ b/.gitignore @@ -266,3 +266,9 @@ __pycache__/ /src/Benchmark/BenchmarkDotNet.Artifacts/results/* /docs/_site/ /assets/*.nupkg + +/benchmark/BenchmarkDotNet.Artifacts +/examples/Api/WebApi/app.db* +BenchmarkDotNet.Artifacts +/benchmark-releases/ReleaseBenchmark/Generated/*.cs +**/Generated/VersionInfo.cs diff --git a/BCrypt.Net.sln b/BCrypt.Net.sln index cee3569..195d306 100644 --- a/BCrypt.Net.sln +++ b/BCrypt.Net.sln @@ -1,21 +1,20 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29709.97 +# Visual Studio Version 18 +VisualStudioVersion = 18.3.11505.172 d18.3 MinimumVisualStudioVersion = 15.0.26228.4 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BCrypt.Net", "src\BCrypt.Net\BCrypt.Net.csproj", "{CD69F016-5940-4FCA-BCA1-9D1D87C6F873}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "tests\UnitTests\BCrypt.Net.UnitTests.csproj", "{2078EB7B-7EDF-4B65-80F7-DB4D92E08CCD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BCrypt.Net.UnitTests", "tests\UnitTests\BCrypt.Net.UnitTests.csproj", "{2078EB7B-7EDF-4B65-80F7-DB4D92E08CCD}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build-tools", "build-tools", "{6F3D5F8B-CD73-474C-BA79-D4A17E9F106D}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".build-tools", ".build-tools", "{6F3D5F8B-CD73-474C-BA79-D4A17E9F106D}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig - Dockerfile = Dockerfile + Directory.Build.props = Directory.Build.props Directory.Packages.props = Directory.Packages.props + Dockerfile = Dockerfile EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmark", "benchmark\BCryptNet.BenchMarks.csproj", "{E75AA3B8-BF28-4366-B5C6-14AF342290C3}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{DBAB6D93-7DE4-4A70-89E7-1EE784C1A466}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExampleConsoleApp", "examples\ConsoleApp\ExampleConsoleApp\ExampleConsoleApp.csproj", "{A24DEBF7-6ED9-4774-B67F-7A0EE18125C5}" @@ -26,59 +25,122 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BCrypt.Net.IdentityExtensio EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "github", "github", "{93729F4E-5F15-4769-8385-19176C7AAA2C}" ProjectSection(SolutionItems) = preProject - .github\dependabot.yml = .github\dependabot.yml .github\ISSUE_TEMPLATE\bug_report.md = .github\ISSUE_TEMPLATE\bug_report.md - .github\ISSUE_TEMPLATE\docs-issue.md = .github\ISSUE_TEMPLATE\docs-issue.md - .github\ISSUE_TEMPLATE\feature_request.md = .github\ISSUE_TEMPLATE\feature_request.md - .github\stale.yml = .github\stale.yml .github\workflows\ci-build.yml = .github\workflows\ci-build.yml - .github\workflows\ci-manual-build-test-sign.yml = .github\workflows\ci-manual-build-test-sign.yml .github\workflows\codeql-analysis.yml = .github\workflows\codeql-analysis.yml + .github\dependabot.yml = .github\dependabot.yml .github\workflows\dependency-review.yml = .github\workflows\dependency-review.yml + .github\ISSUE_TEMPLATE\docs-issue.md = .github\ISSUE_TEMPLATE\docs-issue.md + .github\ISSUE_TEMPLATE\feature_request.md = .github\ISSUE_TEMPLATE\feature_request.md .github\workflows\generate-publish-docs.yml = .github\workflows\generate-publish-docs.yml - .github\workflows\markdown-link-check.yml = .github\workflows\markdown-link-check.yml + .github\stale.yml = .github\stale.yml .github\workflows\upload-coverage-report.yml = .github\workflows\upload-coverage-report.yml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AndroidTest", "examples\AndroidTest\AndroidTest.csproj", "{AEA6712D-0FAE-4031-8FA7-FDD2A026855A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BCrypt.Net.IdentityExtensions.Tests", "tests\BCrypt.Net.IdentityExtensions.Tests\BCrypt.Net.IdentityExtensions.Tests.csproj", "{03E4D4A1-36B4-4538-A030-3C9141C6A3CF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{4FA4B555-4BA9-49D0-84B2-1D222E0F4498}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Diagnostic-Benchmarks", "benchmarks\Diagnostic-Benchmarks\Diagnostic-Benchmarks.csproj", "{0BFE3C36-05D3-4A96-8694-1179FD705162}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Method-Benchmarks", "benchmarks\Method-Benchmarks\Method-Benchmarks.csproj", "{DC455EE9-99A8-4900-AFBA-661E5EFCE50F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Release-Benchmarks", "benchmarks\Release-Benchmarks\Release-Benchmarks.csproj", "{799531B7-02FD-4ED9-BE76-6BFCB083D780}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + All|Any CPU = All|Any CPU Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU + Benchmarks|Any CPU = Benchmarks|Any CPU CodeQL|Any CPU = CodeQL|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD69F016-5940-4FCA-BCA1-9D1D87C6F873}.All|Any CPU.ActiveCfg = All|Any CPU + {CD69F016-5940-4FCA-BCA1-9D1D87C6F873}.All|Any CPU.Build.0 = All|Any CPU {CD69F016-5940-4FCA-BCA1-9D1D87C6F873}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CD69F016-5940-4FCA-BCA1-9D1D87C6F873}.Debug|Any CPU.Build.0 = Debug|Any CPU {CD69F016-5940-4FCA-BCA1-9D1D87C6F873}.Release|Any CPU.ActiveCfg = Release|Any CPU {CD69F016-5940-4FCA-BCA1-9D1D87C6F873}.Release|Any CPU.Build.0 = Release|Any CPU - {CD69F016-5940-4FCA-BCA1-9D1D87C6F873}.CodeQL|Any CPU.ActiveCfg = CodeQL|Any CPU - {CD69F016-5940-4FCA-BCA1-9D1D87C6F873}.CodeQL|Any CPU.Build.0 = CodeQL|Any CPU + {CD69F016-5940-4FCA-BCA1-9D1D87C6F873}.Benchmarks|Any CPU.ActiveCfg = Benchmarks|Any CPU + {CD69F016-5940-4FCA-BCA1-9D1D87C6F873}.Benchmarks|Any CPU.Build.0 = Benchmarks|Any CPU + {CD69F016-5940-4FCA-BCA1-9D1D87C6F873}.CodeQL|Any CPU.ActiveCfg = Release|Any CPU + {CD69F016-5940-4FCA-BCA1-9D1D87C6F873}.CodeQL|Any CPU.Build.0 = Release|Any CPU + {2078EB7B-7EDF-4B65-80F7-DB4D92E08CCD}.All|Any CPU.ActiveCfg = All|Any CPU + {2078EB7B-7EDF-4B65-80F7-DB4D92E08CCD}.All|Any CPU.Build.0 = All|Any CPU {2078EB7B-7EDF-4B65-80F7-DB4D92E08CCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2078EB7B-7EDF-4B65-80F7-DB4D92E08CCD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E75AA3B8-BF28-4366-B5C6-14AF342290C3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E75AA3B8-BF28-4366-B5C6-14AF342290C3}.Release|Any CPU.Build.0 = Release|Any CPU - {E75AA3B8-BF28-4366-B5C6-14AF342290C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E75AA3B8-BF28-4366-B5C6-14AF342290C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2078EB7B-7EDF-4B65-80F7-DB4D92E08CCD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2078EB7B-7EDF-4B65-80F7-DB4D92E08CCD}.Release|Any CPU.Build.0 = Release|Any CPU + {2078EB7B-7EDF-4B65-80F7-DB4D92E08CCD}.Benchmarks|Any CPU.ActiveCfg = Benchmarks|Any CPU + {2078EB7B-7EDF-4B65-80F7-DB4D92E08CCD}.Benchmarks|Any CPU.Build.0 = Benchmarks|Any CPU + {A24DEBF7-6ED9-4774-B67F-7A0EE18125C5}.All|Any CPU.ActiveCfg = All|Any CPU + {A24DEBF7-6ED9-4774-B67F-7A0EE18125C5}.All|Any CPU.Build.0 = All|Any CPU {A24DEBF7-6ED9-4774-B67F-7A0EE18125C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A24DEBF7-6ED9-4774-B67F-7A0EE18125C5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A24DEBF7-6ED9-4774-B67F-7A0EE18125C5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A24DEBF7-6ED9-4774-B67F-7A0EE18125C5}.Benchmarks|Any CPU.ActiveCfg = Benchmarks|Any CPU + {CE412A71-A08D-4263-8960-664827DA526B}.All|Any CPU.ActiveCfg = All|Any CPU + {CE412A71-A08D-4263-8960-664827DA526B}.All|Any CPU.Build.0 = All|Any CPU {CE412A71-A08D-4263-8960-664827DA526B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CE412A71-A08D-4263-8960-664827DA526B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CE412A71-A08D-4263-8960-664827DA526B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CE412A71-A08D-4263-8960-664827DA526B}.Benchmarks|Any CPU.ActiveCfg = Benchmarks|Any CPU + {70440057-8D4E-41B0-8DF7-8A67B4C0EE28}.All|Any CPU.ActiveCfg = All|Any CPU + {70440057-8D4E-41B0-8DF7-8A67B4C0EE28}.All|Any CPU.Build.0 = All|Any CPU {70440057-8D4E-41B0-8DF7-8A67B4C0EE28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {70440057-8D4E-41B0-8DF7-8A67B4C0EE28}.Debug|Any CPU.Build.0 = Debug|Any CPU {70440057-8D4E-41B0-8DF7-8A67B4C0EE28}.Release|Any CPU.ActiveCfg = Release|Any CPU {70440057-8D4E-41B0-8DF7-8A67B4C0EE28}.Release|Any CPU.Build.0 = Release|Any CPU - {70440057-8D4E-41B0-8DF7-8A67B4C0EE28}.CodeQL|Any CPU.ActiveCfg = CodeQL|Any CPU - {70440057-8D4E-41B0-8DF7-8A67B4C0EE28}.CodeQL|Any CPU.Build.0 = CodeQL|Any CPU + {70440057-8D4E-41B0-8DF7-8A67B4C0EE28}.Benchmarks|Any CPU.ActiveCfg = Benchmarks|Any CPU + {70440057-8D4E-41B0-8DF7-8A67B4C0EE28}.Benchmarks|Any CPU.Build.0 = Benchmarks|Any CPU + {70440057-8D4E-41B0-8DF7-8A67B4C0EE28}.CodeQL|Any CPU.ActiveCfg = Release|Any CPU + {70440057-8D4E-41B0-8DF7-8A67B4C0EE28}.CodeQL|Any CPU.Build.0 = Release|Any CPU + {AEA6712D-0FAE-4031-8FA7-FDD2A026855A}.All|Any CPU.ActiveCfg = All|Any CPU + {AEA6712D-0FAE-4031-8FA7-FDD2A026855A}.All|Any CPU.Build.0 = All|Any CPU + {AEA6712D-0FAE-4031-8FA7-FDD2A026855A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AEA6712D-0FAE-4031-8FA7-FDD2A026855A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AEA6712D-0FAE-4031-8FA7-FDD2A026855A}.Benchmarks|Any CPU.ActiveCfg = Benchmarks|Any CPU + {03E4D4A1-36B4-4538-A030-3C9141C6A3CF}.All|Any CPU.ActiveCfg = Debug|Any CPU + {03E4D4A1-36B4-4538-A030-3C9141C6A3CF}.All|Any CPU.Build.0 = Debug|Any CPU + {03E4D4A1-36B4-4538-A030-3C9141C6A3CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03E4D4A1-36B4-4538-A030-3C9141C6A3CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03E4D4A1-36B4-4538-A030-3C9141C6A3CF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03E4D4A1-36B4-4538-A030-3C9141C6A3CF}.Release|Any CPU.Build.0 = Release|Any CPU + {03E4D4A1-36B4-4538-A030-3C9141C6A3CF}.Benchmarks|Any CPU.ActiveCfg = Benchmarks|Any CPU + {03E4D4A1-36B4-4538-A030-3C9141C6A3CF}.Benchmarks|Any CPU.Build.0 = Benchmarks|Any CPU + {0BFE3C36-05D3-4A96-8694-1179FD705162}.All|Any CPU.ActiveCfg = All|Any CPU + {0BFE3C36-05D3-4A96-8694-1179FD705162}.All|Any CPU.Build.0 = All|Any CPU + {0BFE3C36-05D3-4A96-8694-1179FD705162}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0BFE3C36-05D3-4A96-8694-1179FD705162}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0BFE3C36-05D3-4A96-8694-1179FD705162}.Benchmarks|Any CPU.ActiveCfg = Benchmarks|Any CPU + {0BFE3C36-05D3-4A96-8694-1179FD705162}.Benchmarks|Any CPU.Build.0 = Benchmarks|Any CPU + {DC455EE9-99A8-4900-AFBA-661E5EFCE50F}.All|Any CPU.ActiveCfg = All|Any CPU + {DC455EE9-99A8-4900-AFBA-661E5EFCE50F}.All|Any CPU.Build.0 = All|Any CPU + {DC455EE9-99A8-4900-AFBA-661E5EFCE50F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DC455EE9-99A8-4900-AFBA-661E5EFCE50F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DC455EE9-99A8-4900-AFBA-661E5EFCE50F}.Benchmarks|Any CPU.ActiveCfg = Benchmarks|Any CPU + {DC455EE9-99A8-4900-AFBA-661E5EFCE50F}.Benchmarks|Any CPU.Build.0 = Benchmarks|Any CPU + {799531B7-02FD-4ED9-BE76-6BFCB083D780}.All|Any CPU.ActiveCfg = All|Any CPU + {799531B7-02FD-4ED9-BE76-6BFCB083D780}.All|Any CPU.Build.0 = All|Any CPU + {799531B7-02FD-4ED9-BE76-6BFCB083D780}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {799531B7-02FD-4ED9-BE76-6BFCB083D780}.Release|Any CPU.ActiveCfg = Release|Any CPU + {799531B7-02FD-4ED9-BE76-6BFCB083D780}.Benchmarks|Any CPU.ActiveCfg = Benchmarks|Any CPU + {799531B7-02FD-4ED9-BE76-6BFCB083D780}.Benchmarks|Any CPU.Build.0 = Benchmarks|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {D5ADD1E2-61BB-4575-B5D8-78FB549A4DBC} - EndGlobalSection GlobalSection(NestedProjects) = preSolution {A24DEBF7-6ED9-4774-B67F-7A0EE18125C5} = {DBAB6D93-7DE4-4A70-89E7-1EE784C1A466} {CE412A71-A08D-4263-8960-664827DA526B} = {DBAB6D93-7DE4-4A70-89E7-1EE784C1A466} {93729F4E-5F15-4769-8385-19176C7AAA2C} = {6F3D5F8B-CD73-474C-BA79-D4A17E9F106D} + {AEA6712D-0FAE-4031-8FA7-FDD2A026855A} = {DBAB6D93-7DE4-4A70-89E7-1EE784C1A466} + {0BFE3C36-05D3-4A96-8694-1179FD705162} = {4FA4B555-4BA9-49D0-84B2-1D222E0F4498} + {DC455EE9-99A8-4900-AFBA-661E5EFCE50F} = {4FA4B555-4BA9-49D0-84B2-1D222E0F4498} + {799531B7-02FD-4ED9-BE76-6BFCB083D780} = {4FA4B555-4BA9-49D0-84B2-1D222E0F4498} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D5ADD1E2-61BB-4575-B5D8-78FB549A4DBC} EndGlobalSection EndGlobal diff --git a/Directory.Build.props b/Directory.Build.props index c152dcd..75adf20 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -12,7 +12,10 @@ https://github.com/BcryptNet/bcrypt.net/ true MIT + readme.md bcrypt;BCrypt.Net;cryptography;hashing;password;security,hash;crypto;blowfish;gdpr + logo.png + readme.md diff --git a/Directory.Packages.props b/Directory.Packages.props index 1dacc93..8b2467f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,28 +4,34 @@ false - - - - + + + + + + + - - - - - - - - + + + + + + + + + + + - + - - - - + + + + diff --git a/Dockerfile b/Dockerfile index 1b0a00f..d5d483b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ -FROM mcr.microsoft.com/dotnet/sdk:9.0 AS test +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS test WORKDIR /src COPY ./ ./ #RUN dotnet test BCrypt.Net.UnitTests -RUN dotnet build BCrypt.Net.sln -c Benchmark --framework net9.0 -RUN dotnet run --project Benchmark/Benchmark.csproj -c Benchmark --framework net9.0 \ No newline at end of file +RUN dotnet build BCrypt.Net.sln -c Benchmark --framework net10.0 +RUN dotnet run --project Benchmark/Benchmark.csproj -c Benchmark --framework net10.0 \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e7c9301 --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +#!/usr/bin/make -f +#SHELL:=/bin/bash + +default: help +help: # Show help for each of the Makefile recipes. + @grep -E '^[a-zA-Z0-9 -]+:.*#' Makefile | sort | while read -r l; do printf "\033[1;32m$$(echo $$l | cut -f 1 -d':')\033[00m:$$(echo $$l | cut -f 2- -d'#')\n"; done + +.PHONY: all +all: + @echo "Usage: make [target]" + @exit 0 + +.PHONY: ci-test +ci-test: ## Run tests in CI with code coverage + @dotnet run --configuration Release --coverage --coverage-output-format cobertura --report-github + +.PHONY: build-alpha-package +build-alpha-package: # builds a package with a pre-release version suffix + @dotnet pack src/BCrypt.Net/BCrypt.Net.csproj --version-suffix 5.0.0-alpha -o artifacts + +.PHONY: build-package +build-package: # builds a package with the version from the project file + @dotnet pack src/BCrypt.Net/BCrypt.Net.csproj -o artifacts + +.PHONY: benchmarks +benchmarks: + $(MAKE) -C src/benchmarks/ \ No newline at end of file diff --git a/bcrypt.pub b/bcrypt.pub index 400f3b4..21ce884 100644 Binary files a/bcrypt.pub and b/bcrypt.pub differ diff --git a/bcrypt.snk b/bcrypt.snk new file mode 100644 index 0000000..400f3b4 Binary files /dev/null and b/bcrypt.snk differ diff --git a/benchmark/InterrogateHashBenchmarks.cs b/benchmark/InterrogateHashBenchmarks.cs deleted file mode 100644 index 5660483..0000000 --- a/benchmark/InterrogateHashBenchmarks.cs +++ /dev/null @@ -1,32 +0,0 @@ -using BCryptNet.BenchMarks._3._2._1; -using BCryptNet.BenchMarks.HashParser; -using BenchmarkDotNet.Attributes; - -#pragma warning disable 1591 - -namespace BCryptNet.BenchMarks -{ - [MemoryDiagnoser] - [RPlotExporter, RankColumn] - [KeepBenchmarkFiles] - public class InterrogateHashBenchmarks - { - - [Benchmark(Baseline = true)] - [Arguments("$2a$12$k42ZFHFWqBp3vWli.nIn8uYyIkbvYRvodzbfbK18SSsY.CsIQPlxO")] - [Arguments( "$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq")] - public void InterrogateHashUsingRegex(string hash) - { - BaseLine.BCrypt.InterrogateHash(hash); - } - - [Benchmark()] - [Arguments("$2a$12$k42ZFHFWqBp3vWli.nIn8uYyIkbvYRvodzbfbK18SSsY.CsIQPlxO")] - [Arguments( "$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq")] - public void InterrogateHashUsingParser(string hash) - { - Decoder.GetHashInformation(hash); - } - - } -} diff --git a/benchmark/Program.cs b/benchmark/Program.cs deleted file mode 100644 index 8d44a48..0000000 --- a/benchmark/Program.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Environments; -using BenchmarkDotNet.Jobs; -using BenchmarkDotNet.Running; -using BenchmarkDotNet.Validators; - -#pragma warning disable 1591 - -namespace BCryptNet.BenchMarks -{ - class Program - { - static void Main(string[] args) - { - #if DEBUG - BenchmarkRunner.Run(new DebugInProcessConfig().AddValidator(ExecutionValidator.FailOnError)); - // BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly) - // .Run(args, new DebugInProcessConfig() - // // .With(Job.Default.With(CoreRuntime.Latest)) - // // .With(Job.Default.With(ClrRuntime.Net48)) - // .AddValidator(ExecutionValidator.FailOnError)); - #else - var config = DefaultConfig.Instance - .With(Job.Default.With(CoreRuntime.Core60)) - .With(Job.Default.With(ClrRuntime.Net48)); - // BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config); - - BenchmarkRunner.Run(config); - BenchmarkRunner.Run(config); - - // Tests for testing in isolation - BenchmarkRunner.Run(config); - BenchmarkRunner.Run(config); - BenchmarkRunner.Run(config); - BenchmarkRunner.Run(config); - #endif - - } - } -} diff --git a/benchmark/TestB64Decoder.cs b/benchmark/TestB64Decoder.cs deleted file mode 100644 index 6c3ac58..0000000 --- a/benchmark/TestB64Decoder.cs +++ /dev/null @@ -1,58 +0,0 @@ -using BCryptNet.BenchMarks.DecodeB64; -using BenchmarkDotNet.Attributes; - -#pragma warning disable 1591 - -namespace BCryptNet.BenchMarks -{ - [MemoryDiagnoser] - [RPlotExporter, RankColumn] - [KeepBenchmarkFiles] - public class TestB64Decoder - { - - [Benchmark(Baseline = true)] - [Arguments("DCq7YPn5Rq63x1Lad4cll.")] - [Arguments("HqWuK6/Ng6sg9gQzbLrgb.")] - public byte[] DecodeBase64StandardUnSized(string salt) - { - return DecodeB64Methods.DecodeBase64StandardUnSized(salt, 16); - } - - [Benchmark] - [Arguments("DCq7YPn5Rq63x1Lad4cll.")] - [Arguments("HqWuK6/Ng6sg9gQzbLrgb.")] - public byte[] DecodeBase64StandardSized(string salt) - { - return DecodeB64Methods.DecodeBase64StandardSized(salt, 16); - } - -#if !NETFRAMEWORK - [Benchmark] - [Arguments("DCq7YPn5Rq63x1Lad4cll.")] - [Arguments("HqWuK6/Ng6sg9gQzbLrgb.")] - public byte[] DecodeBase64StringCreateSpan(string salt) - { - return DecodeB64Methods.DecodeBase64StringCreateSpan(salt, 16); - } -#else - [Benchmark(Description = "Deliberately Ignore")] - [Arguments("DCq7YPn5Rq63x1Lad4cll.")] - [Arguments("HqWuK6/Ng6sg9gQzbLrgb.")] - public byte[] DecodeBase64StringCreateSpan(string salt) - { - // Deliberately empty https://github.com/dotnet/BenchmarkDotNet/issues/1863#issuecomment-988288587 - return null; - } -#endif - - [Benchmark] - [Arguments("DCq7YPn5Rq63x1Lad4cll.")] - [Arguments("HqWuK6/Ng6sg9gQzbLrgb.")] - public byte[] DecodeBase64ToBytes(string salt) - { - return DecodeB64Methods.DecodeBase64ToBytes(salt, 16); - } - - } -} diff --git a/benchmark/TestB64Encoder.cs b/benchmark/TestB64Encoder.cs deleted file mode 100644 index 5a2f3a7..0000000 --- a/benchmark/TestB64Encoder.cs +++ /dev/null @@ -1,34 +0,0 @@ -using BCryptNet.BenchMarks._3._2._1; -using BCryptNet.BenchMarks.EncodeB64; -using BenchmarkDotNet.Attributes; - -#pragma warning disable 1591 - -namespace BCryptNet.BenchMarks -{ - [MemoryDiagnoser] - [RPlotExporter, RankColumn] - [KeepBenchmarkFiles] - public class TestB64Encoder - { - private static readonly byte[] SaltBytes = BaseLine.BCrypt.DecodeBase64("sGBxdT2q8Qd84NyZEkwTY.", 16); - - [Benchmark(Baseline = true)] - public void EncodeBase64Unsized() - { - var decoded = EncodeB64Methods.EncodeBase64Unsized(SaltBytes, 16); - } - - [Benchmark] - public void EncodeBase64Sized() - { - var decoded = EncodeB64Methods.EncodeBase64Sized(SaltBytes, 16); - } - - [Benchmark] - public void EncodeBase64AsBytes() - { - var decoded = EncodeB64Methods.EncodeBase64AsBytes(SaltBytes, 16); - } - } -} diff --git a/benchmark/TestBcrypt_Hashing.cs b/benchmark/TestBcrypt_Hashing.cs deleted file mode 100644 index 888724b..0000000 --- a/benchmark/TestBcrypt_Hashing.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System.Collections.Generic; -using BCryptNet.BenchMarks._3._2._1; -using BCryptNet.BenchMarks._3._5.perfmerge_1; -using BCryptNet.BenchMarks._4._0._0; -using BenchmarkDotNet.Attributes; - -#pragma warning disable 1591 - -namespace BCryptNet.BenchMarks -{ - [MemoryDiagnoser] - [RPlotExporter, RankColumn] - [KeepBenchmarkFiles] - public class TestBcrypt_Hashing - { - public IEnumerable Data() - { - yield return new object[] { "", "$2a$06$DCq7YPn5Rq63x1Lad4cll.", "$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s." }; - yield return new object[] { "", "$2a$08$HqWuK6/Ng6sg9gQzbLrgb.", "$2a$08$HqWuK6/Ng6sg9gQzbLrgb.Tl.ZHfXLhvt/SgVyWhQqgqcZ7ZuUtye" }; - yield return new object[] { "", "$2a$10$k1wbIrmNyFAPwPVPSVa/ze", "$2a$10$k1wbIrmNyFAPwPVPSVa/zecw2BCEnBwVS2GbrmgzxFUOqW9dk4TCW" }; - yield return new object[] { "", "$2a$12$k42ZFHFWqBp3vWli.nIn8u", "$2a$12$k42ZFHFWqBp3vWli.nIn8uYyIkbvYRvodzbfbK18SSsY.CsIQPlxO" }; - yield return new object[] { "a", "$2a$06$m0CrhHm10qJ3lXRY.5zDGO", "$2a$06$m0CrhHm10qJ3lXRY.5zDGO3rS2KdeeWLuGmsfGlMfOxih58VYVfxe" }; - yield return new object[] { "a", "$2a$08$cfcvVd2aQ8CMvoMpP2EBfe", "$2a$08$cfcvVd2aQ8CMvoMpP2EBfeodLEkkFJ9umNEfPD18.hUF62qqlC/V." }; - yield return new object[] { "a", "$2a$10$k87L/MF28Q673VKh8/cPi.", "$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u" }; - yield return new object[] { "a", "$2a$12$8NJH3LsPrANStV6XtBakCe", "$2a$12$8NJH3LsPrANStV6XtBakCez0cKHXVxmvxIlcz785vxAIZrihHZpeS" }; - yield return new object[] { "abc", "$2a$06$If6bvum7DFjUnE9p2uDeDu", "$2a$06$If6bvum7DFjUnE9p2uDeDu0YHzrHM6tf.iqN8.yx.jNN1ILEf7h0i" }; - yield return new object[] { "abc", "$2a$08$Ro0CUfOqk6cXEKf3dyaM7O", "$2a$08$Ro0CUfOqk6cXEKf3dyaM7OhSCvnwM9s4wIX9JeLapehKK5YdLxKcm" }; - yield return new object[] { "abc", "$2a$10$WvvTPHKwdBJ3uk0Z37EMR.", "$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi" }; - yield return new object[] { "abc", "$2a$12$EXRkfkdmXn2gzds2SSitu.", "$2a$12$EXRkfkdmXn2gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q" }; - yield return new object[] { "abcdefghijklmnopqrstuvwxyz", "$2a$06$.rCVZVOThsIa97pEDOxvGu", "$2a$06$.rCVZVOThsIa97pEDOxvGuRRgzG64bvtJ0938xuqzv18d3ZpQhstC" }; - yield return new object[] { "abcdefghijklmnopqrstuvwxyz", "$2a$08$aTsUwsyowQuzRrDqFflhge", "$2a$08$aTsUwsyowQuzRrDqFflhgekJ8d9/7Z3GV3UcgvzQW3J5zMyrTvlz." }; - yield return new object[] { "abcdefghijklmnopqrstuvwxyz", "$2a$10$fVH8e28OQRj9tqiDXs1e1u", "$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq" }; - yield return new object[] { "abcdefghijklmnopqrstuvwxyz", "$2a$12$D4G5f18o7aMMfwasBL7Gpu", "$2a$12$D4G5f18o7aMMfwasBL7GpuQWuP3pkrZrOAnqP.bmezbMng.QwJ/pG" }; - yield return new object[] { "~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2a$06$fPIsBO8qRqkjj273rfaOI.", "$2a$06$fPIsBO8qRqkjj273rfaOI.HtSV9jLDpTbZn782DC6/t7qT67P6FfO" }; - yield return new object[] { "~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2a$08$Eq2r4G/76Wv39MzSX262hu", "$2a$08$Eq2r4G/76Wv39MzSX262huzPz612MZiYHVUJe/OcOql2jo4.9UxTW" }; - yield return new object[] { "~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2a$10$LgfYWkbzEvQ4JakH7rOvHe", "$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS" }; - yield return new object[] { "~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2a$12$WApznUOJfkEGSmYRfnkrPO", "$2a$12$WApznUOJfkEGSmYRfnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC" }; - } - - [Benchmark(Baseline = true)] - [ArgumentsSource(nameof(Data))] - public string TestHashValidate(string key, string salt, string hash) - { - string hashed = BaseLine.BCrypt.HashPassword(key, salt, enhancedEntropy: false); - var validateHashCheck = BaseLine.BCrypt.Verify(key, hashed); - return hashed + validateHashCheck.ToString(); - } - - [Benchmark] - [ArgumentsSource(nameof(Data))] - public string TestHashValidatePerf1(string key, string salt, string hash) - { - string hashed = PerfMerge1.BCrypt.HashPassword(key, salt, enhancedEntropy: false); - var validateHashCheck = PerfMerge1.BCrypt.Verify(key, hashed); - return hashed + validateHashCheck.ToString(); - } - - [Benchmark] - [ArgumentsSource(nameof(Data))] - public string TestHashValidateV4(string key, string salt, string hash) - { - string hashed = version4.BCrypt.HashPassword(key, salt); - var validateHashCheck = BCrypt.Verify(key, hashed); - return hashed + validateHashCheck.ToString(); - } - - [Benchmark] - [ArgumentsSource(nameof(Data))] - public string TestHashValidateCurrent(string key, string salt, string hash) - { - string hashed = BCrypt.HashPassword(key, salt); - var validateHashCheck = BCrypt.Verify(key, hashed); - return hashed + validateHashCheck.ToString(); - } - - } -} diff --git a/benchmark/TestBcrypt_Hashing_Enhanced.cs b/benchmark/TestBcrypt_Hashing_Enhanced.cs deleted file mode 100644 index 489fae3..0000000 --- a/benchmark/TestBcrypt_Hashing_Enhanced.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Collections.Generic; -using BCryptNet.BenchMarks._3._2._1; -using BCryptNet.BenchMarks._3._5.perfmerge_1; -using BCryptNet.BenchMarks._4._0._0; -using BenchmarkDotNet.Attributes; - -#pragma warning disable 1591 - -namespace BCryptNet.BenchMarks -{ - [MemoryDiagnoser] - [RPlotExporter, RankColumn] - [KeepBenchmarkFiles] - public class TestBcrypt_Hashing_Enhanced - { - public IEnumerable Data() - { - yield return new object[] { "", "$2a$06$DCq7YPn5Rq63x1Lad4cll.", "$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s." }; - yield return new object[] { "", "$2a$08$HqWuK6/Ng6sg9gQzbLrgb.", "$2a$08$HqWuK6/Ng6sg9gQzbLrgb.Tl.ZHfXLhvt/SgVyWhQqgqcZ7ZuUtye" }; - yield return new object[] { "", "$2a$10$k1wbIrmNyFAPwPVPSVa/ze", "$2a$10$k1wbIrmNyFAPwPVPSVa/zecw2BCEnBwVS2GbrmgzxFUOqW9dk4TCW" }; - yield return new object[] { "", "$2a$12$k42ZFHFWqBp3vWli.nIn8u", "$2a$12$k42ZFHFWqBp3vWli.nIn8uYyIkbvYRvodzbfbK18SSsY.CsIQPlxO" }; - yield return new object[] { "a", "$2a$06$m0CrhHm10qJ3lXRY.5zDGO", "$2a$06$m0CrhHm10qJ3lXRY.5zDGO3rS2KdeeWLuGmsfGlMfOxih58VYVfxe" }; - yield return new object[] { "a", "$2a$08$cfcvVd2aQ8CMvoMpP2EBfe", "$2a$08$cfcvVd2aQ8CMvoMpP2EBfeodLEkkFJ9umNEfPD18.hUF62qqlC/V." }; - yield return new object[] { "a", "$2a$10$k87L/MF28Q673VKh8/cPi.", "$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u" }; - yield return new object[] { "a", "$2a$12$8NJH3LsPrANStV6XtBakCe", "$2a$12$8NJH3LsPrANStV6XtBakCez0cKHXVxmvxIlcz785vxAIZrihHZpeS" }; - yield return new object[] { "abc", "$2a$06$If6bvum7DFjUnE9p2uDeDu", "$2a$06$If6bvum7DFjUnE9p2uDeDu0YHzrHM6tf.iqN8.yx.jNN1ILEf7h0i" }; - yield return new object[] { "abc", "$2a$08$Ro0CUfOqk6cXEKf3dyaM7O", "$2a$08$Ro0CUfOqk6cXEKf3dyaM7OhSCvnwM9s4wIX9JeLapehKK5YdLxKcm" }; - yield return new object[] { "abc", "$2a$10$WvvTPHKwdBJ3uk0Z37EMR.", "$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi" }; - yield return new object[] { "abc", "$2a$12$EXRkfkdmXn2gzds2SSitu.", "$2a$12$EXRkfkdmXn2gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q" }; - yield return new object[] { "abcdefghijklmnopqrstuvwxyz", "$2a$06$.rCVZVOThsIa97pEDOxvGu", "$2a$06$.rCVZVOThsIa97pEDOxvGuRRgzG64bvtJ0938xuqzv18d3ZpQhstC" }; - yield return new object[] { "abcdefghijklmnopqrstuvwxyz", "$2a$08$aTsUwsyowQuzRrDqFflhge", "$2a$08$aTsUwsyowQuzRrDqFflhgekJ8d9/7Z3GV3UcgvzQW3J5zMyrTvlz." }; - yield return new object[] { "abcdefghijklmnopqrstuvwxyz", "$2a$10$fVH8e28OQRj9tqiDXs1e1u", "$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq" }; - yield return new object[] { "abcdefghijklmnopqrstuvwxyz", "$2a$12$D4G5f18o7aMMfwasBL7Gpu", "$2a$12$D4G5f18o7aMMfwasBL7GpuQWuP3pkrZrOAnqP.bmezbMng.QwJ/pG" }; - yield return new object[] { "~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2a$06$fPIsBO8qRqkjj273rfaOI.", "$2a$06$fPIsBO8qRqkjj273rfaOI.HtSV9jLDpTbZn782DC6/t7qT67P6FfO" }; - yield return new object[] { "~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2a$08$Eq2r4G/76Wv39MzSX262hu", "$2a$08$Eq2r4G/76Wv39MzSX262huzPz612MZiYHVUJe/OcOql2jo4.9UxTW" }; - yield return new object[] { "~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2a$10$LgfYWkbzEvQ4JakH7rOvHe", "$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS" }; - yield return new object[] { "~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2a$12$WApznUOJfkEGSmYRfnkrPO", "$2a$12$WApznUOJfkEGSmYRfnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC" }; - } - - [Benchmark(Baseline = true)] - [ArgumentsSource(nameof(Data))] - public string TestHashValidateEnhanced(string key, string salt, string hash) - { - string hashed = BaseLine.BCrypt.HashPassword(key, salt, enhancedEntropy: true); - var validateHashCheck = BaseLine.BCrypt.EnhancedVerify(key, hashed); - return hashed + validateHashCheck.ToString(); - } - - [Benchmark] - [ArgumentsSource(nameof(Data))] - public string TestHashValidateEnhancedPerf1(string key, string salt, string hash) - { - string hashed = PerfMerge1.BCrypt.HashPassword(key, salt, enhancedEntropy: true); - var validateHashCheck = PerfMerge1.BCrypt.EnhancedVerify(key, hashed); - return hashed + validateHashCheck.ToString(); - } - - [Benchmark] - [ArgumentsSource(nameof(Data))] - public string TestHashValidateEnhancedv3perf(string key, string salt, string hash) - { - string hashed = PerfMerge1.BCrypt.HashPassword(key, salt, enhancedEntropy: true); - var validateHashCheck = PerfMerge1.BCrypt.EnhancedVerify(key, hashed); - return hashed + validateHashCheck.ToString(); - } - - [Benchmark] - [ArgumentsSource(nameof(Data))] - public string TestHashValidateEnhancedv4perf(string key, string salt, string hash) - { - string hashed = version4.BCrypt.HashPassword(key, salt, enhancedEntropy: true); - var validateHashCheck = version4.BCrypt.EnhancedVerify(key, hashed); - return hashed + validateHashCheck.ToString(); - } - - [Benchmark] - [ArgumentsSource(nameof(Data))] - public string TestHashValidateEnhancedCurrent(string key, string salt, string hash) - { - string hashed = BCryptExtendedV2.HashPassword(key, salt); - var validateHashCheck = BCryptExtendedV2.Verify(key, hashed); - return hashed + validateHashCheck.ToString(); - } - - private static readonly string Hmackey = Guid.NewGuid().ToString(); - - // [Benchmark] - // [ArgumentsSource(nameof(Data))] - // public string TestHashValidateEnhancedNet8Plus(string key, string salt, string hash) - // { - // string hashed = BCryptExtendedV3.HashPassword(Hmackey, key, salt); - // var validateHashCheck = BCryptExtendedV3.Verify(Hmackey, key, hashed); - // return hashed + validateHashCheck.ToString(); - // } - } -} diff --git a/benchmarks/Diagnostic-Benchmarks/BenchmarkCurrent.cs b/benchmarks/Diagnostic-Benchmarks/BenchmarkCurrent.cs new file mode 100644 index 0000000..edd1fb3 --- /dev/null +++ b/benchmarks/Diagnostic-Benchmarks/BenchmarkCurrent.cs @@ -0,0 +1,41 @@ +// /* +// The MIT License (MIT) +// Copyright (c) 2006 Damien Miller djm@mindrot.org (jBCrypt) +// Copyright (c) 2013 Ryan D. Emerle (.Net port) +// Copyright (c) 2016 Chris McKee (.Net-core port / patches / new features) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// */ + +using System; +using BenchmarkDotNet.Attributes; +using Microsoft.VSDiagnostics; +using BCryptNet; + +namespace BenchmarkCurrent +{ + [CPUUsageDiagnoser] + [MemoryDiagnoser] + public class BenchmarkCurrent + { + [Benchmark] + [Arguments("~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2a$12$WApznUOJfkEGSmYRfnkrPO")] + [Arguments("password123", "$2a$10$LgfYWkbzEvQ4JakH7rOvHe")] + public int CreatePasswordHash_Core(string key, string salt) + { + Span outputBuffer = stackalloc char[60]; + BCryptCore.CreatePasswordHash(key.AsSpan(), salt.AsSpan(), outputBuffer, out int written); + return written; + } + } +} diff --git a/benchmarks/Diagnostic-Benchmarks/Diagnostic-Benchmarks.csproj b/benchmarks/Diagnostic-Benchmarks/Diagnostic-Benchmarks.csproj new file mode 100644 index 0000000..b380212 --- /dev/null +++ b/benchmarks/Diagnostic-Benchmarks/Diagnostic-Benchmarks.csproj @@ -0,0 +1,38 @@ + + + BenchmarkCurrent + BenchmarkCurrent + Exe + net10.0 + true + Release;Debug + AnyCPU + + false + portable + false + false + latest + + + + NU5105;NU1507;CS1591;CS0618 + + + true + NU5105;NU1507;CS1591;CS0618 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/benchmarks/Diagnostic-Benchmarks/Program.cs b/benchmarks/Diagnostic-Benchmarks/Program.cs new file mode 100644 index 0000000..70322bb --- /dev/null +++ b/benchmarks/Diagnostic-Benchmarks/Program.cs @@ -0,0 +1,31 @@ +// /* +// The MIT License (MIT) +// Copyright (c) 2006 Damien Miller djm@mindrot.org (jBCrypt) +// Copyright (c) 2013 Ryan D. Emerle (.Net port) +// Copyright (c) 2016 Chris McKee (.Net-core port / patches / new features) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// */ + +using BenchmarkDotNet.Running; + +namespace BenchmarkCurrent +{ + internal class Program + { + static void Main(string[] args) + { + var _ = BenchmarkRunner.Run(typeof(Program).Assembly); + } + } +} diff --git a/benchmarks/Directory.Build.props b/benchmarks/Directory.Build.props new file mode 100644 index 0000000..ddf990a --- /dev/null +++ b/benchmarks/Directory.Build.props @@ -0,0 +1,21 @@ + + + true + False + false + false + false + false + + + + true + ..\..\bcrypt.snk + true + + + + + + + diff --git a/benchmarks/Makefile b/benchmarks/Makefile new file mode 100644 index 0000000..ecf9802 --- /dev/null +++ b/benchmarks/Makefile @@ -0,0 +1,23 @@ +#!/usr/bin/make -f +#SHELL:=/bin/bash + +default: help +help: # Show help for each of the Makefile recipes. + @grep -E '^[a-zA-Z0-9 -]+:.*#' Makefile | sort | while read -r l; do printf "\033[1;32m$$(echo $$l | cut -f 1 -d':')\033[00m:$$(echo $$l | cut -f 2- -d'#')\n"; done + +.PHONY: all +all: + @echo "Usage: make [target]" + @exit 0 + +.PHONY: run-release-benchmark +run-release-benchmark: # compares nuget releases of this package + @dotnet run --configuration Benchmarks --project benchmarks/Release-Benchmarks/Release-Benchmarks.csproj + +.PHONY: run-method-benchmarks +run-method-benchmarks: # compares methods against older versions of this package + @dotnet run --configuration Benchmarks --project benchmarks/Method-Benchmarks/Method-Benchmarks.csproj + +.PHONY: run-diagnostic-benchmarks +run-diagnostic-benchmarks: # benchmarks the main entry point with vs diagnostics enabled + @dotnet run --configuration Benchmarks --project benchmarks/Diagnostic-Benchmarks/Diagnostic-Benchmarks.csproj diff --git a/benchmark/3.2.1/BaseLine.cs b/benchmarks/Method-Benchmarks/3.2.1/BaseLine.cs similarity index 99% rename from benchmark/3.2.1/BaseLine.cs rename to benchmarks/Method-Benchmarks/3.2.1/BaseLine.cs index 86fef14..88c21bc 100644 --- a/benchmark/3.2.1/BaseLine.cs +++ b/benchmarks/Method-Benchmarks/3.2.1/BaseLine.cs @@ -7,7 +7,7 @@ // BASE namespace BCryptNet.BenchMarks._3._2._1 { - public class BaseLine + public class BCryptBaseLine { [Serializable] @@ -852,7 +852,7 @@ public static string GenerateSalt() /// hashing /// /// The text to verify. - /// The previously-hashed password. + /// The previously hashed password. /// HashType used (default SHA384) /// true if the passwords match, false otherwise. public static bool EnhancedVerify(string text, string hash, HashType hashType = DefaultEnhancedHashType) => @@ -863,7 +863,7 @@ public static bool EnhancedVerify(string text, string hash, HashType hashType = /// /// /// The text to verify. - /// The previously-hashed password. + /// The previously hashed password. /// /// Set to true,the string will undergo SHA384 hashing to make use of available entropy prior /// to bcrypt hashing diff --git a/benchmark/3.5.perfmerge_1/PerfMerge1.cs b/benchmarks/Method-Benchmarks/3.5.perfmerge_1/PerfMerge1.cs similarity index 99% rename from benchmark/3.5.perfmerge_1/PerfMerge1.cs rename to benchmarks/Method-Benchmarks/3.5.perfmerge_1/PerfMerge1.cs index 11b5825..1e0dbd8 100644 --- a/benchmark/3.5.perfmerge_1/PerfMerge1.cs +++ b/benchmarks/Method-Benchmarks/3.5.perfmerge_1/PerfMerge1.cs @@ -6,7 +6,7 @@ // BASE namespace BCryptNet.BenchMarks._3._5.perfmerge_1 { - public class PerfMerge1 + public class BCrypt305PerfMerge1 { /// BCrypt implementation. /// @@ -769,7 +769,7 @@ public static string GenerateSalt() /// ; the string will undergo SHA384 hashing to maintain the enhanced entropy work done during hashing /// /// The text to verify. - /// The previously-hashed password. + /// The previously hashed password. /// HashType used (default SHA384) /// true if the passwords match, false otherwise. public static bool EnhancedVerify(string text, string hash, HashType hashType = DefaultEnhancedHashType) => Verify(text, hash, true, hashType); @@ -779,7 +779,7 @@ public static string GenerateSalt() /// /// /// The text to verify. - /// The previously-hashed password. + /// The previously hashed password. /// Set to true,the string will undergo SHA384 hashing to make use of available entropy prior to bcrypt hashing /// HashType used (default SHA384) /// true if the passwords match, false otherwise. diff --git a/benchmark/4.0.0/version4.cs b/benchmarks/Method-Benchmarks/4.0.0/version4.cs similarity index 99% rename from benchmark/4.0.0/version4.cs rename to benchmarks/Method-Benchmarks/4.0.0/version4.cs index 49ccc9d..5eb6e04 100644 --- a/benchmark/4.0.0/version4.cs +++ b/benchmarks/Method-Benchmarks/4.0.0/version4.cs @@ -6,7 +6,7 @@ // BASE namespace BCryptNet.BenchMarks._4._0._0 { - public class version4 + internal class BCryptV4 { /// BCrypt implementation. /// @@ -769,7 +769,7 @@ public static string GenerateSalt() /// ; the string will undergo SHA384 hashing to maintain the enhanced entropy work done during hashing /// /// The text to verify. - /// The previously-hashed password. + /// The previously hashed password. /// HashType used (default SHA384) /// true if the passwords match, false otherwise. public static bool EnhancedVerify(string text, string hash, HashType hashType = DefaultEnhancedHashType) => Verify(text, hash, true, hashType); @@ -779,7 +779,7 @@ public static string GenerateSalt() /// /// /// The text to verify. - /// The previously-hashed password. + /// The previously hashed password. /// Set to true,the string will undergo SHA384 hashing to make use of available entropy prior to bcrypt hashing /// HashType used (default SHA384) /// true if the passwords match, false otherwise. diff --git a/benchmarks/Method-Benchmarks/4.0.3/BCrypt403.cs b/benchmarks/Method-Benchmarks/4.0.3/BCrypt403.cs new file mode 100644 index 0000000..6b7dd18 --- /dev/null +++ b/benchmarks/Method-Benchmarks/4.0.3/BCrypt403.cs @@ -0,0 +1,1230 @@ +// /* +// The MIT License (MIT) +// Copyright (c) 2006 Damien Miller djm@mindrot.org (jBCrypt) +// Copyright (c) 2013 Ryan D. Emerle (.Net port) +// Copyright (c) 2016 Chris McKee (.Net-core port / patches / new features) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// */ + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; +using System.Text; + +namespace BCryptNet.BenchMarks._4._0._3 +{ + internal class BCryptV403 + { + /// BCrypt implementation. + /// + /// + /// BCrypt implements OpenBSD-style Blowfish password hashing using the scheme described in + /// "A Future- + /// Adaptable Password Scheme" by Niels Provos and David Mazieres. + /// + /// + /// This password hashing system tries to thwart off-line password cracking using a + /// computationally-intensive hashing algorithm, based on Bruce Schneier's Blowfish cipher. + /// The work factor of the algorithm is parameterised, so it can be increased as computers + /// get faster. + /// + /// + /// To hash a password using the defaults, call the (which will generate a random salt and hash at default cost), like this: + /// + /// string pw_hash = BCrypt.HashPassword(plain_password); + /// + /// To hash a password using SHA384 pre-hashing for increased entropy call + /// (which will generate a random salt and hash at default cost), like this: + /// + /// string pw_hash = BCrypt.EnhancedHashPassword(plain_password); + /// + /// To check whether a plaintext password matches one that has been hashed previously, + /// use the method: + /// (To validate an enhanced hash you can pass true as the last parameter of Verify or use ) + /// + /// + /// if (BCrypt.Verify(candidate_password, stored_hash)) + /// Console.WriteLine("It matches"); + /// else + /// Console.WriteLine("It does not match"); + /// + /// + /// The method takes an optional parameter (workFactor) that + /// determines the computational complexity of the hashing: + /// + /// + /// string strong_salt = BCrypt.GenerateSalt(10); + /// string stronger_salt = BCrypt.GenerateSalt(12); + /// + /// + /// The amount of work increases exponentially (2^workFactor), so each increment is twice + /// as much work. The default workFactor is 10, and the valid range is 4 to 31. + /// + /// + internal sealed class BCrypt + { + // BCrypt parameters + /// + /// Default Work Factor + /// + private const int DefaultRounds = 11; + + private const int BCryptSaltLen = 128 / 8; // 128 bits + + private static readonly Encoding SafeUTF8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); + + private const HashType DefaultEnhancedHashType = HashType.SHA384; + + // Blowfish parameters + private const int BlowfishNumRounds = 16; + + /// + /// RandomNumberGenerator.Create calls RandomNumberGenerator.Create("System.Security.Cryptography.RandomNumberGenerator"), which will create an instance of RNGCryptoServiceProvider. + /// https://msdn.microsoft.com/en-us/library/42ks8fz1 + /// + private static readonly RandomNumberGenerator RngCsp = RandomNumberGenerator.Create(); // secure PRNG + + #region Initial contents of key schedule + + private static readonly uint[] POrig = + { + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b + }; + + private static readonly uint[] SOrig = + { + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658, + 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, + 0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176, + 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, + 0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8, + 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, + 0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9, + 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a, 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, 0x193602a5, 0x75094c29, + 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, + 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, 0xd19113f9, 0x7ca92ff6, + 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, + 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, 0xec7aec3a, 0xdb851dfa, + 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, + 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, 0xc6150eba, 0x94e2ea78, + 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, + 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, 0xeecc86bc, 0x60622ca7, + 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, + 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, 0x5d4a14d9, 0xe864b7e3, + 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, + 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, 0x7cde3759, 0xcbee7460, + 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, + 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7, 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, + 0x43b7d4b7, 0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, + 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, + 0xee39d7ab, 0x3b124e8b, 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, + 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, + 0x404779a4, 0x5d886e17, 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, + 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, + 0x991be14c, 0xdb6e6b0d, 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, + 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, + 0xd90cec6e, 0xd5abea2a, 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, + 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, + 0x8cd55591, 0xc902de4c, 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, + 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, + 0x53c2dd94, 0xc2c21634, 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, + 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0, 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, + 0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1, + 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, + 0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd, + 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, + 0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b, + 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, + 0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 + }; + + #endregion Initial contents of key schedule + + // BCrypt IV: "OrpheanBeholderScryDoubt" + private static readonly uint[] BfCryptCiphertext = { 0x4f727068, 0x65616e42, 0x65686f6c, 0x64657253, 0x63727944, 0x6f756274 }; + + // Table for Base64 encoding + private static readonly char[] Base64Code = + { + '.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' + }; + + // Table for Base64 decoding + private static readonly int[] Index64 = + { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 54, 55, 56, 57, 58, 59, 60, + 61, 62, 63, -1, -1, -1, -1, -1, -1, -1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, -1, -1, -1, -1, -1, -1, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, -1, -1, -1, -1, -1 + }; + + // Fixed configuration + private const string EmptyString = ""; + private const char DefaultHashVersion = 'a'; + private const string Nul = "\0"; + private const short MinRounds = 4; + private const short MaxRounds = 31; + + + // Expanded Blowfish key + private uint[] _p; + private uint[] _s; + + + /// + /// Validate existing hash and password, + /// + /// Current password / string + /// Current hash to validate password against + /// NEW password / string to be hashed + /// The log2 of the number of rounds of hashing to apply - the work + /// factor therefore increases as 2^workFactor. Default is 11 + /// By default this method will not accept a work factor lower + /// than the one set in the current hash and will set the new work-factor to match. + /// returned if the users hash and current pass doesn't validate + /// returned if the salt is invalid in any way + /// returned if the hash is invalid + /// returned if the user hash is null + /// New hash of new password + public static string ValidateAndReplacePassword(string currentKey, string currentHash, string newKey, int workFactor = DefaultRounds, bool forceWorkFactor = false) => + ValidateAndReplacePassword(currentKey, currentHash, false, HashType.None, newKey, false, HashType.None, workFactor, forceWorkFactor); + + + /// + /// Validate existing hash and password, + /// + /// Current password / string + /// Current hash to validate password against + /// Set to true,the string will undergo SHA384 hashing to make + /// use of available entropy prior to bcrypt hashing + /// HashType used (default SHA384) + /// + /// NEW password / string to be hashed + /// Set to true,the string will undergo SHA384 hashing to make + /// use of available entropy prior to bcrypt hashing + /// HashType to use (default SHA384) + /// The log2 of the number of rounds of hashing to apply - the work + /// factor therefore increases as 2^workFactor. Default is 11 + /// By default this method will not accept a work factor lower + /// than the one set in the current hash and will set the new work-factor to match. + /// returned if the users hash and current pass doesn't validate + /// returned if the salt is invalid in any way + /// returned if the hash is invalid + /// returned if the user hash is null + /// New hash of new password + public static string ValidateAndReplacePassword(string currentKey, string currentHash, bool currentKeyEnhancedEntropy, HashType oldHashType, + string newKey, bool newKeyEnhancedEntropy = false, HashType newHashType = DefaultEnhancedHashType, int workFactor = DefaultRounds, bool forceWorkFactor = false) + { + if (currentKey == null) + { + throw new ArgumentNullException(nameof(currentKey)); + } + + if (string.IsNullOrEmpty(currentHash)) + { + throw new ArgumentException("Invalid Hash", nameof(currentHash)); + } + + if (Verify(currentKey, currentHash, currentKeyEnhancedEntropy, oldHashType)) + { + // Determine the starting offset and validate the salt + int startingOffset; + + if (currentHash[0] != '$' || currentHash[1] != '2') + { + throw new SaltParseException("Invalid bcrypt version"); + } + else if (currentHash[2] == '$') + { + startingOffset = 3; + } + else + { + char minor = currentHash[2]; + if (minor != 'a' && minor != 'b' && minor != 'x' && minor != 'y' || currentHash[3] != '$') + { + throw new SaltParseException("Invalid bcrypt revision"); + } + + startingOffset = 4; + } + + // Extract number of rounds + if (currentHash[startingOffset + 2] > '$') + { + throw new SaltParseException("Missing work factor"); + } + + // Extract details from salt + int currentWorkFactor = Convert.ToInt16(currentHash.Substring(startingOffset, 2)); + + // Throw if log rounds are out of range on hash, deals with custom salts + if (workFactor < 1 || workFactor > 31) + { + throw new SaltParseException("Work factor out of range"); + } + + // Never downgrade workfactor (unless forced) + if (!forceWorkFactor && currentWorkFactor > workFactor) + { + workFactor = currentWorkFactor; + } + + return HashPassword(newKey, GenerateSalt(workFactor), newKeyEnhancedEntropy, newHashType); + } + + throw new BcryptAuthenticationException("Current credentials could not be authenticated"); + } + + /// + /// Hash a string using the OpenBSD BCrypt scheme and a salt generated by . + /// + /// Just an alias for HashPassword. + /// The string to hash. + /// The log2 of the number of rounds of hashing to apply - the work + /// factor therefore increases as 2^workFactor. Default is 11 + /// The hashed string. + /// Thrown when the salt could not be parsed. + [Obsolete("Replace with HashPassword, this method will be removed at a later date")] + public static string HashString(string inputKey, int workFactor = DefaultRounds) => HashPassword(inputKey, GenerateSalt(workFactor)); + + /// + /// Hash a password using the OpenBSD BCrypt scheme and a salt generated by . + /// + /// The password to hash. + /// The hashed password. + /// Thrown when the salt could not be parsed. + public static string HashPassword(string inputKey) => HashPassword(inputKey, GenerateSalt()); + + /// + /// Pre-hash a password with SHA384 then using the OpenBSD BCrypt scheme and a salt generated by . + /// + /// The password to hash. + /// The hashed password. + /// Thrown when the salt could not be parsed. + public static string EnhancedHashPassword(string inputKey) => HashPassword(inputKey, GenerateSalt(), true); + + /// + /// Pre-hash a password with SHA384 then using the OpenBSD BCrypt scheme and a salt generated by . + /// + /// The password to hash. + /// + /// The hashed password. + /// Thrown when the salt could not be parsed. + public static string EnhancedHashPassword(string inputKey, int workFactor) => HashPassword(inputKey, GenerateSalt(workFactor), true); + + /// + /// Pre-hash a password with SHA384 then using the OpenBSD BCrypt scheme and a salt generated by . + /// + /// The password to hash. + /// + /// Configurable hash type for enhanced entropy + /// The hashed password. + /// Thrown when the salt could not be parsed. + public static string EnhancedHashPassword(string inputKey, int workFactor, HashType hashType) => HashPassword(inputKey, GenerateSalt(workFactor), true, hashType); + + + /// + /// Pre-hash a password with SHA384 then using the OpenBSD BCrypt scheme and a salt generated by . + /// + /// The password to hash. + /// Defaults to 11 + /// Configurable hash type for enhanced entropy + /// The hashed password. + /// Thrown when the salt could not be parsed. + public static string EnhancedHashPassword(string inputKey, HashType hashType, int workFactor = DefaultRounds) => HashPassword(inputKey, GenerateSalt(workFactor), true, hashType); + + /// + /// Hash a password using the OpenBSD BCrypt scheme and a salt generated by using the given . + /// + /// The password to hash. + /// The log2 of the number of rounds of hashing to apply - the work + /// factor therefore increases as 2^workFactor. Default is 11 + /// Set to true,the string will undergo SHA384 hashing to make use of available entropy prior to bcrypt hashing + /// The hashed password. + /// Thrown when the salt could not be parsed. + public static string HashPassword(string inputKey, int workFactor, bool enhancedEntropy = false) => HashPassword(inputKey, GenerateSalt(workFactor), enhancedEntropy); + + /// Hash a password using the OpenBSD BCrypt scheme. + /// Thrown when one or more arguments have unsupported or illegal values. + /// The password or string to hash. + /// the salt to hash with (best generated using ). + /// The hashed password + /// Thrown when the could not be parsed. + public static string HashPassword(string inputKey, string salt) => HashPassword(inputKey, salt, false); + + /// Hash a password using the OpenBSD BCrypt scheme. + /// Thrown when one or more arguments have unsupported or illegal values. + /// The password or string to hash. + /// the salt to hash with (best generated using ). + /// Set to true,the string will undergo hashing (defaults to SHA384 then base64 encoding) to make use of available entropy prior to bcrypt hashing + /// Configurable hash type for enhanced entropy + /// The hashed password + /// Thrown when the is null. + /// Thrown when the could not be parsed. + public static string HashPassword(string inputKey, string salt, bool enhancedEntropy, HashType hashType = DefaultEnhancedHashType) + { + if (inputKey == null) + { + throw new ArgumentNullException(nameof(inputKey)); + } + + if (string.IsNullOrEmpty(salt)) + { + throw new ArgumentException("Invalid salt: salt cannot be null or empty", nameof(salt)); + } + + if (enhancedEntropy && hashType == HashType.None) + { + throw new ArgumentException("Invalid HashType, You can't have an enhanced hash with type none. HashType.None is used for internal clarity only.", nameof(hashType)); + } + + // Determine the starting offset and validate the salt + int startingOffset; + char bcryptMinorRevision = (char)0; + if (salt[0] != '$' || salt[1] != '2') + { + throw new SaltParseException("Invalid salt version"); + } + else if (salt[2] == '$') + { + startingOffset = 3; + } + else + { + bcryptMinorRevision = salt[2]; + if (bcryptMinorRevision != 'a' && bcryptMinorRevision != 'b' && bcryptMinorRevision != 'x' && bcryptMinorRevision != 'y' || salt[3] != '$') + { + throw new SaltParseException("Invalid salt revision"); + } + + startingOffset = 4; + } + + // Extract number of rounds + if (salt[startingOffset + 2] > '$') + { + throw new SaltParseException("Missing salt rounds"); + } + + // Extract details from salt + int workFactor = Convert.ToInt16(salt.Substring(startingOffset, 2)); + + // Throw if log rounds are out of range on hash, deals with custom salts + if (workFactor < 1 || workFactor > 31) + { + throw new SaltParseException("Salt rounds out of range"); + } + + string extractedSalt = salt.Substring(startingOffset + 3, 22); + + byte[] inputBytes; + + if (enhancedEntropy) + { + inputBytes = EnhancedHash(SafeUTF8.GetBytes(inputKey), bcryptMinorRevision, hashType); + } + else + { + inputBytes = SafeUTF8.GetBytes(inputKey + (bcryptMinorRevision >= 'a' ? Nul : EmptyString)); + } + + byte[] saltBytes = DecodeBase64(extractedSalt, BCryptSaltLen); + + BCrypt bCrypt = new BCrypt(); + + byte[] hashed = bCrypt.CryptRaw(inputBytes, saltBytes, workFactor); + + // Generate result string + var result = new StringBuilder(60); + result.Append("$2").Append(bcryptMinorRevision).Append('$').Append(workFactor.ToString("D2")).Append('$'); + result.Append(EncodeBase64(saltBytes, saltBytes.Length)); + result.Append(EncodeBase64(hashed, (BfCryptCiphertext.Length * 4) - 1)); + + return result.ToString(); + } + + /// + /// Hashes key, base64 encodes before returning byte array + /// + /// + /// + /// + /// + private static byte[] EnhancedHash(byte[] inputBytes, char bcryptMinorRevision, HashType hashType) + { + switch (hashType) + { + case HashType.SHA256: + using (var sha = SHA256.Create()) + inputBytes = SafeUTF8.GetBytes(Convert.ToBase64String(sha.ComputeHash(inputBytes)) + (bcryptMinorRevision >= 'a' ? Nul : EmptyString)); + break; + case HashType.SHA384: + using (var sha = SHA384.Create()) + inputBytes = SafeUTF8.GetBytes(Convert.ToBase64String(sha.ComputeHash(inputBytes)) + (bcryptMinorRevision >= 'a' ? Nul : EmptyString)); + break; + case HashType.SHA512: + using (var sha = SHA512.Create()) + inputBytes = SafeUTF8.GetBytes(Convert.ToBase64String(sha.ComputeHash(inputBytes)) + (bcryptMinorRevision >= 'a' ? Nul : EmptyString)); + break; + default: + throw new ArgumentOutOfRangeException(nameof(hashType), hashType, null); + } + + return inputBytes; + } + + + /// + /// Generate a salt for use with the method. + /// + /// The log2 of the number of rounds of hashing to apply - the work + /// factor therefore increases as 2**workFactor. + /// + /// Work factor must be between 4 and 31 + /// A base64 encoded salt value. + /// BCrypt Revision should be a, b, x or y + public static string GenerateSalt(int workFactor, char bcryptMinorRevision = DefaultHashVersion) + { + if (workFactor < MinRounds || workFactor > MaxRounds) + { + throw new ArgumentOutOfRangeException(nameof(workFactor), workFactor, $"The work factor must be between {MinRounds} and {MaxRounds} (inclusive)"); + } + + if (bcryptMinorRevision != 'a' && bcryptMinorRevision != 'b' && bcryptMinorRevision != 'x' && bcryptMinorRevision != 'y') + { + throw new ArgumentException("BCrypt Revision should be a, b, x or y", nameof(bcryptMinorRevision)); + } + + byte[] saltBytes = new byte[BCryptSaltLen]; + + RngCsp.GetBytes(saltBytes); + + var result = new StringBuilder(29); + result.Append("$2").Append(bcryptMinorRevision).Append('$').Append(workFactor.ToString("D2")).Append('$'); + result.Append(EncodeBase64(saltBytes, saltBytes.Length)); + + return result.ToString(); + } + + + /// + /// Based on password_needs_rehash in PHP this method will return true + /// if the work factor (logrounds) set on the hash is lower than the new minimum workload passed in + /// + /// full bcrypt hash + /// target workload + /// true if new work factor is higher than the one in the hash + /// throws if the current hash workload (logrounds) can not be parsed + /// + public static bool PasswordNeedsRehash(string hash, int newMinimumWorkLoad) + { + int currentWorkLoad = HashParser.GetWorkFactor(hash); + + return currentWorkLoad < newMinimumWorkLoad; + } + + /// + /// Takes a valid hash and outputs its component parts + /// + /// + /// + public static HashInformation InterrogateHash(string hash) + { + try + { + return HashParser.GetHashInformation(hash); + } + catch (Exception ex) + { + throw new HashInformationException("Error handling string interrogation", ex); + } + } + + + /// + /// Generate a salt for use with the method + /// selecting a reasonable default for the number of hashing rounds to apply. + /// + /// A base64 encoded salt value. + public static string GenerateSalt() + { + return GenerateSalt(DefaultRounds); + } + + /// + /// Verifies that the hash of the given matches the provided + /// ; the string will undergo SHA384 hashing to maintain the enhanced entropy work done during hashing + /// + /// The text to verify. + /// The previously hashed password. + /// HashType used (default SHA384) + /// true if the passwords match, false otherwise. + public static bool EnhancedVerify(string text, string hash, HashType hashType = DefaultEnhancedHashType) => Verify(text, hash, true, hashType); + + /// + /// Verifies that the hash of the given matches the provided + /// + /// + /// The text to verify. + /// The previously hashed password. + /// Set to true,the string will undergo SHA384 hashing to make use of available entropy prior to bcrypt hashing + /// HashType used (default SHA384) + /// true if the passwords match, false otherwise. + /// Thrown when one or more arguments have unsupported or illegal values. + /// Thrown when the salt could not be parsed. + public static bool Verify(string text, string hash, bool enhancedEntropy = false, HashType hashType = DefaultEnhancedHashType) + { + return SecureEquals(SafeUTF8.GetBytes(hash), SafeUTF8.GetBytes(HashPassword(text, hash, enhancedEntropy, hashType))); + } + + // Compares two byte arrays for equality. The method is specifically written so that the loop is not optimised. + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + private static bool SecureEquals(byte[] a, byte[] b) + { + if (a == null && b == null) + { + return true; + } + + if (a == null || b == null || a.Length != b.Length) + { + return false; + } + + int diff = 0; + for (var i = 0; i < a.Length; i++) + { + diff |= (a[i] ^ b[i]); + } + + return diff == 0; + } + + + /// + /// Encode a byte array using BCrypt's slightly-modified base64 encoding scheme. Note that this + /// is *not* compatible with the standard MIME-base64 encoding. + /// + /// Thrown when one or more arguments have unsupported or + /// illegal values. + /// The byte array to encode. + /// The number of bytes to encode. + /// Base64-encoded string. + internal static char[] EncodeBase64(byte[] byteArray, int length) + { + if (length <= 0 || length > byteArray.Length) + { + throw new ArgumentException("Invalid length", nameof(length)); + } + + int encodedSize = (int)Math.Ceiling((length * 4D) / 3); + char[] encoded = new char[encodedSize]; + + int pos = 0; + int off = 0; + while (off < length) + { + int c1 = byteArray[off++] & 0xff; + encoded[pos++] = Base64Code[(c1 >> 2) & 0x3f]; + c1 = (c1 & 0x03) << 4; + if (off >= length) + { + encoded[pos++] = Base64Code[c1 & 0x3f]; + break; + } + + int c2 = byteArray[off++] & 0xff; + c1 |= (c2 >> 4) & 0x0f; + encoded[pos++] = Base64Code[c1 & 0x3f]; + c1 = (c2 & 0x0f) << 2; + if (off >= length) + { + encoded[pos++] = Base64Code[c1 & 0x3f]; + break; + } + + c2 = byteArray[off++] & 0xff; + c1 |= (c2 >> 6) & 0x03; + encoded[pos++] = Base64Code[c1 & 0x3f]; + encoded[pos++] = Base64Code[c2 & 0x3f]; + } + + return encoded; + } + + /// + /// Decode a string encoded using BCrypt's base64 scheme to a byte array. + /// Note that this is *not* compatible with the standard MIME-base64 encoding. + /// + /// Thrown when one or more arguments have unsupported or + /// illegal values. + /// The string to decode. + /// The maximum bytes to decode. + /// The decoded byte array. + internal static byte[] DecodeBase64(string encodedString, int maximumBytes) + { + int sourceLength = encodedString.Length; + int outputLength = 0; + + if (maximumBytes <= 0) + { + throw new ArgumentException("Invalid maximum bytes value", nameof(maximumBytes)); + } + + byte[] result = new byte[maximumBytes]; + + int position = 0; + while (position < sourceLength - 1 && outputLength < maximumBytes) + { + int c1 = Char64(encodedString[position++]); + int c2 = Char64(encodedString[position++]); + if (c1 == -1 || c2 == -1) + { + break; + } + + result[outputLength] = (byte)((c1 << 2) | ((c2 & 0x30) >> 4)); + if (++outputLength >= maximumBytes || position >= sourceLength) + { + break; + } + + int c3 = Char64(encodedString[position++]); + if (c3 == -1) + { + break; + } + + result[outputLength] = (byte)(((c2 & 0x0f) << 4) | ((c3 & 0x3c) >> 2)); + if (++outputLength >= maximumBytes || position >= sourceLength) + { + break; + } + + int c4 = Char64(encodedString[position++]); + result[outputLength] = (byte)(((c3 & 0x03) << 6) | c4); + + ++outputLength; + } + + return result; + } + + /// + /// Look up the 3 bits base64-encoded by the specified character, range-checking against + /// conversion table. + /// + /// The base64-encoded value. + /// The decoded value of x. + private static int Char64(char character) + { + return character < 0 || character > Index64.Length ? -1 : Index64[character]; + } + + /// Blowfish encipher a single 64-bit block encoded as two 32-bit halves. + /// An array containing the two 32-bit half blocks. + /// The position in the array of the blocks. +#if NETCOREAPP + private void Encipher(Span blockArray, int offset) +#else + private void Encipher(uint[] blockArray, int offset) +#endif + { + uint block = blockArray[offset]; + uint r = blockArray[offset + 1]; + + block ^= _p[0]; + + unchecked + { + uint round; + for (round = 0; round <= BlowfishNumRounds - 2;) + { + // Feistel substitution on left word + uint n = _s[(block >> 24) & 0xff]; + n += _s[0x100 | ((block >> 16) & 0xff)]; + n ^= _s[0x200 | ((block >> 8) & 0xff)]; + n += _s[0x300 | (block & 0xff)]; + r ^= n ^ _p[++round]; + + // Feistel substitution on right word + n = _s[(r >> 24) & 0xff]; + n += _s[0x100 | ((r >> 16) & 0xff)]; + n ^= _s[0x200 | ((r >> 8) & 0xff)]; + n += _s[0x300 | (r & 0xff)]; + block ^= n ^ _p[++round]; + } + + blockArray[offset] = r ^ _p[BlowfishNumRounds + 1]; + blockArray[offset + 1] = block; + } + } + + /// Cyclically extract a word of key material. + /// The string to extract the data from. + /// [in,out] The current offset. + /// The next word of material from data. +#if NETCOREAPP + private static uint StreamToWord(ReadOnlySpan data, ref int offset) +#else + private static uint StreamToWord(byte[] data, ref int offset) +#endif + { + int i; + uint word = 0; + + for (i = 0; i < 4; i++) + { + word = (word << 8) | (uint)(data[offset] & 0xff); + offset = (offset + 1) % data.Length; + } + + return word; + } + + /// Initializes the Blowfish key schedule. + private void InitializeKey() + { + _p = new uint[POrig.Length]; + _s = new uint[SOrig.Length]; + Array.Copy(POrig, _p, POrig.Length); + Array.Copy(SOrig, _s, SOrig.Length); + } + + /// Key the Blowfish cipher. + /// The key byte array. +#if NETCOREAPP + private void Key(ReadOnlySpan keyBytes) +#else + private void Key(byte[] keyBytes) +#endif + { + int i; + int koffp = 0; +#if NETCOREAPP + Span lr = stackalloc uint[2] { 0, 0 }; +#else + uint[] lr = { 0, 0 }; +#endif + int plen = _p.Length, slen = _s.Length; + + for (i = 0; i < plen; i++) + { + _p[i] = _p[i] ^ StreamToWord(keyBytes, ref koffp); + } + + for (i = 0; i < plen; i += 2) + { + Encipher(lr, 0); + _p[i] = lr[0]; + _p[i + 1] = lr[1]; + } + + for (i = 0; i < slen; i += 2) + { + Encipher(lr, 0); + _s[i] = lr[0]; + _s[i + 1] = lr[1]; + } + } + + /// + /// Perform the "enhanced key schedule" step described by Provos and Mazieres in + /// "A Future Adaptable Password Scheme" http://www.openbsd.org/papers/bcrypt-paper.ps. + /// + /// Salt byte array. + /// Input byte array. + // ReSharper disable once InconsistentNaming +#if NETCOREAPP + private void EKSKey(ReadOnlySpan saltBytes, ReadOnlySpan inputBytes) +#else + private void EKSKey(byte[] saltBytes, byte[] inputBytes) +#endif + { + int i; + int passwordOffset = 0; + int saltOffset = 0; +#if NETCOREAPP + Span lr = stackalloc uint[2] { 0, 0 }; +#else + uint[] lr = { 0, 0 }; +#endif + int plen = _p.Length, slen = _s.Length; + + for (i = 0; i < plen; i++) + { + _p[i] = _p[i] ^ StreamToWord(inputBytes, ref passwordOffset); + } + + for (i = 0; i < plen; i += 2) + { + lr[0] ^= StreamToWord(saltBytes, ref saltOffset); + lr[1] ^= StreamToWord(saltBytes, ref saltOffset); + Encipher(lr, 0); + _p[i] = lr[0]; + _p[i + 1] = lr[1]; + } + + for (i = 0; i < slen; i += 2) + { + lr[0] ^= StreamToWord(saltBytes, ref saltOffset); + lr[1] ^= StreamToWord(saltBytes, ref saltOffset); + Encipher(lr, 0); + _s[i] = lr[0]; + _s[i + 1] = lr[1]; + } + } + + /// Perform the central hashing step in the BCrypt scheme. + /// Thrown when one or more arguments have unsupported or + /// illegal values. + /// The input byte array to hash. + /// The salt byte array to hash with. + /// The binary logarithm of the number of rounds of hashing to apply. + /// A byte array containing the hashed result. +#if NETCOREAPP + internal byte[] CryptRaw(ReadOnlySpan inputBytes, ReadOnlySpan saltBytes, int workFactor) +#else + internal byte[] CryptRaw(byte[] inputBytes, byte[] saltBytes, int workFactor) +#endif + { + int i; + int j; + +#if NETCOREAPP + Span cdata = stackalloc uint[BfCryptCiphertext.Length]; + BfCryptCiphertext.CopyTo(cdata); +#else + uint[] cdata = new uint[BfCryptCiphertext.Length]; + Array.Copy(BfCryptCiphertext, cdata, BfCryptCiphertext.Length); +#endif + + int clen = cdata.Length; + + if (workFactor < MinRounds || workFactor > MaxRounds) + { + throw new ArgumentException("Bad number of rounds", nameof(workFactor)); + } + + if (saltBytes.Length != BCryptSaltLen) + { + throw new ArgumentException("Bad salt Length", nameof(saltBytes)); + } + + uint rounds = 1u << workFactor; + + // We overflowed rounds at 31 - added safety check + if (rounds < 1) + { + throw new ArgumentException("Bad number of rounds", nameof(workFactor)); + } + + InitializeKey(); + EKSKey(saltBytes, inputBytes); + + for (i = 0; i != rounds; i++) + { + Key(inputBytes); + Key(saltBytes); + } + + for (i = 0; i < 64; i++) + { + for (j = 0; j < (clen >> 1); j++) + { + Encipher(cdata, j << 1); + } + } + + byte[] ret = new byte[clen * 4]; + for (i = 0, j = 0; i < clen; i++) + { + ret[j++] = (byte)((cdata[i] >> 24) & 0xff); + ret[j++] = (byte)((cdata[i] >> 16) & 0xff); + ret[j++] = (byte)((cdata[i] >> 8) & 0xff); + ret[j++] = (byte)(cdata[i] & 0xff); + } + + return ret; + } + } + + /// + /// HashInformation : A value object that contains the results of interrogating a hash + /// Namely its settings (2a$10 for example); version (2a); workfactor (log rounds), and the raw hash returned + /// + [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Local")] + internal sealed class HashInformation + { + /// Constructor. + /// The message. + /// The message. + /// The message. + /// The message. + internal HashInformation(string settings, string version, string workFactor, string rawHash) + { + Settings = settings; + Version = version; + WorkFactor = workFactor; + RawHash = rawHash; + } + + /// + /// Settings string + /// + public string Settings { get; private set; } + + /// + /// Hash Version + /// + public string Version { get; private set; } + + /// + /// log rounds used / workfactor + /// + public string WorkFactor { get; private set; } + + /// + /// Raw Hash + /// + public string RawHash { get; private set; } + } + + /// + /// Exception for signalling hash validation errors. + [Serializable] + internal class BcryptAuthenticationException : Exception + { + /// + /// Default constructor. + public BcryptAuthenticationException() + { + } + + /// + /// Initializes a new instance of . + /// The message. + public BcryptAuthenticationException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of . + /// The message. + /// The inner exception. + public BcryptAuthenticationException(string message, Exception innerException) + : base(message, innerException) + { + } + } + + [Serializable] + internal sealed class HashInformationException : Exception + { + /// + /// Default Constructor + /// + public HashInformationException() + { + } + + /// + /// Initializes a new instance of . + /// + /// + public HashInformationException(string message) : base(message) + { + } + + /// + /// Initializes a new instance of . + /// + /// + /// + public HashInformationException(string message, Exception innerException) : base(message, innerException) + { + } + } + + internal static class HashParser + { + private static readonly HashFormatDescriptor OldFormatDescriptor = new HashFormatDescriptor(versionLength: 1); + private static readonly HashFormatDescriptor NewFormatDescriptor = new HashFormatDescriptor(versionLength: 2); + + public static HashInformation GetHashInformation(string hash) + { + if (!IsValidHash(hash, out var format)) + { + ThrowInvalidHashFormat(); + } + + return new HashInformation( + hash.Substring(0, format.SettingLength), + hash.Substring(1, format.VersionLength), + hash.Substring(format.WorkfactorOffset, 2), + hash.Substring(format.HashOffset)); + } + + public static int GetWorkFactor(string hash) + { + if (!IsValidHash(hash, out var format)) + { + ThrowInvalidHashFormat(); + } + + int offset = format.WorkfactorOffset; + + return 10 * (hash[offset] - '0') + (hash[offset + 1] - '0'); + } + + internal static bool IsValidHash(string hash, out HashFormatDescriptor format) + { + if (hash is null) + { + throw new ArgumentNullException(nameof(hash)); + } + + if (hash.Length != 59 && hash.Length != 60) + { + // Incorrect full hash length + format = null; + return false; + } + + if (!hash.StartsWith("$2")) + { + // Not a bcrypt hash + format = null; + return false; + } + + // Validate version + int offset = 2; + if (IsValidBCryptVersionChar(hash[offset])) + { + offset++; + format = NewFormatDescriptor; + } + else + { + format = OldFormatDescriptor; + } + + if (hash[offset++] != '$') + { + format = null; + return false; + } + + // Validate workfactor + if (!IsAsciiNumeric(hash[offset++]) + || !IsAsciiNumeric(hash[offset++])) + { + format = null; + return false; + } + + if (hash[offset++] != '$') + { + format = null; + return false; + } + + // Validate hash + for (int i = offset; i < hash.Length; ++i) + { + if (!IsValidBCryptBase64Char(hash[i])) + { + format = null; + return false; + } + } + + return true; + } + + private static bool IsValidBCryptVersionChar(char value) + { + return value == 'a' + || value == 'b' + || value == 'x' + || value == 'y'; + } + + private static bool IsValidBCryptBase64Char(char value) + { + // Ordered by ascending ASCII value + return value == '.' + || value == '/' + || (value >= '0' && value <= '9') + || (value >= 'A' && value <= 'Z') + || (value >= 'a' && value <= 'z'); + } + + private static bool IsAsciiNumeric(char value) + { + return value >= '0' && value <= '9'; + } + + private static void ThrowInvalidHashFormat() + { + throw new SaltParseException("Invalid Hash Format"); + } + + internal class HashFormatDescriptor + { + public HashFormatDescriptor(int versionLength) + { + VersionLength = versionLength; + WorkfactorOffset = 1 + VersionLength + 1; + SettingLength = WorkfactorOffset + 2; + HashOffset = SettingLength + 1; + } + + public int VersionLength { get; } + + public int WorkfactorOffset { get; } + + public int SettingLength { get; } + + public int HashOffset { get; } + } + + internal enum HashType + { + None = -1, + SHA256 = 0, + SHA384 = 1, + SHA512 = 2 + } + } + } +} diff --git a/benchmarks/Method-Benchmarks/B64DecoderBenchmark.cs b/benchmarks/Method-Benchmarks/B64DecoderBenchmark.cs new file mode 100644 index 0000000..ee8724c --- /dev/null +++ b/benchmarks/Method-Benchmarks/B64DecoderBenchmark.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using BCryptNet.BenchMarks.DecodeB64; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Order; + +#pragma warning disable 1591 + +namespace BCryptNet.BenchMarks +{ + [MemoryDiagnoser] + [RPlotExporter] + [RankColumn] + [GcServer(true)] + [Orderer(SummaryOrderPolicy.Declared)] + [KeepBenchmarkFiles] + [MarkdownExporterAttribute.GitHub] + [IterationTime(500)] + public class B64DecoderBenchmark + { +#if NETCOREAPP + public B64DecoderBenchmark() + { + var salt = "DCq7YPn5Rq63x1Lad4cll."; + var original = Convert.ToBase64String(DecodeB64Methods.DecodeBase64StandardUnSized(salt, 16)); + var exceptions = new List(); + + if (!Convert.ToBase64String(DecodeB64Methods.DecodeBase64StandardSized(salt, 16)).Equals(original)) + exceptions.Add(new Exception($"DecodeBase64StandardSized failed: {original} vs {Convert.ToBase64String(DecodeB64Methods.DecodeBase64StandardSized(salt, 16))}")); + if (!Convert.ToBase64String(DecodeB64Methods.DecodeBase64StringCreateSpan(salt, 16)).Equals(original)) + exceptions.Add(new Exception($"DecodeBase64StringCreateSpan failed: {original} vs {Convert.ToBase64String(DecodeB64Methods.DecodeBase64StringCreateSpan(salt, 16))}")); + + if (!Convert.ToBase64String(DecodeB64Methods.DecodeBase64ToBytes(salt, 16)).Equals(original)) + exceptions.Add(new Exception($"DecodeBase64ToBytes failed: {original} vs {Convert.ToBase64String(DecodeB64Methods.DecodeBase64ToBytes(salt, 16))}")); + + Span saltBuffer = stackalloc byte[16]; + int written = DecodeB64Methods.DecodeBase64SpanBuffer(salt, saltBuffer); + if (!Convert.ToBase64String(saltBuffer[..written].ToArray()).Equals(original)) + exceptions.Add(new Exception($"DecodeBase64SpanBuffer failed: {original} vs {Convert.ToBase64String(saltBuffer[..written].ToArray())}")); + + if (exceptions.Count > 0) + throw new AggregateException(exceptions); + } +#else + public B64DecoderBenchmark() + { + var salt = "DCq7YPn5Rq63x1Lad4cll."; + var original = Convert.ToBase64String(DecodeB64Methods.DecodeBase64StandardUnSized(salt, 16)); + var exceptions = new List(); + + if (!Convert.ToBase64String(DecodeB64Methods.DecodeBase64StandardSized(salt, 16)).Equals(original)) + exceptions.Add(new Exception($"DecodeBase64StandardSized failed: {original} vs {Convert.ToBase64String(DecodeB64Methods.DecodeBase64StandardSized(salt, 16))}")); + + if (!Convert.ToBase64String(DecodeB64Methods.DecodeBase64ToBytes(salt, 16)).Equals(original)) + exceptions.Add(new Exception($"DecodeBase64ToBytes failed: {original} vs {Convert.ToBase64String(DecodeB64Methods.DecodeBase64ToBytes(salt, 16))}")); + + byte[] saltBuffer = new byte[16]; + int written = DecodeB64Methods.DecodeBase64SpanBuffer(salt, saltBuffer); + byte[] result = new byte[written]; + Array.Copy(saltBuffer, 0, result, 0, written); + if (!Convert.ToBase64String(result).Equals(original)) + exceptions.Add(new Exception($"DecodeBase64SpanBuffer failed: {original} vs {Convert.ToBase64String(result)}")); + + if (exceptions.Count > 0) + throw new AggregateException(exceptions); + } +#endif + + + [Benchmark(Baseline = true)] + [Arguments("DCq7YPn5Rq63x1Lad4cll.")] + [Arguments("HqWuK6/Ng6sg9gQzbLrgb.")] + public byte[] UnsizedStringBuilderOriginal(string salt) + { + return DecodeB64Methods.DecodeBase64StandardUnSized(salt, 16); + } + + [Benchmark] + [Arguments("DCq7YPn5Rq63x1Lad4cll.")] + [Arguments("HqWuK6/Ng6sg9gQzbLrgb.")] + public byte[] SizedStringBuilderOriginal(string salt) + { + return DecodeB64Methods.DecodeBase64StandardSized(salt, 16); + } + +#if NETCOREAPP + [Benchmark] + [Arguments("DCq7YPn5Rq63x1Lad4cll.")] + [Arguments("HqWuK6/Ng6sg9gQzbLrgb.")] + public byte[] StringCreateWithSpanAndBuffer(string salt) + { + return DecodeB64Methods.DecodeBase64StringCreateSpan(salt, 16); + } +#endif + + [Benchmark] + [Arguments("DCq7YPn5Rq63x1Lad4cll.")] + [Arguments("HqWuK6/Ng6sg9gQzbLrgb.")] + public byte[] DecodeBase64ToBytes(string salt) + { + return DecodeB64Methods.DecodeBase64ToBytes(salt, 16); + } + +#if NETCOREAPP + [Benchmark] + [Arguments("DCq7YPn5Rq63x1Lad4cll.")] + [Arguments("HqWuK6/Ng6sg9gQzbLrgb.")] + public byte[] DecodeBase64SpanBuffer(string salt) + { + Span saltBuffer = stackalloc byte[16]; + int written = DecodeB64Methods.DecodeBase64SpanBuffer(salt, saltBuffer); + return saltBuffer[..written].ToArray(); + } +#endif + } +} diff --git a/benchmarks/Method-Benchmarks/B64EncoderBenchmark.cs b/benchmarks/Method-Benchmarks/B64EncoderBenchmark.cs new file mode 100644 index 0000000..55aff32 --- /dev/null +++ b/benchmarks/Method-Benchmarks/B64EncoderBenchmark.cs @@ -0,0 +1,51 @@ +using BCryptNet.BenchMarks._3._2._1; +using BCryptNet.BenchMarks.EncodeB64; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Order; + +#pragma warning disable 1591 + +namespace BCryptNet.BenchMarks +{ + [MemoryDiagnoser] + [RPlotExporter][RankColumn] + [GcServer(true)] + [Orderer(SummaryOrderPolicy.Declared)] + [KeepBenchmarkFiles] + [MarkdownExporterAttribute.GitHub] + [IterationTime(500)] + public class B64EncoderBenchmark + { + private static readonly byte[] SaltBytes = BCryptBaseLine.BCrypt.DecodeBase64("sGBxdT2q8Qd84NyZEkwTY.", 16); + + [Benchmark(Baseline = true)] + public void EncodeBase64Unsized() + { + EncodeB64Methods.EncodeBase64Unsized(SaltBytes, 16); + } + + [Benchmark] + public void EncodeBase64Sized() + { + EncodeB64Methods.EncodeBase64Sized(SaltBytes, 16); + } + + [Benchmark] + public void EncodeBase64AsBytes() + { + EncodeB64Methods.EncodeBase64AsBytes(SaltBytes, 16); + } + + [Benchmark] + public void EncodeBase64StackAlloc() + { + EncodeB64Methods.EncodeBase64StackAlloc(SaltBytes, 16); + } + + [Benchmark] + public void EncodeBase64HeapSpanAlloc() + { + EncodeB64Methods.EncodeBase64HeapAlloc(SaltBytes, 16); + } + } +} diff --git a/benchmark/DecodeB64/Decoder.cs b/benchmarks/Method-Benchmarks/DecodeB64/Decoder.cs similarity index 86% rename from benchmark/DecodeB64/Decoder.cs rename to benchmarks/Method-Benchmarks/DecodeB64/Decoder.cs index 00793b9..b5560c4 100644 --- a/benchmark/DecodeB64/Decoder.cs +++ b/benchmarks/Method-Benchmarks/DecodeB64/Decoder.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; using System.Text; namespace BCryptNet.BenchMarks.DecodeB64 @@ -21,6 +22,7 @@ internal static class DecodeB64Methods 51, 52, 53, -1, -1, -1, -1, -1 }; + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int Char64(char character) { return character < 0 || character > Index64.Length ? -1 : Index64[character]; @@ -77,7 +79,7 @@ public static byte[] DecodeBase64ToBytes(string encodedString, int maximumBytes) return result; } -#if !NETFRAMEWORK || NETCOREAPP +#if NETCOREAPP internal static byte[] DecodeBase64StringCreateSpan(string encodedString, int maximumBytes) { @@ -140,10 +142,8 @@ internal static byte[] DecodeBase64StringCreateSpan(string encodedString, int ma } return ret.ToArray(); - } #endif - internal static byte[] DecodeBase64StandardSized(string encodedString, int maximumBytes) { int sourceLength = encodedString.Length; @@ -197,7 +197,6 @@ internal static byte[] DecodeBase64StandardSized(string encodedString, int maxim } - internal static byte[] DecodeBase64StandardUnSized(string encodedString, int maximumBytes) { @@ -251,5 +250,36 @@ internal static byte[] DecodeBase64StandardUnSized(string encodedString, int max return bval; } + + public static int DecodeBase64SpanBuffer(ReadOnlySpan encodedSpan, Span destination) + { + int outputLength = 0; + int position = 0; + + while (position < encodedSpan.Length - 1 && outputLength < destination.Length) + { + int c1 = Char64(encodedSpan[position++]); + int c2 = Char64(encodedSpan[position++]); + if (c1 == -1 || c2 == -1) break; + + destination[outputLength] = (byte)((c1 << 2) | ((c2 & 0x30) >> 4)); + if (++outputLength >= destination.Length || position >= encodedSpan.Length) break; + + int c3 = Char64(encodedSpan[position++]); + if (c3 == -1) break; + + destination[outputLength] = (byte)(((c2 & 0x0F) << 4) | ((c3 & 0x3C) >> 2)); + if (++outputLength >= destination.Length || position >= encodedSpan.Length) break; + + int c4 = Char64(encodedSpan[position++]); + if (c4 == -1) break; + + destination[outputLength] = (byte)(((c3 & 0x03) << 6) | c4); + ++outputLength; + } + + return outputLength; + } } } + diff --git a/benchmark/EncodeB64/Encoder.cs b/benchmarks/Method-Benchmarks/EncodeB64/Encoder.cs similarity index 59% rename from benchmark/EncodeB64/Encoder.cs rename to benchmarks/Method-Benchmarks/EncodeB64/Encoder.cs index dd44b05..c35b044 100644 --- a/benchmark/EncodeB64/Encoder.cs +++ b/benchmarks/Method-Benchmarks/EncodeB64/Encoder.cs @@ -129,5 +129,95 @@ public static char[] EncodeBase64AsBytes(byte[] byteArray, int length) return encoded; } + + internal static ReadOnlySpan EncodeBase64StackAlloc(ReadOnlySpan byteArray, int length) + { + if (length <= 0 || length > byteArray.Length) + { + throw new ArgumentException("Invalid length", nameof(length)); + } + + int encodedSize = (int)Math.Ceiling((length * 4D) / 3); + Span encoded = stackalloc char[encodedSize]; + + int pos = 0; + int off = 0; + while (off < length) + { + //Process first byte in group + int c1 = byteArray[off++] & 0xff; + encoded[pos++] = Base64Code[(c1 >> 2) & 0x3f]; + c1 = (c1 & 0x03) << 4; + if (off >= length) + { + encoded[pos++] = Base64Code[c1 & 0x3f]; + break; + } + + // second byte of group + int c2 = byteArray[off++] & 0xff; + c1 |= (c2 >> 4) & 0x0f; + encoded[pos++] = Base64Code[c1 & 0x3f]; + c1 = (c2 & 0x0f) << 2; + if (off >= length) + { + encoded[pos++] = Base64Code[c1 & 0x3f]; + break; + } + + // third byte of group + c2 = byteArray[off++] & 0xff; + c1 |= (c2 >> 6) & 0x03; + encoded[pos++] = Base64Code[c1 & 0x3f]; + encoded[pos++] = Base64Code[c2 & 0x3f]; + } + + return encoded.ToArray(); + } + + internal static ReadOnlySpan EncodeBase64HeapAlloc(ReadOnlySpan byteArray, int length) + { + if (length <= 0 || length > byteArray.Length) + { + throw new ArgumentException("Invalid length", nameof(length)); + } + + int encodedSize = (int)Math.Ceiling((length * 4D) / 3); + Span encoded = new char[encodedSize]; + + int pos = 0; + int off = 0; + while (off < length) + { + //Process first byte in group + int c1 = byteArray[off++] & 0xff; + encoded[pos++] = Base64Code[(c1 >> 2) & 0x3f]; + c1 = (c1 & 0x03) << 4; + if (off >= length) + { + encoded[pos++] = Base64Code[c1 & 0x3f]; + break; + } + + // second byte of group + int c2 = byteArray[off++] & 0xff; + c1 |= (c2 >> 4) & 0x0f; + encoded[pos++] = Base64Code[c1 & 0x3f]; + c1 = (c2 & 0x0f) << 2; + if (off >= length) + { + encoded[pos++] = Base64Code[c1 & 0x3f]; + break; + } + + // third byte of group + c2 = byteArray[off++] & 0xff; + c1 |= (c2 >> 6) & 0x03; + encoded[pos++] = Base64Code[c1 & 0x3f]; + encoded[pos++] = Base64Code[c2 & 0x3f]; + } + + return encoded; + } } -} \ No newline at end of file +} diff --git a/benchmarks/Method-Benchmarks/EnhancedHashingBenchmark.cs b/benchmarks/Method-Benchmarks/EnhancedHashingBenchmark.cs new file mode 100644 index 0000000..fce287e --- /dev/null +++ b/benchmarks/Method-Benchmarks/EnhancedHashingBenchmark.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using BCryptNet.BenchMarks._3._2._1; +using BCryptNet.BenchMarks._3._5.perfmerge_1; +using BCryptNet.BenchMarks._4._0._0; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Order; + +#pragma warning disable 1591 + +namespace BCryptNet.BenchMarks +{ + [MemoryDiagnoser] + [RPlotExporter] + [RankColumn] + [GcServer(true)] + [Orderer(SummaryOrderPolicy.Declared)] + [KeepBenchmarkFiles] + [MarkdownExporterAttribute.GitHub] + [ReturnValueValidator(failOnError: true)] + public class EnhancedHashingBenchmark + { + public IEnumerable Data() + { + yield return ["~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2a$10$LgfYWkbzEvQ4JakH7rOvHe"]; + } + + [Benchmark(Baseline = true)] + [ArgumentsSource(nameof(Data))] + public string TestHashValidateEnhanced(string key, string salt) + { + return BCryptBaseLine.BCrypt.HashPassword(key, salt, enhancedEntropy: true); + } + + [Benchmark] + [ArgumentsSource(nameof(Data))] + public string TestHashValidateEnhancedv3Perf(string key, string salt) + { + return BCrypt305PerfMerge1.BCrypt.HashPassword(key, salt, enhancedEntropy: true); + } + + [Benchmark] + [ArgumentsSource(nameof(Data))] + public string TestHashValidateEnhancedv4Perf(string key, string salt) + { + return BCryptV4.BCrypt.HashPassword(key, salt, enhancedEntropy: true); + } + + [Benchmark] + [ArgumentsSource(nameof(Data))] + public string TestHashValidateEnhancedCurrent(string key, string salt) + { + return BCryptExtendedV2.HashPassword(key, salt); + } + + private static readonly string Hmackey = Guid.NewGuid().ToString(); + +#if NETCOREAPP + [Benchmark] + [ArgumentsSource(nameof(Data))] + public string TestHashValidateEnhancedNet8Plus(string key, string salt) + { + return BCryptExtendedV3.HashPassword(Hmackey, key, salt); + } +#endif + } +} diff --git a/benchmarks/Method-Benchmarks/EnhancedHashingV3Benchmark.cs b/benchmarks/Method-Benchmarks/EnhancedHashingV3Benchmark.cs new file mode 100644 index 0000000..09fd941 --- /dev/null +++ b/benchmarks/Method-Benchmarks/EnhancedHashingV3Benchmark.cs @@ -0,0 +1,124 @@ +// /* +// The MIT License (MIT) +// Copyright (c) 2006 Damien Miller djm@mindrot.org (jBCrypt) +// Copyright (c) 2013 Ryan D. Emerle (.Net port) +// Copyright (c) 2016 Chris McKee (.Net-core port / patches / new features) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// */ +using System; +using System.Security.Cryptography; +using System.Text; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Order; + +namespace BCryptNet.BenchMarks; + +[MemoryDiagnoser] +[RPlotExporter] +[RankColumn] +[GcServer(true)] +[Orderer(SummaryOrderPolicy.Declared)] +[KeepBenchmarkFiles] +[MarkdownExporterAttribute.GitHub] +// [ReturnValueValidator(failOnError: true)] +[IterationTime(500)] +public class EnhancedHashingV3Benchmark +{ + // Store the strings as fields + private const string HmacKeyString = "SuperSecureHMACKey"; + private const string InputKeyString = "SensitiveDataToHash"; + private ReadOnlySpan HmacKey => HmacKeyString.AsSpan(); + private ReadOnlySpan InputKey => InputKeyString.AsSpan(); + + private const HashType hashType = HashType.SHA256; + private const char BcryptMinorRevision = 'a'; +#if NETCOREAPP + [Benchmark(Baseline = true)] + public byte[] OldMethod() => EnhancedHashOld(HmacKey, InputKey, hashType, BcryptMinorRevision).ToArray(); + + [Benchmark] + public byte[] NewMethod() => EnhancedHash(HmacKey, InputKey, hashType, BcryptMinorRevision); + + private static Span EnhancedHashOld(ReadOnlySpan hmacKey, ReadOnlySpan inputKey, HashType hashType, char bcryptMinorRevision) + { + switch (hashType) + { + case HashType.SHA256: + using (var sha = new HMACSHA3_256(Encoding.UTF8.GetBytes(hmacKey.ToString()))) + return Encoding.UTF8.GetBytes(Convert.ToBase64String(sha.ComputeHash(Encoding.UTF8.GetBytes(inputKey.ToString()))) + + (bcryptMinorRevision >= 'a' ? "\0" : "")); + case HashType.SHA384: + using (var sha = new HMACSHA3_384(Encoding.UTF8.GetBytes(hmacKey.ToString()))) + return Encoding.UTF8.GetBytes(Convert.ToBase64String(sha.ComputeHash(Encoding.UTF8.GetBytes(inputKey.ToString()))) + + (bcryptMinorRevision >= 'a' ? "\0" : "")); + case HashType.SHA512: + using (var sha = new HMACSHA3_512(Encoding.UTF8.GetBytes(hmacKey.ToString()))) + return Encoding.UTF8.GetBytes(Convert.ToBase64String(sha.ComputeHash(Encoding.UTF8.GetBytes(inputKey.ToString()))) + + (bcryptMinorRevision >= 'a' ? "\0" : "")); + default: + throw new ArgumentOutOfRangeException(nameof(hashType), hashType, null); + } + } + + private static byte[] EnhancedHash(ReadOnlySpan hmacKey, ReadOnlySpan inputKey, HashType hashType, char bcryptMinorRevision) + { + ushort hashLen = hashType switch + { + HashType.SHA256 => 32, + HashType.SHA384 => 48, + HashType.SHA512 => 64, + _ => throw new ArgumentOutOfRangeException(nameof(hashType)) + }; + + Span keyBytes = stackalloc byte[Encoding.UTF8.GetMaxByteCount(hmacKey.Length)]; + Span dataBytes = stackalloc byte[Encoding.UTF8.GetMaxByteCount(inputKey.Length)]; + Span hash = stackalloc byte[hashLen]; + + int keyByteLen = Encoding.UTF8.GetBytes(hmacKey, keyBytes); + int dataByteLen = Encoding.UTF8.GetBytes(inputKey, dataBytes); + + bool success = hashType switch + { + HashType.SHA256 => HMACSHA3_256.TryHashData(keyBytes[..keyByteLen], dataBytes[..dataByteLen], hash, out int len) && len == 32, + HashType.SHA384 => HMACSHA3_384.TryHashData(keyBytes[..keyByteLen], dataBytes[..dataByteLen], hash, out int len) && len == 48, + HashType.SHA512 => HMACSHA3_512.TryHashData(keyBytes[..keyByteLen], dataBytes[..dataByteLen], hash, out int len) && len == 64, + _ => throw new ArgumentOutOfRangeException(nameof(hashType)) + }; + + if (!success) + throw new Exception($"HMAC-{hashType} failed"); + + Span base64Chars = stackalloc char[(hashLen + 2) / 3 * 4]; + if (!Convert.TryToBase64Chars(hash, base64Chars, out int base64Len)) + throw new Exception("Base64 encoding failed in EnhancedHash"); + + Span finalBase64 = stackalloc char[base64Len + (bcryptMinorRevision >= 'a' ? 1 : 0)]; + base64Chars[..base64Len].CopyTo(finalBase64); + if (bcryptMinorRevision >= 'a') finalBase64[base64Len] = '\0'; + + Span utf8Buffer = stackalloc byte[Encoding.UTF8.GetMaxByteCount(finalBase64.Length)]; + int utf8Len = Encoding.UTF8.GetBytes(finalBase64, utf8Buffer); + + return utf8Buffer[..utf8Len].ToArray(); + } +#endif + + public enum HashType + { + None, + SHA256, + SHA384, + SHA512 + } +} diff --git a/benchmarks/Method-Benchmarks/EnhancedHashingValidation.cs b/benchmarks/Method-Benchmarks/EnhancedHashingValidation.cs new file mode 100644 index 0000000..cc12dc8 --- /dev/null +++ b/benchmarks/Method-Benchmarks/EnhancedHashingValidation.cs @@ -0,0 +1,90 @@ +// /* +// The MIT License (MIT) +// Copyright (c) 2006 Damien Miller djm@mindrot.org (jBCrypt) +// Copyright (c) 2013 Ryan D. Emerle (.Net port) +// Copyright (c) 2016 Chris McKee (.Net-core port / patches / new features) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// */ + +using System; +using System.Collections.Generic; +using BCryptNet.BenchMarks._3._2._1; +using BCryptNet.BenchMarks._3._5.perfmerge_1; +using BCryptNet.BenchMarks._4._0._0; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Order; + +namespace BCryptNet.BenchMarks; + +#pragma warning disable 1591 +[MemoryDiagnoser] +[RPlotExporter] +[RankColumn] +[GcServer(true)] +[Orderer(SummaryOrderPolicy.Declared)] +[KeepBenchmarkFiles] +[MarkdownExporterAttribute.GitHub] +// [ReturnValueValidator(failOnError: true)] +public class EnhancedHashingValidation +{ + public IEnumerable Data() + { + yield return ["~!@#$%^&*() ~!@#$%^&*()PNBFRD"]; + } + + private readonly string _baselineEnhancedHash = BCryptBaseLine.BCrypt.EnhancedHashPassword("~!@#$%^&*() ~!@#$%^&*()PNBFRD", BCryptBaseLine.HashType.SHA384, 12); + private readonly string _bCrypt305PerfMerge1EnhancedHash = BCrypt305PerfMerge1.BCrypt.EnhancedHashPassword("~!@#$%^&*() ~!@#$%^&*()PNBFRD", BCrypt305PerfMerge1.HashType.SHA384, 12); + private readonly string _bCryptV4PerfMerge1EnhancedHash = BCryptV4.BCrypt.EnhancedHashPassword("~!@#$%^&*() ~!@#$%^&*()PNBFRD", BCryptV4.HashType.SHA384, 12); + private readonly string _bCryptExtendedV2EnhancedHash = BCryptExtendedV2.HashPassword("~!@#$%^&*() ~!@#$%^&*()PNBFRD", 12, HashType.SHA384); + private static readonly string Hmackey = Guid.NewGuid().ToString(); +#if NETCOREAPP + private readonly string _bCryptExtendedV3EnhancedHash = BCryptExtendedV3.HashPassword(Hmackey, "~!@#$%^&*() ~!@#$%^&*()PNBFRD", 12, HashType.SHA384); +#endif + + [Benchmark(Baseline = true)] + [ArgumentsSource(nameof(Data))] + public bool TestHashValidateEnhanced(string key) + { + return BCryptBaseLine.BCrypt.EnhancedVerify(key, _baselineEnhancedHash); + } + + [Benchmark] + [ArgumentsSource(nameof(Data))] + public bool TestHashValidateEnhancedv305Perf1(string key) + { + return BCrypt305PerfMerge1.BCrypt.EnhancedVerify(key, _bCrypt305PerfMerge1EnhancedHash); + } + + [Benchmark] + [ArgumentsSource(nameof(Data))] + public bool TestHashValidateEnhancedv4Perf(string key) + { + return BCryptV4.BCrypt.EnhancedVerify(key, _bCryptV4PerfMerge1EnhancedHash); + } + + [Benchmark] + [ArgumentsSource(nameof(Data))] + public bool TestHashValidateEnhancedCurrent(string key) + { + return BCryptExtendedV2.Verify(key, _bCryptExtendedV2EnhancedHash); + } +#if NETCOREAPP + [Benchmark] + [ArgumentsSource(nameof(Data))] + public bool TestHashValidateEnhancedNet8Plus(string key) + { + return BCryptExtendedV3.Verify(Hmackey, key, _bCryptExtendedV3EnhancedHash); + } +#endif +} diff --git a/benchmarks/Method-Benchmarks/EnhancedValidationBenchmark.cs b/benchmarks/Method-Benchmarks/EnhancedValidationBenchmark.cs new file mode 100644 index 0000000..ba801d6 --- /dev/null +++ b/benchmarks/Method-Benchmarks/EnhancedValidationBenchmark.cs @@ -0,0 +1,81 @@ +// /* +// The MIT License (MIT) +// Copyright (c) 2006 Damien Miller djm@mindrot.org (jBCrypt) +// Copyright (c) 2013 Ryan D. Emerle (.Net port) +// Copyright (c) 2016 Chris McKee (.Net-core port / patches / new features) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// */ + +using System.Collections.Generic; +using BCryptNet.BenchMarks._3._2._1; +using BCryptNet.BenchMarks._3._5.perfmerge_1; +using BCryptNet.BenchMarks._4._0._0; +using BCryptNet.BenchMarks._4._0._3; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Order; + +namespace BCryptNet.BenchMarks; + +#pragma warning disable 1591 +[MemoryDiagnoser] +[RPlotExporter] +[RankColumn] +[GcServer(true)] +[Orderer(SummaryOrderPolicy.Declared)] +[KeepBenchmarkFiles] +[MarkdownExporterAttribute.GitHub] +// [ReturnValueValidator(failOnError: true)] +public class EnhancedValidationBenchmark +{ + public IEnumerable Data() + { + yield return ["~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2a$10$LgfYWkbzEvQ4JakH7rOvHe", "$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS"]; + yield return ["~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2a$12$WApznUOJfkEGSmYRfnkrPO", "$2a$12$WApznUOJfkEGSmYRfnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC"]; + } + + [Benchmark(Baseline = true)] + [ArgumentsSource(nameof(Data))] + public bool TestHashValidate(string key, string salt, string hash) + { + return BCryptBaseLine.BCrypt.Verify(key, hash); + } + + [Benchmark] + [ArgumentsSource(nameof(Data))] + public bool TestHashValidatePerf1(string key, string salt, string hash) + { + return BCrypt305PerfMerge1.BCrypt.Verify(key, hash); + } + + [Benchmark] + [ArgumentsSource(nameof(Data))] + public bool TestHashValidateV4(string key, string salt, string hash) + { + return BCryptV4.BCrypt.Verify(key, hash); + } + + [Benchmark] + [ArgumentsSource(nameof(Data))] + public bool TestHashValidateV403(string key, string salt, string hash) + { + return BCryptV403.BCrypt.Verify(key, hash); + } + + [Benchmark] + [ArgumentsSource(nameof(Data))] + public bool TestHashValidateCurrent(string key, string salt, string hash) + { + return BCrypt.Verify(key, hash); + } +} diff --git a/benchmark/TestBcrypt_HashInterrogation.cs b/benchmarks/Method-Benchmarks/HashInterrogationBenchmark.cs similarity index 50% rename from benchmark/TestBcrypt_HashInterrogation.cs rename to benchmarks/Method-Benchmarks/HashInterrogationBenchmark.cs index f4c4ea2..5e3000e 100644 --- a/benchmark/TestBcrypt_HashInterrogation.cs +++ b/benchmarks/Method-Benchmarks/HashInterrogationBenchmark.cs @@ -1,22 +1,30 @@ using BCryptNet.BenchMarks._3._2._1; using BCryptNet.BenchMarks._4._0._0; +using BCryptNet.BenchMarks._4._0._3; +using BCryptNet.BenchMarks.HashParser; using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Order; #pragma warning disable 1591 namespace BCryptNet.BenchMarks { [MemoryDiagnoser] - [RPlotExporter, RankColumn] + [RPlotExporter] + [RankColumn] + [GcServer(true)] + [Orderer(SummaryOrderPolicy.Declared)] [KeepBenchmarkFiles] - public class TestBcrypt_HashInterrogation + [MarkdownExporterAttribute.GitHub] + // [ReturnValueValidator(failOnError: true)] + public class HashInterrogationBenchmark { [Benchmark(Baseline = true)] [Arguments("$2a$12$k42ZFHFWqBp3vWli.nIn8uYyIkbvYRvodzbfbK18SSsY.CsIQPlxO")] [Arguments( "$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq")] public void InterrogateHashUsingRegex(string hash) { - BaseLine.BCrypt.InterrogateHash(hash); + BCryptBaseLine.BCrypt.InterrogateHash(hash); } [Benchmark()] @@ -24,7 +32,15 @@ public void InterrogateHashUsingRegex(string hash) [Arguments( "$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq")] public void InterrogateHashUsingParserV4(string hash) { - version4.BCrypt.InterrogateHash(hash); + BCryptV4.BCrypt.InterrogateHash(hash); + } + + [Benchmark()] + [Arguments("$2a$12$k42ZFHFWqBp3vWli.nIn8uYyIkbvYRvodzbfbK18SSsY.CsIQPlxO")] + [Arguments( "$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq")] + public void InterrogateHashUsingParserV403(string hash) + { + BCryptV403.BCrypt.InterrogateHash(hash); } [Benchmark()] @@ -34,5 +50,13 @@ public void InterrogateHashUsingParserCurrent(string hash) { BCrypt.InterrogateHash(hash); } + + [Benchmark()] + [Arguments("$2a$12$k42ZFHFWqBp3vWli.nIn8uYyIkbvYRvodzbfbK18SSsY.CsIQPlxO")] + [Arguments( "$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq")] + public void InterrogateHashUsingParser(string hash) + { + Decoder.GetHashInformation(hash); + } } } diff --git a/benchmark/HashParser/HashParser.cs b/benchmarks/Method-Benchmarks/HashParser/HashParser.cs similarity index 94% rename from benchmark/HashParser/HashParser.cs rename to benchmarks/Method-Benchmarks/HashParser/HashParser.cs index 35f5f07..df7453c 100644 --- a/benchmark/HashParser/HashParser.cs +++ b/benchmarks/Method-Benchmarks/HashParser/HashParser.cs @@ -8,14 +8,14 @@ internal static class Decoder private static readonly HashFormatDescriptor OldFormatDescriptor = new HashFormatDescriptor(versionLength: 1); private static readonly HashFormatDescriptor NewFormatDescriptor = new HashFormatDescriptor(versionLength: 2); - public static BaseLine.HashInformation GetHashInformation(string hash) + public static BCryptBaseLine.HashInformation GetHashInformation(string hash) { if (!IsValidHash(hash, out var format)) { ThrowInvalidHashFormat(); } - return new BaseLine.HashInformation( + return new BCryptBaseLine.HashInformation( hash.Substring(0, format.SettingLength), hash.Substring(1, format.VersionLength), hash.Substring(format.WorkfactorOffset, 2), @@ -125,7 +125,7 @@ private static bool IsAsciiNumeric(char value) private static void ThrowInvalidHashFormat() { - throw new BaseLine.SaltParseException("Invalid Hash Format"); + throw new BCryptBaseLine.SaltParseException("Invalid Hash Format"); } private class HashFormatDescriptor diff --git a/benchmarks/Method-Benchmarks/HashingBenchmark.cs b/benchmarks/Method-Benchmarks/HashingBenchmark.cs new file mode 100644 index 0000000..97285f9 --- /dev/null +++ b/benchmarks/Method-Benchmarks/HashingBenchmark.cs @@ -0,0 +1,84 @@ +// /* +// The MIT License (MIT) +// Copyright (c) 2006 Damien Miller djm@mindrot.org (jBCrypt) +// Copyright (c) 2013 Ryan D. Emerle (.Net port) +// Copyright (c) 2016 Chris McKee (.Net-core port / patches / new features) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// */ + +using System; +using System.Collections.Generic; +using BCryptNet.BenchMarks._3._2._1; +using BCryptNet.BenchMarks._3._5.perfmerge_1; +using BCryptNet.BenchMarks._4._0._0; +using BCryptNet.BenchMarks._4._0._3; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Order; + +#pragma warning disable 1591 + +namespace BCryptNet.BenchMarks +{ + [MemoryDiagnoser] + [RPlotExporter] + [RankColumn] + [GcServer(true)] + [Orderer(SummaryOrderPolicy.Declared)] + [KeepBenchmarkFiles] + [MarkdownExporterAttribute.GitHub] + [ReturnValueValidator(failOnError: true)] + public class HashingBenchmark + { + public static IEnumerable Data() + { + yield return ["~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2a$10$LgfYWkbzEvQ4JakH7rOvHe", "$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS"]; + yield return ["~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2a$12$WApznUOJfkEGSmYRfnkrPO", "$2a$12$WApznUOJfkEGSmYRfnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC"]; + } + + [Benchmark(Baseline = true)] + [ArgumentsSource(nameof(Data))] + public string TestHashValidate(string key, string salt, string hash) + { + return BCryptBaseLine.BCrypt.HashPassword(key, salt, enhancedEntropy: false); + } + + [Benchmark] + [ArgumentsSource(nameof(Data))] + public string TestHashValidatePerf1(string key, string salt, string hash) + { + return BCrypt305PerfMerge1.BCrypt.HashPassword(key, salt, enhancedEntropy: false); + } + + [Benchmark] + [ArgumentsSource(nameof(Data))] + public string TestHashValidateV4(string key, string salt, string hash) + { + return BCryptV4.BCrypt.HashPassword(key, salt); + } + + [Benchmark] + [ArgumentsSource(nameof(Data))] + public string TestHashValidateV403(string key, string salt, string hash) + { + return BCryptV403.BCrypt.HashPassword(key, salt); + } + + [Benchmark] + [ArgumentsSource(nameof(Data))] + public string TestHashValidateCurrent(string key, string salt, string hash) + { + return BCrypt.HashPassword(key, salt); + } + } +} diff --git a/benchmark/BCryptNet.BenchMarks.csproj b/benchmarks/Method-Benchmarks/Method-Benchmarks.csproj similarity index 70% rename from benchmark/BCryptNet.BenchMarks.csproj rename to benchmarks/Method-Benchmarks/Method-Benchmarks.csproj index 6a2425d..4029ca8 100644 --- a/benchmark/BCryptNet.BenchMarks.csproj +++ b/benchmarks/Method-Benchmarks/Method-Benchmarks.csproj @@ -3,7 +3,7 @@ BCryptNet.BenchMarks BCryptNet.BenchMarks Exe - net48;net9.0 + net8.0;net10.0;net48 true Release;Debug AnyCPU @@ -12,38 +12,31 @@ portable false false - - - - true - false - false - false - false - false - false + latest NU5105;NU1507;CS1591;CS0618 + true NU5105;NU1507;CS1591;CS0618 - + + - - + + - + \ No newline at end of file diff --git a/benchmarks/Method-Benchmarks/Program.cs b/benchmarks/Method-Benchmarks/Program.cs new file mode 100644 index 0000000..5caf63c --- /dev/null +++ b/benchmarks/Method-Benchmarks/Program.cs @@ -0,0 +1,24 @@ +using System; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Environments; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Running; +using BenchmarkDotNet.Validators; + +#pragma warning disable 1591 + +namespace BCryptNet.BenchMarks +{ + class Program + { + static void Main(string[] args) + { + var config = DefaultConfig.Instance + .AddJob(Job.Default.WithRuntime(CoreRuntime.Core80)) + .AddJob(Job.Default.WithRuntime(CoreRuntime.Core10_0)) + .AddJob(Job.Default.WithRuntime(ClrRuntime.Net481)) + ; + BenchmarkSwitcher.FromAssemblies([typeof(Program).Assembly]).Run(args, config); + } + } +} diff --git a/benchmark/TestVariantsOnStringBuilding.cs b/benchmarks/Method-Benchmarks/StringBuildingVariantsBenchmark.cs similarity index 55% rename from benchmark/TestVariantsOnStringBuilding.cs rename to benchmarks/Method-Benchmarks/StringBuildingVariantsBenchmark.cs index f4026de..14ae900 100644 --- a/benchmark/TestVariantsOnStringBuilding.cs +++ b/benchmarks/Method-Benchmarks/StringBuildingVariantsBenchmark.cs @@ -1,25 +1,33 @@ -using System.Text; +using System; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Text; using BCryptNet.BenchMarks._3._2._1; using BCryptNet.BenchMarks.EncodeB64; using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Order; #pragma warning disable 1591 namespace BCryptNet.BenchMarks { [MemoryDiagnoser] - [CategoriesColumn] - [RPlotExporter, RankColumn] - [ReturnValueValidator(failOnError: true)] + [RPlotExporter] + [RankColumn] + [GcServer(true)] + [Orderer(SummaryOrderPolicy.Declared)] [KeepBenchmarkFiles] - public class TestVariantsOnStringBuilding + [MarkdownExporterAttribute.GitHub] + [ReturnValueValidator(failOnError: true)] + [IterationTime(500)] + public class StringBuildingVariantsBenchmark { private readonly string bcryptMinorRevision = "a"; private static readonly string hash = "TV4S6ytwfsfvkgY8jIucDrjc8deX1s."; private static readonly string salt = "DCq7YPn5Rq63x1Lad4cll."; - private static readonly byte[] SaltBytes = BaseLine.BCrypt.DecodeBase64(salt, 16); - private static readonly byte[] HashBytes = BaseLine.BCrypt.DecodeBase64(hash, 23); + private static readonly byte[] SaltBytes = BCryptBaseLine.BCrypt.DecodeBase64(salt, 16); + private static readonly byte[] HashBytes = BCryptBaseLine.BCrypt.DecodeBase64(hash, 23); private static readonly char[] EncodedSaltAsChars = EncodeB64Methods.EncodeBase64AsBytes(SaltBytes, 16); private static readonly char[] EncodedHashAsChars = EncodeB64Methods.EncodeBase64AsBytes(HashBytes, 23); @@ -41,7 +49,7 @@ public string Original_StrBuilder_SinEncoding() [Benchmark] [BenchmarkCategory("StringAppend", "AppendChar")] - public string Original_StrBuilder_SinEncoding_AppendChar() + public string StrBuilder_SinEncoding_UsingAppendFormatAndAppendChar() { // Generate result string StringBuilder result = new StringBuilder(); @@ -54,7 +62,7 @@ public string Original_StrBuilder_SinEncoding_AppendChar() [Benchmark] [BenchmarkCategory("StringAppend", "AppendChar")] - public string Original_StrBuilder_SinEncoding_AppendChar_Sized() + public string SizedStrBuilder_SinEncoding_UsingAppendFormatAndAppendChar() { // Generate result string StringBuilder result = new StringBuilder(60); @@ -67,10 +75,14 @@ public string Original_StrBuilder_SinEncoding_AppendChar_Sized() [Benchmark] [BenchmarkCategory("StringAppend", "AppendChar")] - public string Original_StrBuilder_SinEncoding_AppendChar_Sized_PRFmt() + public string SizedStrBuilder_SinEncoding_UsingAppendStringAndChar() { var result = new StringBuilder(60); - result.Append("$2").Append(bcryptMinorRevision).Append('$').Append(workFactor.ToString("D2")).Append('$'); + result.Append("$2"); + result.Append(bcryptMinorRevision); + result.Append('$'); + result.Append(workFactor.ToString("D2")); + result.Append('$'); result.Append(EncodedSaltAsChars); result.Append(EncodedHashAsChars); @@ -79,34 +91,47 @@ public string Original_StrBuilder_SinEncoding_AppendChar_Sized_PRFmt() [Benchmark] [BenchmarkCategory("StringAppend", "AppendChar")] - public string Original_StrBuilder_SinEncoding_AppendChar_Sized_PRFmt_MoreChar() + public string SizedStrBuilder_SinEncoding_UsingAppendStringAndChar_v2() { var result = new StringBuilder(60); - result.Append('$').Append('2').Append(bcryptMinorRevision).Append('$').Append(workFactor.ToString("D2")).Append('$') - .Append(EncodedSaltAsChars) - .Append(EncodedHashAsChars); + result.Append('$'); + result.Append('2'); + result.Append(bcryptMinorRevision); + result.Append('$'); + result.Append(workFactor.ToString("D2", CultureInfo.InvariantCulture)); + result.Append('$'); + result.Append(EncodedSaltAsChars); + result.Append(EncodedHashAsChars); return result.ToString(); } [Benchmark] [BenchmarkCategory("StringAppend", "AppendChar")] - public string Original_StrBuilder_SinEncoding_AppendChar_Sized_PRFmt_MoreString() + public string SizedStrBuilder_SinEncoding_UsingAppendStrings_HashSaltAsChar() { var result = new StringBuilder(60); - result.Append("$2").Append(bcryptMinorRevision).Append("$").Append(workFactor.ToString("D2")).Append("$") - .Append(EncodedSaltAsChars) - .Append(EncodedHashAsChars); + result.Append("$2"); + result.Append(bcryptMinorRevision); + result.Append("$"); + result.Append(workFactor.ToString("D2")); + result.Append("$"); + result.Append(EncodedSaltAsChars); + result.Append(EncodedHashAsChars); return result.ToString(); } [Benchmark] [BenchmarkCategory("StringAppend", "AppendString")] - public string Original_StrBuilder_SinEncoding_AppendChar_Sized_PRFmt_StringNotChar() + public string SizedStrBuilder_SinEncoding_UsingAppendStrings_HashSaltAsString_WorkFactorToString() { var result = new StringBuilder(60); - result.Append("$2").Append(bcryptMinorRevision).Append("$").Append(workFactor.ToString("D2")).Append("$"); + result.Append("$2"); + result.Append(bcryptMinorRevision); + result.Append("$"); + result.Append(workFactor.ToString("D2")); + result.Append("$"); result.Append(salt); result.Append(hash); @@ -115,20 +140,21 @@ public string Original_StrBuilder_SinEncoding_AppendChar_Sized_PRFmt_StringNotCh [Benchmark] [BenchmarkCategory("StringAppend", "AppendString")] - public string Original_StrBuilder_SinEncoding_AppendChar_Sized_FROMSTRING_PRFmt_plusfmt() + public string SizedStrBuilder_SinEncoding_UsingAppendStrings_WorkFactorInterpolated() { var result = new StringBuilder(60); - result.Append("$2") - .Append(bcryptMinorRevision) - .Append("$") - .AppendFormat("{0:00}", workFactor) - .Append("$") - .Append(salt) - .Append(hash); + result.Append("$2"); + result.Append(bcryptMinorRevision); + result.Append("$"); + result.Append($"{workFactor:00}"); + result.Append("$"); + result.Append(salt); + result.Append(hash); return result.ToString(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static char[] Concatenate(char[] array1, char[] array2) { char[] result = new char[array1.Length + array2.Length]; @@ -137,6 +163,17 @@ public static char[] Concatenate(char[] array1, char[] array2) return result; } +#if NETCOREAPP + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ConcatenateToString(char[] array1, char[] array2) + { + Span result = stackalloc char[array1.Length + array2.Length]; + array1.CopyTo(result); + array2.CopyTo(result.Slice(array1.Length)); + return new string(result); + } +#endif + [Benchmark] [BenchmarkCategory("StringFmt", "AppendChar")] public string StringInterpolation_WithChar() @@ -146,11 +183,20 @@ public string StringInterpolation_WithChar() [Benchmark] [BenchmarkCategory("StringFmt", "AppendChar")] - public string StringInterpolation_WithCharMerged() + public string StringInterpolation_WithCharsConcat() { return $"$2{bcryptMinorRevision}${workFactor:00}${new string(Concatenate(EncodedSaltAsChars, EncodedHashAsChars))}"; } +#if NETCOREAPP + [Benchmark] + [BenchmarkCategory("StringFmt", "AppendChar")] + public string StringInterpolation_WithAllocConcat() + { + return $"$2{bcryptMinorRevision}${workFactor:00}${ConcatenateToString(EncodedSaltAsChars, EncodedHashAsChars)}"; + } +#endif + [Benchmark] [BenchmarkCategory("StringFmt", "AppendString")] public string StringInterpolation_WithString() diff --git a/benchmarks/Method-Benchmarks/bench.cmd b/benchmarks/Method-Benchmarks/bench.cmd new file mode 100644 index 0000000..52ba86b --- /dev/null +++ b/benchmarks/Method-Benchmarks/bench.cmd @@ -0,0 +1 @@ +dotnet run --configuration Release --framework net10.0 --runtimes net48 net80 net10_0 --filter * --join diff --git a/benchmark/readme.md b/benchmarks/Method-Benchmarks/readme.md similarity index 58% rename from benchmark/readme.md rename to benchmarks/Method-Benchmarks/readme.md index dee07e6..006278c 100644 --- a/benchmark/readme.md +++ b/benchmarks/Method-Benchmarks/readme.md @@ -2,11 +2,11 @@ Running these -`dotnet run -c Release -f net48 -- --runtimes net48 net9.0 --platform x64` +`dotnet run -c Release -f net9.0 -- --runtimes net9.0 --platform x64` or -`dotnet run -c Release -f net48 -- --runtimes net48 net9.0 --filter * --stopOnFirstError --platform x64` +`dotnet run -c Release -f net9.0 -- --runtimes net9.0 --filter * --stopOnFirstError --platform x64` _Change the framework and remove the netfwk runtimes if running on linux._ diff --git a/benchmarks/Release-Benchmarks/BCryptBasics.cs b/benchmarks/Release-Benchmarks/BCryptBasics.cs new file mode 100644 index 0000000..57853c1 --- /dev/null +++ b/benchmarks/Release-Benchmarks/BCryptBasics.cs @@ -0,0 +1,33 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Environments; +using BenchmarkDotNet.Jobs; + +namespace ReleaseBenchmark; + +[Config(typeof(Config))] +public class BCryptBasics : Benchmark +{ + private class Config : ManualConfig + { + public Config() + { + var versions = new[] + { + ("2.0.0", new Runtime[] {ClrRuntime.Net462, ClrRuntime.Net481}), + ("2.1.4", [ClrRuntime.Net462, ClrRuntime.Net481, CoreRuntime.Core80, CoreRuntime.Core10_0]), + ("3.5.0", [ClrRuntime.Net462, ClrRuntime.Net481, CoreRuntime.Core80, CoreRuntime.Core10_0]), + ("4.0.3", [ClrRuntime.Net462, ClrRuntime.Net481, CoreRuntime.Core80, CoreRuntime.Core10_0]), + (VersionInfo.BCryptVersion, [ClrRuntime.Net462, ClrRuntime.Net481, CoreRuntime.Core80, CoreRuntime.Core10_0]) + }; + + foreach (var (version, runtimes) in versions) + { + foreach (var runtime in runtimes) + { + AddJob(BaseJob.WithRuntime(runtime).WithMsBuildArguments($"/p:BCryptVersion={version}")); + } + } + } + } +} diff --git a/benchmarks/Release-Benchmarks/Benchmark.cs b/benchmarks/Release-Benchmarks/Benchmark.cs new file mode 100644 index 0000000..34fdb0f --- /dev/null +++ b/benchmarks/Release-Benchmarks/Benchmark.cs @@ -0,0 +1,50 @@ +using System; +// ReSharper disable once RedundantUsingDirective +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Jobs; + +namespace ReleaseBenchmark; + +[MemoryDiagnoser] +[GcServer(true)] +[KeepBenchmarkFiles] +[MarkdownExporterAttribute.GitHub] +public abstract class Benchmark +{ + protected static Job BaseJob = Job.MediumRun; + private readonly Consumer _consumer = new(); + + private const string Key = "~!@#$%^&*() ~!@#$%^&*()PNBFRD"; + private const string Salt = "$2a$12$WApznUOJfkEGSmYRfnkrPO"; +#if POSTV5 + private static ReadOnlySpan KeySpan => Key.AsSpan(); + private static ReadOnlySpan SaltSpan => Salt.AsSpan(); +#endif + + [Benchmark] + [BenchmarkCategory("HashSpan")] + public void TestHashing() + { +#if POSTV5 && NETCOREAPP + Span outputBuffer = stackalloc char[60]; + BCryptNet.BCrypt.HashPassword(KeySpan, SaltSpan, outputBuffer, out int outputBufferWritten); + _consumer.Consume(outputBufferWritten); + if (outputBufferWritten > 0) + { + _consumer.Consume(outputBuffer[0]); + } +#elif POSTV5 && NET48_OR_GREATER + var hash = BCryptNet.BCrypt.HashPassword(KeySpan, SaltSpan); + _consumer.Consume(hash); +#elif POSTV5 && NET462 + var hash = BCryptNet.BCrypt.HashPassword(Key, Salt); + _consumer.Consume(hash); +#else + var hash = BCrypt.Net.BCrypt.HashPassword(Key, Salt); + _consumer.Consume(hash); +#endif + } + + +} diff --git a/benchmarks/Release-Benchmarks/Program.cs b/benchmarks/Release-Benchmarks/Program.cs new file mode 100644 index 0000000..2846f47 --- /dev/null +++ b/benchmarks/Release-Benchmarks/Program.cs @@ -0,0 +1,9 @@ +using BenchmarkDotNet.Running; + +namespace ReleaseBenchmark; + +public class Program +{ + public static void Main(string[] args) => + BenchmarkSwitcher.FromAssemblies([typeof(Program).Assembly]).Run(args); +} diff --git a/benchmarks/Release-Benchmarks/Release-Benchmarks.csproj b/benchmarks/Release-Benchmarks/Release-Benchmarks.csproj new file mode 100644 index 0000000..d3dd121 --- /dev/null +++ b/benchmarks/Release-Benchmarks/Release-Benchmarks.csproj @@ -0,0 +1,59 @@ + + + ReleaseBenchmark + ReleaseBenchmark + Exe + net8.0;net10.0;net481;net462 + true + Release;Debug + AnyCPU + AnyCPU + + false + portable + false + false + latest + + 5.0.0-prerelease.g1003abb019 + $(BCryptDefaultCurrent) + $(DefineConstants);POSTV5 + $(DefineConstants);NET48_OR_GREATER + + + + + + + + NU5105;NU1507;CS1591;CS0618 + ;NU1605;NU1603 + + + true + NU5105;NU1507;CS1591;CS0618 + ;NU1605;NU1603 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/benchmarks/Release-Benchmarks/bench.cmd b/benchmarks/Release-Benchmarks/bench.cmd new file mode 100644 index 0000000..979c591 --- /dev/null +++ b/benchmarks/Release-Benchmarks/bench.cmd @@ -0,0 +1 @@ +dotnet run --configuration Release --framework net10.0 --runtimes net462 net481 net80 net10_0 --filter * --join diff --git a/coverage.runsettings b/coverage.runsettings new file mode 100644 index 0000000..f860290 --- /dev/null +++ b/coverage.runsettings @@ -0,0 +1,30 @@ + + + + + + + + cobertura,opencover + + + [*.Tests]*,[*.UnitTests]*,[*.Benchmark]* + + + Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute,ExcludeFromCodeCoverageAttribute + + + **/obj/**/*,**/*.g.cs,**/*.designer.cs + + + false + + + false + true + true + + + + + diff --git a/docs/docs/common-topics.md b/docs/docs/common-topics.md new file mode 100644 index 0000000..7b199bd --- /dev/null +++ b/docs/docs/common-topics.md @@ -0,0 +1,24 @@ +--- +uid: common-topics +--- + + +## Secure String / In memory secrets / protection against memory dumps or RAM attacks + +V5 provides methods allowing you to reduce the amount of time data is in memory through both the zeroing of buffers when the data is no longer required and the reduction of the number of copies of the data in memory and a reduction in the amount of time any data spends in heap. + +Refs: + +* [.net Remarks on SecureString](https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-security-securestring) +* [Dotnet Design Conversation around replacing SecureString](https://github.com/dotnet/designs/pull/147) +* [Microsoft Docs on SecureString](https://learn.microsoft.com/en-us/dotnet/api/system.security.securestring) + +Relevant .net Code + +* +* [](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Security/SecureString.cs) + +Related Issues + +* https://github.com/BcryptNet/bcrypt.net/issues/83 +* https://github.com/dotnet/runtime/issues/118484#issuecomment-3165098658 diff --git a/docs/docs/enhancedEntropy.md b/docs/docs/enhancedEntropy.md index afaefb1..53edeb7 100644 --- a/docs/docs/enhancedEntropy.md +++ b/docs/docs/enhancedEntropy.md @@ -21,7 +21,9 @@ Version 3 of this extension *IS NOT BACKWARDS COMPATIBLE* with previous versions Version 5 of the library supports both V2 and V3 of the enhanced hashing. Enhanced Entropy V3 introduces an additional parameter for you to pass in a HMAC Key, this is used to HMAC the password before passing it to bcrypt. -Much like with V2 the intention is to increase the entropy of the password before passing it to bcrypt and to reduce the risk of truncation reducing the entropy of your password/pass-phrase. +Much like with V2 the intention is to increase the entropy of the password before passing it to bcrypt and to reduce the risk of truncation reducing the entropy of your password/pass-phrase but with an additional pepper. The HMAC key is not stored in the hash, so you must ensure that you have it available for verification, if you lose the key you will not be able to verify any hashes generated with it. + +```csharp ## Enhanced Entropy V2 @@ -49,4 +51,4 @@ var validatePassword = BCryptExtendedV2.Verify(myPassword, enhancedHashPassword, ## Enhanced Entropy V1 -This was marked obsolete in V1 and is removed in V5 +This was marked obsolete in V4 and is removed in V5 diff --git a/docs/docs/schemeVersions.md b/docs/docs/schemeVersions.md index 985e2f7..e38b061 100644 --- a/docs/docs/schemeVersions.md +++ b/docs/docs/schemeVersions.md @@ -32,7 +32,7 @@ $2y$ – Post Post-2011 bug discovery, $2y$ may be used to unambiguously use the produce the same result as using $2a$. ``` -First and foremost this library originated as a port of jBCrypt from `mindrot`, and subsequently the bcrypt revision +First and foremost this library originated as a port of jBCrypt, and subsequently the bcrypt revision was set to match, which in this case is `$2a$`. This has been changed as handling only the single revision causes issues cross-platform with implementations that moved altered their revision to handle migrations and other issues. diff --git a/docs/index.md b/docs/index.md index 5579d8d..d7dd8a0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -32,7 +32,7 @@ File-scoped namespaces are shown; imagine curly brackets if you need to. `Top level namespace` ```csharp -namespace DotNetSix; +namespace SomeNamespace; using BCryptNet; diff --git a/examples/AndroidTest/AndroidTest.csproj b/examples/AndroidTest/AndroidTest.csproj new file mode 100644 index 0000000..00b042c --- /dev/null +++ b/examples/AndroidTest/AndroidTest.csproj @@ -0,0 +1,71 @@ + + + + net10.0-android;net10.0-ios;net10.0-maccatalyst + $(TargetFrameworks);net10.0-windows10.0.19041.0 + + + + + + + Exe + AndroidTest + true + true + enable + enable + + + AndroidTest + + + com.bcryptnext.androidtest + + + 1.0 + 1 + + 11.0 + 13.1 + 21.0 + 10.0.17763.0 + 10.0.17763.0 + 6.5 + Debug;Release + AnyCPU + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/AndroidTest/App.xaml b/examples/AndroidTest/App.xaml new file mode 100644 index 0000000..af08f2b --- /dev/null +++ b/examples/AndroidTest/App.xaml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/examples/AndroidTest/App.xaml.cs b/examples/AndroidTest/App.xaml.cs new file mode 100644 index 0000000..4ae8f8a --- /dev/null +++ b/examples/AndroidTest/App.xaml.cs @@ -0,0 +1,31 @@ +// /* +// The MIT License (MIT) +// Copyright (c) 2006 Damien Miller djm@mindrot.org (jBCrypt) +// Copyright (c) 2013 Ryan D. Emerle (.Net port) +// Copyright (c) 2016 Chris McKee (.Net-core port / patches / new features) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// */ + +namespace AndroidTest +{ + public partial class App : Application + { + public App() + { + InitializeComponent(); + + MainPage = new AppShell(); + } + } +} diff --git a/examples/AndroidTest/AppShell.xaml b/examples/AndroidTest/AppShell.xaml new file mode 100644 index 0000000..0f22c85 --- /dev/null +++ b/examples/AndroidTest/AppShell.xaml @@ -0,0 +1,15 @@ + + + + + + diff --git a/examples/AndroidTest/AppShell.xaml.cs b/examples/AndroidTest/AppShell.xaml.cs new file mode 100644 index 0000000..304bd96 --- /dev/null +++ b/examples/AndroidTest/AppShell.xaml.cs @@ -0,0 +1,29 @@ +// /* +// The MIT License (MIT) +// Copyright (c) 2006 Damien Miller djm@mindrot.org (jBCrypt) +// Copyright (c) 2013 Ryan D. Emerle (.Net port) +// Copyright (c) 2016 Chris McKee (.Net-core port / patches / new features) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// */ + +namespace AndroidTest +{ + public partial class AppShell : Shell + { + public AppShell() + { + InitializeComponent(); + } + } +} diff --git a/examples/AndroidTest/MainPage.xaml b/examples/AndroidTest/MainPage.xaml new file mode 100644 index 0000000..2799e0b --- /dev/null +++ b/examples/AndroidTest/MainPage.xaml @@ -0,0 +1,43 @@ + + + + + + + +