Skip to content

Commit

Permalink
Merge pull request #14 from paseto-standard/emphasize-lucidity
Browse files Browse the repository at this point in the history
Make algorithm lucidity the first step.
  • Loading branch information
paragonie-security authored Sep 7, 2021
2 parents a35e74b + c265301 commit f0cfc38
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 175 deletions.
86 changes: 40 additions & 46 deletions docs/01-Protocol-Versions/Version1.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,20 @@ Given a message (`m`) and a nonce (`n`):

## Encrypt

Before encrypting, first assert that the key being used is intended for use
with `v1.local` tokens.
See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md)
for more information.

Given a message `m`, key `k`, and optional footer `f`
(which defaults to empty string):

1. Set header `h` to `v1.local.`
2. Generate 32 random bytes from the OS's CSPRNG.
3. Calculate `GetNonce()` of `m` and the output of step 2
1. Before encrypting, first assert that the key being used is intended for use
with `v1.local` tokens. See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md)
for more information.
2. Set header `h` to `v1.local.`
3. Generate 32 random bytes from the OS's CSPRNG.
4. Calculate `GetNonce()` of `m` and the output of step 2
to get the nonce, `n`.
* This step is to ensure that an RNG failure does not result
in a nonce-misuse condition that breaks the security of
our stream cipher.
4. Split the key into an Encryption key (`Ek`) and Authentication key (`Ak`),
5. Split the key into an Encryption key (`Ek`) and Authentication key (`Ak`),
using the leftmost 16 bytes of `n` as the HKDF salt:
```
Ek = hkdf_sha384(
Expand All @@ -40,7 +38,7 @@ Given a message `m`, key `k`, and optional footer `f`
salt = n[0:16]
);
```
5. Encrypt the message using `AES-256-CTR`, using `Ek` as the key and
6. Encrypt the message using `AES-256-CTR`, using `Ek` as the key and
the rightmost 16 bytes of `n` as the nonce. We'll call this `c`:
```
c = aes256ctr_encrypt(
Expand All @@ -49,38 +47,36 @@ Given a message `m`, key `k`, and optional footer `f`
key = Ek
);
```
6. Pack `h`, `n`, `c`, and `f` together using
7. Pack `h`, `n`, `c`, and `f` together using
[PAE](Common.md#authentication-padding)
(pre-authentication encoding). We'll call this `preAuth`
7. Calculate HMAC-SHA384 of the output of `preAuth`, using `Ak` as the
8. Calculate HMAC-SHA384 of the output of `preAuth`, using `Ak` as the
authentication key. We'll call this `t`.
8. If `f` is:
9. If `f` is:
* Empty: return "`h` || base64url(`n` || `c` || `t`)"
* Non-empty: return "`h` || base64url(`n` || `c` || `t`) || `.` || base64url(`f`)"
* ...where || means "concatenate"
* Note: `base64url()` means Base64url from RFC 4648 without `=` padding.

## Decrypt

Before decrypting, first assert that the key being used is intended for use
with `v1.local` tokens.
See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md)
for more information.

Given a message `m`, key `k`, and optional footer `f`
(which defaults to empty string):

1. If `f` is not empty, implementations **MAY** verify that the value appended
1. Before decrypting, first assert that the key being used is intended for use
with `v1.local` tokens. See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md)
for more information.
2. If `f` is not empty, implementations **MAY** verify that the value appended
to the token matches some expected string `f`, provided they do so using a
constant-time string compare function.
2. Verify that the message begins with `v1.local.`, otherwise throw an
3. Verify that the message begins with `v1.local.`, otherwise throw an
exception. This constant will be referred to as `h`.
3. Decode the payload (`m` sans `h`, `f`, and the optional trailing period
4. Decode the payload (`m` sans `h`, `f`, and the optional trailing period
between `m` and `f`) from base64url to raw binary. Set:
* `n` to the leftmost 32 bytes
* `t` to the rightmost 48 bytes
* `c` to the middle remainder of the payload, excluding `n` and `t`
4. Split the key (`k`) into an Encryption key (`Ek`) and an Authentication key
5. Split the key (`k`) into an Encryption key (`Ek`) and an Authentication key
(`Ak`), using the leftmost 16 bytes of `n` as the HKDF salt.
* For encryption keys, the **info** parameter for HKDF **MUST** be set to
**paseto-encryption-key**.
Expand All @@ -102,14 +98,14 @@ Given a message `m`, key `k`, and optional footer `f`
salt = n[0:16]
);
```
5. Pack `h`, `n`, `c`, and `f` together (in that order) using
6. Pack `h`, `n`, `c`, and `f` together (in that order) using
[PAE](Common.md#authentication-padding).
We'll call this `preAuth`.
6. Recalculate HMAC-SHA-384 of `preAuth` using `Ak` as the key. We'll call this
7. Recalculate HMAC-SHA-384 of `preAuth` using `Ak` as the key. We'll call this
`t2`.
7. Compare `t` with `t2` using a constant-time string compare function. If they
8. Compare `t` with `t2` using a constant-time string compare function. If they
are not identical, throw an exception.
8. Decrypt `c` using `AES-256-CTR`, using `Ek` as the key and the rightmost 16
9. Decrypt `c` using `AES-256-CTR`, using `Ek` as the key and the rightmost 16
bytes of `n` as the nonce, and return this value.
```
return aes256ctr_decrypt(
Expand All @@ -121,19 +117,18 @@ Given a message `m`, key `k`, and optional footer `f`

## Sign

Before signing, first assert that the key being used is intended for use
with `v1.public` tokens, and is the secret key of the intended keypair.
See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md)
for more information.

