Authenticated and encrypted API tokens using modern crypto

Branca Token

Authenticated and encrypted API tokens using modern crypto.

What?

Branca is a secure, easy to use token format which makes it hard to shoot yourself in the foot. It uses IETF XChaCha20-Poly1305 AEAD symmetric encryption to create encrypted and tamperproof tokens. The payload itself is an arbitrary sequence of bytes. You can use for example a JSON object, plain text string or even binary data serialized by MessagePack or Protocol Buffers.

Although not a goal, it is possible to use Branca as an alternative to JWT. Also see getting started instructions.

This specification defines the external format and encryption scheme of the token to help developers create their own implementations. Branca is closely based on the Fernet specification.

Design Goals

  1. Secure
  2. Easy to implement
  3. Small token size

Token Format

A Branca token consists of a header, ciphertext and an authentication tag. The header consists of a version, timestamp and nonce. Putting them all together we get following structure:

Version (1B) || Timestamp (4B) || Nonce (24B) || Ciphertext (*B) || Tag (16B)

String representation of the above binary token must use base62 encoding with the following character set.

0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz

Version

Version is 8 bits ie. one byte. Currently, the only version is 0xBA. This is a magic byte which you can use to quickly identify a given token. Version number guarantees the token format and encryption algorithm.

Timestamp

Timestamp is 32 bits ie. unsigned big-endian 4 byte UNIX timestamp. By having a timestamp instead of expiration time enables the consuming side to decide how long tokens are valid. You cannot accidentally create tokens which are valid for the next 10 years.

Storing the timestamp as an unsigned integer allows us to avoid the 2038 problem. Unsigned integer overflow will happen in the year 2106. Possible values are 0 - 4294967295.

Key

This is a symmetric key of 32 arbitrary bytes. The key can be randomly generated using a CSPRNG or created during a key-exchange for example.

It is the responsibility of the user of Branca, to ensure the strength and confidentiality of the secret key used. See libsodium for more information on the difference between a password and a secret key.

Nonce

Nonce is 192 bits ie. 24 bytes. It should be cryptographically secure random bytes. A nonce should never be used more than once with the same secret key between different payloads. It should be generated automatically by the implementing library. Allowing end users to provide their own nonce is a foot gun.

Payload

Payload is an arbitrary sequence of bytes of any length.

Ciphertext

Payload is encrypted and authenticated using IETF XChaCha20-Poly1305. Note that this is Authenticated Encryption with Additional Data (AEAD) where the header part of the token is the additional data. This means the data in the header (version, timestamp and nonce) is not encrypted, it is only authenticated. In laymans terms, header can be seen but it cannot be tampered with.

Tag

The authentication tag is 128 bits ie. 16 bytes. This is the Poly1305 message authentication code. It is used to make sure that the payload, as well as the non-encrypted header have not been tampered with.

Working With Tokens

Instructions below assume your crypto library supports combined mode. In combined mode, the authentication tag and the encrypted message are stored together. If your crypto library does not provide combined mode, then the tag is last 16 bytes of the ciphertext|tag combination.

Generating a Token

Given a 256 bit ie. 32 byte secret key and an arbitrary payload, generate a token with the following steps in order:

  1. Generate a 24 byte cryptographically secure random nonce.
  2. If no timestamp is provided, use the current unixtime.
  3. Construct the header by concatenating version, timestamp and nonce.
  4. Encrypt the payload with IETF XChaCha20-Poly1305 AEAD with the key. Use header as the additional data for the AEAD.
  5. Concatenate the header and the returned ciphertext|tag combination from step 4.
  6. Base62 encode the entire token.

Verifying a Token

Given a 256 bit ie. 32 byte secret key and a token to verify that the token is valid and recover the original unencrypted payload, perform the following steps, in order.

  1. Base62 decode the token.
  2. Make sure the first byte of the decoded token is 0xBA.
  3. Extract the header ie. the first 29 bytes from the decoded token.
  4. Extract the nonce ie. the last 24 bytes from the header.
  5. Extract the timestamp ie. bytes 2 to 5 from the header.
  6. Extract ciphertext|tag combination ie. everything starting from byte 30.
  7. Decrypt and verify the ciphertext|tag combination with IETF XChaCha20-Poly1305 AEAD using the secret key and nonce. Use header as the additional data for AEAD.

Working With the Timestamp

Optionally the implementing library may use the timestamp for additional verification. For example the library could provide a non-negative ttl parameter which is used to check if token is expired by adding the ttl to the timestamp and comparing the result with the current unixtime. Any optional timestamp check must happen as the last step after decrypting and verifying the token. Further, the implementing library must ensure adding ttl to timestamp does not overflow 4294967295.

