Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(docs): write reference for encoding schemes #19

Merged
merged 2 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 109 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,19 @@ needs to be updated.

### Algorithms

| Algorithm | Identifiers | Secure |
|-----------|--------------------------------------------------------------------|--------------------|
| argon2 | argon2i, argon2id | :heavy_check_mark: |
| bcrypt | 2, 2a, 2b, 2y | :heavy_check_mark: |
| md5-crypt | 1 | :x: |
| scrypt | scrypt, 7 | :heavy_check_mark: |
| pbkpdf2 | pbkdf2, pbkdf2-sha224, pbkdf2-sha256, pbkdf2-sha384, pbkdf2-sha512 | :heavy_check_mark: |
| Algorithm | Identifiers | Secure |
| -------------- | ------------------------------------------------------------------ | ------------------ |
| [argon2][1] | argon2i, argon2id | :heavy_check_mark: |
| [bcrypt][2] | 2, 2a, 2b, 2y | :heavy_check_mark: |
| [md5-crypt][3] | 1 | :x: |
| [scrypt][4] | scrypt, 7 | :heavy_check_mark: |
| [pbkpdf2][5] | pbkdf2, pbkdf2-sha224, pbkdf2-sha256, pbkdf2-sha384, pbkdf2-sha512 | :heavy_check_mark: |

[1]: https://pkg.go.dev/github.com/zitadel/passwap/argon2
[2]: https://pkg.go.dev/github.com/zitadel/passwap/bcrypt
[3]: https://pkg.go.dev/github.com/zitadel/passwap/md5
[4]: https://pkg.go.dev/github.com/zitadel/passwap/scrypt
[5]: https://pkg.go.dev/github.com/zitadel/passwap/pbkdf2

### Encoding

Expand All @@ -43,10 +49,104 @@ would need to store the parameters used, salt and the resulting hash.
As the salt and hash are typically raw bytes, they also need to be converted
to characters, for example using base64.

All of the Passwap supplied algorithms use dollar sign (`$`) delimited
encoding. This results in a single string containing all of the above for
All of the Passwap supplied algorithms use the dollar sign (`$`) delimited
encoding, aka [Modular Crypt Format](https://passlib.readthedocs.io/en/stable/modular_crypt_format.htm).
This results in a single string containing all of the above for
later password verification.

#### Argon2

Argon2 uses standard raw Base64 encoding (without padding) for salt and hash.
The resulting Modular Crypt Format string looks as follows:

```
$argon2i$v=19$m=4096,t=3,p=1$cmFuZG9tc2FsdGlzaGFyZA$YMvo8AUoNtnKYGqeODruCjHdiEbl1pKL2MsYy9VgU/E
(1) (2) (3) (4)
```


1. The identifier, which can be `argon2i` or `argon2id`. `argon2d`, is not supported by Go, and therefore, is not supported by this library either.
2. Cost parameters.
1. `m` for memory -`4096` KiB in this example.
2. `t` for time - `3` in this example.
3. `p` for parallelism (threads) - `1` in this example.
3. Base64 encoded salt.
4. Base64 encoded Argon2 hash output of the password and salt combined.

Changing any of the parameters or salt produces a different hash output.
More information about the parameters can be found in the upstream [Argon2 package documentation](https://pkg.go.dev/golang.org/x/crypto/argon2).

### Bcrypt

Bcrypt uses a custom Base64 encoding with the character set of `[./A-Za-z0-9]` and padding.
The actual formatting is fully implemented by the [Go package](https://pkg.go.dev/golang.org/x/crypto/bcrypt).
The resulting Modular Crypt Format string looks as follows:

```
$2a$12$aLYFkieuqJyeynvptPTxpehSViui5WeAPuR2Xw1wui9CPHEaacmFq
(1)(2) (3) (4)
```

1. The identifier can be `2a`, `2b` or, `2y`. It indicates the Bcrypt version but is ignored and the same is always produced.
2. The cost parameter that is exponential - `12` in this example.
3. The Base64-encoded salt, always 22 character long.
4. The Base64-encoded Bcrypt hash output of the password and salt combined.


### MD5

MD5 uses its own encoding scheme, which is part of the [hashing algorithm](https://passlib.readthedocs.io/en/stable/lib/passlib.hash.md5_crypt.html#algorithm). It uses a similar alphabet as Base64 but performs an additional shuffling of bytes.
The resulting Modular Crypt Format string looks as follows:

```
$1$kJ4QkJaQ$3EbD/pJddrq5HW3mpZ4KZ1
(1) (2) (3)
```

1. The identifier is always `1`
2. Base64-like-encoded salt.
3. Base64-like-encoded MD5 hash output of the password and salt combined.

There is no cost parameter for MD5 because MD5 is old and is considered too light and insecure. It is provided to verify and migrate to a better algorithm. Do not use for new hashes.

### Scrypt

Scrypt uses standard raw Base64 encoding (no padding) for the salt and hash.
The resulting Modular Crypt Format string looks as follows:

```
$scrypt$ln=16,r=8,p=1$cmFuZG9tc2FsdGlzaGFyZA$Rh+NnJNo1I6nRwaNqbDm6kmADswD1+7FTKZ7Ln9D8nQ
(1) (2) (3) (4)
```

1. The identifier is always `scrypt`.
2. Cost parameters:
1. `ln` is the exponential cost parameter for memory and CPU - `16` in this example.
2. `r` is the block size for optimal performance of the CPU architecture - `8` in this example.
3. `p` is to indicate parallelism - `1` in this example.
3. Base64-encoded salt
4. Base64-encoded Scrypt hash output of the password and salt combined.

### PBKDF2

PBKDF2 uses an alternative Base64 encoding, which is based on the standard with `+` replaced by `.`, and it comes without padding. As we've also seen standard encoding with padding in the wild, the verifier will accept alternative standards with or without padding. The Hasher always produces alternative encoding.

The resulting Modular Crypt Format string looks as follows:

```
$pbkdf2-sha256$12$cmFuZG9tc2FsdGlzaGFyZA$OFvEcLOIPFd/oq8egf10i.qJLI7A8nDjPLnolCWarQY
(1) (2) (3) (4)
```

1. The identifier is made of 2 parts:
1. `pbkdf2` is the identifier prefix for the algorithm.
2. `-sha256` is an optional suffix with dash separator and is the identifier for the hash backend. When omitted, `sha1` is used as a default.
2. The cost parameter in rounds, which is a linear value - `12` in this example.
3. Alternative Base64-encoded salt
4. Alternative Base64 encoded Scrypt hash output of the password and salt combined.

#### Reference

Its origin can be found in
[Glibc](https://man.archlinux.org/man/crypt.5). Passlib for Python is the
most complete implementation and there the
Expand Down
2 changes: 1 addition & 1 deletion internal/encoding/pbkdf2.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
const encodePbkdf2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./"

// Pbkdf2B64 is an alternative base64 encoding used by pbkdf2.
// Bassicaly it's `+` replaced by `.`.
// Basically it's `+` replaced by `.`.
// https://passlib.readthedocs.io/en/stable/lib/passlib.utils.binary.html#passlib.utils.binary.ab64_encode
var Pbkdf2B64 = base64.NewEncoding(encodePbkdf2).WithPadding(base64.NoPadding)

Expand Down