Given a message `m`, 2048-bit RSA secret key `sk`, and
optional footer `f` (which defaults to empty string):

1. Set `h` to `v1.public.`
2. Pack `h`, `m`, and `f` together using
1. Before signing, first assert that the key being used is intended for use
with `v1.public` tokens, and is the secret key of the intended keypair.
See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md)
for more information.
2. Set `h` to `v1.public.`
3. Pack `h`, `m`, and `f` together using
[PAE](Common.md#authentication-padding)
(pre-authentication encoding). We'll call this `m2`.
3. Sign `m2` using RSA with the private key `sk`. We'll call this `sig`.
4. Sign `m2` using RSA with the private key `sk`. We'll call this `sig`.
```
sig = crypto_sign_rsa(
message = m2,
Expand All @@ -145,35 +140,34 @@ optional footer `f` (which defaults to empty string):
);
```
Only the above parameters are supported. PKCS1v1.5 is explicitly forbidden.
4. If `f` is:
5. If `f` is:
* Empty: return "`h` || base64url(`m` || `sig`)"
* Non-empty: return "`h` || base64url(`m` || `sig`) || `.` || base64url(`f`)"
* ...where || means "concatenate"
* Note: `base64url()` means Base64url from RFC 4648 without `=` padding.

## Verify

Before verifying, first assert that the key being used is intended for use
with `v1.public` tokens, and is the public key of the intended keypair.
See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md)
for more information.

Given a signed message `sm`, RSA public key `pk`, and optional
footer `f` (which defaults to empty string):