Libraries

Currently known implementations in the wild.

Language Repository License Crypto library used
Clojure miikka/clj-branca EPL-2.0 terl/lazysodium-java
.NET AmanAgnihotri/Branca LGPL-3.0-or-later NaCl.Core
.NET scottbrady91/IdentityModel Apache-2.0 Bouncy Castle
Elixir tuupola/branca-elixir MIT ArteMisc/libsalty
Erlang 1ma/branca-erl MIT jedisct1/libsodium
Go essentialkaos/branca MIT golang/crypto
Go hako/branca MIT golang/crypto
Go juranki/branca MIT golang/crypto
Java Kowalski-IO/branca MIT Bouncy Castle
Java bjoernw/jbranca Apache-2.0 Bouncy Castle
JavaScript tuupola/branca-js MIT jedisct1/libsodium.js
Kotlin petersamokhin/kbranca Apache-2.0 Bouncy Castle
PHP tuupola/branca-php MIT jedisct1/libsodium
Python tuupola/branca-python MIT jedisct1/libsodium
Ruby thadeu/branca-ruby MIT RubyCrypto/rbnacl
Ruby crossoverhealth/branca MIT RubyCrypto/rbnacl
Rust return/branca MIT brycx/orion

Acceptance Test Vectors

All test vectors can be found here.

Similar Projects

  • PASETO ie. Platform-Agnostic Security Tokens.
  • Fernet which provides AES 128 in CBC mode tokens.

License

The MIT License (MIT). Please see License File for more information.

