Commit 613f57f8 authored by ale's avatar ale
Browse files

Use ai3/go-common for password hashing

parent a430981a
......@@ -9,13 +9,13 @@ import (
"path/filepath"
"strings"
scrypt "github.com/elithrar/simple-scrypt"
"github.com/pquerna/otp/totp"
"github.com/prometheus/client_golang/prometheus"
"github.com/tstranex/u2f"
"gopkg.in/yaml.v2"
"git.autistici.org/ai3/go-common/clientutil"
"git.autistici.org/ai3/go-common/pwhash"
"git.autistici.org/id/auth"
)
......@@ -561,8 +561,7 @@ func (s *Server) checkU2F(user *User, resp *u2f.SignResponse) bool {
}
func checkPassword(password, hash []byte) bool {
err := scrypt.CompareHashAndPassword(hash, password)
return err == nil
return pwhash.ComparePassword(string(hash), string(password))
}
func checkOTP(otp, secret string) bool {
......
......@@ -66,7 +66,7 @@ var (
testUsersFileStr = `---
- name: testuser
email: testuser@example.com
password: "16384$8$1$c479e8eb722f1b071efea7826ccf9c20$96d63ebed0c64afb746026f56f71b2a1f8796c73141d2d6b1958d4ea26c60a0b"
password: "$s$16384$8$1$c479e8eb722f1b071efea7826ccf9c20$96d63ebed0c64afb746026f56f71b2a1f8796c73141d2d6b1958d4ea26c60a0b"
groups:
- group1
- group2
......@@ -74,7 +74,7 @@ var (
- name: 2fauser
email: 2fauser@example.com
shard: 42
password: "16384$8$1$c479e8eb722f1b071efea7826ccf9c20$96d63ebed0c64afb746026f56f71b2a1f8796c73141d2d6b1958d4ea26c60a0b"
password: "$s$16384$8$1$c479e8eb722f1b071efea7826ccf9c20$96d63ebed0c64afb746026f56f71b2a1f8796c73141d2d6b1958d4ea26c60a0b"
totp_secret: "O32OBVS5BL5EAPB5"
`
......
package pwhash
import (
"crypto/rand"
"crypto/subtle"
"encoding/hex"
"errors"
"fmt"
"io"
"strconv"
"strings"
"github.com/amoghe/go-crypt"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/scrypt"
)
// PasswordHash is a convenience interface common to all types in this package.
type PasswordHash interface {
// ComparePassword returns true if the given password matches
// the encrypted one.
ComparePassword(string, string) bool
// Encrypt the given password.
Encrypt(string) string
}
// SystemCryptPasswordHash uses the glibc crypt function.
type SystemCryptPasswordHash struct{}
// ComparePassword returns true if the given password matches the
// encrypted one.
func (s *SystemCryptPasswordHash) ComparePassword(encrypted, password string) bool {
enc2, err := crypt.Crypt(password, encrypted)
if err != nil {
return false
}
return subtle.ConstantTimeCompare([]byte(encrypted), []byte(enc2)) == 1
}
// Encrypt the given password using glibc crypt.
func (s *SystemCryptPasswordHash) Encrypt(password string) string {
salt := fmt.Sprintf("$6$%x$", getRandomBytes(16))
enc, err := crypt.Crypt(password, salt)
if err != nil {
panic(err)
}
return enc
}
var (
argonKeyLen uint32 = 32
argonSaltLen = 16
)
// Argon2PasswordHash uses the Argon2 hashing algorithm.
type Argon2PasswordHash struct{}
// ComparePassword returns true if the given password matches the
// encrypted one.
func (s *Argon2PasswordHash) ComparePassword(encrypted, password string) bool {
params, salt, dk, err := decodeArgon2Hash(encrypted)
if err != nil {
return false
}
dk2 := argon2.Key([]byte(password), salt, params.Time, params.Memory, params.Threads, argonKeyLen)
//log.Printf("params=%+v, salt=%+v, dk=%v, dk2=%v", params, salt, dk, dk2)
return subtle.ConstantTimeCompare(dk, dk2) == 1
}
// Encrypt the given password with the Argon2 algorithm.
func (s *Argon2PasswordHash) Encrypt(password string) string {
salt := getRandomBytes(argonSaltLen)
params := defaultArgon2Params
dk := argon2.Key([]byte(password), salt, params.Time, params.Memory, params.Threads, argonKeyLen)
return encodeArgon2Hash(params, salt, dk)
}
type argon2Params struct {
Time uint32
Memory uint32
Threads uint8
}
var defaultArgon2Params = argon2Params{
Time: 4,
Memory: 32 * 1024,
Threads: 1,
// Test fails with threads > 1 !!
//Threads: uint8(runtime.NumCPU()),
}
func encodeArgon2Hash(params argon2Params, salt, dk []byte) string {
return fmt.Sprintf("$a2$%d$%d$%d$%x$%x", params.Time, params.Memory, params.Threads, salt, dk)
}
func decodeArgon2Hash(s string) (params argon2Params, salt []byte, dk []byte, err error) {
if !strings.HasPrefix(s, "$a2$") {
err = errors.New("not an Argon2 password hash")
return
}
parts := strings.SplitN(s[4:], "$", 5)
if len(parts) != 5 {
err = errors.New("bad encoding")
return
}
var i uint64
if i, err = strconv.ParseUint(parts[0], 10, 32); err != nil {
return
}
params.Time = uint32(i)
if i, err = strconv.ParseUint(parts[1], 10, 32); err != nil {
return
}
params.Memory = uint32(i)
if i, err = strconv.ParseUint(parts[2], 10, 8); err != nil {
return
}
params.Threads = uint8(i)
if salt, err = hex.DecodeString(parts[3]); err != nil {
return
}
dk, err = hex.DecodeString(parts[4])
return
}
var (
scryptKeyLen = 32
scryptSaltLen = 16
)
// ScryptPasswordHash uses the scrypt hashing algorithm.
type ScryptPasswordHash struct{}
// ComparePassword returns true if the given password matches
// the encrypted one.
func (s *ScryptPasswordHash) ComparePassword(encrypted, password string) bool {
params, salt, dk, err := decodeScryptHash(encrypted)
if err != nil {
return false
}
dk2, err := scrypt.Key([]byte(password), salt, params.N, params.R, params.P, scryptKeyLen)
if err != nil {
return false
}
//log.Printf("params=%+v, salt=%+v, dk=%v, dk2=%v", params, salt, dk, dk2)
return subtle.ConstantTimeCompare(dk, dk2) == 1
}
// Encrypt the given password with the scrypt algorithm.
func (s *ScryptPasswordHash) Encrypt(password string) string {
salt := getRandomBytes(scryptSaltLen)
params := defaultScryptParams
dk, err := scrypt.Key([]byte(password), salt, params.N, params.R, params.P, scryptKeyLen)
if err != nil {
panic(err)
}
return encodeScryptHash(params, salt, dk)
}
type scryptParams struct {
N int
R int
P int
}
var defaultScryptParams = scryptParams{
N: 16384,
R: 8,
P: 1,
}
func encodeScryptHash(params scryptParams, salt, dk []byte) string {
return fmt.Sprintf("$s$%d$%d$%d$%x$%x", params.N, params.R, params.P, salt, dk)
}
func decodeScryptHash(s string) (params scryptParams, salt []byte, dk []byte, err error) {
if !strings.HasPrefix(s, "$s$") {
err = errors.New("not a scrypt password hash")
return
}
parts := strings.SplitN(s[3:], "$", 5)
if len(parts) != 5 {
err = errors.New("bad encoding")
return
}
if params.N, err = strconv.Atoi(parts[0]); err != nil {
return
}
if params.R, err = strconv.Atoi(parts[1]); err != nil {
return
}
if params.P, err = strconv.Atoi(parts[2]); err != nil {
return
}
if salt, err = hex.DecodeString(parts[3]); err != nil {
return
}
dk, err = hex.DecodeString(parts[4])
return
}
func getRandomBytes(n int) []byte {
b := make([]byte, n)
_, err := io.ReadFull(rand.Reader, b[:])
if err != nil {
panic(err)
}
return b
}
var prefixRegistry = map[string]PasswordHash{
"$1$": &SystemCryptPasswordHash{},
"$5$": &SystemCryptPasswordHash{},
"$6$": &SystemCryptPasswordHash{},
"$s$": &ScryptPasswordHash{},
"$a2$": &Argon2PasswordHash{},
}
// ComparePassword returns true if the given password matches the
// encrypted one.
func ComparePassword(encrypted, password string) bool {
for pfx, h := range prefixRegistry {
if strings.HasPrefix(encrypted, pfx) {
return h.ComparePassword(encrypted, password)
}
}
return false
}
// DefaultEncryptAlgorithm is used by the Encrypt function to encrypt
// passwords.
var DefaultEncryptAlgorithm PasswordHash = &Argon2PasswordHash{}
// Encrypt will encrypt a password with the default algorithm.
func Encrypt(password string) string {
return DefaultEncryptAlgorithm.Encrypt(password)
}
The MIT License (MIT)
Copyright (c) 2015 Akshay Moghe
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
go-crypt (`crypt`)
==================
[![Build Status](https://travis-ci.org/amoghe/go-crypt.svg)](https://travis-ci.org/amoghe/go-crypt)
Package `crypt` provides go language wrappers around crypt(3). For further information on crypt see the
[man page](http://man7.org/linux/man-pages/man3/crypt.3.html)
If you have questions about how to use crypt (the C function), it is likely this is not the package you
are looking for.
**NOTE** Depending on the platform, this package provides a `Crypt` function that is backed by different
flavors of the libc crypt. This is done by detecting the GOOS and trying to build using `crypt_r` (the GNU
extension) when on linux, and wrapping around plain 'ol `crypt` (guarded by a global lock) otherwise.
Example
-------
```go
import (
"fmt"
"github.com/amoghe/go-crypt"
)
func main() {
md5, err := crypt.Crypt("password", "in")
if err != nil {
fmt.Errorf("error:", err)
return
}
sha512, err := crypt.Crypt("password", "$6$SomeSaltSomePepper$")
if err != nil {
fmt.Errorf("error:", err)
return
}
fmt.Println("MD5:", md5)
fmt.Println("SHA512:", sha512)
}
```
A Note On "Salt"
----------------
You can find out more about salt [here](https://en.wikipedia.org/wiki/Salt_(cryptography))
The hash algorithm can be selected via the salt string. Here is how to do it (relevant
section from the man page):
```
If salt is a character string starting with the characters
"$id$" followed by a string terminated by "$":
$id$salt$encrypted
then instead of using the DES machine, id identifies the
encryption method used and this then determines how the rest
of the password string is interpreted. The following values
of id are supported:
ID | Method
─────────────────────────────────────────────────────────
1 | MD5
2a | Blowfish (not in mainline glibc; added in some
| Linux distributions)
5 | SHA-256 (since glibc 2.7)
6 | SHA-512 (since glibc 2.7)
So $5$salt$encrypted is an SHA-256 encoded password and
$6$salt$encrypted is an SHA-512 encoded one.
"salt" stands for the up to 16 characters following "$id$" in
the salt. The encrypted part of the password string is the
actual computed password. The size of this string is fixed:
MD5 | 22 characters
SHA-256 | 43 characters
SHA-512 | 86 characters
```
Platforms
---------
This package has been tested on the following platforms:
- ubuntu 14.04.2 (libc 2.19)
- ubuntu 12.04.5 (libc 2.15)
- centos (libc 2.17)
- fedora 22 (libc 2.21)
All the platforms tested on have GNU libc (with extensions) so that the GOOS=linux always
compiles the reentrant versions of the crypt function (`crypt_r`), and exposes it to go land.
Other platforms (freebsd, netbsd) should also work (in theory) since their libc expose at least
a posix compliant crypt function. On these platforms the fallback should compile and expose the
'plain' (non reentrant, thus globally locked) crypt function.
Unfortunately, I do not have access to machines that run anything other than Linux, hence the other
platforms have not been tested, however I believe they should work just fine. If you can verify this
(or provide a patch that fixes this), I would be grateful.
TODO
----
* Find someone with access to *BSD system(s)
License
-------
Released under the [MIT License](LICENSE)
// +build darwin freebsd netbsd
// Package crypt provides wrappers around functions available in crypt.h
//
// It wraps around the GNU specific extension (crypt) when the reentrant version
// (crypt_r) is unavailable. The non-reentrant version is guarded by a global lock
// so as to be safely callable from concurrent goroutines.
package crypt
import (
"sync"
"unsafe"
)
/*
#define _GNU_SOURCE
#include <unistd.h>
*/
import "C"
var (
mu sync.Mutex
)
// Crypt provides a wrapper around the glibc crypt() function.
// For the meaning of the arguments, refer to the README.
func Crypt(pass, salt string) (string, error) {
c_pass := C.CString(pass)
defer C.free(unsafe.Pointer(c_pass))
c_salt := C.CString(salt)
defer C.free(unsafe.Pointer(c_salt))
mu.Lock()
c_enc, err := C.crypt(c_pass, c_salt)
mu.Unlock()
if c_enc == nil {
return "", err
}
defer C.free(unsafe.Pointer(c_enc))
// Return nil error if the string is non-nil.
// As per the errno.h manpage, functions are allowed to set errno
// on success. Caller should ignore errno on success.
return C.GoString(c_enc), err
}
// +build linux
// Package crypt provides wrappers around functions available in crypt.h
//
// It wraps around the GNU specific extension (crypt_r) when it is available
// (i.e. where GOOS=linux). This makes the go function reentrant (and thus
// callable from concurrent goroutines).
package crypt
import (
"unsafe"
)
/*
#cgo LDFLAGS: -lcrypt
#define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#include <crypt.h>
char *gnu_ext_crypt(char *pass, char *salt) {
char *enc = NULL;
char *ret = NULL;
struct crypt_data data;
data.initialized = 0;
enc = crypt_r(pass, salt, &data);
if(enc == NULL) {
return NULL;
}
ret = (char *)malloc(strlen(enc)+1); // for trailing null
strncpy(ret, enc, strlen(enc));
ret[strlen(enc)]= '\0'; // paranoid
return ret;
}
*/
import "C"
// Crypt provides a wrapper around the glibc crypt_r() function.
// For the meaning of the arguments, refer to the package README.
func Crypt(pass, salt string) (string, error) {
c_pass := C.CString(pass)
defer C.free(unsafe.Pointer(c_pass))
c_salt := C.CString(salt)
defer C.free(unsafe.Pointer(c_salt))
c_enc, err := C.gnu_ext_crypt(c_pass, c_salt)
if c_enc == nil {
return "", err
}
defer C.free(unsafe.Pointer(c_enc))
// Return nil error if the string is non-nil.
// As per the errno.h manpage, functions are allowed to set errno
// on success. Caller should ignore errno on success.
return C.GoString(c_enc), nil
}
package crypt
/*
#include <gnu/libc-version.h>
*/
import "C"
// Returns version string from libc
func LibCVersion() string {
c_ver := C.gnu_get_libc_version()
return C.GoString(c_ver)
}
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package argon2 implements the key derivation function Argon2.
// Argon2 was selected as the winner of the Password Hashing Competition and can
// be used to derive cryptographic keys from passwords.
// Argon2 is specfifed at https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf
package argon2
import (
"encoding/binary"
"sync"
"golang.org/x/crypto/blake2b"
)
// The Argon2 version implemented by this package.
const Version = 0x13
const (
argon2d = iota
argon2i
argon2id
)
// Key derives a key from the password, salt, and cost parameters using Argon2i
// returning a byte slice of length keyLen that can be used as cryptographic key.
// The CPU cost and parallism degree must be greater than zero.
//
// For example, you can get a derived key for e.g. AES-256 (which needs a 32-byte key) by doing:
// `key := argon2.Key([]byte("some password"), salt, 4, 32*1024, 4, 32)`
//
// The recommended parameters for interactive logins as of 2017 are time=4, memory=32*1024.
// The number of threads can be adjusted to the numbers of available CPUs.
// The time parameter specifies the number of passes over the memory and the memory
// parameter specifies the size of the memory in KiB. For example memory=32*1024 sets the
// memory cost to ~32 MB.
// The cost parameters should be increased as memory latency and CPU parallelism increases.
// Remember to get a good random salt.
func Key(password, salt []byte, time, memory uint32, threads uint8, keyLen uint32) []byte {
return deriveKey(argon2i, password, salt, nil, nil, time, memory, threads, keyLen)
}
func deriveKey(mode int, password, salt, secret, data []byte, time, memory uint32, threads uint8, keyLen uint32) []byte {
if time < 1 {
panic("argon2: number of rounds too small")
}
if threads < 1 {
panic("argon2: paralisim degree too low")
}
mem := memory / (4 * uint32(threads)) * (4 * uint32(threads))
if mem < 8*uint32(threads) {