1. If `f` is not empty, implementations **MAY** verify that the value appended
1. Before verifying, first assert that the key being used is intended for use
with `v1.public` tokens, and is the public key of the intended keypair.
See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md)
for more information.
2. If `f` is not empty, implementations **MAY** verify that the value appended
to the token matches some expected string `f`, provided they do so using a
constant-time string compare function.
2. Verify that the message begins with `v1.public.`, otherwise throw an
3. Verify that the message begins with `v1.public.`, otherwise throw an
exception. This constant will be referred to as `h`.
3. Decode the payload (`sm` sans `h`, `f`, and the optional trailing period
4. Decode the payload (`sm` sans `h`, `f`, and the optional trailing period
between `m` and `f`) from base64url to raw binary. Set:
* `s` to the rightmost 256 bytes
* `m` to the leftmost remainder of the payload, excluding `s`
4. Pack `h`, `m`, and `f` together (in that order) using PAE (see
5. Pack `h`, `m`, and `f` together (in that order) using PAE (see
[PAE](Common.md#authentication-padding).
We'll call this `m2`.
5. Use RSA to verify that the signature is valid for the message:
6. Use RSA to verify that the signature is valid for the message:
```
valid = crypto_sign_rsa_verify(
signature = s,
Expand All @@ -185,4 +179,4 @@ footer `f` (which defaults to empty string):
mgf = "mgf1+sha384"
);
```
6. If the signature is valid, return `m`. Otherwise, throw an exception.
7. If the signature is valid, return `m`. Otherwise, throw an exception.
78 changes: 36 additions & 42 deletions docs/01-Protocol-Versions/Version2.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,21 @@

## Encrypt

Before encrypting, first assert that the key being used is intended for use
with `v2.local` tokens.
See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md)
for more information.

Given a message `m`, key `k`, and optional footer `f`.

1. Set header `h` to `v2.local.`
2. Generate 24 random bytes from the OS's CSPRNG.
3. Calculate BLAKE2b of the message `m` with the output of step 2 as the key,
1. Before encrypting, first assert that the key being used is intended for use
with `v2.local` tokens. See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md)
for more information.
2. Set header `h` to `v2.local.`
3. Generate 24 random bytes from the OS's CSPRNG.
4. Calculate BLAKE2b of the message `m` with the output of step 2 as the key,
with an output length of 24. This will be our nonce, `n`.
* This step is to ensure that an RNG failure does not result in a
nonce-misuse condition that breaks the security of our stream cipher.
4. Pack `h`, `n`, and `f` together (in that order) using
5. Pack `h`, `n`, and `f` together (in that order) using
[PAE](Common.md#authentication-padding).
We'll call this `preAuth`.
5. Encrypt the message using XChaCha20-Poly1305, using an AEAD interface such as
6. Encrypt the message using XChaCha20-Poly1305, using an AEAD interface such as
the one provided in libsodium.
```
c = crypto_aead_xchacha20poly1305_encrypt(
Expand All @@ -28,34 +26,32 @@ Given a message `m`, key `k`, and optional footer `f`.
key = k
);
```
6. If `f` is:
7. If `f` is:
* Empty: return h || base64url(n || c)
* Non-empty: return h || base64url(n || c) || `.` || base64url(f)
* ...where || means "concatenate"
* Note: `base64url()` means Base64url from RFC 4648 without `=` padding.

## Decrypt

Before decrypting, first assert that the key being used is intended for use
with `v2.local` tokens.
See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md)
for more information.

Given a message `m`, key `k`, and optional footer `f`.

1. If `f` is not empty, implementations **MAY** verify that the value appended
1. Before decrypting, first assert that the key being used is intended for use
with `v2.local` tokens. See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md)
for more information.
2. If `f` is not empty, implementations **MAY** verify that the value appended
to the token matches some expected string `f`, provided they do so using a
constant-time string compare function.
2. Verify that the message begins with `v2.local.`, otherwise throw an
3. Verify that the message begins with `v2.local.`, otherwise throw an
exception. This constant will be referred to as `h`.
3. Decode the payload (`m` sans `h`, `f`, and the optional trailing period
4. Decode the payload (`m` sans `h`, `f`, and the optional trailing period
between `m` and `f`) from base64url to raw binary. Set:
* `n` to the leftmost 24 bytes
* `c` to the middle remainder of the payload, excluding `n`.
4. Pack `h`, `n`, and `f` together (in that order) using
5. Pack `h`, `n`, and `f` together (in that order) using
[PAE](Common.md#authentication-padding).
We'll call this `preAuth`
5. Decrypt `c` using `XChaCha20-Poly1305`, store the result in `p`.
6. Decrypt `c` using `XChaCha20-Poly1305`, store the result in `p`.
```
p = crypto_aead_xchacha20poly1305_decrypt(
ciphertext = c
Expand All @@ -64,63 +60,61 @@ Given a message `m`, key `k`, and optional footer `f`.
key = k
);
```
6. If decryption failed, throw an exception. Otherwise, return `p`.
7. If decryption failed, throw an exception. Otherwise, return `p`.

## Sign

Before signing, first assert that the key being used is intended for use
with `v2.public` tokens, and is the secret key of the intended keypair.
See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md)
for more information.

Given a message `m`, Ed25519 secret key `sk`, and
optional footer `f` (which defaults to empty string):

1. Set `h` to `v2.public.`
2. Pack `h`, `m`, and `f` together using
1. Before signing, first assert that the key being used is intended for use
with `v2.public` tokens, and is the secret key of the intended keypair.
See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md)
for more information.
2. Set `h` to `v2.public.`
3. Pack `h`, `m`, and `f` together using
[PAE](Common.md#authentication-padding)
(pre-authentication encoding). We'll call this `m2`.
3. Sign `m2` using Ed25519 `sk`. We'll call this `sig`.
4. Sign `m2` using Ed25519 `sk`. We'll call this `sig`.
```
sig = crypto_sign_detached(
message = m2,
private_key = sk
);
```
4. If `f` is:
5. If `f` is:
* Empty: return "`h` || base64url(`m` || `sig`)"
* Non-empty: return "`h` || base64url(`m` || `sig`) || `.` || base64url(`f`)"
* ...where || means "concatenate"
* Note: `base64url()` means Base64url from RFC 4648 without `=` padding.

## Verify

Before verifying, first assert that the key being used is intended for use
with `v2.public` tokens, and is the public key of the intended keypair.
See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md)
for more information.

Given a signed message `sm`, public key `pk`, and optional footer `f`
(which defaults to empty string):

1. If `f` is not empty, implementations **MAY** verify that the value appended
1. Before verifying, first assert that the key being used is intended for use
with `v2.public` tokens, and is the public key of the intended keypair.
See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md)
for more information.
2. If `f` is not empty, implementations **MAY** verify that the value appended
to the token matches some expected string `f`, provided they do so using a
constant-time string compare function.
2. Verify that the message begins with `v2.public.`, otherwise throw an exception.
3. Verify that the message begins with `v2.public.`, otherwise throw an exception.
This constant will be referred to as `h`.
3. Decode the payload (`sm` sans `h`, `f`, and the optional trailing period
4. Decode the payload (`sm` sans `h`, `f`, and the optional trailing period
between `m` and `f`) from base64url to raw binary. Set:
* `s` to the rightmost 64 bytes
* `m` to the leftmost remainder of the payload, excluding `s`
4. Pack `h`, `m`, and `f` together using
5. Pack `h`, `m`, and `f` together using
[PAE](Common.md#authentication-padding).
We'll call this `m2`.
5. Use Ed25519 to verify that the signature is valid for the message:
6. Use Ed25519 to verify that the signature is valid for the message:
```
valid = crypto_sign_verify_detached(
signature = s,
message = m2,
public_key = pk
);
```
6. If the signature is valid, return `m`. Otherwise, throw an exception.
7. If the signature is valid, return `m`. Otherwise, throw an exception.
Loading

0 comments on commit f0cfc38

Please sign in to comment.