Comments
  • Add reference test vectors

    Add reference test vectors

    #18

    These test vectors combine what I've found in the PHP implementation, with some extra tests. As mentioned, these include the nonces, but can be removed depending on your conclusion @tuupola.

    The extra tests add more edge-cases, related to empty input, non-UTF8, etc.

    TODO:

    • [X] Figure out if nonces should be included
    • [X] Cross-verify against other implementations (any specific ones?) (so far, Rust and PHP)
    • [X] Prettify nonces to be strings instead of hexadecimal ("beefbeefbeefbeefbeefbeef")
    • [X] Remove TC12 and TC7
    • [X] Separate encoding-only and decoding-only tests (separate groups or separate files)

    Test 18, when run with pybranca, fails. This is because it allows the timestamp to overflow the spec 0..4294967295 range, when checking for expiration with a TTL. I assume this is not intended and I would like to restrict the TTL not to overflow the timestamp in the spec, @tuupola? I'd be happy to add it to the spec myself.

    from branca import Branca
    import json
    from binascii import unhexlify, hexlify
    import calendar
    from datetime import datetime
    
    
    with open('test_vectors.json', "r") as test_vectors:
        data = json.load(test_vectors)
    
    for test_vector in data:
        print("Testing:", test_vector['test_vector_id'])
        print("Comment:", test_vector['comment'])
    
        try:
            ctx = Branca(test_vector['key'])
            ctx._nonce = unhexlify(test_vector['nonce'])
            token_actual = ctx.encode(unhexlify(test_vector['msg']), timestamp=test_vector['timestamp'])
    
            try:
                message_actual = ctx.decode(test_vector['token'], ttl=test_vector['ttl'])
            except:
                if test_vector['is_valid'] is False:
                    print("Pass: TRUE")
                else:
                    message_actual = ctx.decode(test_vector['token'])
    
            # Define passing test
            if test_vector['is_valid'] is True:
                if token_actual == test_vector['token'] and message_actual == unhexlify(test_vector['msg']):
                    print("Pass: TRUE")
                else:
                    print("Pass: FALSE")
            else:
                if token_actual != test_vector['token']:
                    print("Pass: TRUE")
                elif message_actual != unhexlify(test_vector['msg']):
                    print("Pass: TRUE")
                else:
                    print("Pass: FALSE")
        except:
            if test_vector['is_valid'] is False:
                print("Pass: TRUE")
            else:
                print("Pass: FALSE")
    
  • Rust implementation

    Rust implementation

    Rust implementation would be welcome. Something similar as frank_jwt by @GildedHonour. Anyone interested in implementing it write me a note here and I will add it to the docs.

  • Go implementation

    Go implementation

    Go implementation is missing. Something similar as fernet-go by @kr. Anyone interested in implementing it write me a note here and I will add it to the docs.

  • Suggestion to optional nonce protection

    Suggestion to optional nonce protection

    $size = CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES; $random = random_bytes($size); $nonce = sodium_crypto_generichash($payload, $random, $size);

    I think it would be good to concretize this a bit to make it clearer, by specifically stating the algorithm this example uses (BLAKE2b IIRC). Also, what if sodium_crypto_generichash changes at some point? If we specifically state the algorithm tied to hashing for the nonce, we can tie that to the version and change it in the far future if need be. The last part is not too important, I'm more into specifying it to make it clear what algorithm is considered secure. In case a user might try to use something else, if libsodium is not accessible to them.

  • Erlang implementation

    Erlang implementation

    Erlang implementation is missing. Something similar as fernet-erl by @bigkevmcd. Anyone interested in implementing it write me a note here and I will add it to the docs.

  • Python implementation

    Python implementation

    Python implementation is badly needed. Something similar as pyjwt by @jpadilla. Anyone interested in implementing it write me a note here and I will add it to the docs.

  • Timestamp is not protected against tampering

    Timestamp is not protected against tampering

    If I understood the Branca specs correctly, the timestamp field seems to be unprotected.

    Let's say a website uses Branca tokens as session cookies and relies on the timestamp field in order to check if the token is still valid. An attacker might get access to a valid or expired token and manipulate the timestamp field whenever needed in order to make the expired token valid again. This applies not only to the context of websites and session cookies but to every possibly untrusted environment in which Branca tokens could be used.

    As far as I can tell, the timestamp field doesn't provide any reliable information in an untrusted environment.

    I'm writing this because not everyone using Branca tokens might be aware of that. I suggest to either make this very clear in the documentation or to change the spec so that tampering with the timestamp field can be detected.

  • Base62 is actually Base61

    Base62 is actually Base61

    The character set provided in the spec:

    0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxy
    

    Only contains 61 characters, and is thus a base61 encoding. Looking at the implementations listed on the document, it seems that most of them (if not all) include a z to the character set as I would expect.

    Is this the intended behaviour? Or should the character set not include the character z?

  • Another one Golang implementation for Branca

    Another one Golang implementation for Branca

    Hi! We have been released our high-grade quality package with Branca implementation - https://github.com/essentialkaos/branca

    Features:

    • Pure Go implementation;
    • No third-party dependencies at all;
    • 100% code coverage;
    • Fuzz tests.

    I think it will be great to add information about this package to the branca website.

  • Suggestion to expiration check

    Suggestion to expiration check

    I think it would make sense to specifically state, that the expiration check for a token using a ttl should happen after authenticating and decrypting. If it happens before, a user would never know if an expired token they received was tampered into expiring, or actually did expire.

  • Typo in Ciphertext paragraph of README

    Typo in Ciphertext paragraph of README

    Note that this is Authenticated Encryption with Additional Data (AEAD) where the he header part of the token is the additional data.

    The word β€˜he’ should be removed.

  • Add disclaimer and new section about known implementations

    Add disclaimer and new section about known implementations

    This introduces several changes as part of #37.

    • We put a disclaimer at the beginning of the list of known implementations. This way, it's clear that this list does is not to be interpreted as an official endorsement.

    • Another separate list is added in the beginning. It's purpose is, that libraries that have been tested previously to be correct, can be added here. We still don't guarantee this list is updated, but can still provide a better starting point compared to some of the libraries listed later, that may not be compliant at all.

    I'd like your take on this additional section @tuupola. The intention is not to provide an endorsement list, just one that contains libraries that have been tested successfully at least once. I think the idea with a checkmark, indicating compliance, as implemented by PASETO suggested in #37 is not the right approach. We don't expect people working on this specification to set aside time to audit a new library, each time a new author wants to add theirs. (The list in this PR are just the ones I'm familiar with)

    With PASETO, I've myself added a library to their list, where I myself indicated that it passed test vectors. I have no idea if this was verified by a third-party at any time. If not, the end result seem equal to simply a author adding their new library - the same level of confidence can be derived from that.

    The new list can be updated on an ad-hoc basis. If someone wanting to use a library verifies it and reports here that it can be moved up the "known to comply at some point" library, we can do so. This was an alternative to a "Recommended libraries" section, but if this that seems better to you @tuupola, we can make that instead.

    The last point I want to address, is that: As I've written about here, some libraries listed suffer security problems in addition to non-compliance. Do we wish to remove these entirely?

  • README and test vectors should reflect new 32 byte hex key expectations from branca-js

    README and test vectors should reflect new 32 byte hex key expectations from branca-js

    The Javascript reference implementation has recently changed to enforce the use of more secure 32 byte hex keys, or a 32 byte Buffer. This change is to require the use of cryptographic keys of the proper length and construction as expected by the underlying encryption algorithm.

    https://github.com/tuupola/branca-js

    The spec should be modified to:

    • add additional context to the spec README about why this change was made
    • note that it is likely a breaking change and thus might require version bumping the spec
    • modify the test vectors so they will work with the new key mechanism

    Other downstream implementations should also be encouraged to adopt the new spec to maintain cross library interoperability.

  • Laravel + Lumen support

    Laravel + Lumen support

    Laravel and Lumen support would be awesome. Something similar as jwt-auth by @tymondesigns. You could also check branca-middleware for inspiration. PHP library for encoding and decoding tokens already exists.

    Anyone interested in implementing it write me a note here and I will add it to the docs.

Authenticated encrypted API tokens (IETF XChaCha20-Poly1305 AEAD) for Golang

branca.go is branca token specification implementation for Golang 1.15+.

Dec 26, 2022
Package gorilla/securecookie encodes and decodes authenticated and optionally encrypted cookie values for Go web applications.

securecookie securecookie encodes and decodes authenticated and optionally encrypted cookie values. Secure cookies can't be forged, because their valu

Dec 26, 2022
Gets Firebase auth tokens (for development purposes only)Gets Firebase auth tokens

Firebase Token Gets Firebase auth tokens (for development purposes only) Getting started Create Firebase project Setup Firebase authentication Setup G

Nov 17, 2021
OauthMicroservice-cassandraCluster - Implement microservice of oauth using golang and cassandra to store user tokens

implement microservice of oauth using golang and cassandra to store user tokens

Jan 24, 2022
Utility to generate tokens to interact with GitHub API via GitHub App integration

GitHub App Authentication for integration with GitHub Introduction GitHub Apps are the officially recommended way to integrate with GitHub because of

Mar 16, 2022
Simple tool to download files or web-pages with proxy-support and hardened crypto-algorithms

VBDownloader (with proxy-support behind firewall) Simple tool to download files or web-pages with proxy-support and hardened crypto-algorithms. This t

Dec 28, 2021
A simple and lightweight library for creating, formatting, manipulating, signing, and validating JSON Web Tokens in Go.

GoJWT - JSON Web Tokens in Go GoJWT is a simple and lightweight library for creating, formatting, manipulating, signing and validating Json Web Tokens

Nov 15, 2022
Safe, simple and fast JSON Web Tokens for Go

jwt JSON Web Token for Go RFC 7519, also see jwt.io for more. The latest version is v3. Rationale There are many JWT libraries, but many of them are h

Jan 4, 2023
Oct 8, 2022
Microservice generates pair of access and refresh JSON web tokens signed by user identifier.

go-jwt-issuer Microservice generates pair access and refresh JSON web tokens signed by user identifier. ?? Deployed on Heroku Run tests: export SECRET

Nov 21, 2022
Generate and verify JWT tokens with Trusted Platform Module (TPM)

golang-jwt for Trusted Platform Module (TPM) This is just an extension for go-jwt i wrote over thanksgiving that allows creating and verifying JWT tok

Oct 7, 2022
Go module with token package to request Azure Resource Manager and Azure Graph tokens.

azAUTH Go module with token package to request Azure Resource Manager and Azure Graph tokens. prerequisites Install azure cli: https://docs.microsoft.

Dec 1, 2021
Generate and verify JWT tokens with PKCS-11

golang-jwt for PKCS11 Another extension for go-jwt that allows creating and verifying JWT tokens where the private key is embedded inside Hardware lik

Dec 5, 2022
Go-Guardian is a golang library that provides a simple, clean, and idiomatic way to create powerful modern API and web authentication.

❗ Cache package has been moved to libcache repository Go-Guardian Go-Guardian is a golang library that provides a simple, clean, and idiomatic way to

Dec 23, 2022
Aegis To KeePass - A simple tool to convert exported (and encrypted) Aegis database to standalone KeePass

ATP - Aegis to KeePass A simple tool to convert exported (and encrypted) Aegis d

Aug 28, 2022
Golang implementation of JSON Web Tokens (JWT)

jwt-go A go (or 'golang' for search engine friendliness) implementation of JSON Web Tokens NEW VERSION COMING: There have been a lot of improvements s

Jan 6, 2023
Platform-Agnostic Security Tokens implementation in GO (Golang)

Golang implementation of PASETO: Platform-Agnostic Security Tokens This is a 100% compatible pure Go (Golang) implementation of PASETO tokens. PASETO

Jan 2, 2023
A go implementation of JSON Web Tokens

jwt-go A go (or 'golang' for search engine friendliness) implementation of JSON Web Tokens NEW VERSION COMING: There have been a lot of improvements s

Jan 7, 2023
an stateless OpenID Connect authorization server that mints ID Tokens from Webauthn challenges

Webauthn-oidc Webauthn-oidc is a very minimal OIDC authorization server that only supports webauthn for authentication. This can be used to bootstrap

Nov 6, 2022