pax_global_header00006660000000000000000000000064151040022540014503gustar00rootroot0000000000000052 comment=fc7a31465be25870e0ed1691e76eb542fd468258 crypt-0.4.7/000077500000000000000000000000001510400225400126545ustar00rootroot00000000000000crypt-0.4.7/.github/000077500000000000000000000000001510400225400142145ustar00rootroot00000000000000crypt-0.4.7/.github/CODEOWNERS000066400000000000000000000001371510400225400156100ustar00rootroot00000000000000# The maintainers team is a code owner for the whole repository. * @go-crypt/maintainers crypt-0.4.7/.github/dependabot.yml000066400000000000000000000001571510400225400170470ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: github-actions directory: / schedule: interval: daily crypt-0.4.7/.github/workflows/000077500000000000000000000000001510400225400162515ustar00rootroot00000000000000crypt-0.4.7/.github/workflows/codeql.yml000066400000000000000000000022671510400225400202520ustar00rootroot00000000000000name: 'CodeQL' on: push: branches: - 'master' pull_request: branches: - 'master' schedule: - cron: '0 0 * * 1' permissions: contents: 'read' jobs: analyze: name: 'Analyze' runs-on: 'ubuntu-latest' permissions: actions: 'read' contents: 'read' security-events: 'write' strategy: fail-fast: false matrix: language: - 'go' steps: - name: 'Harden Runner' uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: egress-policy: 'audit' - name: 'Checkout' uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: 'Initialize CodeQL' uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5 with: languages: ${{ matrix.language }} - name: 'Build' uses: github/codeql-action/autobuild@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5 - name: 'Perform CodeQL Analysis' uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5 with: category: "/language:${{matrix.language}}" crypt-0.4.7/.github/workflows/dependabot.yml000066400000000000000000000015651510400225400211100ustar00rootroot00000000000000name: 'Dependabot' on: pull_request: {} permissions: contents: 'write' pull-requests: 'write' jobs: automerge: name: 'Auto-Merge' runs-on: 'ubuntu-latest' if: github.event.pull_request.user.login == 'dependabot[bot]' && github.repository == 'go-crypt/crypt' steps: - name: 'Harden Runner' uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: egress-policy: 'audit' - name: 'Dependabot Fetch Metadata' id: 'metadata' uses: dependabot/fetch-metadata@08eff52bf64351f401fb50d4972fa95b9f2c2d1b with: github-token: ${{ secrets.GITHUB_TOKEN }} - name: 'Enable Auto-Merge' run: | gh pr merge --auto --squash "$PR_URL" env: PR_URL: ${{github.event.pull_request.html_url}} GH_TOKEN: ${{secrets.GITHUB_TOKEN}} crypt-0.4.7/.github/workflows/dependency-review.yml000066400000000000000000000011241510400225400224070ustar00rootroot00000000000000name: 'Dependency Review' on: pull_request: {} permissions: contents: 'read' jobs: dependency-review: name: 'Dependency Review' runs-on: 'ubuntu-latest' steps: - name: 'Harden Runner' uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: egress-policy: 'audit' - name: 'Checkout Repository' uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: 'Dependency Review' uses: actions/dependency-review-action@40c09b7dc99638e5ddb0bfd91c1673effc064d8a # v4.8.1 crypt-0.4.7/.github/workflows/go.yml000066400000000000000000000033701510400225400174040ustar00rootroot00000000000000name: 'Go' on: pull_request: {} push: branches: - 'master' permissions: contents: 'read' jobs: cover: name: 'Coverage' runs-on: 'ubuntu-latest' steps: - name: 'Harden Runner' uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: egress-policy: 'audit' - name: 'Set up Go' uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 with: go-version: 1.25 - name: 'Checkout' uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: 'Get Dependencies' run: | go get -v -t -d ./... - name: 'Test' run: | go test -coverprofile=coverage.txt -v ./... - name: 'Coverage' uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 with: token: ${{ secrets.CODECOV_TOKEN }} build: name: 'Build and Test' runs-on: 'ubuntu-latest' strategy: matrix: go: - '1.24' - '1.25' fail-fast: false steps: - name: 'Harden Runner' uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: egress-policy: 'audit' - name: 'Set up Go ${{ matrix.go }}' uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 with: go-version: ${{ matrix.go }} - name: 'Checkout' uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: 'Get Dependencies' run: | go get -v -t -d ./... - name: 'Build' run: | go build -v ./... - name: 'Test' run: | go test -v ./...crypt-0.4.7/.github/workflows/scorecards.yml000066400000000000000000000024431510400225400211270ustar00rootroot00000000000000name: 'Scorecard' on: branch_protection_rule: {} schedule: - cron: '20 7 * * 2' push: branches: - 'master' permissions: 'read-all' jobs: analysis: name: 'Analysis' runs-on: 'ubuntu-latest' permissions: security-events: 'write' id-token: 'write' contents: 'read' actions: 'read' steps: - name: 'Harden Runner' uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: egress-policy: 'audit' - name: 'Checkout' uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - name: 'Run' uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 with: results_file: 'results.sarif' results_format: 'sarif' publish_results: true - name: 'Upload' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: 'SARIF file' path: 'results.sarif' retention-days: 5 - name: 'Upload to Code Scanning Dashboard' uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5 with: sarif_file: 'results.sarif' crypt-0.4.7/.golangci.yml000066400000000000000000000012151510400225400152370ustar00rootroot00000000000000--- run: timeout: 3m linters-settings: goconst: min-len: 2 min-occurrences: 2 gocyclo: min-complexity: 16 godot: check-all: true goimports: local-prefixes: github.com/go-crypt/crypt linters: enable: - asciicheck - goconst - gocritic - gocyclo - godot - gofmt - goimports - gosec - misspell - nolintlint - prealloc - revive - unconvert - unparam - whitespace - wsl issues: exclude: - 'SA4011: ineffective break statement. Did you mean to break out of the outer loop' exclude-use-default: false max-issues-per-linter: 0 max-same-issues: 0 ... crypt-0.4.7/.pre-commit-config.yaml000066400000000000000000000005011510400225400171310ustar00rootroot00000000000000repos: - repo: https://github.com/gitleaks/gitleaks rev: v8.16.3 hooks: - id: gitleaks - repo: https://github.com/golangci/golangci-lint rev: v1.52.2 hooks: - id: golangci-lint - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - id: end-of-file-fixer - id: trailing-whitespace crypt-0.4.7/.renovaterc000066400000000000000000000007031510400225400150250ustar00rootroot00000000000000{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "constraints": { "go": "1.25" }, "extends": [ "config:recommended", ":semanticCommitTypeAll(build)", ":separatePatchReleases" ], "ignorePresets": [ ":combinePatchMinorReleases", ":prHourlyLimit2", ":semanticPrefixFixDepsChoreOthers" ], "enabledManagers": [ "gomod" ], "postUpdateOptions": [ "gomodTidy", "gomodMassage" ] } crypt-0.4.7/LICENSE000066400000000000000000000020711510400225400136610ustar00rootroot00000000000000MIT License Copyright (c) 2022 github.com/go-crypt/crypt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.crypt-0.4.7/README.md000066400000000000000000000243111510400225400141340ustar00rootroot00000000000000[![Go Reference](https://pkg.go.dev/badge/github.com/go-crypt/crypt.svg)](https://pkg.go.dev/github.com/go-crypt/crypt) [![Go Report Card](https://goreportcard.com/badge/github.com/go-crypt/crypt)](https://goreportcard.com/report/github.com/go-crypt/crypt) # github.com/go-crypt/crypt Password Hashing / Digest / Crypt library. ## Intent This library aims to provide a convenient layer over the go password hashing crypto functions. ## Tasks A list of tasks that need to be accomplished are listed in the [General Project](https://github.com/orgs/go-crypt/projects/1). ## Algorithms ### Supported | Algorithm | Variants | Identifiers | |:----------------------------------------------------------------------------:|:------------------------------------:|:-------------------------------------------------------------------------------------------:| | [Argon2](https://www.rfc-editor.org/rfc/rfc9106.html) | Argon2id, Argon2i, Argon2d | `argon2id`, `argon2i`, `argon2d` | | [SHA-crypt](https://www.akkadia.org/drepper/SHA-crypt.txt) | SHA256, SHA512 | `5`, `6` | | PBKDF2 | SHA1, SHA224, SHA256, SHA384, SHA512 | `pbkdf2`, `pbkdf2-sha1`, `pbkdf2-sha224`, `pbkdf2-sha256`, `pbkdf2-sha384`, `pbkdf2-sha512` | | [bcrypt](https://www.usenix.org/legacy/event/usenix99/provos/provos_html/) | bcrypt, bcrypt-sha256 | `2`, `2a`, `2b`, `2x`, `2y`, `bcrypt-sha256` | | [scrypt](https://www.rfc-editor.org/rfc/rfc7914.html) | scrypt, yescrypt | `scrypt`, `y` | | md5crypt | standard, sun | `1`, `md5` | | sha1crypt | standard | `sha1` | | [PlainText](#plain-text-format) | plaintext, base64 | `plaintext`, `base64` | #### Plain Text Format In addition to the standard crypt functions we also support a plain text storage format which has a regular plain text variant and a Base64 format (for storage, not security). The [PHC string format] we decided to use is as follows: ``` $$ ``` Where `id` is either `plaintext` or `base64`, and `data` is either the password string or the [Base64 (Adapted)](#base64-adapted) encoded string. #### bcrypt-sha256 This algorithm was thought of by the developers of [Passlib]. It circumvents the issue in bcrypt where the maximum password length is effectively 72 bytes by passing the password via a HMAC-SHA-256 function which uses the salt bytes as the key. *__Note:__ Only bcrypt-sha256 version 2 which uses the [PHC string format] and passes the password through a HMAC-SHA-256 function the salt as the key is supported. The bcrypt-sha256 version 1 which uses the [Modular Crypt Format] and only passes the password via a SHA-256 sum function not supported at all.* [Passlib]: https://passlib.readthedocs.io/en/stable/ [PHC string format]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md [Modular Crypt Format]: https://passlib.readthedocs.io/en/stable/modular_crypt_format.html ### Possible Future Support | Algorithm | Reasoning | |:---------------:|:-----------------------------------------------------:| | Type 7 (cisco) | Explicit Backwards Compatibility and Interoperability | | Type 8 (cisco) | Explicit Backwards Compatibility and Interoperability | | Type 9 (cisco) | Explicit Backwards Compatibility and Interoperability | | Type 10 (cisco) | Explicit Backwards Compatibility and Interoperability | | LDAP RFC2307 | Explicit Backwards Compatibility and Interoperability | Additional support for LDAP specific formats is also very likely, either via normalization and encoding options or via explicit algorithm variants and/or specific algorithms. ## Base64 (Adapted) Many password storage formats use Base64 with an Adapted charset to store the bytes of the salt or hash key. This uses the standard Base64 encoding without padding as per [RFC4648 section 4] but replaces the `+` chars with a `.`. [RFC4648 section 4]: https://datatracker.ietf.org/doc/html/rfc4648#section-4 ## Installation Use `go get` to add this module to your project with `go get github.com/go-crypt/crypt`. ### Requirements - go 1.23+ ## Usage The following examples show how easy it is to interact with the argon2 algorithm. Most other algorithm implementations are relatively similar. ### Functional Options Pattern The `algorithm.Hasher` implementations use a functional options pattern. This pattern is accessible via the `New` function in each algorithm package or via a receiver function of the individual `algorithm.Hasher` implementation called `WithOptions`. Most algorithm implementations have at least the following functional option signatures: - `WithVariant(variant Variant) Opt` - `WithVariantName(identifier string) Opt` - `WithIterations(iterations int) Opt` With the exception of `WithVariantName` which takes a string, and `WithVariant` which takes a `Variant` type (which is technically a int), nearly every functional option takes a single `int`. There are a few functional options which take a single `uint32` where the maximum value exceeds the maximum value for an untyped int on 32bit architectures. If the `uint32` methods are an issue for anyone using this module we suggest opening an issue and describing why and we'll consider adding another functional option which takes an `int`. ### Creating a Decoder While several convenience functions exist for building password decoders and checking individual passwords it is *__STRONGLY RECOMMENDED__* that users implementing this library explicitly create a decoder that fits their particular use case after sufficiently researching each algorithm and their benefits. At the time of this writing we strongly recommend the `argon2id` variant of `argon2`. This can be done via the `crypt.NewDecoder` function as shown below. ```go package main import ( "fmt" "github.com/go-crypt/crypt" "github.com/go-crypt/crypt/algorithm" "github.com/go-crypt/crypt/algorithm/argon2" ) func main() { var ( decoder *crypt.Decoder err error digest algorithm.Digest ) if decoder, err = NewDecoderArgon2idOnly(); err != nil { panic(err) } if digest, err = decoder.Decode("$argon2id$v=19$m=2097152,t=1,p=4$BjVeoTI4ntTQc0WkFQdLWg$OAUnkkyx5STI0Ixl+OSpv4JnI6J1TYWKuCuvIbUGHTY"); err != nil { panic(err) } fmt.Printf("Digest Matches Password 'example': %t\n", digest.Match("example")) fmt.Printf("Digest Matches Password 'invalid': %t\n", digest.Match("invalid")) } // NewDecoderArgon2idOnly returns a decoder which can only decode argon2id encoded digests. func NewDecoderArgon2idOnly() (decoder *crypt.Decoder, err error) { decoder = crypt.NewDecoder() if err = argon2.RegisterDecoderArgon2id(decoder); err != nil { return nil, err } return decoder, nil } ``` ### Decoding a Password and Validating It This method of checking passwords is recommended if you have a database of hashes which are going to live in memory. The `crypt.Digest` and `crypt.NullDigest` types provide helpful interface implementations to simplify Marshal/Unmarshal and database operations. ```go package main import ( "fmt" "github.com/go-crypt/crypt" "github.com/go-crypt/crypt/algorithm" ) func main() { var ( decoder *crypt.Decoder err error digest algorithm.Digest ) if decoder, err = crypt.NewDefaultDecoder(); err != nil { panic(err) } if digest, err = decoder.Decode("$argon2id$v=19$m=2097152,t=1,p=4$BjVeoTI4ntTQc0WkFQdLWg$OAUnkkyx5STI0Ixl+OSpv4JnI6J1TYWKuCuvIbUGHTY"); err != nil { panic(err) } fmt.Printf("Digest Matches Password 'example': %t\n", digest.Match("example")) fmt.Printf("Digest Matches Password 'invalid': %t\n", digest.Match("invalid")) } ``` ### Checking a Password Against a Hash This method of checking passwords is quick and dirty and most useful when users are providing the hash as the input such as in situations where you are allowing them to check a password themselves via a CLI or otherwise. ```go package main import ( "fmt" "github.com/go-crypt/crypt" ) func main() { var ( valid bool err error ) if valid, err = crypt.CheckPassword("example","$argon2id$v=19$m=2097152,t=1,p=4$BjVeoTI4ntTQc0WkFQdLWg$OAUnkkyx5STI0Ixl+OSpv4JnI6J1TYWKuCuvIbUGHTY"); err != nil { panic(err) } fmt.Printf("Digest Matches Password 'example': %t\n", valid) if valid, err = crypt.CheckPassword("invalid","$argon2id$v=19$m=2097152,t=1,p=4$BjVeoTI4ntTQc0WkFQdLWg$OAUnkkyx5STI0Ixl+OSpv4JnI6J1TYWKuCuvIbUGHTY"); err != nil { panic(err) } fmt.Printf("Digest Matches Password 'invalid': %t\n", valid) } ``` ### Generating an Encoded Digest from a Password ```go package main import ( "fmt" "github.com/go-crypt/crypt/algorithm" "github.com/go-crypt/crypt/algorithm/argon2" ) func main() { var ( hasher *argon2.Hasher err error digest algorithm.Digest ) if hasher, err = argon2.New( argon2.WithProfileRFC9106LowMemory(), ); err != nil { panic(err) } if digest, err = hasher.Hash("example"); err != nil { panic(err) } fmt.Printf("Encoded Digest With Password 'example': %s\n", digest.Encode()) } ```crypt-0.4.7/algorithm/000077500000000000000000000000001510400225400146425ustar00rootroot00000000000000crypt-0.4.7/algorithm/argon2/000077500000000000000000000000001510400225400160325ustar00rootroot00000000000000crypt-0.4.7/algorithm/argon2/const.go000066400000000000000000000046101510400225400175100ustar00rootroot00000000000000package argon2 import ( "math" ) const ( // EncodingFmt is the encoding format for this algorithm. EncodingFmt = "$%s$v=%d$m=%d,t=%d,p=%d$%s$%s" // AlgName is the name for this algorithm. AlgName = "argon2" // AlgIdentifierVariantI is the identifier used in encoded argon2i variants of this algorithm. AlgIdentifierVariantI = argon2i // AlgIdentifierVariantD is the identifier used in encoded argon2d variants of this algorithm. AlgIdentifierVariantD = argon2d // AlgIdentifierVariantID is the identifier used in encoded argon2id variants of this algorithm. AlgIdentifierVariantID = argon2id // KeyLengthMin is the minimum tag length output. KeyLengthMin = 4 // KeyLengthMax is the maximum tag length output. KeyLengthMax = math.MaxInt32 // KeyLengthDefault is the default key length. KeyLengthDefault = 32 // SaltLengthMin is the minimum salt length input/output. SaltLengthMin = 1 // SaltLengthMax is the maximum salt length input/output. SaltLengthMax = math.MaxInt32 // IterationsMin is the minimum number of passes input. IterationsMin = 1 // IterationsMax is the maximum number of passes input. IterationsMax = math.MaxInt32 // IterationsDefault is the default number of passes. IterationsDefault = IterationsMin // ParallelismMin is the minimum parallelism factor input. ParallelismMin = 1 // ParallelismMax is the maximum parallelism factor input. ParallelismMax = 16777215 // ParallelismDefault is the default parallelism factor. ParallelismDefault = 4 // MemoryMinParallelismMultiplier is the parallelism multiplier which determines the minimum memory. MemoryMinParallelismMultiplier = 8 // MemoryRoundingParallelismMultiplier is the parallelism multiplier which determines the actual memory value. The // value is the closest multiple of this multiplied by the parallelism input. MemoryRoundingParallelismMultiplier = 4 // MemoryMin is the minimum input for memory. MemoryMin = ParallelismMin * MemoryMinParallelismMultiplier // MemoryMax is the maximum input for memory. MemoryMax uint32 = math.MaxUint32 // MemoryDefault represents the default memory value. MemoryDefault = 2 * 1024 * 1024 // PasswordInputSizeMax is the maximum input for the password content. PasswordInputSizeMax = math.MaxInt32 ) const ( argon2i = "argon2i" argon2d = "argon2d" argon2id = "argon2id" variantDefault = VariantID oV = "v" oK = "k" oM = "m" oT = "t" oP = "p" ) crypt-0.4.7/algorithm/argon2/decoder.go000066400000000000000000000113471510400225400177740ustar00rootroot00000000000000package argon2 import ( "encoding/base64" "fmt" "strconv" "github.com/go-crypt/x/argon2" "github.com/go-crypt/crypt/algorithm" "github.com/go-crypt/crypt/internal/encoding" ) // RegisterDecoder the decoder with the algorithm.DecoderRegister. func RegisterDecoder(r algorithm.DecoderRegister) (err error) { if err = RegisterDecoderArgon2id(r); err != nil { return err } if err = RegisterDecoderArgon2i(r); err != nil { return err } if err = RegisterDecoderArgon2d(r); err != nil { return err } return nil } // RegisterDecoderArgon2id registers specifically the argon2id decoder variant with the algorithm.DecoderRegister. func RegisterDecoderArgon2id(r algorithm.DecoderRegister) (err error) { if err = r.RegisterDecodeFunc(VariantID.Prefix(), DecodeVariant(VariantID)); err != nil { return err } return nil } // RegisterDecoderArgon2i registers specifically the argon2i decoder variant with the algorithm.DecoderRegister. func RegisterDecoderArgon2i(r algorithm.DecoderRegister) (err error) { if err = r.RegisterDecodeFunc(VariantI.Prefix(), DecodeVariant(VariantI)); err != nil { return err } return nil } // RegisterDecoderArgon2d registers specifically the argon2d decoder variant with the algorithm.DecoderRegister. func RegisterDecoderArgon2d(r algorithm.DecoderRegister) (err error) { if err = r.RegisterDecodeFunc(VariantD.Prefix(), DecodeVariant(VariantD)); err != nil { return err } return nil } // Decode the encoded digest into a algorithm.Digest. func Decode(encodedDigest string) (digest algorithm.Digest, err error) { return DecodeVariant(VariantNone)(encodedDigest) } // DecodeVariant the encoded digest into a algorithm.Digest provided it matches the provided argon2.Variant. If // argon2.VariantNone is used all variants can be decoded. func DecodeVariant(v Variant) func(encodedDigest string) (digest algorithm.Digest, err error) { return func(encodedDigest string) (digest algorithm.Digest, err error) { var ( parts []string variant Variant ) if variant, parts, err = decoderParts(encodedDigest); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) } if v != VariantNone && v != variant { 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())) } if digest, err = decode(variant, parts); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) } return digest, nil } } func decoderParts(encodedDigest string) (variant Variant, parts []string, err error) { parts = encoding.Split(encodedDigest, -1) if len(parts) != 6 { return VariantNone, nil, algorithm.ErrEncodedHashInvalidFormat } variant = NewVariant(parts[1]) if variant == VariantNone { return variant, nil, fmt.Errorf("%w: identifier '%s' is not an encoded %s digest", algorithm.ErrEncodedHashInvalidIdentifier, parts[1], AlgName) } return variant, parts[2:], nil } //nolint:gocyclo func decode(variant Variant, parts []string) (digest algorithm.Digest, err error) { decoded := &Digest{ variant: variant, } var ( value uint64 bitSize int ) var params []encoding.Parameter if params, err = encoding.DecodeParameterStr(parts[1] + "," + parts[0]); err != nil { return nil, err } for _, param := range params { switch param.Key { case oV: bitSize = 8 default: bitSize = 32 } if value, err = strconv.ParseUint(param.Value, 10, bitSize); err != nil { return nil, fmt.Errorf("%w: option '%s' has invalid value '%s': %v", algorithm.ErrEncodedHashInvalidOptionValue, param.Key, param.Value, err) } switch param.Key { case oV: decoded.v = uint8(value) if decoded.v != argon2.Version { return nil, fmt.Errorf("%w: version %d is supported but encoded hash is version %d", algorithm.ErrEncodedHashInvalidVersion, argon2.Version, decoded.v) } case oK: break case oM: decoded.m = uint32(value) case oT: decoded.t = uint32(value) case oP: decoded.p = uint32(value) default: return nil, fmt.Errorf("%w: option '%s' with value '%s' is unknown", algorithm.ErrEncodedHashInvalidOptionKey, param.Key, param.Value) } } if decoded.salt, err = base64.RawStdEncoding.DecodeString(parts[2]); err != nil { return nil, fmt.Errorf("%w: %v", algorithm.ErrEncodedHashSaltEncoding, err) } if decoded.key, err = base64.RawStdEncoding.DecodeString(parts[3]); err != nil { return nil, fmt.Errorf("%w: %v", algorithm.ErrEncodedHashKeyEncoding, err) } if len(decoded.key) == 0 { return nil, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrEncodedHashKeyEncoding) } if decoded.t == 0 { decoded.t = 1 } if decoded.p == 0 { decoded.p = 4 } if decoded.m == 0 { decoded.m = 32 * 1024 } return decoded, nil } crypt-0.4.7/algorithm/argon2/digest.go000066400000000000000000000047141510400225400176460ustar00rootroot00000000000000package argon2 import ( "crypto/subtle" "encoding/base64" "fmt" "strings" "github.com/go-crypt/x/argon2" "github.com/go-crypt/crypt/algorithm" "github.com/go-crypt/crypt/internal/math" ) // Digest is a digest which handles Argon2 hashes like Argon2id, Argon2i, and Argon2d. type Digest struct { variant Variant v uint8 m, t, p uint32 salt, key []byte } // Match returns true if the string password matches the current argon2.Digest. func (d *Digest) Match(password string) (match bool) { return d.MatchBytes([]byte(password)) } // MatchBytes returns true if the []byte passwordBytes matches the current argon2.Digest. func (d *Digest) MatchBytes(passwordBytes []byte) (match bool) { match, _ = d.MatchBytesAdvanced(passwordBytes) return match } // MatchAdvanced is the same as Match except if there is an error it returns that as well. func (d *Digest) MatchAdvanced(password string) (match bool, err error) { return d.MatchBytesAdvanced([]byte(password)) } // MatchBytesAdvanced is the same as MatchBytes except if there is an error it returns that as well. func (d *Digest) MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) { if len(d.key) == 0 { return false, fmt.Errorf(algorithm.ErrFmtDigestMatch, AlgName, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrPasswordInvalid)) } return subtle.ConstantTimeCompare(d.key, d.variant.KeyFunc()(passwordBytes, d.salt, d.t, d.m, d.p, uint32(len(d.key)))) == 1, nil } // Encode returns the encoded form of this argon2.Digest. func (d *Digest) Encode() (encodedHash string) { return strings.ReplaceAll(fmt.Sprintf(EncodingFmt, d.variant.Prefix(), argon2.Version, d.m, d.t, d.p, base64.RawStdEncoding.EncodeToString(d.salt), base64.RawStdEncoding.EncodeToString(d.key), ), "\n", "") } // String returns the storable format of the argon2.Digest encoded hash. func (d *Digest) String() string { return d.Encode() } func (d *Digest) defaults() { switch d.variant { case VariantID, VariantI, VariantD: break default: d.variant = variantDefault } if d.t < IterationsMin { d.t = IterationsDefault } if d.p < ParallelismMin { d.p = ParallelismDefault } if d.m < MemoryMin { d.m = MemoryDefault } /* 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 is m rounded down to the nearest multiple of 4*p. */ pM := d.p * MemoryRoundingParallelismMultiplier d.m = math.Uint32RoundDownToNearestMultiple(d.m, pM) } crypt-0.4.7/algorithm/argon2/doc.go000066400000000000000000000003601510400225400171250ustar00rootroot00000000000000// Package argon2 provides helpful abstractions for an implementation of RFC9106 and implements // github.com/go-crypt/crypt interfaces. // // This implementation is loaded by crypt.NewDefaultDecoder and crypt.NewDecoderAll. package argon2 crypt-0.4.7/algorithm/argon2/hasher.go000066400000000000000000000111561510400225400176370ustar00rootroot00000000000000package argon2 import ( "fmt" "github.com/go-crypt/crypt/algorithm" "github.com/go-crypt/crypt/internal/random" ) // New returns a new argon2.Hasher with the provided functional options applied. func New(opts ...Opt) (hasher *Hasher, err error) { hasher = &Hasher{} if err = hasher.WithOptions(opts...); err != nil { return nil, err } if err = hasher.Validate(); err != nil { return nil, err } return hasher, nil } // Hasher is a crypt.Hash for Argon2 which can be initialized via argon2.New using a functional options pattern. type Hasher struct { variant Variant s, k, t, p int m uint32 d bool } // WithOptions applies the provided functional options provided as an argon2.Opt to the argon2.Hasher. func (h *Hasher) WithOptions(opts ...Opt) (err error) { for _, opt := range opts { if err = opt(h); err != nil { return err } } return nil } // Copy copies all parameters from this argon2.Hasher to another *argon2.Hasher. func (h *Hasher) Copy(hasher *Hasher) { hasher.variant, hasher.t, hasher.p, hasher.m, hasher.k, hasher.s = h.variant, h.t, h.p, h.m, h.k, h.s } // Clone returns a clone from this argon2.Hasher to another *argon2.Hasher. func (h *Hasher) Clone() *Hasher { return &Hasher{ variant: h.variant, t: h.t, p: h.p, m: h.m, k: h.k, s: h.s, } } // Merge copies all parameters from this argon2.Hasher to another *argon2.Hasher where the parameters are unset. func (h *Hasher) Merge(hash *Hasher) { if hash.variant == VariantNone { hash.variant = h.variant } if hash.t == 0 { hash.t = h.t } if hash.p == 0 { hash.p = h.p } if hash.m == 0 { hash.m = h.m } if hash.k == 0 { hash.k = h.k } if hash.s == 0 { hash.s = h.s } } // Hash performs the hashing operation and returns either a argon2.Digest or an error. func (h *Hasher) Hash(password string) (digest algorithm.Digest, err error) { h.defaults() if digest, err = h.hash(password); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) } return digest, nil } func (h *Hasher) hash(password string) (hashed algorithm.Digest, err error) { var salt []byte if salt, err = random.Bytes(h.s); err != nil { return nil, fmt.Errorf("%w: %v", algorithm.ErrSaltReadRandomBytes, err) } return h.hashWithSalt(password, salt) } // HashWithSalt overloads the Hash method allowing the user to provide a salt. It's recommended instead to configure the // salt size and let this be a random value generated using crypto/rand. func (h *Hasher) HashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { h.defaults() if digest, err = h.hashWithSalt(password, salt); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) } return digest, nil } func (h *Hasher) hashWithSalt(passwordRaw string, salt []byte) (digest algorithm.Digest, err error) { if s := len(salt); s > SaltLengthMax || s < SaltLengthMin { 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)) } password := []byte(passwordRaw) if len(password) > PasswordInputSizeMax { 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) } d := &Digest{ variant: h.variant, t: uint32(h.t), p: uint32(h.p), m: h.m, salt: salt, } d.defaults() d.key = d.variant.KeyFunc()(password, d.salt, d.t, d.m, d.p, uint32(h.k)) return d, nil } // MustHash overloads the Hash method and panics if the error is not nil. It's recommended if you use this option to // utilize the Validate method first or handle the panic appropriately. func (h *Hasher) MustHash(password string) (hashed algorithm.Digest) { var err error if hashed, err = h.Hash(password); err != nil { panic(err) } return hashed } // Validate checks the settings/parameters for this argon2.Hasher and returns an error. func (h *Hasher) Validate() (err error) { if err = h.validate(); err != nil { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, err) } return nil } func (h *Hasher) validate() (err error) { h.defaults() mMin := uint32(h.p) * MemoryMinParallelismMultiplier if h.m < mMin || h.m > MemoryMax { return fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "m", mMin, " (p * 8)", MemoryMax, h.m) } return nil } func (h *Hasher) defaults() { if h.d { return } h.d = true if h.k < KeyLengthMin { h.s = KeyLengthDefault } if h.s < SaltLengthMin { h.s = algorithm.SaltLengthDefault } } crypt-0.4.7/algorithm/argon2/opts.go000066400000000000000000000155121510400225400173520ustar00rootroot00000000000000package argon2 import ( "fmt" "github.com/go-crypt/crypt/algorithm" ) // Opt describes the functional option pattern for the argon2.Hasher. type Opt func(h *Hasher) (err error) // WithVariant is used to configure the argon2.Variant of the resulting argon2.Digest. // Default is argon2.VariantID. func WithVariant(variant Variant) Opt { return func(h *Hasher) (err error) { switch variant { case VariantNone, VariantI, VariantID, VariantD: h.variant = variant return nil default: return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant '%d' is invalid", algorithm.ErrParameterInvalid, variant)) } } } // WithVariantName uses the variant name or identifier to configure the argon2.Variant of the resulting argon2.Digest. // Default is argon2.VariantID. func WithVariantName(identifier string) Opt { return func(h *Hasher) (err error) { if identifier == "" { return nil } variant := NewVariant(identifier) if variant == VariantNone { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant identifier '%s' is invalid", algorithm.ErrParameterInvalid, identifier)) } h.variant = variant return nil } } // WithVariantI satisfies the argon2.Opt type and sets the variant as argon2.VariantI. func WithVariantI() Opt { return func(h *Hasher) error { h.variant = VariantI return nil } } // WithVariantID satisfies the argon2.Opt type and sets the variant as argon2.VariantID. func WithVariantID() Opt { return func(h *Hasher) error { h.variant = VariantID return nil } } // WithVariantD satisfies the argon2.Opt type and sets the variant as argon2.VariantD. func WithVariantD() Opt { return func(h *Hasher) error { h.variant = VariantD return nil } } // WithP satisfies the argon2.Opt type for the argon2.Hasher and sets input 'p' known as the degree of parallelism. // // Degree of parallelism p determines how many independent (but synchronizing) computational chains (lanes) can be run. // It MUST be an integer value from 1 to 2^(24)-1. // // Minimum is 1, Maximum is 16777215. Default is 4. // // RFC9106 section 3.1 "Argon2 Inputs and Outputs" https://www.rfc-editor.org/rfc/rfc9106.html#name-argon2-inputs-and-outputs. func WithP(p int) Opt { return func(h *Hasher) (err error) { if p < ParallelismMin || p > ParallelismMax { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "parallelism", ParallelismMin, "", ParallelismMax, p)) } h.p = p return nil } } // WithParallelism is an alias for WithP. func WithParallelism(p int) Opt { return WithP(p) } // WithM satisfies the argon2.Opt type for the argon2.Hasher and sets input 'm' known as the memory size. // // 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 // is m rounded down to the nearest multiple of 4*p. // // Minimum is 8, Maximum is 4294967295. Default is 2097152. // // RFC9106 section 3.1 "Argon2 Inputs and Outputs" https://www.rfc-editor.org/rfc/rfc9106.html#name-argon2-inputs-and-outputs. func WithM(m uint32) Opt { return func(h *Hasher) (err error) { if m < MemoryMin || m > MemoryMax { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "memory", MemoryMin, "", MemoryMax, m)) } h.m = m return nil } } // WithMemoryInKiB is an alias for WithM. func WithMemoryInKiB(m uint32) Opt { return WithM(m) } // WithT satisfies the argon2.Opt type for the argon2.Hasher and sets input 't' known as the number of passes. // // 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. // // Minimum is 1, Maximum is 2147483647. Default is 1. // // RFC9106 section 3.1 "Argon2 Inputs and Outputs" https://www.rfc-editor.org/rfc/rfc9106.html#name-argon2-inputs-and-outputs. func WithT(t int) Opt { return func(h *Hasher) (err error) { if t < IterationsMin || t > IterationsMax { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "t", IterationsMin, "", IterationsMax, t)) } h.t = t return nil } } // WithIterations is an alias for WithT. func WithIterations(t int) Opt { return WithT(t) } // WithK satisfies the argon2.Opt type for the argon2.Hasher and sets input 'T' known as the tag length. // // 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. // // Minimum is 4, Maximum is 2147483647. Default is 32. // // RFC9106 section 3.1 "Argon2 Inputs and Outputs" https://www.rfc-editor.org/rfc/rfc9106.html#name-argon2-inputs-and-outputs. func WithK(k int) Opt { return func(h *Hasher) (err error) { if k < KeyLengthMin || k > KeyLengthMax { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "k", KeyLengthMin, "", KeyLengthMax, k)) } h.k = k return nil } } // WithTagLength is an alias for WithK. func WithTagLength(k int) Opt { return WithK(k) } // WithKeyLength is an alias for WithK. func WithKeyLength(k int) Opt { return WithK(k) } // WithS satisfies the argon2.Opt type for the argon2.Hasher and sets the length of input 'S' known as the salt length. // // Nonce S, which is a salt for password hashing applications. It MUST have a length not greater than 2^(32)-1 bytes. // 16 bytes is RECOMMENDED for password hashing. The salt SHOULD be unique for each password. // // Minimum is 1, Maximum is 2147483647. Default is 16. // // RFC9106 section 3.1 "Argon2 Inputs and Outputs" https://www.rfc-editor.org/rfc/rfc9106.html#name-argon2-inputs-and-outputs. func WithS(s int) Opt { return func(h *Hasher) (err error) { if s < SaltLengthMin || s > SaltLengthMax { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "s", SaltLengthMin, "", SaltLengthMax, s)) } h.s = s return nil } } // WithSaltLength is an alias for WithS. func WithSaltLength(s int) Opt { return WithS(s) } // WithProfileRFC9106Recommended is the recommended standard RFC9106 profile. // // RFC9106 section 4.0 "Parameter Choice" https://www.rfc-editor.org/rfc/rfc9106.html#name-parameter-choice func WithProfileRFC9106Recommended() Opt { return func(h *Hasher) (err error) { ProfileRFC9106Recommended.Hasher().Merge(h) return nil } } // WithProfileRFC9106LowMemory is the recommended low memory RFC9106 profile. // // RFC9106 section 4.0 "Parameter Choice" https://www.rfc-editor.org/rfc/rfc9106.html#name-parameter-choice func WithProfileRFC9106LowMemory() Opt { return func(h *Hasher) (err error) { ProfileRFC9106LowMemory.Hasher().Merge(h) return nil } } crypt-0.4.7/algorithm/argon2/profile.go000066400000000000000000000015321510400225400200220ustar00rootroot00000000000000package argon2 import ( "github.com/go-crypt/crypt/algorithm" ) // Profile represents a hashing profile for Argon2Hash. type Profile int const ( // ProfileRFC9106LowMemory is the RFC9106 low memory profile. ProfileRFC9106LowMemory Profile = iota // ProfileRFC9106Recommended is the RFC9106 recommended profile. ProfileRFC9106Recommended ) // Hasher returns the argon2.Profile parameters as an argon2.Hasher. func (p Profile) Hasher() *Hasher { switch p { case ProfileRFC9106LowMemory: return &Hasher{variant: VariantID, t: 3, p: 4, m: 64 * 1024, k: KeyLengthDefault, s: algorithm.SaltLengthDefault} case ProfileRFC9106Recommended: return &Hasher{variant: VariantID, t: IterationsDefault, p: ParallelismDefault, m: MemoryDefault, k: KeyLengthDefault, s: algorithm.SaltLengthDefault} default: return ProfileRFC9106Recommended.Hasher() } } crypt-0.4.7/algorithm/argon2/variant.go000066400000000000000000000027051510400225400200310ustar00rootroot00000000000000package argon2 import ( "github.com/go-crypt/x/argon2" ) // NewVariant converts an identifier string to a argon2.Variant. func NewVariant(identifier string) (variant Variant) { switch identifier { case AlgIdentifierVariantID: return VariantID case AlgIdentifierVariantI: return VariantI case AlgIdentifierVariantD: return VariantD default: return VariantNone } } // Variant is a variant of the argon2.Digest. type Variant int const ( // VariantNone is a variant of the argon2.Digest which is unknown. VariantNone Variant = iota // VariantD is the argon2d variant of the argon2.Digest. VariantD // VariantI is the argon2i variant of the argon2.Digest. VariantI // VariantID is the argon2id variant of the argon2.Digest. VariantID ) // String implements the fmt.Stringer returning a string representation of the argon2.Variant. func (v Variant) String() string { return v.Prefix() } // Prefix returns the argon2.Variant prefix identifier. func (v Variant) Prefix() (prefix string) { switch v { case VariantID: return AlgIdentifierVariantID case VariantI: return AlgIdentifierVariantI case VariantD: return AlgIdentifierVariantD default: return } } // KeyFunc returns the argon2.KeyFunc key derivation function of this argon2.Variant. func (v Variant) KeyFunc() argon2.KeyFunc { switch v { case VariantID: return argon2.IDKey case VariantI: return argon2.IKey case VariantD: return argon2.DKey default: return nil } } crypt-0.4.7/algorithm/bcrypt/000077500000000000000000000000001510400225400161455ustar00rootroot00000000000000crypt-0.4.7/algorithm/bcrypt/const.go000066400000000000000000000031541510400225400176250ustar00rootroot00000000000000package bcrypt import ( "github.com/go-crypt/crypt/algorithm" ) const ( // EncodingFmt is the encoding format for this algorithm. EncodingFmt = "$%s$%d$%s%s" // EncodingFmtSHA256 is the encoding format for the SHA256 variant of this algorithm. EncodingFmtSHA256 = "$%s$v=2,t=%s,r=%d$%s$%s" // AlgName is the name for this algorithm. AlgName = "bcrypt" // AlgIdentifier is the identifier used in this algorithm. AlgIdentifier = "2b" // AlgIdentifierVariantSHA256 is the identifier used in encoded SHA256 variant of this algorithm. AlgIdentifierVariantSHA256 = "bcrypt-sha256" // AlgIdentifierVerA is the identifier used in this algorithm (version a). AlgIdentifierVerA = "2a" // AlgIdentifierVerX is the identifier used in this algorithm (version x). AlgIdentifierVerX = "2x" // AlgIdentifierVerY is the identifier used in this algorithm (version y). AlgIdentifierVerY = "2y" // AlgIdentifierUnversioned is the identifier used in this algorithm (no version). AlgIdentifierUnversioned = "2" // VariantNameStandard is the variant name of the bcrypt.VariantStandard. VariantNameStandard = "standard" // VariantNameSHA256 is the variant name of the bcrypt.VariantSHA256. VariantNameSHA256 = algorithm.DigestSHA256 // IterationsMin is the minimum iterations accepted. IterationsMin = 10 // IterationsMax is the maximum iterations accepted. IterationsMax = 31 // IterationsDefault is the default iterations. IterationsDefault = 13 // PasswordInputSizeMax is the maximum password input size accepted. PasswordInputSizeMax = 72 variantDefault = VariantStandard ) const ( oV = "v" oT = "t" oR = "r" ) crypt-0.4.7/algorithm/bcrypt/decoder.go000066400000000000000000000132561510400225400201100ustar00rootroot00000000000000package bcrypt import ( "fmt" "strconv" "github.com/go-crypt/x/bcrypt" "github.com/go-crypt/crypt/algorithm" "github.com/go-crypt/crypt/internal/encoding" ) // RegisterDecoder the decoder with the algorithm.DecoderRegister. func RegisterDecoder(r algorithm.DecoderRegister) (err error) { if err = RegisterDecoderStandard(r); err != nil { return err } if err = RegisterDecoderSHA256(r); err != nil { return err } return nil } // RegisterDecoderStandard registers specifically the standard decoder variant with the algorithm.DecoderRegister. func RegisterDecoderStandard(r algorithm.DecoderRegister) (err error) { decodefunc := DecodeVariant(VariantStandard) if err = r.RegisterDecodeFunc(VariantStandard.Prefix(), decodefunc); err != nil { return err } if err = r.RegisterDecodeFunc(AlgIdentifierVerA, decodefunc); err != nil { return err } if err = r.RegisterDecodeFunc(AlgIdentifierVerX, decodefunc); err != nil { return err } if err = r.RegisterDecodeFunc(AlgIdentifierVerY, decodefunc); err != nil { return err } if err = r.RegisterDecodeFunc(AlgIdentifierUnversioned, decodefunc); err != nil { return err } return nil } // RegisterDecoderSHA256 registers specifically the sha256 decoder variant with the algorithm.DecoderRegister. func RegisterDecoderSHA256(r algorithm.DecoderRegister) (err error) { if err = r.RegisterDecodeFunc(VariantSHA256.Prefix(), DecodeVariant(VariantSHA256)); err != nil { return err } return nil } // Decode the encoded digest into a algorithm.Digest. func Decode(encodedDigest string) (digest algorithm.Digest, err error) { return DecodeVariant(VariantNone)(encodedDigest) } // DecodeVariant the encoded digest into a algorithm.Digest provided it matches the provided bcrypt.Variant. If // bcrypt.VariantNone is used all variants can be decoded. func DecodeVariant(v Variant) func(encodedDigest string) (digest algorithm.Digest, err error) { return func(encodedDigest string) (digest algorithm.Digest, err error) { var ( parts []string variant Variant ) if variant, parts, err = decoderParts(encodedDigest); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) } if v != VariantNone && v != variant { 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())) } if digest, err = decode(variant, parts); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) } return digest, nil } } func decoderParts(encodedDigest string) (variant Variant, parts []string, err error) { parts = encoding.Split(encodedDigest, -1) if len(parts) < 4 { return VariantNone, nil, algorithm.ErrEncodedHashInvalidFormat } variant = NewVariant(parts[1]) if variant == VariantNone { return variant, nil, fmt.Errorf("%w: identifier '%s' is not an encoded %s digest", algorithm.ErrEncodedHashInvalidIdentifier, parts[1], AlgName) } return variant, parts[2:], nil } func decode(variant Variant, parts []string) (digest algorithm.Digest, err error) { countParts := len(parts) var ( salt, key []byte ) decoded := &Digest{ variant: variant, } switch decoded.variant { case VariantStandard: if countParts != 2 { return nil, algorithm.ErrEncodedHashInvalidFormat } if decoded.iterations, err = strconv.Atoi(parts[0]); err != nil { return nil, fmt.Errorf("%w: iterations could not be parsed: %v", algorithm.ErrEncodedHashInvalidOptionValue, err) } switch n, i := len(parts[1]), bcrypt.EncodedSaltSize+bcrypt.EncodedHashSize; n { case i: break case 0: return nil, fmt.Errorf("%w: key is expected to be %d bytes but it was empty", algorithm.ErrEncodedHashKeyEncoding, i) default: return nil, fmt.Errorf("%w: key is expected to be %d bytes but it has %d bytes", algorithm.ErrEncodedHashKeyEncoding, i, n) } salt, key = bcrypt.DecodeSecret([]byte(parts[1])) case VariantSHA256: if countParts != 3 { return nil, algorithm.ErrEncodedHashInvalidFormat } salt, key = []byte(parts[1]), []byte(parts[2]) switch n := len(salt); n { case bcrypt.EncodedSaltSize: break case 0: return nil, fmt.Errorf("%w: salt is expected to be %d bytes but it was empty", algorithm.ErrEncodedHashSaltEncoding, bcrypt.EncodedSaltSize) default: return nil, fmt.Errorf("%w: salt is expected to be %d bytes but it has %d bytes", algorithm.ErrEncodedHashSaltEncoding, bcrypt.EncodedSaltSize, n) } switch n := len(key); n { case bcrypt.EncodedHashSize: break case 0: return nil, fmt.Errorf("%w: key is expected to be %d bytes but it was empty", algorithm.ErrEncodedHashKeyEncoding, bcrypt.EncodedHashSize) default: return nil, fmt.Errorf("%w: key is expected to be %d bytes but it has %d bytes", algorithm.ErrEncodedHashKeyEncoding, bcrypt.EncodedHashSize, n) } var params []encoding.Parameter if params, err = encoding.DecodeParameterStr(parts[0]); err != nil { return nil, err } for _, param := range params { switch param.Key { case oV, oT: break case oR: decoded.iterations, err = param.Int() default: return nil, fmt.Errorf("%w: option '%s' with value '%s' is unknown", algorithm.ErrEncodedHashInvalidOptionKey, param.Key, param.Value) } if err != nil { return nil, fmt.Errorf("%w: option '%s' has invalid value '%s': %v", algorithm.ErrEncodedHashInvalidOptionValue, param.Key, param.Value, err) } } } if decoded.salt, err = bcrypt.Base64Decode(salt); err != nil { return nil, fmt.Errorf("%w: %v", algorithm.ErrEncodedHashSaltEncoding, err) } if len(key) == 0 { return nil, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrEncodedHashKeyEncoding) } decoded.key = key return decoded, nil } crypt-0.4.7/algorithm/bcrypt/digest.go000066400000000000000000000041001510400225400177460ustar00rootroot00000000000000package bcrypt import ( "crypto/subtle" "fmt" "github.com/go-crypt/x/bcrypt" "github.com/go-crypt/crypt/algorithm" ) // Digest is a digest which handles bcrypt hashes. type Digest struct { variant Variant iterations int salt, key []byte } // Match returns true if the string password matches the current bcrypt.Digest. func (d *Digest) Match(password string) (match bool) { return d.MatchBytes([]byte(password)) } // MatchBytes returns true if the []byte passwordBytes matches the current bcrypt.Digest. func (d *Digest) MatchBytes(passwordBytes []byte) (match bool) { match, _ = d.MatchBytesAdvanced(passwordBytes) return match } // MatchAdvanced is the same as Match except if there is an error it returns that as well. func (d *Digest) MatchAdvanced(password string) (match bool, err error) { return d.MatchBytesAdvanced([]byte(password)) } // MatchBytesAdvanced is the same as MatchBytes except if there is an error it returns that as well. func (d *Digest) MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) { if len(d.key) == 0 { return false, fmt.Errorf(algorithm.ErrFmtDigestMatch, AlgName, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrPasswordInvalid)) } input := d.variant.EncodeInput(passwordBytes, d.salt) var key []byte if key, err = bcrypt.Key(input, d.salt, d.iterations); err != nil { return false, fmt.Errorf(algorithm.ErrFmtDigestMatch, AlgName, fmt.Errorf("%w: %v", algorithm.ErrKeyDerivation, err)) } return subtle.ConstantTimeCompare(d.key, key) == 1, nil } // Encode returns the encoded form of this bcrypt.Digest. func (d *Digest) Encode() string { return d.variant.Encode(d.iterations, AlgIdentifier, bcrypt.Base64Encode(d.salt), d.key) } // String returns the storable format of the bcrypt.Digest encoded hash. func (d *Digest) String() string { return d.Encode() } func (d *Digest) defaults() { switch d.variant { case VariantNone: d.variant = VariantStandard case VariantStandard, VariantSHA256: break default: d.variant = variantDefault } if d.iterations < IterationsMin { d.iterations = IterationsDefault } } crypt-0.4.7/algorithm/bcrypt/doc.go000066400000000000000000000003571510400225400172460ustar00rootroot00000000000000// Package bcrypt provides helpful abstractions for an implementation of bcrypt and implements // github.com/go-crypt/crypt interfaces. // // This implementation is loaded by crypt.NewDefaultDecoder and crypt.NewDecoderAll. package bcrypt crypt-0.4.7/algorithm/bcrypt/hasher.go000066400000000000000000000074221510400225400177530ustar00rootroot00000000000000package bcrypt import ( "fmt" "github.com/go-crypt/x/bcrypt" "github.com/go-crypt/crypt/algorithm" "github.com/go-crypt/crypt/internal/random" ) // New returns a new bcrypt.Hasher with the provided functional options applied. func New(opts ...Opt) (hasher *Hasher, err error) { hasher = &Hasher{} if err = hasher.WithOptions(opts...); err != nil { return nil, err } if err = hasher.Validate(); err != nil { return nil, err } return hasher, nil } // NewSHA256 returns a new bcrypt.Hasher with the provided functional options applied as well as the bcrypt.VariantSHA256 // applied via the bcrypt.WithVariant bcrypt.Opt. func NewSHA256(opts ...Opt) (hasher *Hasher, err error) { if hasher, err = New(opts...); err != nil { return nil, err } if err = hasher.WithOptions(WithVariant(VariantSHA256)); err != nil { return nil, err } return hasher, nil } // Hasher is a crypt.Hash for bcrypt which can be initialized via bcrypt.New using a functional options pattern. type Hasher struct { variant Variant iterations int } // WithOptions applies the provided functional options provided as an bcrypt.Opt to the bcrypt.Hasher. func (h *Hasher) WithOptions(opts ...Opt) (err error) { for _, opt := range opts { if err = opt(h); err != nil { return err } } return nil } // Hash performs the hashing operation and returns either an algorithm.Digest or an error. func (h *Hasher) Hash(password string) (digest algorithm.Digest, err error) { if digest, err = h.hash(password); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) } return digest, nil } func (h *Hasher) hash(password string) (digest algorithm.Digest, err error) { var salt []byte if salt, err = random.Bytes(algorithm.SaltLengthDefault); err != nil { return nil, fmt.Errorf("%w: %v", algorithm.ErrSaltReadRandomBytes, err) } return h.hashWithSalt(password, salt) } // HashWithSalt overloads the Hash method allowing the user to provide a salt. It's recommended instead to configure the // salt size and let this be a random value generated using crypto/rand. func (h *Hasher) HashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { if digest, err = h.hashWithSalt(password, salt); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) } return digest, nil } func (h *Hasher) hashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { if len(salt) != algorithm.SaltLengthDefault { return nil, fmt.Errorf("%w: salt size must be 16 bytes but it's %d bytes", algorithm.ErrSaltInvalid, len(salt)) } d := &Digest{ variant: h.variant, iterations: h.iterations, salt: salt, } d.defaults() passwordMaxLen := d.variant.PasswordMaxLength() if passwordMaxLen != -1 && len(password) > passwordMaxLen { return nil, fmt.Errorf("%w: password must be %d bytes or less but it's %d bytes", algorithm.ErrPasswordInvalid, passwordMaxLen, len(password)) } if d.key, err = bcrypt.Key(d.variant.EncodeInput([]byte(password), salt), salt, d.iterations); err != nil { return nil, fmt.Errorf("%w: %v", algorithm.ErrKeyDerivation, err) } return d, nil } // MustHash overloads the Hash method and panics if the error is not nil. It's recommended if you use this option to // utilize the Validate method first or handle the panic appropriately. func (h *Hasher) MustHash(password string) (digest algorithm.Digest) { var err error if digest, err = h.Hash(password); err != nil { panic(err) } return digest } // Validate checks the settings/parameters for this bcrypt.Hasher and returns an error. func (h *Hasher) Validate() (err error) { if err = h.validate(); err != nil { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, err) } return nil } func (h *Hasher) validate() (err error) { return nil } crypt-0.4.7/algorithm/bcrypt/opts.go000066400000000000000000000035231510400225400174640ustar00rootroot00000000000000package bcrypt import ( "fmt" "github.com/go-crypt/crypt/algorithm" ) // Opt describes the functional option pattern for the bcrypt.Hasher. type Opt func(h *Hasher) (err error) // WithVariant is used to configure the bcrypt.Variant of the resulting bcrypt.Digest. // Default is bcrypt.VariantStandard. func WithVariant(variant Variant) Opt { return func(h *Hasher) (err error) { switch variant { case VariantNone, VariantStandard, VariantSHA256: h.variant = variant return nil default: return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant '%d' is invalid", algorithm.ErrParameterInvalid, variant)) } } } // WithVariantName uses the variant name or identifier to configure the bcrypt.Variant of the resulting bcrypt.Digest. // Default is bcrypt.VariantStandard. func WithVariantName(identifier string) Opt { return func(h *Hasher) (err error) { if identifier == "" { return nil } variant := NewVariant(identifier) if variant == VariantNone { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant identifier '%s' is invalid", algorithm.ErrParameterInvalid, identifier)) } h.variant = variant return nil } } // WithIterations sets the iterations parameter of the resulting bcrypt.Digest. // Minimum is 10, Maximum is 31. Default is 12. func WithIterations(iterations int) Opt { return func(h *Hasher) (err error) { if iterations < IterationsMin || iterations > IterationsMax { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "iterations", IterationsMin, "", IterationsMax, iterations)) } h.iterations = iterations return nil } } // WithCost is an alias for bcrypt.WithIterations. func WithCost(iterations int) Opt { return WithIterations(iterations) } crypt-0.4.7/algorithm/bcrypt/variant.go000066400000000000000000000045151510400225400201450ustar00rootroot00000000000000package bcrypt import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "fmt" "github.com/go-crypt/x/bcrypt" ) // NewVariant converts an identifier string to a bcrypt.Variant. func NewVariant(identifier string) (variant Variant) { switch identifier { case AlgIdentifier, AlgIdentifierVerA, AlgIdentifierVerX, AlgIdentifierVerY, "", VariantNameStandard, "common": return VariantStandard case AlgIdentifierVariantSHA256, VariantNameSHA256: return VariantSHA256 default: return VariantNone } } // Variant is a variant of the bcrypt.Digest. type Variant int const ( // VariantNone is a variant of the bcrypt.Digest which is unknown. VariantNone Variant = iota // VariantStandard is the standard variant of bcrypt.Digest. VariantStandard // VariantSHA256 is the variant of bcrypt.Digest which hashes the password with HMAC-SHA256. VariantSHA256 ) // String implements the fmt.Stringer returning a string representation of the bcrypt.Variant. func (v Variant) String() (name string) { switch v { case VariantStandard: return VariantNameStandard case VariantSHA256: return VariantNameSHA256 default: return } } // Prefix returns the bcrypt.Variant prefix identifier. func (v Variant) Prefix() (prefix string) { switch v { case VariantStandard: return AlgIdentifier case VariantSHA256: return AlgIdentifierVariantSHA256 default: return } } // PasswordMaxLength returns -1 if the variant has no max length, otherwise returns the maximum password length. func (v Variant) PasswordMaxLength() int { switch v { case VariantSHA256: return -1 default: return PasswordInputSizeMax } } // Encode formats the variant encoded bcrypt.Digest. func (v Variant) Encode(cost int, version string, salt, key []byte) (f string) { switch v { case VariantStandard: return fmt.Sprintf(EncodingFmt, version, cost, salt, key) case VariantSHA256: return fmt.Sprintf(EncodingFmtSHA256, v.Prefix(), version, cost, salt, key) default: return } } // EncodeInput returns the appropriate algorithm input. func (v Variant) EncodeInput(src, salt []byte) (dst []byte) { switch v { case VariantSHA256: h := hmac.New(sha256.New, bcrypt.Base64Encode(salt)) h.Write(src) digest := h.Sum(nil) dst = make([]byte, base64.StdEncoding.EncodedLen(len(digest))) base64.StdEncoding.Encode(dst, digest) return dst default: return src } } crypt-0.4.7/algorithm/const.go000066400000000000000000000011211510400225400163120ustar00rootroot00000000000000package algorithm const ( // DigestSHA1 is te name for SHA1 digests. DigestSHA1 = "sha1" // DigestSHA224 is te name for SHA224 digests. DigestSHA224 = "sha224" // DigestSHA256 is te name for SHA256 digests. DigestSHA256 = "sha256" // DigestSHA384 is te name for SHA384 digests. DigestSHA384 = "sha384" // DigestSHA512 is te name for SHA512 digests. DigestSHA512 = "sha512" ) const ( // SaltLengthDefault is the default salt size for most implementations. SaltLengthDefault = 16 // KeyLengthDefault is the default key size for most implementations. KeyLengthDefault = 32 ) crypt-0.4.7/algorithm/doc.go000066400000000000000000000002201510400225400157300ustar00rootroot00000000000000// Package algorithm is a package which contains the individual algorithms and interfaces related to their // implementation. package algorithm crypt-0.4.7/algorithm/errors.go000066400000000000000000000062541510400225400165140ustar00rootroot00000000000000package algorithm import ( "errors" ) var ( // ErrEncodedHashInvalidFormat is an error returned when an encoded hash has an invalid format. ErrEncodedHashInvalidFormat = errors.New("provided encoded hash has an invalid format") // ErrEncodedHashInvalidIdentifier is an error returned when an encoded hash has an invalid identifier for the // given digest. ErrEncodedHashInvalidIdentifier = errors.New("provided encoded hash has an invalid identifier") // ErrEncodedHashInvalidVersion is an error returned when an encoded hash has an unsupported or otherwise invalid // version. ErrEncodedHashInvalidVersion = errors.New("provided encoded hash has an invalid version") // ErrEncodedHashInvalidOption is an error returned when an encoded hash has an unsupported or otherwise invalid // option in the option field. ErrEncodedHashInvalidOption = errors.New("provided encoded hash has an invalid option") // ErrEncodedHashInvalidOptionKey is an error returned when an encoded hash has an unknown or otherwise invalid // option key in the option field. ErrEncodedHashInvalidOptionKey = errors.New("provided encoded hash has an invalid option key") // ErrEncodedHashInvalidOptionValue is an error returned when an encoded hash has an unknown or otherwise invalid // option value in the option field. ErrEncodedHashInvalidOptionValue = errors.New("provided encoded hash has an invalid option value") // ErrEncodedHashKeyEncoding is an error returned when an encoded hash has a salt with an invalid or unsupported // encoding. ErrEncodedHashKeyEncoding = errors.New("provided encoded hash has a key value that can't be decoded") // ErrEncodedHashSaltEncoding is an error returned when an encoded hash has a salt with an invalid or unsupported // encoding. ErrEncodedHashSaltEncoding = errors.New("provided encoded hash has a salt value that can't be decoded") // ErrKeyDerivation is returned when a Key function returns an error. ErrKeyDerivation = errors.New("failed to derive the key with the provided parameters") // ErrSaltEncoding is an error returned when a salt has an invalid or unsupported encoding. ErrSaltEncoding = errors.New("provided salt has a value that can't be decoded") // ErrPasswordInvalid is an error returned when a password has an invalid or unsupported properties. It is NOT // returned on password mismatches. ErrPasswordInvalid = errors.New("password is invalid") // ErrSaltInvalid is an error returned when a salt has an invalid or unsupported properties. ErrSaltInvalid = errors.New("salt is invalid") // ErrSaltReadRandomBytes is an error returned when generating the random bytes for salt resulted in an error. ErrSaltReadRandomBytes = errors.New("could not read random bytes for salt") // ErrParameterInvalid is an error returned when a parameter has an invalid value. ErrParameterInvalid = errors.New("parameter is invalid") ) // Error format strings. const ( ErrFmtInvalidIntParameter = "%w: parameter '%s' must be between %d%s and %d but is set to '%d'" ErrFmtDigestDecode = "%s decode error: %w" ErrFmtDigestMatch = "%s match error: %w" ErrFmtHasherHash = "%s hashing error: %w" ErrFmtHasherValidation = "%s validation error: %w" ) crypt-0.4.7/algorithm/md5crypt/000077500000000000000000000000001510400225400164115ustar00rootroot00000000000000crypt-0.4.7/algorithm/md5crypt/const.go000066400000000000000000000031041510400225400200640ustar00rootroot00000000000000package md5crypt import ( "math" ) const ( // EncodingFmt is the encoding format for this algorithm. EncodingFmt = "$1$%s$%s" // EncodingFmtSun is the encoding format for this algorithm when using md5crypt.VariantSun. EncodingFmtSun = "$md5$%s$$%s" // EncodingFmtSunIterations is the encoding format for this algorithm when using md5crypt.VariantSun and iterations more than 0. EncodingFmtSunIterations = "$md5,iterations=%d$%s$$%s" // AlgName is the name for this algorithm. AlgName = "md5crypt" // AlgIdentifier is the identifier used in this algorithm. AlgIdentifier = "1" // AlgIdentifierVariantSun is the identifier used in this algorithm when using md5crypt.VariantSun. AlgIdentifierVariantSun = "md5" // VariantNameStandard is the md5crypt.Variant name for md5crypt.VariantStandard. VariantNameStandard = "standard" // VariantNameSun is the md5crypt.Variant name for md5crypt.VariantSun. VariantNameSun = "sun" // SaltLengthMin is the minimum salt size accepted. SaltLengthMin = 1 // SaltLengthMax is the maximum salt size accepted. SaltLengthMax = 8 // SaltLengthDefault is the default salt size. SaltLengthDefault = SaltLengthMax // SaltCharSet are the valid characters for the salt. SaltCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./" // IterationsMin is the minimum iterations accepted. IterationsMin = 0 // IterationsMax is the maximum iterations accepted. IterationsMax uint32 = math.MaxUint32 // IterationsDefault is the default iterations. IterationsDefault = 34000 ) const ( variantDefault = VariantStandard ) crypt-0.4.7/algorithm/md5crypt/decoder.go000066400000000000000000000104551510400225400203520ustar00rootroot00000000000000package md5crypt import ( "fmt" "strconv" "strings" "github.com/go-crypt/crypt/algorithm" "github.com/go-crypt/crypt/internal/encoding" ) // RegisterDecoder the decoder with the algorithm.DecoderRegister. func RegisterDecoder(r algorithm.DecoderRegister) (err error) { if err = RegisterDecoderCommon(r); err != nil { return err } if err = RegisterDecoderSun(r); err != nil { return err } return nil } // RegisterDecoderCommon registers specifically the common decoder variant with the algorithm.DecoderRegister. func RegisterDecoderCommon(r algorithm.DecoderRegister) (err error) { if err = r.RegisterDecodeFunc(VariantStandard.Prefix(), DecodeVariant(VariantStandard)); err != nil { return err } return nil } // RegisterDecoderSun registers specifically the sun decoder variant with the algorithm.DecoderRegister. func RegisterDecoderSun(r algorithm.DecoderRegister) (err error) { if err = r.RegisterDecodeFunc(VariantSun.Prefix(), DecodeVariant(VariantSun)); err != nil { return err } if err = r.RegisterDecodePrefix("$md5,", VariantSun.Prefix()); err != nil { return err } return nil } // Decode the encoded digest into a algorithm.Digest. func Decode(encodedDigest string) (digest algorithm.Digest, err error) { return DecodeVariant(VariantNone)(encodedDigest) } // DecodeVariant the encoded digest into a algorithm.Digest provided it matches the provided Variant. If VariantNone is // used all variants can be decoded. func DecodeVariant(v Variant) func(encodedDigest string) (digest algorithm.Digest, err error) { return func(encodedDigest string) (digest algorithm.Digest, err error) { var ( parts []string variant Variant ) if variant, parts, err = decoderParts(encodedDigest); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) } if v != VariantNone && v != variant { 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())) } if digest, err = decode(variant, parts); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) } return digest, nil } } func decoderParts(encodedDigest string) (variant Variant, parts []string, err error) { partsTemp := encoding.Split(encodedDigest, -1) p := len(partsTemp) if p != 4 && p != 5 { return VariantNone, nil, algorithm.ErrEncodedHashInvalidFormat } switch partsTemp[1] { case AlgIdentifier: if p != 4 { return VariantNone, nil, algorithm.ErrEncodedHashInvalidFormat } default: if p != 5 { return VariantNone, nil, algorithm.ErrEncodedHashInvalidFormat } } if strings.HasPrefix(partsTemp[1], "md5,") { parts = append([]string{strings.SplitN(partsTemp[1], ",", 2)[1]}, partsTemp[2:]...) variant = VariantSun } else { switch variant = NewVariant(partsTemp[1]); variant { case VariantNone: return variant, nil, fmt.Errorf("%w: identifier '%s' is not an encoded %s digest", algorithm.ErrEncodedHashInvalidIdentifier, partsTemp[1], AlgName) default: parts = append([]string{""}, partsTemp[2:]...) } } return variant, parts, nil } func decode(variant Variant, parts []string) (digest algorithm.Digest, err error) { decoded := &Digest{ variant: variant, } var params []encoding.Parameter if parts[0] != "" { if variant != VariantSun { 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()) } if params, err = encoding.DecodeParameterStr(parts[0]); err != nil { return nil, err } for _, param := range params { switch param.Key { case "rounds": var value uint64 if value, err = strconv.ParseUint(param.Value, 10, 32); err != nil { return nil, fmt.Errorf("%w: option '%s' has invalid value '%s': %v", algorithm.ErrEncodedHashInvalidOptionValue, param.Key, param.Value, err) } decoded.iterations = uint32(value) default: return nil, fmt.Errorf("%w: option '%s' with value '%s' is unknown", algorithm.ErrEncodedHashInvalidOptionKey, param.Key, param.Value) } } } switch variant { case VariantSun: decoded.salt, decoded.key = []byte(parts[1]), []byte(parts[3]) default: decoded.salt, decoded.key = []byte(parts[1]), []byte(parts[2]) } return decoded, nil } crypt-0.4.7/algorithm/md5crypt/digest.go000066400000000000000000000042701510400225400202220ustar00rootroot00000000000000package md5crypt import ( "crypto/subtle" "fmt" "github.com/go-crypt/x/crypt" "github.com/go-crypt/crypt/algorithm" ) // Digest is a algorithm.Digest which handles md5crypt hashes. type Digest struct { variant Variant iterations uint32 salt, key []byte } // Match returns true if the string password matches the current md5crypt.Digest. func (d *Digest) Match(password string) (match bool) { return d.MatchBytes([]byte(password)) } // MatchBytes returns true if the []byte passwordBytes matches the current md5crypt.Digest. func (d *Digest) MatchBytes(passwordBytes []byte) (match bool) { match, _ = d.MatchBytesAdvanced(passwordBytes) return match } // MatchAdvanced is the same as Match except if there is an error it returns that as well. func (d *Digest) MatchAdvanced(password string) (match bool, err error) { return d.MatchBytesAdvanced([]byte(password)) } // MatchBytesAdvanced is the same as MatchBytes except if there is an error it returns that as well. func (d *Digest) MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) { if len(d.key) == 0 { return false, fmt.Errorf(algorithm.ErrFmtDigestMatch, AlgName, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrPasswordInvalid)) } switch d.variant { case VariantSun: return subtle.ConstantTimeCompare(d.key, crypt.KeyMD5CryptSun(passwordBytes, d.salt, d.iterations)) == 1, nil default: return subtle.ConstantTimeCompare(d.key, crypt.KeyMD5Crypt(passwordBytes, d.salt)) == 1, nil } } // Encode returns the encoded form of this md5crypt.Digest. func (d *Digest) Encode() string { switch { case d.variant == VariantSun && d.iterations > 0: return fmt.Sprintf(EncodingFmtSunIterations, d.iterations, d.salt, d.key, ) case d.variant == VariantSun: return fmt.Sprintf(EncodingFmtSun, d.salt, d.key, ) default: return fmt.Sprintf(EncodingFmt, d.salt, d.key, ) } } // String returns the storable format of the md5crypt.Digest encoded hash. func (d *Digest) String() string { return d.Encode() } func (d *Digest) defaults() { switch d.variant { case VariantStandard, VariantSun: break default: d.variant = variantDefault } if d.iterations < IterationsMin { d.iterations = IterationsDefault } } crypt-0.4.7/algorithm/md5crypt/doc.go000066400000000000000000000003341510400225400175050ustar00rootroot00000000000000// Package md5crypt provides helpful abstractions for an implementation of crypt (MD5) and implements // github.com/go-crypt/crypt interfaces. // // This implementation is loaded by crypt.NewDecoderAll. package md5crypt crypt-0.4.7/algorithm/md5crypt/hasher.go000066400000000000000000000063541510400225400202220ustar00rootroot00000000000000package md5crypt import ( "fmt" "github.com/go-crypt/x/crypt" "github.com/go-crypt/crypt/algorithm" "github.com/go-crypt/crypt/internal/random" ) // New returns a *md5crypt.Hasher with the additional opts applied if any. func New(opts ...Opt) (hasher *Hasher, err error) { hasher = &Hasher{} if err = hasher.WithOptions(opts...); err != nil { return nil, err } if err = hasher.Validate(); err != nil { return nil, err } return hasher, nil } // Hasher is a crypt.Hash for md5crypt which can be initialized via md5crypt.New using a functional options pattern. type Hasher struct { variant Variant iterations uint32 bytesSalt int d bool } // WithOptions applies the provided functional options provided as a md5crypt.Opt to the md5crypt.Hasher. func (h *Hasher) WithOptions(opts ...Opt) (err error) { for _, opt := range opts { if err = opt(h); err != nil { return err } } return nil } // Hash performs the hashing operation and returns either a algorithm.Digest or an error. func (h *Hasher) Hash(password string) (digest algorithm.Digest, err error) { h.defaults() if digest, err = h.hash(password); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) } return digest, nil } // MustHash overloads the Hash method and panics if the error is not nil. It's recommended if you use this option to // utilize the Validate method first or handle the panic appropriately. func (h *Hasher) MustHash(password string) (digest algorithm.Digest) { var err error if digest, err = h.Hash(password); err != nil { panic(err) } return digest } // HashWithSalt overloads the Hash method allowing the user to provide a salt. It's recommended instead to configure the // salt size and let this be a random value generated using crypto/rand. func (h *Hasher) HashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { h.defaults() if digest, err = h.hashWithSalt(password, salt); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) } return digest, nil } // Validate checks the settings/parameters for this md5crypt.Hasher and returns an error. func (h *Hasher) Validate() (err error) { h.defaults() return nil } func (h *Hasher) hash(password string) (digest algorithm.Digest, err error) { var salt []byte if salt, err = random.CharSetBytes(h.bytesSalt, SaltCharSet); err != nil { return nil, fmt.Errorf("%w: %v", algorithm.ErrSaltReadRandomBytes, err) } return h.hashWithSalt(password, salt) } func (h *Hasher) hashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { if s := len(salt); s > SaltLengthMax || s < SaltLengthMin { 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)) } d := &Digest{ variant: h.variant, iterations: h.iterations, salt: salt, } d.defaults() switch d.variant { case VariantSun: d.key = crypt.KeyMD5CryptSun([]byte(password), d.salt, d.iterations) default: d.key = crypt.KeyMD5Crypt([]byte(password), d.salt) } return d, nil } func (h *Hasher) defaults() { if h.d { return } h.d = true if h.bytesSalt < SaltLengthMin { h.bytesSalt = SaltLengthDefault } } crypt-0.4.7/algorithm/md5crypt/opts.go000066400000000000000000000047371510400225400177400ustar00rootroot00000000000000package md5crypt import ( "fmt" "github.com/go-crypt/crypt/algorithm" ) // Opt describes the functional option pattern for the md5crypt.Hasher. type Opt func(h *Hasher) (err error) // WithVariant is used to configure the md5crypt.Variant of the resulting md5crypt.Digest. // Default is md5crypt.VariantStandard. func WithVariant(variant Variant) Opt { return func(h *Hasher) (err error) { switch variant { case VariantNone: return nil case VariantStandard, VariantSun: h.variant = variant return nil default: return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant '%d' is invalid", algorithm.ErrParameterInvalid, variant)) } } } // WithVariantName uses the variant name or identifier to configure the md5crypt.Variant of the resulting md5crypt.Digest. // Default is md5crypt.VariantStandard. func WithVariantName(identifier string) Opt { return func(h *Hasher) (err error) { if identifier == "" { return nil } variant := NewVariant(identifier) if variant == VariantNone { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant identifier '%s' is invalid", algorithm.ErrParameterInvalid, identifier)) } h.variant = variant return nil } } // WithIterations sets the iterations parameter of the resulting md5crypt.Digest. Only valid for the Sun variant. This // is encoded in the hash with the 'iterations' parameter. // Minimum is 0, Maximum is 4294967295. Default is 34000. func WithIterations(iterations uint32) Opt { return func(h *Hasher) (err error) { if iterations < IterationsMin || iterations > IterationsMax { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "iterations", IterationsMin, "", IterationsMax, iterations)) } h.iterations = iterations return nil } } // WithRounds is an alias for md5crypt.WithIterations. func WithRounds(rounds uint32) Opt { return WithIterations(rounds) } // WithSaltLength adjusts the salt size (in bytes) of the resulting md5crypt.Digest. // Minimum is 1, Maximum is 8. Default is 8. func WithSaltLength(bytes int) Opt { return func(h *Hasher) (err error) { if bytes < SaltLengthMin || bytes > SaltLengthMax { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "salt length", SaltLengthMin, "", SaltLengthMax, bytes)) } h.bytesSalt = bytes return nil } } crypt-0.4.7/algorithm/md5crypt/variant.go000066400000000000000000000023151510400225400204050ustar00rootroot00000000000000package md5crypt // NewVariant converts an identifier string to a md5crypt.Variant. func NewVariant(identifier string) (variant Variant) { switch identifier { case AlgIdentifier, AlgName, VariantNameStandard, "common": return VariantStandard case AlgIdentifierVariantSun, VariantNameSun: return VariantSun default: return VariantNone } } // Variant is a variant of the md5crypt.Digest. type Variant int const ( // VariantNone is a variant of the md5crypt.Digest which is unknown. VariantNone Variant = iota // VariantStandard is a variant of the md5crypt.Digest which uses the standard md5crypt format. VariantStandard // VariantSun is a variant of the md5crypt.Digest designed at Sun. VariantSun ) // String implements the fmt.Stringer returning a string representation of the md5crypt.Variant. func (v Variant) String() (prefix string) { switch v { case VariantStandard: return VariantNameStandard case VariantSun: return VariantNameSun default: return } } // Prefix returns the md5crypt.Variant prefix identifier. func (v Variant) Prefix() (prefix string) { switch v { case VariantStandard: return AlgIdentifier case VariantSun: return AlgIdentifierVariantSun default: return } } crypt-0.4.7/algorithm/pbkdf2/000077500000000000000000000000001510400225400160125ustar00rootroot00000000000000crypt-0.4.7/algorithm/pbkdf2/const.go000066400000000000000000000033311510400225400174670ustar00rootroot00000000000000package pbkdf2 import ( "math" ) const ( // EncodingFmt is the encoding format for this algorithm. EncodingFmt = "$%s$%d$%s$%s" // AlgName is the name for this algorithm. AlgName = "pbkdf2" // AlgIdentifier is the identifier used in encoded digests for this algorithm. AlgIdentifier = AlgName // AlgIdentifierSHA1 is the identifier used in encoded SHA1 variants of this algorithm. AlgIdentifierSHA1 = "pbkdf2-sha1" // AlgIdentifierSHA224 is the identifier used in encoded SHA224 variants of this algorithm. AlgIdentifierSHA224 = "pbkdf2-sha224" // AlgIdentifierSHA256 is the identifier used in encoded SHA256 variants of this algorithm. AlgIdentifierSHA256 = "pbkdf2-sha256" // AlgIdentifierSHA384 is the identifier used in encoded SHA384 variants of this algorithm. AlgIdentifierSHA384 = "pbkdf2-sha384" // AlgIdentifierSHA512 is the identifier used in encoded SHA512 variants of this algorithm. AlgIdentifierSHA512 = "pbkdf2-sha512" // KeyLengthMax is the maximum tag size accepted. KeyLengthMax = math.MaxInt32 // SaltLengthMin is the minimum salt size accepted. SaltLengthMin = 8 // SaltLengthMax is the maximum salt size accepted. SaltLengthMax = math.MaxInt32 // IterationsMin is the minimum iterations accepted. IterationsMin = 100000 // IterationsMax is the maximum iterations accepted. IterationsMax = math.MaxInt32 // IterationsDefaultSHA1 is the default iterations for algorithms SHA1 and SHA224. IterationsDefaultSHA1 = 720000 // IterationsDefaultSHA256 is the default iterations for algorithms SHA256 and SHA384. IterationsDefaultSHA256 = 310000 // IterationsDefaultSHA512 is the default iterations for algorithms SHA512. IterationsDefaultSHA512 = 120000 variantDefault = VariantSHA256 ) crypt-0.4.7/algorithm/pbkdf2/decoder.go000066400000000000000000000111351510400225400177470ustar00rootroot00000000000000package pbkdf2 import ( "fmt" "strconv" "github.com/go-crypt/crypt/algorithm" "github.com/go-crypt/crypt/internal/encoding" ) // RegisterDecoder the decoder with the algorithm.DecoderRegister. func RegisterDecoder(r algorithm.DecoderRegister) (err error) { if err = RegisterDecoderSHA1(r); err != nil { return err } if err = RegisterDecoderSHA224(r); err != nil { return err } if err = RegisterDecoderSHA256(r); err != nil { return err } if err = RegisterDecoderSHA384(r); err != nil { return err } if err = RegisterDecoderSHA512(r); err != nil { return err } return nil } // RegisterDecoderSHA1 registers specifically the sha1 decoder variant with the algorithm.DecoderRegister. func RegisterDecoderSHA1(r algorithm.DecoderRegister) (err error) { decodefunc := DecodeVariant(VariantSHA1) if err = r.RegisterDecodeFunc(VariantSHA1.Prefix(), decodefunc); err != nil { return err } if err = r.RegisterDecodeFunc(AlgIdentifierSHA1, decodefunc); err != nil { return err } return nil } // RegisterDecoderSHA224 registers specifically the sha224 decoder variant with the algorithm.DecoderRegister. func RegisterDecoderSHA224(r algorithm.DecoderRegister) (err error) { if err = r.RegisterDecodeFunc(VariantSHA224.Prefix(), DecodeVariant(VariantSHA224)); err != nil { return err } return nil } // RegisterDecoderSHA256 registers specifically the sha256 decoder variant with the algorithm.DecoderRegister. func RegisterDecoderSHA256(r algorithm.DecoderRegister) (err error) { if err = r.RegisterDecodeFunc(VariantSHA256.Prefix(), DecodeVariant(VariantSHA256)); err != nil { return err } return nil } // RegisterDecoderSHA384 registers specifically the sha384 decoder variant with the algorithm.DecoderRegister. func RegisterDecoderSHA384(r algorithm.DecoderRegister) (err error) { if err = r.RegisterDecodeFunc(VariantSHA384.Prefix(), DecodeVariant(VariantSHA384)); err != nil { return err } return nil } // RegisterDecoderSHA512 registers specifically the sha512 decoder variant with the algorithm.DecoderRegister. func RegisterDecoderSHA512(r algorithm.DecoderRegister) (err error) { if err = r.RegisterDecodeFunc(VariantSHA512.Prefix(), DecodeVariant(VariantSHA512)); err != nil { return err } return nil } // Decode the encoded digest into a algorithm.Digest. func Decode(encodedDigest string) (digest algorithm.Digest, err error) { return DecodeVariant(VariantNone)(encodedDigest) } // DecodeVariant the encoded digest into a algorithm.Digest provided it matches the provided pbkdf2.Variant. If // pbkdf2.VariantNone is used all variants can be decoded. func DecodeVariant(v Variant) func(encodedDigest string) (digest algorithm.Digest, err error) { return func(encodedDigest string) (digest algorithm.Digest, err error) { var ( parts []string variant Variant ) if variant, parts, err = decoderParts(encodedDigest); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) } if v != VariantNone && v != variant { 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())) } if digest, err = decode(variant, parts); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) } return digest, nil } } func decoderParts(encodedDigest string) (variant Variant, parts []string, err error) { parts = encoding.Split(encodedDigest, -1) if len(parts) != 5 { return VariantNone, nil, algorithm.ErrEncodedHashInvalidFormat } variant = NewVariant(parts[1]) if variant == VariantNone { return variant, nil, fmt.Errorf("%w: identifier '%s' is not an encoded %s digest", algorithm.ErrEncodedHashInvalidIdentifier, parts[1], AlgName) } return variant, parts[2:], nil } func decode(variant Variant, parts []string) (digest algorithm.Digest, err error) { decoded := &Digest{ variant: variant, } decoded.variant = variant if decoded.iterations, err = strconv.Atoi(parts[0]); err != nil { return nil, fmt.Errorf("%w: iterations could not be parsed: %v", algorithm.ErrEncodedHashInvalidOptionValue, err) } if decoded.salt, err = encoding.Base64RawAdaptedEncoding.DecodeString(parts[1]); err != nil { return nil, fmt.Errorf("%w: %v", algorithm.ErrEncodedHashSaltEncoding, err) } if decoded.key, err = encoding.Base64RawAdaptedEncoding.DecodeString(parts[2]); err != nil { return nil, fmt.Errorf("%w: %v", algorithm.ErrEncodedHashKeyEncoding, err) } decoded.t = len(decoded.key) if decoded.t == 0 { return nil, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrEncodedHashKeyEncoding) } return decoded, nil } crypt-0.4.7/algorithm/pbkdf2/digest.go000066400000000000000000000041451510400225400176240ustar00rootroot00000000000000package pbkdf2 import ( "crypto/subtle" "fmt" "github.com/go-crypt/x/pbkdf2" "github.com/go-crypt/crypt/algorithm" "github.com/go-crypt/crypt/internal/encoding" ) // Digest is a pbkdf2.Digest which handles PBKDF2 hashes. type Digest struct { variant Variant iterations int t int salt, key []byte } // Match returns true if the string password matches the current pbkdf2.Digest. func (d *Digest) Match(password string) (match bool) { return d.MatchBytes([]byte(password)) } // MatchBytes returns true if the []byte passwordBytes matches the current pbkdf2.Digest. func (d *Digest) MatchBytes(passwordBytes []byte) (match bool) { match, _ = d.MatchBytesAdvanced(passwordBytes) return match } // MatchAdvanced is the same as Match except if there is an error it returns that as well. func (d *Digest) MatchAdvanced(password string) (match bool, err error) { return d.MatchBytesAdvanced([]byte(password)) } // MatchBytesAdvanced is the same as MatchBytes except if there is an error it returns that as well. func (d *Digest) MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) { if len(d.key) == 0 { return false, fmt.Errorf(algorithm.ErrFmtDigestMatch, AlgName, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrPasswordInvalid)) } return subtle.ConstantTimeCompare(d.key, pbkdf2.Key(passwordBytes, d.salt, d.iterations, d.t, d.variant.HashFunc())) == 1, nil } // Encode returns the encoded form of this pbkdf2.Digest. func (d *Digest) Encode() string { return fmt.Sprintf(EncodingFmt, d.variant.Prefix(), d.iterations, encoding.Base64RawAdaptedEncoding.EncodeToString(d.salt), encoding.Base64RawAdaptedEncoding.EncodeToString(d.key), ) } // String returns the storable format of the pbkdf2.Digest encoded hash. func (d *Digest) String() string { return d.Encode() } func (d *Digest) defaults() { switch d.variant { case VariantSHA1, VariantSHA224, VariantSHA256, VariantSHA384, VariantSHA512: break default: d.variant = variantDefault } if d.iterations < IterationsMin { d.iterations = d.variant.DefaultIterations() } if d.t == 0 { d.t = d.variant.HashFunc()().Size() } } crypt-0.4.7/algorithm/pbkdf2/doc.go000066400000000000000000000003571510400225400171130ustar00rootroot00000000000000// Package pbkdf2 provides helpful abstractions for an implementation of PBKDF2 and implements // github.com/go-crypt/crypt interfaces. // // This implementation is loaded by crypt.NewDefaultDecoder and crypt.NewDecoderAll. package pbkdf2 crypt-0.4.7/algorithm/pbkdf2/hasher.go000066400000000000000000000125021510400225400176130ustar00rootroot00000000000000package pbkdf2 import ( "fmt" "github.com/go-crypt/x/pbkdf2" "github.com/go-crypt/crypt/algorithm" "github.com/go-crypt/crypt/internal/random" ) // New returns a *pbkdf2.Hasher with the additional opts applied if any. func New(opts ...Opt) (hasher *Hasher, err error) { hasher = &Hasher{} if err = hasher.WithOptions(opts...); err != nil { return nil, err } if err = hasher.Validate(); err != nil { return nil, err } return hasher, nil } // NewSHA1 returns a SHA1 variant *pbkdf2.Hasher with the additional opts applied if any. func NewSHA1(opts ...Opt) (hasher *Hasher, err error) { if hasher, err = New(opts...); err != nil { return nil, err } if err = hasher.WithOptions(WithVariant(VariantSHA1)); err != nil { return nil, err } return hasher, nil } // NewSHA224 returns a SHA224 variant *pbkdf2.Hasher with the additional opts applied if any. func NewSHA224(opts ...Opt) (hasher *Hasher, err error) { if hasher, err = New(opts...); err != nil { return nil, err } if err = hasher.WithOptions(WithVariant(VariantSHA224)); err != nil { return nil, err } return hasher, nil } // NewSHA256 returns a SHA256 variant *pbkdf2.Hasher with the additional opts applied if any. func NewSHA256(opts ...Opt) (hasher *Hasher, err error) { if hasher, err = New(opts...); err != nil { return nil, err } if err = hasher.WithOptions(WithVariant(VariantSHA256)); err != nil { return nil, err } return hasher, nil } // NewSHA384 returns a SHA384 variant *pbkdf2.Hasher with the additional opts applied if any. func NewSHA384(opts ...Opt) (hasher *Hasher, err error) { if hasher, err = New(opts...); err != nil { return nil, err } if err = hasher.WithOptions(WithVariant(VariantSHA384)); err != nil { return nil, err } return hasher, nil } // NewSHA512 returns a SHA512 variant *pbkdf2.Hasher with the additional opts applied if any. func NewSHA512(opts ...Opt) (hasher *Hasher, err error) { if hasher, err = New(opts...); err != nil { return nil, err } if err = hasher.WithOptions(WithVariant(VariantSHA512)); err != nil { return nil, err } return hasher, nil } // Hasher is a crypt.Hash for PBKDF2 which can be initialized via pbkdf2.New using a functional options pattern. type Hasher struct { variant Variant iterations, bytesKey, bytesSalt int d bool } // WithOptions applies the provided functional options provided as a pbkdf2.Opt to the pbkdf2.Hasher. func (h *Hasher) WithOptions(opts ...Opt) (err error) { for _, opt := range opts { if err = opt(h); err != nil { return err } } return nil } // Hash performs the hashing operation and returns either a algorithm.Digest or an error. func (h *Hasher) Hash(password string) (digest algorithm.Digest, err error) { h.defaults() if digest, err = h.hash(password); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) } return digest, nil } func (h *Hasher) hash(password string) (digest algorithm.Digest, err error) { var salt []byte if salt, err = random.Bytes(h.bytesSalt); err != nil { return nil, fmt.Errorf("%w: %v", algorithm.ErrSaltReadRandomBytes, err) } return h.hashWithSalt(password, salt) } // HashWithSalt overloads the Hash method allowing the user to provide a salt. It's recommended instead to configure the // salt size and let this be a random value generated using crypto/rand. func (h *Hasher) HashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { h.defaults() if digest, err = h.hashWithSalt(password, salt); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) } return digest, nil } func (h *Hasher) hashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { if s := len(salt); s > SaltLengthMax || s < SaltLengthMin { 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)) } d := &Digest{ variant: h.variant, iterations: h.iterations, t: h.bytesKey, salt: salt, } d.defaults() d.key = pbkdf2.Key([]byte(password), d.salt, d.iterations, d.t, d.variant.HashFunc()) return d, nil } // MustHash overloads the Hash method and panics if the error is not nil. It's recommended if you use this option to // utilize the Validate method first or handle the panic appropriately. func (h *Hasher) MustHash(password string) (digest algorithm.Digest) { var err error if digest, err = h.Hash(password); err != nil { panic(err) } return digest } // Validate checks the settings/parameters for this Hash and returns an error. func (h *Hasher) Validate() (err error) { if err = h.validate(); err != nil { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, err) } return nil } func (h *Hasher) validate() (err error) { h.defaults() keyLengthMin := h.variant.HashFunc()().Size() if h.bytesKey < keyLengthMin || h.bytesKey > KeyLengthMax { return fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "key length", keyLengthMin, "", KeyLengthMax, h.bytesKey) } return nil } func (h *Hasher) defaults() { if h.d { return } h.d = true if h.variant == VariantNone { h.variant = variantDefault } if h.bytesKey == 0 { h.bytesKey = h.variant.HashFunc()().Size() } if h.bytesSalt < SaltLengthMin { h.bytesSalt = algorithm.SaltLengthDefault } } crypt-0.4.7/algorithm/pbkdf2/opts.go000066400000000000000000000065741510400225400173420ustar00rootroot00000000000000package pbkdf2 import ( "fmt" "github.com/go-crypt/crypt/algorithm" ) // Opt describes the functional option pattern for the pbkdf2.Hasher. type Opt func(h *Hasher) (err error) // WithVariant configures the pbkdf2.Variant of the resulting pbkdf2.Digest. // Default is pbkdf2.VariantSHA256. func WithVariant(variant Variant) Opt { return func(h *Hasher) (err error) { switch variant { case VariantNone: return nil case VariantSHA1, VariantSHA224, VariantSHA256, VariantSHA384, VariantSHA512: h.variant = variant return nil default: return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant '%d' is invalid", algorithm.ErrParameterInvalid, variant)) } } } // WithVariantName uses the variant name or identifier to configure the pbkdf2.Variant of the resulting pbkdf2.Digest. // Default is pbkdf2.VariantSHA256. func WithVariantName(identifier string) Opt { return func(h *Hasher) (err error) { if identifier == "" { return nil } variant := NewVariant(identifier) if variant == VariantNone { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant identifier '%s' is invalid", algorithm.ErrParameterInvalid, identifier)) } h.variant = variant return nil } } // WithIterations sets the iterations parameter of the resulting pbkdf2.Digest. // Minimum is 100000, Maximum is 2147483647. Default is 29000. func WithIterations(iterations int) Opt { return func(h *Hasher) (err error) { if iterations < IterationsMin || iterations > IterationsMax { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "iterations", IterationsMin, "", IterationsMax, iterations)) } h.iterations = iterations return nil } } // WithKeyLength adjusts the tag length (in bytes) of the resulting pbkdf2.Digest. Default is the output length of the // HMAC digest. Generally it's NOT recommended to change this value at all and let the default values be applied. // Longer tag lengths technically reduce security by forcing a longer hash calculation for legitimate users but not // requiring this for an attacker. In addition most implementations expect the tag length to match the output length of // the HMAC digest. This option MUST come after a specific pbkdf2.WithVariant. func WithKeyLength(bytes int) Opt { return func(h *Hasher) (err error) { if h.variant == VariantNone { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("tag size must not be set before the variant is set")) } keySizeMin := h.variant.HashFunc()().Size() if bytes < keySizeMin || bytes > KeyLengthMax { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "tag size", keySizeMin, "", KeyLengthMax, bytes)) } h.bytesKey = bytes return nil } } // WithSaltLength adjusts the salt size (in bytes) of the resulting pbkdf2.Digest. // Minimum is 8, Maximum is 2147483647. Default is 16. func WithSaltLength(bytes int) Opt { return func(h *Hasher) (err error) { if bytes < SaltLengthMin || bytes > SaltLengthMax { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "salt length", SaltLengthMin, "", SaltLengthMax, bytes)) } h.bytesSalt = bytes return nil } } crypt-0.4.7/algorithm/pbkdf2/variant.go000066400000000000000000000054371510400225400200160ustar00rootroot00000000000000package pbkdf2 import ( "crypto/sha1" //nolint:gosec "crypto/sha256" "crypto/sha512" "github.com/go-crypt/crypt/algorithm" ) // NewVariant converts an identifier string to a pbkdf2.Variant. func NewVariant(identifier string) (variant Variant) { switch identifier { case AlgIdentifier, AlgIdentifierSHA1, algorithm.DigestSHA1: return VariantSHA1 case AlgIdentifierSHA224, algorithm.DigestSHA224: return VariantSHA224 case AlgIdentifierSHA256, algorithm.DigestSHA256: return VariantSHA256 case AlgIdentifierSHA384, algorithm.DigestSHA384: return VariantSHA384 case AlgIdentifierSHA512, algorithm.DigestSHA512: return VariantSHA512 default: return VariantNone } } // Variant is a variant of the pbkdf2.Digest. type Variant int const ( // VariantNone is a variant of the pbkdf2.Digest which is unknown. VariantNone Variant = iota // VariantSHA1 is a variant of the pbkdf2.Digest which uses HMAC-SHA-1. VariantSHA1 // VariantSHA224 is a variant of the pbkdf2.Digest which uses HMAC-SHA-224. VariantSHA224 // VariantSHA256 is a variant of the pbkdf2.Digest which uses HMAC-SHA-256. VariantSHA256 // VariantSHA384 is a variant of the pbkdf2.Digest which uses HMAC-SHA-384. VariantSHA384 // VariantSHA512 is a variant of the pbkdf2.Digest which uses HMAC-SHA-512. VariantSHA512 ) // String implements the fmt.Stringer returning a string representation of the pbkdf2.Variant. func (v Variant) String() (variant string) { switch v { case VariantSHA1: return algorithm.DigestSHA1 case VariantSHA224: return algorithm.DigestSHA224 case VariantSHA256: return algorithm.DigestSHA256 case VariantSHA384: return algorithm.DigestSHA384 case VariantSHA512: return algorithm.DigestSHA512 default: return } } // Prefix returns the pbkdf2.Variant prefix identifier. func (v Variant) Prefix() (prefix string) { switch v { case VariantSHA1: return AlgIdentifier case VariantSHA224: return AlgIdentifierSHA224 case VariantSHA256: return AlgIdentifierSHA256 case VariantSHA384: return AlgIdentifierSHA384 case VariantSHA512: return AlgIdentifierSHA512 default: return } } // HashFunc returns the internal HMAC algorithm.HashFunc. func (v Variant) HashFunc() algorithm.HashFunc { switch v { case VariantSHA1: return sha1.New case VariantSHA224: return sha256.New224 case VariantSHA256: return sha256.New case VariantSHA384: return sha512.New384 case VariantSHA512: return sha512.New default: return nil } } // DefaultIterations returns the default iterations for a variant. func (v Variant) DefaultIterations() int { switch v { case VariantSHA1, VariantSHA224: return IterationsDefaultSHA1 case VariantSHA256, VariantSHA384: return IterationsDefaultSHA256 case VariantSHA512: return IterationsDefaultSHA512 default: return IterationsDefaultSHA1 } } crypt-0.4.7/algorithm/plaintext/000077500000000000000000000000001510400225400166525ustar00rootroot00000000000000crypt-0.4.7/algorithm/plaintext/const.go000066400000000000000000000006701510400225400203320ustar00rootroot00000000000000package plaintext const ( // EncodingFmt is the encoding format for this algorithm. EncodingFmt = "$%s$%s" // AlgName is the name for this algorithm. AlgName = "plaintext" // AlgIdentifierPlainText is the identifier used in encoded plaintext variants of this algorithm. AlgIdentifierPlainText = AlgName // AlgIdentifierBase64 is the identifier used in encoded base64 variants of this algorithm. AlgIdentifierBase64 = "base64" ) crypt-0.4.7/algorithm/plaintext/decoder.go000066400000000000000000000057631510400225400206210ustar00rootroot00000000000000package plaintext import ( "fmt" "github.com/go-crypt/crypt/algorithm" "github.com/go-crypt/crypt/internal/encoding" ) // RegisterDecoder the decoder with the algorithm.DecoderRegister. func RegisterDecoder(r algorithm.DecoderRegister) (err error) { if err = RegisterDecoderPlainText(r); err != nil { return err } if err = r.RegisterDecodeFunc(AlgIdentifierBase64, Decode); err != nil { return err } return nil } // RegisterDecoderPlainText registers specifically the plaintext decoder variant with the algorithm.DecoderRegister. func RegisterDecoderPlainText(r algorithm.DecoderRegister) (err error) { if err = r.RegisterDecodeFunc(VariantPlainText.Prefix(), DecodeVariant(VariantPlainText)); err != nil { return err } return nil } // RegisterDecoderBase64 registers specifically the base64 decoder variant with the algorithm.DecoderRegister. func RegisterDecoderBase64(r algorithm.DecoderRegister) (err error) { if err = r.RegisterDecodeFunc(VariantBase64.Prefix(), DecodeVariant(VariantBase64)); err != nil { return err } return nil } // Decode the encoded digest into a algorithm.Digest. func Decode(encodedDigest string) (digest algorithm.Digest, err error) { return DecodeVariant(VariantNone)(encodedDigest) } // DecodeVariant the encoded digest into a algorithm.Digest provided it matches the provided plaintext.Variant. If // plaintext.VariantNone is used all variants can be decoded. func DecodeVariant(v Variant) func(encodedDigest string) (digest algorithm.Digest, err error) { return func(encodedDigest string) (digest algorithm.Digest, err error) { var ( parts []string variant Variant ) if variant, parts, err = decoderParts(encodedDigest); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) } if v != VariantNone && v != variant { 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())) } if digest, err = decode(variant, parts); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) } return digest, nil } } func decoderParts(encodedDigest string) (variant Variant, parts []string, err error) { parts = encoding.Split(encodedDigest, 3) if len(parts) != 3 { return VariantNone, nil, algorithm.ErrEncodedHashInvalidFormat } variant = NewVariant(parts[1]) if variant == VariantNone { return variant, nil, fmt.Errorf("%w: identifier '%s' is not an encoded %s digest", algorithm.ErrEncodedHashInvalidIdentifier, parts[1], AlgName) } return variant, parts[2:], nil } func decode(variant Variant, parts []string) (digest algorithm.Digest, err error) { decoded := &Digest{ variant: variant, } if decoded.key, err = decoded.variant.Decode(parts[0]); err != nil { return nil, fmt.Errorf("%w: %v", algorithm.ErrEncodedHashKeyEncoding, err) } if len(decoded.key) == 0 { return nil, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrEncodedHashKeyEncoding) } return decoded, nil } crypt-0.4.7/algorithm/plaintext/digest.go000066400000000000000000000047301510400225400204640ustar00rootroot00000000000000package plaintext import ( "crypto/subtle" "fmt" "github.com/go-crypt/crypt/algorithm" ) // NewDigest creates a new plaintext.Digest using the plaintext.Variant. func NewDigest(password string) (digest Digest) { digest = Digest{ variant: VariantPlainText, key: []byte(password), } return digest } // NewBase64Digest creates a new plaintext.Digest using the Base64 plaintext.Variant. func NewBase64Digest(password string) (digest Digest) { digest = Digest{ variant: VariantBase64, key: []byte(password), } return digest } // Digest is an algorithm.Digest which handles plaintext matching. type Digest struct { variant Variant key []byte } // Match returns true if the string password matches the current plaintext.Digest. func (d *Digest) Match(password string) (match bool) { return d.MatchBytes([]byte(password)) } // MatchBytes returns true if the []byte passwordBytes matches the current plaintext.Digest. func (d *Digest) MatchBytes(passwordBytes []byte) (match bool) { match, _ = d.MatchBytesAdvanced(passwordBytes) return match } // MatchAdvanced is the same as Match except if there is an error it returns that as well. func (d *Digest) MatchAdvanced(password string) (match bool, err error) { if len(d.key) == 0 { return false, fmt.Errorf(algorithm.ErrFmtDigestMatch, AlgName, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrPasswordInvalid)) } return d.MatchBytesAdvanced([]byte(password)) } // MatchBytesAdvanced is the same as MatchBytes except if there is an error it returns that as well. func (d *Digest) MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) { if len(d.key) == 0 { return false, fmt.Errorf(algorithm.ErrFmtDigestMatch, AlgName, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrPasswordInvalid)) } return subtle.ConstantTimeCompare(d.key, passwordBytes) == 1, nil } // Encode returns the encoded form of this plaintext.Digest. func (d *Digest) Encode() string { return fmt.Sprintf(EncodingFmt, d.variant.Prefix(), d.variant.Encode(d.key)) } // String returns the storable format of the plaintext.Digest encoded hash. func (d *Digest) String() string { return d.Encode() } func (d *Digest) defaults() { switch d.variant { case VariantPlainText, VariantBase64: break default: d.variant = VariantPlainText } } // Key returns the raw plaintext key which can be used in situations where the plaintext value is required such as // validating JWT's signed by HMAC-SHA256. func (d *Digest) Key() []byte { return d.key } crypt-0.4.7/algorithm/plaintext/doc.go000066400000000000000000000003521510400225400177460ustar00rootroot00000000000000// Package plaintext implements github.com/go-crypt/crypt interfaces with variants of plaintext useful for easy uptake // of previously unhashed passwords. // // This implementation is loaded by crypt.NewDecoderAll. package plaintext crypt-0.4.7/algorithm/plaintext/hasher.go000066400000000000000000000036141510400225400204570ustar00rootroot00000000000000package plaintext import ( "github.com/go-crypt/crypt/algorithm" ) // New returns a *plaintext.Hasher without any settings configured. func New(opts ...Opt) (hasher *Hasher, err error) { hasher = &Hasher{} if err = hasher.WithOptions(opts...); err != nil { return nil, err } if err = hasher.Validate(); err != nil { return nil, err } return hasher, nil } // Hasher is a crypt.Hash for plaintext which can be initialized via plaintext.New using a functional options pattern. type Hasher struct { variant Variant } // WithOptions applies the provided functional options provided as an plaintext.Opt to the plaintext.Hasher. func (h *Hasher) WithOptions(opts ...Opt) (err error) { for _, opt := range opts { if err = opt(h); err != nil { return err } } return nil } // Validate checks the hasher configuration to ensure it's valid. This should be used when the plaintext.Hasher is going // to be reused and you should use it in conjunction with MustHash. func (h *Hasher) Validate() (err error) { return nil } // Hash performs the hashing operation on a password and resets any relevant parameters such as a manually set salt. // It then returns a plaintext.Digest and error. func (h *Hasher) Hash(password string) (hashed algorithm.Digest, err error) { d := &Digest{ variant: h.variant, key: []byte(password), } d.defaults() return d, nil } // HashWithSalt is an overload of plaintext.Digest that also accepts a salt. func (h *Hasher) HashWithSalt(password string, _ []byte) (hashed algorithm.Digest, err error) { return h.Hash(password) } // MustHash overloads the Hash method and panics if the error is not nil. It's recommended if you use this method to // utilize the Validate method first or handle the panic appropriately. func (h *Hasher) MustHash(password string) (hashed algorithm.Digest) { if d, err := h.Hash(password); err != nil { panic(err) } else { return d } } crypt-0.4.7/algorithm/plaintext/opts.go000066400000000000000000000023261510400225400201710ustar00rootroot00000000000000package plaintext import ( "fmt" "github.com/go-crypt/crypt/algorithm" ) // Opt describes the functional option pattern for the plaintext.Hasher. type Opt func(h *Hasher) (err error) // WithVariant configures the plaintext.Variant of the resulting plaintext.Digest. // Default is plaintext.VariantPlainText. func WithVariant(variant Variant) Opt { return func(h *Hasher) (err error) { switch variant { case VariantNone, VariantPlainText, VariantBase64: h.variant = variant return nil default: return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant '%d' is invalid", algorithm.ErrParameterInvalid, variant)) } } } // WithVariantName uses the variant name or identifier to configure the plaintext.Variant of the resulting plaintext.Digest. // Default is plaintext.VariantPlainText. func WithVariantName(identifier string) Opt { return func(h *Hasher) (err error) { if identifier == "" { return nil } variant := NewVariant(identifier) if variant == VariantNone { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant identifier '%s' is invalid", algorithm.ErrParameterInvalid, identifier)) } h.variant = variant return nil } } crypt-0.4.7/algorithm/plaintext/variant.go000066400000000000000000000027721510400225400206550ustar00rootroot00000000000000package plaintext import ( "github.com/go-crypt/crypt/internal/encoding" ) // NewVariant converts an identifier string to a plaintext.Variant. func NewVariant(identifier string) (variant Variant) { switch identifier { case AlgIdentifierPlainText: return VariantPlainText case AlgIdentifierBase64: return VariantBase64 default: return VariantNone } } // Variant is a variant of the plaintext.Digest. type Variant int const ( // VariantNone is a variant of the plaintext.Digest which is unknown. VariantNone Variant = iota // VariantPlainText is a variant of the plaintext.Digest which stores the key as plain text. VariantPlainText // VariantBase64 is a variant of the plaintext.Digest which stores the key as a base64 string. VariantBase64 ) // Prefix returns the plaintext.Variant prefix identifier. func (v Variant) Prefix() (prefix string) { switch v { case VariantPlainText: return AlgIdentifierPlainText case VariantBase64: return AlgIdentifierBase64 default: return } } // Decode performs the decode operation for this plaintext.Variant. func (v Variant) Decode(src string) (dst []byte, err error) { switch v { case VariantBase64: return encoding.Base64RawAdaptedEncoding.DecodeString(src) default: return []byte(src), nil } } // Encode performs the encode operation for this plaintext.Variant. func (v Variant) Encode(src []byte) (dst string) { switch v { case VariantBase64: return encoding.Base64RawAdaptedEncoding.EncodeToString(src) default: return string(src) } } crypt-0.4.7/algorithm/scrypt/000077500000000000000000000000001510400225400161665ustar00rootroot00000000000000crypt-0.4.7/algorithm/scrypt/const.amd64.go000066400000000000000000000002421510400225400205530ustar00rootroot00000000000000//go:build amd64 && !purego package scrypt import ( "math" ) const ( // KeyLengthMax is the maximum key size accepted. KeyLengthMax = math.MaxUint32 * 32 ) crypt-0.4.7/algorithm/scrypt/const.go000066400000000000000000000034511510400225400176460ustar00rootroot00000000000000package scrypt import ( "math" ) const ( // EncodingFmt is the format of the encoded digest. EncodingFmt = "$%s$ln=%d,r=%d,p=%d$%s$%s" // EncodingFmtYescrypt is the format of the encoded digest. EncodingFmtYescrypt = "$%s$%s$%s$%s" // AlgName is the name for this algorithm. AlgName = "scrypt" // AlgNameYescrypt is the name for this algorithm's yescrypt variant. AlgNameYescrypt = "yescrypt" // KeyLengthMin is the minimum key length accepted. KeyLengthMin = 1 // SaltLengthMin is the minimum salt length accepted. SaltLengthMin = 8 // SaltLengthMax is the maximum salt length accepted. SaltLengthMax = 1024 // IterationsMin is the minimum number of iterations accepted. IterationsMin = 1 // IterationsMax is the maximum number of iterations accepted. IterationsMax = 58 // IterationsDefault is the default number of iterations. IterationsDefault = 16 // BlockSizeMin is the minimum block size accepted. BlockSizeMin = 1 // BlockSizeMax is the maximum block size accepted. BlockSizeMax = math.MaxInt / 256 // BlockSizeDefault is the default block size. BlockSizeDefault = 8 // ParallelismMin is the minimum parallelism factor accepted. ParallelismMin = 1 // ParallelismMax is the maximum parallelism factor accepted. // // Equation is based on the following text from RFC: // // The parallelization parameter p // ("parallelizationParameter") is a positive integer less than or equal // to ((2^32-1) * 32) / (128 * r). // // When r has a minimum of 1, this makes the equation ((2^32-1) * 32) / 128. ParallelismMax = 1073741823 // ParallelismDefault is the default parallelism factor. ParallelismDefault = ParallelismMin ) const ( AlgIdentifier = AlgName AlgIdentifierYescrypt = "y" ) const ( oP = "p" oR = "r" oLN = "ln" variantDefault = VariantScrypt ) crypt-0.4.7/algorithm/scrypt/const.pure.go000066400000000000000000000002341510400225400206140ustar00rootroot00000000000000//go:build !amd64 || purego package scrypt import ( "math" ) const ( // KeyLengthMax is the maximum key size accepted. KeyLengthMax = math.MaxInt32 ) crypt-0.4.7/algorithm/scrypt/decoder.go000066400000000000000000000101711510400225400201220ustar00rootroot00000000000000package scrypt import ( "encoding/base64" "fmt" "github.com/go-crypt/x/yescrypt" "github.com/go-crypt/crypt/algorithm" "github.com/go-crypt/crypt/internal/encoding" ) // RegisterDecoder the decoder with the algorithm.DecoderRegister. func RegisterDecoder(r algorithm.DecoderRegister) (err error) { if err = RegisterDecoderScrypt(r); err != nil { return err } if err = RegisterDecoderYescrypt(r); err != nil { return err } return nil } // RegisterDecoderScrypt the scrypt decoder with the algorithm.DecoderRegister. func RegisterDecoderScrypt(r algorithm.DecoderRegister) (err error) { if err = r.RegisterDecodeFunc(VariantScrypt.Prefix(), Decode); err != nil { return err } return nil } // RegisterDecoderYescrypt the yescrypt decoder with the algorithm.DecoderRegister. func RegisterDecoderYescrypt(r algorithm.DecoderRegister) (err error) { if err = r.RegisterDecodeFunc(VariantYescrypt.Prefix(), Decode); err != nil { return err } return nil } // Decode the encoded digest into a algorithm.Digest. func Decode(encodedDigest string) (digest algorithm.Digest, err error) { return DecodeVariant(VariantNone)(encodedDigest) } // DecodeVariant the encoded digest into a algorithm.Digest provided it matches the provided scrypt.Variant. If // scrypt.VariantNone is used all variants can be decoded. func DecodeVariant(v Variant) func(encodedDigest string) (digest algorithm.Digest, err error) { return func(encodedDigest string) (digest algorithm.Digest, err error) { var ( parts []string variant Variant ) if variant, parts, err = decoderParts(encodedDigest); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) } if v != VariantNone && v != variant { 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())) } if digest, err = decode(variant, parts); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) } return digest, nil } } func decoderParts(encodedDigest string) (variant Variant, parts []string, err error) { parts = encoding.Split(encodedDigest, -1) if len(parts) != 5 { return VariantNone, nil, algorithm.ErrEncodedHashInvalidFormat } variant = NewVariant(parts[1]) if variant == VariantNone { return variant, nil, fmt.Errorf("%w: identifier '%s' is not an encoded %s digest", algorithm.ErrEncodedHashInvalidIdentifier, parts[1], AlgName) } return variant, parts[2:], nil } func decode(variant Variant, parts []string) (digest algorithm.Digest, err error) { decoded := &Digest{ variant: variant, ln: IterationsDefault, r: BlockSizeDefault, p: ParallelismDefault, } switch variant { case VariantYescrypt: if _, decoded.ln, decoded.r, err = yescrypt.DecodeSetting([]byte(parts[0])); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) } decoded.salt, decoded.key = yescrypt.Decode64([]byte(parts[1])), yescrypt.Decode64([]byte(parts[2])) default: var params []encoding.Parameter if params, err = encoding.DecodeParameterStr(parts[0]); err != nil { return nil, err } for _, param := range params { switch param.Key { case oLN: decoded.ln, err = param.Int() case oR: decoded.r, err = param.Int() case oP: decoded.p, err = param.Int() default: return nil, fmt.Errorf("%w: option '%s' with value '%s' is unknown", algorithm.ErrEncodedHashInvalidOptionKey, param.Key, param.Value) } if err != nil { return nil, fmt.Errorf("%w: option '%s' has invalid value '%s': %v", algorithm.ErrEncodedHashInvalidOptionValue, param.Key, param.Value, err) } } if decoded.salt, err = base64.RawStdEncoding.DecodeString(parts[1]); err != nil { return nil, fmt.Errorf("%w: %v", algorithm.ErrEncodedHashSaltEncoding, err) } if decoded.key, err = base64.RawStdEncoding.DecodeString(parts[2]); err != nil { return nil, fmt.Errorf("%w: %v", algorithm.ErrEncodedHashKeyEncoding, err) } } if len(decoded.key) == 0 { return nil, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrEncodedHashKeyEncoding) } return decoded, nil } crypt-0.4.7/algorithm/scrypt/digest.go000066400000000000000000000041321510400225400177740ustar00rootroot00000000000000package scrypt import ( "crypto/subtle" "fmt" "github.com/go-crypt/crypt/algorithm" ) // Digest is a scrypt.Digest which handles scrypt hashes. type Digest struct { variant Variant ln, r, p int salt, key []byte } // Match returns true if the string password matches the current scrypt.Digest. func (d *Digest) Match(password string) (match bool) { return d.MatchBytes([]byte(password)) } // MatchBytes returns true if the []byte passwordBytes matches the current scrypt.Digest. func (d *Digest) MatchBytes(passwordBytes []byte) (match bool) { match, _ = d.MatchBytesAdvanced(passwordBytes) return match } // MatchAdvanced is the same as Match except if there is an error it returns that as well. func (d *Digest) MatchAdvanced(password string) (match bool, err error) { if match, err = d.MatchBytesAdvanced([]byte(password)); err != nil { return match, fmt.Errorf(algorithm.ErrFmtDigestMatch, AlgName, err) } return match, nil } // MatchBytesAdvanced is the same as MatchBytes except if there is an error it returns that as well. func (d *Digest) MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) { if len(d.key) == 0 { return false, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrPasswordInvalid) } var key []byte if key, err = d.variant.KeyFunc()(passwordBytes, d.salt, d.n(), d.r, d.p, len(d.key)); err != nil { return false, err } return subtle.ConstantTimeCompare(d.key, key) == 1, nil } // Encode returns the encoded form of this scrypt.Digest. func (d *Digest) Encode() string { return d.variant.Encode(d.ln, d.r, d.p, d.salt, d.key) } // String returns the storable format of the scrypt.Digest encoded hash. func (d *Digest) String() string { return d.Encode() } // n returns 2 to the power of log N i.e d.ln. func (d *Digest) n() (n int) { return 1 << d.ln } func (d *Digest) defaults() { switch d.variant { case VariantScrypt, VariantYescrypt: break default: d.variant = variantDefault } if d.ln < IterationsMin { d.ln = IterationsDefault } if d.r < BlockSizeMin { d.r = BlockSizeDefault } if d.p < ParallelismMin { d.p = ParallelismDefault } } crypt-0.4.7/algorithm/scrypt/doc.go000066400000000000000000000003601510400225400172610ustar00rootroot00000000000000// Package scrypt provides helpful abstractions for an implementation of RFC7914 and implements // github.com/go-crypt/crypt interfaces. // // This implementation is loaded by crypt.NewDefaultDecoder and crypt.NewDecoderAll. package scrypt crypt-0.4.7/algorithm/scrypt/hasher.go000066400000000000000000000116571510400225400200010ustar00rootroot00000000000000package scrypt import ( "fmt" "math" "github.com/go-crypt/crypt/algorithm" "github.com/go-crypt/crypt/internal/random" ) // scrypt RFC7914: https://www.rfc-editor.org/rfc/rfc7914.html. // New returns a new scrypt.Hasher with the provided functional options applied. func New(opts ...Opt) (hasher *Hasher, err error) { hasher = &Hasher{} if err = hasher.WithOptions(opts...); err != nil { return nil, err } if err = hasher.Validate(); err != nil { return nil, err } return hasher, nil } func NewScrypt(opts ...Opt) (hasher *Hasher, err error) { if hasher, err = New(opts...); err != nil { return nil, err } if err = hasher.WithOptions(WithVariant(VariantScrypt)); err != nil { return nil, err } return hasher, nil } func NewYescrypt(opts ...Opt) (hasher *Hasher, err error) { if hasher, err = New(opts...); err != nil { return nil, err } if err = hasher.WithOptions(WithVariant(VariantYescrypt)); err != nil { return nil, err } return hasher, nil } // Hasher is a crypt.Hash for scrypt which can be initialized via New using a functional options pattern. type Hasher struct { variant Variant ln, r, k, p, bytesSalt int d bool } // WithOptions defines the options for this scrypt.Hasher. func (h *Hasher) WithOptions(opts ...Opt) (err error) { for _, opt := range opts { if err = opt(h); err != nil { return err } } return nil } // Hash performs the hashing operation and returns either a Digest or an error. func (h *Hasher) Hash(password string) (digest algorithm.Digest, err error) { h.defaults() if digest, err = h.hash(password); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) } return digest, nil } func (h *Hasher) hash(password string) (digest algorithm.Digest, err error) { h.defaults() var salt []byte if salt, err = random.Bytes(h.bytesSalt); err != nil { return nil, fmt.Errorf("%w: %v", algorithm.ErrSaltReadRandomBytes, err) } return h.hashWithSalt(password, salt) } // HashWithSalt overloads the Hash method allowing the user to provide a salt. It's recommended instead to configure the // salt size and let this be a random value generated using crypto/rand. func (h *Hasher) HashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { h.defaults() if digest, err = h.hashWithSalt(password, salt); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) } return digest, nil } func (h *Hasher) hashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { if s := len(salt); s > SaltLengthMax || s < SaltLengthMin { 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)) } d := &Digest{ variant: h.variant, ln: h.ln, r: h.r, p: h.p, salt: salt, } d.defaults() if d.key, err = d.variant.KeyFunc()([]byte(password), d.salt, d.n(), d.r, d.p, h.k); err != nil { return nil, fmt.Errorf("%w: %v", algorithm.ErrKeyDerivation, err) } return d, nil } // MustHash overloads the Hash method and panics if the error is not nil. It's recommended if you use this option to // utilize the Validate method first or handle the panic appropriately. func (h *Hasher) MustHash(password string) (digest algorithm.Digest) { var err error if digest, err = h.Hash(password); err != nil { panic(err) } return digest } // Validate checks the settings/parameters for this Hash and returns an error. func (h *Hasher) Validate() (err error) { h.defaults() if err = h.validate(); err != nil { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, err) } return nil } func (h *Hasher) validate() (err error) { rp := uint64(h.r) * uint64(h.p) if rp >= 1<<30 { return fmt.Errorf("%w: parameters 'r' and 'p' must be less than %d when multiplied but they are '%d'", algorithm.ErrParameterInvalid, 1<<30, rp) } if h.r > 0 { mp := KeyLengthMax / (128 * h.r) if h.p < ParallelismMin || h.p > mp { return fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "p", ParallelismMin, "", mp, h.p) } nr := math.MaxInt / 128 / h.r N := 1 << h.ln if N > nr { 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) } } if h.p > 0 { pr := math.MaxInt / 128 / h.p if pr < BlockSizeMax { if h.r > pr { 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) } } } return nil } func (h *Hasher) defaults() { if h.d { return } h.d = true if h.variant == VariantNone { h.variant = variantDefault } if h.k == 0 { h.k = algorithm.KeyLengthDefault } if h.bytesSalt == 0 { h.bytesSalt = algorithm.SaltLengthDefault } } crypt-0.4.7/algorithm/scrypt/opts.go000066400000000000000000000075331510400225400175120ustar00rootroot00000000000000package scrypt import ( "fmt" "github.com/go-crypt/crypt/algorithm" ) // Opt describes the functional option pattern for the scrypt.Hasher. type Opt func(h *Hasher) (err error) // WithVariant configures the scrypt.Variant of the resulting scrypt.Digest. // Default is scrypt.VariantScrypt. func WithVariant(variant Variant) Opt { return func(h *Hasher) (err error) { switch variant { case VariantNone: return nil case VariantScrypt, VariantYescrypt: h.variant = variant return nil default: return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant '%d' is invalid", algorithm.ErrParameterInvalid, variant)) } } } // WithVariantName uses the variant name or identifier to configure the scrypt.Variant of the resulting scrypt.Digest. // Default is scrypt.VariantScrypt. func WithVariantName(identifier string) Opt { return func(h *Hasher) (err error) { if identifier == "" { return nil } variant := NewVariant(identifier) if variant == VariantNone { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant identifier '%s' is invalid", algorithm.ErrParameterInvalid, identifier)) } h.variant = variant return nil } } // WithK adjusts the key length of the resulting scrypt.Digest. // Minimum is 1, Maximum is 137438953440. Default is 32. func WithK(k int) Opt { return func(h *Hasher) (err error) { if k < KeyLengthMin || k > KeyLengthMax { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "key length", KeyLengthMin, "", KeyLengthMax, k)) } h.k = k return nil } } // WithKeyLength is an alias for WithK. func WithKeyLength(k int) Opt { return WithK(k) } // WithS adjusts the salt length of the resulting scrypt.Digest. // Minimum is 8, Maximum is 1024. Default is 16. func WithS(s int) Opt { return func(h *Hasher) (err error) { if s < SaltLengthMin || s > SaltLengthMax { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "salt length", SaltLengthMin, "", SaltLengthMax, s)) } h.bytesSalt = s return nil } } // WithSaltLength is an alias for WithS. func WithSaltLength(s int) Opt { return WithS(s) } // WithLN sets the ln parameter (logN) of the resulting scrypt.Digest. // Minimum is 1, Maximum is 58. Default is 16. func WithLN(ln int) Opt { return func(h *Hasher) (err error) { if ln < IterationsMin || ln > IterationsMax { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "iterations", IterationsMin, "", IterationsMax, ln)) } h.ln = ln return nil } } // WithR sets the r parameter (block size) of the resulting scrypt.Digest. // Minimum is 1, Maximum is math.MaxInt / 256. Default is 8. func WithR(r int) Opt { return func(h *Hasher) (err error) { if r < BlockSizeMin || r > BlockSizeMax { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "block size", BlockSizeMin, "", BlockSizeMax, r)) } h.r = r return nil } } // WithBlockSize is an alias for WithR. func WithBlockSize(r int) Opt { return WithS(r) } // WithP sets the p parameter (parallelism factor) of the resulting scrypt.Digest. // Minimum is 1, Maximum is 1073741823. Default is 1. func WithP(p int) Opt { return func(h *Hasher) (err error) { if p < ParallelismMin || p > ParallelismMax { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "parallelism", ParallelismMin, "", ParallelismMax, p)) } h.p = p return nil } } // WithParallelism is an alias for WithP. func WithParallelism(p int) Opt { return WithP(p) } crypt-0.4.7/algorithm/scrypt/variant.go000066400000000000000000000035131510400225400201630ustar00rootroot00000000000000package scrypt import ( "encoding/base64" "fmt" "github.com/go-crypt/x/scrypt" "github.com/go-crypt/x/yescrypt" ) // NewVariant converts an identifier string to a scrypt.Variant. func NewVariant(identifier string) (variant Variant) { switch identifier { case AlgName: return VariantScrypt case AlgNameYescrypt, AlgIdentifierYescrypt: return VariantYescrypt default: return VariantNone } } // Variant is a variant of the scrypt.Digest. type Variant int const ( // VariantNone is the default variant of Scrypt. VariantNone Variant = iota VariantScrypt VariantYescrypt ) // String implements the fmt.Stringer returning a string representation of the scrypt.Variant. func (v Variant) String() (variant string) { switch v { case VariantScrypt: return AlgIdentifier case VariantYescrypt: return AlgIdentifierYescrypt default: return } } // Prefix returns the scrypt.Variant prefix identifier. func (v Variant) Prefix() (prefix string) { return v.String() } // KeyFunc returns the internal HMAC algorithm.HashFunc. func (v Variant) KeyFunc() KeyFunc { switch v { case VariantScrypt: return scrypt.Key case VariantYescrypt: return yescrypt.Key default: return nil } } // Encode formats the variant encoded bcrypt.Digest. func (v Variant) Encode(ln, r, p int, salt, key []byte) (f string) { switch v { case VariantScrypt: return fmt.Sprintf(EncodingFmt, v.Prefix(), ln, r, p, base64.RawStdEncoding.EncodeToString(salt), base64.RawStdEncoding.EncodeToString(key)) case VariantYescrypt: return fmt.Sprintf(EncodingFmtYescrypt, v.Prefix(), yescrypt.EncodeSetting(0, ln, r), yescrypt.Encode64(salt), yescrypt.Encode64(key)) default: return } } // KeyFunc represents the KeyFunc used by scrypt implementations. type KeyFunc func(password []byte, salt []byte, N int, r int, p int, keyLen int) (key []byte, err error) crypt-0.4.7/algorithm/sha1crypt/000077500000000000000000000000001510400225400165605ustar00rootroot00000000000000crypt-0.4.7/algorithm/sha1crypt/const.go000066400000000000000000000016001510400225400202320ustar00rootroot00000000000000package sha1crypt import ( "math" ) const ( // EncodingFmt is the encoding format for this algorithm. EncodingFmt = "$sha1$%d$%s$%s" // AlgName is the name for this algorithm. AlgName = "sha1crypt" // AlgIdentifier is the identifier used in this algorithm. AlgIdentifier = "sha1" // SaltLengthMin is the minimum salt size accepted. SaltLengthMin = 0 // SaltLengthMax is the maximum salt size accepted. SaltLengthMax = 64 // SaltLengthDefault is the default salt size. SaltLengthDefault = 8 // SaltCharSet are the valid characters for the salt. SaltCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./" // IterationsMin is the minimum iterations accepted. IterationsMin = 0 // IterationsMax is the maximum iterations accepted. IterationsMax uint32 = math.MaxUint32 // IterationsDefault is the default iterations. IterationsDefault = 480000 ) crypt-0.4.7/algorithm/sha1crypt/decoder.go000066400000000000000000000036231510400225400205200ustar00rootroot00000000000000package sha1crypt import ( "fmt" "strconv" "github.com/go-crypt/crypt/algorithm" "github.com/go-crypt/crypt/internal/encoding" ) // RegisterDecoder the decoder with the algorithm.DecoderRegister. func RegisterDecoder(r algorithm.DecoderRegister) (err error) { if err = RegisterDecoderCommon(r); err != nil { return err } return nil } // RegisterDecoderCommon registers specifically the common decoder variant with the algorithm.DecoderRegister. func RegisterDecoderCommon(r algorithm.DecoderRegister) (err error) { if err = r.RegisterDecodeFunc(AlgIdentifier, Decode); err != nil { return err } return nil } // Decode the encoded digest into a algorithm.Digest. func Decode(encodedDigest string) (digest algorithm.Digest, err error) { var ( parts []string ) if parts, err = decoderParts(encodedDigest); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) } if digest, err = decode(parts); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) } return digest, nil } func decoderParts(encodedDigest string) (parts []string, err error) { parts = encoding.Split(encodedDigest, -1) if len(parts) != 5 { return nil, algorithm.ErrEncodedHashInvalidFormat } if parts[1] != AlgIdentifier { return nil, fmt.Errorf("%w: identifier '%s' is not an encoded %s digest", algorithm.ErrEncodedHashInvalidIdentifier, parts[1], AlgName) } return parts[2:], nil } func decode(parts []string) (digest algorithm.Digest, err error) { decoded := &Digest{} if parts[0] != "" { var iterations uint64 if iterations, err = strconv.ParseUint(parts[0], 10, 32); err != nil { return nil, fmt.Errorf("%w: option '%s' has invalid value '%s': %v", algorithm.ErrEncodedHashInvalidOptionValue, "rounds", parts[0], err) } decoded.iterations = uint32(iterations) } decoded.salt, decoded.key = []byte(parts[1]), []byte(parts[2]) return decoded, nil } crypt-0.4.7/algorithm/sha1crypt/digest.go000066400000000000000000000032621510400225400203710ustar00rootroot00000000000000package sha1crypt import ( "crypto/subtle" "fmt" "github.com/go-crypt/x/crypt" "github.com/go-crypt/crypt/algorithm" ) // Digest is a algorithm.Digest which handles sha1crypt hashes. type Digest struct { iterations uint32 i bool salt, key []byte } // Match returns true if the string password matches the current sha1crypt.Digest. func (d *Digest) Match(password string) (match bool) { return d.MatchBytes([]byte(password)) } // MatchBytes returns true if the []byte passwordBytes matches the current sha1crypt.Digest. func (d *Digest) MatchBytes(passwordBytes []byte) (match bool) { match, _ = d.MatchBytesAdvanced(passwordBytes) return match } // MatchAdvanced is the same as Match except if there is an error it returns that as well. func (d *Digest) MatchAdvanced(password string) (match bool, err error) { return d.MatchBytesAdvanced([]byte(password)) } // MatchBytesAdvanced is the same as MatchBytes except if there is an error it returns that as well. func (d *Digest) MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) { if len(d.key) == 0 { return false, fmt.Errorf(algorithm.ErrFmtDigestMatch, AlgName, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrPasswordInvalid)) } return subtle.ConstantTimeCompare(d.key, crypt.KeySHA1Crypt(passwordBytes, d.salt, d.iterations)) == 1, nil } // Encode returns the encoded form of this sha1crypt.Digest. func (d *Digest) Encode() string { return fmt.Sprintf(EncodingFmt, d.iterations, d.salt, d.key, ) } // String returns the storable format of the sha1crypt.Digest encoded hash. func (d *Digest) String() string { return d.Encode() } func (d *Digest) defaults() { if !d.i { d.iterations = IterationsDefault } } crypt-0.4.7/algorithm/sha1crypt/doc.go000066400000000000000000000003371510400225400176570ustar00rootroot00000000000000// Package sha1crypt provides helpful abstractions for an implementation of crypt (SHA1) and implements // github.com/go-crypt/crypt interfaces. // // This implementation is loaded by crypt.NewDecoderAll. package sha1crypt crypt-0.4.7/algorithm/sha1crypt/hasher.go000066400000000000000000000062001510400225400203570ustar00rootroot00000000000000package sha1crypt import ( "fmt" "github.com/go-crypt/x/crypt" "github.com/go-crypt/crypt/algorithm" "github.com/go-crypt/crypt/internal/random" ) // New returns a *sha1crypt.Hasher with the additional opts applied if any. func New(opts ...Opt) (hasher *Hasher, err error) { hasher = &Hasher{} if err = hasher.WithOptions(opts...); err != nil { return nil, err } if err = hasher.Validate(); err != nil { return nil, err } return hasher, nil } // Hasher is a crypt.Hash for sha1crypt which can be initialized via sha1crypt.New using a functional options pattern. type Hasher struct { iterations uint32 i bool bytesSalt int d bool } // WithOptions applies the provided functional options provided as a sha1crypt.Opt to the sha1crypt.Hasher. func (h *Hasher) WithOptions(opts ...Opt) (err error) { for _, opt := range opts { if err = opt(h); err != nil { return err } } return nil } // Hash performs the hashing operation and returns either a algorithm.Digest or an error. func (h *Hasher) Hash(password string) (digest algorithm.Digest, err error) { h.defaults() if digest, err = h.hash(password); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) } return digest, nil } // MustHash overloads the Hash method and panics if the error is not nil. It's recommended if you use this option to // utilize the Validate method first or handle the panic appropriately. func (h *Hasher) MustHash(password string) (digest algorithm.Digest) { var err error if digest, err = h.Hash(password); err != nil { panic(err) } return digest } // HashWithSalt overloads the Hash method allowing the user to provide a salt. It's recommended instead to configure the // salt size and let this be a random value generated using crypto/rand. func (h *Hasher) HashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { h.defaults() if digest, err = h.hashWithSalt(password, salt); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) } return digest, nil } // Validate checks the settings/parameters for this sha1crypt.Hasher and returns an error. func (h *Hasher) Validate() (err error) { h.defaults() return nil } func (h *Hasher) hash(password string) (digest algorithm.Digest, err error) { var salt []byte if salt, err = random.CharSetBytes(h.bytesSalt, SaltCharSet); err != nil { return nil, fmt.Errorf("%w: %v", algorithm.ErrSaltReadRandomBytes, err) } return h.hashWithSalt(password, salt) } func (h *Hasher) hashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { if s := len(salt); s > SaltLengthMax || s < SaltLengthMin { 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)) } d := &Digest{ iterations: h.iterations, i: h.i, salt: salt, } d.defaults() d.key = crypt.KeySHA1Crypt([]byte(password), d.salt, d.iterations) return d, nil } func (h *Hasher) defaults() { if h.d { return } h.d = true if h.bytesSalt < SaltLengthMin { h.bytesSalt = SaltLengthDefault } } crypt-0.4.7/algorithm/sha1crypt/opts.go000066400000000000000000000025461510400225400201030ustar00rootroot00000000000000package sha1crypt import ( "fmt" "github.com/go-crypt/crypt/algorithm" ) // Opt describes the functional option pattern for the sha1crypt.Hasher. type Opt func(h *Hasher) (err error) // WithIterations sets the iterations parameter of the resulting sha1crypt.Digest. // Minimum is 0, Maximum is 4294967295. Default is 480000. func WithIterations(iterations uint32) Opt { return func(h *Hasher) (err error) { if iterations < IterationsMin || iterations > IterationsMax { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "iterations", IterationsMin, "", IterationsMax, iterations)) } h.i = true h.iterations = iterations return nil } } // WithRounds is an alias for sha1crypt.WithIterations. func WithRounds(rounds uint32) Opt { return WithIterations(rounds) } // WithSaltLength adjusts the salt size (in bytes) of the resulting sha1crypt.Digest. // Minimum is 1, Maximum is 64. Default is 8. func WithSaltLength(bytes int) Opt { return func(h *Hasher) (err error) { if bytes < SaltLengthMin || bytes > SaltLengthMax { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "salt length", SaltLengthMin, "", SaltLengthMax, bytes)) } h.bytesSalt = bytes return nil } } crypt-0.4.7/algorithm/shacrypt/000077500000000000000000000000001510400225400164775ustar00rootroot00000000000000crypt-0.4.7/algorithm/shacrypt/const.go000066400000000000000000000026151510400225400201600ustar00rootroot00000000000000package shacrypt const ( // EncodingFmt is the encoding format for this algorithm. EncodingFmt = "$%s$rounds=%d$%s$%s" // EncodingFmtRoundsOmitted is the encoding format for this algorithm when the rounds can be omitted. EncodingFmtRoundsOmitted = "$%s$%s$%s" // AlgName is the name for this algorithm. AlgName = "shacrypt" // AlgIdentifierSHA256 is the identifier used in encoded SHA256 variants of this algorithm. AlgIdentifierSHA256 = "5" // AlgIdentifierSHA512 is the identifier used in encoded SHA512 variants of this algorithm. AlgIdentifierSHA512 = "6" // IterationsMin is the minimum number of iterations accepted. IterationsMin = 1000 // IterationsMax is the maximum number of iterations accepted. IterationsMax = 999999999 // IterationsDefaultSHA256 is the default number of iterations for SHA256. IterationsDefaultSHA256 = 1000000 // IterationsDefaultSHA512 is the default number of iterations for SHA512. IterationsDefaultSHA512 = 500000 // IterationsDefaultOmitted is the default number of iterations when the rounds are omitted. IterationsDefaultOmitted = 5000 // SaltLengthMin is the minimum salt length. SaltLengthMin = 1 // SaltLengthMax is the maximum salt length. SaltLengthMax = 16 // SaltCharSet are the valid characters for the salt. SaltCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./" ) const ( variantDefault = VariantSHA512 ) crypt-0.4.7/algorithm/shacrypt/decoder.go000066400000000000000000000072621510400225400204420ustar00rootroot00000000000000package shacrypt import ( "fmt" "strconv" "github.com/go-crypt/crypt/algorithm" "github.com/go-crypt/crypt/internal/encoding" ) // RegisterDecoder the decoder with the algorithm.DecoderRegister. func RegisterDecoder(r algorithm.DecoderRegister) (err error) { if err = RegisterDecoderSHA256(r); err != nil { return err } if err = RegisterDecoderSHA512(r); err != nil { return err } return nil } // RegisterDecoderSHA256 registers specifically the sha256 decoder variant with the algorithm.DecoderRegister. func RegisterDecoderSHA256(r algorithm.DecoderRegister) (err error) { if err = r.RegisterDecodeFunc(VariantSHA256.Prefix(), DecodeVariant(VariantSHA256)); err != nil { return err } return nil } // RegisterDecoderSHA512 registers specifically the sha512 decoder variant with the algorithm.DecoderRegister. func RegisterDecoderSHA512(r algorithm.DecoderRegister) (err error) { if err = r.RegisterDecodeFunc(VariantSHA512.Prefix(), DecodeVariant(VariantSHA512)); err != nil { return err } return nil } // Decode the encoded digest into a algorithm.Digest. func Decode(encodedDigest string) (digest algorithm.Digest, err error) { return DecodeVariant(VariantNone)(encodedDigest) } // DecodeVariant the encoded digest into a algorithm.Digest provided it matches the provided Variant. If VariantNone is // used all variants can be decoded. func DecodeVariant(v Variant) func(encodedDigest string) (digest algorithm.Digest, err error) { return func(encodedDigest string) (digest algorithm.Digest, err error) { var ( parts []string variant Variant ) if variant, parts, err = decoderParts(encodedDigest); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) } if v != VariantNone && v != variant { 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())) } if digest, err = decode(variant, parts); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err) } return digest, nil } } func decoderParts(encodedDigest string) (variant Variant, parts []string, err error) { parts = encoding.Split(encodedDigest, -1) if n := len(parts); n != 4 && n != 5 { return VariantNone, nil, algorithm.ErrEncodedHashInvalidFormat } variant = NewVariant(parts[1]) if variant == VariantNone { return variant, nil, fmt.Errorf("%w: identifier '%s' is not an encoded %s digest", algorithm.ErrEncodedHashInvalidIdentifier, parts[1], AlgName) } return variant, parts[2:], nil } func decode(variant Variant, parts []string) (digest algorithm.Digest, err error) { decoded := &Digest{ variant: variant, } var ( ip, is, ik int ) switch len(parts) { case 2: ip, is, ik = -1, 0, 1 case 3: ip, is, ik = 0, 1, 2 } if len(parts[ik]) == 0 { return nil, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrEncodedHashKeyEncoding) } decoded.iterations = IterationsDefaultOmitted var params []encoding.Parameter if ip >= 0 { if params, err = encoding.DecodeParameterStr(parts[ip]); err != nil { return nil, err } } for _, param := range params { switch param.Key { case "rounds": var rounds uint64 if rounds, err = strconv.ParseUint(param.Value, 10, 32); err != nil { return nil, fmt.Errorf("%w: option '%s' has invalid value '%s': %v", algorithm.ErrEncodedHashInvalidOptionValue, param.Key, param.Value, err) } decoded.iterations = int(rounds) default: return nil, fmt.Errorf("%w: option '%s' with value '%s' is unknown", algorithm.ErrEncodedHashInvalidOptionKey, param.Key, param.Value) } } decoded.salt, decoded.key = []byte(parts[is]), []byte(parts[ik]) return decoded, nil } crypt-0.4.7/algorithm/shacrypt/digest.go000066400000000000000000000043041510400225400203060ustar00rootroot00000000000000package shacrypt import ( "crypto/subtle" "fmt" "strings" xcrypt "github.com/go-crypt/x/crypt" "github.com/go-crypt/crypt/algorithm" ) // Digest is a digest which handles SHA-crypt hashes like SHA256 or SHA512. type Digest struct { variant Variant iterations int salt, key []byte } // Match returns true if the string password matches the current shacrypt.Digest. func (d *Digest) Match(password string) (match bool) { return d.MatchBytes([]byte(password)) } // MatchBytes returns true if the []byte passwordBytes matches the current shacrypt.Digest. func (d *Digest) MatchBytes(passwordBytes []byte) (match bool) { match, _ = d.MatchBytesAdvanced(passwordBytes) return match } // MatchAdvanced is the same as Match except if there is an error it returns that as well. func (d *Digest) MatchAdvanced(password string) (match bool, err error) { if match, err = d.MatchBytesAdvanced([]byte(password)); err != nil { return match, fmt.Errorf(algorithm.ErrFmtDigestMatch, AlgName, err) } return match, nil } // MatchBytesAdvanced is the same as MatchBytes except if there is an error it returns that as well. func (d *Digest) MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) { if len(d.key) == 0 { return false, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrPasswordInvalid) } return subtle.ConstantTimeCompare(d.key, xcrypt.KeySHACrypt(d.variant.HashFunc(), passwordBytes, d.salt, d.iterations)) == 1, nil } // Encode this Digest as a string for storage. func (d *Digest) Encode() (hash string) { switch d.iterations { case IterationsDefaultOmitted: return strings.ReplaceAll(fmt.Sprintf(EncodingFmtRoundsOmitted, d.variant.Prefix(), d.salt, d.key, ), "\n", "") default: return strings.ReplaceAll(fmt.Sprintf(EncodingFmt, d.variant.Prefix(), d.iterations, d.salt, d.key, ), "\n", "") } } // String returns the storable format of the shacrypt.Digest hash utilizing fmt.Sprintf and shacrypt.EncodingFmt. func (d *Digest) String() string { return d.Encode() } func (d *Digest) defaults() { switch d.variant { case VariantSHA256, VariantSHA512: break default: d.variant = variantDefault } if d.iterations == 0 { d.iterations = d.variant.DefaultIterations() } } crypt-0.4.7/algorithm/shacrypt/doc.go000066400000000000000000000005121510400225400175710ustar00rootroot00000000000000// Package shacrypt provides helpful abstractions for an implementation of SHA-crypt and implements // github.com/go-crypt/crypt interfaces. // // See https://www.akkadia.org/drepper/SHA-crypt.html for specification details. // // This implementation is loaded by crypt.NewDefaultDecoder and crypt.NewDecoderAll. package shacrypt crypt-0.4.7/algorithm/shacrypt/hasher.go000066400000000000000000000100371510400225400203010ustar00rootroot00000000000000package shacrypt import ( "fmt" xcrypt "github.com/go-crypt/x/crypt" "github.com/go-crypt/crypt/algorithm" "github.com/go-crypt/crypt/internal/random" ) // New returns a *Hasher without any settings configured. This d to a SHA512 hash.Hash // with 1000000 iterations. These settings can be overridden with the methods with the With prefix. func New(opts ...Opt) (hasher *Hasher, err error) { hasher = &Hasher{} if err = hasher.WithOptions(opts...); err != nil { return nil, err } if err = hasher.Validate(); err != nil { return nil, err } return hasher, nil } // Hasher is a algorithm.Hash for SHA-crypt which can be initialized via shacrypt.New using a functional options pattern. type Hasher struct { variant Variant iterations, bytesSalt int d bool } // NewSHA256 returns a *Hasher with the SHA256 hash.Hash which d to 1000000 iterations. These // settings can be overridden with the methods with the With prefix. func NewSHA256() (hasher *Hasher, err error) { return New( WithVariant(VariantSHA256), WithIterations(VariantSHA256.DefaultIterations()), ) } // NewSHA512 returns a *Hasher with the SHA512 hash.Hash which d to 1000000 iterations. These // settings can be overridden with the methods with the With prefix. func NewSHA512() (hasher *Hasher, err error) { return New( WithVariant(VariantSHA512), WithIterations(VariantSHA512.DefaultIterations()), ) } // WithOptions defines the options for this scrypt.Hasher. func (h *Hasher) WithOptions(opts ...Opt) (err error) { for _, opt := range opts { if err = opt(h); err != nil { return err } } return nil } // Hash performs the hashing operation and returns either a shacrypt.Digest as a algorithm.Digest or an error. func (h *Hasher) Hash(password string) (digest algorithm.Digest, err error) { h.defaults() if digest, err = h.hash(password); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) } return digest, nil } func (h *Hasher) hash(password string) (digest algorithm.Digest, err error) { var salt []byte if salt, err = random.CharSetBytes(h.bytesSalt, SaltCharSet); err != nil { return nil, fmt.Errorf("%w: %v", algorithm.ErrSaltReadRandomBytes, err) } return h.hashWithSalt(password, salt) } // HashWithSalt overloads the Hash method allowing the user to provide a salt. It's recommended instead to configure the // salt size and let this be a random value generated using crypto/rand. func (h *Hasher) HashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { h.defaults() if digest, err = h.hashWithSalt(password, salt); err != nil { return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err) } return digest, nil } func (h *Hasher) hashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) { if s := len(salt); s > SaltLengthMax || s < SaltLengthMin { 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)) } d := &Digest{ variant: h.variant, iterations: h.iterations, salt: salt, } d.defaults() d.key = xcrypt.KeySHACrypt(d.variant.HashFunc(), []byte(password), d.salt, d.iterations) return d, nil } // MustHash overloads the Hash method and panics if the error is not nil. It's recommended if you use this option to // utilize the Validate method first or handle the panic appropriately. func (h *Hasher) MustHash(password string) (digest algorithm.Digest) { var err error if digest, err = h.Hash(password); err != nil { panic(err) } return digest } // Validate checks the settings/parameters for this shacrypt.Hasher and returns an error. func (h *Hasher) Validate() (err error) { h.defaults() if err = h.validate(); err != nil { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, err) } return nil } func (h *Hasher) validate() (err error) { h.defaults() return nil } func (h *Hasher) defaults() { if h.d { return } h.d = true if h.bytesSalt < SaltLengthMin { h.bytesSalt = algorithm.SaltLengthDefault } } crypt-0.4.7/algorithm/shacrypt/opts.go000066400000000000000000000053101510400225400200120ustar00rootroot00000000000000package shacrypt import ( "fmt" "github.com/go-crypt/crypt/algorithm" ) // Opt describes the functional option pattern for the shacrypt.Hasher. type Opt func(h *Hasher) (err error) // WithVariant configures the shacrypt.Variant of the resulting shacrypt.Digest. // Default is shacrypt.VariantSHA512. func WithVariant(variant Variant) Opt { return func(h *Hasher) (err error) { switch variant { case VariantNone: return nil case VariantSHA256, VariantSHA512: h.variant = variant return nil default: return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant '%d' is invalid", algorithm.ErrParameterInvalid, variant)) } } } // WithVariantName uses the variant name or identifier to configure the shacrypt.Variant of the resulting shacrypt.Digest. // Default is shacrypt.VariantSHA512. func WithVariantName(identifier string) Opt { return func(h *Hasher) (err error) { if identifier == "" { return nil } variant := NewVariant(identifier) if variant == VariantNone { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant identifier '%s' is invalid", algorithm.ErrParameterInvalid, identifier)) } h.variant = variant return nil } } // WithSHA256 adjusts this Hasher to utilize the SHA256 hash.Hash. func WithSHA256() Opt { return func(h *Hasher) (err error) { h.variant = VariantSHA256 return nil } } // WithSHA512 adjusts this Hasher to utilize the SHA512 hash.Hash. func WithSHA512() Opt { return func(h *Hasher) (err error) { h.variant = VariantSHA512 return nil } } // WithIterations sets the iterations parameter of the resulting shacrypt.Digest. // Minimum 1000, Maximum 999999999. Default is 1000000. func WithIterations(iterations int) Opt { return func(h *Hasher) (err error) { if iterations < IterationsMin || iterations > IterationsMax { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "iterations", IterationsMin, "", IterationsMax, iterations)) } h.iterations = iterations return nil } } // WithRounds is an alias for shacrypt.WithIterations. func WithRounds(rounds int) Opt { return WithIterations(rounds) } // WithSaltLength adjusts the salt size (in bytes) of the resulting shacrypt.Digest. // Minimum 1, Maximum 16. Default is 16. func WithSaltLength(bytes int) Opt { return func(h *Hasher) (err error) { if bytes < SaltLengthMin || bytes > SaltLengthMax { return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "salt length", SaltLengthMin, "", SaltLengthMax, bytes)) } h.bytesSalt = bytes return nil } } crypt-0.4.7/algorithm/shacrypt/variant.go000066400000000000000000000037531510400225400205020ustar00rootroot00000000000000package shacrypt import ( "crypto/sha256" "crypto/sha512" "github.com/go-crypt/crypt/algorithm" ) // NewVariant converts an identifier string to a shacrypt.Variant. func NewVariant(identifier string) Variant { switch identifier { case AlgIdentifierSHA256, algorithm.DigestSHA256: return VariantSHA256 case AlgIdentifierSHA512, algorithm.DigestSHA512: return VariantSHA512 default: return VariantSHA512 } } // Variant is a variant of the shacrypt.Digest. type Variant int const ( // VariantNone is a variant of the shacrypt.Digest which is unknown. VariantNone Variant = iota // VariantSHA256 is a variant of the shacrypt.Digest which uses SHA-256. VariantSHA256 // VariantSHA512 is a variant of the shacrypt.Digest which uses SHA-512. VariantSHA512 ) // String implements the fmt.Stringer returning a string representation of the shacrypt.Variant. func (v Variant) String() (identifier string) { switch v { case VariantSHA256: return algorithm.DigestSHA256 case VariantSHA512: return algorithm.DigestSHA512 default: return } } // Prefix returns the shacrypt.Variant prefix identifier. func (v Variant) Prefix() (prefix string) { switch v { case VariantSHA256: return AlgIdentifierSHA256 case VariantSHA512: return AlgIdentifierSHA512 default: return AlgIdentifierSHA512 } } // Name returns the Variant name. func (v Variant) Name() (s string) { switch v { case VariantSHA256: return algorithm.DigestSHA256 case VariantSHA512: return algorithm.DigestSHA512 default: return algorithm.DigestSHA512 } } // HashFunc returns the internal HMAC HashFunc. func (v Variant) HashFunc() algorithm.HashFunc { switch v { case VariantSHA256: return sha256.New case VariantSHA512: return sha512.New default: return sha512.New } } // DefaultIterations returns the default iterations for the particular variant. func (v Variant) DefaultIterations() int { switch v { case VariantSHA512: return IterationsDefaultSHA512 default: return IterationsDefaultSHA256 } } crypt-0.4.7/algorithm/types.go000066400000000000000000000042331510400225400163370ustar00rootroot00000000000000package algorithm import ( "fmt" "hash" ) // Hash is an interface which implements password hashing. type Hash interface { // Validate checks the hasher configuration to ensure it's valid. This should be used when the Hash is going to be // reused and you should use it in conjunction with MustHash. Validate() (err error) // Hash performs the hashing operation on a password and resets any relevant parameters such as a manually set salt. // It then returns a Digest and error. Hash(password string) (hashed Digest, err error) // HashWithSalt is an overload of Digest that also accepts a salt. HashWithSalt(password string, salt []byte) (hashed Digest, err error) // MustHash overloads the Hash method and panics if the error is not nil. It's recommended if you use this method to // utilize the Validate method first or handle the panic appropriately. MustHash(password string) (hashed Digest) } // Matcher is an interface used to match passwords. type Matcher interface { Match(password string) (match bool) MatchBytes(passwordBytes []byte) (match bool) MatchAdvanced(password string) (match bool, err error) MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) } // Digest represents a hashed password. It's implemented by all hashed password results so that when we pass a // stored hash into its relevant type we can verify the password against the hash. type Digest interface { fmt.Stringer Matcher Encode() (hash string) } // DecodeFunc describes a function to decode an encoded digest into a algorithm.Digest. type DecodeFunc func(encodedDigest string) (digest Digest, err error) // DecoderRegister describes an implementation that allows registering DecodeFunc's. type DecoderRegister interface { RegisterDecodeFunc(prefix string, decoder DecodeFunc) (err error) RegisterDecodePrefix(prefix, identifier string) (err error) Decoder } // Decoder is a representation of a implementation that performs generic decoding. Currently this is just intended for // use by implementers. type Decoder interface { Decode(encodedDigest string) (digest Digest, err error) } // HashFunc is a function which returns a hash.Hash. type HashFunc func() hash.Hash crypt-0.4.7/const.go000066400000000000000000000007021510400225400143300ustar00rootroot00000000000000package crypt import ( "github.com/go-crypt/crypt/internal/encoding" ) const ( // Delimiter for all storage formats. Delimiter = encoding.DelimiterStr ) const ( // StorageFormatPrefixLDAPCrypt is a prefix used by OpenLDAP for crypt format encoded digests. StorageFormatPrefixLDAPCrypt = "{CRYPT}" // StorageFormatPrefixLDAPArgon2 is a prefix used by OpenLDAP for argon2 format encoded digests. StorageFormatPrefixLDAPArgon2 = "{ARGON2}" ) crypt-0.4.7/const_test.go000066400000000000000000000001271510400225400153700ustar00rootroot00000000000000package crypt const ( password = "password" wrongPassword = "wrong_password" ) crypt-0.4.7/decode.go000066400000000000000000000017251510400225400144330ustar00rootroot00000000000000package crypt import ( "github.com/go-crypt/crypt/algorithm" ) // The global Decoder. This is utilized by the Decode function. var gdecoder *Decoder // Decode is a convenience function which wraps the Decoder functionality. It's recommended to create your own decoder // instead via NewDecoder or NewDefaultDecoder. // // CRITICAL STABILITY NOTE: the decoders loaded via this function are not guaranteed to remain the same. It is strongly // recommended that users implementing this library use the NewDecoder function and explicitly register each decoder // which they wish to support. func Decode(encodedDigest string) (digest algorithm.Digest, err error) { if digest, err = decode(encodedDigest); err != nil { return nil, err } return digest, nil } func decode(encodedDigest string) (digest algorithm.Digest, err error) { if gdecoder == nil { if gdecoder, err = NewDefaultDecoder(); err != nil { return nil, err } } return gdecoder.Decode(encodedDigest) } crypt-0.4.7/decoder.go000066400000000000000000000144111510400225400146110ustar00rootroot00000000000000package crypt import ( "fmt" "strconv" "strings" "github.com/go-crypt/crypt/algorithm" "github.com/go-crypt/crypt/algorithm/argon2" "github.com/go-crypt/crypt/algorithm/bcrypt" "github.com/go-crypt/crypt/algorithm/md5crypt" "github.com/go-crypt/crypt/algorithm/pbkdf2" "github.com/go-crypt/crypt/algorithm/plaintext" "github.com/go-crypt/crypt/algorithm/scrypt" "github.com/go-crypt/crypt/algorithm/sha1crypt" "github.com/go-crypt/crypt/algorithm/shacrypt" "github.com/go-crypt/crypt/internal/encoding" ) // NewDecoder returns a new empty *Decoder. // // See Also: NewDefaultDecoder and NewDecoderAll. func NewDecoder() *Decoder { return &Decoder{ decoders: map[string]algorithm.DecodeFunc{}, prefixes: map[string]string{}, } } // NewDefaultDecoder returns the default decoder recommended for new implementations. // // Loaded Decoders: argon2, bcrypt, pbkdf2, scrypt, shacrypt. // // CRITICAL STABILITY NOTE: the decoders loaded via this function are not guaranteed to remain the same. It is strongly // recommended that users implementing this library use this or NewDecodersAll only as an example for building their own // decoder via NewDecoder instead which returns an empty decoder. It is much safer for security and stability to be // explicit in harmony with your specific use case. It is the responsibility of the implementer to determine which // password algorithms are sufficiently safe for their particular use case. func NewDefaultDecoder() (d *Decoder, err error) { d = &Decoder{ decoders: map[string]algorithm.DecodeFunc{}, prefixes: map[string]string{}, } if err = decoderProfileDefault(d); err != nil { return nil, err } return d, nil } // NewDecoderAll is the same as NewDefaultDecoder but it also adds legacy and/or insecure decoders. // // Loaded Decoders (in addition to NewDefaultDecoder): plaintext, md5crypt, sha1crypt. // // CRITICAL STABILITY NOTE: the decoders loaded via this function are not guaranteed to remain the same. It is strongly // recommended that users implementing this library use this or NewDecodersAll only as an example for building their own // decoder via NewDecoder instead which returns an empty decoder. It is much safer for security and stability to be // explicit in harmony with your specific use case. It is the responsibility of the implementer to determine which // password algorithms are sufficiently safe for their particular use case. func NewDecoderAll() (d *Decoder, err error) { d = &Decoder{ decoders: map[string]algorithm.DecodeFunc{}, prefixes: map[string]string{}, } if err = decoderProfileDefault(d); err != nil { return nil, err } if err = plaintext.RegisterDecoder(d); err != nil { return nil, fmt.Errorf("could not register the plaintext decoder: %w", err) } if err = md5crypt.RegisterDecoder(d); err != nil { return nil, fmt.Errorf("could not register the md5crypt decoder: %w", err) } if err = sha1crypt.RegisterDecoder(d); err != nil { return nil, fmt.Errorf("could not register the sha1crypt decoder: %w", err) } return d, nil } // Decoder is a struct which allows registering algorithm.DecodeFunc's and utilizing the programmatically to decode an // encoded digest with them. type Decoder struct { decoders map[string]algorithm.DecodeFunc prefixes map[string]string } // RegisterDecodeFunc registers a new algorithm.DecodeFunc with this Decoder against a specific identifier. func (d *Decoder) RegisterDecodeFunc(identifier string, decoder algorithm.DecodeFunc) (err error) { if d.decoders == nil { d.decoders = map[string]algorithm.DecodeFunc{} } if _, ok := d.decoders[identifier]; ok { return fmt.Errorf("decoder already registered for identifier '%s'", identifier) } d.decoders[identifier] = decoder return nil } // RegisterDecodePrefix registers a prefix which is matched by strings.HasPrefix. func (d *Decoder) RegisterDecodePrefix(prefix, identifier string) (err error) { if d.decoders == nil { return fmt.Errorf("no decoders are registered") } if d.prefixes == nil { d.prefixes = map[string]string{} } if _, ok := d.decoders[identifier]; !ok { return fmt.Errorf("decoder isn't registered for dentifier '%s'", identifier) } d.prefixes[prefix] = identifier return nil } // Decode an encoded digest into a algorithm.Digest. func (d *Decoder) Decode(encodedDigest string) (digest algorithm.Digest, err error) { if digest, err = d.decode(encodedDigest); err != nil { return nil, err } return digest, nil } func (d *Decoder) decode(encodedDigest string) (digest algorithm.Digest, err error) { for prefix, key := range d.prefixes { if strings.HasPrefix(encodedDigest, prefix) { return d.decoders[key](encodedDigest) } } encodedDigest = Normalize(encodedDigest) if len(encodedDigest) == 0 || rune(encodedDigest[0]) != encoding.Delimiter { 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)) } parts := encoding.Split(encodedDigest, 3) if len(parts) != 3 { 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) } if decodeFunc, ok := d.decoders[parts[1]]; ok { return decodeFunc(encodedDigest) } switch d { case gdecoder: return nil, fmt.Errorf("%w: the identifier '%s' is unknown to the global decoder", algorithm.ErrEncodedHashInvalidIdentifier, parts[1]) default: return nil, fmt.Errorf("%w: the identifier '%s' is unknown to the decoder", algorithm.ErrEncodedHashInvalidIdentifier, parts[1]) } } func decoderProfileDefault(decoder *Decoder) (err error) { if err = argon2.RegisterDecoder(decoder); err != nil { return fmt.Errorf("could not register the argon2 decoder: %w", err) } if err = bcrypt.RegisterDecoder(decoder); err != nil { return fmt.Errorf("could not register the bcrypt decoder: %w", err) } if err = pbkdf2.RegisterDecoder(decoder); err != nil { return fmt.Errorf("could not register the pbkdf2 decoder: %w", err) } if err = scrypt.RegisterDecoder(decoder); err != nil { return fmt.Errorf("could not register the scrypt decoder: %w", err) } if err = shacrypt.RegisterDecoder(decoder); err != nil { return fmt.Errorf("could not register the shacrypt decoder: %w", err) } return nil } crypt-0.4.7/doc.go000066400000000000000000000015771510400225400137620ustar00rootroot00000000000000// Package crypt provides helpful abstractions for github.com/go-crypt/x. These abstractions allow generating password // hashes, encoding them in a common storage format, and comparing them to ensure they are valid. // // It's recommended that you either use crypt.NewDefaultDecoder for decoding existing encoded digests into the // algorithm.Digest. The Match function on the algorithm.Digest as well as the other methods described by // algorithm.Matcher can be utilized to validate passwords. // // The algorithm.Digest implementations include an Encode method which encodes the algorithm.Digest in the PHC String Format. // // To create new algorithm.Digest results you can utilize the algorithm.Hash implementations which exist for each algorithm. // The implementations utilize the functional options pattern where all options methods have the pattern With* or // Without*. package crypt crypt-0.4.7/go.mod000066400000000000000000000005111510400225400137570ustar00rootroot00000000000000module github.com/go-crypt/crypt go 1.24.0 toolchain go1.25.4 require ( github.com/go-crypt/x v0.4.9 github.com/stretchr/testify v1.11.1 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/sys v0.38.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) crypt-0.4.7/go.sum000066400000000000000000000022551510400225400140130ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-crypt/x v0.4.9 h1:vntXq1sbMCUfEyR5aCYAJgQdioTsHhlPK3WrudzUS5o= github.com/go-crypt/x v0.4.9/go.mod h1:RJOdhkuk1mjVm0AKW1lmQ6d1LIXtR1jTskxl3ZQenAA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= crypt-0.4.7/helper.go000066400000000000000000000034111510400225400144610ustar00rootroot00000000000000package crypt import ( "github.com/go-crypt/crypt/algorithm" ) // CheckPassword takes the string password and an encoded digest. It decodes the Digest, then performs the // MatchAdvanced() function on the Digest. If any process returns an error it returns false with the error, otherwise // it returns the result of MatchAdvanced(). This is just a helper function and implementers can manually invoke this // process themselves in situations where they may want to store the Digest to perform matches at a later date to avoid // decoding multiple times for example. // // CRITICAL STABILITY NOTE: the decoders loaded via this function are not guaranteed to remain the same. It is strongly // recommended that users implementing this library use the NewDecoder function and explicitly register each decoder // which they wish to support. func CheckPassword(password, encodedDigest string) (valid bool, err error) { var digest algorithm.Digest if digest, err = Decode(encodedDigest); err != nil { return false, err } return digest.MatchAdvanced(password) } // CheckPasswordWithPlainText is the same as CheckPassword however it also allows the plaintext passwords. // // CRITICAL STABILITY NOTE: the decoders loaded via this function are not guaranteed to remain the same. It is strongly // recommended that users implementing this library use the NewDecoder function and explicitly register each decoder // which they wish to support. func CheckPasswordWithPlainText(password, encodedDigest string) (valid bool, err error) { var ( digest algorithm.Digest decoder algorithm.Decoder ) if decoder, err = NewDecoderAll(); err != nil { return false, err } if digest, err = decoder.Decode(encodedDigest); err != nil { return false, err } return digest.MatchAdvanced(password) } crypt-0.4.7/internal/000077500000000000000000000000001510400225400144705ustar00rootroot00000000000000crypt-0.4.7/internal/encoding/000077500000000000000000000000001510400225400162565ustar00rootroot00000000000000crypt-0.4.7/internal/encoding/base64adapted.go000066400000000000000000000005311510400225400212130ustar00rootroot00000000000000package encoding import ( "encoding/base64" ) const ( encodeBase64Adapted = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./" ) var ( // Base64RawAdaptedEncoding is the adapted encoding for crypt purposes without padding. Base64RawAdaptedEncoding = base64.NewEncoding(encodeBase64Adapted).WithPadding(base64.NoPadding) ) crypt-0.4.7/internal/encoding/const.go000066400000000000000000000002631510400225400177340ustar00rootroot00000000000000package encoding const ( // Delimiter rune for all encodings. Delimiter = rune('$') // DelimiterStr is the string variation of Delimiter. DelimiterStr = string(Delimiter) ) crypt-0.4.7/internal/encoding/digest.go000066400000000000000000000003231510400225400200620ustar00rootroot00000000000000package encoding import ( "strings" ) // Split an encoded digest by the encoding.Delimiter. func Split(encodedDigest string, n int) (parts []string) { return strings.SplitN(encodedDigest, DelimiterStr, n) } crypt-0.4.7/internal/encoding/doc.go000066400000000000000000000001151510400225400173470ustar00rootroot00000000000000// Package encoding is an internal encoding helper package. package encoding crypt-0.4.7/internal/encoding/parameters.go000066400000000000000000000030051510400225400207460ustar00rootroot00000000000000package encoding import ( "fmt" "strconv" "strings" ) // Parameter is a key value pair. type Parameter struct { Key string Value string } // Int converts the Value to an int using strconv.Atoi. func (p Parameter) Int() (int, error) { return strconv.Atoi(p.Value) } const ( // ParameterDefaultItemSeparator is the default item separator. ParameterDefaultItemSeparator = "," // ParameterDefaultKeyValueSeparator is the default key value separator. ParameterDefaultKeyValueSeparator = "=" ) // DecodeParameterStr is an alias for DecodeParameterStrAdvanced using item separator and key value separator // of ',' and '=' respectively. func DecodeParameterStr(input string) (opts []Parameter, err error) { return DecodeParameterStrAdvanced(input, ParameterDefaultItemSeparator, ParameterDefaultKeyValueSeparator) } // DecodeParameterStrAdvanced decodes parameter strings into a []Parameter where sepItem separates each parameter, and sepKV separates the key and value. func DecodeParameterStrAdvanced(input string, sepItem, sepKV string) (opts []Parameter, err error) { if input == "" { return nil, fmt.Errorf("empty strings can't be decoded to parameters") } o := strings.Split(input, sepItem) opts = make([]Parameter, len(o)) for i, joined := range o { kv := strings.SplitN(joined, sepKV, 2) if len(kv) != 2 { return nil, fmt.Errorf("parameter pair '%s' is not properly encoded: does not contain kv separator '%s'", joined, sepKV) } opts[i] = Parameter{Key: kv[0], Value: kv[1]} } return opts, nil } crypt-0.4.7/internal/math/000077500000000000000000000000001510400225400154215ustar00rootroot00000000000000crypt-0.4.7/internal/math/doc.go000066400000000000000000000001101510400225400165050ustar00rootroot00000000000000// Package math is an internal mathematics helper package. package math crypt-0.4.7/internal/math/rounding.go000066400000000000000000000006021510400225400175730ustar00rootroot00000000000000package math // RoundDownToNearestMultiple returns the nearest multiple of value. func RoundDownToNearestMultiple(value, multiple int) int { return (value / multiple) * multiple } // Uint32RoundDownToNearestMultiple returns the nearest multiple of value (uint32 version). func Uint32RoundDownToNearestMultiple(value, multiple uint32) uint32 { return (value / multiple) * multiple } crypt-0.4.7/internal/random/000077500000000000000000000000001510400225400157505ustar00rootroot00000000000000crypt-0.4.7/internal/random/bytes.go000066400000000000000000000011701510400225400174240ustar00rootroot00000000000000package random import ( "crypto/rand" "io" ) // Bytes returns random arbitrary bytes with a length of n. func Bytes(n int) (bytes []byte, err error) { bytes = make([]byte, n) if _, err = io.ReadFull(rand.Reader, bytes); err != nil { return nil, err } return bytes, nil } // CharSetBytes returns random bytes with a length of n from the characters in the charset. func CharSetBytes(n int, charset string) (bytes []byte, err error) { bytes = make([]byte, n) if _, err = rand.Read(bytes); err != nil { return nil, err } for i, b := range bytes { bytes[i] = charset[b%byte(len(charset))] } return bytes, nil } crypt-0.4.7/internal/random/doc.go000066400000000000000000000001001510400225400170330ustar00rootroot00000000000000// Package random is an internal helper package. package random crypt-0.4.7/normalize.go000066400000000000000000000021011510400225400151750ustar00rootroot00000000000000package crypt import ( "fmt" "regexp" "strings" ) var ( reAlgorithmPrefixPBKDF2 = regexp.MustCompile(`^\{(?PPBKDF2(-SHA\d+)?)}(?P\d+\$.*)$`) ) // Normalize performs normalization on an encoded digest. This removes prefixes which are not necessary and performs // minimal modification to the encoded digest to make it possible for decoding. func Normalize(encodedDigest string) string { if strings.HasPrefix(encodedDigest, StorageFormatPrefixLDAPCrypt) { encodedDigest = encodedDigest[7:] } if strings.HasPrefix(encodedDigest, StorageFormatPrefixLDAPArgon2) { encodedDigest = encodedDigest[8:] } matchesPBKDF2 := reAlgorithmPrefixPBKDF2.FindStringSubmatch(encodedDigest) if len(matchesPBKDF2) != 0 { var identifier, remainder string for g, group := range reAlgorithmPrefixPBKDF2.SubexpNames() { switch group { case "identifier": identifier = matchesPBKDF2[g] case "remainder": identifier = matchesPBKDF2[g] } } encodedDigest = fmt.Sprintf("$%s$%s", strings.ToLower(identifier), remainder) } return encodedDigest } crypt-0.4.7/t_argon2_test.go000066400000000000000000000020641510400225400157570ustar00rootroot00000000000000package crypt import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestArgon2Outputs(t *testing.T) { testcCases := []struct { name string have string }{ { "ShouldValidatePasswordArgon2id", "$argon2id$v=19$m=65536,t=3,p=4$QmkpoTw3W72fzd7RrWofuw$r0xig+VVj7ynnE2S1jrE5us7dPKv2S2ff6Z6ts4mVuU", }, { "ShouldValidatePasswordArgon2i", "$argon2i$v=19$m=65536,t=3,p=4$ScGiEq8Low5K7B7/IwYxgA$q6Zo0u/aDtZk404ZNmBi33WXkC5g0y60QdOQQ3oziyU", }, { "ShouldValidatePasswordArgon2d", "$argon2d$v=19$m=65536,t=3,p=4$HV/RIiFSYEMoRYqBcFnqfg$eGNckPZjkL2xOIHZv8Q4ROg5xbcdD8ijIJOgPwVAPmA", }, } for _, tc := range testcCases { t.Run(tc.name, func(t *testing.T) { t.Run("CorrectPassword", func(t *testing.T) { valid, err := CheckPassword(password, tc.have) require.NoError(t, err) assert.True(t, valid) }) t.Run("IncorrectPassword", func(t *testing.T) { valid, err := CheckPassword(wrongPassword, tc.have) require.NoError(t, err) assert.False(t, valid) }) }) } } crypt-0.4.7/t_bcrypt_test.go000066400000000000000000000032221510400225400160670ustar00rootroot00000000000000package crypt import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestBcryptOutputs(t *testing.T) { testcCases := []struct { name string have string }{ { "ShouldValidatePasswordStandardVariantB", "$2b$10$3o9IF74Phgdz4Q6j7K7s0unovt.v.7YBLKFyV73pGTd2.tfdz/F8e", }, { "ShouldValidatePasswordStandardVariantA", "$2a$10$3o9IF74Phgdz4Q6j7K7s0unovt.v.7YBLKFyV73pGTd2.tfdz/F8e", }, { "ShouldValidatePasswordStandardVariantX", "$2x$10$3o9IF74Phgdz4Q6j7K7s0unovt.v.7YBLKFyV73pGTd2.tfdz/F8e", }, { "ShouldValidatePasswordStandardVariantY", "$2y$10$3o9IF74Phgdz4Q6j7K7s0unovt.v.7YBLKFyV73pGTd2.tfdz/F8e", }, { "ShouldValidatePasswordSHA256VariantB", "$bcrypt-sha256$v=2,t=2b,r=10$oYmTNJVOBi3hdhUYy4JqOe$jCuMDm.Pw9hhoF/FDC6sOi48yBAoWvC", }, { "ShouldValidatePasswordSHA256VariantA", "$bcrypt-sha256$v=2,t=2a,r=10$oYmTNJVOBi3hdhUYy4JqOe$jCuMDm.Pw9hhoF/FDC6sOi48yBAoWvC", }, { "ShouldValidatePasswordSHA256VariantX", "$bcrypt-sha256$v=2,t=2x,r=10$oYmTNJVOBi3hdhUYy4JqOe$jCuMDm.Pw9hhoF/FDC6sOi48yBAoWvC", }, { "ShouldValidatePasswordSHA256VariantY", "$bcrypt-sha256$v=2,t=2y,r=10$oYmTNJVOBi3hdhUYy4JqOe$jCuMDm.Pw9hhoF/FDC6sOi48yBAoWvC", }, } for _, tc := range testcCases { t.Run(tc.name, func(t *testing.T) { t.Run("CorrectPassword", func(t *testing.T) { valid, err := CheckPassword(password, tc.have) require.NoError(t, err) assert.True(t, valid) }) t.Run("IncorrectPassword", func(t *testing.T) { valid, err := CheckPassword(wrongPassword, tc.have) require.NoError(t, err) assert.False(t, valid) }) }) } } crypt-0.4.7/t_pbkdf2_test.go000066400000000000000000000025171510400225400157420ustar00rootroot00000000000000package crypt import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestPBKDF2Outputs(t *testing.T) { testcCases := []struct { name string have string }{ { "ShouldValidatePasswordSHA1", "$pbkdf2$100000$atrXFCWdBlpmzIi/nXwJOw$20Lsx44nZwmh09bjGHFJ//oRZh8", }, { "ShouldValidatePasswordSHA224", "$pbkdf2-sha224$100000$qRzrfXPp6ilID9bO89rJkA$akPivgY3p3gLDj8Kd7agycHkM5b0xlTxeLEsqg", }, { "ShouldValidatePasswordSHA256", "$pbkdf2-sha256$100000$aoWHXwyz0im1Hqg93.N.tA$bO5LsjmnnPle2Xm9RE6W1PMWdJTy1TnEia1TLzynuIQ", }, { "ShouldValidatePasswordSHA384", "$pbkdf2-sha384$100000$GIZt3eMjZrEs0ycxed3zHg$o8IZWpxd.shbcATBSk9nHqktuvLTv1YeLYowxZM7mO5hhWLa3s4tVFejl9NH9jSO", }, { "ShouldValidatePasswordSHA512", "$pbkdf2-sha512$100000$bHfSOIyj0UDoCo1Q4Bz49w$v/olF/T/R6On84NuHlNCiI/sUwsdyOC7J4cO8Cz7feNtLHHEKNjayeEZj0b/Js/cgkMK6zLFw2vynLo2el028Q", }, } for _, tc := range testcCases { t.Run(tc.name, func(t *testing.T) { t.Run("CorrectPassword", func(t *testing.T) { valid, err := CheckPassword(password, tc.have) require.NoError(t, err) assert.True(t, valid) }) t.Run("IncorrectPassword", func(t *testing.T) { valid, err := CheckPassword(wrongPassword, tc.have) require.NoError(t, err) assert.False(t, valid) }) }) } } crypt-0.4.7/t_scrypt_test.go000066400000000000000000000035461510400225400161210ustar00rootroot00000000000000package crypt import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/go-crypt/crypt/algorithm/scrypt" ) func TestScryptOutputs(t *testing.T) { testcCases := []struct { name string have string expected string }{ { "ShouldValidateScrypt", "$scrypt$ln=4,r=8,p=1$ySYknWRq9On6wWfpsOUQQg$C28LpWaXQ3P0/dcbN0njxJx4VL/UCQIAWlnYAJgT/mY", password, }, { "ShouldValidateYeScrpytNative", "$y$j75$z7ztFz2FayrKI79/jEwlL.$u5x/j193MQ09wbFaRGYr0AH/A/jh3kunjuhYRVRNkmC", "test1", }, } for _, tc := range testcCases { t.Run(tc.name, func(t *testing.T) { t.Run("CorrectPassword", func(t *testing.T) { valid, err := CheckPassword(tc.expected, tc.have) require.NoError(t, err) assert.True(t, valid) }) t.Run("IncorrectPassword", func(t *testing.T) { valid, err := CheckPassword(wrongPassword, tc.have) require.NoError(t, err) assert.False(t, valid) }) }) } } func TestScryptOutputsNative(t *testing.T) { testcCases := []struct { name string have string expected string }{ { "ShouldValidateYeScrpytExample", "$y$j75$z7ztFz2FayrKI79/jEwlL.$u5x/j193MQ09wbFaRGYr0AH/A/jh3kunjuhYRVRNkmC", "test1", }, { "ShouldValidateYesCryptOutput", "$y$jD5$K3wjJ.n1W9g1TfLeI0ESC0$SAt46wIbyewhlHlKVQcelosVETYUGOaV6mC1qjurql9", "password", }, } for _, tc := range testcCases { t.Run(tc.name, func(t *testing.T) { valid, err := CheckPassword(tc.expected, tc.have) require.NoError(t, err) assert.True(t, valid) }) } } func TestScryptEncodeDecode(t *testing.T) { hash, err := scrypt.NewYescrypt() require.NoError(t, err) digest, err := hash.HashWithSalt("password", []byte("aa131311")) require.NoError(t, err) raw := digest.Encode() assert.Equal(t, "$y$jD5$V3KAn2nAl21$2f0mscSRW3Z0u.oHoVtRAfYwQ3ZbWUIbi4SB04ztMSB", raw) } crypt-0.4.7/t_shacrypt_test.go000077500000000000000000000032371510400225400164320ustar00rootroot00000000000000package crypt import ( "testing" "github.com/stretchr/testify/assert" ) func TestSHACRYPTOutputsMKPASSWD(t *testing.T) { testcCases := []struct { name string have string expected string valid bool err string }{ { "ShouldValidatePasswordWithOmittedRoundsSHA256", "$5$4X/QmdRP6q7Ilhpc$2sperIXN6jawEYd8a8arineQHqYIEGURjZGdD4H4xs8", password, true, "", }, { "ShouldNotValidatePasswordWithOmittedRoundsSHA256", "$5$4X/QmdRP6q7Ilhpc$2sperIXN6jawEYd8a8arineQHqYIEGURjZGdD4H4xs8", wrongPassword, false, "", }, { "ShouldValidatePasswordWithOmittedRoundsSHA512", "$6$rB2PL49BuajVczWm$sA.XUPEt/j6k4kFnO58EDKsEU8rXau47.eSH6lpqc/tgC9Y0BbYcG7H3.KmMMpthWMcip/xmDn83nTUXK5Vp90", password, true, "", }, { "ShouldNotValidatePasswordWithOmittedRoundsSHA512", "$6$rB2PL49BuajVczWm$sA.XUPEt/j6k4kFnO58EDKsEU8rXau47.eSH6lpqc/tgC9Y0BbYcG7H3.KmMMpthWMcip/xmDn83nTUXK5Vp90", wrongPassword, false, "", }, { "ShouldNotValidatePasswordWithRoundsSHA512", "$6$rounds=1000$rB2PL49BuajVczWm$sA.XUPEt/j6k4kFnO58EDKsEU8rXau47.eSH6lpqc/tgC9Y0BbYcG7H3.KmMMpthWMcip/xmDn83nTUXK5Vp90", wrongPassword, false, "", }, { "ShouldValidatePasswordWithRoundsSHA512", "$6$rounds=1000$eG49klxUKySvBpju$.1paw1pj51FdmvNAnsNoX8lyHMdH/S74DfkZnWWTOnGI9keTp/DXjR9ro5kJrncPSF5fc.krAwdkBxc4C8kSU1", password, true, "", }, } for _, tc := range testcCases { t.Run(tc.name, func(t *testing.T) { valid, err := CheckPassword(tc.expected, tc.have) assert.Equal(t, tc.valid, valid) if len(tc.err) == 0 { assert.NoError(t, err) } else { assert.EqualError(t, err, tc.err) } }) } } crypt-0.4.7/types.go000066400000000000000000000155451510400225400143610ustar00rootroot00000000000000package crypt import ( "database/sql/driver" "fmt" "github.com/go-crypt/crypt/algorithm" ) // NewDigest wraps an algorithm.Digest in the convenience layer of the crypt.Digest. func NewDigest(d algorithm.Digest) (digest *Digest, err error) { if d == nil { return nil, fmt.Errorf("can't create crypt.Digest from nil") } return &Digest{digest: d}, nil } // NewDigestDecode decodes a string into a algorithm.Digest and wraps it in the convenience layer of the crypt.Digest. func NewDigestDecode(encodedDigest string) (digest *Digest, err error) { if len(encodedDigest) == 0 { return nil, fmt.Errorf("can't create crypt.Digest from empty string") } var d algorithm.Digest if d, err = Decode(encodedDigest); err != nil { return nil, err } return &Digest{digest: d}, nil } // NewNullDigest wraps an algorithm.Digest in the convenience layer of the crypt.NullDigest. func NewNullDigest(d algorithm.Digest) (digest *NullDigest) { return &NullDigest{digest: d} } // NewNullDigestDecode decodes a string into a algorithm.Digest and wraps it in the convenience layer of the crypt.NullDigest. func NewNullDigestDecode(encodedDigest string) (digest *NullDigest, err error) { if len(encodedDigest) == 0 { return &NullDigest{}, nil } var ( d algorithm.Digest ) if d, err = Decode(encodedDigest); err != nil { return nil, err } return &NullDigest{digest: d}, nil } // Digest is a decorator struct which wraps the algorithm.Digest and adds sql.Scanner/driver.Valuer, // encoding.TextMarshaler/encoding.TextUnmarshaler, and encoding.BinaryMarshaler/encoding.BinaryUnmarshaler // implementations. type Digest struct { digest algorithm.Digest } // Encode decorates the algorithm.Digest Encode function. func (d *Digest) Encode() string { return d.digest.Encode() } // String decorates the algorithm.Digest String function. func (d *Digest) String() string { return d.digest.String() } // MatchBytes decorates the algorithm.Digest MatchBytes function. func (d *Digest) MatchBytes(passwordBytes []byte) (match bool) { return d.digest.MatchBytes(passwordBytes) } // MatchAdvanced decorates the algorithm.Digest MatchAdvanced function. func (d *Digest) MatchAdvanced(password string) (match bool, err error) { return d.digest.MatchAdvanced(password) } // MatchBytesAdvanced decorates the algorithm.Digest MatchBytesAdvanced function. func (d *Digest) MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) { return d.digest.MatchBytesAdvanced(passwordBytes) } // Match decorates the algorithm.Digest Match function. func (d *Digest) Match(password string) (match bool) { return d.digest.Match(password) } // Value implements driver.Valuer. func (d *Digest) Value() (value driver.Value, err error) { if d.digest == nil { return "", nil } return d.digest.Encode(), nil } // Scan implements sql.Scanner. func (d *Digest) Scan(src any) (err error) { switch digest := src.(type) { case nil: return fmt.Errorf("invalid type for crypt.Digest: can't scan nil value into crypt.Digest: use crypt.NullDigest instead") case string: if d.digest, err = Decode(digest); err != nil { return err } return nil case byte: if d.digest, err = Decode(string(digest)); err != nil { return err } return nil default: return fmt.Errorf("invalid type for crypt.Digest: can't scan %T into crypt.Digest", digest) } } // MarshalText implements encoding.TextMarshaler. func (d *Digest) MarshalText() (data []byte, err error) { if d.digest == nil { return []byte(""), nil } return []byte(d.digest.Encode()), nil } // UnmarshalText implements encoding.TextUnmarshaler. func (d *Digest) UnmarshalText(data []byte) (err error) { if len(data) == 0 { return fmt.Errorf("can't unmarhsal empty data to crypt.Digest") } var digest algorithm.Digest if digest, err = Decode(string(data)); err != nil { return err } d.digest = digest return nil } // MarshalBinary implements encoding.BinaryMarshaler. func (d *Digest) MarshalBinary() (data []byte, err error) { return d.MarshalText() } // UnmarshalBinary implements encoding.BinaryUnmarshaler. func (d *Digest) UnmarshalBinary(data []byte) (err error) { return d.UnmarshalText(data) } // NullDigest is variation of crypt.Digest which accepts nulls. type NullDigest struct { digest algorithm.Digest } // Encode decorates the algorithm.Digest Encode function. func (d *NullDigest) Encode() string { if d.digest == nil { return "" } return d.digest.Encode() } // String decorates the algorithm.Digest String function. func (d *NullDigest) String() string { if d.digest == nil { return "" } return d.digest.String() } // Match decorates the algorithm.Digest Match function. func (d *NullDigest) Match(password string) (match bool) { if d.digest == nil { return false } return d.digest.Match(password) } // MatchBytes decorates the algorithm.Digest MatchBytes function. func (d *NullDigest) MatchBytes(passwordBytes []byte) (match bool) { if d.digest == nil { return false } return d.digest.MatchBytes(passwordBytes) } // MatchAdvanced decorates the algorithm.Digest MatchAdvanced function. func (d *NullDigest) MatchAdvanced(password string) (match bool, err error) { if d.digest == nil { return false, nil } return d.digest.MatchAdvanced(password) } // MatchBytesAdvanced decorates the algorithm.Digest MatchBytesAdvanced function. func (d *NullDigest) MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) { if d.digest == nil { return false, nil } return d.digest.MatchBytesAdvanced(passwordBytes) } // Value implements driver.Valuer. func (d *NullDigest) Value() (value driver.Value, err error) { if d.digest == nil { return nil, nil } return d.digest.Encode(), nil } // Scan implements sql.Scanner. func (d *NullDigest) Scan(src any) (err error) { switch digest := src.(type) { case nil: d.digest = nil return nil case string: if d.digest, err = Decode(digest); err != nil { return err } return nil case byte: if d.digest, err = Decode(string(digest)); err != nil { return err } return nil default: return fmt.Errorf("invalid type for crypt.Digest: can't scan %T into crypt.Digest", digest) } } // MarshalText implements encoding.TextMarshaler. func (d *NullDigest) MarshalText() (data []byte, err error) { if d.digest == nil { return nil, nil } return []byte(d.digest.Encode()), nil } // UnmarshalText implements encoding.TextUnmarshaler. func (d *NullDigest) UnmarshalText(data []byte) (err error) { if len(data) == 0 { d.digest = nil return nil } var digest algorithm.Digest if digest, err = Decode(string(data)); err != nil { return err } d.digest = digest return nil } // MarshalBinary implements encoding.BinaryMarshaler. func (d *NullDigest) MarshalBinary() (data []byte, err error) { return d.MarshalText() } // UnmarshalBinary implements encoding.BinaryUnmarshaler. func (d *NullDigest) UnmarshalBinary(data []byte) (err error) { return d.UnmarshalText(data) }