From dc170dc46e8f81321e28c4dac96866baf2b25cd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Tue, 25 Jun 2024 11:10:49 +0300 Subject: [PATCH] feat(crypto): support md5 plain for imported password hashes (#8189) # Which Problems Are Solved Allow verification of imported passwords hashed with plain md5, without salt. These are password digests typically created by one of: - `printf "password" | md5sum` on most linux systems. - PHP's `md5("password")` - Python3's `hashlib.md5(b"password").hexdigest()` # How the Problems Are Solved - Upgrade passwap to [v0.6.0](https://github.com/zitadel/passwap/releases/tag/v0.6.0) - Add md5plain as a new verfier option in `defaults.yaml` # Additional Changes - Updated documentation to explain difference between `md5` (crypt) and `md5plain` verifiers. # Additional Context - Requested by customer for import case --- cmd/defaults.yaml | 7 ++++--- docs/docs/concepts/architecture/secrets.md | 3 ++- go.mod | 2 +- go.sum | 4 ++-- internal/crypto/passwap.go | 22 +++++++++++++++++++--- 5 files changed, 28 insertions(+), 10 deletions(-) diff --git a/cmd/defaults.yaml b/cmd/defaults.yaml index bb09354b99..7de25efb18 100644 --- a/cmd/defaults.yaml +++ b/cmd/defaults.yaml @@ -480,11 +480,12 @@ SystemDefaults: # # Supported verifiers: (uncomment to enable) Verifiers: # ZITADEL_SYSTEMDEFAULTS_PASSWORDHASHER_VERIFIERS - # - "argon2" # verifier for both argon2i and argon2id. + # - "argon2" # verifier for both argon2i and argon2id. # - "bcrypt" - # - "md5" + # - "md5" # md5Crypt with salt and password shuffling. + # - "md5plain" # md5 digest of a password without salt # - "scrypt" - # - "pbkdf2" # verifier for all pbkdf2 hash modes. + # - "pbkdf2" # verifier for all pbkdf2 hash modes. SecretHasher: # Set hasher configuration for machine users, API and OIDC client secrets. Hasher: diff --git a/docs/docs/concepts/architecture/secrets.md b/docs/docs/concepts/architecture/secrets.md index 0b79ef497e..2f8c196797 100644 --- a/docs/docs/concepts/architecture/secrets.md +++ b/docs/docs/concepts/architecture/secrets.md @@ -68,7 +68,8 @@ The following hash algorithms are supported: - argon2i / id[^1] - bcrypt (Default) -- md5[^2] +- md5: implementation of md5Crypt with salt and password shuffling [^2] +- md5plain: md5 digest of a password without salt [^2] - scrypt - pbkdf2 diff --git a/go.mod b/go.mod index cff93d4c84..f801360470 100644 --- a/go.mod +++ b/go.mod @@ -59,7 +59,7 @@ require ( github.com/ttacon/libphonenumber v1.2.1 github.com/zitadel/logging v0.6.0 github.com/zitadel/oidc/v3 v3.25.0 - github.com/zitadel/passwap v0.5.0 + github.com/zitadel/passwap v0.6.0 github.com/zitadel/saml v0.1.3 github.com/zitadel/schema v1.3.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 diff --git a/go.sum b/go.sum index 1ec896708a..77a9df95e5 100644 --- a/go.sum +++ b/go.sum @@ -731,8 +731,8 @@ github.com/zitadel/logging v0.6.0 h1:t5Nnt//r+m2ZhhoTmoPX+c96pbMarqJvW1Vq6xFTank github.com/zitadel/logging v0.6.0/go.mod h1:Y4CyAXHpl3Mig6JOszcV5Rqqsojj+3n7y2F591Mp/ow= github.com/zitadel/oidc/v3 v3.25.0 h1:DosOUc31IPM9ZtKaT58+0iNicwDFTFk5Ctt7mgYtsA8= github.com/zitadel/oidc/v3 v3.25.0/go.mod h1:UDwD+PRFbUBzabyPd9JORrakty3/wec7VpKZYi9Ahh0= -github.com/zitadel/passwap v0.5.0 h1:kFMoRyo0GnxtOz7j9+r/CsRwSCjHGRaAKoUe69NwPvs= -github.com/zitadel/passwap v0.5.0/go.mod h1:uqY7D3jqdTFcKsW0Q3Pcv5qDMmSHpVTzUZewUKC1KZA= +github.com/zitadel/passwap v0.6.0 h1:m9F3epFC0VkBXu25rihSLGyHvWiNlCzU5kk8RoI+SXQ= +github.com/zitadel/passwap v0.6.0/go.mod h1:kqAiJ4I4eZvm3Y6oAk6hlEqlZZOkjMHraGXF90GG7LI= github.com/zitadel/saml v0.1.3 h1:LI4DOCVyyU1qKPkzs3vrGcA5J3H4pH3+CL9zr9ShkpM= github.com/zitadel/saml v0.1.3/go.mod h1:MdkjyU3mwnTuh4lNnhPG+RyZL/VfzD72wUG/eWWBaXc= github.com/zitadel/schema v1.3.0 h1:kQ9W9tvIwZICCKWcMvCEweXET1OcOyGEuFbHs4o5kg0= diff --git a/internal/crypto/passwap.go b/internal/crypto/passwap.go index 986019c5ba..6ff0f4ea10 100644 --- a/internal/crypto/passwap.go +++ b/internal/crypto/passwap.go @@ -1,7 +1,9 @@ package crypto import ( + "encoding/hex" "fmt" + "slices" "strings" "github.com/mitchellh/mapstructure" @@ -9,6 +11,7 @@ import ( "github.com/zitadel/passwap/argon2" "github.com/zitadel/passwap/bcrypt" "github.com/zitadel/passwap/md5" + "github.com/zitadel/passwap/md5plain" "github.com/zitadel/passwap/pbkdf2" "github.com/zitadel/passwap/scrypt" "github.com/zitadel/passwap/verifier" @@ -18,7 +21,8 @@ import ( type Hasher struct { *passwap.Swapper - Prefixes []string + Prefixes []string + HexSupported bool } func (h *Hasher) EncodingSupported(encodedHash string) bool { @@ -27,6 +31,12 @@ func (h *Hasher) EncodingSupported(encodedHash string) bool { return true } } + if h.HexSupported { + _, err := hex.DecodeString(encodedHash) + if err == nil { + return true + } + } return false } @@ -38,6 +48,7 @@ const ( HashNameArgon2id HashName = "argon2id" // hash only HashNameBcrypt HashName = "bcrypt" // hash and verify HashNameMd5 HashName = "md5" // verify only, as hashing with md5 is insecure and deprecated + HashNameMd5Plain HashName = "md5plain" // verify only, as hashing with md5 is insecure and deprecated HashNameScrypt HashName = "scrypt" // hash and verify HashNamePBKDF2 HashName = "pbkdf2" // hash and verify ) @@ -69,8 +80,9 @@ func (c *HashConfig) NewHasher() (*Hasher, error) { return nil, zerrors.ThrowInvalidArgument(err, "CRYPT-Que4r", "password hash config invalid") } return &Hasher{ - Swapper: passwap.NewSwapper(hasher, verifiers...), - Prefixes: append(hPrefixes, vPrefixes...), + Swapper: passwap.NewSwapper(hasher, verifiers...), + Prefixes: append(hPrefixes, vPrefixes...), + HexSupported: slices.Contains(c.Verifiers, HashNameMd5Plain), }, nil } @@ -95,6 +107,10 @@ var knowVerifiers = map[HashName]prefixVerifier{ prefixes: []string{md5.Prefix}, verifier: md5.Verifier, }, + HashNameMd5Plain: { + prefixes: nil, // hex encoded without identifier or prefix + verifier: md5plain.Verifier, + }, HashNameScrypt: { prefixes: []string{scrypt.Prefix, scrypt.Prefix_Linux}, verifier: scrypt.Verifier,