Fast, secure and efficient secure cookie encoder/decoder

GoDoc Build Coverage Go Report Status release

Encode and Decode secure cookies

This package provides functions to encode and decode secure cookie values.

A secure cookie has its value ciphered and signed with a message authentication code. This prevents the remote cookie owner from knowing what information is stored in the cookie or modifying it. It also prevents an attacker from forging a fake cookie.

This package differs from the Gorilla secure cookie in that its encoding and decoding is 3 times faster and needs no heap allocation with an equivalent security strength. Both use AES128 and SHA256 to secure the value.

Note: This package uses its own secure cookie value encoding. It is thus incompatible with the Gorilla secure cookie package and the ones provided with other language frameworks. This encoding is simpler and more efficient, and adds a version number to support evolution with backwards compatibility.

Warning: Because this package impacts security of web applications, it is a critical functionality. Review feedbacks are always welcome.

Content

Installation

To install or update this secure cookie package use the instruction:

go get -u "github.com/chmike/securecookie"

Usage examples

To use this cookie package in your server, add the following import.

import "github.com/chmike/securecookie"

Generating a random key

It is strongly recommended to generate the random key with the following function. Save the key in a file using hex.EncodeToString() and restrict access to that file.

var key []byte = securecookie.MustGenerateRandomKey()

To mitigate the risk of an attacker getting the saved key, you might store a second key in another place and use the xor of both keys as secure cookie key. The attacker will have to get both keys to reconstruct the effective key which should be more difficult.

Instantiating a cookie object

A secure cookie is instantiated with the New function. It returns an error if an argument is invalid.

obj, err := securecookie.New("session", key, securecookie.Params{
		Path:     "/sec",           // cookie received only when URL starts with this path
		Domain:   "example.com",    // cookie received only when URL domain matches this one
		MaxAge:   3600,             // cookie becomes invalid 3600 seconds after it is set
		HTTPOnly: true,             // disallow access by remote javascript code 
		Secure:   true,             // cookie received only with HTTPS, never with HTTP
		SameSite: securecookie.Lax, // cookie received with same or sub-domain names
})
if err != nil {
    // ...
}

It is also possible to instantiate a secure cookie object without returning an error and panic if an argument is invalid. To do this, use securecookie.MustNew(). In the following example, session is the cookie name and the Path is /sec. A secured value may be stored in the remote browser by calling the SetValue() method. After that, every subsequent request from that browser with a URL starting with /sec will have the cookie sent along. Calling the method GetValue() will extract the secure value from the request. A request to delete the cookie may be sent to the remote browser by calling the method Delete().

