├── docs ├── 02-Implementation-Guide │ ├── Test-Vectors │ │ └── README.md │ ├── README.md │ ├── 02-Validators.md │ ├── 04-Claims.md │ ├── 05-API-UX.md │ ├── 03-Algorithm-Lucidity.md │ └── 01-Payload-Processing.md ├── 01-Protocol-Versions │ ├── Common.md │ ├── Version2.md │ ├── Version1.md │ ├── Version4.md │ ├── README.md │ └── Version3.md ├── README.md └── Rationale-V3-V4.md └── README.md /docs/02-Implementation-Guide/Test-Vectors/README.md: -------------------------------------------------------------------------------- 1 | # Moved 2 | 3 | See [this repository](https://github.com/paseto-standard/test-vectors) 4 | for the test vectors. 5 | -------------------------------------------------------------------------------- /docs/02-Implementation-Guide/README.md: -------------------------------------------------------------------------------- 1 | # Implementation Guide 2 | 3 | This section of the documentation should serve as a guide for implementors 4 | who seek to bring PASETO to their favorite programming language or framework. 5 | 6 | This covers the nitty gritty engineering details, trade-offs, and any questions 7 | whose answers don't fit elegantly in the protocol definition. 8 | 9 | ## Overview 10 | 11 | PASETO is a suite of protocols with distinct [versions](../01-Protocol-Versions). 12 | Each version may impose its own requirements in order to achieve cryptographic 13 | security, so long as the [rules for new versions are followed](../01-Protocol-Versions#rules-for-current-and-future-protocol-versions). 14 | 15 | PASETO can be separated into two distinct parts: 16 | 17 | 1. The cryptography protocol (defined in the [Protocol Versions section](../01-Protocol-Versions)) 18 | that protects a payload. 19 | 2. The payload. 20 | 21 | ## Sections in This Guide 22 | 23 | * [Payload Processing](01-Payload-Processing.md) 24 | * [User-Defined Parser Validation](02-Validators.md) 25 | * [Algorithm Lucidity](03-Algorithm-Lucidity.md) 26 | * [Registered Claims](04-Claims.md) 27 | * [API / User Experience Recommendations](05-API-UX.md) 28 | -------------------------------------------------------------------------------- /docs/02-Implementation-Guide/02-Validators.md: -------------------------------------------------------------------------------- 1 | # Validators 2 | 3 | After verifying and extracting the payloads from the token, but before returning 4 | the object representation of the payload to the user, library authors may wish to 5 | add the ability for their token parsers to automatically validate the token against 6 | some basic constraints. 7 | 8 | This is not strictly required, but validation support is highly recommended. 9 | 10 | Some examples of validation rules that libraries may wish to provide include: 11 | 12 | * `ForAudience` which compares the payload-provided `aud` claim with an expected 13 | value. 14 | * `IdentifiedBy` which compares the payload-provided `jti` claim with an expected 15 | value. 16 | * `IssuedBy` which compares the payload-provided `iss` claim with an expected 17 | value. 18 | * `NotExpired` which verifies that the current time is less than or equal to the 19 | DateTime stored in the `exp` claim. 20 | * `Subject` which compares the payload-provided `sub` claim with an expected 21 | value. 22 | * `ValidAt` which verifies all of the following: 23 | * The current time is less than or equal to the DateTime stored in the `exp` claim. 24 | * The current time is greater than or equal to the DateTime stored in the `iat` claim. 25 | * The current time is greater than or equal to the DateTime stored in the `nbf` claim. 26 | 27 | Example implementations of these validators are included in the PHP implementation. 28 | 29 | Validation should fail-closed by default (e.g., if invalid data is provided). 30 | -------------------------------------------------------------------------------- /docs/02-Implementation-Guide/04-Claims.md: -------------------------------------------------------------------------------- 1 | # Registered Claims 2 | 3 | ## Payload Claims 4 | 5 | The following keys are reserved for use within PASETO. Users SHOULD NOT write 6 | arbitrary/invalid data to any keys in a top-level PASETO in the list below: 7 | 8 | | Key | Name | Type | Example | 9 | | ----- | ---------------- | -------- | --------------------------------------------------------- | 10 | | `iss` | Issuer | string | `{"iss":"paragonie.com"}` | 11 | | `sub` | Subject | string | `{"sub":"test"}` | 12 | | `aud` | Audience | string | `{"aud":"pie-hosted.com"}` | 13 | | `exp` | Expiration | DateTime | `{"exp":"2039-01-01T00:00:00+00:00"}` | 14 | | `nbf` | Not Before | DateTime | `{"nbf":"2038-04-01T00:00:00+00:00"}` | 15 | | `iat` | Issued At | DateTime | `{"iat":"2038-03-17T00:00:00+00:00"}` | 16 | | `jti` | Token Identifier | string | `{"jti":"87IFSGFgPNtQNNuw0AtuLttPYFfYwOkjhqdWcLoYQHvL"}` | 17 | 18 | In the table above, DateTime means a string in [RFC 3339 Section 5.6 Internet Date/Time 19 | Format](https://datatracker.ietf.org/doc/html/rfc3339#section-5.6) (a profile of ISO 8601). 20 | Further, date and time MUST be separated with an uppercase "T", and "Z" MUST be capitalized when 21 | used as a time offset. While arbitrary fractional seconds SHOULD be supported in parsers, is 22 | RECOMMENDED to omit them on creation. Time offsets confer no special meaning, so they MUST NOT be 23 | used for validation (beyond determining the correct moment in time). It is RECOMMENDED that all 24 | DateTime claims use "Z" as a time offset. 25 | 26 | Any other claims can be freely used. These keys are only reserved in the top-level 27 | JSON object. 28 | 29 | The keys in the above table are case-sensitive. 30 | 31 | Implementors SHOULD provide some means to discourage setting invalid/arbitrary data 32 | to these reserved claims. 33 | 34 | ## Optional Footer Claims 35 | 36 | The optional footer **MAY** contain an optional JSON object encoded as a UTF-8 string. 37 | It does not have to be JSON. Refer to [this document](01-Payload-Processing.md#optional-footer) 38 | for safe JSON handling recommendations, especially for preprocessing the JSON string before 39 | parsing it into memory. 40 | 41 | If the optional footer does contain JSON, the following claims may be stored in the footer. 42 | Users SHOULD NOT write arbitrary/invalid data to any keys in a top-level PASETO in the list below: 43 | 44 | | Key | Name | Type | Example | 45 | | ----- | -------------- | ------ | --------------------------------------------------------------- | 46 | | `kid` | Key ID | string | `{"kid":"k4.lid.iVtYQDjr5gEijCSjJC3fQaJm7nCeQSeaty0Jixy8dbsk"}` | 47 | | `wpk` | Wrapped PASERK | string | `{"wpk":"k4.local-wrap.pie.pu-fBxw... [truncated] ...0eo8iCS"}` | 48 | 49 | Any other claims can be freely used. These keys are only reserved in the top-level 50 | JSON object (if the footer contains a JSON object). 51 | 52 | The keys in the above table are case-sensitive. 53 | 54 | Implementors SHOULD provide some means to discourage setting invalid/arbitrary data 55 | to these reserved claims. 56 | 57 | #### Wrapped PASERK 58 | 59 | Some types of serialized keys may be stored in the footer. 60 | 61 | See [PASERK](https://github.com/paseto-standard/paserk) for more information. 62 | -------------------------------------------------------------------------------- /docs/01-Protocol-Versions/Common.md: -------------------------------------------------------------------------------- 1 | # Common Implementation Details 2 | 3 | ## Base64 Encoding 4 | 5 | Nearly every component in a Paseto (except for the version, purpose, and the `.` 6 | separators) will be encoded using [Base64url](https://tools.ietf.org/html/rfc4648#page-8), 7 | without `=` padding. 8 | 9 | This is implemented in our [constant-time RFC 4648 library](https://github.com/paragonie/constant_time_encoding) 10 | as `Base64UrlSafe::encodeUnpadded()`. 11 | 12 | ### Base64 Decoding 13 | 14 | When decoding a base64url-encoded segment of a PASETO token, implementations 15 | **MUST** be strict about the padding: 16 | 17 | * Padding with `=` characters is *forbidden*. 18 | * If there are trailing bits (2 or 4) due to the length of the segment, all trailing 19 | **MUST** be cleared, or the message is rejected. 20 | 21 | ## Authentication Padding 22 | 23 | Multi-part messages (e.g. header, content, footer) are encoded 24 | in a specific manner before being passed to the respective 25 | cryptographic function. 26 | 27 | In `local` mode, this encoding is applied to the additional 28 | associated data (AAD). In `remote` mode, which is not encrypted, 29 | this encoding is applied to the components of the token, with 30 | respect to the protocol version being followed. 31 | 32 | The reference implementation resides in `Util::preAuthEncode()`. 33 | We will refer to it as **PAE** in this document (short for 34 | Pre-Authentication Encoding). 35 | 36 | ### PAE Definition 37 | 38 | **PAE()** accepts an array of strings (usually denoted as 39 | `array` in docblocks to signify integer keys, but in 40 | other languages, `string[]` is preferred; in the PHP community 41 | they're synonymous). 42 | 43 | **LE64()** encodes a 64-bit unsigned integer into a little-endian 44 | binary string. The most significant bit MUST be cleared for interoperability 45 | with programming languages that do not have unsigned integer support. 46 | 47 | The first 8 bytes of the output will be the number of pieces. Typically 48 | this is a small number (3 or 4). This is calculated by `LE64()` of the 49 | size of the array. 50 | 51 | Next, for each piece provided, the length of the piece is encoded via 52 | `LE64()` and prefixed to each piece before concatenation. 53 | 54 | An implementation may look like this: 55 | 56 | ```javascript 57 | function LE64(n) { 58 | var str = ''; 59 | for (var i = 0; i < 8; ++i) { 60 | if (i === 7) { 61 | // Clear the MSB for interoperability 62 | n &= 127; 63 | } 64 | str += String.fromCharCode(n & 255); 65 | n = n >>> 8; 66 | } 67 | return str; 68 | } 69 | function PAE(pieces) { 70 | if (!Array.isArray(pieces)) { 71 | throw TypeError('Expected an array.'); 72 | } 73 | var count = pieces.length; 74 | var output = LE64(count); 75 | for (var i = 0; i < count; i++) { 76 | output += LE64(pieces[i].length); 77 | output += pieces[i]; 78 | } 79 | return output; 80 | } 81 | ``` 82 | 83 | As a consequence: 84 | 85 | * `PAE([])` will always return `"\x00\x00\x00\x00\x00\x00\x00\x00"` 86 | * `PAE([''])` will always return 87 | `"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"` 88 | * `PAE(['test'])` will always return 89 | `"\x01\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00test"` 90 | * `PAE('test')` will throw a `TypeError` 91 | 92 | As a result, you cannot create a collision with only a partially controlled 93 | plaintext. Either the number of pieces will differ, or the length of one 94 | of the fields (which is prefixed to the input you can provide) will differ, 95 | or both. 96 | 97 | Due to the length being expressed as an unsigned 64-bit integer, it remains 98 | infeasible to generate/transmit enough data to create an integer overflow. 99 | 100 | This is not used to encode data prior to decryption, and no decoding function 101 | is provided or specified. This merely exists to prevent canonicalization 102 | attacks. 103 | -------------------------------------------------------------------------------- /docs/02-Implementation-Guide/05-API-UX.md: -------------------------------------------------------------------------------- 1 | # API / User Experience Recommendations 2 | 3 | PASETO libraries should be easy to use, misuse-resistant, and secure by default. 4 | 5 | ## Builder and Parsers 6 | 7 | The PASETO specification only covers the cryptography and message format. This section 8 | aims to provide guidance and recommendations for how user-facing token Builders and 9 | Parsers should be constructed. 10 | 11 | A **Builder** is a function or class that assembles an encrypted or signed PASETO token 12 | at runtime. 13 | 14 | A **Parser** is a function or class that verifies the cryptographic integrity of a 15 | PASETO token, and also allows users to assert that the claims in the token conform to 16 | their expectations via Parser Rules. 17 | 18 | ### Ease of Use 19 | 20 | We have no specific recommendations on ease-of-use, since every programming language is subtly 21 | different from each other. 22 | 23 | ### Misuse Resistance 24 | 25 | Every Builder and Parser **SHOULD** only accept one version of the PASETO protocol and one 26 | purpose. If you want to parse different versions and/or purposes, this **SHOULD** require 27 | different instances at runtime. These instances **MAY** be the same underlying code (i.e. 28 | a class) if your programming language and/or framework supports it. 29 | 30 | Implementations **MAY** [parse the footer for Key IDs](01-Payload-Processing.md#key-id-support) 31 | before processing the token (and, in the process, support more than one key for a given 32 | Builder or Parser). Libraries that offer Key ID support **MUST** ensure all keys registered 33 | in the Builder/Parser's keyring match the expected key and purpose of the Builder/Parser. 34 | This responsibility **MUST NOT** be shifted to the user. 35 | 36 | #### Why Constrain Builders and Parsers to One (Version, Purpose) Tuple? 37 | 38 | Each Builder/Parser should limit the runtime negotiation as much as possible. 39 | 40 | If you're only ever vending `v4.local` tokens, and you unexpectedly receive a `v3.local` 41 | or `v4.public` token, whether it's a mistake or an active attack, it should be treated as 42 | invalid input. 43 | 44 | If applications want to support multiple versions/purposes simultaneously, they can make 45 | the deliberate decision to do so by mapping the version and purpose from an incoming 46 | token to the corresponding Builder/Parser instance. 47 | 48 | The library **SHOULD NOT** provide version routing out-of-the-box. 49 | It's trivial to write code to map `v4.local.` token headers to a `ParserV4Local` object 50 | (or a `Parser` object instantiated with `ProtocolV4` and `LocalPurpose`) at runtime. 51 | 52 | Library authors **MAY** provide version routing out-of-the-box as a separate 53 | package/module. We recommend enforcing a strict allow-list on the versions and purposes 54 | permitted by a version router. Implementations **MUST** fail closed (e.g. throw an Exception) 55 | if a user provides a (version, purpose) for which the application has not specified a key. 56 | 57 | ### Secure Defaults 58 | 59 | Libraries are encouraged to provide secure defaults for the claims in your Builders and Parsers, 60 | through which ever mechanism is most suitable for the language and framework you're developing in. 61 | 62 | #### Default Expiration Claims 63 | 64 | Builders and Parsers **SHOULD**, by default, include an `exp` claim if the user did not specify 65 | an expiration time. Users **MUST** have some means of explicitly declaring non-expiring tokens. 66 | 67 | The recommended default expiration time is **1 hour**. 68 | 69 | #### Why Set a Default Expiration Claim? 70 | 71 | The use case for a non-expiring claim in the world of security tokens is fairly limited. 72 | If someone omits an expiration header from their tokens--or forgets to require one when 73 | processing them--this is almost certainly an oversight rather than a deliberate choice. 74 | 75 | When it *is* a deliberate choice, users should be given the opportunity to deliberately 76 | remove this claim from the Builder and Parser. 77 | 78 | #### Other Default Claims 79 | 80 | Implementations are free to choose any additional default values for [registered claims](04-Claims.md#registered-claims) 81 | for their Parsers and Builders, but they are not required. 82 | 83 | The default values for `iat` and `nbf` (if included in the list of default claims by the 84 | library, and not overriden or opted out by the user) **MUST** be the current time. 85 | 86 | PASETO libraries **MUST NOT** add default values for non-registered claims. These are 87 | considered Custom Claims and should be left to each application to define and/or require. 88 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Implementation Details 2 | 3 | ## Paseto Message Format: 4 | 5 | ### Without the Optional Footer 6 | 7 | ``` 8 | version.purpose.payload 9 | ``` 10 | 11 | ### With the Optional Footer 12 | 13 | ``` 14 | version.purpose.payload.footer 15 | ``` 16 | 17 | The `version` is a string that represents the current version of the protocol. Currently, 18 | two versions are specified, which each possess their own ciphersuites. Accepted values: 19 | `v1`, `v2`, `v3`, `v4`. 20 | 21 | PASETO serializes its payload as a JSON string. Future documents **MAY** specify using 22 | PASETO with non-JSON encoding. When this happens, a suffix will be appended to the version tag 23 | when a non-JSON encoding rule is used. 24 | 25 | > For example, a future PASETO-CBOR proposal might define its versions as `v1c`, `v2c`, `v3c`, 26 | > and `v4c`. The underlying cryptography will be the same as `v1`, `v2`, `v3`, and `v4` 27 | > respectively. Keys **SHOULD** be portable across different underlying encodings, but tokens 28 | > **MUST NOT** be transmutable between encodings without access to the symmetric key (`local` tokens) 29 | > or secret key (`public` tokens). 30 | 31 | The `purpose` is a short string describing the purpose of the token. Accepted values: 32 | `local`, `public`. 33 | 34 | * `local`: shared-key authenticated encryption 35 | * `public`: public-key digital signatures; **not encrypted** 36 | 37 | Any optional data can be appended to the end. This information is NOT encrypted, but it is 38 | used in calculating the authentication tag for the payload. It's always base64url-encoded. 39 | 40 | * For local tokens, it's included in the associated data alongside the nonce. 41 | * For public tokens, it's appended to the message during the actual 42 | authentication/signing step, in accordance to 43 | [our standard format](01-Protocol-Versions/Common.md#authentication-padding). 44 | 45 | Thus, if you want unencrypted, but authenticated, tokens, you can simply set your payload 46 | to an empty string, then your footer to the message you want to authenticate, and use a 47 | local token. 48 | 49 | Conversely, if you want to support key rotation, you can use the unencrypted footer to store 50 | the Key-ID. 51 | 52 | If you want public-key encryption, check out [PASERK](https://github.com/paseto-standard/paserk). 53 | 54 | ### Implicit Assertions 55 | 56 | PASETO `v3` and `v4` tokens support a feature called **implicit assertions**, which are used 57 | in the calculation of the MAC (`local` tokens) or digital signature (`public` tokens), but 58 | **NOT** stored in the token. (Thus, its implicitness.) 59 | 60 | An implicit assertion MUST be provided by the caller explicitly when validating a PASETO token 61 | if it was provided at the time of creation. 62 | 63 | ## Versions and their Respective Purposes 64 | 65 | See [Protocol Versions](01-Protocol-Versions) for specifics. 66 | 67 | ## What are Paseto's design goals? 68 | 69 | ### 1. Resistance to Implementation Error / Misuse 70 | 71 | While it will be possible for motivated developers to discover novel ways to 72 | make any tool insecure, Paseto attempts to make it easier to develop secure 73 | implementations than to develop insecure implementations of the standard. 74 | 75 | To accomplish this goal, we cast aside runtime protocol negotiation and 76 | so-called "algorithm agility" in favor of pre-negotiated protocols with 77 | version identifiers. 78 | 79 | For `local` tokens, we encrypt them exclusively using authenticated encryption 80 | with additional data (AEAD) modes. 81 | 82 | ### 2. Usability 83 | 84 | Developers who are already familiar with JSON Web Tokens (JWT) should be able 85 | to, intuitively, use Paseto in their software with minimal friction. 86 | 87 | Additionally, developers who are not already familiar with JWT should be able 88 | to pick up Paseto and use it successfully without introducing security flaws 89 | into their application. 90 | 91 | ## Was "Stateless Session Tokens" one of Paseto's Design Goals? 92 | 93 | No, neither Paseto nor JWT were designed for 94 | [stateless session management](http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/), 95 | which is largely an anti-pattern. 96 | 97 | There is no built-in mechanism to defeat replay attacks within the validity 98 | window, should a token become compromised, without server-side persistent 99 | data storage. 100 | 101 | Therefore, neither PASETO nor JWT should be used in any attempt to obviate the 102 | need for server-side persistent data storage. 103 | 104 | ### What Should We Use PASETO For? 105 | 106 | Some example use-cases: 107 | 108 | 1. (`local`): Tamper-proof, short-lived immutable data stored on client machines. 109 | * e.g. "remember me on this computer" cookies, which secure a unique ID that 110 | are used in a database lookup upon successful validation to provide long-term 111 | user authentication across multiple browsing sessions. 112 | 2. (`public`): Transparent claims provided by a third party. 113 | * e.g. Authentication and authorization protocols (OAuth 2, OIDC). 114 | 115 | ## Does PASETO Guarantee the Order of Keys in Its Payload? 116 | 117 | **No.** Consistently guaranteeing a given order to a deserialized JSON string is 118 | nontrivial across programming languages. You should not rely on this ordering behavior 119 | when using PASETO. 120 | 121 | Although ordering is not guaranteed, the contents will be cryptographically verified, 122 | and the thing that gets authenticated is a JSON string, not a deserialized object, so 123 | this non-guarantee will not affect the security of the token. 124 | -------------------------------------------------------------------------------- /docs/01-Protocol-Versions/Version2.md: -------------------------------------------------------------------------------- 1 | # Paseto Version 2 2 | 3 | > PASETO Version 2 is deprecated. Implementations **SHOULD** migrate to [Version 4](Version4.md). 4 | 5 | ## Encrypt 6 | 7 | Given a message `m`, key `k`, and optional footer `f`. 8 | 9 | 1. Before encrypting, first assert that the key being used is intended for use 10 | with `v2.local` tokens, and has a length of 256 bits (32 bytes). 11 | See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) 12 | for more information. 13 | 2. Set header `h` to `v2.local.` 14 | **Note**: This includes the trailing period. 15 | 3. Generate 24 random bytes from the OS's CSPRNG, `b`. 16 | 4. Calculate BLAKE2b of the message `m` with `b` as the key, 17 | with an output length of 24. This will be our nonce, `n`. 18 | * This step is to ensure that an RNG failure does not result in a 19 | nonce-misuse condition that breaks the security of our stream cipher. 20 | 5. Pack `h`, `n`, and `f` together (in that order) using 21 | [PAE](Common.md#authentication-padding). 22 | We'll call this `preAuth`. 23 | 6. Encrypt the message using XChaCha20-Poly1305, using an AEAD interface such as 24 | the one provided in libsodium. 25 | ``` 26 | c = crypto_aead_xchacha20poly1305_encrypt( 27 | message = m 28 | aad = preAuth 29 | nonce = n 30 | key = k 31 | ); 32 | ``` 33 | 7. If `f` is: 34 | * Empty: return h || base64url(n || c) 35 | * Non-empty: return h || base64url(n || c) || `.` || base64url(f) 36 | * ...where || means "concatenate" 37 | * Note: `base64url()` means Base64url from RFC 4648 without `=` padding. 38 | 39 | ## Decrypt 40 | 41 | Given a message `m`, key `k`, and optional footer `f`. 42 | 43 | 1. Before decrypting, first assert that the key being used is intended for use 44 | with `v2.local` tokens, and has a length of 256 bits (32 bytes). 45 | See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) 46 | for more information. 47 | 2. If `f` is not empty, implementations **MAY** verify that the value appended 48 | to the token matches some expected string `f`, provided they do so using a 49 | constant-time string compare function. 50 | 3. Verify that the message begins with `v2.local.`, otherwise throw an 51 | exception. This constant will be referred to as `h`. 52 | **Note**: This includes the trailing period. 53 | 4. Decode the payload (`m` sans `h`, `f`, and the optional trailing period 54 | between `m` and `f`) from base64url to raw binary. Set: 55 | * `n` to the leftmost 24 bytes 56 | * `c` to the middle remainder of the payload, excluding `n`. 57 | 5. Pack `h`, `n`, and `f` together (in that order) using 58 | [PAE](Common.md#authentication-padding). 59 | We'll call this `preAuth` 60 | 6. Decrypt `c` using `XChaCha20-Poly1305`, store the result in `p`. 61 | ``` 62 | p = crypto_aead_xchacha20poly1305_decrypt( 63 | ciphertext = c 64 | aad = preAuth 65 | nonce = n 66 | key = k 67 | ); 68 | ``` 69 | 7. If decryption failed, throw an exception. Otherwise, return `p`. 70 | 71 | ## Sign 72 | 73 | Given a message `m`, Ed25519 secret key `sk`, and 74 | optional footer `f` (which defaults to empty string): 75 | 76 | 1. Before signing, first assert that the key being used is intended for use 77 | with `v2.public` tokens, and is the secret key of the intended keypair. 78 | See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) 79 | for more information. 80 | 2. Set `h` to `v2.public.` 81 | **Note**: This includes the trailing period. 82 | 3. Pack `h`, `m`, and `f` together using 83 | [PAE](Common.md#authentication-padding) 84 | (pre-authentication encoding). We'll call this `m2`. 85 | 4. Sign `m2` using Ed25519 `sk`. We'll call this `sig`. 86 | ``` 87 | sig = crypto_sign_detached( 88 | message = m2, 89 | private_key = sk 90 | ); 91 | ``` 92 | 5. If `f` is: 93 | * Empty: return "`h` || base64url(`m` || `sig`)" 94 | * Non-empty: return "`h` || base64url(`m` || `sig`) || `.` || base64url(`f`)" 95 | * ...where || means "concatenate" 96 | * Note: `base64url()` means Base64url from RFC 4648 without `=` padding. 97 | 98 | ## Verify 99 | 100 | Given a signed message `sm`, public key `pk`, and optional footer `f` 101 | (which defaults to empty string): 102 | 103 | 1. Before verifying, first assert that the key being used is intended for use 104 | with `v2.public` tokens, and is the public key of the intended keypair. 105 | See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) 106 | for more information. 107 | 2. If `f` is not empty, implementations **MAY** verify that the value appended 108 | to the token matches some expected string `f`, provided they do so using a 109 | constant-time string compare function. 110 | 3. Verify that the message begins with `v2.public.`, otherwise throw an exception. 111 | This constant will be referred to as `h`. 112 | **Note**: This includes the trailing period. 113 | 4. Decode the payload (`sm` sans `h`, `f`, and the optional trailing period 114 | between `m` and `f`) from base64url to raw binary. Set: 115 | * `s` to the rightmost 64 bytes 116 | * `m` to the leftmost remainder of the payload, excluding `s` 117 | 5. Pack `h`, `m`, and `f` together using 118 | [PAE](Common.md#authentication-padding). 119 | We'll call this `m2`. 120 | 6. Use Ed25519 to verify that the signature is valid for the message: 121 | ``` 122 | valid = crypto_sign_verify_detached( 123 | signature = s, 124 | message = m2, 125 | public_key = pk 126 | ); 127 | ``` 128 | 7. If the signature is valid, return `m`. Otherwise, throw an exception. 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PASETO: Platform-Agnostic Security Tokens 2 | 3 | Paseto is everything you love about JOSE (JWT, JWE, JWS) without any of the 4 | [many design deficits that plague the JOSE standards](https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-bad-standard-that-everyone-should-avoid). 5 | 6 | # What is Paseto? 7 | 8 | Paseto (Platform-Agnostic SEcurity TOkens) is a specification and reference implementation 9 | for secure stateless tokens. 10 | 11 | Paseto is pronounced paw-set-oh (pɔːsɛtəʊ). 12 | 13 | ## Key Differences between Paseto and JWT 14 | 15 | Unlike JSON Web Tokens (JWT), which gives developers more than enough rope with which to 16 | hang themselves, Paseto only allows secure operations. JWT gives you "algorithm agility", 17 | Paseto gives you "versioned protocols". It's incredibly unlikely that you'll be able to 18 | use Paseto in [an insecure way](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries). 19 | 20 | > **Caution:** Neither JWT nor Paseto were designed for 21 | > [stateless session management](http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/). 22 | > Paseto is suitable for tamper-proof cookies, but cannot prevent replay attacks 23 | > by itself. 24 | 25 | ### Paseto 26 | 27 | #### Paseto Example 1 28 | 29 | ``` 30 | v2.local.QAxIpVe-ECVNI1z4xQbm_qQYomyT3h8FtV8bxkz8pBJWkT8f7HtlOpbroPDEZUKop_vaglyp76CzYy375cHmKCW8e1CCkV0Lflu4GTDyXMqQdpZMM1E6OaoQW27gaRSvWBrR3IgbFIa0AkuUFw.UGFyYWdvbiBJbml0aWF0aXZlIEVudGVycHJpc2Vz 31 | ``` 32 | 33 | This decodes to: 34 | 35 | * Version: `v2` 36 | * Purpose: `local` (shared-key authenticated encryption) 37 | * Payload (hex-encoded): 38 | ``` 39 | 400c48a557be10254d235cf8c506e6fea418a26c93de1f05b55f1bc64cfca412 40 | 56913f1fec7b653a96eba0f0c46542a8a7fbda825ca9efa0b3632dfbe5c1e628 41 | 25bc7b5082915d0b7e5bb81930f25cca9076964c33513a39aa105b6ee06914af 42 | 581ad1dc881b1486b4024b9417 43 | ``` 44 | * Nonce: `400c48a557be10254d235cf8c506e6fea418a26c93de1f05` 45 | * Authentication tag: `6914af581ad1dc881b1486b4024b9417` 46 | * Decrypted Payload: 47 | ```json 48 | { 49 | "data": "this is a signed message", 50 | "exp": "2039-01-01T00:00:00+00:00" 51 | } 52 | ``` 53 | * Key used in this example (hex-encoded): 54 | ``` 55 | 707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f 56 | ``` 57 | * Footer: 58 | ``` 59 | Paragon Initiative Enterprises 60 | ``` 61 | 62 | #### Paseto Example 2 63 | 64 | ``` 65 | v2.public.eyJleHAiOiIyMDM5LTAxLTAxVDAwOjAwOjAwKzAwOjAwIiwiZGF0YSI6InRoaXMgaXMgYSBzaWduZWQgbWVzc2FnZSJ91gC7-jCWsN3mv4uJaZxZp0btLJgcyVwL-svJD7f4IHyGteKe3HTLjHYTGHI1MtCqJ-ESDLNoE7otkIzamFskCA 66 | ``` 67 | 68 | This decodes to: 69 | 70 | * Version: `v2` 71 | * Purpose: `public` (public-key digital signature) 72 | * Payload: 73 | ```json 74 | { 75 | "data": "this is a signed message", 76 | "exp": "2039-01-01T00:00:00+00:00" 77 | } 78 | ``` 79 | * Signature (hex-encoded): 80 | ``` 81 | d600bbfa3096b0dde6bf8b89699c59a746ed2c981cc95c0bfacbc90fb7f8207c 82 | 86b5e29edc74cb8c761318723532d0aa27e1120cb36813ba2d908cda985b2408 83 | ``` 84 | * Public key (hex-encoded): 85 | ``` 86 | 11324397f535562178d53ff538e49d5a162242970556b4edd950c87c7d86648a 87 | ``` 88 | 89 | To learn what each version means, please see [this page in the documentation](https://github.com/paseto-standard/paseto-spec/tree/master/docs/01-Protocol-Versions). 90 | 91 | ### JWT 92 | 93 | An example JWT ([taken from JWT.io](https://jwt.io)) might look like this: 94 | 95 | ``` 96 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ 97 | ``` 98 | 99 | This decodes to: 100 | 101 | **Header**: 102 | ```json 103 | { 104 | "alg": "HS256", 105 | "typ": "JWT" 106 | } 107 | ``` 108 | 109 | **Body**: 110 | ```json 111 | { 112 | "sub": "1234567890", 113 | "name": "John Doe", 114 | "admin": true 115 | } 116 | ``` 117 | 118 | **Signature**: 119 | ``` 120 | TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ 121 | ``` 122 | 123 | ## Motivation 124 | 125 | As you can see, with JWT, you get to specify an `alg` header. There are a lot of options to 126 | choose from (including `none`). 127 | 128 | There have been ways to exploit JWT libraries by replacing RS256 with HS256 and using 129 | the known public key as the HMAC-SHA256 key, thereby allowing arbitrary token forgery. 130 | 131 | With Paseto, your options are `version` and a `purpose`. There are two possible 132 | values for `purpose`: 133 | 134 | * `local` -- shared-key encryption (symmetric-key, [AEAD](https://tonyarcieri.com/all-the-crypto-code-youve-ever-written-is-probably-broken)) 135 | * `public` -- public-key digital signatures (asymmetric-key) 136 | 137 | Paseto only allows you to use [authenticated modes](https://tonyarcieri.com/all-the-crypto-code-youve-ever-written-is-probably-broken). 138 | 139 | Regardless of the purpose selected, the header (and an optional footer, which is always 140 | cleartext but base64url-encoded) is included in the signature or authentication tag. 141 | 142 | ## PASETO Implementations 143 | 144 | The curation of implementations has been moved to [paseto.io](https://paseto.io). 145 | See https://github.com/paragonie/paseto-io for the website source code. 146 | 147 | ### Test Vectors 148 | 149 | See [this repository](https://github.com/paseto-standard/test-vectors) 150 | for the PASETO test vectors. 151 | 152 | ## PASETO Extensions 153 | 154 | ### PASERK 155 | 156 | [PASERK (Platform-Agnostic SERialized Keys)](https://github.com/paseto-standard/paserk) 157 | is an extension to PASETO that provides key-wrapping and serialization. 158 | 159 | PASERK is where envelope encryption, public-key encryption, and 160 | password-based key encryption are specified. 161 | 162 | *PASERK is to PASETO what JWK is to JWT.* 163 | 164 | -------------------------------------------------------------------------------- /docs/02-Implementation-Guide/03-Algorithm-Lucidity.md: -------------------------------------------------------------------------------- 1 | # Algorithm Lucidity 2 | 3 | Algorithm Lucidity refers to resilience against algorithm confusion attacks. 4 | 5 | This document aims to make it easy for PASETO implementations to achieve this property. 6 | 7 | ## PASETO Cryptography Key Requirements 8 | 9 | Cryptography keys in PASETO are defined as **both the raw key material and its 10 | parameter choices, not just the raw key material**. 11 | 12 | PASETO implementations **MUST** enforce some logical separation between different key types; 13 | especially when the raw key material is the same (i.e. a 256-bit opaque blob). 14 | 15 | Arbitrary strings (or byte arrays, or equivalent language constructs) **MUST NOT** 16 | be accepted as a key in any PASETO library, **UNLESS** it's an application-specific 17 | encoding that encapsulates both the key and an algorithm identifier. (For example, 18 | a [`k2.local` PASERK](https://github.com/paseto-standard/paserk/blob/master/types/local.md).) 19 | 20 | In order to allow for key interoperability between different PASETO libraries, 21 | any PASETO library **SHOULD** support the `local`, `public` and `secret` types 22 | from [PASERK](https://github.com/paseto-standard/paserk). 23 | 24 | ## Implementation Guidance 25 | 26 | ### Object-Oriented Languages 27 | 28 | Strict type separation should be employed in object-oriented languages. 29 | 30 | * For local tokens, you only need a `SymmetricKey`. 31 | * For public tokens, you need an `AsymmetricSecretKey` and an `AsymmetricPublicKey`. 32 | 33 | Each key type should be parametrized by two inputs: The key material and an algorithm identifier. 34 | 35 | For example, you might implement something like this: 36 | 37 | ```java 38 | public enum Version { V1, V2, V3, V4 }; 39 | public enum Purpose { PURPOSE_LOCAL, PURPOSE_PUBLIC }; 40 | 41 | abstract class Key { 42 | protected byte[] material; 43 | protected Version version; 44 | 45 | public Key(byte[] keyMaterial, Version version) { 46 | } 47 | 48 | abstract public bool isKeyValidFor(Version v, Purpose p); 49 | 50 | /* ... */ 51 | } 52 | 53 | class SymmetricKey extends Key { 54 | public SymmetricKey(byte[] keyMaterial, Version version) { 55 | super(keyMaterial, version); 56 | } 57 | 58 | public bool isKeyValidFor(Version v, Purpose p) { 59 | return v == this.version && p == Purpose.PURPOSE_LOCAL; 60 | } 61 | } 62 | class AsymmetricSecretKey extends Key { 63 | public SymmetricKey(byte[] keyMaterial, Version version) { 64 | super(keyMaterial, version); 65 | } 66 | 67 | public bool isKeyValidFor(Version v, Purpose p) { 68 | return v == this.version && p == Purpose.PURPOSE_PUBLIC; 69 | } 70 | } 71 | class AsymmetricPublicKey extends Key { 72 | public SymmetricKey(byte[] keyMaterial, Version version) { 73 | super(keyMaterial, version); 74 | } 75 | 76 | public bool isKeyValidFor(Version v, Purpose p) { 77 | return v == this.version && p == Purpose.PURPOSE_PUBLIC; 78 | } 79 | } 80 | ``` 81 | 82 | Whenever you implement one of the PASETO operations, you would then first invoke 83 | `givenKey.isKeyValidFor()` for the expected version and purpose. If it doesn't 84 | return true, throw an `Exception`. 85 | 86 | ### Procedural Languages 87 | 88 | If you're working in a procedural programming language, where the concept of classes isn't 89 | available to lean on, the algorithm identifier should be hard-coded alongside the key material 90 | (e.g. in a `struct` or a header attached to the key material in memory) and checked by the 91 | library. 92 | 93 | Here's a rough pseudocode example for the C programming language: 94 | 95 | ```c 96 | #include "sodium.h" 97 | 98 | enum KeyHeaders { V3_LOCAL, V3_LOCAL, V3_PUBLIC }; 99 | 100 | unsigned char* paseto_v3_local_keygen() { 101 | unsigned char out[33]; 102 | out[0] = (unsigned char) V3_LOCAL & 0xff; 103 | randombytes_buf(out + 1, 32); 104 | return out; 105 | } 106 | unsigned char* paseto_v4_local_keygen() { 107 | unsigned char out[33]; 108 | out[0] = (unsigned char) V4_LOCAL & 0xff; 109 | randombytes_buf(out + 1, 32); 110 | return out; 111 | } 112 | unsigned char* paseto_v4_public_keygen() { 113 | unsigned char out[65]; 114 | unsigned char tmp[32]; 115 | out[0] = (unsigned char) V4_PUBLIC & 0xff; 116 | crypto_sign_keypair(tmp, out + 1); 117 | return out; 118 | } 119 | ``` 120 | 121 | And then when processing a token: 122 | 123 | ```c 124 | int paseto_v4_local_decrypt(unsigned char* out, const unsigned char* in, const unsigned char* key) { 125 | if (key[0] != V4_LOCAL) { 126 | return -1; /* Wrong version or purpose */ 127 | } 128 | } 129 | ``` 130 | 131 | ## Why This Matters 132 | 133 | PASETO largely obviates the risk of algorithm confusion attacks by design. It accomplishes 134 | this by limiting the in-band negotiation to the bare minimum: 135 | 136 | * What version are you using? 137 | * Are you expecting a `local` or `public` token? 138 | 139 | However, PASETO implementations that allow the incorrect key to be used for a given algorithm 140 | may open themselves up to attack. 141 | 142 | By enforcing Algorithm Lucidity, the libraries that implement PASETO (and the applications that 143 | depend on those libraries) can boast a stronger misuse-resistance story. 144 | 145 | The absence of this property isn't a vulnerability, because there may be other mitigating factors 146 | at play. 147 | 148 | For example: If the keys are used in totally disparate code paths, and there is no potential for 149 | a Confused Deputy in the implementation's architecture (n.b., any high-level user-facing API that 150 | switches between the disparate back-end code paths), then the absence of Algorithm Lucidity cannot 151 | cause a security vulnerability. 152 | 153 | However, the moment someone comes along and makes said implementation more user-friendly, the 154 | assumptions that made this *safe* are invalidated. It's also not great to have to decide between 155 | security and convenience. 156 | 157 | The simple solution to that whole conundrum is: **Always implement Algorithm Lucidity in PASETO 158 | libraries, and feel free to make your API as user-friendly as possible.** 159 | -------------------------------------------------------------------------------- /docs/01-Protocol-Versions/Version1.md: -------------------------------------------------------------------------------- 1 | # Paseto Version 1 2 | 3 | > PASETO Version 1 is deprecated. Implementations **SHOULD** migrate to [Version 3](Version3.md). 4 | 5 | ## GetNonce 6 | 7 | Given a message (`m`) and a nonce (`n`): 8 | 9 | 1. Calculate HMAC-SHA384 of the message `m` with `n` as the key. 10 | 2. Return the leftmost 32 bytes of step 1. 11 | 12 | ## Encrypt 13 | 14 | Given a message `m`, key `k`, and optional footer `f` 15 | (which defaults to empty string): 16 | 17 | 1. Before encrypting, first assert that the key being used is intended for use 18 | with `v1.local` tokens, and has a length of 256 bits (32 bytes). 19 | See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) 20 | for more information. 21 | 2. Set header `h` to `v1.local.` 22 | **Note**: This includes the trailing period. 23 | 3. Generate 32 random bytes from the OS's CSPRNG, `b`. 24 | 4. Calculate `GetNonce()` of `m` and the `b` to get the nonce, `n`. 25 | * This step is to ensure that an RNG failure does not result 26 | in a nonce-misuse condition that breaks the security of 27 | our stream cipher. 28 | 5. Split the key into an Encryption key (`Ek`) and Authentication key (`Ak`), 29 | using the leftmost 16 bytes of `n` as the HKDF salt: 30 | ``` 31 | Ek = hkdf_sha384( 32 | len = 32 33 | ikm = k, 34 | info = "paseto-encryption-key", 35 | salt = n[0:16] 36 | ); 37 | Ak = hkdf_sha384( 38 | len = 32 39 | ikm = k, 40 | info = "paseto-auth-key-for-aead", 41 | salt = n[0:16] 42 | ); 43 | ``` 44 | 6. Encrypt the message using `AES-256-CTR`, using `Ek` as the key and 45 | the rightmost 16 bytes of `n` as the nonce. We'll call this `c`: 46 | ``` 47 | c = aes256ctr_encrypt( 48 | plaintext = m, 49 | nonce = n[16:] 50 | key = Ek 51 | ); 52 | ``` 53 | 7. Pack `h`, `n`, `c`, and `f` together using 54 | [PAE](Common.md#authentication-padding) 55 | (pre-authentication encoding). We'll call this `preAuth` 56 | 8. Calculate HMAC-SHA384 of the output of `preAuth`, using `Ak` as the 57 | authentication key. We'll call this `t`. 58 | 9. If `f` is: 59 | * Empty: return "`h` || base64url(`n` || `c` || `t`)" 60 | * Non-empty: return "`h` || base64url(`n` || `c` || `t`) || `.` || base64url(`f`)" 61 | * ...where || means "concatenate" 62 | * Note: `base64url()` means Base64url from RFC 4648 without `=` padding. 63 | 64 | ## Decrypt 65 | 66 | Given a message `m`, key `k`, and optional footer `f` 67 | (which defaults to empty string): 68 | 69 | 1. Before decrypting, first assert that the key being used is intended for use 70 | with `v1.local` tokens, and has a length of 256 bits (32 bytes). 71 | See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) 72 | for more information. 73 | 2. If `f` is not empty, implementations **MAY** verify that the value appended 74 | to the token matches some expected string `f`, provided they do so using a 75 | constant-time string compare function. 76 | 3. Verify that the message begins with `v1.local.`, otherwise throw an 77 | exception. This constant will be referred to as `h`. 78 | **Note**: This includes the trailing period. 79 | 4. Decode the payload (`m` sans `h`, `f`, and the optional trailing period 80 | between `m` and `f`) from base64url to raw binary. Set: 81 | * `n` to the leftmost 32 bytes 82 | * `t` to the rightmost 48 bytes 83 | * `c` to the middle remainder of the payload, excluding `n` and `t` 84 | 5. Split the key (`k`) into an Encryption key (`Ek`) and an Authentication key 85 | (`Ak`), using the leftmost 16 bytes of `n` as the HKDF salt. 86 | * For encryption keys, the **info** parameter for HKDF **MUST** be set to 87 | **paseto-encryption-key**. 88 | * For authentication keys, the **info** parameter for HKDF **MUST** be set to 89 | **paseto-auth-key-for-aead**. 90 | * The output length **MUST** be 32 for both keys. 91 | 92 | ``` 93 | Ek = hkdf_sha384( 94 | len = 32 95 | ikm = k, 96 | info = "paseto-encryption-key", 97 | salt = n[0:16] 98 | ); 99 | Ak = hkdf_sha384( 100 | len = 32 101 | ikm = k, 102 | info = "paseto-auth-key-for-aead", 103 | salt = n[0:16] 104 | ); 105 | ``` 106 | 6. Pack `h`, `n`, `c`, and `f` together (in that order) using 107 | [PAE](Common.md#authentication-padding). 108 | We'll call this `preAuth`. 109 | 7. Recalculate HMAC-SHA-384 of `preAuth` using `Ak` as the key. We'll call this 110 | `t2`. 111 | 8. Compare `t` with `t2` using a constant-time string compare function. If they 112 | are not identical, throw an exception. 113 | 9. Decrypt `c` using `AES-256-CTR`, using `Ek` as the key and the rightmost 16 114 | bytes of `n` as the nonce, and return this value. 115 | ``` 116 | return aes256ctr_decrypt( 117 | cipherext = c, 118 | nonce = n[16:] 119 | key = Ek 120 | ); 121 | ``` 122 | 123 | ## Sign 124 | 125 | Given a message `m`, 2048-bit RSA secret key `sk`, and 126 | optional footer `f` (which defaults to empty string): 127 | 128 | 1. Before signing, first assert that the key being used is intended for use 129 | with `v1.public` tokens, and is the secret key of the intended keypair. 130 | See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) 131 | for more information. 132 | 2. Set `h` to `v1.public.` 133 | **Note**: This includes the trailing period. 134 | 3. Pack `h`, `m`, and `f` together using 135 | [PAE](Common.md#authentication-padding) 136 | (pre-authentication encoding). We'll call this `m2`. 137 | 4. Sign `m2` using RSA with the private key `sk`. We'll call this `sig`. 138 | ``` 139 | sig = crypto_sign_rsa( 140 | message = m2, 141 | private_key = sk, 142 | padding_mode = "pss", 143 | public_exponent = 65537, 144 | hash = "sha384" 145 | mgf = "mgf1+sha384" 146 | ); 147 | ``` 148 | Only the above parameters are supported. PKCS1v1.5 is explicitly forbidden. 149 | 5. If `f` is: 150 | * Empty: return "`h` || base64url(`m` || `sig`)" 151 | * Non-empty: return "`h` || base64url(`m` || `sig`) || `.` || base64url(`f`)" 152 | * ...where || means "concatenate" 153 | * Note: `base64url()` means Base64url from RFC 4648 without `=` padding. 154 | 155 | ## Verify 156 | 157 | Given a signed message `sm`, RSA public key `pk`, and optional 158 | footer `f` (which defaults to empty string): 159 | 160 | 1. Before verifying, first assert that the key being used is intended for use 161 | with `v1.public` tokens, and is the public key of the intended keypair. 162 | See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) 163 | for more information. 164 | 2. If `f` is not empty, implementations **MAY** verify that the value appended 165 | to the token matches some expected string `f`, provided they do so using a 166 | constant-time string compare function. 167 | 3. Verify that the message begins with `v1.public.`, otherwise throw an 168 | exception. This constant will be referred to as `h`. 169 | **Note**: This includes the trailing period. 170 | 4. Decode the payload (`sm` sans `h`, `f`, and the optional trailing period 171 | between `m` and `f`) from base64url to raw binary. Set: 172 | * `s` to the rightmost 256 bytes 173 | * `m` to the leftmost remainder of the payload, excluding `s` 174 | 5. Pack `h`, `m`, and `f` together (in that order) using PAE (see 175 | [PAE](Common.md#authentication-padding). 176 | We'll call this `m2`. 177 | 6. Use RSA to verify that the signature is valid for the message: 178 | ``` 179 | valid = crypto_sign_rsa_verify( 180 | signature = s, 181 | message = m2, 182 | public_key = pk, 183 | padding_mode = "pss", 184 | public_exponent = 65537, 185 | hash = "sha384" 186 | mgf = "mgf1+sha384" 187 | ); 188 | ``` 189 | 7. If the signature is valid, return `m`. Otherwise, throw an exception. 190 | -------------------------------------------------------------------------------- /docs/01-Protocol-Versions/Version4.md: -------------------------------------------------------------------------------- 1 | # Paseto Version 4 2 | 3 | ## Encrypt 4 | 5 | Given a message `m`, key `k`, and optional footer `f` (which defaults to empty 6 | string), and an optional implicit assertion `i` (which defaults to empty string): 7 | 8 | 1. Before encrypting, first assert that the key being used is intended for use 9 | with `v4.local` tokens, and has a length of 256 bits (32 bytes). 10 | See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) 11 | for more information. 12 | 2. Set header `h` to `v4.local.` 13 | **Note**: This includes the trailing period. 14 | 3. Generate 32 random bytes from the OS's CSPRNG, `n`. 15 | 4. Split the key into an Encryption key (`Ek`) and Authentication key (`Ak`), 16 | using keyed BLAKE2b, using the domain separation constants and `n` as the 17 | message, and the input key as the key. The first value will be 56 bytes, 18 | the second will be 32 bytes. 19 | The derived key will be the leftmost 32 bytes of the hash output. 20 | The remaining 24 bytes will be used as a counter nonce (`n2`): 21 | ``` 22 | tmp = crypto_generichash( 23 | msg = "paseto-encryption-key" || n, 24 | key = key, 25 | length = 56 26 | ); 27 | Ek = tmp[0:32] 28 | n2 = tmp[32:] 29 | Ak = crypto_generichash( 30 | msg = "paseto-auth-key-for-aead" || n, 31 | key = key, 32 | length = 32 33 | ); 34 | ``` 35 | 5. Encrypt the message using XChaCha20, using `n2` from step 3 as the nonce and `Ek` as the key. 36 | ``` 37 | c = crypto_stream_xchacha20_xor( 38 | message = m 39 | nonce = n2 40 | key = Ek 41 | ); 42 | ``` 43 | 6. Pack `h`, `n`, `c`, `f`, and `i` together (in that order) using 44 | [PAE](Common.md#authentication-padding). 45 | We'll call this `preAuth`. 46 | 7. Calculate BLAKE2b-MAC of the output of `preAuth`, using `Ak` as the 47 | authentication key. We'll call this `t`. 48 | ``` 49 | t = crypto_generichash( 50 | message = preAuth 51 | key = Ak, 52 | length = 32 53 | ); 54 | ``` 55 | 8. If `f` is: 56 | * Empty: return h || base64url(n || c || t) 57 | * Non-empty: return h || base64url(n || c || t) || `.` || base64url(f) 58 | * ...where || means "concatenate" 59 | * Note: `base64url()` means Base64url from RFC 4648 without `=` padding. 60 | 61 | ## Decrypt 62 | 63 | Given a message `m`, key `k`, and optional footer `f` 64 | (which defaults to empty string), and an optional 65 | implicit assertion `i` (which defaults to empty string): 66 | 67 | 1. Before decrypting, first assert that the key being used is intended for use 68 | with `v4.local` tokens, and has a length of 256 bits (32 bytes). 69 | See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) 70 | for more information. 71 | 2. If `f` is not empty, implementations **MAY** verify that the value appended 72 | to the token matches some expected string `f`, provided they do so using a 73 | constant-time string compare function. 74 | 3. Verify that the message begins with `v4.local.`, otherwise throw an 75 | exception. This constant will be referred to as `h`. 76 | * **Note**: This header includes the trailing period. 77 | * **Future-proofing**: If a future PASETO variant allows for encodings other 78 | than JSON (e.g., CBOR), future implementations **MAY** also permit those 79 | values at this step (e.g. `v4c.local.`). 80 | 4. Decode the payload (`m` sans `h`, `f`, and the optional trailing period 81 | between `m` and `f`) from base64url to raw binary. Set: 82 | * `n` to the leftmost 32 bytes 83 | * `t` to the rightmost 32 bytes 84 | * `c` to the middle remainder of the payload, excluding `n` and `t`. 85 | 5. Split the key into an Encryption key (`Ek`) and Authentication key (`Ak`), 86 | using keyed BLAKE2b, using the domain separation constants and `n` as the 87 | message, and the input key as the key. The first value will be 56 bytes, 88 | the second will be 32 bytes. 89 | The derived key will be the leftmost 32 bytes of the hash output. 90 | The remaining 24 bytes will be used as a counter nonce (`n2`): 91 | ``` 92 | tmp = crypto_generichash( 93 | msg = "paseto-encryption-key" || n, 94 | key = key, 95 | length = 56 96 | ); 97 | Ek = tmp[0:32] 98 | n2 = tmp[32:] 99 | Ak = crypto_generichash( 100 | msg = "paseto-auth-key-for-aead" || n, 101 | key = key, 102 | length = 32 103 | ); 104 | ``` 105 | 6. Pack `h`, `n`, `c`, `f`, and `i` together (in that order) using 106 | [PAE](Common.md#authentication-padding). 107 | We'll call this `preAuth`. 108 | 7. Re-calculate BLAKE2b-MAC of the output of `preAuth`, using `Ak` as the 109 | authentication key. We'll call this `t2`. 110 | ``` 111 | t2 = crypto_generichash( 112 | message = preAuth 113 | key = Ak, 114 | length = 32 115 | ); 116 | ``` 117 | 8. Compare `t` with `t2` using a constant-time string compare function. If they 118 | are not identical, throw an exception. 119 | * You **MUST** use a constant-time string compare function to be compliant. 120 | If you do not have one available to you in your programming language/framework, 121 | you MUST use [Double HMAC](https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy). 122 | 9. Decrypt `c` using `XChaCha20`, store the result in `p`. 123 | ``` 124 | p = crypto_stream_xchacha20_xor( 125 | ciphertext = c 126 | nonce = n2 127 | key = Ek 128 | ); 129 | ``` 130 | 10. If decryption failed, throw an exception. Otherwise, return `p`. 131 | 132 | ## Sign 133 | 134 | Given a message `m`, Ed25519 secret key `sk`, and 135 | optional footer `f` (which defaults to empty string), and an optional 136 | implicit assertion `i` (which defaults to empty string): 137 | 138 | 1. Before signing, first assert that the key being used is intended for use 139 | with `v4.public` tokens, and is the secret key of the intended keypair. 140 | See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) 141 | for more information. 142 | 2. Set `h` to `v4.public.` 143 | **Note**: This includes the trailing period. 144 | 3. Pack `h`, `m`, `f`, and `i` together using 145 | [PAE](Common.md#authentication-padding) 146 | (pre-authentication encoding). We'll call this `m2`. 147 | 4. Sign `m2` using Ed25519 `sk`. We'll call this `sig`. 148 | ``` 149 | sig = crypto_sign_detached( 150 | message = m2, 151 | private_key = sk 152 | ); 153 | ``` 154 | 5. If `f` is: 155 | * Empty: return "`h` || base64url(`m` || `sig`)" 156 | * Non-empty: return "`h` || base64url(`m` || `sig`) || `.` || base64url(`f`)" 157 | * ...where || means "concatenate" 158 | * Note: `base64url()` means Base64url from RFC 4648 without `=` padding. 159 | 160 | ## Verify 161 | 162 | Given a signed message `sm`, public key `pk`, and optional footer `f` 163 | (which defaults to empty string), and an optional 164 | implicit assertion `i` (which defaults to empty string): 165 | 166 | 1. Before verifying, first assert that the key being used is intended for use 167 | with `v4.public` tokens, and is the public key of the intended keypair. 168 | See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) 169 | for more information. 170 | 2. If `f` is not empty, implementations **MAY** verify that the value appended 171 | to the token matches some expected string `f`, provided they do so using a 172 | constant-time string compare function. 173 | 3. Verify that the message begins with `v4.public.`, otherwise throw an exception. 174 | This constant will be referred to as `h`. 175 | **Note**: This includes the trailing period. 176 | 4. Decode the payload (`sm` sans `h`, `f`, and the optional trailing period 177 | between `m` and `f`) from base64url to raw binary. Set: 178 | * `s` to the rightmost 64 bytes 179 | * `m` to the leftmost remainder of the payload, excluding `s` 180 | 5. Pack `h`, `m`, `f`, `i` together using 181 | [PAE](Common.md#authentication-padding). 182 | We'll call this `m2`. 183 | 6. Use Ed25519 to verify that the signature is valid for the message: 184 | ``` 185 | valid = crypto_sign_verify_detached( 186 | signature = s, 187 | message = m2, 188 | public_key = pk 189 | ); 190 | ``` 191 | 7. If the signature is valid, return `m`. Otherwise, throw an exception. 192 | -------------------------------------------------------------------------------- /docs/01-Protocol-Versions/README.md: -------------------------------------------------------------------------------- 1 | # Protocol Versions 2 | 3 | This document describes the cryptography and encoding rules for Paseto protocol versions, 4 | to assist in cross-platform library development. 5 | 6 | ## Naming Conventions 7 | 8 | The cryptography protocol version is named using this convention: `/^Version \d$/`. 9 | When we discuss "Version 4" spelled out, we're talking about the cryptography 10 | without any regard to the underlying encoding format for the payload containing 11 | the claims. 12 | 13 | The token format is named using this convention: `/^v\d([a-z]+?)$/`. When 14 | no optional suffix is provided, this describes a PASETO token using JSON 15 | to encode claims, along with the corresponding Version (see previous paragraph) 16 | to protect those claims. 17 | 18 | The intent is that the cryptographic format ("Version 3", "Version 4") can be 19 | reused for arbitrary payloads, but the token format ("v3", "v4") refers to a 20 | specific encoding of claims under-the-hood. 21 | 22 | If this is confusing, just know that most of the time, you only need to deal 23 | with the complete token (n.b., some permutation of {`v1`, `v2`, `v3`, `v4`} 24 | and {`local`, `public`}). 25 | The cryptographic layer (`Version 1`, `Version 2`, `Version 3`, `Version 4`) 26 | is mostly for cryptographers to argue about. 27 | 28 | ## Rules for Current and Future Protocol Versions 29 | 30 | 1. Everything must be authenticated. Attackers should never be allowed the opportunity 31 | to alter messages freely. 32 | * If encryption is specified, unauthenticated modes (e.g. AES-CBC) are forbidden. 33 | * The nonce or initialization vector must be covered by the authentication 34 | tag, not just the ciphertext. 35 | * Some degree of nonce-misuse resistance should be provided by any future schemes. 36 | 2. Non-deterministic, stateful, and otherwise dangerous signature schemes (e.g. ~~ECDSA 37 | without RFC 6979,~~ XMSS) are forbidden. 38 | * ECDSA without RFC 6979 is permitted, but *only* when a CSPRNG is reliably available. 39 | If this cannot be guaranteed, you **MUST NOT** implement ECDSA without RFC 6979. 40 | 3. Public-key cryptography must be IND-CCA2 secure to be considered for inclusion. 41 | * This means no RSA with PKCS1v1.5 padding, textbook RSA, etc. 42 | 4. By default, libraries should only allow the two most recent versions in a family 43 | to be used. 44 | * The NIST family of versions is `Version 1` and `Version 3`. 45 | * The Sodium family of versions is `Version 2` and `Version 4`. 46 | * If a future post-quantum `Version 5` (NIST) and/or `Version 6` (Sodium) is defined, 47 | `Version 1` and `Version 2` should no longer be accepted. 48 | * This is a deviation from the **original** intent of this rule, to encapsulate 49 | the fact that we have parallel versions. In the future, we expect this to converge 50 | to one family of versions. 51 | 5. New versions will be decided and formalized by the PASETO developers. 52 | * User-defined homemade protocols are discouraged. If implementors wish to break 53 | this rule and define their own custom protocol suite, they must NOT continue 54 | the {`v1`, `v2`, ... } series naming convention for tokens. 55 | * Any version identifiers that match the regular expression, 56 | `/^v[0-9\-\.]+([a-z]+?)/` are reserved by the PASETO development team. 57 | 58 | # Versions 59 | 60 | ## Version 1: NIST Compatibility 61 | 62 | See [the version 1 specification](Version1.md) for details. At a glance: 63 | 64 | * **`v1.local`**: Symmetric Authenticated Encryption: 65 | * AES-256-CTR + HMAC-SHA384 (Encrypt-then-MAC) 66 | * Key-splitting: HKDF-SHA384 67 | * Info for encryption key: `paseto-encryption-key` 68 | * Info for authentication key: `paseto-auth-key-for-aead` 69 | * 32-byte nonce (first half for AES-CTR, latter half for the HKDF salt) 70 | * The nonce calculated from HMAC-SHA384(message, `random_bytes(32)`) 71 | truncated to 32 bytes, during encryption only 72 | * The HMAC covers the header, nonce, and ciphertext 73 | * It also covers the footer, if provided 74 | * Reference implementation in [Version1.php](https://github.com/paragonie/paseto/blob/master/src/Protocol/Version1.php): 75 | * See `aeadEncrypt()` for encryption 76 | * See `aeadDecrypt()` for decryption 77 | * **`v1.public`**: Asymmetric Authentication (Public-Key Signatures): 78 | * 2048-bit RSA keys 79 | * RSASSA-PSS with 80 | * Hash function: SHA384 as the hash function 81 | * Mask generation function: MGF1+SHA384 82 | * Public exponent: 65537 83 | * Reference implementation in [Version1.php](https://github.com/paragonie/paseto/blob/master/src/Protocol/Version1.php): 84 | * See `sign()` for signature generation 85 | * See `verify()` for signature verification 86 | 87 | Version 1 implements the best possible RSA + AES + SHA2 ciphersuite. We only use 88 | OAEP and PSS for RSA encryption and RSA signatures (respectively), never PKCS1v1.5. 89 | 90 | Version 1 is recommended only for legacy systems that cannot use modern cryptography. 91 | 92 | See also: [Common implementation details for all versions](Common.md). 93 | 94 | ## Version 2: Sodium Original 95 | 96 | See [the version 2 specification](Version2.md) for details. At a glance: 97 | 98 | * **`v2.local`**: Symmetric Encryption: 99 | * XChaCha20-Poly1305 (192-bit nonce, 256-bit key, 128-bit authentication tag) 100 | * Encrypting: `sodium_crypto_aead_xchacha20poly1305_ietf_encrypt()` 101 | * Decrypting: `sodium_crypto_aead_xchacha20poly1305_ietf_decrypt()` 102 | * The nonce is calculated from `sodium_crypto_generichash()` of the message, 103 | with a BLAKE2b key provided by `random_bytes(24)` and an output length of 24, 104 | during encryption only 105 | * Reference implementation in [Version2.php](https://github.com/paragonie/paseto/blob/master/src/Protocol/Version2.php): 106 | * See `aeadEncrypt()` for encryption 107 | * See `aeadDecrypt()` for decryption 108 | * **`v2.public`**: Asymmetric Authentication (Public-Key Signatures): 109 | * Ed25519 (EdDSA over Curve25519) 110 | * Signing: `sodium_crypto_sign_detached()` 111 | * Verifying: `sodium_crypto_sign_verify_detached()` 112 | * Reference implementation in [Version2.php](https://github.com/paragonie/paseto/blob/master/src/Protocol/Version2.php): 113 | * See `sign()` for signature generation 114 | * See `verify()` for signature verification 115 | 116 | See also: [Common implementation details for all versions](Common.md). 117 | 118 | ## Version 3: NIST Modern 119 | 120 | See [the version 3 specification](Version3.md) for details. At a glance: 121 | 122 | * **`v3.local`**: Symmetric Authenticated Encryption: 123 | * AES-256-CTR + HMAC-SHA384 (Encrypt-then-MAC) 124 | * Key-splitting: HKDF-SHA384 125 | * Info for encryption key: `paseto-encryption-key` 126 | The encryption key and implicit counter nonce are both returned 127 | from HKDF in this version. 128 | * Info for authentication key: `paseto-auth-key-for-aead` 129 | * 32-byte nonce (no longer prehashed), passed entirely to HKDF 130 | (as part of the `info` tag, rather than as a salt). 131 | * The HMAC covers the header, nonce, and ciphertext 132 | * It also covers the footer, if provided 133 | * It also covers the implicit assertions, if provided 134 | * **`v3.public`**: Asymmetric Authentication (Public-Key Signatures): 135 | * ECDSA over NIST P-384, with SHA-384, 136 | using [RFC 6979 deterministic k-values](https://tools.ietf.org/html/rfc6979) 137 | (if reasonably practical; otherwise a CSPRNG **MUST** be used). 138 | Hedged signatures are allowed too. 139 | * The public key is also included in the PAE step, to ensure 140 | `v3.public` tokens provide Exclusive Ownership. 141 | 142 | See also: [Common implementation details for all versions](Common.md). 143 | 144 | ## Version 4: Sodium Modern 145 | 146 | See [the version 4 specification](Version4.md) for details. At a glance: 147 | 148 | * **`v4.local`**: Symmetric Authenticated Encryption: 149 | * XChaCha20 + BLAKE2b-MAC (Encrypt-then-MAC) 150 | * Key-splitting: BLAKE2b 151 | * Info for encryption key: `paseto-encryption-key` 152 | The encryption key and implicit counter nonce are both returned 153 | from BLAKE2b in this version. 154 | * Info for authentication key: `paseto-auth-key-for-aead` 155 | * 32-byte nonce (no longer prehashed), passed entirely to BLAKE2b. 156 | * The BLAKE2b-MAC covers the header, nonce, and ciphertext 157 | * It also covers the footer, if provided 158 | * It also covers the implicit assertions, if provided 159 | * **`v4.public`**: Asymmetric Authentication (Public-Key Signatures): 160 | * Ed25519 (EdDSA over Curve25519) 161 | * Signing: `sodium_crypto_sign_detached()` 162 | * Verifying: `sodium_crypto_sign_verify_detached()` 163 | 164 | See also: [Common implementation details for all versions](Common.md). 165 | -------------------------------------------------------------------------------- /docs/02-Implementation-Guide/01-Payload-Processing.md: -------------------------------------------------------------------------------- 1 | # Payload Processing 2 | 3 | All PASETO payloads must be a JSON-encoded object represented as a UTF-8 encoded 4 | string. The topmost JSON object should be an object, map, or associative array 5 | (select appropriate for your language), not a flat array or list. 6 | 7 | PASETO library implementors **MUST** ensure uniqueness of object key names. 8 | 9 | > **Valid**: 10 | > 11 | > * `{"foo":"bar"}` 12 | > * `{"foo":"bar","baz":12345,"678":["a","b","c"]}` 13 | > * `{}` 14 | > 15 | > **Invalid**: 16 | > 17 | > * `[{"foo":"bar"}]` 18 | > * `["foo"]` 19 | > * `{0: "test"}` 20 | > * `[]` 21 | > * (Empty string) 22 | > * `{"foo":"bar","foo":"baz"}` 23 | 24 | If non-UTF-8 character sets are desired for some fields, implementors are 25 | encouraged to use [Base64url](https://tools.ietf.org/html/rfc4648#page-7) 26 | encoding to preserve the original intended binary data, but still use UTF-8 for 27 | the actual payloads. 28 | 29 | ## Type Safety with Cryptographic Keys 30 | 31 | PASETO library implementations **MUST** implement some means of preventing type 32 | confusion bugs between different cryptography keys. For example: 33 | 34 | * Prepending each key in memory with a magic byte to serve as a type indicator 35 | (distinct for every combination of version and purpose). 36 | * In object-oriented programming languages, using separate classes for each 37 | cryptography key object that may share an interface or common base class. 38 | 39 | It **MUST NOT** be possible for a user to take a known public key (used by 40 | *public* tokens), and generate a *local* token with the same key that any PASETO 41 | implementations will accept. 42 | 43 | See [Algorithm Lucidity](03-Algorithm-Lucidity.md) for more information and guidance. 44 | 45 | ## Optional Footer 46 | 47 | PASETO places no restrictions on the contents of the authenticated footer. 48 | The footer's contents **MAY** be JSON-encoded (as is the payload), but it 49 | doesn't have to be. 50 | 51 | The footer contents is intended to be free-form and application-specific. 52 | 53 | ### Storing JSON in the Footer 54 | 55 | Implementations that allow users to store JSON-encoded objects in the footer 56 | **MUST** give users some mechanism to validate the footer before decoding. 57 | 58 | Some example parser rules include: 59 | 60 | 1. Enforcing a maximum length of the JSON-encoded string. 61 | 2. Enforcing a maximum depth of the decoded JSON object. 62 | (Recommended default: Only 1-dimensional objects.) 63 | 3. Enforcing the maximum number of named keys within an object. 64 | 65 | The motivation for these additional rules is to mitigate the following 66 | security risks: 67 | 68 | 1. Stack overflows in JSON parsers caused by too much recursion. 69 | 2. Denial-of-Service attacks enabled by hash-table collisions. 70 | 71 | #### Enforcing Maximum Depth Without Parsing the JSON String 72 | 73 | Arbitrary-depth JSON strings can be a risk for stack overflows in some JSON 74 | parsing libraries. One mitigation to this is to enforce an upper limit on the 75 | maximum stack depth. Some JSON libraries do not allow you to configure this 76 | upper limit, so you're forced to take matters into your own hands. 77 | 78 | A simple way of enforcing the maximum depth of a JSON string without having 79 | to parse it with your JSON library is to employ the following algorithm: 80 | 81 | 1. Create a copy of the JSON string with all `\"` sequences and whitespace 82 | characters removed. 83 | This will prevent weird edge cases in step 2. 84 | 2. Use a regular expression to remove all quoted strings and their contents. 85 | For example, replacing `/"[^"]+?"([:,\}\]])/` with the first match will 86 | strip the contents of any quoted strings. 87 | 3. Remove all characters except `[`, `{`, `}`, and `]`. 88 | 4. If you're left with an empty string, return `1`. 89 | 5. Initialize a variable called `depth` to `1`. 90 | 6. While the stripped variable is not empty **and** not equal to the output 91 | of the previous iteration, remove all `{}` and `[]` pairs, then increment 92 | `depth`. 93 | 7. If you end up with a non-empty string, you know you have invalid JSON: 94 | Either you have a `[` that isn't paired with a `]`, or a `{` that isn't 95 | paired with a `}`. Throw an exception. 96 | 8. Return `depth`. 97 | 98 | An example of this logic implemented in TypeScript is below: 99 | 100 | ```typescript 101 | function getJsonDepth(data: string): number { 102 | // Step 1 103 | let stripped = data.replace(/\\"/g, '').replace(/\s+/g, ''); 104 | 105 | // Step 2 106 | stripped = stripped.replace(/"[^"]+"([:,\}\]])/g, '$1'); 107 | 108 | // Step 3 109 | stripped = stripped.replace(/[^\[\{\}\]]/g, ''); 110 | 111 | // Step 4 112 | if (stripped.length === 0) { 113 | return 1; 114 | } 115 | // Step 5 116 | let previous = ''; 117 | let depth = 1; 118 | 119 | // Step 6 120 | while (stripped.length > 0 && stripped !== previous) { 121 | previous = stripped; 122 | stripped = stripped.replace(/({}|\[\])/g, ''); 123 | depth++; 124 | } 125 | 126 | // Step 7 127 | if (stripped.length > 0) { 128 | throw new Error(`Invalid JSON string`); 129 | } 130 | 131 | // Step 8 132 | return depth; 133 | } 134 | ``` 135 | 136 | #### Enforcing Maximum Key Count Without Parsing the JSON String 137 | 138 | Hash-collision Denial of Service attacks (Hash-DoS) is made possible by 139 | creating a very large number of keys that will hash to the same value, 140 | with a given hash function (e.g., djb33). 141 | 142 | One mitigation strategy is to limit the number of keys contained within 143 | an object (at any arbitrary depth). 144 | 145 | The easiest way is to count the number of times you encounter a `":` 146 | token that isn't followed by a backslash (to side-step corner-cases where 147 | JSON is encoded as a string inside a JSON value). 148 | 149 | Here's an example implementation in TypeScript: 150 | 151 | ```typescript 152 | /** 153 | * Split the string based on the number of `":` pairs without a preceding 154 | * backslash, then return the number of pieces it was broken into. 155 | */ 156 | function countKeys(json: string): number { 157 | return json.split(/[^\\]":/).length; 158 | } 159 | ``` 160 | 161 | ## Registered Claims 162 | 163 | This has been moved to a separate document to make it easier to locate: 164 | [Registered Claims](04-Claims.md). 165 | 166 | ### Key-ID Support 167 | 168 | Some systems need to support key rotation, but since the payloads of a `local` 169 | token are always encrypted, you can't just drop a `kid` claim inside the payload. 170 | 171 | Instead, users should store Key-ID claims (`kid`) in the unencrypted footer. 172 | 173 | For example, if you set the footer to `{"kid":"gandalf0"}`, you can read it without 174 | needing to first decrypt the token (which would in turn knowing which key to use to 175 | decrypt the token). 176 | 177 | [PASERK](https://github.com/paseto-standard/paserk), a PASETO extension, defines a 178 | universal and unambiguous way to calculate key identifiers for a PASETO key. See 179 | [the specification for PASERK's `ID` operation](https://github.com/paseto-standard/paserk/blob/master/operations/ID.md) 180 | for more information. PASERK is the **RECOMMENDED** way to serialize Key IDs. 181 | 182 | Implementations should feel free to provide a means to extract the footer from a token, 183 | before decryption, since the footer is used in the calculation of the authentication 184 | tag for the encrypted payload. 185 | 186 | Users should beware that, until this authentication tag has been verified, the 187 | footer's contents are not authenticated. 188 | 189 | While a key identifier can generally be safely used for selecting the cryptographic 190 | key used to decrypt and/or verify payloads before verification, provided that they 191 | `key-id` is a public number that is associated with a particular key which is not 192 | supplied by attackers, any other fields stored in the footer MUST be distrusted 193 | until the payload has been verified. 194 | 195 | **IMPORTANT**: Key identifiers MUST be independent of the actual keys 196 | used by Paseto. 197 | 198 | For example, you MUST NOT just drop the public key into the footer for 199 | a `public` token and have the recipient use the provided public key. 200 | Doing so would allow an attacker to simply replace the public key with 201 | one of their own choosing, which will cause the recipient to simply 202 | accept any signature for any message as valid, which defeats the 203 | security goals of public-key cryptography. 204 | 205 | Instead, it's recommended that implementors and users use a unique 206 | identifier for each key (independent of the cryptographic key's contents 207 | itself) that is used in a database or other key-value store to select 208 | the apppropriate cryptographic key. These search operations MUST fail 209 | closed if no valid key is found for the given key identifier. 210 | 211 | ## Future Changes to Payload Processing 212 | 213 | The payload processing SHOULD NOT change after version 1.0.0 of the reference 214 | implementation has been tagged, signed, and released; only the cryptography 215 | protocols will receive new versions. 216 | 217 | In the event that this turns out to not be true, we will change the first letter 218 | of the version identifier (`v`) to another ASCII-compatible alphanumeric character. 219 | 220 | However, we hope to never need to do this. 221 | -------------------------------------------------------------------------------- /docs/01-Protocol-Versions/Version3.md: -------------------------------------------------------------------------------- 1 | # Paseto Version 3 2 | 3 | ## GetNonce 4 | 5 | Throw an exception. We don't do this in version 3. 6 | 7 | ## Encrypt 8 | 9 | Given a message `m`, key `k`, and optional footer `f` (which defaults to empty 10 | string), and an optional implicit assertion `i` (which defaults to empty string): 11 | 12 | 1. Before encrypting, first assert that the key being used is intended for use 13 | with `v3.local` tokens, and has a length of 256 bits (32 bytes). 14 | See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) 15 | for more information. 16 | 2. Set header `h` to `v3.local.` 17 | **Note**: This includes the trailing period. 18 | 3. Generate 32 random bytes from the OS's CSPRNG to get the nonce, `n`. 19 | 4. Split the key into an Encryption key (`Ek`) and Authentication key (`Ak`), 20 | using HKDF-HMAC-SHA384, with `n` appended to the info rather than the salt. 21 | * The output length **MUST** be 48 for both key derivations. 22 | * The derived key will be the leftmost 32 bytes of the first HKDF derivation. 23 | 24 | The remaining 16 bytes of the first key derivation (from which `Ek` is derived) 25 | will be used as a counter nonce (`n2`): 26 | ``` 27 | tmp = hkdf_sha384( 28 | len = 48, 29 | ikm = k, 30 | info = "paseto-encryption-key" || n, 31 | salt = NULL 32 | ); 33 | Ek = tmp[0:32] 34 | n2 = tmp[32:] 35 | Ak = hkdf_sha384( 36 | len = 48, 37 | ikm = k, 38 | info = "paseto-auth-key-for-aead" || n, 39 | salt = NULL 40 | ); 41 | ``` 42 | 5. Encrypt the message using `AES-256-CTR`, using `Ek` as the key and `n2` as the nonce. 43 | We'll call the encrypted output of this step `c`: 44 | ``` 45 | c = aes256ctr_encrypt( 46 | plaintext = m, 47 | nonce = n2 48 | key = Ek 49 | ); 50 | ``` 51 | 6. Pack `h`, `n`, `c`, `f`, and `i` together using 52 | [PAE](Common.md#authentication-padding) 53 | (pre-authentication encoding). We'll call this `preAuth`. 54 | 7. Calculate HMAC-SHA384 of the output of `preAuth`, using `Ak` as the 55 | authentication key. We'll call this `t`. 56 | 8. If `f` is: 57 | * Empty: return "`h` || base64url(`n` || `c` || `t`)" 58 | * Non-empty: return "`h` || base64url(`n` || `c` || `t`) || `.` || base64url(`f`)" 59 | * ...where || means "concatenate" 60 | * Note: `base64url()` means Base64url from RFC 4648 without `=` padding. 61 | 62 | ## Decrypt 63 | 64 | Given a message `m`, key `k`, and optional footer `f` 65 | (which defaults to empty string), and an optional 66 | implicit assertion `i` (which defaults to empty string): 67 | 68 | 1. Before decrypting, first assert that the key being used is intended for use 69 | with `v3.local` tokens, and has a length of 256 bits (32 bytes). 70 | See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) 71 | for more information. 72 | 2. If `f` is not empty, implementations **MAY** verify that the value appended 73 | to the token matches some expected string `f`, provided they do so using a 74 | constant-time string compare function. 75 | * If `f` is allowed to be a JSON-encoded blob, implementations **SHOULD** allow 76 | users to provide guardrails against invalid JSON tokens. 77 | See [this document](../02-Implementation-Guide/01-Payload-Processing.md#optional-footer) 78 | for specific guidance and example code. 79 | 3. Verify that the message begins with `v3.local.`, otherwise throw an 80 | exception. This constant will be referred to as `h`. 81 | * **Note**: This includes the trailing period. 82 | * **Future-proofing**: If a future PASETO variant allows for encodings other 83 | than JSON (e.g., CBOR), future implementations **MAY** also permit those 84 | values at this step (e.g. `v3c.local.`). 85 | 4. Decode the payload (`m` sans `h`, `f`, and the optional trailing period 86 | between `m` and `f`) from base64url to raw binary. Set: 87 | * `n` to the leftmost 32 bytes 88 | * `t` to the rightmost 48 bytes 89 | * `c` to the middle remainder of the payload, excluding `n` and `t` 90 | 5. Split the key (`k`) into an Encryption key (`Ek`) and an Authentication key 91 | (`Ak`), `n` appended to the HKDF info. 92 | * For encryption keys, the **info** parameter for HKDF **MUST** be set to 93 | **paseto-encryption-key**. 94 | * For authentication keys, the **info** parameter for HKDF **MUST** be set to 95 | **paseto-auth-key-for-aead**. 96 | * The output length **MUST** be 48 for both key derivations. 97 | The leftmost 32 bytes of the first key derivation will produce `Ek`, while 98 | the remaining 16 bytes will be the AES nonce `n2`. 99 | 100 | ``` 101 | tmp = hkdf_sha384( 102 | len = 48, 103 | ikm = k, 104 | info = "paseto-encryption-key" || n, 105 | salt = NULL 106 | ); 107 | Ek = tmp[0:32] 108 | n2 = tmp[32:] 109 | Ak = hkdf_sha384( 110 | len = 48, 111 | ikm = k, 112 | info = "paseto-auth-key-for-aead" || n, 113 | salt = NULL 114 | ); 115 | ``` 116 | 6. Pack `h`, `n`, `c`, `f`, and `i` together (in that order) using 117 | [PAE](Common.md#authentication-padding). 118 | We'll call this `preAuth`. 119 | 7. Recalculate HMAC-SHA-384 of `preAuth` using `Ak` as the key. We'll call this `t2`. 120 | 8. Compare `t` with `t2` using a constant-time string compare function. If they 121 | are not identical, throw an exception. 122 | * You **MUST** use a constant-time string compare function to be compliant. 123 | If you do not have one available to you in your programming language/framework, 124 | you MUST use [Double HMAC](https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy). 125 | * Common utilities that were not intended for cryptographic comparisons, such as 126 | Java's `Array.equals()` or PHP's `==` operator, are explicitly forbidden. 127 | 9. Decrypt `c` using `AES-256-CTR`, using `Ek` as the key and `n2` as the nonce, 128 | then return the plaintext. 129 | ``` 130 | return aes256ctr_decrypt( 131 | cipherext = c, 132 | nonce = n2 133 | key = Ek 134 | ); 135 | ``` 136 | 137 | ## Sign 138 | 139 | Given a message `m`, 384-bit ECDSA secret key `sk`, an optional footer `f` 140 | (which defaults to empty string), and an optional implicit assertion `i` 141 | (which defaults to empty string): 142 | 143 | 1. Before signing, first assert that the key being used is intended for use 144 | with `v3.public` tokens, and is the secret key of the intended keypair. 145 | See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) 146 | for more information. 147 | 2. Set `h` to `v3.public.` 148 | **Note**: This includes the trailing period. 149 | 3. Pack `pk`, `h`, `m`, `f`, and `i` together using 150 | [PAE](Common.md#authentication-padding) 151 | (pre-authentication encoding). We'll call this `m2`. 152 | * Note: `pk` is the public key corresponding to `sk` (which **MUST** use 153 | [point compression](https://www.secg.org/sec1-v2.pdf)). `pk` **MUST** be 49 154 | bytes long, and the first byte **MUST** be `0x02` or `0x03` (depending on 155 | [the least significant bit of Y](https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.202.2977&rep=rep1&type=pdf); 156 | section 4.3.6, step 2.2). 157 | The remaining bytes **MUST** be the X coordinate, using big-endian byte order. 158 | 4. Sign `m2` using ECDSA over P-384 and SHA-384 with the private key `sk`. 159 | We'll call this `sig`. The output of `sig` MUST be in the format `r || s` 160 | (where `||`means concatenate), for a total length of 96 bytes. 161 | * Signatures **SHOULD** use deterministic nonces ([RFC 6979](https://tools.ietf.org/html/rfc6979)) 162 | if possible, to mitigate the risk of [k-value reuse](https://blog.trailofbits.com/2020/06/11/ecdsa-handle-with-care/). 163 | * If RFC 6979 is not available in your programming language, ECDSA **MUST** use a CSPRNG 164 | to generate the k-value. 165 | * Hedged signatures (RFC 6979 + additional randomness to provide resilience to fault attacks) 166 | are allowed. 167 | ``` 168 | sig = crypto_sign_ecdsa_p384( 169 | message = m2, 170 | private_key = sk 171 | ); 172 | ``` 173 | 5. If `f` is: 174 | * Empty: return "`h` || base64url(`m` || `sig`)" 175 | * Non-empty: return "`h` || base64url(`m` || `sig`) || `.` || base64url(`f`)" 176 | * ...where || means "concatenate" 177 | * Note: `base64url()` means Base64url from RFC 4648 without `=` padding. 178 | 179 | ### ECDSA Public Key Point Compression 180 | 181 | Given a public key consisting of two coordinates (X, Y): 182 | 183 | 1. Set the header to `0x02`. 184 | 2. Take the least significant bit of `Y` and add it to the header. 185 | 3. Append the X coordinate (in big-endian byte order) to the header. 186 | 187 | In pseudocode: 188 | 189 | ``` 190 | lsb(y): 191 | return y[y.length - 1] & 1 192 | 193 | pubKeyCompress(x, y): 194 | header = [0x02 + lsb(y)] 195 | return header.concat(x) 196 | ``` 197 | 198 | ## Verify 199 | 200 | Given a signed message `sm`, ECDSA public key `pk` (which **MUST** use 201 | [point compression](https://www.secg.org/sec1-v2.pdf) (Section 2.3.3)), 202 | and optional footer `f` (which defaults to empty string), and an optional 203 | implicit assertion `i` (which defaults to empty string): 204 | 205 | 1. Before verifying, first assert that the key being used is intended for use 206 | with `v3.public` tokens, and is the public key of the intended keypair. 207 | See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) 208 | for more information. 209 | 2. If `f` is not empty, implementations **MAY** verify that the value appended 210 | to the token matches some expected string `f`, provided they do so using a 211 | constant-time string compare function. 212 | 3. Verify that the message begins with `v3.public.`, otherwise throw an 213 | exception. This constant will be referred to as `h`. 214 | **Note**: This includes the trailing period. 215 | 4. Decode the payload (`sm` sans `h`, `f`, and the optional trailing period 216 | between `m` and `f`) from base64url to raw binary. Set: 217 | * `s` to the rightmost 96 bytes 218 | * `m` to the leftmost remainder of the payload, excluding `s` 219 | 5. Pack `pk`, `h`, `m`, `f`, and `i` together (in that order) using PAE (see 220 | [PAE](Common.md#authentication-padding). 221 | We'll call this `m2`. 222 | * `pk` **MUST** be 49 bytes long, and the first byte **MUST** be `0x02` or `0x03` 223 | (depending on the sign of the Y coordinate). The remaining bytes **MUST** be 224 | the X coordinate, using big-endian byte order. 225 | 6. Use ECDSA to verify that the signature is valid for the message: 226 | ``` 227 | valid = crypto_sign_ecdsa_p384_verify( 228 | signature = s, 229 | message = m2, 230 | public_key = pk 231 | ); 232 | ``` 233 | 7. If the signature is valid, return `m`. Otherwise, throw an exception. 234 | -------------------------------------------------------------------------------- /docs/Rationale-V3-V4.md: -------------------------------------------------------------------------------- 1 | # Rationale for V3/V4 2 | 3 | This document aims to capture the rationale for specifying new modes 4 | (v3 to succeed v1, v4 to succeed v2) for PASETO. 5 | 6 | ## Primary Motivations for New Versions 7 | 8 | ### v4.local 9 | 10 | v2.local was originally specified to use XChaCha20-Poly1305, a boring 11 | AEAD mode that's obviously secure. However, we've since learned about 12 | key- and message-commitment, which is an important security property 13 | in systems with multiple possible symmetric keys. 14 | 15 | Since PASETO added footers to support key-ids and key rotation 16 | strategies, this means we MUST take attacks that depend on 17 | [random-key robustness](https://eprint.iacr.org/2020/1491) seriously. 18 | 19 | PASETO v4.local uses XChaCha20 to encrypt the message, but then uses 20 | a keyed BLAKE2b hash (which acts as HMAC) for the authentication tag. 21 | 22 | ### v3.public 23 | 24 | We specified RSA for PASETO v1.public tokens, under the assumption that 25 | applications that must ONLY support NIST algorithms (e.g. because they 26 | MUST only use FIPS 140-2 validated modules to maintain compliance) would 27 | be adequately served by RSA signatures. This assumption turned out to be 28 | incorrect, and elliptic curve cryptography is now preferred. 29 | 30 | To better meet the needs of applications that are NIST-dependent, PASETO 31 | v3.public tokens will support ECDSA over NIST's P-384 curve, with SHA-384, 32 | and (preferably) using RFC 6979 deterministic signatures. (RFC 6979 is a 33 | **SHOULD**, not a **MUST**, due to library availability issues and 34 | [fault attacks](https://eprint.iacr.org/2017/1014).) 35 | 36 | #### ECDSA Security 37 | 38 | ECDSA is much more dangerous to implement than Ed25519: 39 | 40 | 1. You have to ensure the one-time secret `k` is never reused for different 41 | messages, or you leak your secret key. 42 | 2. If you're not generating `k` deterministically, you have to take extra 43 | care to ensure your random number generator isn't biased. If you fail 44 | to ensure this, attackers can determine your secret key through 45 | [lattice attacks](https://eprint.iacr.org/2019/023). 46 | 3. The computing `k^-1 (mod p)` must be constant-time to avoid leaking `k`. 47 | * Most bignum libraries **DO NOT** provide a constant-time modular 48 | inverse function, but cryptography libraries often do. This is something 49 | a security auditor will need to verify for each implementation. 50 | 51 | There are additional worries with ECDSA [with different curves](https://safecurves.cr.yp.to/), 52 | but we side-step most of these problems by hard-coding *one* NIST curve and 53 | refusing to support any others. The outstanding problems are: 54 | 55 | * The NIST curve P-384 [is not rigid](https://safecurves.cr.yp.to/rigid.html). 56 | * If you're concerned about NSA backdoors, don't use v3 (which only uses 57 | NIST-approved algorithms). Use v4 instead. 58 | * Weierstrass curves (such as P-384) historically did not use a 59 | [constant-time ladder](https://safecurves.cr.yp.to/ladder.html) or offer 60 | [complete addition formulas](https://safecurves.cr.yp.to/complete.html). 61 | * This is more of a problem for ECDH than ECDSA. 62 | * [Complete addition formulas for P-384 exist](https://eprint.iacr.org/2015/1060). 63 | 64 | There are additional protocol-level security concerns for ECDSA, namely: 65 | 66 | * Invalid Curve Attacks, which are known to break ECDH. 67 | * This is solved in PASETO through requiring support for 68 | [Point Compression](https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.202.2977&rep=rep1&type=pdf). 69 | * Implementations **MAY** also optionally support PEM encoding of 70 | uncompressed public key points, but if they do, they **MUST** validate 71 | that the public key is a point on the curve. 72 | * Point compression used to be patented, but it expired. It's high time 73 | we stopped avoiding its usage as an industry. 74 | * Exclusive Ownership. [See below.](#v3-signatures-prove-exclusive-ownership-enhancement) 75 | 76 | Because of these concerns, we previously forbid any implementation of ECDSA 77 | *without* RFC 6979 deterministic k-values in a future version. 78 | 79 | However, given the real-world requirements of applications and systems that 80 | must comply with NIST guidance on cryptography algorithms, we've relaxed this 81 | requirement. 82 | 83 | Additionally, deterministic k-values make signers more susceptible to fault 84 | attacks than randomized signatures. If you're implementing PASETO signing in 85 | embedded devices, or environments where fault injection may be a practical 86 | risk, there are two things you can do: 87 | 88 | 1. Don't use deterministic signatures because of your specific threat model. 89 | 2. Hedged signatures: Inject additional randomness into the RFC 6979 step. 90 | This randomness doesn't need to be signed. 91 | 92 | #### Questions For Security Auditors 93 | 94 | Due to the risks inherent to ECDSA, security assessors should take care to 95 | cover the following questions in any review of a PASETO implementation that 96 | supports `v3.public` tokens (in addition to their own investigations). 97 | 98 | 1. Is RFC 6979 supported and used by the implementation? 99 | 1. If not, is a cryptographically secure random number generator used? 100 | 2. If the answer to both questions is "No", fail. 101 | 2. Is modular inversion (`k^-1 (mod p)`) constant-time? 102 | 1. If not, fail. 103 | 3. Are public keys expressed as compressed points? 104 | 1. If not, is the public key explicitly validated to be on the correct 105 | curve (P-384)? 106 | 2. If the answer to both questions is "No", fail. 107 | 4. Does the underlying cryptography library use complete addition formulas 108 | for NIST P-384? 109 | 1. If not, investigate how the library ensures that scalar multiplication 110 | is constant-time. (This affects the security of key generation.) 111 | 112 | Affirmative answers to these questions should provide assurance that the 113 | ECDSA implementation is safe to use with P-384, and security auditors can 114 | focus their attention on other topics of interest. 115 | 116 | ### v3.local / v4.public 117 | 118 | No specific changes were needed from (v1.local, v2.public) respectively. 119 | See below for some broader changes. 120 | 121 | ## Beneficial Changes to V3/V4 122 | 123 | ### No More Nonce-Hashing (Change) 124 | 125 | The initial motivation for hashing the random nonce with the message was 126 | to create an SIV-like construction to mitigate the consequences of weak 127 | random number generators, such as OpenSSL's (which isn't 128 | [fork-safe](https://github.com/ramsey/uuid/issues/80)). 129 | 130 | However, this creates an unfortunate failure mode: If your RNG fails, 131 | the resultant nonce is a hash of your message, which can be used to 132 | perform offline attacks on the plaintext. This was first discovered by 133 | [Thái Dương](https://twitter.com/XorNinja/status/1157882553610563585). 134 | 135 | To avoid this failure mode, neither v3.local nor v4.local will pre-hash 136 | the message and random value to derive a nonce. Instead, it will trust 137 | the CSPRNG to be secure. 138 | 139 | ### Implicit Assertions (Feature) 140 | 141 | PASETO v3 and v4 tokens will support optional additional authenticated 142 | data that **IS NOT** stored in the token, but **IS USED** to calculate the 143 | authentication tag (local) or signature (public). 144 | 145 | These are called **implicit assertions**. These can be any application-specific 146 | data that must be provided when validating tokens, but isn't appropriate to 147 | store in the token itself (e.g. sensitive internal values). 148 | 149 | One example where implicit assertions might be desirable is ensuring that a PASETO 150 | is only used by a specific user in a multi-tenant system. Simply providing the 151 | user's account ID when minting and consuming PASETOs will bind the token to the 152 | desired context. 153 | 154 | ### Better Use of HKDF Salts (Change) 155 | 156 | With v1.local, half of the 32-byte random value was used as an HKDF salt and 157 | half was used as an AES-CTR nonce. This is tricky to analyze and didn't extend 158 | well for the v4.local proposal. 159 | 160 | For the sake of consistency and easy-to-analyze security designs, in both v3.local 161 | and v4.local, we now use the entire 32-byte random value in the HKDF step. 162 | 163 | Instead of being used as a salt, however, it will be appended to the info tag. 164 | This subtle change allows us to use the 165 | [standard security definition for HKDF](https://eprint.iacr.org/2010/264) 166 | in arguments for PASETO's security, rather than treating it as just a 167 | pseudo-random function (PRF). This security definition requires only one salt 168 | to be used, but for many contexts (info tags). 169 | 170 | The nonce used by AES-256-CTR and XChaCha20 will be derived from the HKDF output 171 | (which is now 48 bytes for v3.local and 56 bytes for v4.local). The first 32 172 | bytes of each HKDF output will be used as the key. The remaining bytes will be 173 | used as the nonce for the underlying cipher. 174 | 175 | Local PASETOs in v3 and v4 will always have a predictable storage size, and the 176 | security of these constructions is more obvious: 177 | 178 | * The probability space for either mode is 256-bits of randomness + 256-bits of 179 | key, for a total of 512 bits. 180 | * The HKDF output in v3.local is 384 bits. 181 | * The HKDF output in v4.local is 448 bits. 182 | * Neither of these output sizes reduces the security against collisions. 183 | (If they were larger than the input domain of 512 bits, that would be a 184 | blunder.) 185 | * A single key can be used for 2^112 PASETOs before rotation is necessary. 186 | * The birthday bound for a 256-bit salt is 2^128 (for a 50% chance of 187 | a single collision occurring). Setting the safety threshold to 2^-32 188 | (which is roughly a 1 in 4 billion chance) for a space of 2^256 189 | yields 2^112. 190 | * The actual nonce passed to AES-CTR and XChaCha is not revealed publicly. 191 | 192 | ### V3 Signatures Prove Exclusive Ownership (Enhancement) 193 | 194 | RSA and ECDSA signatures [do not prove Exclusive Ownership](http://www.bolet.org/~pornin/2005-acns-pornin+stern.pdf). 195 | This is almost never a problem for most protocols, unless you *expect* this property 196 | to hold when it doesn't. 197 | 198 | Section 3.3 of the paper linked above describes how to achieve Universal Exclusive 199 | Ownership (UEO) without increasing the signature size: Always include the public 200 | key in the message that's being signed. 201 | 202 | Consequently, `v3.public` PASETOs will include the raw bytes of the public 203 | key in the PAE step for calculating signatures. The public key is always a 204 | compressed point (`0x02` or `0x03`, followed by the X coordinate, for a total 205 | of 49 bytes). 206 | 207 | We decided to use point compression in the construction of the tokens as a 208 | forcing function so that all PASETO implementations support compressed points 209 | (and don't just phone it in with PEM-encoded uncompressed points). 210 | 211 | [Ed25519, by design, does not suffer from this](https://eprint.iacr.org/2020/823), 212 | since Ed25519 already includes public key with the hash function when signing 213 | messages. Therefore, we can safely omit this extra step in `v4.public` tokens. 214 | 215 | ## Miscellaneous Changes 216 | 217 | ### Define Mechanism for Extending PASETO for non-JSON Encodings 218 | 219 | PASETO serializes its payload as a JSON string. Future documents **MAY** specify using 220 | PASETO with non-JSON encoding. When this happens, a suffix will be appended to the version tag 221 | when a non-JSON encoding rule is used. 222 | 223 | > For example, a future PASETO-CBOR proposal might define its versions as `v1c`, `v2c`, `v3c`, 224 | > and `v4c`. The underlying cryptography will be the same as `v1`, `v2`, `v3`, and `v4` 225 | > respectively. Keys **SHOULD** be portable across different underlying encodings, but tokens 226 | > **MUST NOT** be transmutable between encodings without access to the symmetric key (`local` tokens) 227 | > or secret key (`public` tokens). 228 | 229 | ## Questions and Answers 230 | 231 | ### Why Not AES-GCM in `v3.local`? 232 | 233 | While it's true that AES-GCM is more broadly supported in environments that use 234 | NIST and FIPS-approved cryptography, GMAC is neither 235 | [message-committing nor key-committing](https://eprint.iacr.org/2019/016). 236 | 237 | The techniques for turning an AEAD scheme into an AEAD scheme [is well known](https://eprint.iacr.org/2020/1153), 238 | but it requires publishing an additional SHA2 hash (or KDF output) of the 239 | key being used. 240 | 241 | Using GCM would require us to also publish an additional hash *anyway*. At 242 | that point, it doesn't offer any clear advantage over CTR+HMAC. 243 | 244 | CTR+HMAC (with separate keys and PAE) is a secure construction and provides 245 | the cryptographic properties we need to use PASETO in threat models where 246 | multiple keys are used or [partitioning oracles](https://eprint.iacr.org/2020/1491) 247 | are possible. 248 | 249 | ### Why P-384 in `v3.public` instead of P-256 or P-521? 250 | 251 | Security experts that work heavily with NIST algorithms expressed a 252 | slight preference for P-384 over P-521 and P-256 when we asked. This is also 253 | congruent for our choice of SHA-384 as a hash function over SHA-256 or SHA-512. 254 | 255 | The [security considerations](#ecdsa-security) for the NIST curves are mostly 256 | congruent (albeit the ECDLP security and performance differs a bit). 257 | 258 | If you want smaller tokens or better performance than P-384, make sure Ed25519 259 | lands in FIPS 186-5 and use `v4.public` instead. 260 | --------------------------------------------------------------------------------