PHP Classes

File: docs/01-Protocol-Versions/Version1.md

Recommend this page to a friend!
  Classes of Scott Arciszewski   PHP PASeTo   docs/01-Protocol-Versions/Version1.md   Download  
File: docs/01-Protocol-Versions/Version1.md
Role: Auxiliary data
Content type: text/markdown
Description: Auxiliary data
Class: PHP PASeTo
Encrypt and decrypt data with PaSeTO protocol
Author: By
Last change:
Date: 4 years ago
Size: 6,120 bytes
 

Contents

Class file image Download

Paseto Version 1

GetNonce

Given a message (m) and a nonce (n):

  1. Calculate HMAC-SHA384 of the message `m` with `n` as the key.
  2. Return the leftmost 32 bytes of step 1.

Encrypt

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 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`),
    using the leftmost 16 bytes of `n` as the HKDF salt:
    Ek = hkdf_sha384(
        len = 32
        ikm = k,
        info = "paseto-encryption-key",
        salt = n[0:16]
    );
    Ak = hkdf_sha384(
        len = 32
        ikm = k,
        info = "paseto-auth-key-for-aead",
        salt = n[0:16]
    );
    
  5. 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(
        plaintext = m,
        nonce = n[16:]
        key = Ek
    );
    
  6. Pack `h`, `n`, `c`, and `f` together using PAE (pre-authentication encoding). We'll call this `preAuth`
  7. Calculate HMAC-SHA384 of the output of `preAuth`, using `Ak` as the authentication key. We'll call this `t`.
  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):

  1. 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 exception. This constant will be referred to as `h`.
  3. Decode the payload (`m` sans `h`, `f`, and the optional trailing period between `m` and `f`) from b64 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 (`Ak`), using the leftmost 16 bytes of `n` as the HKDF salt. For encryption keys, theinfoparameter for HKDFMUST* be set to paseto-encryption-key. For authentication keys, theinfoparameter for HKDFMUST* be set to paseto-auth-key-for-aead. The output lengthMUST* be 32 for both keys.

    Ek = hkdf_sha384(
        len = 32
        ikm = k,
        info = "paseto-encryption-key",
        salt = n[0:16]
    );
    Ak = hkdf_sha384(
        len = 32
        ikm = k,
        info = "paseto-auth-key-for-aead",
        salt = n[0:16]
    );
    
  5. Pack `h`, `n`, `c`, and `f` together (in that order) using PAE. We'll call this `preAuth`.
  6. 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 are not identical, throw an exception.
  8. 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(
        cipherext = c,
        nonce = n[16:]
        key = Ek
    );
    

Sign

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 PAE (pre-authentication encoding). We'll call this `m2`.
  3. Sign `m2` using RSA with the private key `sk`. We'll call this `sig`.
    sig = crypto_sign_rsa(
        message = m2,
        private_key = sk,
        padding_mode = "pss",
        public_exponent = 65537,
        hash = "sha384"
        mgf = "mgf1+sha384"
    );
    
    Only the above parameters are supported. PKCS1v1.5 is explicitly forbidden.
  4. 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, 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 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 exception. This constant will be referred to as `h`.
  3. Decode the payload (`sm` sans `h`, `f`, and the optional trailing period between `m` and `f`) from b64 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 PAE. We'll call this `m2`.
  5. Use RSA to verify that the signature is valid for the message:
    valid = crypto_sign_rsa_verify(
        signature = s,
        message = m2,
        public_key = pk,
        padding_mode = "pss",
        public_exponent = 65537,
        hash = "sha384"
        mgf = "mgf1+sha384"
    );
    
  6. If the signature is valid, return `m`. Otherwise, throw an exception.