var obj = securecookie.MustNew("Auth", key, securecookie.Params{
		Path:     "/sec",           // cookie received only when URL starts with this path
		Domain:   "example.com",    // cookie received only when URL domain matches this one
		MaxAge:   3600,             // cookie becomes invalid 3600 seconds after it is set
		HTTPOnly: true,             // disallow access by remote javascript code 
		Secure:   true,             // cookie received only with HTTPS, never with HTTP
		SameSite: securecookie.Lax, // cookie received with same or sub-domain names
}

Remember that the key should not be stored in the source code or in a repository.

Adding a secure cookie to a server response

var val = []byte("some value")
// with w as the http.ResponseWriter
if err := obj.SetValue(w, val); err != nil {
    // ...
}

Decoding a secure cookie value

The value is appended to the given buffer. If buf is nil a new buffer ([]byte) is allocated. If buf is too small it is grown.

// with r as the *http.Request
val, err := obj.GetValue(buf, r) 
if err != nil {
  // ...
}

The returned value is of type []byte.

Deleting a cookie

// with w as the http.ResponseWriter
if err := obj.Delete(w); err != nil {
  // ...
}

Note: don't rely on the assumption that the remote user agent (browser) will effectively delete the cookie. Evil users will try anything to break your site.

Complete example

An complete example is provided here.

Follow these steps to test the example:

  1. create a directory in /tmp and set it as the working directory: "mkdir /tmp/sctest; cd /tmp/sctest"
  2. create a file named main.go and copy the example code referenced above into it
  3. create a go.mod file: "go mod init example.com"
  4. get the latest version of the securecookie packaqe: "go get github.com/chmike/[email protected]"
  5. run the server: "go run main.go"
  6. with your browser, request "http://localhost:8080/set/someValue" to set the secure cookie value
  7. with your browser, request "http://localhost:8080/val" to retriev the secure cookie value

Benchmarking

Encoding the cookie named "test" with value "some value". See benchmark functions are at the bottom of cookie_test.go file. The ns/op values were obtained by running the benchmark 10 times and taking the minimal value. These values were obtained with go1.14 (27-Feb-2020) on an Ubuntu OS with an i5-7400 3GHz processor.

Chmike Gorilla
Value len 84 112
Set ns/op 2393 6309
Get ns/op 1515 5199
Set B/op 350 2994
Get B/op 192 2720
Set allocs/op 3 35
Get allocs/op 2 38

The secure cookie value encoding and decoding functions of this package need 0 heap allocations.

The benchmarks were obtained with release v0.4. Subsequent release may alter the benchmark results.

Qualitative comparison

The latest version was updated to put the security in line with the Gorilla secure cookie.

  • We both use CTR-AES-128 encryption with a 16 byte nonce, and HMAC-SHA-256.
  • We both encrypt first then compute the MAC over the cipher text.
  • A time stamp is added to the encoded value.
  • The hmac is computed over the cookie value name, the ciphered time stamp and value.
  • Both packages don't take special measures to secure the secret key.
  • Both packages don't effectively conceal the value byte length.

The differences between the Gorilla secure cookie and this implementation are:

  • This code is more efficient, and there is still room for improvement.
  • This secure value encoding is more compact without weakening the security.
  • This secure cookie encoding is incompatible with other secure cookie encoding. I don't know the status of Gorilla's encoding.
  • This encoding adds an encoding version number allowing to change or add new encoding without breaking backwards compatibility. Gorilla doesn't have this.
  • This package provides a Delete cookie method.

This package and Gorilla both provide equivalently secure cookies if we discard the fact that no special measure is taken to conceal the key in memory and the value length. This package is quite new and needs more reviews to validate the security of the implementation.

Feedback and contributions are welcome.

Value encoding

  1. A clear text message is first assembled as follow:

    [tag][nonce][stamp][value][padding]

  • The tag is 1 byte. The 6 most significant bits encode the version number of the encoding (currently 0). The 2 less significant bits encode the number of padding bytes (0, 1 or 2). 3 is an invalid padding length. The number is picked so that the total length including the MAC is a multiple of 3. This simplifies base64 encoding by avoiding its padding.
  • The nonce is 16 bytes long (AES block length) and contains cryptographically secure pseudorandom bytes.
  • The stamp is the unix time subtracted by an epochOffset value (1505230500), and encoded using the LEB128 encoding.
  • The value is a copy of the user provided value to secure.
  • The padding bytes are cryptographically secure pseudorandom bytes. There may be 0 to 2 padding bytes. The number is picked so that the total length including the MAC is a multiple of 3. This simplifies base64 encoding by avoiding its padding.
  1. The stamp, value and padding bytes are ciphered using CTR-AES with the 16 last bytes of the key as ciphering key. The nonce is used as iv and counter initialization value. The tag and nonce are left unciphered.

  2. An HMAC-SHA-256 is computed over (1) the cookie name and (2) the bytes sequence obtained after step 2. The 32 byte long MAC is appended after the padding.

  3. The whole byte sequence, from the tag to the last byte of the MAC is encoded in Base64 using the URL encoding. There is no padding since the byte length is a multiple of 3 bytes.

The tag which provides an encoding version allows completely changing the encoding while preserving backwards compatibility if required.

Contributors

  • lstokeworth (reddit):
    • suggest to replace copy with append,
    • remove the Expires Params field and use only the MaxAge,
    • provide a constant date in the past for Delete.
  • cstockton (github):
  • flowonyx (github):
    • fix many typos in the README and comments.

Usage advise

It is very important to understand that the security is limited to the cookie content. Nothing proves that the other data received by the server with a secure cookie has been created by the user's request.

When you have properly set the domain path, and HTTPOnly with the Secure flag, and use HTTPS, only the user's browser can send the cookie. But it is still possible for an attacker to trick your browser to send a request to the site without the user's knowledge and consent. This is known as a CSRF attack.

Consider this scenario with a Pizza ordering web site. First let's see what happens when everything goes as expected.

The user has to login to the site to be allowed to order pizzas. During the login transaction a secure cookie is added into the user's browser. The user is then shown a form with the number of pizzas to order. When the user clicks the Order button, his browser will make a request to an URL provided with the form. It will join the field values and the secure cookie since the URL path and domain match the one specified at the login transaction.

When the server receives this request, it checks the cookie validity to determine who that client is and if he is legitimate. All is fine. The order is then forwarded to the pizza chef. The pizza is later delivered to the user.

Now comes the villain. He sets up some random site with a form and a validation button that the victim will very likely click (e.g., "Subscribe to spam" with a Please no button as validation button). The villain has set up the form so that the URL associated to the validation button is the URL to order pizzas. He will have added a hidden field with the number of pizzas to order set to 10!

When the user clicks that validation button, his browser will send a request to the pizza ordering site with the field value and the secure cookie since the URL matches the cookie path and domain.

The pizza ordering site checks the secure cookie and it will be authenticated. It will assume that the user issued that order. When the delivery man rings at the user's door with 10 pizzas in his hand, there will be a conflict and no way to know to know who's fault it is. Notice how the value 10 associated with the cookie in the ordering request was not signed by the user's browser.

To avoid this, the solution is to add a way to authenticate the form response. This is done by adding a hidden field in the form with a random byte sequence, and set a secure cookie with that byte sequence as value and a validity date limit. When the user fills that form, the server will receive back the secure cookie and the hidden field value. The server then checks that they match to validate the response.

An attacker can forge a random byte sequence, but can't forge the secure cookie that goes with it.

Note that this protection is void in case of XSS attack (script injection).

The above method works with forms, not with REST API like requests because the server can't send the random token to the client that can be used as a challenge. For REST API like authenticated transactions, the client and server have to both know a secret byte sequence they use to compute a hmac value over the URI, the method, the data and a message sequence number. They can then authenticate the message and the source.

The secret byte sequence can be determined in the authentication transaction with public and private keys. There is no need for TLS to securely authenticate the client and server. A secret cookie is no help here.

Owner
Christophe Meessen
Software engineer at CNRS. I like solving problems and inventing things. I'm programming in Go since 2015 and love it.
Christophe Meessen
Comments
  • Cookie age

    Cookie age

    Hi ...

    Thanks for taking the time to make this small but useful library !

    The model is really nice, when I set cookies on response I like to only do so when needed, I really like to know how old a given cookie is in order to keep some kind of track on this, and make it possible to set new cookie based on age (like 10 min before expire).

    I know this is found inside the encoding as the stamp, but I can't react on it as for now.

    It would be nice with a obj.Age(r *http.Request) int or even a obj.ValueAndAge(r *http.Request) (val []byte, int) to keep performance and compatibility in mind.

    Until then, I will set cookie on each response :-)

  • strange error

    strange error

    so I have this:

    	locCookie, err = securecookie.New("session", key, securecookie.Params{
    		Path:     "/",         // cookie received only when URL starts with this path
    		Domain:   "localhost", // cookie received only when URL domain matches this one
    		MaxAge:   3600,        // cookie becomes invalid 3600 seconds after it is set
    		HTTPOnly: true,        // disallow access by remote javascript code
    	})
    
    	if err != nil {
    		println(err)
    	}
    

    and this shows me that error is 6?

    Which I looked up and is caused by:

    func New(name string, key []byte, p Params) (*Obj, error) {
    	block, err := aes.NewCipher(key[len(key)/2:])
    	if err != nil {
    		return nil, err
    	}
    

    the aes.NewCipher(key[len(key)/2:]) returns the 6 as an error. Any ideas in how to fix this?

  • Cookie life on browser reload

    Cookie life on browser reload

    I am using this lib with big success for a small site I am building, and I try to limit the number of times I use Set-Cookie in my response, and I only set this when max age are about to run out (it is part of the decoded cookie).

    This works fantastic, thanks to this small and cool library.

    But, this only works as long as the browser stays on the same page, as soon as i do a reload (or hot update) the browser (both chromium and firefox) forget the cookie, like I can't see it in the debugger anymore.

    My cookie init looks like this :

    obj, err := securecookie.New("session", sessionKey, securecookie.Params{
    		Path:     path,                // cookie received only when URL starts with this path
    		Domain:   siteName,            // cookie received only when URL domain matches this one
    		MaxAge:   3600,       // cookie becomes invalid sessionMaxAge seconds after it is set
    		HTTPOnly: true,                // disallow access by remote javascript code
    		Secure:   true,                // cookie received only with HTTPS, never with HTTP
    		SameSite: securecookie.Lax, // cookie received with same or sub-domain names
    })
    

    I have tried to toggle both HTTPOnly and Secure but to no use.

    Is this a know problem/feature ?

  • Unable to create session cookie

    Unable to create session cookie

    To me it seems due to the fact, that Obj.MaxAge cannot be ommited despite it is optional, it is impossible to create a session cookie: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#attributes

  • Cookie Domain Should Allow Hyphens

    Cookie Domain Should Allow Hyphens

    func checkDomain currently returns an "invalid character" error if any characters in the domain are not an ANSI letter, digit, or period. Hyphens should also be allowed in certain positions for domains such as www.foo-bar.com.

    The func comment references RFC 1034 Sec 3.5. According to the RFC each label - the name portion between each period - (1) must begin with a letter, (2) must end with a letter or digit if at least 2 characters long, and (3) may contain a letter, digit, or hyphen for intermediate characters when at least 3 characters long.

    I'm working on a patch right now for my own use and will submit a PR for consideration once complete. I don't see a CONTRIBUTING.md file. Are there any specific requirements besides updating the TestCheckDomain test?

  • invalid encoded cookie

    invalid encoded cookie

    Hello,

    I have a strange behavior when I'm trying to get the value of the cookie:

    invalid encoded cookie value (MAC mismatch)

    The creation of the cookie:

    return securecookie.New("refresh_token", key, securecookie.Params{
        Path:     "/api/refresh",                        // accessCookie to be sent back only when URL starts with this path
        Domain:   globalConfig.Domain,                   // accessCookie to be sent back only when URL domain matches this one
        MaxAge:   int(REFRESH_TOKEN_DURATION.Seconds()), // accessCookie becomes invalid
        HTTPOnly: true,                                  // disallow access by remote javascript code
        Secure:   globalConfig.SecureCookie,             // accessCookie received with HTTP for testing purpose
        SameSite: securecookie.Lax,                      // cookie received with same or sub-domain names
      })
    

    The key come from a docker swarm secret.

    I have this error only when I release a new version of the project. If I remove the swarm stack and re-deploy the same version it works.

    Maybe someone has an idea, I'm a bit lost.

    Thank you very much.

  • Unclear how to add a key + value

    Unclear how to add a key + value

    So I have the store. But I expected to be able to store a key + value in that store, but I only see examples on a value only.

    or Do I totally misunderstand and don't I have a store but only a single cookie when securecookie.New() called.

    I would like to save like this ("name", "Marcello") or something...

  • Fixed typos in documentation

    Fixed typos in documentation

    I saw that you did a lot of work on the documentation, so I just wanted to fix some typos I saw so they won't distract from what you are saying. There were a couple of things that were not really typos but I changed for consistency.

  • The hmac object is stored in the Cookie object

    The hmac object is stored in the Cookie object

    The hmac hash.Hash object is stored in the Cookie object. SetCookie or GetValue() are not thread safe. The hash.Hash object state changes when computing the hmac. If two go routines compute the hmac at the same time they will interfere and the hmac result will be invalid.

  • Compute the hmac over the cookie name

    Compute the hmac over the cookie name

    A secure cookie should have the same name it was set with. A cookie name change must invalidate the cookie.

    The hmac must thus include the cookie name in its computation.

  • Add a timestamp to the encoded value

    Add a timestamp to the encoded value

    A secure cookie must have a timestamp in a given range to be valid. A timestamp (unix time?) must be added to the encoded value.

    The timestamp encoding is obtained by subtracting a constant value to the unix time. This is allowed because the timestamp will never be older than now. Then use encoding/binary to get a compact binary encoding of the time stamp value.

  • Crypto Go :we are a research group to help developers build secure applications.

    Crypto Go :we are a research group to help developers build secure applications.

    Hi, we are a research group to help developers build secure applications. We designed a cryptographic misuse detector (i.e., CryptoGo) on Go language. We found your great public repository from Github, and several security issues detected by CryptoGo are shown in the following. Note that the cryptographic algorithms are categorized with two aspects: security strength and security vulnerability based on NIST Special Publication 800-57 and other public publications. Moreover, CryptoGo defined certain rules derived from the APIs of Go cryptographic library and other popular cryptographic misuse detectors. The specific security issues we found are as follows: Location: securecookie.go:166; Broken rule: Constant key in AES; We wish the above security issues could truly help you to build a secure application. If you have any concern or suggestion, please feel free to contact us, we are looking forward to your reply. Thanks.

Package gorilla/sessions provides cookie and filesystem sessions and infrastructure for custom session backends.

sessions gorilla/sessions provides cookie and filesystem sessions and infrastructure for custom session backends. The key features are: Simple API: us

Dec 28, 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
Advent of Code Input Loader, provide a session cookie and a problem date, returns a string or []byte of the input

Advent of Code Get (aocget) A small lib to download your puzzle input for a given day. Uses your session token to authenticate to obtain your personal

Dec 9, 2021
Goauth - Basic username password cookie based authentication with Go Lang

goauth [WIP] Basic username password cookie based authentication with Go Lang Overview Use a Postgres DB to store Sign-in and Sign-up info Redis for c

Jan 4, 2022
Cocos2d-x texture unpacker, primarily for Cookie Run.

boofunpack Cocos2d-x texture unpacker, primarily for Cookie Run: OvenBreak and Cookie Run for Kakao/LINE (though it likely works for other .plist form

Oct 11, 2022
Go-gin-jwt - Secure web api using jwt token and caching mechanism

Project Description This project demonstrate how to create api and secure it wit

Jan 27, 2022
:key: Secure alternative to JWT. Authenticated Encrypted API Tokens for Go.

branca branca is a secure alternative to JWT, This implementation is written in pure Go (no cgo dependencies) and implements the branca token specific

Dec 29, 2022
Jan 9, 2023
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
A fast and simple JWT implementation for Go
A fast and simple JWT implementation for Go

JWT Fast and simple JWT implementation written in Go. This package was designed with security, performance and simplicity in mind, it protects your to

Jan 5, 2023
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
simple-jwt-provider - Simple and lightweight provider which exhibits JWTs, supports login, password-reset (via mail) and user management.

Simple and lightweight JWT-Provider written in go (golang). It exhibits JWT for the in postgres persisted user, which can be managed via api. Also, a password-reset flow via mail verification is available. User specific custom-claims also available for jwt-generation and mail rendering.

Dec 18, 2022
Certificate authority and access plane for SSH, Kubernetes, web applications, and databases

Teleport is an identity-aware, multi-protocol access proxy which understands SSH, HTTPS, Kubernetes API, MySQL and PostgreSQL wire protocols.

Jan 9, 2023
🍪CookieMonster is a command-line tool and API for decoding and modifying vulnerable session cookies from several different frameworks.

?? CookieMonster CookieMonster is a command-line tool and API for decoding and modifying vulnerable session cookies from several different frameworks.

Jan 8, 2023
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
Authorization and authentication. Learning go by writing a simple authentication and authorization service.

Authorization and authentication. Learning go by writing a simple authentication and authorization service.

Aug 5, 2022
Package goth provides a simple, clean, and idiomatic way to write authentication packages for Go web applications.

Goth: Multi-Provider Authentication for Go Package goth provides a simple, clean, and idiomatic way to write authentication packages for Go web applic

Dec 29, 2022
Time-Based One-Time Password (TOTP) and HMAC-Based One-Time Password (HOTP) library for Go.

otpgo HMAC-Based and Time-Based One-Time Password (HOTP and TOTP) library for Go. Implements RFC 4226 and RFC 6238. Contents Supported Operations Read

Dec 19, 2022
:closed_lock_with_key: Middleware for keeping track of users, login states and permissions

Permissions2 Middleware for keeping track of users, login states and permissions. Online API Documentation godoc.org Features and limitations Uses sec

Dec 31, 2022