/goSecretBoxPassword

A probably paranoid Golang utility library for securely hashing and encrypting passwords based on the Dropbox method. This implementation uses Blake2b, Scrypt and XSalsa20-Poly1305 (via NaCl SecretBox) to create secure password hashes that are also encrypted using a master passphrase.

Primary LanguageGoMIT LicenseMIT

goSecretBoxPassword

Go Report Card GoDoc Build Status CodeQL

This is a Golang library for securing passwords it is based on the Dropbox method for password storage. The both passphrases are first hashed with Blake2b-512 then a random 64-bit salt is generated and a secure hash is generated using Scrypt with the user specified parameters. The salt is appended to resulting 56 byte hash for a total of 64 bytes. The masterpassphrase Scrypt output, which Dropbox describes as a global pepper, is then hashed with Blake2b-256 and is used as a key along with a 192-bit random nonce value for the user passphrase Scrypt output along with Scrypt salt to be encrypted using NaCl Secretbox. NaCl Secretbox uses XSalsa20 and Poly1305 to encrypt and authenticate data.

All hashing and crypto is done by Go library packages. This is only a utility library to make the process described easier.

The primary changes from the Dropbox method are the use of Blake2b as both a password stretching and hashing method rather than SHA. This provides predictable and acceptable length input for both Scrypt and NaCl Secretbox rather than relying on users to provide sufficient length input. The other changes is the use of NaCl Secretbox using XSalsa20 for encryption and Poly1305 for authentication rather than AES-GCM.

The resulting string is not more than 225 characters in length and contains an identifier, version (used for master passphrase version), the ciphertext Scrypt base-64 value, masterkey Scrypt salt as base-64, and user passphrase scrypt parameters as integers, followed by master passphrase Scrypt parameters in the same format with sections separated by $.

You should not store the master passphrase with the password hashes. How you choose to store this value is up to you, but you should understand that losing the masterpassphrase will cause you to lose access to all passwords encrypted with this. According to the math, you should be able to use the same master passphrase for several quintillion passphrases without key exhaustion using XSalsa20 so you should feel free to use it for all users rather than attempting to rotate this. You can store the master passphrase and an environmental variable, CLI argument or come up with your own novel approach as long as you don't lose it.

Using this method, even if the user credential database were to be compromised the attacker would first need to break the Secretbox encryption before being able to then attempt to crack the Scrypt passphrase hashes which would still only reveal the Blake2b-512 hash of the passphrase. By tuning the Scrypt parameters you can make the password hash derivation more costly (more time and resource consuming). You will need to balance security and speed.

As of writing, secure recommendations for interactive logins are N=32768, r=8 and p=1. The parameters N, r, and p should be increased as memory latency and CPU parallelism increases; consider setting N to the highest power of 2 you can derive within an acceptable period of time. Because these parameters are combined to be stored with the output you should have no issue changing the settings without forcing users to update. There is also a version tag included to allow for future library updates without causing breaking changes for existing users.

The params are used for both the user passphrase and master passphrase hash, keep in mind that any settings is being done twice as thus Scrypt hashing params have twice the impact. I have considered creating an updated version where master or user passphrase hash is computed using a different algorithm or with different parameters to increase speed. This would not be a breaking change since there is a version included in all hash outputs.

Example Output:

secBoxv1$0$G0Ke6q1upuoC+fs+cPvK5swmF8WNNxc9cWHwyp5pZtL/Yc+KmjD1x/43mjy/ySWj4uAFc92LL5tKmvsTCedFMqyNJ8URdzJ1MgdqmCgMIkOXy87JacKdLnxjyIWjeeNnLVxiCWjXhrI=$8OPoOpUeIf0=$32768$16$1$16384$8$1

Usage

Latest from Github:

go get github.com/dwin/goSecretBoxPassword
import "github.com/dwin/goSecretBoxPassword"

Future Plans

  • Helper function for updating master passphrase without modifying user passphrase
  • Allow seamless change of master passphrase using version code
  • Allow disable of Scrypt, use of different parameters or algorithm for master passphrase hash to increase speed or security

Example

package main

import (
    "fmt"

    password "github.com/dwin/goSecretBoxPassword"
)

// This should be from config file or environemnt variables rather than your source
var mastPw = "masterpassword" 

func main() {
    userPw := "userpassword"
    // Hash and encrypt passphrase
    pwHash, err := password.Hash(userPw, mastPw, 0, password.ScryptParams{N: 32768, R: 16, P: 1}, password.DefaultParams)
    if err != nil {
        fmt.Println("Hash fail. ", err)
    }
   // Store pwHash to Database ->

    // -------- Verify -------------
    // Get pwHash from database <- and compare to user input using same
    // masterpassword stored hash was created with.
    // Verify will return nil unless password does not match or other error occurs
    err = password.Verify(userPw, mastPw, pwHash)
    if err != nil {
        fmt.Println("Verify fail. ", err)
    }
    fmt.Println("Success")
}

Contributors