Skip to content

Latest commit

 

History

History
191 lines (179 loc) · 7.45 KB

Version4.md

File metadata and controls

191 lines (179 loc) · 7.45 KB

Paseto Version 4

Encrypt

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):

  1. 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.
  2. Set header h to v4.local.
    Note: This includes the trailing period.
  3. Generate 32 random bytes from the OS's CSPRNG, n.
  4. Split the key into an Encryption key (Ek) and Authentication key (Ak), using keyed BLAKE2b, using the domain separation constants and n 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
    );
    
  5. Encrypt the message using XChaCha20, using n2 from step 3 as the nonce and Ek as the key.
    c = crypto_stream_xchacha20_xor(
        message = m
        nonce = n2
        key = Ek
    );
    
  6. Pack h, n, c, f, and i together (in that order) using PAE. We'll call this preAuth.
  7. Calculate BLAKE2b-MAC of the output of preAuth, using Ak as the authentication key. We'll call this t.
    t = crypto_generichash(
        message = preAuth
        key = Ak,
        length = 32
    );
    
  8. 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

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):

  1. 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.
  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.
  3. Verify that the message begins with v4.local., otherwise throw an exception. This constant will be referred to as h.
    • 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.).
  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 32 bytes
    • c to the middle remainder of the payload, excluding n and t.
  5. Split the key into an Encryption key (Ek) and Authentication key (Ak), using keyed BLAKE2b, using the domain separation constants and n 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
    );
    
  6. Pack h, n, c, f, and i together (in that order) using PAE. We'll call this preAuth.
  7. Re-calculate BLAKE2b-MAC of the output of preAuth, using Ak as the authentication key. We'll call this t2.
    t2 = crypto_generichash(
        message = preAuth
        key = Ak,
        length = 32
    );
    
  8. Compare t with t2 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.
  9. Decrypt c using XChaCha20, store the result in p.
    p = crypto_stream_xchacha20_xor(
       ciphertext = c
       nonce = n2
       key = Ek
    );
    
  10. If decryption failed, throw an exception. Otherwise, return p.

Sign

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):

  1. 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.
  2. Set h to v4.public.
    Note: This includes the trailing period.
  3. Pack h, m, f, and i together using PAE (pre-authentication encoding). We'll call this m2.
  4. Sign m2 using Ed25519 sk. We'll call this sig.
    sig = crypto_sign_detached(
        message = m2,
        private_key = sk
    );
    
  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

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):

  1. 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.
  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.
  3. Verify that the message begins with v4.public., otherwise throw an exception. This constant will be referred to as h.
    Note: This includes the 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
  5. Pack h, m, f, i together using PAE. We'll call this m2.
  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
    );
    
  7. If the signature is valid, return m. Otherwise, throw an exception.