Given a message m
, key k
, and optional footer f
(which defaults to empty
string), and an optional implicit assertion i
(which defaults to empty string):
- Before encrypting, first assert that the key being used is intended for use
with
v4.local
tokens, and has a length of 256 bits (32 bytes). See Algorithm Lucidity for more information. - Set header
h
tov4.local.
Note: This includes the trailing period. - Generate 32 random bytes from the OS's CSPRNG,
n
. - Split the key into an Encryption key (
Ek
) and Authentication key (Ak
), using keyed BLAKE2b, using the domain separation constants andn
as the message, and the input key as the key. The first value will be 56 bytes, the second will be 32 bytes. The derived key will be the leftmost 32 bytes of the hash output. The remaining 24 bytes will be used as a counter nonce (n2
):tmp = crypto_generichash( msg = "paseto-encryption-key" || n, key = key, length = 56 ); Ek = tmp[0:32] n2 = tmp[32:] Ak = crypto_generichash( msg = "paseto-auth-key-for-aead" || n, key = key, length = 32 );
- Encrypt the message using XChaCha20, using
n2
from step 3 as the nonce andEk
as the key.c = crypto_stream_xchacha20_xor( message = m nonce = n2 key = Ek );
- Pack
h
,n
,c
,f
, andi
together (in that order) using PAE. We'll call thispreAuth
. - Calculate BLAKE2b-MAC of the output of
preAuth
, usingAk
as the authentication key. We'll call thist
.t = crypto_generichash( message = preAuth key = Ak, length = 32 );
- 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.
Given a message m
, key k
, and optional footer f
(which defaults to empty string), and an optional
implicit assertion i
(which defaults to empty string):
- Before decrypting, first assert that the key being used is intended for use
with
v4.local
tokens, and has a length of 256 bits (32 bytes). See Algorithm Lucidity for more information. - If
f
is not empty, implementations MAY verify that the value appended to the token matches some expected stringf
, provided they do so using a constant-time string compare function. - Verify that the message begins with
v4.local.
, otherwise throw an exception. This constant will be referred to ash
.- Note: This header includes the trailing period.
- Future-proofing: If a future PASETO variant allows for encodings other
than JSON (e.g., CBOR), future implementations MAY also permit those
values at this step (e.g.
v4c.local.
).
- Decode the payload (
m
sansh
,f
, and the optional trailing period betweenm
andf
) from base64url to raw binary. Set:n
to the leftmost 32 bytest
to the rightmost 32 bytesc
to the middle remainder of the payload, excludingn
andt
.
- Split the key into an Encryption key (
Ek
) and Authentication key (Ak
), using keyed BLAKE2b, using the domain separation constants andn
as the message, and the input key as the key. The first value will be 56 bytes, the second will be 32 bytes. The derived key will be the leftmost 32 bytes of the hash output. The remaining 24 bytes will be used as a counter nonce (n2
):tmp = crypto_generichash( msg = "paseto-encryption-key" || n, key = key, length = 56 ); Ek = tmp[0:32] n2 = tmp[32:] Ak = crypto_generichash( msg = "paseto-auth-key-for-aead" || n, key = key, length = 32 );
- Pack
h
,n
,c
,f
, andi
together (in that order) using PAE. We'll call thispreAuth
. - Re-calculate BLAKE2b-MAC of the output of
preAuth
, usingAk
as the authentication key. We'll call thist2
.t2 = crypto_generichash( message = preAuth key = Ak, length = 32 );
- Compare
t
witht2
using a constant-time string compare function. If they are not identical, throw an exception.- You MUST use a constant-time string compare function to be compliant. If you do not have one available to you in your programming language/framework, you MUST use Double HMAC.
- Decrypt
c
usingXChaCha20
, store the result inp
.p = crypto_stream_xchacha20_xor( ciphertext = c nonce = n2 key = Ek );
- If decryption failed, throw an exception. Otherwise, return
p
.
Given a message m
, Ed25519 secret key sk
, and
optional footer f
(which defaults to empty string), and an optional
implicit assertion i
(which defaults to empty string):
- Before signing, first assert that the key being used is intended for use
with
v4.public
tokens, and is the secret key of the intended keypair. See Algorithm Lucidity for more information. - Set
h
tov4.public.
Note: This includes the trailing period. - Pack
h
,m
,f
, andi
together using PAE (pre-authentication encoding). We'll call thism2
. - Sign
m2
using Ed25519sk
. We'll call thissig
.sig = crypto_sign_detached( message = m2, private_key = sk );
- 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.
- Empty: return "
Given a signed message sm
, public key pk
, and optional footer f
(which defaults to empty string), and an optional
implicit assertion i
(which defaults to empty string):
- Before verifying, first assert that the key being used is intended for use
with
v4.public
tokens, and is the public key of the intended keypair. See Algorithm Lucidity for more information. - If
f
is not empty, implementations MAY verify that the value appended to the token matches some expected stringf
, provided they do so using a constant-time string compare function. - Verify that the message begins with
v4.public.
, otherwise throw an exception. This constant will be referred to ash
.
Note: This includes the trailing period. - Decode the payload (
sm
sansh
,f
, and the optional trailing period betweenm
andf
) from base64url to raw binary. Set:s
to the rightmost 64 bytesm
to the leftmost remainder of the payload, excludings
- Pack
h
,m
,f
,i
together using PAE. We'll call thism2
. - Use Ed25519 to verify that the signature is valid for the message:
valid = crypto_sign_verify_detached( signature = s, message = m2, public_key = pk );
- If the signature is valid, return
m
. Otherwise, throw an exception.