├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── codeql.yml │ ├── dependency-review.yml │ ├── go.yml │ └── scorecards.yml ├── .golangci.yml ├── .pre-commit-config.yaml ├── .renovaterc ├── LICENSE ├── README.md ├── algorithm ├── argon2 │ ├── const.go │ ├── decoder.go │ ├── digest.go │ ├── doc.go │ ├── hasher.go │ ├── opts.go │ ├── profile.go │ └── variant.go ├── bcrypt │ ├── const.go │ ├── decoder.go │ ├── digest.go │ ├── doc.go │ ├── hasher.go │ ├── opts.go │ └── variant.go ├── const.go ├── doc.go ├── errors.go ├── md5crypt │ ├── const.go │ ├── decoder.go │ ├── digest.go │ ├── doc.go │ ├── hasher.go │ ├── opts.go │ └── variant.go ├── pbkdf2 │ ├── const.go │ ├── decoder.go │ ├── digest.go │ ├── doc.go │ ├── hasher.go │ ├── opts.go │ └── variant.go ├── plaintext │ ├── const.go │ ├── decoder.go │ ├── digest.go │ ├── doc.go │ ├── hasher.go │ ├── opts.go │ └── variant.go ├── scrypt │ ├── const.amd64.go │ ├── const.go │ ├── const.pure.go │ ├── decoder.go │ ├── digest.go │ ├── doc.go │ ├── hasher.go │ ├── opts.go │ └── variant.go ├── sha1crypt │ ├── const.go │ ├── decoder.go │ ├── digest.go │ ├── doc.go │ ├── hasher.go │ └── opts.go ├── shacrypt │ ├── const.go │ ├── decoder.go │ ├── digest.go │ ├── doc.go │ ├── hasher.go │ ├── opts.go │ └── variant.go └── types.go ├── const.go ├── const_test.go ├── decode.go ├── decoder.go ├── doc.go ├── go.mod ├── go.sum ├── helper.go ├── internal ├── encoding │ ├── base64adapted.go │ ├── const.go │ ├── digest.go │ ├── doc.go │ └── parameters.go ├── math │ ├── doc.go │ └── rounding.go └── random │ ├── bytes.go │ └── doc.go ├── normalize.go ├── t_argon2_test.go ├── t_bcrypt_test.go ├── t_pbkdf2_test.go ├── t_scrypt_test.go ├── t_shacrypt_test.go └── types.go /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # The maintainers team is a code owner for the whole repository. 2 | * @go-crypt/maintainers 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: ["master"] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: ["master"] 20 | schedule: 21 | - cron: "0 0 * * 1" 22 | 23 | permissions: 24 | contents: read 25 | 26 | jobs: 27 | analyze: 28 | name: Analyze 29 | runs-on: ubuntu-latest 30 | permissions: 31 | actions: read 32 | contents: read 33 | security-events: write 34 | 35 | strategy: 36 | fail-fast: false 37 | matrix: 38 | language: ["go"] 39 | # CodeQL supports [ $supported-codeql-languages ] 40 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 41 | 42 | steps: 43 | - name: Harden Runner 44 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 45 | with: 46 | egress-policy: audit 47 | 48 | - name: Checkout repository 49 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 50 | 51 | # Initializes the CodeQL tools for scanning. 52 | - name: Initialize CodeQL 53 | uses: github/codeql-action/init@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 54 | with: 55 | languages: ${{ matrix.language }} 56 | # If you wish to specify custom queries, you can do so here or in a config file. 57 | # By default, queries listed here will override any specified in a config file. 58 | # Prefix the list here with "+" to use these queries and those in the config file. 59 | 60 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 61 | # If this step fails, then you should remove it and run the build manually (see below) 62 | - name: Autobuild 63 | uses: github/codeql-action/autobuild@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 64 | 65 | # ℹ️ Command-line programs to run using the OS shell. 66 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 67 | 68 | # If the Autobuild fails above, remove it and uncomment the following three lines. 69 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 70 | 71 | # - run: | 72 | # echo "Run, Build Application using script" 73 | # ./location_of_script_within_repo/buildscript.sh 74 | 75 | - name: Perform CodeQL Analysis 76 | uses: github/codeql-action/analyze@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 77 | with: 78 | category: "/language:${{matrix.language}}" 79 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, 4 | # surfacing known-vulnerable versions of the packages declared or updated in the PR. 5 | # Once installed, if the workflow run is marked as required, 6 | # PRs introducing known-vulnerable packages will be blocked from merging. 7 | # 8 | # Source repository: https://github.com/actions/dependency-review-action 9 | name: 'Dependency Review' 10 | on: [pull_request] 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | dependency-review: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Harden Runner 20 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 21 | with: 22 | egress-policy: audit 23 | 24 | - name: 'Checkout Repository' 25 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 26 | - name: 'Dependency Review' 27 | uses: actions/dependency-review-action@38ecb5b593bf0eb19e335c03f97670f792489a8b # v4.7.0 28 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: 3 | pull_request: {} 4 | push: 5 | branches: 6 | - master 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | go: 17 | - '1.22' 18 | - '1.23' 19 | - '1.24' 20 | fail-fast: false 21 | steps: 22 | - name: Harden Runner 23 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 24 | with: 25 | egress-policy: audit 26 | 27 | - name: Set up Go ${{ matrix.go }} 28 | uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 29 | with: 30 | go-version: ${{ matrix.go }} 31 | - name: Check out code into the Go module directory 32 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 33 | - name: Get dependencies 34 | run: | 35 | go get -v -t -d ./... 36 | if [ -f Gopkg.toml ]; then 37 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 38 | dep ensure 39 | fi 40 | #- name: Build 41 | # run: go build -v ./... 42 | - name: Test 43 | run: go test -v ./... -------------------------------------------------------------------------------- /.github/workflows/scorecards.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. They are provided 2 | # by a third-party and are governed by separate terms of service, privacy 3 | # policy, and support documentation. 4 | 5 | name: Scorecard supply-chain security 6 | on: 7 | # For Branch-Protection check. Only the default branch is supported. See 8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 9 | branch_protection_rule: 10 | # To guarantee Maintained check is occasionally updated. See 11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained 12 | schedule: 13 | - cron: '20 7 * * 2' 14 | push: 15 | branches: ["master"] 16 | 17 | # Declare default permissions as read only. 18 | permissions: read-all 19 | 20 | jobs: 21 | analysis: 22 | name: Scorecard analysis 23 | runs-on: ubuntu-latest 24 | permissions: 25 | # Needed to upload the results to code-scanning dashboard. 26 | security-events: write 27 | # Needed to publish results and get a badge (see publish_results below). 28 | id-token: write 29 | contents: read 30 | actions: read 31 | 32 | steps: 33 | - name: Harden Runner 34 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 35 | with: 36 | egress-policy: audit 37 | 38 | - name: "Checkout code" 39 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 40 | with: 41 | persist-credentials: false 42 | 43 | - name: "Run analysis" 44 | uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 45 | with: 46 | results_file: results.sarif 47 | results_format: sarif 48 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: 49 | # - you want to enable the Branch-Protection check on a *public* repository, or 50 | # - you are installing Scorecards on a *private* repository 51 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. 52 | # repo_token: ${{ secrets.SCORECARD_TOKEN }} 53 | 54 | # Public repositories: 55 | # - Publish results to OpenSSF REST API for easy access by consumers 56 | # - Allows the repository to include the Scorecard badge. 57 | # - See https://github.com/ossf/scorecard-action#publishing-results. 58 | # For private repositories: 59 | # - `publish_results` will always be set to `false`, regardless 60 | # of the value entered here. 61 | publish_results: true 62 | 63 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 64 | # format to the repository Actions tab. 65 | - name: "Upload artifact" 66 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 67 | with: 68 | name: SARIF file 69 | path: results.sarif 70 | retention-days: 5 71 | 72 | # Upload the results to GitHub's code scanning dashboard. 73 | - name: "Upload to code-scanning" 74 | uses: github/codeql-action/upload-sarif@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 75 | with: 76 | sarif_file: results.sarif 77 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | run: 3 | timeout: 3m 4 | 5 | linters-settings: 6 | goconst: 7 | min-len: 2 8 | min-occurrences: 2 9 | gocyclo: 10 | min-complexity: 16 11 | godot: 12 | check-all: true 13 | goimports: 14 | local-prefixes: github.com/go-crypt/crypt 15 | 16 | linters: 17 | enable: 18 | - asciicheck 19 | - goconst 20 | - gocritic 21 | - gocyclo 22 | - godot 23 | - gofmt 24 | - goimports 25 | - gosec 26 | - misspell 27 | - nolintlint 28 | - prealloc 29 | - revive 30 | - unconvert 31 | - unparam 32 | - whitespace 33 | - wsl 34 | 35 | issues: 36 | exclude: 37 | - 'SA4011: ineffective break statement. Did you mean to break out of the outer loop' 38 | exclude-use-default: false 39 | max-issues-per-linter: 0 40 | max-same-issues: 0 41 | ... 42 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/gitleaks/gitleaks 3 | rev: v8.16.3 4 | hooks: 5 | - id: gitleaks 6 | - repo: https://github.com/golangci/golangci-lint 7 | rev: v1.52.2 8 | hooks: 9 | - id: golangci-lint 10 | - repo: https://github.com/pre-commit/pre-commit-hooks 11 | rev: v4.4.0 12 | hooks: 13 | - id: end-of-file-fixer 14 | - id: trailing-whitespace 15 | -------------------------------------------------------------------------------- /.renovaterc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "constraints": { 4 | "go": "1.24" 5 | }, 6 | "extends": [ 7 | "config:base", 8 | ":semanticCommitTypeAll(build)", 9 | ":separatePatchReleases" 10 | ], 11 | "ignorePresets": [ 12 | ":combinePatchMinorReleases", 13 | ":prHourlyLimit2", 14 | ":semanticPrefixFixDepsChoreOthers" 15 | ], 16 | "enabledManagers": [ 17 | "gomod" 18 | ], 19 | "postUpdateOptions": [ 20 | "gomodTidy", 21 | "gomodMassage" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 github.com/go-crypt/crypt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /algorithm/argon2/const.go: -------------------------------------------------------------------------------- 1 | package argon2 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | const ( 8 | // EncodingFmt is the encoding format for this algorithm. 9 | EncodingFmt = "$%s$v=%d$m=%d,t=%d,p=%d$%s$%s" 10 | 11 | // AlgName is the name for this algorithm. 12 | AlgName = "argon2" 13 | 14 | // AlgIdentifierVariantI is the identifier used in encoded argon2i variants of this algorithm. 15 | AlgIdentifierVariantI = argon2i 16 | 17 | // AlgIdentifierVariantD is the identifier used in encoded argon2d variants of this algorithm. 18 | AlgIdentifierVariantD = argon2d 19 | 20 | // AlgIdentifierVariantID is the identifier used in encoded argon2id variants of this algorithm. 21 | AlgIdentifierVariantID = argon2id 22 | 23 | // KeyLengthMin is the minimum tag length output. 24 | KeyLengthMin = 4 25 | 26 | // KeyLengthMax is the maximum tag length output. 27 | KeyLengthMax = math.MaxInt32 28 | 29 | // KeyLengthDefault is the default key length. 30 | KeyLengthDefault = 32 31 | 32 | // SaltLengthMin is the minimum salt length input/output. 33 | SaltLengthMin = 1 34 | 35 | // SaltLengthMax is the maximum salt length input/output. 36 | SaltLengthMax = math.MaxInt32 37 | 38 | // IterationsMin is the minimum number of passes input. 39 | IterationsMin = 1 40 | 41 | // IterationsMax is the maximum number of passes input. 42 | IterationsMax = math.MaxInt32 43 | 44 | // IterationsDefault is the default number of passes. 45 | IterationsDefault = IterationsMin 46 | 47 | // ParallelismMin is the minimum parallelism factor input. 48 | ParallelismMin = 1 49 | 50 | // ParallelismMax is the maximum parallelism factor input. 51 | ParallelismMax = 16777215 52 | 53 | // ParallelismDefault is the default parallelism factor. 54 | ParallelismDefault = 4 55 | 56 | // MemoryMinParallelismMultiplier is the parallelism multiplier which determines the minimum memory. 57 | MemoryMinParallelismMultiplier = 8 58 | 59 | // MemoryRoundingParallelismMultiplier is the parallelism multiplier which determines the actual memory value. The 60 | // value is the closest multiple of this multiplied by the parallelism input. 61 | MemoryRoundingParallelismMultiplier = 4 62 | 63 | // MemoryMin is the minimum input for memory. 64 | MemoryMin = ParallelismMin * MemoryMinParallelismMultiplier 65 | 66 | // MemoryMax is the maximum input for memory. 67 | MemoryMax uint32 = math.MaxUint32 68 | 69 | // MemoryDefault represents the default memory value. 70 | MemoryDefault = 2 * 1024 * 1024 71 | 72 | // PasswordInputSizeMax is the maximum input for the password content. 73 | PasswordInputSizeMax = math.MaxInt32 74 | ) 75 | 76 | const ( 77 | argon2i = "argon2i" 78 | argon2d = "argon2d" 79 | argon2id = "argon2id" 80 | 81 | variantDefault = VariantID 82 | 83 | oV = "v" 84 | oK = "k" 85 | oM = "m" 86 | oT = "t" 87 | oP = "p" 88 | ) 89 | -------------------------------------------------------------------------------- /algorithm/argon2/decoder.go: -------------------------------------------------------------------------------- 1 | package argon2 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "strconv" 7 | 8 | "github.com/go-crypt/x/argon2" 9 | 10 | "github.com/go-crypt/crypt/algorithm" 11 | "github.com/go-crypt/crypt/internal/encoding" 12 | ) 13 | 14 | // RegisterDecoder the decoder with the algorithm.DecoderRegister. 15 | func RegisterDecoder(r algorithm.DecoderRegister) (err error) { 16 | if err = RegisterDecoderArgon2id(r); err != nil { 17 | return err 18 | } 19 | 20 | if err = RegisterDecoderArgon2i(r); err != nil { 21 | return err 22 | } 23 | 24 | if err = RegisterDecoderArgon2d(r); err != nil { 25 | return err 26 | } 27 | 28 | return nil 29 | } 30 | 31 | // RegisterDecoderArgon2id registers specifically the argon2id decoder variant with the algorithm.DecoderRegister. 32 | func RegisterDecoderArgon2id(r algorithm.DecoderRegister) (err error) { 33 | if err = r.RegisterDecodeFunc(VariantID.Prefix(), DecodeVariant(VariantID)); err != nil { 34 | return err 35 | } 36 | 37 | return nil 38 | } 39 | 40 | // RegisterDecoderArgon2i registers specifically the argon2i decoder variant with the algorithm.DecoderRegister. 41 | func RegisterDecoderArgon2i(r algorithm.DecoderRegister) (err error) { 42 | if err = r.RegisterDecodeFunc(VariantI.Prefix(), DecodeVariant(VariantI)); err != nil { 43 | return err 44 | } 45 | 46 | return nil 47 | } 48 | 49 | // RegisterDecoderArgon2d registers specifically the argon2d decoder variant with the algorithm.DecoderRegister. 50 | func RegisterDecoderArgon2d(r algorithm.DecoderRegister) (err error) { 51 | if err = r.RegisterDecodeFunc(VariantD.Prefix(), DecodeVariant(VariantD)); err != nil { 52 | return err 53 | } 54 | 55 | return nil 56 | } 57 | 58 | // Decode the encoded digest into a algorithm.Digest. 59 | func Decode(encodedDigest string) (digest algorithm.Digest, err error) { 60 | return DecodeVariant(VariantNone)(encodedDigest) 61 | } 62 | 63 | // DecodeVariant the encoded digest into a algorithm.Digest provided it matches the provided argon2.Variant. If 64 | // argon2.VariantNone is used all variants can be decoded. 65 | func DecodeVariant(v Variant) func(encodedDigest string) (digest algorithm.Digest, err error) { 66 | return func(encodedDigest string) (digest algorithm.Digest, err error) { 67 | var ( 68 | parts []string 69 | variant Variant 70 | ) 71 | 72 | if variant, parts, err = decoderParts(encodedDigest); err != nil { 73 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) 74 | } 75 | 76 | if v != VariantNone && v != variant { 77 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, fmt.Errorf("the '%s' variant cannot be decoded only the '%s' variant can be", variant.String(), v.String())) 78 | } 79 | 80 | if digest, err = decode(variant, parts); err != nil { 81 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) 82 | } 83 | 84 | return digest, nil 85 | } 86 | } 87 | 88 | func decoderParts(encodedDigest string) (variant Variant, parts []string, err error) { 89 | parts = encoding.Split(encodedDigest, -1) 90 | 91 | if len(parts) != 6 { 92 | return VariantNone, nil, algorithm.ErrEncodedHashInvalidFormat 93 | } 94 | 95 | variant = NewVariant(parts[1]) 96 | 97 | if variant == VariantNone { 98 | return variant, nil, fmt.Errorf("%w: identifier '%s' is not an encoded %s digest", algorithm.ErrEncodedHashInvalidIdentifier, parts[1], AlgName) 99 | } 100 | 101 | return variant, parts[2:], nil 102 | } 103 | 104 | //nolint:gocyclo 105 | func decode(variant Variant, parts []string) (digest algorithm.Digest, err error) { 106 | decoded := &Digest{ 107 | variant: variant, 108 | } 109 | 110 | var ( 111 | value uint64 112 | bitSize int 113 | ) 114 | 115 | var params []encoding.Parameter 116 | 117 | if params, err = encoding.DecodeParameterStr(parts[1] + "," + parts[0]); err != nil { 118 | return nil, err 119 | } 120 | 121 | for _, param := range params { 122 | switch param.Key { 123 | case oV: 124 | bitSize = 8 125 | default: 126 | bitSize = 32 127 | } 128 | 129 | if value, err = strconv.ParseUint(param.Value, 10, bitSize); err != nil { 130 | return nil, fmt.Errorf("%w: option '%s' has invalid value '%s': %v", algorithm.ErrEncodedHashInvalidOptionValue, param.Key, param.Value, err) 131 | } 132 | 133 | switch param.Key { 134 | case oV: 135 | decoded.v = uint8(value) 136 | 137 | if decoded.v != argon2.Version { 138 | return nil, fmt.Errorf("%w: version %d is supported but encoded hash is version %d", algorithm.ErrEncodedHashInvalidVersion, argon2.Version, decoded.v) 139 | } 140 | case oK: 141 | break 142 | case oM: 143 | decoded.m = uint32(value) 144 | case oT: 145 | decoded.t = uint32(value) 146 | case oP: 147 | decoded.p = uint32(value) 148 | default: 149 | return nil, fmt.Errorf("%w: option '%s' with value '%s' is unknown", algorithm.ErrEncodedHashInvalidOptionKey, param.Key, param.Value) 150 | } 151 | } 152 | 153 | if decoded.salt, err = base64.RawStdEncoding.DecodeString(parts[2]); err != nil { 154 | return nil, fmt.Errorf("%w: %v", algorithm.ErrEncodedHashSaltEncoding, err) 155 | } 156 | 157 | if decoded.key, err = base64.RawStdEncoding.DecodeString(parts[3]); err != nil { 158 | return nil, fmt.Errorf("%w: %v", algorithm.ErrEncodedHashKeyEncoding, err) 159 | } 160 | 161 | if len(decoded.key) == 0 { 162 | return nil, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrEncodedHashKeyEncoding) 163 | } 164 | 165 | if decoded.t == 0 { 166 | decoded.t = 1 167 | } 168 | 169 | if decoded.p == 0 { 170 | decoded.p = 4 171 | } 172 | 173 | if decoded.m == 0 { 174 | decoded.m = 32 * 1024 175 | } 176 | 177 | return decoded, nil 178 | } 179 | -------------------------------------------------------------------------------- /algorithm/argon2/digest.go: -------------------------------------------------------------------------------- 1 | package argon2 2 | 3 | import ( 4 | "crypto/subtle" 5 | "encoding/base64" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/go-crypt/x/argon2" 10 | 11 | "github.com/go-crypt/crypt/algorithm" 12 | "github.com/go-crypt/crypt/internal/math" 13 | ) 14 | 15 | // Digest is a digest which handles Argon2 hashes like Argon2id, Argon2i, and Argon2d. 16 | type Digest struct { 17 | variant Variant 18 | 19 | v uint8 20 | 21 | m, t, p uint32 22 | 23 | salt, key []byte 24 | } 25 | 26 | // Match returns true if the string password matches the current argon2.Digest. 27 | func (d *Digest) Match(password string) (match bool) { 28 | return d.MatchBytes([]byte(password)) 29 | } 30 | 31 | // MatchBytes returns true if the []byte passwordBytes matches the current argon2.Digest. 32 | func (d *Digest) MatchBytes(passwordBytes []byte) (match bool) { 33 | match, _ = d.MatchBytesAdvanced(passwordBytes) 34 | 35 | return match 36 | } 37 | 38 | // MatchAdvanced is the same as Match except if there is an error it returns that as well. 39 | func (d *Digest) MatchAdvanced(password string) (match bool, err error) { 40 | return d.MatchBytesAdvanced([]byte(password)) 41 | } 42 | 43 | // MatchBytesAdvanced is the same as MatchBytes except if there is an error it returns that as well. 44 | func (d *Digest) MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) { 45 | if len(d.key) == 0 { 46 | return false, fmt.Errorf(algorithm.ErrFmtDigestMatch, AlgName, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrPasswordInvalid)) 47 | } 48 | 49 | return subtle.ConstantTimeCompare(d.key, d.variant.KeyFunc()(passwordBytes, d.salt, d.t, d.m, d.p, uint32(len(d.key)))) == 1, nil 50 | } 51 | 52 | // Encode returns the encoded form of this argon2.Digest. 53 | func (d *Digest) Encode() (encodedHash string) { 54 | return strings.ReplaceAll(fmt.Sprintf(EncodingFmt, 55 | d.variant.Prefix(), argon2.Version, 56 | d.m, d.t, d.p, 57 | base64.RawStdEncoding.EncodeToString(d.salt), base64.RawStdEncoding.EncodeToString(d.key), 58 | ), "\n", "") 59 | } 60 | 61 | // String returns the storable format of the argon2.Digest encoded hash. 62 | func (d *Digest) String() string { 63 | return d.Encode() 64 | } 65 | 66 | func (d *Digest) defaults() { 67 | switch d.variant { 68 | case VariantID, VariantI, VariantD: 69 | break 70 | default: 71 | d.variant = variantDefault 72 | } 73 | 74 | if d.t < IterationsMin { 75 | d.t = IterationsDefault 76 | } 77 | 78 | if d.p < ParallelismMin { 79 | d.p = ParallelismDefault 80 | } 81 | 82 | if d.m < MemoryMin { 83 | d.m = MemoryDefault 84 | } 85 | 86 | /* 87 | Memory size m MUST be an integer number of kibibytes from 8*p to 88 | 2^(32)-1. The actual number of blocks is m', which is m rounded 89 | down to the nearest multiple of 4*p. 90 | */ 91 | 92 | pM := d.p * MemoryRoundingParallelismMultiplier 93 | 94 | d.m = math.Uint32RoundDownToNearestMultiple(d.m, pM) 95 | } 96 | -------------------------------------------------------------------------------- /algorithm/argon2/doc.go: -------------------------------------------------------------------------------- 1 | // Package argon2 provides helpful abstractions for an implementation of RFC9106 and implements 2 | // github.com/go-crypt/crypt interfaces. 3 | // 4 | // This implementation is loaded by crypt.NewDefaultDecoder and crypt.NewDecoderAll. 5 | package argon2 6 | -------------------------------------------------------------------------------- /algorithm/argon2/hasher.go: -------------------------------------------------------------------------------- 1 | package argon2 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-crypt/crypt/algorithm" 7 | "github.com/go-crypt/crypt/internal/random" 8 | ) 9 | 10 | // New returns a new argon2.Hasher with the provided functional options applied. 11 | func New(opts ...Opt) (hasher *Hasher, err error) { 12 | hasher = &Hasher{} 13 | 14 | if err = hasher.WithOptions(opts...); err != nil { 15 | return nil, err 16 | } 17 | 18 | if err = hasher.Validate(); err != nil { 19 | return nil, err 20 | } 21 | 22 | return hasher, nil 23 | } 24 | 25 | // Hasher is a crypt.Hash for Argon2 which can be initialized via argon2.New using a functional options pattern. 26 | type Hasher struct { 27 | variant Variant 28 | 29 | s, k, t, p int 30 | 31 | m uint32 32 | 33 | d bool 34 | } 35 | 36 | // WithOptions applies the provided functional options provided as an argon2.Opt to the argon2.Hasher. 37 | func (h *Hasher) WithOptions(opts ...Opt) (err error) { 38 | for _, opt := range opts { 39 | if err = opt(h); err != nil { 40 | return err 41 | } 42 | } 43 | 44 | return nil 45 | } 46 | 47 | // Copy copies all parameters from this argon2.Hasher to another *argon2.Hasher. 48 | func (h *Hasher) Copy(hasher *Hasher) { 49 | hasher.variant, hasher.t, hasher.p, hasher.m, hasher.k, hasher.s = h.variant, h.t, h.p, h.m, h.k, h.s 50 | } 51 | 52 | // Clone returns a clone from this argon2.Hasher to another *argon2.Hasher. 53 | func (h *Hasher) Clone() *Hasher { 54 | return &Hasher{ 55 | variant: h.variant, 56 | t: h.t, 57 | p: h.p, 58 | m: h.m, 59 | k: h.k, 60 | s: h.s, 61 | } 62 | } 63 | 64 | // Merge copies all parameters from this argon2.Hasher to another *argon2.Hasher where the parameters are unset. 65 | func (h *Hasher) Merge(hash *Hasher) { 66 | if hash.variant == VariantNone { 67 | hash.variant = h.variant 68 | } 69 | 70 | if hash.t == 0 { 71 | hash.t = h.t 72 | } 73 | 74 | if hash.p == 0 { 75 | hash.p = h.p 76 | } 77 | 78 | if hash.m == 0 { 79 | hash.m = h.m 80 | } 81 | 82 | if hash.k == 0 { 83 | hash.k = h.k 84 | } 85 | 86 | if hash.s == 0 { 87 | hash.s = h.s 88 | } 89 | } 90 | 91 | // Hash performs the hashing operation and returns either a argon2.Digest or an error. 92 | func (h *Hasher) Hash(password string) (digest algorithm.Digest, err error) { 93 | h.defaults() 94 | 95 | if digest, err = h.hash(password); err != nil { 96 | return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) 97 | } 98 | 99 | return digest, nil 100 | } 101 | 102 | func (h *Hasher) hash(password string) (hashed algorithm.Digest, err error) { 103 | var salt []byte 104 | 105 | if salt, err = random.Bytes(h.s); err != nil { 106 | return nil, fmt.Errorf("%w: %v", algorithm.ErrSaltReadRandomBytes, err) 107 | } 108 | 109 | return h.hashWithSalt(password, salt) 110 | } 111 | 112 | // HashWithSalt overloads the Hash method allowing the user to provide a salt. It's recommended instead to configure the 113 | // salt size and let this be a random value generated using crypto/rand. 114 | func (h *Hasher) HashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { 115 | h.defaults() 116 | 117 | if digest, err = h.hashWithSalt(password, salt); err != nil { 118 | return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) 119 | } 120 | 121 | return digest, nil 122 | } 123 | 124 | func (h *Hasher) hashWithSalt(passwordRaw string, salt []byte) (digest algorithm.Digest, err error) { 125 | if s := len(salt); s > SaltLengthMax || s < SaltLengthMin { 126 | return nil, fmt.Errorf("%w: salt bytes must have a length of between %d and %d but has a length of %d", algorithm.ErrSaltInvalid, SaltLengthMin, SaltLengthMax, len(salt)) 127 | } 128 | 129 | password := []byte(passwordRaw) 130 | 131 | if len(password) > PasswordInputSizeMax { 132 | return nil, fmt.Errorf("%w: passwordRaw has a length of '%d' but must be less than or equal to %d", algorithm.ErrParameterInvalid, len(password), PasswordInputSizeMax) 133 | } 134 | 135 | d := &Digest{ 136 | variant: h.variant, 137 | t: uint32(h.t), 138 | p: uint32(h.p), 139 | m: h.m, 140 | salt: salt, 141 | } 142 | 143 | d.defaults() 144 | 145 | d.key = d.variant.KeyFunc()(password, d.salt, d.t, d.m, d.p, uint32(h.k)) 146 | 147 | return d, nil 148 | } 149 | 150 | // MustHash overloads the Hash method and panics if the error is not nil. It's recommended if you use this option to 151 | // utilize the Validate method first or handle the panic appropriately. 152 | func (h *Hasher) MustHash(password string) (hashed algorithm.Digest) { 153 | var err error 154 | 155 | if hashed, err = h.Hash(password); err != nil { 156 | panic(err) 157 | } 158 | 159 | return hashed 160 | } 161 | 162 | // Validate checks the settings/parameters for this argon2.Hasher and returns an error. 163 | func (h *Hasher) Validate() (err error) { 164 | if err = h.validate(); err != nil { 165 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, err) 166 | } 167 | 168 | return nil 169 | } 170 | 171 | func (h *Hasher) validate() (err error) { 172 | h.defaults() 173 | 174 | mMin := uint32(h.p) * MemoryMinParallelismMultiplier 175 | 176 | if h.m < mMin || h.m > MemoryMax { 177 | return fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "m", mMin, " (p * 8)", MemoryMax, h.m) 178 | } 179 | 180 | return nil 181 | } 182 | 183 | func (h *Hasher) defaults() { 184 | if h.d { 185 | return 186 | } 187 | 188 | h.d = true 189 | 190 | if h.k < KeyLengthMin { 191 | h.s = KeyLengthDefault 192 | } 193 | 194 | if h.s < SaltLengthMin { 195 | h.s = algorithm.SaltLengthDefault 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /algorithm/argon2/opts.go: -------------------------------------------------------------------------------- 1 | package argon2 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-crypt/crypt/algorithm" 7 | ) 8 | 9 | // Opt describes the functional option pattern for the argon2.Hasher. 10 | type Opt func(h *Hasher) (err error) 11 | 12 | // WithVariant is used to configure the argon2.Variant of the resulting argon2.Digest. 13 | // Default is argon2.VariantID. 14 | func WithVariant(variant Variant) Opt { 15 | return func(h *Hasher) (err error) { 16 | switch variant { 17 | case VariantNone, VariantI, VariantID, VariantD: 18 | h.variant = variant 19 | 20 | return nil 21 | default: 22 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant '%d' is invalid", algorithm.ErrParameterInvalid, variant)) 23 | } 24 | } 25 | } 26 | 27 | // WithVariantName uses the variant name or identifier to configure the argon2.Variant of the resulting argon2.Digest. 28 | // Default is argon2.VariantID. 29 | func WithVariantName(identifier string) Opt { 30 | return func(h *Hasher) (err error) { 31 | if identifier == "" { 32 | return nil 33 | } 34 | 35 | variant := NewVariant(identifier) 36 | 37 | if variant == VariantNone { 38 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant identifier '%s' is invalid", algorithm.ErrParameterInvalid, identifier)) 39 | } 40 | 41 | h.variant = variant 42 | 43 | return nil 44 | } 45 | } 46 | 47 | // WithVariantI satisfies the argon2.Opt type and sets the variant as argon2.VariantI. 48 | func WithVariantI() Opt { 49 | return func(h *Hasher) error { 50 | h.variant = VariantI 51 | 52 | return nil 53 | } 54 | } 55 | 56 | // WithVariantID satisfies the argon2.Opt type and sets the variant as argon2.VariantID. 57 | func WithVariantID() Opt { 58 | return func(h *Hasher) error { 59 | h.variant = VariantID 60 | 61 | return nil 62 | } 63 | } 64 | 65 | // WithVariantD satisfies the argon2.Opt type and sets the variant as argon2.VariantD. 66 | func WithVariantD() Opt { 67 | return func(h *Hasher) error { 68 | h.variant = VariantD 69 | 70 | return nil 71 | } 72 | } 73 | 74 | // WithP satisfies the argon2.Opt type for the argon2.Hasher and sets input 'p' known as the degree of parallelism. 75 | // 76 | // Degree of parallelism p determines how many independent (but synchronizing) computational chains (lanes) can be run. 77 | // It MUST be an integer value from 1 to 2^(24)-1. 78 | // 79 | // Minimum is 1, Maximum is 16777215. Default is 4. 80 | // 81 | // RFC9106 section 3.1 "Argon2 Inputs and Outputs" https://www.rfc-editor.org/rfc/rfc9106.html#name-argon2-inputs-and-outputs. 82 | func WithP(p int) Opt { 83 | return func(h *Hasher) (err error) { 84 | if p < ParallelismMin || p > ParallelismMax { 85 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "parallelism", ParallelismMin, "", ParallelismMax, p)) 86 | } 87 | 88 | h.p = p 89 | 90 | return nil 91 | } 92 | } 93 | 94 | // WithParallelism is an alias for WithP. 95 | func WithParallelism(p int) Opt { 96 | return WithP(p) 97 | } 98 | 99 | // WithM satisfies the argon2.Opt type for the argon2.Hasher and sets input 'm' known as the memory size. 100 | // 101 | // Memory size m MUST be an integer number of kibibytes from 8*p to 2^(32)-1. The actual number of blocks is m', which 102 | // is m rounded down to the nearest multiple of 4*p. 103 | // 104 | // Minimum is 8, Maximum is 4294967295. Default is 2097152. 105 | // 106 | // RFC9106 section 3.1 "Argon2 Inputs and Outputs" https://www.rfc-editor.org/rfc/rfc9106.html#name-argon2-inputs-and-outputs. 107 | func WithM(m uint32) Opt { 108 | return func(h *Hasher) (err error) { 109 | if m < MemoryMin || m > MemoryMax { 110 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "memory", MemoryMin, "", MemoryMax, m)) 111 | } 112 | 113 | h.m = m 114 | 115 | return nil 116 | } 117 | } 118 | 119 | // WithMemoryInKiB is an alias for WithM. 120 | func WithMemoryInKiB(m uint32) Opt { 121 | return WithM(m) 122 | } 123 | 124 | // WithT satisfies the argon2.Opt type for the argon2.Hasher and sets input 't' known as the number of passes. 125 | // 126 | // Number of passes t (used to tune the running time independently of the memory size) MUST be an integer number from 1 to 2^(32)-1. 127 | // 128 | // Minimum is 1, Maximum is 2147483647. Default is 1. 129 | // 130 | // RFC9106 section 3.1 "Argon2 Inputs and Outputs" https://www.rfc-editor.org/rfc/rfc9106.html#name-argon2-inputs-and-outputs. 131 | func WithT(t int) Opt { 132 | return func(h *Hasher) (err error) { 133 | if t < IterationsMin || t > IterationsMax { 134 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "t", IterationsMin, "", IterationsMax, t)) 135 | } 136 | 137 | h.t = t 138 | 139 | return nil 140 | } 141 | } 142 | 143 | // WithIterations is an alias for WithT. 144 | func WithIterations(t int) Opt { 145 | return WithT(t) 146 | } 147 | 148 | // WithK satisfies the argon2.Opt type for the argon2.Hasher and sets input 'T' known as the tag length. 149 | // 150 | // Tag length T MUST be an integer number of bytes from 4 to 2^(32)-1. The Argon2 output, or "tag", is a string T bytes long. 151 | // 152 | // Minimum is 4, Maximum is 2147483647. Default is 32. 153 | // 154 | // RFC9106 section 3.1 "Argon2 Inputs and Outputs" https://www.rfc-editor.org/rfc/rfc9106.html#name-argon2-inputs-and-outputs. 155 | func WithK(k int) Opt { 156 | return func(h *Hasher) (err error) { 157 | if k < KeyLengthMin || k > KeyLengthMax { 158 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "k", KeyLengthMin, "", KeyLengthMax, k)) 159 | } 160 | 161 | h.k = k 162 | 163 | return nil 164 | } 165 | } 166 | 167 | // WithTagLength is an alias for WithK. 168 | func WithTagLength(k int) Opt { 169 | return WithK(k) 170 | } 171 | 172 | // WithKeyLength is an alias for WithK. 173 | func WithKeyLength(k int) Opt { 174 | return WithK(k) 175 | } 176 | 177 | // WithS satisfies the argon2.Opt type for the argon2.Hasher and sets the length of input 'S' known as the salt length. 178 | // 179 | // Nonce S, which is a salt for password hashing applications. It MUST have a length not greater than 2^(32)-1 bytes. 180 | // 16 bytes is RECOMMENDED for password hashing. The salt SHOULD be unique for each password. 181 | // 182 | // Minimum is 1, Maximum is 2147483647. Default is 16. 183 | // 184 | // RFC9106 section 3.1 "Argon2 Inputs and Outputs" https://www.rfc-editor.org/rfc/rfc9106.html#name-argon2-inputs-and-outputs. 185 | func WithS(s int) Opt { 186 | return func(h *Hasher) (err error) { 187 | if s < SaltLengthMin || s > SaltLengthMax { 188 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "s", SaltLengthMin, "", SaltLengthMax, s)) 189 | } 190 | 191 | h.s = s 192 | 193 | return nil 194 | } 195 | } 196 | 197 | // WithSaltLength is an alias for WithS. 198 | func WithSaltLength(s int) Opt { 199 | return WithS(s) 200 | } 201 | 202 | // WithProfileRFC9106Recommended is the recommended standard RFC9106 profile. 203 | // 204 | // RFC9106 section 4.0 "Parameter Choice" https://www.rfc-editor.org/rfc/rfc9106.html#name-parameter-choice 205 | func WithProfileRFC9106Recommended() Opt { 206 | return func(h *Hasher) (err error) { 207 | ProfileRFC9106Recommended.Hasher().Merge(h) 208 | 209 | return nil 210 | } 211 | } 212 | 213 | // WithProfileRFC9106LowMemory is the recommended low memory RFC9106 profile. 214 | // 215 | // RFC9106 section 4.0 "Parameter Choice" https://www.rfc-editor.org/rfc/rfc9106.html#name-parameter-choice 216 | func WithProfileRFC9106LowMemory() Opt { 217 | return func(h *Hasher) (err error) { 218 | ProfileRFC9106LowMemory.Hasher().Merge(h) 219 | 220 | return nil 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /algorithm/argon2/profile.go: -------------------------------------------------------------------------------- 1 | package argon2 2 | 3 | import ( 4 | "github.com/go-crypt/crypt/algorithm" 5 | ) 6 | 7 | // Profile represents a hashing profile for Argon2Hash. 8 | type Profile int 9 | 10 | const ( 11 | // ProfileRFC9106LowMemory is the RFC9106 low memory profile. 12 | ProfileRFC9106LowMemory Profile = iota 13 | 14 | // ProfileRFC9106Recommended is the RFC9106 recommended profile. 15 | ProfileRFC9106Recommended 16 | ) 17 | 18 | // Hasher returns the argon2.Profile parameters as an argon2.Hasher. 19 | func (p Profile) Hasher() *Hasher { 20 | switch p { 21 | case ProfileRFC9106LowMemory: 22 | return &Hasher{variant: VariantID, t: 3, p: 4, m: 64 * 1024, k: KeyLengthDefault, s: algorithm.SaltLengthDefault} 23 | case ProfileRFC9106Recommended: 24 | return &Hasher{variant: VariantID, t: IterationsDefault, p: ParallelismDefault, m: MemoryDefault, k: KeyLengthDefault, s: algorithm.SaltLengthDefault} 25 | default: 26 | return ProfileRFC9106Recommended.Hasher() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /algorithm/argon2/variant.go: -------------------------------------------------------------------------------- 1 | package argon2 2 | 3 | import ( 4 | "github.com/go-crypt/x/argon2" 5 | ) 6 | 7 | // NewVariant converts an identifier string to a argon2.Variant. 8 | func NewVariant(identifier string) (variant Variant) { 9 | switch identifier { 10 | case AlgIdentifierVariantID: 11 | return VariantID 12 | case AlgIdentifierVariantI: 13 | return VariantI 14 | case AlgIdentifierVariantD: 15 | return VariantD 16 | default: 17 | return VariantNone 18 | } 19 | } 20 | 21 | // Variant is a variant of the argon2.Digest. 22 | type Variant int 23 | 24 | const ( 25 | // VariantNone is a variant of the argon2.Digest which is unknown. 26 | VariantNone Variant = iota 27 | 28 | // VariantD is the argon2d variant of the argon2.Digest. 29 | VariantD 30 | 31 | // VariantI is the argon2i variant of the argon2.Digest. 32 | VariantI 33 | 34 | // VariantID is the argon2id variant of the argon2.Digest. 35 | VariantID 36 | ) 37 | 38 | // String implements the fmt.Stringer returning a string representation of the argon2.Variant. 39 | func (v Variant) String() string { 40 | return v.Prefix() 41 | } 42 | 43 | // Prefix returns the argon2.Variant prefix identifier. 44 | func (v Variant) Prefix() (prefix string) { 45 | switch v { 46 | case VariantID: 47 | return AlgIdentifierVariantID 48 | case VariantI: 49 | return AlgIdentifierVariantI 50 | case VariantD: 51 | return AlgIdentifierVariantD 52 | default: 53 | return 54 | } 55 | } 56 | 57 | // KeyFunc returns the argon2.KeyFunc key derivation function of this argon2.Variant. 58 | func (v Variant) KeyFunc() argon2.KeyFunc { 59 | switch v { 60 | case VariantID: 61 | return argon2.IDKey 62 | case VariantI: 63 | return argon2.IKey 64 | case VariantD: 65 | return argon2.DKey 66 | default: 67 | return nil 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /algorithm/bcrypt/const.go: -------------------------------------------------------------------------------- 1 | package bcrypt 2 | 3 | import ( 4 | "github.com/go-crypt/crypt/algorithm" 5 | ) 6 | 7 | const ( 8 | // EncodingFmt is the encoding format for this algorithm. 9 | EncodingFmt = "$%s$%d$%s%s" 10 | 11 | // EncodingFmtSHA256 is the encoding format for the SHA256 variant of this algorithm. 12 | EncodingFmtSHA256 = "$%s$v=2,t=%s,r=%d$%s$%s" 13 | 14 | // AlgName is the name for this algorithm. 15 | AlgName = "bcrypt" 16 | 17 | // AlgIdentifier is the identifier used in this algorithm. 18 | AlgIdentifier = "2b" 19 | 20 | // AlgIdentifierVariantSHA256 is the identifier used in encoded SHA256 variant of this algorithm. 21 | AlgIdentifierVariantSHA256 = "bcrypt-sha256" 22 | 23 | // AlgIdentifierVerA is the identifier used in this algorithm (version a). 24 | AlgIdentifierVerA = "2a" 25 | 26 | // AlgIdentifierVerX is the identifier used in this algorithm (version x). 27 | AlgIdentifierVerX = "2x" 28 | 29 | // AlgIdentifierVerY is the identifier used in this algorithm (version y). 30 | AlgIdentifierVerY = "2y" 31 | 32 | // AlgIdentifierUnversioned is the identifier used in this algorithm (no version). 33 | AlgIdentifierUnversioned = "2" 34 | 35 | // VariantNameStandard is the variant name of the bcrypt.VariantStandard. 36 | VariantNameStandard = "standard" 37 | 38 | // VariantNameSHA256 is the variant name of the bcrypt.VariantSHA256. 39 | VariantNameSHA256 = algorithm.DigestSHA256 40 | 41 | // IterationsMin is the minimum iterations accepted. 42 | IterationsMin = 10 43 | 44 | // IterationsMax is the maximum iterations accepted. 45 | IterationsMax = 31 46 | 47 | // IterationsDefault is the default iterations. 48 | IterationsDefault = 13 49 | 50 | // PasswordInputSizeMax is the maximum password input size accepted. 51 | PasswordInputSizeMax = 72 52 | 53 | variantDefault = VariantStandard 54 | ) 55 | 56 | const ( 57 | oV = "v" 58 | oT = "t" 59 | oR = "r" 60 | ) 61 | -------------------------------------------------------------------------------- /algorithm/bcrypt/decoder.go: -------------------------------------------------------------------------------- 1 | package bcrypt 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/go-crypt/x/bcrypt" 8 | 9 | "github.com/go-crypt/crypt/algorithm" 10 | "github.com/go-crypt/crypt/internal/encoding" 11 | ) 12 | 13 | // RegisterDecoder the decoder with the algorithm.DecoderRegister. 14 | func RegisterDecoder(r algorithm.DecoderRegister) (err error) { 15 | if err = RegisterDecoderStandard(r); err != nil { 16 | return err 17 | } 18 | 19 | if err = RegisterDecoderSHA256(r); err != nil { 20 | return err 21 | } 22 | 23 | return nil 24 | } 25 | 26 | // RegisterDecoderStandard registers specifically the standard decoder variant with the algorithm.DecoderRegister. 27 | func RegisterDecoderStandard(r algorithm.DecoderRegister) (err error) { 28 | decodefunc := DecodeVariant(VariantStandard) 29 | 30 | if err = r.RegisterDecodeFunc(VariantStandard.Prefix(), decodefunc); err != nil { 31 | return err 32 | } 33 | 34 | if err = r.RegisterDecodeFunc(AlgIdentifierVerA, decodefunc); err != nil { 35 | return err 36 | } 37 | 38 | if err = r.RegisterDecodeFunc(AlgIdentifierVerX, decodefunc); err != nil { 39 | return err 40 | } 41 | 42 | if err = r.RegisterDecodeFunc(AlgIdentifierVerY, decodefunc); err != nil { 43 | return err 44 | } 45 | 46 | if err = r.RegisterDecodeFunc(AlgIdentifierUnversioned, decodefunc); err != nil { 47 | return err 48 | } 49 | 50 | return nil 51 | } 52 | 53 | // RegisterDecoderSHA256 registers specifically the sha256 decoder variant with the algorithm.DecoderRegister. 54 | func RegisterDecoderSHA256(r algorithm.DecoderRegister) (err error) { 55 | if err = r.RegisterDecodeFunc(VariantSHA256.Prefix(), DecodeVariant(VariantSHA256)); err != nil { 56 | return err 57 | } 58 | 59 | return nil 60 | } 61 | 62 | // Decode the encoded digest into a algorithm.Digest. 63 | func Decode(encodedDigest string) (digest algorithm.Digest, err error) { 64 | return DecodeVariant(VariantNone)(encodedDigest) 65 | } 66 | 67 | // DecodeVariant the encoded digest into a algorithm.Digest provided it matches the provided bcrypt.Variant. If 68 | // bcrypt.VariantNone is used all variants can be decoded. 69 | func DecodeVariant(v Variant) func(encodedDigest string) (digest algorithm.Digest, err error) { 70 | return func(encodedDigest string) (digest algorithm.Digest, err error) { 71 | var ( 72 | parts []string 73 | variant Variant 74 | ) 75 | 76 | if variant, parts, err = decoderParts(encodedDigest); err != nil { 77 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) 78 | } 79 | 80 | if v != VariantNone && v != variant { 81 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, fmt.Errorf("the '%s' variant cannot be decoded only the '%s' variant can be", variant.String(), v.String())) 82 | } 83 | 84 | if digest, err = decode(variant, parts); err != nil { 85 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) 86 | } 87 | 88 | return digest, nil 89 | } 90 | } 91 | 92 | func decoderParts(encodedDigest string) (variant Variant, parts []string, err error) { 93 | parts = encoding.Split(encodedDigest, -1) 94 | 95 | if len(parts) < 4 { 96 | return VariantNone, nil, algorithm.ErrEncodedHashInvalidFormat 97 | } 98 | 99 | variant = NewVariant(parts[1]) 100 | 101 | if variant == VariantNone { 102 | return variant, nil, fmt.Errorf("%w: identifier '%s' is not an encoded %s digest", algorithm.ErrEncodedHashInvalidIdentifier, parts[1], AlgName) 103 | } 104 | 105 | return variant, parts[2:], nil 106 | } 107 | 108 | func decode(variant Variant, parts []string) (digest algorithm.Digest, err error) { 109 | countParts := len(parts) 110 | 111 | var ( 112 | salt, key []byte 113 | ) 114 | 115 | decoded := &Digest{ 116 | variant: variant, 117 | } 118 | 119 | switch decoded.variant { 120 | case VariantStandard: 121 | if countParts != 2 { 122 | return nil, algorithm.ErrEncodedHashInvalidFormat 123 | } 124 | 125 | if decoded.iterations, err = strconv.Atoi(parts[0]); err != nil { 126 | return nil, fmt.Errorf("%w: iterations could not be parsed: %v", algorithm.ErrEncodedHashInvalidOptionValue, err) 127 | } 128 | 129 | switch n, i := len(parts[1]), bcrypt.EncodedSaltSize+bcrypt.EncodedHashSize; n { 130 | case i: 131 | break 132 | case 0: 133 | return nil, fmt.Errorf("%w: key is expected to be %d bytes but it was empty", algorithm.ErrEncodedHashKeyEncoding, i) 134 | default: 135 | return nil, fmt.Errorf("%w: key is expected to be %d bytes but it has %d bytes", algorithm.ErrEncodedHashKeyEncoding, i, n) 136 | } 137 | 138 | salt, key = bcrypt.DecodeSecret([]byte(parts[1])) 139 | case VariantSHA256: 140 | if countParts != 3 { 141 | return nil, algorithm.ErrEncodedHashInvalidFormat 142 | } 143 | 144 | salt, key = []byte(parts[1]), []byte(parts[2]) 145 | 146 | switch n := len(salt); n { 147 | case bcrypt.EncodedSaltSize: 148 | break 149 | case 0: 150 | return nil, fmt.Errorf("%w: salt is expected to be %d bytes but it was empty", algorithm.ErrEncodedHashSaltEncoding, bcrypt.EncodedSaltSize) 151 | default: 152 | return nil, fmt.Errorf("%w: salt is expected to be %d bytes but it has %d bytes", algorithm.ErrEncodedHashSaltEncoding, bcrypt.EncodedSaltSize, n) 153 | } 154 | 155 | switch n := len(key); n { 156 | case bcrypt.EncodedHashSize: 157 | break 158 | case 0: 159 | return nil, fmt.Errorf("%w: key is expected to be %d bytes but it was empty", algorithm.ErrEncodedHashKeyEncoding, bcrypt.EncodedHashSize) 160 | default: 161 | return nil, fmt.Errorf("%w: key is expected to be %d bytes but it has %d bytes", algorithm.ErrEncodedHashKeyEncoding, bcrypt.EncodedHashSize, n) 162 | } 163 | 164 | var params []encoding.Parameter 165 | 166 | if params, err = encoding.DecodeParameterStr(parts[0]); err != nil { 167 | return nil, err 168 | } 169 | 170 | for _, param := range params { 171 | switch param.Key { 172 | case oV, oT: 173 | break 174 | case oR: 175 | decoded.iterations, err = param.Int() 176 | default: 177 | return nil, fmt.Errorf("%w: option '%s' with value '%s' is unknown", algorithm.ErrEncodedHashInvalidOptionKey, param.Key, param.Value) 178 | } 179 | 180 | if err != nil { 181 | return nil, fmt.Errorf("%w: option '%s' has invalid value '%s': %v", algorithm.ErrEncodedHashInvalidOptionValue, param.Key, param.Value, err) 182 | } 183 | } 184 | } 185 | 186 | if decoded.salt, err = bcrypt.Base64Decode(salt); err != nil { 187 | return nil, fmt.Errorf("%w: %v", algorithm.ErrEncodedHashSaltEncoding, err) 188 | } 189 | 190 | if len(key) == 0 { 191 | return nil, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrEncodedHashKeyEncoding) 192 | } 193 | 194 | decoded.key = key 195 | 196 | return decoded, nil 197 | } 198 | -------------------------------------------------------------------------------- /algorithm/bcrypt/digest.go: -------------------------------------------------------------------------------- 1 | package bcrypt 2 | 3 | import ( 4 | "crypto/subtle" 5 | "fmt" 6 | 7 | "github.com/go-crypt/x/bcrypt" 8 | 9 | "github.com/go-crypt/crypt/algorithm" 10 | ) 11 | 12 | // Digest is a digest which handles bcrypt hashes. 13 | type Digest struct { 14 | variant Variant 15 | 16 | iterations int 17 | 18 | salt, key []byte 19 | } 20 | 21 | // Match returns true if the string password matches the current bcrypt.Digest. 22 | func (d *Digest) Match(password string) (match bool) { 23 | return d.MatchBytes([]byte(password)) 24 | } 25 | 26 | // MatchBytes returns true if the []byte passwordBytes matches the current bcrypt.Digest. 27 | func (d *Digest) MatchBytes(passwordBytes []byte) (match bool) { 28 | match, _ = d.MatchBytesAdvanced(passwordBytes) 29 | 30 | return match 31 | } 32 | 33 | // MatchAdvanced is the same as Match except if there is an error it returns that as well. 34 | func (d *Digest) MatchAdvanced(password string) (match bool, err error) { 35 | return d.MatchBytesAdvanced([]byte(password)) 36 | } 37 | 38 | // MatchBytesAdvanced is the same as MatchBytes except if there is an error it returns that as well. 39 | func (d *Digest) MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) { 40 | if len(d.key) == 0 { 41 | return false, fmt.Errorf(algorithm.ErrFmtDigestMatch, AlgName, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrPasswordInvalid)) 42 | } 43 | 44 | input := d.variant.EncodeInput(passwordBytes, d.salt) 45 | 46 | var key []byte 47 | 48 | if key, err = bcrypt.Key(input, d.salt, d.iterations); err != nil { 49 | return false, fmt.Errorf(algorithm.ErrFmtDigestMatch, AlgName, fmt.Errorf("%w: %v", algorithm.ErrKeyDerivation, err)) 50 | } 51 | 52 | return subtle.ConstantTimeCompare(d.key, key) == 1, nil 53 | } 54 | 55 | // Encode returns the encoded form of this bcrypt.Digest. 56 | func (d *Digest) Encode() string { 57 | return d.variant.Encode(d.iterations, AlgIdentifier, bcrypt.Base64Encode(d.salt), d.key) 58 | } 59 | 60 | // String returns the storable format of the bcrypt.Digest encoded hash. 61 | func (d *Digest) String() string { 62 | return d.Encode() 63 | } 64 | 65 | func (d *Digest) defaults() { 66 | switch d.variant { 67 | case VariantNone: 68 | d.variant = VariantStandard 69 | case VariantStandard, VariantSHA256: 70 | break 71 | default: 72 | d.variant = variantDefault 73 | } 74 | 75 | if d.iterations < IterationsMin { 76 | d.iterations = IterationsDefault 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /algorithm/bcrypt/doc.go: -------------------------------------------------------------------------------- 1 | // Package bcrypt provides helpful abstractions for an implementation of bcrypt and implements 2 | // github.com/go-crypt/crypt interfaces. 3 | // 4 | // This implementation is loaded by crypt.NewDefaultDecoder and crypt.NewDecoderAll. 5 | package bcrypt 6 | -------------------------------------------------------------------------------- /algorithm/bcrypt/hasher.go: -------------------------------------------------------------------------------- 1 | package bcrypt 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-crypt/x/bcrypt" 7 | 8 | "github.com/go-crypt/crypt/algorithm" 9 | "github.com/go-crypt/crypt/internal/random" 10 | ) 11 | 12 | // New returns a new bcrypt.Hasher with the provided functional options applied. 13 | func New(opts ...Opt) (hasher *Hasher, err error) { 14 | hasher = &Hasher{} 15 | 16 | if err = hasher.WithOptions(opts...); err != nil { 17 | return nil, err 18 | } 19 | 20 | if err = hasher.Validate(); err != nil { 21 | return nil, err 22 | } 23 | 24 | return hasher, nil 25 | } 26 | 27 | // NewSHA256 returns a new bcrypt.Hasher with the provided functional options applied as well as the bcrypt.VariantSHA256 28 | // applied via the bcrypt.WithVariant bcrypt.Opt. 29 | func NewSHA256(opts ...Opt) (hasher *Hasher, err error) { 30 | if hasher, err = New(opts...); err != nil { 31 | return nil, err 32 | } 33 | 34 | if err = hasher.WithOptions(WithVariant(VariantSHA256)); err != nil { 35 | return nil, err 36 | } 37 | 38 | return hasher, nil 39 | } 40 | 41 | // Hasher is a crypt.Hash for bcrypt which can be initialized via bcrypt.New using a functional options pattern. 42 | type Hasher struct { 43 | variant Variant 44 | 45 | iterations int 46 | } 47 | 48 | // WithOptions applies the provided functional options provided as an bcrypt.Opt to the bcrypt.Hasher. 49 | func (h *Hasher) WithOptions(opts ...Opt) (err error) { 50 | for _, opt := range opts { 51 | if err = opt(h); err != nil { 52 | return err 53 | } 54 | } 55 | 56 | return nil 57 | } 58 | 59 | // Hash performs the hashing operation and returns either an algorithm.Digest or an error. 60 | func (h *Hasher) Hash(password string) (digest algorithm.Digest, err error) { 61 | if digest, err = h.hash(password); err != nil { 62 | return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) 63 | } 64 | 65 | return digest, nil 66 | } 67 | 68 | func (h *Hasher) hash(password string) (digest algorithm.Digest, err error) { 69 | var salt []byte 70 | 71 | if salt, err = random.Bytes(algorithm.SaltLengthDefault); err != nil { 72 | return nil, fmt.Errorf("%w: %v", algorithm.ErrSaltReadRandomBytes, err) 73 | } 74 | 75 | return h.hashWithSalt(password, salt) 76 | } 77 | 78 | // HashWithSalt overloads the Hash method allowing the user to provide a salt. It's recommended instead to configure the 79 | // salt size and let this be a random value generated using crypto/rand. 80 | func (h *Hasher) HashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { 81 | if digest, err = h.hashWithSalt(password, salt); err != nil { 82 | return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) 83 | } 84 | 85 | return digest, nil 86 | } 87 | 88 | func (h *Hasher) hashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { 89 | if len(salt) != algorithm.SaltLengthDefault { 90 | return nil, fmt.Errorf("%w: salt size must be 16 bytes but it's %d bytes", algorithm.ErrSaltInvalid, len(salt)) 91 | } 92 | 93 | d := &Digest{ 94 | variant: h.variant, 95 | iterations: h.iterations, 96 | salt: salt, 97 | } 98 | 99 | d.defaults() 100 | 101 | passwordMaxLen := d.variant.PasswordMaxLength() 102 | 103 | if passwordMaxLen != -1 && len(password) > passwordMaxLen { 104 | return nil, fmt.Errorf("%w: password must be %d bytes or less but it's %d bytes", algorithm.ErrPasswordInvalid, passwordMaxLen, len(password)) 105 | } 106 | 107 | if d.key, err = bcrypt.Key(d.variant.EncodeInput([]byte(password), salt), salt, d.iterations); err != nil { 108 | return nil, fmt.Errorf("%w: %v", algorithm.ErrKeyDerivation, err) 109 | } 110 | 111 | return d, nil 112 | } 113 | 114 | // MustHash overloads the Hash method and panics if the error is not nil. It's recommended if you use this option to 115 | // utilize the Validate method first or handle the panic appropriately. 116 | func (h *Hasher) MustHash(password string) (digest algorithm.Digest) { 117 | var err error 118 | 119 | if digest, err = h.Hash(password); err != nil { 120 | panic(err) 121 | } 122 | 123 | return digest 124 | } 125 | 126 | // Validate checks the settings/parameters for this bcrypt.Hasher and returns an error. 127 | func (h *Hasher) Validate() (err error) { 128 | if err = h.validate(); err != nil { 129 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, err) 130 | } 131 | 132 | return nil 133 | } 134 | 135 | func (h *Hasher) validate() (err error) { 136 | return nil 137 | } 138 | -------------------------------------------------------------------------------- /algorithm/bcrypt/opts.go: -------------------------------------------------------------------------------- 1 | package bcrypt 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-crypt/crypt/algorithm" 7 | ) 8 | 9 | // Opt describes the functional option pattern for the bcrypt.Hasher. 10 | type Opt func(h *Hasher) (err error) 11 | 12 | // WithVariant is used to configure the bcrypt.Variant of the resulting bcrypt.Digest. 13 | // Default is bcrypt.VariantStandard. 14 | func WithVariant(variant Variant) Opt { 15 | return func(h *Hasher) (err error) { 16 | switch variant { 17 | case VariantNone, VariantStandard, VariantSHA256: 18 | h.variant = variant 19 | 20 | return nil 21 | default: 22 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant '%d' is invalid", algorithm.ErrParameterInvalid, variant)) 23 | } 24 | } 25 | } 26 | 27 | // WithVariantName uses the variant name or identifier to configure the bcrypt.Variant of the resulting bcrypt.Digest. 28 | // Default is bcrypt.VariantStandard. 29 | func WithVariantName(identifier string) Opt { 30 | return func(h *Hasher) (err error) { 31 | if identifier == "" { 32 | return nil 33 | } 34 | 35 | variant := NewVariant(identifier) 36 | 37 | if variant == VariantNone { 38 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant identifier '%s' is invalid", algorithm.ErrParameterInvalid, identifier)) 39 | } 40 | 41 | h.variant = variant 42 | 43 | return nil 44 | } 45 | } 46 | 47 | // WithIterations sets the iterations parameter of the resulting bcrypt.Digest. 48 | // Minimum is 10, Maximum is 31. Default is 12. 49 | func WithIterations(iterations int) Opt { 50 | return func(h *Hasher) (err error) { 51 | if iterations < IterationsMin || iterations > IterationsMax { 52 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "iterations", IterationsMin, "", IterationsMax, iterations)) 53 | } 54 | 55 | h.iterations = iterations 56 | 57 | return nil 58 | } 59 | } 60 | 61 | // WithCost is an alias for bcrypt.WithIterations. 62 | func WithCost(iterations int) Opt { 63 | return WithIterations(iterations) 64 | } 65 | -------------------------------------------------------------------------------- /algorithm/bcrypt/variant.go: -------------------------------------------------------------------------------- 1 | package bcrypt 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha256" 6 | "encoding/base64" 7 | "fmt" 8 | 9 | "github.com/go-crypt/x/bcrypt" 10 | ) 11 | 12 | // NewVariant converts an identifier string to a bcrypt.Variant. 13 | func NewVariant(identifier string) (variant Variant) { 14 | switch identifier { 15 | case AlgIdentifier, AlgIdentifierVerA, AlgIdentifierVerX, AlgIdentifierVerY, "", VariantNameStandard, "common": 16 | return VariantStandard 17 | case AlgIdentifierVariantSHA256, VariantNameSHA256: 18 | return VariantSHA256 19 | default: 20 | return VariantNone 21 | } 22 | } 23 | 24 | // Variant is a variant of the bcrypt.Digest. 25 | type Variant int 26 | 27 | const ( 28 | // VariantNone is a variant of the bcrypt.Digest which is unknown. 29 | VariantNone Variant = iota 30 | 31 | // VariantStandard is the standard variant of bcrypt.Digest. 32 | VariantStandard 33 | 34 | // VariantSHA256 is the variant of bcrypt.Digest which hashes the password with HMAC-SHA256. 35 | VariantSHA256 36 | ) 37 | 38 | // String implements the fmt.Stringer returning a string representation of the bcrypt.Variant. 39 | func (v Variant) String() (name string) { 40 | switch v { 41 | case VariantStandard: 42 | return VariantNameStandard 43 | case VariantSHA256: 44 | return VariantNameSHA256 45 | default: 46 | return 47 | } 48 | } 49 | 50 | // Prefix returns the bcrypt.Variant prefix identifier. 51 | func (v Variant) Prefix() (prefix string) { 52 | switch v { 53 | case VariantStandard: 54 | return AlgIdentifier 55 | case VariantSHA256: 56 | return AlgIdentifierVariantSHA256 57 | default: 58 | return 59 | } 60 | } 61 | 62 | // PasswordMaxLength returns -1 if the variant has no max length, otherwise returns the maximum password length. 63 | func (v Variant) PasswordMaxLength() int { 64 | switch v { 65 | case VariantSHA256: 66 | return -1 67 | default: 68 | return PasswordInputSizeMax 69 | } 70 | } 71 | 72 | // Encode formats the variant encoded bcrypt.Digest. 73 | func (v Variant) Encode(cost int, version string, salt, key []byte) (f string) { 74 | switch v { 75 | case VariantStandard: 76 | return fmt.Sprintf(EncodingFmt, version, cost, salt, key) 77 | case VariantSHA256: 78 | return fmt.Sprintf(EncodingFmtSHA256, v.Prefix(), version, cost, salt, key) 79 | default: 80 | return 81 | } 82 | } 83 | 84 | // EncodeInput returns the appropriate algorithm input. 85 | func (v Variant) EncodeInput(src, salt []byte) (dst []byte) { 86 | switch v { 87 | case VariantSHA256: 88 | h := hmac.New(sha256.New, bcrypt.Base64Encode(salt)) 89 | h.Write(src) 90 | 91 | digest := h.Sum(nil) 92 | 93 | dst = make([]byte, base64.StdEncoding.EncodedLen(len(digest))) 94 | 95 | base64.StdEncoding.Encode(dst, digest) 96 | 97 | return dst 98 | default: 99 | return src 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /algorithm/const.go: -------------------------------------------------------------------------------- 1 | package algorithm 2 | 3 | const ( 4 | // DigestSHA1 is te name for SHA1 digests. 5 | DigestSHA1 = "sha1" 6 | 7 | // DigestSHA224 is te name for SHA224 digests. 8 | DigestSHA224 = "sha224" 9 | 10 | // DigestSHA256 is te name for SHA256 digests. 11 | DigestSHA256 = "sha256" 12 | 13 | // DigestSHA384 is te name for SHA384 digests. 14 | DigestSHA384 = "sha384" 15 | 16 | // DigestSHA512 is te name for SHA512 digests. 17 | DigestSHA512 = "sha512" 18 | ) 19 | 20 | const ( 21 | // SaltLengthDefault is the default salt size for most implementations. 22 | SaltLengthDefault = 16 23 | 24 | // KeyLengthDefault is the default key size for most implementations. 25 | KeyLengthDefault = 32 26 | ) 27 | -------------------------------------------------------------------------------- /algorithm/doc.go: -------------------------------------------------------------------------------- 1 | // Package algorithm is a package which contains the individual algorithms and interfaces related to their 2 | // implementation. 3 | package algorithm 4 | -------------------------------------------------------------------------------- /algorithm/errors.go: -------------------------------------------------------------------------------- 1 | package algorithm 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | // ErrEncodedHashInvalidFormat is an error returned when an encoded hash has an invalid format. 9 | ErrEncodedHashInvalidFormat = errors.New("provided encoded hash has an invalid format") 10 | 11 | // ErrEncodedHashInvalidIdentifier is an error returned when an encoded hash has an invalid identifier for the 12 | // given digest. 13 | ErrEncodedHashInvalidIdentifier = errors.New("provided encoded hash has an invalid identifier") 14 | 15 | // ErrEncodedHashInvalidVersion is an error returned when an encoded hash has an unsupported or otherwise invalid 16 | // version. 17 | ErrEncodedHashInvalidVersion = errors.New("provided encoded hash has an invalid version") 18 | 19 | // ErrEncodedHashInvalidOption is an error returned when an encoded hash has an unsupported or otherwise invalid 20 | // option in the option field. 21 | ErrEncodedHashInvalidOption = errors.New("provided encoded hash has an invalid option") 22 | 23 | // ErrEncodedHashInvalidOptionKey is an error returned when an encoded hash has an unknown or otherwise invalid 24 | // option key in the option field. 25 | ErrEncodedHashInvalidOptionKey = errors.New("provided encoded hash has an invalid option key") 26 | 27 | // ErrEncodedHashInvalidOptionValue is an error returned when an encoded hash has an unknown or otherwise invalid 28 | // option value in the option field. 29 | ErrEncodedHashInvalidOptionValue = errors.New("provided encoded hash has an invalid option value") 30 | 31 | // ErrEncodedHashKeyEncoding is an error returned when an encoded hash has a salt with an invalid or unsupported 32 | // encoding. 33 | ErrEncodedHashKeyEncoding = errors.New("provided encoded hash has a key value that can't be decoded") 34 | 35 | // ErrEncodedHashSaltEncoding is an error returned when an encoded hash has a salt with an invalid or unsupported 36 | // encoding. 37 | ErrEncodedHashSaltEncoding = errors.New("provided encoded hash has a salt value that can't be decoded") 38 | 39 | // ErrKeyDerivation is returned when a Key function returns an error. 40 | ErrKeyDerivation = errors.New("failed to derive the key with the provided parameters") 41 | 42 | // ErrSaltEncoding is an error returned when a salt has an invalid or unsupported encoding. 43 | ErrSaltEncoding = errors.New("provided salt has a value that can't be decoded") 44 | 45 | // ErrPasswordInvalid is an error returned when a password has an invalid or unsupported properties. It is NOT 46 | // returned on password mismatches. 47 | ErrPasswordInvalid = errors.New("password is invalid") 48 | 49 | // ErrSaltInvalid is an error returned when a salt has an invalid or unsupported properties. 50 | ErrSaltInvalid = errors.New("salt is invalid") 51 | 52 | // ErrSaltReadRandomBytes is an error returned when generating the random bytes for salt resulted in an error. 53 | ErrSaltReadRandomBytes = errors.New("could not read random bytes for salt") 54 | 55 | // ErrParameterInvalid is an error returned when a parameter has an invalid value. 56 | ErrParameterInvalid = errors.New("parameter is invalid") 57 | ) 58 | 59 | // Error format strings. 60 | const ( 61 | ErrFmtInvalidIntParameter = "%w: parameter '%s' must be between %d%s and %d but is set to '%d'" 62 | ErrFmtDigestDecode = "%s decode error: %w" 63 | ErrFmtDigestMatch = "%s match error: %w" 64 | ErrFmtHasherHash = "%s hashing error: %w" 65 | ErrFmtHasherValidation = "%s validation error: %w" 66 | ) 67 | -------------------------------------------------------------------------------- /algorithm/md5crypt/const.go: -------------------------------------------------------------------------------- 1 | package md5crypt 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | const ( 8 | // EncodingFmt is the encoding format for this algorithm. 9 | EncodingFmt = "$1$%s$%s" 10 | 11 | // EncodingFmtSun is the encoding format for this algorithm when using md5crypt.VariantSun. 12 | EncodingFmtSun = "$md5$%s$$%s" 13 | 14 | // EncodingFmtSunIterations is the encoding format for this algorithm when using md5crypt.VariantSun and iterations more than 0. 15 | EncodingFmtSunIterations = "$md5,iterations=%d$%s$$%s" 16 | 17 | // AlgName is the name for this algorithm. 18 | AlgName = "md5crypt" 19 | 20 | // AlgIdentifier is the identifier used in this algorithm. 21 | AlgIdentifier = "1" 22 | 23 | // AlgIdentifierVariantSun is the identifier used in this algorithm when using md5crypt.VariantSun. 24 | AlgIdentifierVariantSun = "md5" 25 | 26 | // VariantNameStandard is the md5crypt.Variant name for md5crypt.VariantStandard. 27 | VariantNameStandard = "standard" 28 | 29 | // VariantNameSun is the md5crypt.Variant name for md5crypt.VariantSun. 30 | VariantNameSun = "sun" 31 | 32 | // SaltLengthMin is the minimum salt size accepted. 33 | SaltLengthMin = 1 34 | 35 | // SaltLengthMax is the maximum salt size accepted. 36 | SaltLengthMax = 8 37 | 38 | // SaltLengthDefault is the default salt size. 39 | SaltLengthDefault = SaltLengthMax 40 | 41 | // SaltCharSet are the valid characters for the salt. 42 | SaltCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./" 43 | 44 | // IterationsMin is the minimum iterations accepted. 45 | IterationsMin = 0 46 | 47 | // IterationsMax is the maximum iterations accepted. 48 | IterationsMax uint32 = math.MaxUint32 49 | 50 | // IterationsDefault is the default iterations. 51 | IterationsDefault = 34000 52 | ) 53 | 54 | const ( 55 | variantDefault = VariantStandard 56 | ) 57 | -------------------------------------------------------------------------------- /algorithm/md5crypt/decoder.go: -------------------------------------------------------------------------------- 1 | package md5crypt 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/go-crypt/crypt/algorithm" 9 | "github.com/go-crypt/crypt/internal/encoding" 10 | ) 11 | 12 | // RegisterDecoder the decoder with the algorithm.DecoderRegister. 13 | func RegisterDecoder(r algorithm.DecoderRegister) (err error) { 14 | if err = RegisterDecoderCommon(r); err != nil { 15 | return err 16 | } 17 | 18 | if err = RegisterDecoderSun(r); err != nil { 19 | return err 20 | } 21 | 22 | return nil 23 | } 24 | 25 | // RegisterDecoderCommon registers specifically the common decoder variant with the algorithm.DecoderRegister. 26 | func RegisterDecoderCommon(r algorithm.DecoderRegister) (err error) { 27 | if err = r.RegisterDecodeFunc(VariantStandard.Prefix(), DecodeVariant(VariantStandard)); err != nil { 28 | return err 29 | } 30 | 31 | return nil 32 | } 33 | 34 | // RegisterDecoderSun registers specifically the sun decoder variant with the algorithm.DecoderRegister. 35 | func RegisterDecoderSun(r algorithm.DecoderRegister) (err error) { 36 | if err = r.RegisterDecodeFunc(VariantSun.Prefix(), DecodeVariant(VariantSun)); err != nil { 37 | return err 38 | } 39 | 40 | if err = r.RegisterDecodePrefix("$md5,", VariantSun.Prefix()); err != nil { 41 | return err 42 | } 43 | 44 | return nil 45 | } 46 | 47 | // Decode the encoded digest into a algorithm.Digest. 48 | func Decode(encodedDigest string) (digest algorithm.Digest, err error) { 49 | return DecodeVariant(VariantNone)(encodedDigest) 50 | } 51 | 52 | // DecodeVariant the encoded digest into a algorithm.Digest provided it matches the provided Variant. If VariantNone is 53 | // used all variants can be decoded. 54 | func DecodeVariant(v Variant) func(encodedDigest string) (digest algorithm.Digest, err error) { 55 | return func(encodedDigest string) (digest algorithm.Digest, err error) { 56 | var ( 57 | parts []string 58 | variant Variant 59 | ) 60 | 61 | if variant, parts, err = decoderParts(encodedDigest); err != nil { 62 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) 63 | } 64 | 65 | if v != VariantNone && v != variant { 66 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, fmt.Errorf("the '%s' variant cannot be decoded only the '%s' variant can be", variant.String(), v.String())) 67 | } 68 | 69 | if digest, err = decode(variant, parts); err != nil { 70 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) 71 | } 72 | 73 | return digest, nil 74 | } 75 | } 76 | 77 | func decoderParts(encodedDigest string) (variant Variant, parts []string, err error) { 78 | partsTemp := encoding.Split(encodedDigest, -1) 79 | 80 | p := len(partsTemp) 81 | 82 | if p != 4 && p != 5 { 83 | return VariantNone, nil, algorithm.ErrEncodedHashInvalidFormat 84 | } 85 | 86 | switch partsTemp[1] { 87 | case AlgIdentifier: 88 | if p != 4 { 89 | return VariantNone, nil, algorithm.ErrEncodedHashInvalidFormat 90 | } 91 | default: 92 | if p != 5 { 93 | return VariantNone, nil, algorithm.ErrEncodedHashInvalidFormat 94 | } 95 | } 96 | 97 | if strings.HasPrefix(partsTemp[1], "md5,") { 98 | parts = append([]string{strings.SplitN(partsTemp[1], ",", 2)[1]}, partsTemp[2:]...) 99 | 100 | variant = VariantSun 101 | } else { 102 | switch variant = NewVariant(partsTemp[1]); variant { 103 | case VariantNone: 104 | return variant, nil, fmt.Errorf("%w: identifier '%s' is not an encoded %s digest", algorithm.ErrEncodedHashInvalidIdentifier, partsTemp[1], AlgName) 105 | default: 106 | parts = append([]string{""}, partsTemp[2:]...) 107 | } 108 | } 109 | 110 | return variant, parts, nil 111 | } 112 | 113 | func decode(variant Variant, parts []string) (digest algorithm.Digest, err error) { 114 | decoded := &Digest{ 115 | variant: variant, 116 | } 117 | 118 | var params []encoding.Parameter 119 | 120 | if parts[0] != "" { 121 | if variant != VariantSun { 122 | return nil, fmt.Errorf("%w: parameters are only valid for the %s variant but the %s variant was decoded", algorithm.ErrParameterInvalid, VariantSun.String(), variant.String()) 123 | } 124 | 125 | if params, err = encoding.DecodeParameterStr(parts[0]); err != nil { 126 | return nil, err 127 | } 128 | 129 | for _, param := range params { 130 | switch param.Key { 131 | case "rounds": 132 | var value uint64 133 | 134 | if value, err = strconv.ParseUint(param.Value, 10, 32); err != nil { 135 | return nil, fmt.Errorf("%w: option '%s' has invalid value '%s': %v", algorithm.ErrEncodedHashInvalidOptionValue, param.Key, param.Value, err) 136 | } 137 | 138 | decoded.iterations = uint32(value) 139 | default: 140 | return nil, fmt.Errorf("%w: option '%s' with value '%s' is unknown", algorithm.ErrEncodedHashInvalidOptionKey, param.Key, param.Value) 141 | } 142 | } 143 | } 144 | 145 | switch variant { 146 | case VariantSun: 147 | decoded.salt, decoded.key = []byte(parts[1]), []byte(parts[3]) 148 | default: 149 | decoded.salt, decoded.key = []byte(parts[1]), []byte(parts[2]) 150 | } 151 | 152 | return decoded, nil 153 | } 154 | -------------------------------------------------------------------------------- /algorithm/md5crypt/digest.go: -------------------------------------------------------------------------------- 1 | package md5crypt 2 | 3 | import ( 4 | "crypto/subtle" 5 | "fmt" 6 | 7 | "github.com/go-crypt/x/crypt" 8 | 9 | "github.com/go-crypt/crypt/algorithm" 10 | ) 11 | 12 | // Digest is a algorithm.Digest which handles md5crypt hashes. 13 | type Digest struct { 14 | variant Variant 15 | 16 | iterations uint32 17 | 18 | salt, key []byte 19 | } 20 | 21 | // Match returns true if the string password matches the current md5crypt.Digest. 22 | func (d *Digest) Match(password string) (match bool) { 23 | return d.MatchBytes([]byte(password)) 24 | } 25 | 26 | // MatchBytes returns true if the []byte passwordBytes matches the current md5crypt.Digest. 27 | func (d *Digest) MatchBytes(passwordBytes []byte) (match bool) { 28 | match, _ = d.MatchBytesAdvanced(passwordBytes) 29 | 30 | return match 31 | } 32 | 33 | // MatchAdvanced is the same as Match except if there is an error it returns that as well. 34 | func (d *Digest) MatchAdvanced(password string) (match bool, err error) { 35 | return d.MatchBytesAdvanced([]byte(password)) 36 | } 37 | 38 | // MatchBytesAdvanced is the same as MatchBytes except if there is an error it returns that as well. 39 | func (d *Digest) MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) { 40 | if len(d.key) == 0 { 41 | return false, fmt.Errorf(algorithm.ErrFmtDigestMatch, AlgName, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrPasswordInvalid)) 42 | } 43 | 44 | switch d.variant { 45 | case VariantSun: 46 | return subtle.ConstantTimeCompare(d.key, crypt.KeyMD5CryptSun(passwordBytes, d.salt, d.iterations)) == 1, nil 47 | default: 48 | return subtle.ConstantTimeCompare(d.key, crypt.KeyMD5Crypt(passwordBytes, d.salt)) == 1, nil 49 | } 50 | } 51 | 52 | // Encode returns the encoded form of this md5crypt.Digest. 53 | func (d *Digest) Encode() string { 54 | switch { 55 | case d.variant == VariantSun && d.iterations > 0: 56 | return fmt.Sprintf(EncodingFmtSunIterations, 57 | d.iterations, d.salt, d.key, 58 | ) 59 | case d.variant == VariantSun: 60 | return fmt.Sprintf(EncodingFmtSun, 61 | d.salt, d.key, 62 | ) 63 | default: 64 | return fmt.Sprintf(EncodingFmt, 65 | d.salt, d.key, 66 | ) 67 | } 68 | } 69 | 70 | // String returns the storable format of the md5crypt.Digest encoded hash. 71 | func (d *Digest) String() string { 72 | return d.Encode() 73 | } 74 | 75 | func (d *Digest) defaults() { 76 | switch d.variant { 77 | case VariantStandard, VariantSun: 78 | break 79 | default: 80 | d.variant = variantDefault 81 | } 82 | 83 | if d.iterations < IterationsMin { 84 | d.iterations = IterationsDefault 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /algorithm/md5crypt/doc.go: -------------------------------------------------------------------------------- 1 | // Package md5crypt provides helpful abstractions for an implementation of crypt (MD5) and implements 2 | // github.com/go-crypt/crypt interfaces. 3 | // 4 | // This implementation is loaded by crypt.NewDecoderAll. 5 | package md5crypt 6 | -------------------------------------------------------------------------------- /algorithm/md5crypt/hasher.go: -------------------------------------------------------------------------------- 1 | package md5crypt 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-crypt/x/crypt" 7 | 8 | "github.com/go-crypt/crypt/algorithm" 9 | "github.com/go-crypt/crypt/internal/random" 10 | ) 11 | 12 | // New returns a *md5crypt.Hasher with the additional opts applied if any. 13 | func New(opts ...Opt) (hasher *Hasher, err error) { 14 | hasher = &Hasher{} 15 | 16 | if err = hasher.WithOptions(opts...); err != nil { 17 | return nil, err 18 | } 19 | 20 | if err = hasher.Validate(); err != nil { 21 | return nil, err 22 | } 23 | 24 | return hasher, nil 25 | } 26 | 27 | // Hasher is a crypt.Hash for md5crypt which can be initialized via md5crypt.New using a functional options pattern. 28 | type Hasher struct { 29 | variant Variant 30 | 31 | iterations uint32 32 | 33 | bytesSalt int 34 | 35 | d bool 36 | } 37 | 38 | // WithOptions applies the provided functional options provided as a md5crypt.Opt to the md5crypt.Hasher. 39 | func (h *Hasher) WithOptions(opts ...Opt) (err error) { 40 | for _, opt := range opts { 41 | if err = opt(h); err != nil { 42 | return err 43 | } 44 | } 45 | 46 | return nil 47 | } 48 | 49 | // Hash performs the hashing operation and returns either a algorithm.Digest or an error. 50 | func (h *Hasher) Hash(password string) (digest algorithm.Digest, err error) { 51 | h.defaults() 52 | 53 | if digest, err = h.hash(password); err != nil { 54 | return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) 55 | } 56 | 57 | return digest, nil 58 | } 59 | 60 | // MustHash overloads the Hash method and panics if the error is not nil. It's recommended if you use this option to 61 | // utilize the Validate method first or handle the panic appropriately. 62 | func (h *Hasher) MustHash(password string) (digest algorithm.Digest) { 63 | var err error 64 | 65 | if digest, err = h.Hash(password); err != nil { 66 | panic(err) 67 | } 68 | 69 | return digest 70 | } 71 | 72 | // HashWithSalt overloads the Hash method allowing the user to provide a salt. It's recommended instead to configure the 73 | // salt size and let this be a random value generated using crypto/rand. 74 | func (h *Hasher) HashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { 75 | h.defaults() 76 | 77 | if digest, err = h.hashWithSalt(password, salt); err != nil { 78 | return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) 79 | } 80 | 81 | return digest, nil 82 | } 83 | 84 | // Validate checks the settings/parameters for this md5crypt.Hasher and returns an error. 85 | func (h *Hasher) Validate() (err error) { 86 | h.defaults() 87 | 88 | return nil 89 | } 90 | 91 | func (h *Hasher) hash(password string) (digest algorithm.Digest, err error) { 92 | var salt []byte 93 | 94 | if salt, err = random.CharSetBytes(h.bytesSalt, SaltCharSet); err != nil { 95 | return nil, fmt.Errorf("%w: %v", algorithm.ErrSaltReadRandomBytes, err) 96 | } 97 | 98 | return h.hashWithSalt(password, salt) 99 | } 100 | 101 | func (h *Hasher) hashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { 102 | if s := len(salt); s > SaltLengthMax || s < SaltLengthMin { 103 | return nil, fmt.Errorf("%w: salt bytes must have a length of between %d and %d but has a length of %d", algorithm.ErrSaltInvalid, SaltLengthMin, SaltLengthMax, len(salt)) 104 | } 105 | 106 | d := &Digest{ 107 | variant: h.variant, 108 | iterations: h.iterations, 109 | salt: salt, 110 | } 111 | 112 | d.defaults() 113 | 114 | switch d.variant { 115 | case VariantSun: 116 | d.key = crypt.KeyMD5CryptSun([]byte(password), d.salt, d.iterations) 117 | default: 118 | d.key = crypt.KeyMD5Crypt([]byte(password), d.salt) 119 | } 120 | 121 | return d, nil 122 | } 123 | 124 | func (h *Hasher) defaults() { 125 | if h.d { 126 | return 127 | } 128 | 129 | h.d = true 130 | 131 | if h.bytesSalt < SaltLengthMin { 132 | h.bytesSalt = SaltLengthDefault 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /algorithm/md5crypt/opts.go: -------------------------------------------------------------------------------- 1 | package md5crypt 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-crypt/crypt/algorithm" 7 | ) 8 | 9 | // Opt describes the functional option pattern for the md5crypt.Hasher. 10 | type Opt func(h *Hasher) (err error) 11 | 12 | // WithVariant is used to configure the md5crypt.Variant of the resulting md5crypt.Digest. 13 | // Default is md5crypt.VariantStandard. 14 | func WithVariant(variant Variant) Opt { 15 | return func(h *Hasher) (err error) { 16 | switch variant { 17 | case VariantNone: 18 | return nil 19 | case VariantStandard, VariantSun: 20 | h.variant = variant 21 | 22 | return nil 23 | default: 24 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant '%d' is invalid", algorithm.ErrParameterInvalid, variant)) 25 | } 26 | } 27 | } 28 | 29 | // WithVariantName uses the variant name or identifier to configure the md5crypt.Variant of the resulting md5crypt.Digest. 30 | // Default is md5crypt.VariantStandard. 31 | func WithVariantName(identifier string) Opt { 32 | return func(h *Hasher) (err error) { 33 | if identifier == "" { 34 | return nil 35 | } 36 | 37 | variant := NewVariant(identifier) 38 | 39 | if variant == VariantNone { 40 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant identifier '%s' is invalid", algorithm.ErrParameterInvalid, identifier)) 41 | } 42 | 43 | h.variant = variant 44 | 45 | return nil 46 | } 47 | } 48 | 49 | // WithIterations sets the iterations parameter of the resulting md5crypt.Digest. Only valid for the Sun variant. This 50 | // is encoded in the hash with the 'iterations' parameter. 51 | // Minimum is 0, Maximum is 4294967295. Default is 34000. 52 | func WithIterations(iterations uint32) Opt { 53 | return func(h *Hasher) (err error) { 54 | if iterations < IterationsMin || iterations > IterationsMax { 55 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "iterations", IterationsMin, "", IterationsMax, iterations)) 56 | } 57 | 58 | h.iterations = iterations 59 | 60 | return nil 61 | } 62 | } 63 | 64 | // WithRounds is an alias for md5crypt.WithIterations. 65 | func WithRounds(rounds uint32) Opt { 66 | return WithIterations(rounds) 67 | } 68 | 69 | // WithSaltLength adjusts the salt size (in bytes) of the resulting md5crypt.Digest. 70 | // Minimum is 1, Maximum is 8. Default is 8. 71 | func WithSaltLength(bytes int) Opt { 72 | return func(h *Hasher) (err error) { 73 | if bytes < SaltLengthMin || bytes > SaltLengthMax { 74 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "salt length", SaltLengthMin, "", SaltLengthMax, bytes)) 75 | } 76 | 77 | h.bytesSalt = bytes 78 | 79 | return nil 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /algorithm/md5crypt/variant.go: -------------------------------------------------------------------------------- 1 | package md5crypt 2 | 3 | // NewVariant converts an identifier string to a md5crypt.Variant. 4 | func NewVariant(identifier string) (variant Variant) { 5 | switch identifier { 6 | case AlgIdentifier, AlgName, VariantNameStandard, "common": 7 | return VariantStandard 8 | case AlgIdentifierVariantSun, VariantNameSun: 9 | return VariantSun 10 | default: 11 | return VariantNone 12 | } 13 | } 14 | 15 | // Variant is a variant of the md5crypt.Digest. 16 | type Variant int 17 | 18 | const ( 19 | // VariantNone is a variant of the md5crypt.Digest which is unknown. 20 | VariantNone Variant = iota 21 | 22 | // VariantStandard is a variant of the md5crypt.Digest which uses the standard md5crypt format. 23 | VariantStandard 24 | 25 | // VariantSun is a variant of the md5crypt.Digest designed at Sun. 26 | VariantSun 27 | ) 28 | 29 | // String implements the fmt.Stringer returning a string representation of the md5crypt.Variant. 30 | func (v Variant) String() (prefix string) { 31 | switch v { 32 | case VariantStandard: 33 | return VariantNameStandard 34 | case VariantSun: 35 | return VariantNameSun 36 | default: 37 | return 38 | } 39 | } 40 | 41 | // Prefix returns the md5crypt.Variant prefix identifier. 42 | func (v Variant) Prefix() (prefix string) { 43 | switch v { 44 | case VariantStandard: 45 | return AlgIdentifier 46 | case VariantSun: 47 | return AlgIdentifierVariantSun 48 | default: 49 | return 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /algorithm/pbkdf2/const.go: -------------------------------------------------------------------------------- 1 | package pbkdf2 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | const ( 8 | // EncodingFmt is the encoding format for this algorithm. 9 | EncodingFmt = "$%s$%d$%s$%s" 10 | 11 | // AlgName is the name for this algorithm. 12 | AlgName = "pbkdf2" 13 | 14 | // AlgIdentifier is the identifier used in encoded digests for this algorithm. 15 | AlgIdentifier = AlgName 16 | 17 | // AlgIdentifierSHA1 is the identifier used in encoded SHA1 variants of this algorithm. 18 | AlgIdentifierSHA1 = "pbkdf2-sha1" 19 | 20 | // AlgIdentifierSHA224 is the identifier used in encoded SHA224 variants of this algorithm. 21 | AlgIdentifierSHA224 = "pbkdf2-sha224" 22 | 23 | // AlgIdentifierSHA256 is the identifier used in encoded SHA256 variants of this algorithm. 24 | AlgIdentifierSHA256 = "pbkdf2-sha256" 25 | 26 | // AlgIdentifierSHA384 is the identifier used in encoded SHA384 variants of this algorithm. 27 | AlgIdentifierSHA384 = "pbkdf2-sha384" 28 | 29 | // AlgIdentifierSHA512 is the identifier used in encoded SHA512 variants of this algorithm. 30 | AlgIdentifierSHA512 = "pbkdf2-sha512" 31 | 32 | // KeyLengthMax is the maximum tag size accepted. 33 | KeyLengthMax = math.MaxInt32 34 | 35 | // SaltLengthMin is the minimum salt size accepted. 36 | SaltLengthMin = 8 37 | 38 | // SaltLengthMax is the maximum salt size accepted. 39 | SaltLengthMax = math.MaxInt32 40 | 41 | // IterationsMin is the minimum iterations accepted. 42 | IterationsMin = 100000 43 | 44 | // IterationsMax is the maximum iterations accepted. 45 | IterationsMax = math.MaxInt32 46 | 47 | // IterationsDefaultSHA1 is the default iterations for algorithms SHA1 and SHA224. 48 | IterationsDefaultSHA1 = 720000 49 | 50 | // IterationsDefaultSHA256 is the default iterations for algorithms SHA256 and SHA384. 51 | IterationsDefaultSHA256 = 310000 52 | 53 | // IterationsDefaultSHA512 is the default iterations for algorithms SHA512. 54 | IterationsDefaultSHA512 = 120000 55 | 56 | variantDefault = VariantSHA256 57 | ) 58 | -------------------------------------------------------------------------------- /algorithm/pbkdf2/decoder.go: -------------------------------------------------------------------------------- 1 | package pbkdf2 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/go-crypt/crypt/algorithm" 8 | "github.com/go-crypt/crypt/internal/encoding" 9 | ) 10 | 11 | // RegisterDecoder the decoder with the algorithm.DecoderRegister. 12 | func RegisterDecoder(r algorithm.DecoderRegister) (err error) { 13 | if err = RegisterDecoderSHA1(r); err != nil { 14 | return err 15 | } 16 | 17 | if err = RegisterDecoderSHA224(r); err != nil { 18 | return err 19 | } 20 | 21 | if err = RegisterDecoderSHA256(r); err != nil { 22 | return err 23 | } 24 | 25 | if err = RegisterDecoderSHA384(r); err != nil { 26 | return err 27 | } 28 | 29 | if err = RegisterDecoderSHA512(r); err != nil { 30 | return err 31 | } 32 | 33 | return nil 34 | } 35 | 36 | // RegisterDecoderSHA1 registers specifically the sha1 decoder variant with the algorithm.DecoderRegister. 37 | func RegisterDecoderSHA1(r algorithm.DecoderRegister) (err error) { 38 | decodefunc := DecodeVariant(VariantSHA1) 39 | 40 | if err = r.RegisterDecodeFunc(VariantSHA1.Prefix(), decodefunc); err != nil { 41 | return err 42 | } 43 | 44 | if err = r.RegisterDecodeFunc(AlgIdentifierSHA1, decodefunc); err != nil { 45 | return err 46 | } 47 | 48 | return nil 49 | } 50 | 51 | // RegisterDecoderSHA224 registers specifically the sha224 decoder variant with the algorithm.DecoderRegister. 52 | func RegisterDecoderSHA224(r algorithm.DecoderRegister) (err error) { 53 | if err = r.RegisterDecodeFunc(VariantSHA224.Prefix(), DecodeVariant(VariantSHA224)); err != nil { 54 | return err 55 | } 56 | 57 | return nil 58 | } 59 | 60 | // RegisterDecoderSHA256 registers specifically the sha256 decoder variant with the algorithm.DecoderRegister. 61 | func RegisterDecoderSHA256(r algorithm.DecoderRegister) (err error) { 62 | if err = r.RegisterDecodeFunc(VariantSHA256.Prefix(), DecodeVariant(VariantSHA256)); err != nil { 63 | return err 64 | } 65 | 66 | return nil 67 | } 68 | 69 | // RegisterDecoderSHA384 registers specifically the sha384 decoder variant with the algorithm.DecoderRegister. 70 | func RegisterDecoderSHA384(r algorithm.DecoderRegister) (err error) { 71 | if err = r.RegisterDecodeFunc(VariantSHA384.Prefix(), DecodeVariant(VariantSHA384)); err != nil { 72 | return err 73 | } 74 | 75 | return nil 76 | } 77 | 78 | // RegisterDecoderSHA512 registers specifically the sha512 decoder variant with the algorithm.DecoderRegister. 79 | func RegisterDecoderSHA512(r algorithm.DecoderRegister) (err error) { 80 | if err = r.RegisterDecodeFunc(VariantSHA512.Prefix(), DecodeVariant(VariantSHA512)); err != nil { 81 | return err 82 | } 83 | 84 | return nil 85 | } 86 | 87 | // Decode the encoded digest into a algorithm.Digest. 88 | func Decode(encodedDigest string) (digest algorithm.Digest, err error) { 89 | return DecodeVariant(VariantNone)(encodedDigest) 90 | } 91 | 92 | // DecodeVariant the encoded digest into a algorithm.Digest provided it matches the provided pbkdf2.Variant. If 93 | // pbkdf2.VariantNone is used all variants can be decoded. 94 | func DecodeVariant(v Variant) func(encodedDigest string) (digest algorithm.Digest, err error) { 95 | return func(encodedDigest string) (digest algorithm.Digest, err error) { 96 | var ( 97 | parts []string 98 | variant Variant 99 | ) 100 | 101 | if variant, parts, err = decoderParts(encodedDigest); err != nil { 102 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) 103 | } 104 | 105 | if v != VariantNone && v != variant { 106 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, fmt.Errorf("the '%s' variant cannot be decoded only the '%s' variant can be", variant.String(), v.String())) 107 | } 108 | 109 | if digest, err = decode(variant, parts); err != nil { 110 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) 111 | } 112 | 113 | return digest, nil 114 | } 115 | } 116 | 117 | func decoderParts(encodedDigest string) (variant Variant, parts []string, err error) { 118 | parts = encoding.Split(encodedDigest, -1) 119 | 120 | if len(parts) != 5 { 121 | return VariantNone, nil, algorithm.ErrEncodedHashInvalidFormat 122 | } 123 | 124 | variant = NewVariant(parts[1]) 125 | 126 | if variant == VariantNone { 127 | return variant, nil, fmt.Errorf("%w: identifier '%s' is not an encoded %s digest", algorithm.ErrEncodedHashInvalidIdentifier, parts[1], AlgName) 128 | } 129 | 130 | return variant, parts[2:], nil 131 | } 132 | 133 | func decode(variant Variant, parts []string) (digest algorithm.Digest, err error) { 134 | decoded := &Digest{ 135 | variant: variant, 136 | } 137 | 138 | decoded.variant = variant 139 | 140 | if decoded.iterations, err = strconv.Atoi(parts[0]); err != nil { 141 | return nil, fmt.Errorf("%w: iterations could not be parsed: %v", algorithm.ErrEncodedHashInvalidOptionValue, err) 142 | } 143 | 144 | if decoded.salt, err = encoding.Base64RawAdaptedEncoding.DecodeString(parts[1]); err != nil { 145 | return nil, fmt.Errorf("%w: %v", algorithm.ErrEncodedHashSaltEncoding, err) 146 | } 147 | 148 | if decoded.key, err = encoding.Base64RawAdaptedEncoding.DecodeString(parts[2]); err != nil { 149 | return nil, fmt.Errorf("%w: %v", algorithm.ErrEncodedHashKeyEncoding, err) 150 | } 151 | 152 | decoded.t = len(decoded.key) 153 | 154 | if decoded.t == 0 { 155 | return nil, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrEncodedHashKeyEncoding) 156 | } 157 | 158 | return decoded, nil 159 | } 160 | -------------------------------------------------------------------------------- /algorithm/pbkdf2/digest.go: -------------------------------------------------------------------------------- 1 | package pbkdf2 2 | 3 | import ( 4 | "crypto/subtle" 5 | "fmt" 6 | 7 | "github.com/go-crypt/x/pbkdf2" 8 | 9 | "github.com/go-crypt/crypt/algorithm" 10 | "github.com/go-crypt/crypt/internal/encoding" 11 | ) 12 | 13 | // Digest is a pbkdf2.Digest which handles PBKDF2 hashes. 14 | type Digest struct { 15 | variant Variant 16 | 17 | iterations int 18 | t int 19 | salt, key []byte 20 | } 21 | 22 | // Match returns true if the string password matches the current pbkdf2.Digest. 23 | func (d *Digest) Match(password string) (match bool) { 24 | return d.MatchBytes([]byte(password)) 25 | } 26 | 27 | // MatchBytes returns true if the []byte passwordBytes matches the current pbkdf2.Digest. 28 | func (d *Digest) MatchBytes(passwordBytes []byte) (match bool) { 29 | match, _ = d.MatchBytesAdvanced(passwordBytes) 30 | 31 | return match 32 | } 33 | 34 | // MatchAdvanced is the same as Match except if there is an error it returns that as well. 35 | func (d *Digest) MatchAdvanced(password string) (match bool, err error) { 36 | return d.MatchBytesAdvanced([]byte(password)) 37 | } 38 | 39 | // MatchBytesAdvanced is the same as MatchBytes except if there is an error it returns that as well. 40 | func (d *Digest) MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) { 41 | if len(d.key) == 0 { 42 | return false, fmt.Errorf(algorithm.ErrFmtDigestMatch, AlgName, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrPasswordInvalid)) 43 | } 44 | 45 | return subtle.ConstantTimeCompare(d.key, pbkdf2.Key(passwordBytes, d.salt, d.iterations, d.t, d.variant.HashFunc())) == 1, nil 46 | } 47 | 48 | // Encode returns the encoded form of this pbkdf2.Digest. 49 | func (d *Digest) Encode() string { 50 | return fmt.Sprintf(EncodingFmt, 51 | d.variant.Prefix(), 52 | d.iterations, 53 | encoding.Base64RawAdaptedEncoding.EncodeToString(d.salt), encoding.Base64RawAdaptedEncoding.EncodeToString(d.key), 54 | ) 55 | } 56 | 57 | // String returns the storable format of the pbkdf2.Digest encoded hash. 58 | func (d *Digest) String() string { 59 | return d.Encode() 60 | } 61 | 62 | func (d *Digest) defaults() { 63 | switch d.variant { 64 | case VariantSHA1, VariantSHA224, VariantSHA256, VariantSHA384, VariantSHA512: 65 | break 66 | default: 67 | d.variant = variantDefault 68 | } 69 | 70 | if d.iterations < IterationsMin { 71 | d.iterations = d.variant.DefaultIterations() 72 | } 73 | 74 | if d.t == 0 { 75 | d.t = d.variant.HashFunc()().Size() 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /algorithm/pbkdf2/doc.go: -------------------------------------------------------------------------------- 1 | // Package pbkdf2 provides helpful abstractions for an implementation of PBKDF2 and implements 2 | // github.com/go-crypt/crypt interfaces. 3 | // 4 | // This implementation is loaded by crypt.NewDefaultDecoder and crypt.NewDecoderAll. 5 | package pbkdf2 6 | -------------------------------------------------------------------------------- /algorithm/pbkdf2/hasher.go: -------------------------------------------------------------------------------- 1 | package pbkdf2 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-crypt/x/pbkdf2" 7 | 8 | "github.com/go-crypt/crypt/algorithm" 9 | "github.com/go-crypt/crypt/internal/random" 10 | ) 11 | 12 | // New returns a *pbkdf2.Hasher with the additional opts applied if any. 13 | func New(opts ...Opt) (hasher *Hasher, err error) { 14 | hasher = &Hasher{} 15 | 16 | if err = hasher.WithOptions(opts...); err != nil { 17 | return nil, err 18 | } 19 | 20 | if err = hasher.Validate(); err != nil { 21 | return nil, err 22 | } 23 | 24 | return hasher, nil 25 | } 26 | 27 | // NewSHA1 returns a SHA1 variant *pbkdf2.Hasher with the additional opts applied if any. 28 | func NewSHA1(opts ...Opt) (hasher *Hasher, err error) { 29 | if hasher, err = New(opts...); err != nil { 30 | return nil, err 31 | } 32 | 33 | if err = hasher.WithOptions(WithVariant(VariantSHA1)); err != nil { 34 | return nil, err 35 | } 36 | 37 | return hasher, nil 38 | } 39 | 40 | // NewSHA224 returns a SHA224 variant *pbkdf2.Hasher with the additional opts applied if any. 41 | func NewSHA224(opts ...Opt) (hasher *Hasher, err error) { 42 | if hasher, err = New(opts...); err != nil { 43 | return nil, err 44 | } 45 | 46 | if err = hasher.WithOptions(WithVariant(VariantSHA224)); err != nil { 47 | return nil, err 48 | } 49 | 50 | return hasher, nil 51 | } 52 | 53 | // NewSHA256 returns a SHA256 variant *pbkdf2.Hasher with the additional opts applied if any. 54 | func NewSHA256(opts ...Opt) (hasher *Hasher, err error) { 55 | if hasher, err = New(opts...); err != nil { 56 | return nil, err 57 | } 58 | 59 | if err = hasher.WithOptions(WithVariant(VariantSHA256)); err != nil { 60 | return nil, err 61 | } 62 | 63 | return hasher, nil 64 | } 65 | 66 | // NewSHA384 returns a SHA384 variant *pbkdf2.Hasher with the additional opts applied if any. 67 | func NewSHA384(opts ...Opt) (hasher *Hasher, err error) { 68 | if hasher, err = New(opts...); err != nil { 69 | return nil, err 70 | } 71 | 72 | if err = hasher.WithOptions(WithVariant(VariantSHA384)); err != nil { 73 | return nil, err 74 | } 75 | 76 | return hasher, nil 77 | } 78 | 79 | // NewSHA512 returns a SHA512 variant *pbkdf2.Hasher with the additional opts applied if any. 80 | func NewSHA512(opts ...Opt) (hasher *Hasher, err error) { 81 | if hasher, err = New(opts...); err != nil { 82 | return nil, err 83 | } 84 | 85 | if err = hasher.WithOptions(WithVariant(VariantSHA512)); err != nil { 86 | return nil, err 87 | } 88 | 89 | return hasher, nil 90 | } 91 | 92 | // Hasher is a crypt.Hash for PBKDF2 which can be initialized via pbkdf2.New using a functional options pattern. 93 | type Hasher struct { 94 | variant Variant 95 | 96 | iterations, bytesKey, bytesSalt int 97 | 98 | d bool 99 | } 100 | 101 | // WithOptions applies the provided functional options provided as a pbkdf2.Opt to the pbkdf2.Hasher. 102 | func (h *Hasher) WithOptions(opts ...Opt) (err error) { 103 | for _, opt := range opts { 104 | if err = opt(h); err != nil { 105 | return err 106 | } 107 | } 108 | 109 | return nil 110 | } 111 | 112 | // Hash performs the hashing operation and returns either a algorithm.Digest or an error. 113 | func (h *Hasher) Hash(password string) (digest algorithm.Digest, err error) { 114 | h.defaults() 115 | 116 | if digest, err = h.hash(password); err != nil { 117 | return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) 118 | } 119 | 120 | return digest, nil 121 | } 122 | 123 | func (h *Hasher) hash(password string) (digest algorithm.Digest, err error) { 124 | var salt []byte 125 | 126 | if salt, err = random.Bytes(h.bytesSalt); err != nil { 127 | return nil, fmt.Errorf("%w: %v", algorithm.ErrSaltReadRandomBytes, err) 128 | } 129 | 130 | return h.hashWithSalt(password, salt) 131 | } 132 | 133 | // HashWithSalt overloads the Hash method allowing the user to provide a salt. It's recommended instead to configure the 134 | // salt size and let this be a random value generated using crypto/rand. 135 | func (h *Hasher) HashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { 136 | h.defaults() 137 | 138 | if digest, err = h.hashWithSalt(password, salt); err != nil { 139 | return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) 140 | } 141 | 142 | return digest, nil 143 | } 144 | 145 | func (h *Hasher) hashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { 146 | if s := len(salt); s > SaltLengthMax || s < SaltLengthMin { 147 | return nil, fmt.Errorf("%w: salt bytes must have a length of between %d and %d but has a length of %d", algorithm.ErrSaltInvalid, SaltLengthMin, SaltLengthMax, len(salt)) 148 | } 149 | 150 | d := &Digest{ 151 | variant: h.variant, 152 | iterations: h.iterations, 153 | t: h.bytesKey, 154 | salt: salt, 155 | } 156 | 157 | d.defaults() 158 | 159 | d.key = pbkdf2.Key([]byte(password), d.salt, d.iterations, d.t, d.variant.HashFunc()) 160 | 161 | return d, nil 162 | } 163 | 164 | // MustHash overloads the Hash method and panics if the error is not nil. It's recommended if you use this option to 165 | // utilize the Validate method first or handle the panic appropriately. 166 | func (h *Hasher) MustHash(password string) (digest algorithm.Digest) { 167 | var err error 168 | 169 | if digest, err = h.Hash(password); err != nil { 170 | panic(err) 171 | } 172 | 173 | return digest 174 | } 175 | 176 | // Validate checks the settings/parameters for this Hash and returns an error. 177 | func (h *Hasher) Validate() (err error) { 178 | if err = h.validate(); err != nil { 179 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, err) 180 | } 181 | 182 | return nil 183 | } 184 | 185 | func (h *Hasher) validate() (err error) { 186 | h.defaults() 187 | 188 | keyLengthMin := h.variant.HashFunc()().Size() 189 | 190 | if h.bytesKey < keyLengthMin || h.bytesKey > KeyLengthMax { 191 | return fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "key length", keyLengthMin, "", KeyLengthMax, h.bytesKey) 192 | } 193 | 194 | return nil 195 | } 196 | 197 | func (h *Hasher) defaults() { 198 | if h.d { 199 | return 200 | } 201 | 202 | h.d = true 203 | 204 | if h.variant == VariantNone { 205 | h.variant = variantDefault 206 | } 207 | 208 | if h.bytesKey == 0 { 209 | h.bytesKey = h.variant.HashFunc()().Size() 210 | } 211 | 212 | if h.bytesSalt < SaltLengthMin { 213 | h.bytesSalt = algorithm.SaltLengthDefault 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /algorithm/pbkdf2/opts.go: -------------------------------------------------------------------------------- 1 | package pbkdf2 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-crypt/crypt/algorithm" 7 | ) 8 | 9 | // Opt describes the functional option pattern for the pbkdf2.Hasher. 10 | type Opt func(h *Hasher) (err error) 11 | 12 | // WithVariant configures the pbkdf2.Variant of the resulting pbkdf2.Digest. 13 | // Default is pbkdf2.VariantSHA256. 14 | func WithVariant(variant Variant) Opt { 15 | return func(h *Hasher) (err error) { 16 | switch variant { 17 | case VariantNone: 18 | return nil 19 | case VariantSHA1, VariantSHA224, VariantSHA256, VariantSHA384, VariantSHA512: 20 | h.variant = variant 21 | 22 | return nil 23 | default: 24 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant '%d' is invalid", algorithm.ErrParameterInvalid, variant)) 25 | } 26 | } 27 | } 28 | 29 | // WithVariantName uses the variant name or identifier to configure the pbkdf2.Variant of the resulting pbkdf2.Digest. 30 | // Default is pbkdf2.VariantSHA256. 31 | func WithVariantName(identifier string) Opt { 32 | return func(h *Hasher) (err error) { 33 | if identifier == "" { 34 | return nil 35 | } 36 | 37 | variant := NewVariant(identifier) 38 | 39 | if variant == VariantNone { 40 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant identifier '%s' is invalid", algorithm.ErrParameterInvalid, identifier)) 41 | } 42 | 43 | h.variant = variant 44 | 45 | return nil 46 | } 47 | } 48 | 49 | // WithIterations sets the iterations parameter of the resulting pbkdf2.Digest. 50 | // Minimum is 100000, Maximum is 2147483647. Default is 29000. 51 | func WithIterations(iterations int) Opt { 52 | return func(h *Hasher) (err error) { 53 | if iterations < IterationsMin || iterations > IterationsMax { 54 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "iterations", IterationsMin, "", IterationsMax, iterations)) 55 | } 56 | 57 | h.iterations = iterations 58 | 59 | return nil 60 | } 61 | } 62 | 63 | // WithKeyLength adjusts the tag length (in bytes) of the resulting pbkdf2.Digest. Default is the output length of the 64 | // HMAC digest. Generally it's NOT recommended to change this value at all and let the default values be applied. 65 | // Longer tag lengths technically reduce security by forcing a longer hash calculation for legitimate users but not 66 | // requiring this for an attacker. In addition most implementations expect the tag length to match the output length of 67 | // the HMAC digest. This option MUST come after a specific pbkdf2.WithVariant. 68 | func WithKeyLength(bytes int) Opt { 69 | return func(h *Hasher) (err error) { 70 | if h.variant == VariantNone { 71 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("tag size must not be set before the variant is set")) 72 | } 73 | 74 | keySizeMin := h.variant.HashFunc()().Size() 75 | 76 | if bytes < keySizeMin || bytes > KeyLengthMax { 77 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "tag size", keySizeMin, "", KeyLengthMax, bytes)) 78 | } 79 | 80 | h.bytesKey = bytes 81 | 82 | return nil 83 | } 84 | } 85 | 86 | // WithSaltLength adjusts the salt size (in bytes) of the resulting pbkdf2.Digest. 87 | // Minimum is 8, Maximum is 2147483647. Default is 16. 88 | func WithSaltLength(bytes int) Opt { 89 | return func(h *Hasher) (err error) { 90 | if bytes < SaltLengthMin || bytes > SaltLengthMax { 91 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "salt length", SaltLengthMin, "", SaltLengthMax, bytes)) 92 | } 93 | 94 | h.bytesSalt = bytes 95 | 96 | return nil 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /algorithm/pbkdf2/variant.go: -------------------------------------------------------------------------------- 1 | package pbkdf2 2 | 3 | import ( 4 | "crypto/sha1" //nolint:gosec 5 | "crypto/sha256" 6 | "crypto/sha512" 7 | 8 | "github.com/go-crypt/crypt/algorithm" 9 | ) 10 | 11 | // NewVariant converts an identifier string to a pbkdf2.Variant. 12 | func NewVariant(identifier string) (variant Variant) { 13 | switch identifier { 14 | case AlgIdentifier, AlgIdentifierSHA1, algorithm.DigestSHA1: 15 | return VariantSHA1 16 | case AlgIdentifierSHA224, algorithm.DigestSHA224: 17 | return VariantSHA224 18 | case AlgIdentifierSHA256, algorithm.DigestSHA256: 19 | return VariantSHA256 20 | case AlgIdentifierSHA384, algorithm.DigestSHA384: 21 | return VariantSHA384 22 | case AlgIdentifierSHA512, algorithm.DigestSHA512: 23 | return VariantSHA512 24 | default: 25 | return VariantNone 26 | } 27 | } 28 | 29 | // Variant is a variant of the pbkdf2.Digest. 30 | type Variant int 31 | 32 | const ( 33 | // VariantNone is a variant of the pbkdf2.Digest which is unknown. 34 | VariantNone Variant = iota 35 | 36 | // VariantSHA1 is a variant of the pbkdf2.Digest which uses HMAC-SHA-1. 37 | VariantSHA1 38 | 39 | // VariantSHA224 is a variant of the pbkdf2.Digest which uses HMAC-SHA-224. 40 | VariantSHA224 41 | 42 | // VariantSHA256 is a variant of the pbkdf2.Digest which uses HMAC-SHA-256. 43 | VariantSHA256 44 | 45 | // VariantSHA384 is a variant of the pbkdf2.Digest which uses HMAC-SHA-384. 46 | VariantSHA384 47 | 48 | // VariantSHA512 is a variant of the pbkdf2.Digest which uses HMAC-SHA-512. 49 | VariantSHA512 50 | ) 51 | 52 | // String implements the fmt.Stringer returning a string representation of the pbkdf2.Variant. 53 | func (v Variant) String() (variant string) { 54 | switch v { 55 | case VariantSHA1: 56 | return algorithm.DigestSHA1 57 | case VariantSHA224: 58 | return algorithm.DigestSHA224 59 | case VariantSHA256: 60 | return algorithm.DigestSHA256 61 | case VariantSHA384: 62 | return algorithm.DigestSHA384 63 | case VariantSHA512: 64 | return algorithm.DigestSHA512 65 | default: 66 | return 67 | } 68 | } 69 | 70 | // Prefix returns the pbkdf2.Variant prefix identifier. 71 | func (v Variant) Prefix() (prefix string) { 72 | switch v { 73 | case VariantSHA1: 74 | return AlgIdentifier 75 | case VariantSHA224: 76 | return AlgIdentifierSHA224 77 | case VariantSHA256: 78 | return AlgIdentifierSHA256 79 | case VariantSHA384: 80 | return AlgIdentifierSHA384 81 | case VariantSHA512: 82 | return AlgIdentifierSHA512 83 | default: 84 | return 85 | } 86 | } 87 | 88 | // HashFunc returns the internal HMAC algorithm.HashFunc. 89 | func (v Variant) HashFunc() algorithm.HashFunc { 90 | switch v { 91 | case VariantSHA1: 92 | return sha1.New 93 | case VariantSHA224: 94 | return sha256.New224 95 | case VariantSHA256: 96 | return sha256.New 97 | case VariantSHA384: 98 | return sha512.New384 99 | case VariantSHA512: 100 | return sha512.New 101 | default: 102 | return nil 103 | } 104 | } 105 | 106 | // DefaultIterations returns the default iterations for a variant. 107 | func (v Variant) DefaultIterations() int { 108 | switch v { 109 | case VariantSHA1, VariantSHA224: 110 | return IterationsDefaultSHA1 111 | case VariantSHA256, VariantSHA384: 112 | return IterationsDefaultSHA256 113 | case VariantSHA512: 114 | return IterationsDefaultSHA512 115 | default: 116 | return IterationsDefaultSHA1 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /algorithm/plaintext/const.go: -------------------------------------------------------------------------------- 1 | package plaintext 2 | 3 | const ( 4 | // EncodingFmt is the encoding format for this algorithm. 5 | EncodingFmt = "$%s$%s" 6 | 7 | // AlgName is the name for this algorithm. 8 | AlgName = "plaintext" 9 | 10 | // AlgIdentifierPlainText is the identifier used in encoded plaintext variants of this algorithm. 11 | AlgIdentifierPlainText = AlgName 12 | 13 | // AlgIdentifierBase64 is the identifier used in encoded base64 variants of this algorithm. 14 | AlgIdentifierBase64 = "base64" 15 | ) 16 | -------------------------------------------------------------------------------- /algorithm/plaintext/decoder.go: -------------------------------------------------------------------------------- 1 | package plaintext 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-crypt/crypt/algorithm" 7 | "github.com/go-crypt/crypt/internal/encoding" 8 | ) 9 | 10 | // RegisterDecoder the decoder with the algorithm.DecoderRegister. 11 | func RegisterDecoder(r algorithm.DecoderRegister) (err error) { 12 | if err = RegisterDecoderPlainText(r); err != nil { 13 | return err 14 | } 15 | 16 | if err = r.RegisterDecodeFunc(AlgIdentifierBase64, Decode); err != nil { 17 | return err 18 | } 19 | 20 | return nil 21 | } 22 | 23 | // RegisterDecoderPlainText registers specifically the plaintext decoder variant with the algorithm.DecoderRegister. 24 | func RegisterDecoderPlainText(r algorithm.DecoderRegister) (err error) { 25 | if err = r.RegisterDecodeFunc(VariantPlainText.Prefix(), DecodeVariant(VariantPlainText)); err != nil { 26 | return err 27 | } 28 | 29 | return nil 30 | } 31 | 32 | // RegisterDecoderBase64 registers specifically the base64 decoder variant with the algorithm.DecoderRegister. 33 | func RegisterDecoderBase64(r algorithm.DecoderRegister) (err error) { 34 | if err = r.RegisterDecodeFunc(VariantBase64.Prefix(), DecodeVariant(VariantBase64)); err != nil { 35 | return err 36 | } 37 | 38 | return nil 39 | } 40 | 41 | // Decode the encoded digest into a algorithm.Digest. 42 | func Decode(encodedDigest string) (digest algorithm.Digest, err error) { 43 | return DecodeVariant(VariantNone)(encodedDigest) 44 | } 45 | 46 | // DecodeVariant the encoded digest into a algorithm.Digest provided it matches the provided plaintext.Variant. If 47 | // plaintext.VariantNone is used all variants can be decoded. 48 | func DecodeVariant(v Variant) func(encodedDigest string) (digest algorithm.Digest, err error) { 49 | return func(encodedDigest string) (digest algorithm.Digest, err error) { 50 | var ( 51 | parts []string 52 | variant Variant 53 | ) 54 | 55 | if variant, parts, err = decoderParts(encodedDigest); err != nil { 56 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) 57 | } 58 | 59 | if v != VariantNone && v != variant { 60 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, fmt.Errorf("the '%s' variant cannot be decoded only the '%s' variant can be", variant.Prefix(), v.Prefix())) 61 | } 62 | 63 | if digest, err = decode(variant, parts); err != nil { 64 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) 65 | } 66 | 67 | return digest, nil 68 | } 69 | } 70 | 71 | func decoderParts(encodedDigest string) (variant Variant, parts []string, err error) { 72 | parts = encoding.Split(encodedDigest, 3) 73 | 74 | if len(parts) != 3 { 75 | return VariantNone, nil, algorithm.ErrEncodedHashInvalidFormat 76 | } 77 | 78 | variant = NewVariant(parts[1]) 79 | 80 | if variant == VariantNone { 81 | return variant, nil, fmt.Errorf("%w: identifier '%s' is not an encoded %s digest", algorithm.ErrEncodedHashInvalidIdentifier, parts[1], AlgName) 82 | } 83 | 84 | return variant, parts[2:], nil 85 | } 86 | 87 | func decode(variant Variant, parts []string) (digest algorithm.Digest, err error) { 88 | decoded := &Digest{ 89 | variant: variant, 90 | } 91 | 92 | if decoded.key, err = decoded.variant.Decode(parts[0]); err != nil { 93 | return nil, fmt.Errorf("%w: %v", algorithm.ErrEncodedHashKeyEncoding, err) 94 | } 95 | 96 | if len(decoded.key) == 0 { 97 | return nil, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrEncodedHashKeyEncoding) 98 | } 99 | 100 | return decoded, nil 101 | } 102 | -------------------------------------------------------------------------------- /algorithm/plaintext/digest.go: -------------------------------------------------------------------------------- 1 | package plaintext 2 | 3 | import ( 4 | "crypto/subtle" 5 | "fmt" 6 | 7 | "github.com/go-crypt/crypt/algorithm" 8 | ) 9 | 10 | // NewDigest creates a new plaintext.Digest using the plaintext.Variant. 11 | func NewDigest(password string) (digest Digest) { 12 | digest = Digest{ 13 | variant: VariantPlainText, 14 | key: []byte(password), 15 | } 16 | 17 | return digest 18 | } 19 | 20 | // NewBase64Digest creates a new plaintext.Digest using the Base64 plaintext.Variant. 21 | func NewBase64Digest(password string) (digest Digest) { 22 | digest = Digest{ 23 | variant: VariantBase64, 24 | key: []byte(password), 25 | } 26 | 27 | return digest 28 | } 29 | 30 | // Digest is an algorithm.Digest which handles plaintext matching. 31 | type Digest struct { 32 | variant Variant 33 | 34 | key []byte 35 | } 36 | 37 | // Match returns true if the string password matches the current plaintext.Digest. 38 | func (d *Digest) Match(password string) (match bool) { 39 | return d.MatchBytes([]byte(password)) 40 | } 41 | 42 | // MatchBytes returns true if the []byte passwordBytes matches the current plaintext.Digest. 43 | func (d *Digest) MatchBytes(passwordBytes []byte) (match bool) { 44 | match, _ = d.MatchBytesAdvanced(passwordBytes) 45 | 46 | return match 47 | } 48 | 49 | // MatchAdvanced is the same as Match except if there is an error it returns that as well. 50 | func (d *Digest) MatchAdvanced(password string) (match bool, err error) { 51 | if len(d.key) == 0 { 52 | return false, fmt.Errorf(algorithm.ErrFmtDigestMatch, AlgName, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrPasswordInvalid)) 53 | } 54 | 55 | return d.MatchBytesAdvanced([]byte(password)) 56 | } 57 | 58 | // MatchBytesAdvanced is the same as MatchBytes except if there is an error it returns that as well. 59 | func (d *Digest) MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) { 60 | if len(d.key) == 0 { 61 | return false, fmt.Errorf(algorithm.ErrFmtDigestMatch, AlgName, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrPasswordInvalid)) 62 | } 63 | 64 | return subtle.ConstantTimeCompare(d.key, passwordBytes) == 1, nil 65 | } 66 | 67 | // Encode returns the encoded form of this plaintext.Digest. 68 | func (d *Digest) Encode() string { 69 | return fmt.Sprintf(EncodingFmt, d.variant.Prefix(), d.variant.Encode(d.key)) 70 | } 71 | 72 | // String returns the storable format of the plaintext.Digest encoded hash. 73 | func (d *Digest) String() string { 74 | return d.Encode() 75 | } 76 | 77 | func (d *Digest) defaults() { 78 | switch d.variant { 79 | case VariantPlainText, VariantBase64: 80 | break 81 | default: 82 | d.variant = VariantPlainText 83 | } 84 | } 85 | 86 | // Key returns the raw plaintext key which can be used in situations where the plaintext value is required such as 87 | // validating JWT's signed by HMAC-SHA256. 88 | func (d *Digest) Key() []byte { 89 | return d.key 90 | } 91 | -------------------------------------------------------------------------------- /algorithm/plaintext/doc.go: -------------------------------------------------------------------------------- 1 | // Package plaintext implements github.com/go-crypt/crypt interfaces with variants of plaintext useful for easy uptake 2 | // of previously unhashed passwords. 3 | // 4 | // This implementation is loaded by crypt.NewDecoderAll. 5 | package plaintext 6 | -------------------------------------------------------------------------------- /algorithm/plaintext/hasher.go: -------------------------------------------------------------------------------- 1 | package plaintext 2 | 3 | import ( 4 | "github.com/go-crypt/crypt/algorithm" 5 | ) 6 | 7 | // New returns a *plaintext.Hasher without any settings configured. 8 | func New(opts ...Opt) (hasher *Hasher, err error) { 9 | hasher = &Hasher{} 10 | 11 | if err = hasher.WithOptions(opts...); err != nil { 12 | return nil, err 13 | } 14 | 15 | if err = hasher.Validate(); err != nil { 16 | return nil, err 17 | } 18 | 19 | return hasher, nil 20 | } 21 | 22 | // Hasher is a crypt.Hash for plaintext which can be initialized via plaintext.New using a functional options pattern. 23 | type Hasher struct { 24 | variant Variant 25 | } 26 | 27 | // WithOptions applies the provided functional options provided as an plaintext.Opt to the plaintext.Hasher. 28 | func (h *Hasher) WithOptions(opts ...Opt) (err error) { 29 | for _, opt := range opts { 30 | if err = opt(h); err != nil { 31 | return err 32 | } 33 | } 34 | 35 | return nil 36 | } 37 | 38 | // Validate checks the hasher configuration to ensure it's valid. This should be used when the plaintext.Hasher is going 39 | // to be reused and you should use it in conjunction with MustHash. 40 | func (h *Hasher) Validate() (err error) { 41 | return nil 42 | } 43 | 44 | // Hash performs the hashing operation on a password and resets any relevant parameters such as a manually set salt. 45 | // It then returns a plaintext.Digest and error. 46 | func (h *Hasher) Hash(password string) (hashed algorithm.Digest, err error) { 47 | d := &Digest{ 48 | variant: h.variant, 49 | key: []byte(password), 50 | } 51 | 52 | d.defaults() 53 | 54 | return d, nil 55 | } 56 | 57 | // HashWithSalt is an overload of plaintext.Digest that also accepts a salt. 58 | func (h *Hasher) HashWithSalt(password string, _ []byte) (hashed algorithm.Digest, err error) { 59 | return h.Hash(password) 60 | } 61 | 62 | // MustHash overloads the Hash method and panics if the error is not nil. It's recommended if you use this method to 63 | // utilize the Validate method first or handle the panic appropriately. 64 | func (h *Hasher) MustHash(password string) (hashed algorithm.Digest) { 65 | if d, err := h.Hash(password); err != nil { 66 | panic(err) 67 | } else { 68 | return d 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /algorithm/plaintext/opts.go: -------------------------------------------------------------------------------- 1 | package plaintext 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-crypt/crypt/algorithm" 7 | ) 8 | 9 | // Opt describes the functional option pattern for the plaintext.Hasher. 10 | type Opt func(h *Hasher) (err error) 11 | 12 | // WithVariant configures the plaintext.Variant of the resulting plaintext.Digest. 13 | // Default is plaintext.VariantPlainText. 14 | func WithVariant(variant Variant) Opt { 15 | return func(h *Hasher) (err error) { 16 | switch variant { 17 | case VariantNone, VariantPlainText, VariantBase64: 18 | h.variant = variant 19 | 20 | return nil 21 | default: 22 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant '%d' is invalid", algorithm.ErrParameterInvalid, variant)) 23 | } 24 | } 25 | } 26 | 27 | // WithVariantName uses the variant name or identifier to configure the plaintext.Variant of the resulting plaintext.Digest. 28 | // Default is plaintext.VariantPlainText. 29 | func WithVariantName(identifier string) Opt { 30 | return func(h *Hasher) (err error) { 31 | if identifier == "" { 32 | return nil 33 | } 34 | 35 | variant := NewVariant(identifier) 36 | 37 | if variant == VariantNone { 38 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant identifier '%s' is invalid", algorithm.ErrParameterInvalid, identifier)) 39 | } 40 | 41 | h.variant = variant 42 | 43 | return nil 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /algorithm/plaintext/variant.go: -------------------------------------------------------------------------------- 1 | package plaintext 2 | 3 | import ( 4 | "github.com/go-crypt/crypt/internal/encoding" 5 | ) 6 | 7 | // NewVariant converts an identifier string to a plaintext.Variant. 8 | func NewVariant(identifier string) (variant Variant) { 9 | switch identifier { 10 | case AlgIdentifierPlainText: 11 | return VariantPlainText 12 | case AlgIdentifierBase64: 13 | return VariantBase64 14 | default: 15 | return VariantNone 16 | } 17 | } 18 | 19 | // Variant is a variant of the plaintext.Digest. 20 | type Variant int 21 | 22 | const ( 23 | // VariantNone is a variant of the plaintext.Digest which is unknown. 24 | VariantNone Variant = iota 25 | 26 | // VariantPlainText is a variant of the plaintext.Digest which stores the key as plain text. 27 | VariantPlainText 28 | 29 | // VariantBase64 is a variant of the plaintext.Digest which stores the key as a base64 string. 30 | VariantBase64 31 | ) 32 | 33 | // Prefix returns the plaintext.Variant prefix identifier. 34 | func (v Variant) Prefix() (prefix string) { 35 | switch v { 36 | case VariantPlainText: 37 | return AlgIdentifierPlainText 38 | case VariantBase64: 39 | return AlgIdentifierBase64 40 | default: 41 | return 42 | } 43 | } 44 | 45 | // Decode performs the decode operation for this plaintext.Variant. 46 | func (v Variant) Decode(src string) (dst []byte, err error) { 47 | switch v { 48 | case VariantBase64: 49 | return encoding.Base64RawAdaptedEncoding.DecodeString(src) 50 | default: 51 | return []byte(src), nil 52 | } 53 | } 54 | 55 | // Encode performs the encode operation for this plaintext.Variant. 56 | func (v Variant) Encode(src []byte) (dst string) { 57 | switch v { 58 | case VariantBase64: 59 | return encoding.Base64RawAdaptedEncoding.EncodeToString(src) 60 | default: 61 | return string(src) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /algorithm/scrypt/const.amd64.go: -------------------------------------------------------------------------------- 1 | //go:build amd64 && !purego 2 | 3 | package scrypt 4 | 5 | import ( 6 | "math" 7 | ) 8 | 9 | const ( 10 | // KeyLengthMax is the maximum key size accepted. 11 | KeyLengthMax = math.MaxUint32 * 32 12 | ) 13 | -------------------------------------------------------------------------------- /algorithm/scrypt/const.go: -------------------------------------------------------------------------------- 1 | package scrypt 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | const ( 8 | // EncodingFmt is the format of the encoded digest. 9 | EncodingFmt = "$%s$ln=%d,r=%d,p=%d$%s$%s" 10 | 11 | // EncodingFmtYescrypt is the format of the encoded digest. 12 | EncodingFmtYescrypt = "$%s$%s$%s$%s" 13 | 14 | // AlgName is the name for this algorithm. 15 | AlgName = "scrypt" 16 | 17 | // AlgNameYescrypt is the name for this algorithm's yescrypt variant. 18 | AlgNameYescrypt = "yescrypt" 19 | 20 | // KeyLengthMin is the minimum key length accepted. 21 | KeyLengthMin = 1 22 | 23 | // SaltLengthMin is the minimum salt length accepted. 24 | SaltLengthMin = 8 25 | 26 | // SaltLengthMax is the maximum salt length accepted. 27 | SaltLengthMax = 1024 28 | 29 | // IterationsMin is the minimum number of iterations accepted. 30 | IterationsMin = 1 31 | 32 | // IterationsMax is the maximum number of iterations accepted. 33 | IterationsMax = 58 34 | 35 | // IterationsDefault is the default number of iterations. 36 | IterationsDefault = 16 37 | 38 | // BlockSizeMin is the minimum block size accepted. 39 | BlockSizeMin = 1 40 | 41 | // BlockSizeMax is the maximum block size accepted. 42 | BlockSizeMax = math.MaxInt / 256 43 | 44 | // BlockSizeDefault is the default block size. 45 | BlockSizeDefault = 8 46 | 47 | // ParallelismMin is the minimum parallelism factor accepted. 48 | ParallelismMin = 1 49 | 50 | // ParallelismMax is the maximum parallelism factor accepted. 51 | // 52 | // Equation is based on the following text from RFC: 53 | // 54 | // The parallelization parameter p 55 | // ("parallelizationParameter") is a positive integer less than or equal 56 | // to ((2^32-1) * 32) / (128 * r). 57 | // 58 | // When r has a minimum of 1, this makes the equation ((2^32-1) * 32) / 128. 59 | ParallelismMax = 1073741823 60 | 61 | // ParallelismDefault is the default parallelism factor. 62 | ParallelismDefault = ParallelismMin 63 | ) 64 | 65 | const ( 66 | AlgIdentifier = AlgName 67 | 68 | AlgIdentifierYescrypt = "y" 69 | ) 70 | 71 | const ( 72 | oP = "p" 73 | oR = "r" 74 | oLN = "ln" 75 | 76 | variantDefault = VariantScrypt 77 | ) 78 | -------------------------------------------------------------------------------- /algorithm/scrypt/const.pure.go: -------------------------------------------------------------------------------- 1 | //go:build !amd64 || purego 2 | 3 | package scrypt 4 | 5 | import ( 6 | "math" 7 | ) 8 | 9 | const ( 10 | // KeyLengthMax is the maximum key size accepted. 11 | KeyLengthMax = math.MaxInt32 12 | ) 13 | -------------------------------------------------------------------------------- /algorithm/scrypt/decoder.go: -------------------------------------------------------------------------------- 1 | package scrypt 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | 7 | "github.com/go-crypt/x/yescrypt" 8 | 9 | "github.com/go-crypt/crypt/algorithm" 10 | "github.com/go-crypt/crypt/internal/encoding" 11 | ) 12 | 13 | // RegisterDecoder the decoder with the algorithm.DecoderRegister. 14 | func RegisterDecoder(r algorithm.DecoderRegister) (err error) { 15 | if err = RegisterDecoderScrypt(r); err != nil { 16 | return err 17 | } 18 | 19 | if err = RegisterDecoderYescrypt(r); err != nil { 20 | return err 21 | } 22 | 23 | return nil 24 | } 25 | 26 | // RegisterDecoderScrypt the scrypt decoder with the algorithm.DecoderRegister. 27 | func RegisterDecoderScrypt(r algorithm.DecoderRegister) (err error) { 28 | if err = r.RegisterDecodeFunc(VariantScrypt.Prefix(), Decode); err != nil { 29 | return err 30 | } 31 | 32 | return nil 33 | } 34 | 35 | // RegisterDecoderYescrypt the yescrypt decoder with the algorithm.DecoderRegister. 36 | func RegisterDecoderYescrypt(r algorithm.DecoderRegister) (err error) { 37 | if err = r.RegisterDecodeFunc(VariantYescrypt.Prefix(), Decode); err != nil { 38 | return err 39 | } 40 | 41 | return nil 42 | } 43 | 44 | // Decode the encoded digest into a algorithm.Digest. 45 | func Decode(encodedDigest string) (digest algorithm.Digest, err error) { 46 | return DecodeVariant(VariantNone)(encodedDigest) 47 | } 48 | 49 | // DecodeVariant the encoded digest into a algorithm.Digest provided it matches the provided scrypt.Variant. If 50 | // scrypt.VariantNone is used all variants can be decoded. 51 | func DecodeVariant(v Variant) func(encodedDigest string) (digest algorithm.Digest, err error) { 52 | return func(encodedDigest string) (digest algorithm.Digest, err error) { 53 | var ( 54 | parts []string 55 | variant Variant 56 | ) 57 | 58 | if variant, parts, err = decoderParts(encodedDigest); err != nil { 59 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) 60 | } 61 | 62 | if v != VariantNone && v != variant { 63 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, fmt.Errorf("the '%s' variant cannot be decoded only the '%s' variant can be", variant.String(), v.String())) 64 | } 65 | 66 | if digest, err = decode(variant, parts); err != nil { 67 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) 68 | } 69 | 70 | return digest, nil 71 | } 72 | } 73 | 74 | func decoderParts(encodedDigest string) (variant Variant, parts []string, err error) { 75 | parts = encoding.Split(encodedDigest, -1) 76 | 77 | if len(parts) != 5 { 78 | return VariantNone, nil, algorithm.ErrEncodedHashInvalidFormat 79 | } 80 | 81 | variant = NewVariant(parts[1]) 82 | 83 | if variant == VariantNone { 84 | return variant, nil, fmt.Errorf("%w: identifier '%s' is not an encoded %s digest", algorithm.ErrEncodedHashInvalidIdentifier, parts[1], AlgName) 85 | } 86 | 87 | return variant, parts[2:], nil 88 | } 89 | 90 | func decode(variant Variant, parts []string) (digest algorithm.Digest, err error) { 91 | decoded := &Digest{ 92 | variant: variant, 93 | ln: IterationsDefault, 94 | r: BlockSizeDefault, 95 | p: ParallelismDefault, 96 | } 97 | 98 | switch variant { 99 | case VariantYescrypt: 100 | if _, decoded.ln, decoded.r, err = yescrypt.DecodeSetting([]byte(parts[0])); err != nil { 101 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) 102 | } 103 | 104 | decoded.salt, decoded.key = yescrypt.Decode64([]byte(parts[1])), yescrypt.Decode64([]byte(parts[2])) 105 | default: 106 | var params []encoding.Parameter 107 | 108 | if params, err = encoding.DecodeParameterStr(parts[0]); err != nil { 109 | return nil, err 110 | } 111 | 112 | for _, param := range params { 113 | switch param.Key { 114 | case oLN: 115 | decoded.ln, err = param.Int() 116 | case oR: 117 | decoded.r, err = param.Int() 118 | case oP: 119 | decoded.p, err = param.Int() 120 | default: 121 | return nil, fmt.Errorf("%w: option '%s' with value '%s' is unknown", algorithm.ErrEncodedHashInvalidOptionKey, param.Key, param.Value) 122 | } 123 | 124 | if err != nil { 125 | return nil, fmt.Errorf("%w: option '%s' has invalid value '%s': %v", algorithm.ErrEncodedHashInvalidOptionValue, param.Key, param.Value, err) 126 | } 127 | } 128 | 129 | if decoded.salt, err = base64.RawStdEncoding.DecodeString(parts[1]); err != nil { 130 | return nil, fmt.Errorf("%w: %v", algorithm.ErrEncodedHashSaltEncoding, err) 131 | } 132 | 133 | if decoded.key, err = base64.RawStdEncoding.DecodeString(parts[2]); err != nil { 134 | return nil, fmt.Errorf("%w: %v", algorithm.ErrEncodedHashKeyEncoding, err) 135 | } 136 | } 137 | 138 | if len(decoded.key) == 0 { 139 | return nil, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrEncodedHashKeyEncoding) 140 | } 141 | 142 | return decoded, nil 143 | } 144 | -------------------------------------------------------------------------------- /algorithm/scrypt/digest.go: -------------------------------------------------------------------------------- 1 | package scrypt 2 | 3 | import ( 4 | "crypto/subtle" 5 | "fmt" 6 | 7 | "github.com/go-crypt/crypt/algorithm" 8 | ) 9 | 10 | // Digest is a scrypt.Digest which handles scrypt hashes. 11 | type Digest struct { 12 | variant Variant 13 | 14 | ln, r, p int 15 | 16 | salt, key []byte 17 | } 18 | 19 | // Match returns true if the string password matches the current scrypt.Digest. 20 | func (d *Digest) Match(password string) (match bool) { 21 | return d.MatchBytes([]byte(password)) 22 | } 23 | 24 | // MatchBytes returns true if the []byte passwordBytes matches the current scrypt.Digest. 25 | func (d *Digest) MatchBytes(passwordBytes []byte) (match bool) { 26 | match, _ = d.MatchBytesAdvanced(passwordBytes) 27 | 28 | return match 29 | } 30 | 31 | // MatchAdvanced is the same as Match except if there is an error it returns that as well. 32 | func (d *Digest) MatchAdvanced(password string) (match bool, err error) { 33 | if match, err = d.MatchBytesAdvanced([]byte(password)); err != nil { 34 | return match, fmt.Errorf(algorithm.ErrFmtDigestMatch, AlgName, err) 35 | } 36 | 37 | return match, nil 38 | } 39 | 40 | // MatchBytesAdvanced is the same as MatchBytes except if there is an error it returns that as well. 41 | func (d *Digest) MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) { 42 | if len(d.key) == 0 { 43 | return false, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrPasswordInvalid) 44 | } 45 | 46 | var key []byte 47 | 48 | if key, err = d.variant.KeyFunc()(passwordBytes, d.salt, d.n(), d.r, d.p, len(d.key)); err != nil { 49 | return false, err 50 | } 51 | 52 | return subtle.ConstantTimeCompare(d.key, key) == 1, nil 53 | } 54 | 55 | // Encode returns the encoded form of this scrypt.Digest. 56 | func (d *Digest) Encode() string { 57 | return d.variant.Encode(d.ln, d.r, d.p, d.salt, d.key) 58 | } 59 | 60 | // String returns the storable format of the scrypt.Digest encoded hash. 61 | func (d *Digest) String() string { 62 | return d.Encode() 63 | } 64 | 65 | // n returns 2 to the power of log N i.e d.ln. 66 | func (d *Digest) n() (n int) { 67 | return 1 << d.ln 68 | } 69 | 70 | func (d *Digest) defaults() { 71 | switch d.variant { 72 | case VariantScrypt, VariantYescrypt: 73 | break 74 | default: 75 | d.variant = variantDefault 76 | } 77 | 78 | if d.ln < IterationsMin { 79 | d.ln = IterationsDefault 80 | } 81 | 82 | if d.r < BlockSizeMin { 83 | d.r = BlockSizeDefault 84 | } 85 | 86 | if d.p < ParallelismMin { 87 | d.p = ParallelismDefault 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /algorithm/scrypt/doc.go: -------------------------------------------------------------------------------- 1 | // Package scrypt provides helpful abstractions for an implementation of RFC7914 and implements 2 | // github.com/go-crypt/crypt interfaces. 3 | // 4 | // This implementation is loaded by crypt.NewDefaultDecoder and crypt.NewDecoderAll. 5 | package scrypt 6 | -------------------------------------------------------------------------------- /algorithm/scrypt/hasher.go: -------------------------------------------------------------------------------- 1 | package scrypt 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | 7 | "github.com/go-crypt/crypt/algorithm" 8 | "github.com/go-crypt/crypt/internal/random" 9 | ) 10 | 11 | // scrypt RFC7914: https://www.rfc-editor.org/rfc/rfc7914.html. 12 | 13 | // New returns a new scrypt.Hasher with the provided functional options applied. 14 | func New(opts ...Opt) (hasher *Hasher, err error) { 15 | hasher = &Hasher{} 16 | 17 | if err = hasher.WithOptions(opts...); err != nil { 18 | return nil, err 19 | } 20 | 21 | if err = hasher.Validate(); err != nil { 22 | return nil, err 23 | } 24 | 25 | return hasher, nil 26 | } 27 | 28 | func NewScrypt(opts ...Opt) (hasher *Hasher, err error) { 29 | if hasher, err = New(opts...); err != nil { 30 | return nil, err 31 | } 32 | 33 | if err = hasher.WithOptions(WithVariant(VariantScrypt)); err != nil { 34 | return nil, err 35 | } 36 | 37 | return hasher, nil 38 | } 39 | 40 | func NewYescrypt(opts ...Opt) (hasher *Hasher, err error) { 41 | if hasher, err = New(opts...); err != nil { 42 | return nil, err 43 | } 44 | 45 | if err = hasher.WithOptions(WithVariant(VariantYescrypt)); err != nil { 46 | return nil, err 47 | } 48 | 49 | return hasher, nil 50 | } 51 | 52 | // Hasher is a crypt.Hash for scrypt which can be initialized via New using a functional options pattern. 53 | type Hasher struct { 54 | variant Variant 55 | 56 | ln, r, k, p, bytesSalt int 57 | 58 | d bool 59 | } 60 | 61 | // WithOptions defines the options for this scrypt.Hasher. 62 | func (h *Hasher) WithOptions(opts ...Opt) (err error) { 63 | for _, opt := range opts { 64 | if err = opt(h); err != nil { 65 | return err 66 | } 67 | } 68 | 69 | return nil 70 | } 71 | 72 | // Hash performs the hashing operation and returns either a Digest or an error. 73 | func (h *Hasher) Hash(password string) (digest algorithm.Digest, err error) { 74 | h.defaults() 75 | 76 | if digest, err = h.hash(password); err != nil { 77 | return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) 78 | } 79 | 80 | return digest, nil 81 | } 82 | 83 | func (h *Hasher) hash(password string) (digest algorithm.Digest, err error) { 84 | h.defaults() 85 | 86 | var salt []byte 87 | 88 | if salt, err = random.Bytes(h.bytesSalt); err != nil { 89 | return nil, fmt.Errorf("%w: %v", algorithm.ErrSaltReadRandomBytes, err) 90 | } 91 | 92 | return h.hashWithSalt(password, salt) 93 | } 94 | 95 | // HashWithSalt overloads the Hash method allowing the user to provide a salt. It's recommended instead to configure the 96 | // salt size and let this be a random value generated using crypto/rand. 97 | func (h *Hasher) HashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { 98 | h.defaults() 99 | 100 | if digest, err = h.hashWithSalt(password, salt); err != nil { 101 | return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) 102 | } 103 | 104 | return digest, nil 105 | } 106 | 107 | func (h *Hasher) hashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { 108 | if s := len(salt); s > SaltLengthMax || s < SaltLengthMin { 109 | return nil, fmt.Errorf("%w: salt bytes must have a length of between %d and %d but has a length of %d", algorithm.ErrSaltInvalid, SaltLengthMin, SaltLengthMax, len(salt)) 110 | } 111 | 112 | d := &Digest{ 113 | variant: h.variant, 114 | ln: h.ln, 115 | r: h.r, 116 | p: h.p, 117 | salt: salt, 118 | } 119 | 120 | d.defaults() 121 | 122 | if d.key, err = d.variant.KeyFunc()([]byte(password), d.salt, d.n(), d.r, d.p, h.k); err != nil { 123 | return nil, fmt.Errorf("%w: %v", algorithm.ErrKeyDerivation, err) 124 | } 125 | 126 | return d, nil 127 | } 128 | 129 | // MustHash overloads the Hash method and panics if the error is not nil. It's recommended if you use this option to 130 | // utilize the Validate method first or handle the panic appropriately. 131 | func (h *Hasher) MustHash(password string) (digest algorithm.Digest) { 132 | var err error 133 | 134 | if digest, err = h.Hash(password); err != nil { 135 | panic(err) 136 | } 137 | 138 | return digest 139 | } 140 | 141 | // Validate checks the settings/parameters for this Hash and returns an error. 142 | func (h *Hasher) Validate() (err error) { 143 | h.defaults() 144 | 145 | if err = h.validate(); err != nil { 146 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, err) 147 | } 148 | 149 | return nil 150 | } 151 | 152 | func (h *Hasher) validate() (err error) { 153 | rp := uint64(h.r) * uint64(h.p) 154 | 155 | if rp >= 1<<30 { 156 | return fmt.Errorf("%w: parameters 'r' and 'p' must be less than %d when multiplied but they are '%d'", algorithm.ErrParameterInvalid, 1<<30, rp) 157 | } 158 | 159 | if h.r > 0 { 160 | mp := KeyLengthMax / (128 * h.r) 161 | 162 | if h.p < ParallelismMin || h.p > mp { 163 | return fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "p", ParallelismMin, "", mp, h.p) 164 | } 165 | 166 | nr := math.MaxInt / 128 / h.r 167 | 168 | N := 1 << h.ln 169 | 170 | if N > nr { 171 | return fmt.Errorf("%w: parameter 'ln' when raised to the power of 2 must be less than or equal to %d (%d / r) but it is set to '%d' which is equal to '%d'", algorithm.ErrParameterInvalid, nr, math.MaxInt/128, h.ln, N) 172 | } 173 | } 174 | 175 | if h.p > 0 { 176 | pr := math.MaxInt / 128 / h.p 177 | 178 | if pr < BlockSizeMax { 179 | if h.r > pr { 180 | return fmt.Errorf("%w: parameter 'r' when parameter 'p' is %d must be less than %d (%d / p) but it is set to '%d'", algorithm.ErrParameterInvalid, h.p, pr, math.MaxInt/128, h.r) 181 | } 182 | } 183 | } 184 | 185 | return nil 186 | } 187 | 188 | func (h *Hasher) defaults() { 189 | if h.d { 190 | return 191 | } 192 | 193 | h.d = true 194 | 195 | if h.variant == VariantNone { 196 | h.variant = variantDefault 197 | } 198 | 199 | if h.k == 0 { 200 | h.k = algorithm.KeyLengthDefault 201 | } 202 | 203 | if h.bytesSalt == 0 { 204 | h.bytesSalt = algorithm.SaltLengthDefault 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /algorithm/scrypt/opts.go: -------------------------------------------------------------------------------- 1 | package scrypt 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-crypt/crypt/algorithm" 7 | ) 8 | 9 | // Opt describes the functional option pattern for the scrypt.Hasher. 10 | type Opt func(h *Hasher) (err error) 11 | 12 | // WithVariant configures the scrypt.Variant of the resulting scrypt.Digest. 13 | // Default is scrypt.VariantScrypt. 14 | func WithVariant(variant Variant) Opt { 15 | return func(h *Hasher) (err error) { 16 | switch variant { 17 | case VariantNone: 18 | return nil 19 | case VariantScrypt, VariantYescrypt: 20 | h.variant = variant 21 | 22 | return nil 23 | default: 24 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant '%d' is invalid", algorithm.ErrParameterInvalid, variant)) 25 | } 26 | } 27 | } 28 | 29 | // WithVariantName uses the variant name or identifier to configure the scrypt.Variant of the resulting scrypt.Digest. 30 | // Default is scrypt.VariantScrypt. 31 | func WithVariantName(identifier string) Opt { 32 | return func(h *Hasher) (err error) { 33 | if identifier == "" { 34 | return nil 35 | } 36 | 37 | variant := NewVariant(identifier) 38 | 39 | if variant == VariantNone { 40 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant identifier '%s' is invalid", algorithm.ErrParameterInvalid, identifier)) 41 | } 42 | 43 | h.variant = variant 44 | 45 | return nil 46 | } 47 | } 48 | 49 | // WithK adjusts the key length of the resulting scrypt.Digest. 50 | // Minimum is 1, Maximum is 137438953440. Default is 32. 51 | func WithK(k int) Opt { 52 | return func(h *Hasher) (err error) { 53 | if k < KeyLengthMin || k > KeyLengthMax { 54 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "key length", KeyLengthMin, "", KeyLengthMax, k)) 55 | } 56 | 57 | h.k = k 58 | 59 | return nil 60 | } 61 | } 62 | 63 | // WithKeyLength is an alias for WithK. 64 | func WithKeyLength(k int) Opt { 65 | return WithK(k) 66 | } 67 | 68 | // WithS adjusts the salt length of the resulting scrypt.Digest. 69 | // Minimum is 8, Maximum is 1024. Default is 16. 70 | func WithS(s int) Opt { 71 | return func(h *Hasher) (err error) { 72 | if s < SaltLengthMin || s > SaltLengthMax { 73 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "salt length", SaltLengthMin, "", SaltLengthMax, s)) 74 | } 75 | 76 | h.bytesSalt = s 77 | 78 | return nil 79 | } 80 | } 81 | 82 | // WithSaltLength is an alias for WithS. 83 | func WithSaltLength(s int) Opt { 84 | return WithS(s) 85 | } 86 | 87 | // WithLN sets the ln parameter (logN) of the resulting scrypt.Digest. 88 | // Minimum is 1, Maximum is 58. Default is 16. 89 | func WithLN(ln int) Opt { 90 | return func(h *Hasher) (err error) { 91 | if ln < IterationsMin || ln > IterationsMax { 92 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "iterations", IterationsMin, "", IterationsMax, ln)) 93 | } 94 | 95 | h.ln = ln 96 | 97 | return nil 98 | } 99 | } 100 | 101 | // WithR sets the r parameter (block size) of the resulting scrypt.Digest. 102 | // Minimum is 1, Maximum is math.MaxInt / 256. Default is 8. 103 | func WithR(r int) Opt { 104 | return func(h *Hasher) (err error) { 105 | if r < BlockSizeMin || r > BlockSizeMax { 106 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "block size", BlockSizeMin, "", BlockSizeMax, r)) 107 | } 108 | 109 | h.r = r 110 | 111 | return nil 112 | } 113 | } 114 | 115 | // WithBlockSize is an alias for WithR. 116 | func WithBlockSize(r int) Opt { 117 | return WithS(r) 118 | } 119 | 120 | // WithP sets the p parameter (parallelism factor) of the resulting scrypt.Digest. 121 | // Minimum is 1, Maximum is 1073741823. Default is 1. 122 | func WithP(p int) Opt { 123 | return func(h *Hasher) (err error) { 124 | if p < ParallelismMin || p > ParallelismMax { 125 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "parallelism", ParallelismMin, "", ParallelismMax, p)) 126 | } 127 | 128 | h.p = p 129 | 130 | return nil 131 | } 132 | } 133 | 134 | // WithParallelism is an alias for WithP. 135 | func WithParallelism(p int) Opt { 136 | return WithP(p) 137 | } 138 | -------------------------------------------------------------------------------- /algorithm/scrypt/variant.go: -------------------------------------------------------------------------------- 1 | package scrypt 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | 7 | "github.com/go-crypt/x/scrypt" 8 | "github.com/go-crypt/x/yescrypt" 9 | ) 10 | 11 | // NewVariant converts an identifier string to a scrypt.Variant. 12 | func NewVariant(identifier string) (variant Variant) { 13 | switch identifier { 14 | case AlgName: 15 | return VariantScrypt 16 | case AlgNameYescrypt, AlgIdentifierYescrypt: 17 | return VariantYescrypt 18 | default: 19 | return VariantNone 20 | } 21 | } 22 | 23 | // Variant is a variant of the scrypt.Digest. 24 | type Variant int 25 | 26 | const ( 27 | // VariantNone is the default variant of Scrypt. 28 | VariantNone Variant = iota 29 | 30 | VariantScrypt 31 | 32 | VariantYescrypt 33 | ) 34 | 35 | // String implements the fmt.Stringer returning a string representation of the scrypt.Variant. 36 | func (v Variant) String() (variant string) { 37 | switch v { 38 | case VariantScrypt: 39 | return AlgIdentifier 40 | case VariantYescrypt: 41 | return AlgIdentifierYescrypt 42 | default: 43 | return 44 | } 45 | } 46 | 47 | // Prefix returns the scrypt.Variant prefix identifier. 48 | func (v Variant) Prefix() (prefix string) { 49 | return v.String() 50 | } 51 | 52 | // KeyFunc returns the internal HMAC algorithm.HashFunc. 53 | func (v Variant) KeyFunc() KeyFunc { 54 | switch v { 55 | case VariantScrypt: 56 | return scrypt.Key 57 | case VariantYescrypt: 58 | return yescrypt.Key 59 | default: 60 | return nil 61 | } 62 | } 63 | 64 | // Encode formats the variant encoded bcrypt.Digest. 65 | func (v Variant) Encode(ln, r, p int, salt, key []byte) (f string) { 66 | switch v { 67 | case VariantScrypt: 68 | return fmt.Sprintf(EncodingFmt, v.Prefix(), ln, r, p, base64.RawStdEncoding.EncodeToString(salt), base64.RawStdEncoding.EncodeToString(key)) 69 | case VariantYescrypt: 70 | return fmt.Sprintf(EncodingFmtYescrypt, v.Prefix(), yescrypt.EncodeSetting(0, ln, r), yescrypt.Encode64(salt), yescrypt.Encode64(key)) 71 | default: 72 | return 73 | } 74 | } 75 | 76 | // KeyFunc represents the KeyFunc used by scrypt implementations. 77 | type KeyFunc func(password []byte, salt []byte, N int, r int, p int, keyLen int) (key []byte, err error) 78 | -------------------------------------------------------------------------------- /algorithm/sha1crypt/const.go: -------------------------------------------------------------------------------- 1 | package sha1crypt 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | const ( 8 | // EncodingFmt is the encoding format for this algorithm. 9 | EncodingFmt = "$sha1$%d$%s$%s" 10 | 11 | // AlgName is the name for this algorithm. 12 | AlgName = "sha1crypt" 13 | 14 | // AlgIdentifier is the identifier used in this algorithm. 15 | AlgIdentifier = "sha1" 16 | 17 | // SaltLengthMin is the minimum salt size accepted. 18 | SaltLengthMin = 0 19 | 20 | // SaltLengthMax is the maximum salt size accepted. 21 | SaltLengthMax = 64 22 | 23 | // SaltLengthDefault is the default salt size. 24 | SaltLengthDefault = 8 25 | 26 | // SaltCharSet are the valid characters for the salt. 27 | SaltCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./" 28 | 29 | // IterationsMin is the minimum iterations accepted. 30 | IterationsMin = 0 31 | 32 | // IterationsMax is the maximum iterations accepted. 33 | IterationsMax uint32 = math.MaxUint32 34 | 35 | // IterationsDefault is the default iterations. 36 | IterationsDefault = 480000 37 | ) 38 | -------------------------------------------------------------------------------- /algorithm/sha1crypt/decoder.go: -------------------------------------------------------------------------------- 1 | package sha1crypt 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/go-crypt/crypt/algorithm" 8 | "github.com/go-crypt/crypt/internal/encoding" 9 | ) 10 | 11 | // RegisterDecoder the decoder with the algorithm.DecoderRegister. 12 | func RegisterDecoder(r algorithm.DecoderRegister) (err error) { 13 | if err = RegisterDecoderCommon(r); err != nil { 14 | return err 15 | } 16 | 17 | return nil 18 | } 19 | 20 | // RegisterDecoderCommon registers specifically the common decoder variant with the algorithm.DecoderRegister. 21 | func RegisterDecoderCommon(r algorithm.DecoderRegister) (err error) { 22 | if err = r.RegisterDecodeFunc(AlgIdentifier, Decode); err != nil { 23 | return err 24 | } 25 | 26 | return nil 27 | } 28 | 29 | // Decode the encoded digest into a algorithm.Digest. 30 | func Decode(encodedDigest string) (digest algorithm.Digest, err error) { 31 | var ( 32 | parts []string 33 | ) 34 | 35 | if parts, err = decoderParts(encodedDigest); err != nil { 36 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) 37 | } 38 | 39 | if digest, err = decode(parts); err != nil { 40 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) 41 | } 42 | 43 | return digest, nil 44 | } 45 | 46 | func decoderParts(encodedDigest string) (parts []string, err error) { 47 | parts = encoding.Split(encodedDigest, -1) 48 | 49 | if len(parts) != 5 { 50 | return nil, algorithm.ErrEncodedHashInvalidFormat 51 | } 52 | 53 | if parts[1] != AlgIdentifier { 54 | return nil, fmt.Errorf("%w: identifier '%s' is not an encoded %s digest", algorithm.ErrEncodedHashInvalidIdentifier, parts[1], AlgName) 55 | } 56 | 57 | return parts[2:], nil 58 | } 59 | 60 | func decode(parts []string) (digest algorithm.Digest, err error) { 61 | decoded := &Digest{} 62 | 63 | if parts[0] != "" { 64 | var iterations uint64 65 | 66 | if iterations, err = strconv.ParseUint(parts[0], 10, 32); err != nil { 67 | return nil, fmt.Errorf("%w: option '%s' has invalid value '%s': %v", algorithm.ErrEncodedHashInvalidOptionValue, "rounds", parts[0], err) 68 | } 69 | 70 | decoded.iterations = uint32(iterations) 71 | } 72 | 73 | decoded.salt, decoded.key = []byte(parts[1]), []byte(parts[2]) 74 | 75 | return decoded, nil 76 | } 77 | -------------------------------------------------------------------------------- /algorithm/sha1crypt/digest.go: -------------------------------------------------------------------------------- 1 | package sha1crypt 2 | 3 | import ( 4 | "crypto/subtle" 5 | "fmt" 6 | 7 | "github.com/go-crypt/x/crypt" 8 | 9 | "github.com/go-crypt/crypt/algorithm" 10 | ) 11 | 12 | // Digest is a algorithm.Digest which handles sha1crypt hashes. 13 | type Digest struct { 14 | iterations uint32 15 | 16 | i bool 17 | 18 | salt, key []byte 19 | } 20 | 21 | // Match returns true if the string password matches the current sha1crypt.Digest. 22 | func (d *Digest) Match(password string) (match bool) { 23 | return d.MatchBytes([]byte(password)) 24 | } 25 | 26 | // MatchBytes returns true if the []byte passwordBytes matches the current sha1crypt.Digest. 27 | func (d *Digest) MatchBytes(passwordBytes []byte) (match bool) { 28 | match, _ = d.MatchBytesAdvanced(passwordBytes) 29 | 30 | return match 31 | } 32 | 33 | // MatchAdvanced is the same as Match except if there is an error it returns that as well. 34 | func (d *Digest) MatchAdvanced(password string) (match bool, err error) { 35 | return d.MatchBytesAdvanced([]byte(password)) 36 | } 37 | 38 | // MatchBytesAdvanced is the same as MatchBytes except if there is an error it returns that as well. 39 | func (d *Digest) MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) { 40 | if len(d.key) == 0 { 41 | return false, fmt.Errorf(algorithm.ErrFmtDigestMatch, AlgName, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrPasswordInvalid)) 42 | } 43 | 44 | return subtle.ConstantTimeCompare(d.key, crypt.KeySHA1Crypt(passwordBytes, d.salt, d.iterations)) == 1, nil 45 | } 46 | 47 | // Encode returns the encoded form of this sha1crypt.Digest. 48 | func (d *Digest) Encode() string { 49 | return fmt.Sprintf(EncodingFmt, 50 | d.iterations, d.salt, d.key, 51 | ) 52 | } 53 | 54 | // String returns the storable format of the sha1crypt.Digest encoded hash. 55 | func (d *Digest) String() string { 56 | return d.Encode() 57 | } 58 | 59 | func (d *Digest) defaults() { 60 | if !d.i { 61 | d.iterations = IterationsDefault 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /algorithm/sha1crypt/doc.go: -------------------------------------------------------------------------------- 1 | // Package sha1crypt provides helpful abstractions for an implementation of crypt (SHA1) and implements 2 | // github.com/go-crypt/crypt interfaces. 3 | // 4 | // This implementation is loaded by crypt.NewDecoderAll. 5 | package sha1crypt 6 | -------------------------------------------------------------------------------- /algorithm/sha1crypt/hasher.go: -------------------------------------------------------------------------------- 1 | package sha1crypt 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-crypt/x/crypt" 7 | 8 | "github.com/go-crypt/crypt/algorithm" 9 | "github.com/go-crypt/crypt/internal/random" 10 | ) 11 | 12 | // New returns a *sha1crypt.Hasher with the additional opts applied if any. 13 | func New(opts ...Opt) (hasher *Hasher, err error) { 14 | hasher = &Hasher{} 15 | 16 | if err = hasher.WithOptions(opts...); err != nil { 17 | return nil, err 18 | } 19 | 20 | if err = hasher.Validate(); err != nil { 21 | return nil, err 22 | } 23 | 24 | return hasher, nil 25 | } 26 | 27 | // Hasher is a crypt.Hash for sha1crypt which can be initialized via sha1crypt.New using a functional options pattern. 28 | type Hasher struct { 29 | iterations uint32 30 | i bool 31 | 32 | bytesSalt int 33 | 34 | d bool 35 | } 36 | 37 | // WithOptions applies the provided functional options provided as a sha1crypt.Opt to the sha1crypt.Hasher. 38 | func (h *Hasher) WithOptions(opts ...Opt) (err error) { 39 | for _, opt := range opts { 40 | if err = opt(h); err != nil { 41 | return err 42 | } 43 | } 44 | 45 | return nil 46 | } 47 | 48 | // Hash performs the hashing operation and returns either a algorithm.Digest or an error. 49 | func (h *Hasher) Hash(password string) (digest algorithm.Digest, err error) { 50 | h.defaults() 51 | 52 | if digest, err = h.hash(password); err != nil { 53 | return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) 54 | } 55 | 56 | return digest, nil 57 | } 58 | 59 | // MustHash overloads the Hash method and panics if the error is not nil. It's recommended if you use this option to 60 | // utilize the Validate method first or handle the panic appropriately. 61 | func (h *Hasher) MustHash(password string) (digest algorithm.Digest) { 62 | var err error 63 | 64 | if digest, err = h.Hash(password); err != nil { 65 | panic(err) 66 | } 67 | 68 | return digest 69 | } 70 | 71 | // HashWithSalt overloads the Hash method allowing the user to provide a salt. It's recommended instead to configure the 72 | // salt size and let this be a random value generated using crypto/rand. 73 | func (h *Hasher) HashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { 74 | h.defaults() 75 | 76 | if digest, err = h.hashWithSalt(password, salt); err != nil { 77 | return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) 78 | } 79 | 80 | return digest, nil 81 | } 82 | 83 | // Validate checks the settings/parameters for this sha1crypt.Hasher and returns an error. 84 | func (h *Hasher) Validate() (err error) { 85 | h.defaults() 86 | 87 | return nil 88 | } 89 | 90 | func (h *Hasher) hash(password string) (digest algorithm.Digest, err error) { 91 | var salt []byte 92 | 93 | if salt, err = random.CharSetBytes(h.bytesSalt, SaltCharSet); err != nil { 94 | return nil, fmt.Errorf("%w: %v", algorithm.ErrSaltReadRandomBytes, err) 95 | } 96 | 97 | return h.hashWithSalt(password, salt) 98 | } 99 | 100 | func (h *Hasher) hashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { 101 | if s := len(salt); s > SaltLengthMax || s < SaltLengthMin { 102 | return nil, fmt.Errorf("%w: salt bytes must have a length of between %d and %d but has a length of %d", algorithm.ErrSaltInvalid, SaltLengthMin, SaltLengthMax, len(salt)) 103 | } 104 | 105 | d := &Digest{ 106 | iterations: h.iterations, 107 | i: h.i, 108 | salt: salt, 109 | } 110 | 111 | d.defaults() 112 | 113 | d.key = crypt.KeySHA1Crypt([]byte(password), d.salt, d.iterations) 114 | 115 | return d, nil 116 | } 117 | 118 | func (h *Hasher) defaults() { 119 | if h.d { 120 | return 121 | } 122 | 123 | h.d = true 124 | 125 | if h.bytesSalt < SaltLengthMin { 126 | h.bytesSalt = SaltLengthDefault 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /algorithm/sha1crypt/opts.go: -------------------------------------------------------------------------------- 1 | package sha1crypt 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-crypt/crypt/algorithm" 7 | ) 8 | 9 | // Opt describes the functional option pattern for the sha1crypt.Hasher. 10 | type Opt func(h *Hasher) (err error) 11 | 12 | // WithIterations sets the iterations parameter of the resulting sha1crypt.Digest. 13 | // Minimum is 0, Maximum is 4294967295. Default is 480000. 14 | func WithIterations(iterations uint32) Opt { 15 | return func(h *Hasher) (err error) { 16 | if iterations < IterationsMin || iterations > IterationsMax { 17 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "iterations", IterationsMin, "", IterationsMax, iterations)) 18 | } 19 | 20 | h.i = true 21 | h.iterations = iterations 22 | 23 | return nil 24 | } 25 | } 26 | 27 | // WithRounds is an alias for sha1crypt.WithIterations. 28 | func WithRounds(rounds uint32) Opt { 29 | return WithIterations(rounds) 30 | } 31 | 32 | // WithSaltLength adjusts the salt size (in bytes) of the resulting sha1crypt.Digest. 33 | // Minimum is 1, Maximum is 64. Default is 8. 34 | func WithSaltLength(bytes int) Opt { 35 | return func(h *Hasher) (err error) { 36 | if bytes < SaltLengthMin || bytes > SaltLengthMax { 37 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "salt length", SaltLengthMin, "", SaltLengthMax, bytes)) 38 | } 39 | 40 | h.bytesSalt = bytes 41 | 42 | return nil 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /algorithm/shacrypt/const.go: -------------------------------------------------------------------------------- 1 | package shacrypt 2 | 3 | const ( 4 | // EncodingFmt is the encoding format for this algorithm. 5 | EncodingFmt = "$%s$rounds=%d$%s$%s" 6 | 7 | // EncodingFmtRoundsOmitted is the encoding format for this algorithm when the rounds can be omitted. 8 | EncodingFmtRoundsOmitted = "$%s$%s$%s" 9 | 10 | // AlgName is the name for this algorithm. 11 | AlgName = "shacrypt" 12 | 13 | // AlgIdentifierSHA256 is the identifier used in encoded SHA256 variants of this algorithm. 14 | AlgIdentifierSHA256 = "5" 15 | 16 | // AlgIdentifierSHA512 is the identifier used in encoded SHA512 variants of this algorithm. 17 | AlgIdentifierSHA512 = "6" 18 | 19 | // IterationsMin is the minimum number of iterations accepted. 20 | IterationsMin = 1000 21 | 22 | // IterationsMax is the maximum number of iterations accepted. 23 | IterationsMax = 999999999 24 | 25 | // IterationsDefaultSHA256 is the default number of iterations for SHA256. 26 | IterationsDefaultSHA256 = 1000000 27 | 28 | // IterationsDefaultSHA512 is the default number of iterations for SHA512. 29 | IterationsDefaultSHA512 = 500000 30 | 31 | // IterationsDefaultOmitted is the default number of iterations when the rounds are omitted. 32 | IterationsDefaultOmitted = 5000 33 | 34 | // SaltLengthMin is the minimum salt length. 35 | SaltLengthMin = 1 36 | 37 | // SaltLengthMax is the maximum salt length. 38 | SaltLengthMax = 16 39 | 40 | // SaltCharSet are the valid characters for the salt. 41 | SaltCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./" 42 | ) 43 | 44 | const ( 45 | variantDefault = VariantSHA512 46 | ) 47 | -------------------------------------------------------------------------------- /algorithm/shacrypt/decoder.go: -------------------------------------------------------------------------------- 1 | package shacrypt 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/go-crypt/crypt/algorithm" 8 | "github.com/go-crypt/crypt/internal/encoding" 9 | ) 10 | 11 | // RegisterDecoder the decoder with the algorithm.DecoderRegister. 12 | func RegisterDecoder(r algorithm.DecoderRegister) (err error) { 13 | if err = RegisterDecoderSHA256(r); err != nil { 14 | return err 15 | } 16 | 17 | if err = RegisterDecoderSHA512(r); err != nil { 18 | return err 19 | } 20 | 21 | return nil 22 | } 23 | 24 | // RegisterDecoderSHA256 registers specifically the sha256 decoder variant with the algorithm.DecoderRegister. 25 | func RegisterDecoderSHA256(r algorithm.DecoderRegister) (err error) { 26 | if err = r.RegisterDecodeFunc(VariantSHA256.Prefix(), DecodeVariant(VariantSHA256)); err != nil { 27 | return err 28 | } 29 | 30 | return nil 31 | } 32 | 33 | // RegisterDecoderSHA512 registers specifically the sha512 decoder variant with the algorithm.DecoderRegister. 34 | func RegisterDecoderSHA512(r algorithm.DecoderRegister) (err error) { 35 | if err = r.RegisterDecodeFunc(VariantSHA512.Prefix(), DecodeVariant(VariantSHA512)); err != nil { 36 | return err 37 | } 38 | 39 | return nil 40 | } 41 | 42 | // Decode the encoded digest into a algorithm.Digest. 43 | func Decode(encodedDigest string) (digest algorithm.Digest, err error) { 44 | return DecodeVariant(VariantNone)(encodedDigest) 45 | } 46 | 47 | // DecodeVariant the encoded digest into a algorithm.Digest provided it matches the provided Variant. If VariantNone is 48 | // used all variants can be decoded. 49 | func DecodeVariant(v Variant) func(encodedDigest string) (digest algorithm.Digest, err error) { 50 | return func(encodedDigest string) (digest algorithm.Digest, err error) { 51 | var ( 52 | parts []string 53 | variant Variant 54 | ) 55 | 56 | if variant, parts, err = decoderParts(encodedDigest); err != nil { 57 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) 58 | } 59 | 60 | if v != VariantNone && v != variant { 61 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, fmt.Errorf("the '%s' variant cannot be decoded only the '%s' variant can be", variant.String(), v.String())) 62 | } 63 | 64 | if digest, err = decode(variant, parts); err != nil { 65 | return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) 66 | } 67 | 68 | return digest, nil 69 | } 70 | } 71 | 72 | func decoderParts(encodedDigest string) (variant Variant, parts []string, err error) { 73 | parts = encoding.Split(encodedDigest, -1) 74 | 75 | if n := len(parts); n != 4 && n != 5 { 76 | return VariantNone, nil, algorithm.ErrEncodedHashInvalidFormat 77 | } 78 | 79 | variant = NewVariant(parts[1]) 80 | 81 | if variant == VariantNone { 82 | return variant, nil, fmt.Errorf("%w: identifier '%s' is not an encoded %s digest", algorithm.ErrEncodedHashInvalidIdentifier, parts[1], AlgName) 83 | } 84 | 85 | return variant, parts[2:], nil 86 | } 87 | 88 | func decode(variant Variant, parts []string) (digest algorithm.Digest, err error) { 89 | decoded := &Digest{ 90 | variant: variant, 91 | } 92 | 93 | var ( 94 | ip, is, ik int 95 | ) 96 | 97 | switch len(parts) { 98 | case 2: 99 | ip, is, ik = -1, 0, 1 100 | case 3: 101 | ip, is, ik = 0, 1, 2 102 | } 103 | 104 | if len(parts[ik]) == 0 { 105 | return nil, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrEncodedHashKeyEncoding) 106 | } 107 | 108 | decoded.iterations = IterationsDefaultOmitted 109 | 110 | var params []encoding.Parameter 111 | 112 | if ip >= 0 { 113 | if params, err = encoding.DecodeParameterStr(parts[ip]); err != nil { 114 | return nil, err 115 | } 116 | } 117 | 118 | for _, param := range params { 119 | switch param.Key { 120 | case "rounds": 121 | var rounds uint64 122 | 123 | if rounds, err = strconv.ParseUint(param.Value, 10, 32); err != nil { 124 | return nil, fmt.Errorf("%w: option '%s' has invalid value '%s': %v", algorithm.ErrEncodedHashInvalidOptionValue, param.Key, param.Value, err) 125 | } 126 | 127 | decoded.iterations = int(rounds) 128 | default: 129 | return nil, fmt.Errorf("%w: option '%s' with value '%s' is unknown", algorithm.ErrEncodedHashInvalidOptionKey, param.Key, param.Value) 130 | } 131 | } 132 | 133 | decoded.salt, decoded.key = []byte(parts[is]), []byte(parts[ik]) 134 | 135 | return decoded, nil 136 | } 137 | -------------------------------------------------------------------------------- /algorithm/shacrypt/digest.go: -------------------------------------------------------------------------------- 1 | package shacrypt 2 | 3 | import ( 4 | "crypto/subtle" 5 | "fmt" 6 | "strings" 7 | 8 | xcrypt "github.com/go-crypt/x/crypt" 9 | 10 | "github.com/go-crypt/crypt/algorithm" 11 | ) 12 | 13 | // Digest is a digest which handles SHA-crypt hashes like SHA256 or SHA512. 14 | type Digest struct { 15 | variant Variant 16 | 17 | iterations int 18 | salt, key []byte 19 | } 20 | 21 | // Match returns true if the string password matches the current shacrypt.Digest. 22 | func (d *Digest) Match(password string) (match bool) { 23 | return d.MatchBytes([]byte(password)) 24 | } 25 | 26 | // MatchBytes returns true if the []byte passwordBytes matches the current shacrypt.Digest. 27 | func (d *Digest) MatchBytes(passwordBytes []byte) (match bool) { 28 | match, _ = d.MatchBytesAdvanced(passwordBytes) 29 | 30 | return match 31 | } 32 | 33 | // MatchAdvanced is the same as Match except if there is an error it returns that as well. 34 | func (d *Digest) MatchAdvanced(password string) (match bool, err error) { 35 | if match, err = d.MatchBytesAdvanced([]byte(password)); err != nil { 36 | return match, fmt.Errorf(algorithm.ErrFmtDigestMatch, AlgName, err) 37 | } 38 | 39 | return match, nil 40 | } 41 | 42 | // MatchBytesAdvanced is the same as MatchBytes except if there is an error it returns that as well. 43 | func (d *Digest) MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) { 44 | if len(d.key) == 0 { 45 | return false, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrPasswordInvalid) 46 | } 47 | 48 | return subtle.ConstantTimeCompare(d.key, xcrypt.KeySHACrypt(d.variant.HashFunc(), passwordBytes, d.salt, d.iterations)) == 1, nil 49 | } 50 | 51 | // Encode this Digest as a string for storage. 52 | func (d *Digest) Encode() (hash string) { 53 | switch d.iterations { 54 | case IterationsDefaultOmitted: 55 | return strings.ReplaceAll(fmt.Sprintf(EncodingFmtRoundsOmitted, 56 | d.variant.Prefix(), 57 | d.salt, d.key, 58 | ), "\n", "") 59 | default: 60 | return strings.ReplaceAll(fmt.Sprintf(EncodingFmt, 61 | d.variant.Prefix(), d.iterations, 62 | d.salt, d.key, 63 | ), "\n", "") 64 | } 65 | } 66 | 67 | // String returns the storable format of the shacrypt.Digest hash utilizing fmt.Sprintf and shacrypt.EncodingFmt. 68 | func (d *Digest) String() string { 69 | return d.Encode() 70 | } 71 | 72 | func (d *Digest) defaults() { 73 | switch d.variant { 74 | case VariantSHA256, VariantSHA512: 75 | break 76 | default: 77 | d.variant = variantDefault 78 | } 79 | 80 | if d.iterations == 0 { 81 | d.iterations = d.variant.DefaultIterations() 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /algorithm/shacrypt/doc.go: -------------------------------------------------------------------------------- 1 | // Package shacrypt provides helpful abstractions for an implementation of SHA-crypt and implements 2 | // github.com/go-crypt/crypt interfaces. 3 | // 4 | // See https://www.akkadia.org/drepper/SHA-crypt.html for specification details. 5 | // 6 | // This implementation is loaded by crypt.NewDefaultDecoder and crypt.NewDecoderAll. 7 | package shacrypt 8 | -------------------------------------------------------------------------------- /algorithm/shacrypt/hasher.go: -------------------------------------------------------------------------------- 1 | package shacrypt 2 | 3 | import ( 4 | "fmt" 5 | 6 | xcrypt "github.com/go-crypt/x/crypt" 7 | 8 | "github.com/go-crypt/crypt/algorithm" 9 | "github.com/go-crypt/crypt/internal/random" 10 | ) 11 | 12 | // New returns a *Hasher without any settings configured. This d to a SHA512 hash.Hash 13 | // with 1000000 iterations. These settings can be overridden with the methods with the With prefix. 14 | func New(opts ...Opt) (hasher *Hasher, err error) { 15 | hasher = &Hasher{} 16 | 17 | if err = hasher.WithOptions(opts...); err != nil { 18 | return nil, err 19 | } 20 | 21 | if err = hasher.Validate(); err != nil { 22 | return nil, err 23 | } 24 | 25 | return hasher, nil 26 | } 27 | 28 | // Hasher is a algorithm.Hash for SHA-crypt which can be initialized via shacrypt.New using a functional options pattern. 29 | type Hasher struct { 30 | variant Variant 31 | 32 | iterations, bytesSalt int 33 | 34 | d bool 35 | } 36 | 37 | // NewSHA256 returns a *Hasher with the SHA256 hash.Hash which d to 1000000 iterations. These 38 | // settings can be overridden with the methods with the With prefix. 39 | func NewSHA256() (hasher *Hasher, err error) { 40 | return New( 41 | WithVariant(VariantSHA256), 42 | WithIterations(VariantSHA256.DefaultIterations()), 43 | ) 44 | } 45 | 46 | // NewSHA512 returns a *Hasher with the SHA512 hash.Hash which d to 1000000 iterations. These 47 | // settings can be overridden with the methods with the With prefix. 48 | func NewSHA512() (hasher *Hasher, err error) { 49 | return New( 50 | WithVariant(VariantSHA512), 51 | WithIterations(VariantSHA512.DefaultIterations()), 52 | ) 53 | } 54 | 55 | // WithOptions defines the options for this scrypt.Hasher. 56 | func (h *Hasher) WithOptions(opts ...Opt) (err error) { 57 | for _, opt := range opts { 58 | if err = opt(h); err != nil { 59 | return err 60 | } 61 | } 62 | 63 | return nil 64 | } 65 | 66 | // Hash performs the hashing operation and returns either a shacrypt.Digest as a algorithm.Digest or an error. 67 | func (h *Hasher) Hash(password string) (digest algorithm.Digest, err error) { 68 | h.defaults() 69 | 70 | if digest, err = h.hash(password); err != nil { 71 | return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) 72 | } 73 | 74 | return digest, nil 75 | } 76 | 77 | func (h *Hasher) hash(password string) (digest algorithm.Digest, err error) { 78 | var salt []byte 79 | 80 | if salt, err = random.CharSetBytes(h.bytesSalt, SaltCharSet); err != nil { 81 | return nil, fmt.Errorf("%w: %v", algorithm.ErrSaltReadRandomBytes, err) 82 | } 83 | 84 | return h.hashWithSalt(password, salt) 85 | } 86 | 87 | // HashWithSalt overloads the Hash method allowing the user to provide a salt. It's recommended instead to configure the 88 | // salt size and let this be a random value generated using crypto/rand. 89 | func (h *Hasher) HashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { 90 | h.defaults() 91 | 92 | if digest, err = h.hashWithSalt(password, salt); err != nil { 93 | return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) 94 | } 95 | 96 | return digest, nil 97 | } 98 | 99 | func (h *Hasher) hashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { 100 | if s := len(salt); s > SaltLengthMax || s < SaltLengthMin { 101 | return nil, fmt.Errorf("%w: salt bytes must have a length of between %d and %d but has a length of %d", algorithm.ErrSaltInvalid, SaltLengthMin, SaltLengthMax, len(salt)) 102 | } 103 | 104 | d := &Digest{ 105 | variant: h.variant, 106 | iterations: h.iterations, 107 | salt: salt, 108 | } 109 | 110 | d.defaults() 111 | 112 | d.key = xcrypt.KeySHACrypt(d.variant.HashFunc(), []byte(password), d.salt, d.iterations) 113 | 114 | return d, nil 115 | } 116 | 117 | // MustHash overloads the Hash method and panics if the error is not nil. It's recommended if you use this option to 118 | // utilize the Validate method first or handle the panic appropriately. 119 | func (h *Hasher) MustHash(password string) (digest algorithm.Digest) { 120 | var err error 121 | 122 | if digest, err = h.Hash(password); err != nil { 123 | panic(err) 124 | } 125 | 126 | return digest 127 | } 128 | 129 | // Validate checks the settings/parameters for this shacrypt.Hasher and returns an error. 130 | func (h *Hasher) Validate() (err error) { 131 | h.defaults() 132 | 133 | if err = h.validate(); err != nil { 134 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, err) 135 | } 136 | 137 | return nil 138 | } 139 | 140 | func (h *Hasher) validate() (err error) { 141 | h.defaults() 142 | 143 | return nil 144 | } 145 | 146 | func (h *Hasher) defaults() { 147 | if h.d { 148 | return 149 | } 150 | 151 | h.d = true 152 | 153 | if h.bytesSalt < SaltLengthMin { 154 | h.bytesSalt = algorithm.SaltLengthDefault 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /algorithm/shacrypt/opts.go: -------------------------------------------------------------------------------- 1 | package shacrypt 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-crypt/crypt/algorithm" 7 | ) 8 | 9 | // Opt describes the functional option pattern for the shacrypt.Hasher. 10 | type Opt func(h *Hasher) (err error) 11 | 12 | // WithVariant configures the shacrypt.Variant of the resulting shacrypt.Digest. 13 | // Default is shacrypt.VariantSHA512. 14 | func WithVariant(variant Variant) Opt { 15 | return func(h *Hasher) (err error) { 16 | switch variant { 17 | case VariantNone: 18 | return nil 19 | case VariantSHA256, VariantSHA512: 20 | h.variant = variant 21 | 22 | return nil 23 | default: 24 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant '%d' is invalid", algorithm.ErrParameterInvalid, variant)) 25 | } 26 | } 27 | } 28 | 29 | // WithVariantName uses the variant name or identifier to configure the shacrypt.Variant of the resulting shacrypt.Digest. 30 | // Default is shacrypt.VariantSHA512. 31 | func WithVariantName(identifier string) Opt { 32 | return func(h *Hasher) (err error) { 33 | if identifier == "" { 34 | return nil 35 | } 36 | 37 | variant := NewVariant(identifier) 38 | 39 | if variant == VariantNone { 40 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant identifier '%s' is invalid", algorithm.ErrParameterInvalid, identifier)) 41 | } 42 | 43 | h.variant = variant 44 | 45 | return nil 46 | } 47 | } 48 | 49 | // WithSHA256 adjusts this Hasher to utilize the SHA256 hash.Hash. 50 | func WithSHA256() Opt { 51 | return func(h *Hasher) (err error) { 52 | h.variant = VariantSHA256 53 | 54 | return nil 55 | } 56 | } 57 | 58 | // WithSHA512 adjusts this Hasher to utilize the SHA512 hash.Hash. 59 | func WithSHA512() Opt { 60 | return func(h *Hasher) (err error) { 61 | h.variant = VariantSHA512 62 | 63 | return nil 64 | } 65 | } 66 | 67 | // WithIterations sets the iterations parameter of the resulting shacrypt.Digest. 68 | // Minimum 1000, Maximum 999999999. Default is 1000000. 69 | func WithIterations(iterations int) Opt { 70 | return func(h *Hasher) (err error) { 71 | if iterations < IterationsMin || iterations > IterationsMax { 72 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "iterations", IterationsMin, "", IterationsMax, iterations)) 73 | } 74 | 75 | h.iterations = iterations 76 | 77 | return nil 78 | } 79 | } 80 | 81 | // WithRounds is an alias for shacrypt.WithIterations. 82 | func WithRounds(rounds int) Opt { 83 | return WithIterations(rounds) 84 | } 85 | 86 | // WithSaltLength adjusts the salt size (in bytes) of the resulting shacrypt.Digest. 87 | // Minimum 1, Maximum 16. Default is 16. 88 | func WithSaltLength(bytes int) Opt { 89 | return func(h *Hasher) (err error) { 90 | if bytes < SaltLengthMin || bytes > SaltLengthMax { 91 | return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "salt length", SaltLengthMin, "", SaltLengthMax, bytes)) 92 | } 93 | 94 | h.bytesSalt = bytes 95 | 96 | return nil 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /algorithm/shacrypt/variant.go: -------------------------------------------------------------------------------- 1 | package shacrypt 2 | 3 | import ( 4 | "crypto/sha256" 5 | "crypto/sha512" 6 | 7 | "github.com/go-crypt/crypt/algorithm" 8 | ) 9 | 10 | // NewVariant converts an identifier string to a shacrypt.Variant. 11 | func NewVariant(identifier string) Variant { 12 | switch identifier { 13 | case AlgIdentifierSHA256, algorithm.DigestSHA256: 14 | return VariantSHA256 15 | case AlgIdentifierSHA512, algorithm.DigestSHA512: 16 | return VariantSHA512 17 | default: 18 | return VariantSHA512 19 | } 20 | } 21 | 22 | // Variant is a variant of the shacrypt.Digest. 23 | type Variant int 24 | 25 | const ( 26 | // VariantNone is a variant of the shacrypt.Digest which is unknown. 27 | VariantNone Variant = iota 28 | 29 | // VariantSHA256 is a variant of the shacrypt.Digest which uses SHA-256. 30 | VariantSHA256 31 | 32 | // VariantSHA512 is a variant of the shacrypt.Digest which uses SHA-512. 33 | VariantSHA512 34 | ) 35 | 36 | // String implements the fmt.Stringer returning a string representation of the shacrypt.Variant. 37 | func (v Variant) String() (identifier string) { 38 | switch v { 39 | case VariantSHA256: 40 | return algorithm.DigestSHA256 41 | case VariantSHA512: 42 | return algorithm.DigestSHA512 43 | default: 44 | return 45 | } 46 | } 47 | 48 | // Prefix returns the shacrypt.Variant prefix identifier. 49 | func (v Variant) Prefix() (prefix string) { 50 | switch v { 51 | case VariantSHA256: 52 | return AlgIdentifierSHA256 53 | case VariantSHA512: 54 | return AlgIdentifierSHA512 55 | default: 56 | return AlgIdentifierSHA512 57 | } 58 | } 59 | 60 | // Name returns the Variant name. 61 | func (v Variant) Name() (s string) { 62 | switch v { 63 | case VariantSHA256: 64 | return algorithm.DigestSHA256 65 | case VariantSHA512: 66 | return algorithm.DigestSHA512 67 | default: 68 | return algorithm.DigestSHA512 69 | } 70 | } 71 | 72 | // HashFunc returns the internal HMAC HashFunc. 73 | func (v Variant) HashFunc() algorithm.HashFunc { 74 | switch v { 75 | case VariantSHA256: 76 | return sha256.New 77 | case VariantSHA512: 78 | return sha512.New 79 | default: 80 | return sha512.New 81 | } 82 | } 83 | 84 | // DefaultIterations returns the default iterations for the particular variant. 85 | func (v Variant) DefaultIterations() int { 86 | switch v { 87 | case VariantSHA512: 88 | return IterationsDefaultSHA512 89 | default: 90 | return IterationsDefaultSHA256 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /algorithm/types.go: -------------------------------------------------------------------------------- 1 | package algorithm 2 | 3 | import ( 4 | "fmt" 5 | "hash" 6 | ) 7 | 8 | // Hash is an interface which implements password hashing. 9 | type Hash interface { 10 | // Validate checks the hasher configuration to ensure it's valid. This should be used when the Hash is going to be 11 | // reused and you should use it in conjunction with MustHash. 12 | Validate() (err error) 13 | 14 | // Hash performs the hashing operation on a password and resets any relevant parameters such as a manually set salt. 15 | // It then returns a Digest and error. 16 | Hash(password string) (hashed Digest, err error) 17 | 18 | // HashWithSalt is an overload of Digest that also accepts a salt. 19 | HashWithSalt(password string, salt []byte) (hashed Digest, err error) 20 | 21 | // MustHash overloads the Hash method and panics if the error is not nil. It's recommended if you use this method to 22 | // utilize the Validate method first or handle the panic appropriately. 23 | MustHash(password string) (hashed Digest) 24 | } 25 | 26 | // Matcher is an interface used to match passwords. 27 | type Matcher interface { 28 | Match(password string) (match bool) 29 | MatchBytes(passwordBytes []byte) (match bool) 30 | MatchAdvanced(password string) (match bool, err error) 31 | MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) 32 | } 33 | 34 | // Digest represents a hashed password. It's implemented by all hashed password results so that when we pass a 35 | // stored hash into its relevant type we can verify the password against the hash. 36 | type Digest interface { 37 | fmt.Stringer 38 | 39 | Matcher 40 | 41 | Encode() (hash string) 42 | } 43 | 44 | // DecodeFunc describes a function to decode an encoded digest into a algorithm.Digest. 45 | type DecodeFunc func(encodedDigest string) (digest Digest, err error) 46 | 47 | // DecoderRegister describes an implementation that allows registering DecodeFunc's. 48 | type DecoderRegister interface { 49 | RegisterDecodeFunc(prefix string, decoder DecodeFunc) (err error) 50 | RegisterDecodePrefix(prefix, identifier string) (err error) 51 | 52 | Decoder 53 | } 54 | 55 | // Decoder is a representation of a implementation that performs generic decoding. Currently this is just intended for 56 | // use by implementers. 57 | type Decoder interface { 58 | Decode(encodedDigest string) (digest Digest, err error) 59 | } 60 | 61 | // HashFunc is a function which returns a hash.Hash. 62 | type HashFunc func() hash.Hash 63 | -------------------------------------------------------------------------------- /const.go: -------------------------------------------------------------------------------- 1 | package crypt 2 | 3 | import ( 4 | "github.com/go-crypt/crypt/internal/encoding" 5 | ) 6 | 7 | const ( 8 | // Delimiter for all storage formats. 9 | Delimiter = encoding.DelimiterStr 10 | ) 11 | 12 | const ( 13 | // StorageFormatPrefixLDAPCrypt is a prefix used by OpenLDAP for crypt format encoded digests. 14 | StorageFormatPrefixLDAPCrypt = "{CRYPT}" 15 | 16 | // StorageFormatPrefixLDAPArgon2 is a prefix used by OpenLDAP for argon2 format encoded digests. 17 | StorageFormatPrefixLDAPArgon2 = "{ARGON2}" 18 | ) 19 | -------------------------------------------------------------------------------- /const_test.go: -------------------------------------------------------------------------------- 1 | package crypt 2 | 3 | const ( 4 | password = "password" 5 | wrongPassword = "wrong_password" 6 | ) 7 | -------------------------------------------------------------------------------- /decode.go: -------------------------------------------------------------------------------- 1 | package crypt 2 | 3 | import ( 4 | "github.com/go-crypt/crypt/algorithm" 5 | ) 6 | 7 | // The global Decoder. This is utilized by the Decode function. 8 | var gdecoder *Decoder 9 | 10 | // Decode is a convenience function which wraps the Decoder functionality. It's recommended to create your own decoder 11 | // instead via NewDecoder or NewDefaultDecoder. 12 | // 13 | // CRITICAL STABILITY NOTE: the decoders loaded via this function are not guaranteed to remain the same. It is strongly 14 | // recommended that users implementing this library use the NewDecoder function and explicitly register each decoder 15 | // which they wish to support. 16 | func Decode(encodedDigest string) (digest algorithm.Digest, err error) { 17 | if digest, err = decode(encodedDigest); err != nil { 18 | return nil, err 19 | } 20 | 21 | return digest, nil 22 | } 23 | 24 | func decode(encodedDigest string) (digest algorithm.Digest, err error) { 25 | if gdecoder == nil { 26 | if gdecoder, err = NewDefaultDecoder(); err != nil { 27 | return nil, err 28 | } 29 | } 30 | 31 | return gdecoder.Decode(encodedDigest) 32 | } 33 | -------------------------------------------------------------------------------- /decoder.go: -------------------------------------------------------------------------------- 1 | package crypt 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/go-crypt/crypt/algorithm" 9 | "github.com/go-crypt/crypt/algorithm/argon2" 10 | "github.com/go-crypt/crypt/algorithm/bcrypt" 11 | "github.com/go-crypt/crypt/algorithm/md5crypt" 12 | "github.com/go-crypt/crypt/algorithm/pbkdf2" 13 | "github.com/go-crypt/crypt/algorithm/plaintext" 14 | "github.com/go-crypt/crypt/algorithm/scrypt" 15 | "github.com/go-crypt/crypt/algorithm/sha1crypt" 16 | "github.com/go-crypt/crypt/algorithm/shacrypt" 17 | "github.com/go-crypt/crypt/internal/encoding" 18 | ) 19 | 20 | // NewDecoder returns a new empty *Decoder. 21 | // 22 | // See Also: NewDefaultDecoder and NewDecoderAll. 23 | func NewDecoder() *Decoder { 24 | return &Decoder{ 25 | decoders: map[string]algorithm.DecodeFunc{}, 26 | prefixes: map[string]string{}, 27 | } 28 | } 29 | 30 | // NewDefaultDecoder returns the default decoder recommended for new implementations. 31 | // 32 | // Loaded Decoders: argon2, bcrypt, pbkdf2, scrypt, shacrypt. 33 | // 34 | // CRITICAL STABILITY NOTE: the decoders loaded via this function are not guaranteed to remain the same. It is strongly 35 | // recommended that users implementing this library use this or NewDecodersAll only as an example for building their own 36 | // decoder via NewDecoder instead which returns an empty decoder. It is much safer for security and stability to be 37 | // explicit in harmony with your specific use case. It is the responsibility of the implementer to determine which 38 | // password algorithms are sufficiently safe for their particular use case. 39 | func NewDefaultDecoder() (d *Decoder, err error) { 40 | d = &Decoder{ 41 | decoders: map[string]algorithm.DecodeFunc{}, 42 | prefixes: map[string]string{}, 43 | } 44 | 45 | if err = decoderProfileDefault(d); err != nil { 46 | return nil, err 47 | } 48 | 49 | return d, nil 50 | } 51 | 52 | // NewDecoderAll is the same as NewDefaultDecoder but it also adds legacy and/or insecure decoders. 53 | // 54 | // Loaded Decoders (in addition to NewDefaultDecoder): plaintext, md5crypt, sha1crypt. 55 | // 56 | // CRITICAL STABILITY NOTE: the decoders loaded via this function are not guaranteed to remain the same. It is strongly 57 | // recommended that users implementing this library use this or NewDecodersAll only as an example for building their own 58 | // decoder via NewDecoder instead which returns an empty decoder. It is much safer for security and stability to be 59 | // explicit in harmony with your specific use case. It is the responsibility of the implementer to determine which 60 | // password algorithms are sufficiently safe for their particular use case. 61 | func NewDecoderAll() (d *Decoder, err error) { 62 | d = &Decoder{ 63 | decoders: map[string]algorithm.DecodeFunc{}, 64 | prefixes: map[string]string{}, 65 | } 66 | 67 | if err = decoderProfileDefault(d); err != nil { 68 | return nil, err 69 | } 70 | 71 | if err = plaintext.RegisterDecoder(d); err != nil { 72 | return nil, fmt.Errorf("could not register the plaintext decoder: %w", err) 73 | } 74 | 75 | if err = md5crypt.RegisterDecoder(d); err != nil { 76 | return nil, fmt.Errorf("could not register the md5crypt decoder: %w", err) 77 | } 78 | 79 | if err = sha1crypt.RegisterDecoder(d); err != nil { 80 | return nil, fmt.Errorf("could not register the sha1crypt decoder: %w", err) 81 | } 82 | 83 | return d, nil 84 | } 85 | 86 | // Decoder is a struct which allows registering algorithm.DecodeFunc's and utilizing the programmatically to decode an 87 | // encoded digest with them. 88 | type Decoder struct { 89 | decoders map[string]algorithm.DecodeFunc 90 | prefixes map[string]string 91 | } 92 | 93 | // RegisterDecodeFunc registers a new algorithm.DecodeFunc with this Decoder against a specific identifier. 94 | func (d *Decoder) RegisterDecodeFunc(identifier string, decoder algorithm.DecodeFunc) (err error) { 95 | if d.decoders == nil { 96 | d.decoders = map[string]algorithm.DecodeFunc{} 97 | } 98 | 99 | if _, ok := d.decoders[identifier]; ok { 100 | return fmt.Errorf("decoder already registered for identifier '%s'", identifier) 101 | } 102 | 103 | d.decoders[identifier] = decoder 104 | 105 | return nil 106 | } 107 | 108 | // RegisterDecodePrefix registers a prefix which is matched by strings.HasPrefix. 109 | func (d *Decoder) RegisterDecodePrefix(prefix, identifier string) (err error) { 110 | if d.decoders == nil { 111 | return fmt.Errorf("no decoders are registered") 112 | } 113 | 114 | if d.prefixes == nil { 115 | d.prefixes = map[string]string{} 116 | } 117 | 118 | if _, ok := d.decoders[identifier]; !ok { 119 | return fmt.Errorf("decoder isn't registered for dentifier '%s'", identifier) 120 | } 121 | 122 | d.prefixes[prefix] = identifier 123 | 124 | return nil 125 | } 126 | 127 | // Decode an encoded digest into a algorithm.Digest. 128 | func (d *Decoder) Decode(encodedDigest string) (digest algorithm.Digest, err error) { 129 | if digest, err = d.decode(encodedDigest); err != nil { 130 | return nil, err 131 | } 132 | 133 | return digest, nil 134 | } 135 | 136 | func (d *Decoder) decode(encodedDigest string) (digest algorithm.Digest, err error) { 137 | for prefix, key := range d.prefixes { 138 | if strings.HasPrefix(encodedDigest, prefix) { 139 | return d.decoders[key](encodedDigest) 140 | } 141 | } 142 | 143 | encodedDigest = Normalize(encodedDigest) 144 | 145 | if len(encodedDigest) == 0 || rune(encodedDigest[0]) != encoding.Delimiter { 146 | return nil, fmt.Errorf("%w: the digest doesn't begin with the delimiter %s and is not one of the other understood formats", algorithm.ErrEncodedHashInvalidFormat, strconv.QuoteRune(encoding.Delimiter)) 147 | } 148 | 149 | parts := encoding.Split(encodedDigest, 3) 150 | 151 | if len(parts) != 3 { 152 | return nil, fmt.Errorf("%w: the digest doesn't have the minimum number of parts for it to be considered an encoded digest", algorithm.ErrEncodedHashInvalidFormat) 153 | } 154 | 155 | if decodeFunc, ok := d.decoders[parts[1]]; ok { 156 | return decodeFunc(encodedDigest) 157 | } 158 | 159 | switch d { 160 | case gdecoder: 161 | return nil, fmt.Errorf("%w: the identifier '%s' is unknown to the global decoder", algorithm.ErrEncodedHashInvalidIdentifier, parts[1]) 162 | default: 163 | return nil, fmt.Errorf("%w: the identifier '%s' is unknown to the decoder", algorithm.ErrEncodedHashInvalidIdentifier, parts[1]) 164 | } 165 | } 166 | 167 | func decoderProfileDefault(decoder *Decoder) (err error) { 168 | if err = argon2.RegisterDecoder(decoder); err != nil { 169 | return fmt.Errorf("could not register the argon2 decoder: %w", err) 170 | } 171 | 172 | if err = bcrypt.RegisterDecoder(decoder); err != nil { 173 | return fmt.Errorf("could not register the bcrypt decoder: %w", err) 174 | } 175 | 176 | if err = pbkdf2.RegisterDecoder(decoder); err != nil { 177 | return fmt.Errorf("could not register the pbkdf2 decoder: %w", err) 178 | } 179 | 180 | if err = scrypt.RegisterDecoder(decoder); err != nil { 181 | return fmt.Errorf("could not register the scrypt decoder: %w", err) 182 | } 183 | 184 | if err = shacrypt.RegisterDecoder(decoder); err != nil { 185 | return fmt.Errorf("could not register the shacrypt decoder: %w", err) 186 | } 187 | 188 | return nil 189 | } 190 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package crypt provides helpful abstractions for github.com/go-crypt/x. These abstractions allow generating password 2 | // hashes, encoding them in a common storage format, and comparing them to ensure they are valid. 3 | // 4 | // It's recommended that you either use crypt.NewDefaultDecoder for decoding existing encoded digests into the 5 | // algorithm.Digest. The Match function on the algorithm.Digest as well as the other methods described by 6 | // algorithm.Matcher can be utilized to validate passwords. 7 | // 8 | // The algorithm.Digest implementations include an Encode method which encodes the algorithm.Digest in the PHC String Format. 9 | // 10 | // To create new algorithm.Digest results you can utilize the algorithm.Hash implementations which exist for each algorithm. 11 | // The implementations utilize the functional options pattern where all options methods have the pattern With* or 12 | // Without*. 13 | package crypt 14 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-crypt/crypt 2 | 3 | go 1.24 4 | 5 | toolchain go1.24.4 6 | 7 | require ( 8 | github.com/go-crypt/x v0.4.2 9 | github.com/stretchr/testify v1.10.0 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | golang.org/x/sys v0.33.0 // indirect 16 | gopkg.in/yaml.v3 v3.0.1 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/go-crypt/x v0.4.2 h1:4RyHHXhjGKkBTXhKEoXzQ6WWMdownFWBvt6+sxCA4TM= 4 | github.com/go-crypt/x v0.4.2/go.mod h1:9nal1V6xWFWUnQf9Tn70WCBRD5I9JcHw9jaRSz5tJ8w= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 8 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 9 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 10 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 12 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 13 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 14 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 15 | -------------------------------------------------------------------------------- /helper.go: -------------------------------------------------------------------------------- 1 | package crypt 2 | 3 | import ( 4 | "github.com/go-crypt/crypt/algorithm" 5 | ) 6 | 7 | // CheckPassword takes the string password and an encoded digest. It decodes the Digest, then performs the 8 | // MatchAdvanced() function on the Digest. If any process returns an error it returns false with the error, otherwise 9 | // it returns the result of MatchAdvanced(). This is just a helper function and implementers can manually invoke this 10 | // process themselves in situations where they may want to store the Digest to perform matches at a later date to avoid 11 | // decoding multiple times for example. 12 | // 13 | // CRITICAL STABILITY NOTE: the decoders loaded via this function are not guaranteed to remain the same. It is strongly 14 | // recommended that users implementing this library use the NewDecoder function and explicitly register each decoder 15 | // which they wish to support. 16 | func CheckPassword(password, encodedDigest string) (valid bool, err error) { 17 | var digest algorithm.Digest 18 | 19 | if digest, err = Decode(encodedDigest); err != nil { 20 | return false, err 21 | } 22 | 23 | return digest.MatchAdvanced(password) 24 | } 25 | 26 | // CheckPasswordWithPlainText is the same as CheckPassword however it also allows the plaintext passwords. 27 | // 28 | // CRITICAL STABILITY NOTE: the decoders loaded via this function are not guaranteed to remain the same. It is strongly 29 | // recommended that users implementing this library use the NewDecoder function and explicitly register each decoder 30 | // which they wish to support. 31 | func CheckPasswordWithPlainText(password, encodedDigest string) (valid bool, err error) { 32 | var ( 33 | digest algorithm.Digest 34 | decoder algorithm.Decoder 35 | ) 36 | 37 | if decoder, err = NewDecoderAll(); err != nil { 38 | return false, err 39 | } 40 | 41 | if digest, err = decoder.Decode(encodedDigest); err != nil { 42 | return false, err 43 | } 44 | 45 | return digest.MatchAdvanced(password) 46 | } 47 | -------------------------------------------------------------------------------- /internal/encoding/base64adapted.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | import ( 4 | "encoding/base64" 5 | ) 6 | 7 | const ( 8 | encodeBase64Adapted = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./" 9 | ) 10 | 11 | var ( 12 | // Base64RawAdaptedEncoding is the adapted encoding for crypt purposes without padding. 13 | Base64RawAdaptedEncoding = base64.NewEncoding(encodeBase64Adapted).WithPadding(base64.NoPadding) 14 | ) 15 | -------------------------------------------------------------------------------- /internal/encoding/const.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | const ( 4 | // Delimiter rune for all encodings. 5 | Delimiter = rune('$') 6 | 7 | // DelimiterStr is the string variation of Delimiter. 8 | DelimiterStr = string(Delimiter) 9 | ) 10 | -------------------------------------------------------------------------------- /internal/encoding/digest.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Split an encoded digest by the encoding.Delimiter. 8 | func Split(encodedDigest string, n int) (parts []string) { 9 | return strings.SplitN(encodedDigest, DelimiterStr, n) 10 | } 11 | -------------------------------------------------------------------------------- /internal/encoding/doc.go: -------------------------------------------------------------------------------- 1 | // Package encoding is an internal encoding helper package. 2 | package encoding 3 | -------------------------------------------------------------------------------- /internal/encoding/parameters.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // Parameter is a key value pair. 10 | type Parameter struct { 11 | Key string 12 | Value string 13 | } 14 | 15 | // Int converts the Value to an int using strconv.Atoi. 16 | func (p Parameter) Int() (int, error) { 17 | return strconv.Atoi(p.Value) 18 | } 19 | 20 | const ( 21 | // ParameterDefaultItemSeparator is the default item separator. 22 | ParameterDefaultItemSeparator = "," 23 | 24 | // ParameterDefaultKeyValueSeparator is the default key value separator. 25 | ParameterDefaultKeyValueSeparator = "=" 26 | ) 27 | 28 | // DecodeParameterStr is an alias for DecodeParameterStrAdvanced using item separator and key value separator 29 | // of ',' and '=' respectively. 30 | func DecodeParameterStr(input string) (opts []Parameter, err error) { 31 | return DecodeParameterStrAdvanced(input, ParameterDefaultItemSeparator, ParameterDefaultKeyValueSeparator) 32 | } 33 | 34 | // DecodeParameterStrAdvanced decodes parameter strings into a []Parameter where sepItem separates each parameter, and sepKV separates the key and value. 35 | func DecodeParameterStrAdvanced(input string, sepItem, sepKV string) (opts []Parameter, err error) { 36 | if input == "" { 37 | return nil, fmt.Errorf("empty strings can't be decoded to parameters") 38 | } 39 | 40 | o := strings.Split(input, sepItem) 41 | 42 | opts = make([]Parameter, len(o)) 43 | 44 | for i, joined := range o { 45 | kv := strings.SplitN(joined, sepKV, 2) 46 | if len(kv) != 2 { 47 | return nil, fmt.Errorf("parameter pair '%s' is not properly encoded: does not contain kv separator '%s'", joined, sepKV) 48 | } 49 | 50 | opts[i] = Parameter{Key: kv[0], Value: kv[1]} 51 | } 52 | 53 | return opts, nil 54 | } 55 | -------------------------------------------------------------------------------- /internal/math/doc.go: -------------------------------------------------------------------------------- 1 | // Package math is an internal mathematics helper package. 2 | package math 3 | -------------------------------------------------------------------------------- /internal/math/rounding.go: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | // RoundDownToNearestMultiple returns the nearest multiple of value. 4 | func RoundDownToNearestMultiple(value, multiple int) int { 5 | return (value / multiple) * multiple 6 | } 7 | 8 | // Uint32RoundDownToNearestMultiple returns the nearest multiple of value (uint32 version). 9 | func Uint32RoundDownToNearestMultiple(value, multiple uint32) uint32 { 10 | return (value / multiple) * multiple 11 | } 12 | -------------------------------------------------------------------------------- /internal/random/bytes.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | import ( 4 | "crypto/rand" 5 | "io" 6 | ) 7 | 8 | // Bytes returns random arbitrary bytes with a length of n. 9 | func Bytes(n int) (bytes []byte, err error) { 10 | bytes = make([]byte, n) 11 | 12 | if _, err = io.ReadFull(rand.Reader, bytes); err != nil { 13 | return nil, err 14 | } 15 | 16 | return bytes, nil 17 | } 18 | 19 | // CharSetBytes returns random bytes with a length of n from the characters in the charset. 20 | func CharSetBytes(n int, charset string) (bytes []byte, err error) { 21 | bytes = make([]byte, n) 22 | 23 | if _, err = rand.Read(bytes); err != nil { 24 | return nil, err 25 | } 26 | 27 | for i, b := range bytes { 28 | bytes[i] = charset[b%byte(len(charset))] 29 | } 30 | 31 | return bytes, nil 32 | } 33 | -------------------------------------------------------------------------------- /internal/random/doc.go: -------------------------------------------------------------------------------- 1 | // Package random is an internal helper package. 2 | package random 3 | -------------------------------------------------------------------------------- /normalize.go: -------------------------------------------------------------------------------- 1 | package crypt 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strings" 7 | ) 8 | 9 | var ( 10 | reAlgorithmPrefixPBKDF2 = regexp.MustCompile(`^\{(?PPBKDF2(-SHA\d+)?)}(?P\d+\$.*)$`) 11 | ) 12 | 13 | // Normalize performs normalization on an encoded digest. This removes prefixes which are not necessary and performs 14 | // minimal modification to the encoded digest to make it possible for decoding. 15 | func Normalize(encodedDigest string) string { 16 | if strings.HasPrefix(encodedDigest, StorageFormatPrefixLDAPCrypt) { 17 | encodedDigest = encodedDigest[7:] 18 | } 19 | 20 | if strings.HasPrefix(encodedDigest, StorageFormatPrefixLDAPArgon2) { 21 | encodedDigest = encodedDigest[8:] 22 | } 23 | 24 | matchesPBKDF2 := reAlgorithmPrefixPBKDF2.FindStringSubmatch(encodedDigest) 25 | 26 | if len(matchesPBKDF2) != 0 { 27 | var identifier, remainder string 28 | 29 | for g, group := range reAlgorithmPrefixPBKDF2.SubexpNames() { 30 | switch group { 31 | case "identifier": 32 | identifier = matchesPBKDF2[g] 33 | case "remainder": 34 | identifier = matchesPBKDF2[g] 35 | } 36 | } 37 | 38 | encodedDigest = fmt.Sprintf("$%s$%s", strings.ToLower(identifier), remainder) 39 | } 40 | 41 | return encodedDigest 42 | } 43 | -------------------------------------------------------------------------------- /t_argon2_test.go: -------------------------------------------------------------------------------- 1 | package crypt 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestArgon2Outputs(t *testing.T) { 11 | testcCases := []struct { 12 | name string 13 | have string 14 | }{ 15 | { 16 | "ShouldValidatePasswordArgon2id", 17 | "$argon2id$v=19$m=65536,t=3,p=4$QmkpoTw3W72fzd7RrWofuw$r0xig+VVj7ynnE2S1jrE5us7dPKv2S2ff6Z6ts4mVuU", 18 | }, 19 | { 20 | "ShouldValidatePasswordArgon2i", 21 | "$argon2i$v=19$m=65536,t=3,p=4$ScGiEq8Low5K7B7/IwYxgA$q6Zo0u/aDtZk404ZNmBi33WXkC5g0y60QdOQQ3oziyU", 22 | }, 23 | { 24 | "ShouldValidatePasswordArgon2d", 25 | "$argon2d$v=19$m=65536,t=3,p=4$HV/RIiFSYEMoRYqBcFnqfg$eGNckPZjkL2xOIHZv8Q4ROg5xbcdD8ijIJOgPwVAPmA", 26 | }, 27 | } 28 | 29 | for _, tc := range testcCases { 30 | t.Run(tc.name, func(t *testing.T) { 31 | t.Run("CorrectPassword", func(t *testing.T) { 32 | valid, err := CheckPassword(password, tc.have) 33 | 34 | require.NoError(t, err) 35 | assert.True(t, valid) 36 | }) 37 | 38 | t.Run("IncorrectPassword", func(t *testing.T) { 39 | valid, err := CheckPassword(wrongPassword, tc.have) 40 | 41 | require.NoError(t, err) 42 | assert.False(t, valid) 43 | }) 44 | }) 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /t_bcrypt_test.go: -------------------------------------------------------------------------------- 1 | package crypt 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestBcryptOutputs(t *testing.T) { 11 | testcCases := []struct { 12 | name string 13 | have string 14 | }{ 15 | { 16 | "ShouldValidatePasswordStandardVariantB", 17 | "$2b$10$3o9IF74Phgdz4Q6j7K7s0unovt.v.7YBLKFyV73pGTd2.tfdz/F8e", 18 | }, 19 | { 20 | "ShouldValidatePasswordStandardVariantA", 21 | "$2a$10$3o9IF74Phgdz4Q6j7K7s0unovt.v.7YBLKFyV73pGTd2.tfdz/F8e", 22 | }, 23 | { 24 | "ShouldValidatePasswordStandardVariantX", 25 | "$2x$10$3o9IF74Phgdz4Q6j7K7s0unovt.v.7YBLKFyV73pGTd2.tfdz/F8e", 26 | }, 27 | { 28 | "ShouldValidatePasswordStandardVariantY", 29 | "$2y$10$3o9IF74Phgdz4Q6j7K7s0unovt.v.7YBLKFyV73pGTd2.tfdz/F8e", 30 | }, 31 | { 32 | "ShouldValidatePasswordSHA256VariantB", 33 | "$bcrypt-sha256$v=2,t=2b,r=10$oYmTNJVOBi3hdhUYy4JqOe$jCuMDm.Pw9hhoF/FDC6sOi48yBAoWvC", 34 | }, 35 | { 36 | "ShouldValidatePasswordSHA256VariantA", 37 | "$bcrypt-sha256$v=2,t=2a,r=10$oYmTNJVOBi3hdhUYy4JqOe$jCuMDm.Pw9hhoF/FDC6sOi48yBAoWvC", 38 | }, 39 | { 40 | "ShouldValidatePasswordSHA256VariantX", 41 | "$bcrypt-sha256$v=2,t=2x,r=10$oYmTNJVOBi3hdhUYy4JqOe$jCuMDm.Pw9hhoF/FDC6sOi48yBAoWvC", 42 | }, 43 | { 44 | "ShouldValidatePasswordSHA256VariantY", 45 | "$bcrypt-sha256$v=2,t=2y,r=10$oYmTNJVOBi3hdhUYy4JqOe$jCuMDm.Pw9hhoF/FDC6sOi48yBAoWvC", 46 | }, 47 | } 48 | 49 | for _, tc := range testcCases { 50 | t.Run(tc.name, func(t *testing.T) { 51 | t.Run("CorrectPassword", func(t *testing.T) { 52 | valid, err := CheckPassword(password, tc.have) 53 | 54 | require.NoError(t, err) 55 | assert.True(t, valid) 56 | }) 57 | 58 | t.Run("IncorrectPassword", func(t *testing.T) { 59 | valid, err := CheckPassword(wrongPassword, tc.have) 60 | 61 | require.NoError(t, err) 62 | assert.False(t, valid) 63 | }) 64 | }) 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /t_pbkdf2_test.go: -------------------------------------------------------------------------------- 1 | package crypt 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestPBKDF2Outputs(t *testing.T) { 11 | testcCases := []struct { 12 | name string 13 | have string 14 | }{ 15 | { 16 | "ShouldValidatePasswordSHA1", 17 | "$pbkdf2$100000$atrXFCWdBlpmzIi/nXwJOw$20Lsx44nZwmh09bjGHFJ//oRZh8", 18 | }, 19 | { 20 | "ShouldValidatePasswordSHA224", 21 | "$pbkdf2-sha224$100000$qRzrfXPp6ilID9bO89rJkA$akPivgY3p3gLDj8Kd7agycHkM5b0xlTxeLEsqg", 22 | }, 23 | { 24 | "ShouldValidatePasswordSHA256", 25 | "$pbkdf2-sha256$100000$aoWHXwyz0im1Hqg93.N.tA$bO5LsjmnnPle2Xm9RE6W1PMWdJTy1TnEia1TLzynuIQ", 26 | }, 27 | { 28 | "ShouldValidatePasswordSHA384", 29 | "$pbkdf2-sha384$100000$GIZt3eMjZrEs0ycxed3zHg$o8IZWpxd.shbcATBSk9nHqktuvLTv1YeLYowxZM7mO5hhWLa3s4tVFejl9NH9jSO", 30 | }, 31 | { 32 | "ShouldValidatePasswordSHA512", 33 | "$pbkdf2-sha512$100000$bHfSOIyj0UDoCo1Q4Bz49w$v/olF/T/R6On84NuHlNCiI/sUwsdyOC7J4cO8Cz7feNtLHHEKNjayeEZj0b/Js/cgkMK6zLFw2vynLo2el028Q", 34 | }, 35 | } 36 | 37 | for _, tc := range testcCases { 38 | t.Run(tc.name, func(t *testing.T) { 39 | t.Run("CorrectPassword", func(t *testing.T) { 40 | valid, err := CheckPassword(password, tc.have) 41 | 42 | require.NoError(t, err) 43 | assert.True(t, valid) 44 | }) 45 | 46 | t.Run("IncorrectPassword", func(t *testing.T) { 47 | valid, err := CheckPassword(wrongPassword, tc.have) 48 | 49 | require.NoError(t, err) 50 | assert.False(t, valid) 51 | }) 52 | }) 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /t_scrypt_test.go: -------------------------------------------------------------------------------- 1 | package crypt 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | 9 | "github.com/go-crypt/crypt/algorithm/scrypt" 10 | ) 11 | 12 | func TestScryptOutputs(t *testing.T) { 13 | testcCases := []struct { 14 | name string 15 | have string 16 | expected string 17 | }{ 18 | { 19 | "ShouldValidateScrypt", 20 | "$scrypt$ln=4,r=8,p=1$ySYknWRq9On6wWfpsOUQQg$C28LpWaXQ3P0/dcbN0njxJx4VL/UCQIAWlnYAJgT/mY", 21 | password, 22 | }, 23 | { 24 | "ShouldValidateYeScrpytNative", 25 | "$y$j75$z7ztFz2FayrKI79/jEwlL.$u5x/j193MQ09wbFaRGYr0AH/A/jh3kunjuhYRVRNkmC", 26 | "test1", 27 | }, 28 | } 29 | 30 | for _, tc := range testcCases { 31 | t.Run(tc.name, func(t *testing.T) { 32 | t.Run("CorrectPassword", func(t *testing.T) { 33 | valid, err := CheckPassword(tc.expected, tc.have) 34 | 35 | require.NoError(t, err) 36 | assert.True(t, valid) 37 | }) 38 | 39 | t.Run("IncorrectPassword", func(t *testing.T) { 40 | valid, err := CheckPassword(wrongPassword, tc.have) 41 | 42 | require.NoError(t, err) 43 | assert.False(t, valid) 44 | }) 45 | }) 46 | } 47 | } 48 | 49 | func TestScryptOutputsNative(t *testing.T) { 50 | testcCases := []struct { 51 | name string 52 | have string 53 | expected string 54 | }{ 55 | { 56 | "ShouldValidateYeScrpytExample", 57 | "$y$j75$z7ztFz2FayrKI79/jEwlL.$u5x/j193MQ09wbFaRGYr0AH/A/jh3kunjuhYRVRNkmC", 58 | "test1", 59 | }, 60 | { 61 | "ShouldValidateYesCryptOutput", 62 | "$y$jD5$K3wjJ.n1W9g1TfLeI0ESC0$SAt46wIbyewhlHlKVQcelosVETYUGOaV6mC1qjurql9", 63 | "password", 64 | }, 65 | } 66 | 67 | for _, tc := range testcCases { 68 | t.Run(tc.name, func(t *testing.T) { 69 | valid, err := CheckPassword(tc.expected, tc.have) 70 | 71 | require.NoError(t, err) 72 | assert.True(t, valid) 73 | }) 74 | } 75 | } 76 | 77 | func TestScryptEncodeDecode(t *testing.T) { 78 | hash, err := scrypt.NewYescrypt() 79 | require.NoError(t, err) 80 | 81 | digest, err := hash.HashWithSalt("password", []byte("aa131311")) 82 | require.NoError(t, err) 83 | 84 | raw := digest.Encode() 85 | 86 | assert.Equal(t, "$y$jD5$V3KAn2nAl21$2f0mscSRW3Z0u.oHoVtRAfYwQ3ZbWUIbi4SB04ztMSB", raw) 87 | } 88 | -------------------------------------------------------------------------------- /t_shacrypt_test.go: -------------------------------------------------------------------------------- 1 | package crypt 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestSHACRYPTOutputsMKPASSWD(t *testing.T) { 10 | testcCases := []struct { 11 | name string 12 | have string 13 | expected string 14 | valid bool 15 | err string 16 | }{ 17 | { 18 | "ShouldValidatePasswordWithOmittedRoundsSHA256", 19 | "$5$4X/QmdRP6q7Ilhpc$2sperIXN6jawEYd8a8arineQHqYIEGURjZGdD4H4xs8", 20 | password, 21 | true, 22 | "", 23 | }, 24 | { 25 | "ShouldNotValidatePasswordWithOmittedRoundsSHA256", 26 | "$5$4X/QmdRP6q7Ilhpc$2sperIXN6jawEYd8a8arineQHqYIEGURjZGdD4H4xs8", 27 | wrongPassword, 28 | false, 29 | "", 30 | }, 31 | { 32 | "ShouldValidatePasswordWithOmittedRoundsSHA512", 33 | "$6$rB2PL49BuajVczWm$sA.XUPEt/j6k4kFnO58EDKsEU8rXau47.eSH6lpqc/tgC9Y0BbYcG7H3.KmMMpthWMcip/xmDn83nTUXK5Vp90", 34 | password, 35 | true, 36 | "", 37 | }, 38 | { 39 | "ShouldNotValidatePasswordWithOmittedRoundsSHA512", 40 | "$6$rB2PL49BuajVczWm$sA.XUPEt/j6k4kFnO58EDKsEU8rXau47.eSH6lpqc/tgC9Y0BbYcG7H3.KmMMpthWMcip/xmDn83nTUXK5Vp90", 41 | wrongPassword, 42 | false, 43 | "", 44 | }, 45 | { 46 | "ShouldNotValidatePasswordWithRoundsSHA512", 47 | "$6$rounds=1000$rB2PL49BuajVczWm$sA.XUPEt/j6k4kFnO58EDKsEU8rXau47.eSH6lpqc/tgC9Y0BbYcG7H3.KmMMpthWMcip/xmDn83nTUXK5Vp90", 48 | wrongPassword, 49 | false, 50 | "", 51 | }, 52 | { 53 | "ShouldValidatePasswordWithRoundsSHA512", 54 | "$6$rounds=1000$eG49klxUKySvBpju$.1paw1pj51FdmvNAnsNoX8lyHMdH/S74DfkZnWWTOnGI9keTp/DXjR9ro5kJrncPSF5fc.krAwdkBxc4C8kSU1", 55 | password, 56 | true, 57 | "", 58 | }, 59 | } 60 | 61 | for _, tc := range testcCases { 62 | t.Run(tc.name, func(t *testing.T) { 63 | valid, err := CheckPassword(tc.expected, tc.have) 64 | 65 | assert.Equal(t, tc.valid, valid) 66 | if len(tc.err) == 0 { 67 | assert.NoError(t, err) 68 | } else { 69 | assert.EqualError(t, err, tc.err) 70 | } 71 | }) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | package crypt 2 | 3 | import ( 4 | "database/sql/driver" 5 | "fmt" 6 | 7 | "github.com/go-crypt/crypt/algorithm" 8 | ) 9 | 10 | // NewDigest wraps an algorithm.Digest in the convenience layer of the crypt.Digest. 11 | func NewDigest(d algorithm.Digest) (digest *Digest, err error) { 12 | if d == nil { 13 | return nil, fmt.Errorf("can't create crypt.Digest from nil") 14 | } 15 | 16 | return &Digest{digest: d}, nil 17 | } 18 | 19 | // NewDigestDecode decodes a string into a algorithm.Digest and wraps it in the convenience layer of the crypt.Digest. 20 | func NewDigestDecode(encodedDigest string) (digest *Digest, err error) { 21 | if len(encodedDigest) == 0 { 22 | return nil, fmt.Errorf("can't create crypt.Digest from empty string") 23 | } 24 | 25 | var d algorithm.Digest 26 | 27 | if d, err = Decode(encodedDigest); err != nil { 28 | return nil, err 29 | } 30 | 31 | return &Digest{digest: d}, nil 32 | } 33 | 34 | // NewNullDigest wraps an algorithm.Digest in the convenience layer of the crypt.NullDigest. 35 | func NewNullDigest(d algorithm.Digest) (digest *NullDigest) { 36 | return &NullDigest{digest: d} 37 | } 38 | 39 | // NewNullDigestDecode decodes a string into a algorithm.Digest and wraps it in the convenience layer of the crypt.NullDigest. 40 | func NewNullDigestDecode(encodedDigest string) (digest *NullDigest, err error) { 41 | if len(encodedDigest) == 0 { 42 | return &NullDigest{}, nil 43 | } 44 | 45 | var ( 46 | d algorithm.Digest 47 | ) 48 | 49 | if d, err = Decode(encodedDigest); err != nil { 50 | return nil, err 51 | } 52 | 53 | return &NullDigest{digest: d}, nil 54 | } 55 | 56 | // Digest is a decorator struct which wraps the algorithm.Digest and adds sql.Scanner/driver.Valuer, 57 | // encoding.TextMarshaler/encoding.TextUnmarshaler, and encoding.BinaryMarshaler/encoding.BinaryUnmarshaler 58 | // implementations. 59 | type Digest struct { 60 | digest algorithm.Digest 61 | } 62 | 63 | // Encode decorates the algorithm.Digest Encode function. 64 | func (d *Digest) Encode() string { 65 | return d.digest.Encode() 66 | } 67 | 68 | // String decorates the algorithm.Digest String function. 69 | func (d *Digest) String() string { 70 | return d.digest.String() 71 | } 72 | 73 | // MatchBytes decorates the algorithm.Digest MatchBytes function. 74 | func (d *Digest) MatchBytes(passwordBytes []byte) (match bool) { 75 | return d.digest.MatchBytes(passwordBytes) 76 | } 77 | 78 | // MatchAdvanced decorates the algorithm.Digest MatchAdvanced function. 79 | func (d *Digest) MatchAdvanced(password string) (match bool, err error) { 80 | return d.digest.MatchAdvanced(password) 81 | } 82 | 83 | // MatchBytesAdvanced decorates the algorithm.Digest MatchBytesAdvanced function. 84 | func (d *Digest) MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) { 85 | return d.digest.MatchBytesAdvanced(passwordBytes) 86 | } 87 | 88 | // Match decorates the algorithm.Digest Match function. 89 | func (d *Digest) Match(password string) (match bool) { 90 | return d.digest.Match(password) 91 | } 92 | 93 | // Value implements driver.Valuer. 94 | func (d *Digest) Value() (value driver.Value, err error) { 95 | if d.digest == nil { 96 | return "", nil 97 | } 98 | 99 | return d.digest.Encode(), nil 100 | } 101 | 102 | // Scan implements sql.Scanner. 103 | func (d *Digest) Scan(src any) (err error) { 104 | switch digest := src.(type) { 105 | case nil: 106 | return fmt.Errorf("invalid type for crypt.Digest: can't scan nil value into crypt.Digest: use crypt.NullDigest instead") 107 | case string: 108 | if d.digest, err = Decode(digest); err != nil { 109 | return err 110 | } 111 | 112 | return nil 113 | case byte: 114 | if d.digest, err = Decode(string(digest)); err != nil { 115 | return err 116 | } 117 | 118 | return nil 119 | default: 120 | return fmt.Errorf("invalid type for crypt.Digest: can't scan %T into crypt.Digest", digest) 121 | } 122 | } 123 | 124 | // MarshalText implements encoding.TextMarshaler. 125 | func (d *Digest) MarshalText() (data []byte, err error) { 126 | if d.digest == nil { 127 | return []byte(""), nil 128 | } 129 | 130 | return []byte(d.digest.Encode()), nil 131 | } 132 | 133 | // UnmarshalText implements encoding.TextUnmarshaler. 134 | func (d *Digest) UnmarshalText(data []byte) (err error) { 135 | if len(data) == 0 { 136 | return fmt.Errorf("can't unmarhsal empty data to crypt.Digest") 137 | } 138 | 139 | var digest algorithm.Digest 140 | 141 | if digest, err = Decode(string(data)); err != nil { 142 | return err 143 | } 144 | 145 | d.digest = digest 146 | 147 | return nil 148 | } 149 | 150 | // MarshalBinary implements encoding.BinaryMarshaler. 151 | func (d *Digest) MarshalBinary() (data []byte, err error) { 152 | return d.MarshalText() 153 | } 154 | 155 | // UnmarshalBinary implements encoding.BinaryUnmarshaler. 156 | func (d *Digest) UnmarshalBinary(data []byte) (err error) { 157 | return d.UnmarshalText(data) 158 | } 159 | 160 | // NullDigest is variation of crypt.Digest which accepts nulls. 161 | type NullDigest struct { 162 | digest algorithm.Digest 163 | } 164 | 165 | // Encode decorates the algorithm.Digest Encode function. 166 | func (d *NullDigest) Encode() string { 167 | if d.digest == nil { 168 | return "" 169 | } 170 | 171 | return d.digest.Encode() 172 | } 173 | 174 | // String decorates the algorithm.Digest String function. 175 | func (d *NullDigest) String() string { 176 | if d.digest == nil { 177 | return "" 178 | } 179 | 180 | return d.digest.String() 181 | } 182 | 183 | // Match decorates the algorithm.Digest Match function. 184 | func (d *NullDigest) Match(password string) (match bool) { 185 | if d.digest == nil { 186 | return false 187 | } 188 | 189 | return d.digest.Match(password) 190 | } 191 | 192 | // MatchBytes decorates the algorithm.Digest MatchBytes function. 193 | func (d *NullDigest) MatchBytes(passwordBytes []byte) (match bool) { 194 | if d.digest == nil { 195 | return false 196 | } 197 | 198 | return d.digest.MatchBytes(passwordBytes) 199 | } 200 | 201 | // MatchAdvanced decorates the algorithm.Digest MatchAdvanced function. 202 | func (d *NullDigest) MatchAdvanced(password string) (match bool, err error) { 203 | if d.digest == nil { 204 | return false, nil 205 | } 206 | 207 | return d.digest.MatchAdvanced(password) 208 | } 209 | 210 | // MatchBytesAdvanced decorates the algorithm.Digest MatchBytesAdvanced function. 211 | func (d *NullDigest) MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) { 212 | if d.digest == nil { 213 | return false, nil 214 | } 215 | 216 | return d.digest.MatchBytesAdvanced(passwordBytes) 217 | } 218 | 219 | // Value implements driver.Valuer. 220 | func (d *NullDigest) Value() (value driver.Value, err error) { 221 | if d.digest == nil { 222 | return nil, nil 223 | } 224 | 225 | return d.digest.Encode(), nil 226 | } 227 | 228 | // Scan implements sql.Scanner. 229 | func (d *NullDigest) Scan(src any) (err error) { 230 | switch digest := src.(type) { 231 | case nil: 232 | d.digest = nil 233 | 234 | return nil 235 | case string: 236 | if d.digest, err = Decode(digest); err != nil { 237 | return err 238 | } 239 | 240 | return nil 241 | case byte: 242 | if d.digest, err = Decode(string(digest)); err != nil { 243 | return err 244 | } 245 | 246 | return nil 247 | default: 248 | return fmt.Errorf("invalid type for crypt.Digest: can't scan %T into crypt.Digest", digest) 249 | } 250 | } 251 | 252 | // MarshalText implements encoding.TextMarshaler. 253 | func (d *NullDigest) MarshalText() (data []byte, err error) { 254 | if d.digest == nil { 255 | return nil, nil 256 | } 257 | 258 | return []byte(d.digest.Encode()), nil 259 | } 260 | 261 | // UnmarshalText implements encoding.TextUnmarshaler. 262 | func (d *NullDigest) UnmarshalText(data []byte) (err error) { 263 | if len(data) == 0 { 264 | d.digest = nil 265 | 266 | return nil 267 | } 268 | 269 | var digest algorithm.Digest 270 | 271 | if digest, err = Decode(string(data)); err != nil { 272 | return err 273 | } 274 | 275 | d.digest = digest 276 | 277 | return nil 278 | } 279 | 280 | // MarshalBinary implements encoding.BinaryMarshaler. 281 | func (d *NullDigest) MarshalBinary() (data []byte, err error) { 282 | return d.MarshalText() 283 | } 284 | 285 | // UnmarshalBinary implements encoding.BinaryUnmarshaler. 286 | func (d *NullDigest) UnmarshalBinary(data []byte) (err error) { 287 | return d.UnmarshalText(data) 288 | } 289 | --------------------------------------------------------------------------------