blob: 914e8e4342f6f8abc6665a86628b19bee66edbd6 [file] [log] [blame]
// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package utils
import (
"crypto/rand"
"crypto/sha512"
"encoding/base64"
"fmt"
"io"
"golang.org/x/crypto/pbkdf2"
)
// CompatSalt is because Juju 1.16 and older used a hard-coded salt to compute
// the password hash for all users and agents
var CompatSalt = string([]byte{0x75, 0x82, 0x81, 0xca})
const randomPasswordBytes = 18
// MinAgentPasswordLength describes how long agent passwords should be. We
// require this length because we assume enough entropy in the Agent password
// that it is safe to not do extra rounds of iterated hashing.
var MinAgentPasswordLength = base64.StdEncoding.EncodedLen(randomPasswordBytes)
// RandomBytes returns n random bytes.
func RandomBytes(n int) ([]byte, error) {
buf := make([]byte, n)
_, err := io.ReadFull(rand.Reader, buf)
if err != nil {
return nil, fmt.Errorf("cannot read random bytes: %v", err)
}
return buf, nil
}
// RandomPassword generates a random base64-encoded password.
func RandomPassword() (string, error) {
b, err := RandomBytes(randomPasswordBytes)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(b), nil
}
// RandomSalt generates a random base64 data suitable for using as a password
// salt The pbkdf2 guideline is to use 8 bytes of salt, so we do 12 raw bytes
// into 16 base64 bytes. (The alternative is 6 raw into 8 base64).
func RandomSalt() (string, error) {
b, err := RandomBytes(12)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(b), nil
}
// FastInsecureHash specifies whether a fast, insecure version of the hash
// algorithm will be used. Changing this will cause PasswordHash to
// produce incompatible passwords. It should only be changed for
// testing purposes - to make tests run faster.
var FastInsecureHash = false
// UserPasswordHash returns base64-encoded one-way hash password that is
// computationally hard to crack by iterating through possible passwords.
func UserPasswordHash(password string, salt string) string {
if salt == "" {
panic("salt is not allowed to be empty")
}
iter := 8192
if FastInsecureHash {
iter = 1
}
// Generate 18 byte passwords because we know that MongoDB
// uses the MD5 sum of the password anyway, so there's
// no point in using more bytes. (18 so we don't get base 64
// padding characters).
h := pbkdf2.Key([]byte(password), []byte(salt), iter, 18, sha512.New)
return base64.StdEncoding.EncodeToString(h)
}
// AgentPasswordHash returns base64-encoded one-way hash of password. This is
// not suitable for User passwords because those will have limited entropy (see
// UserPasswordHash). However, since we generate long random passwords for
// agents, we can trust that there is sufficient entropy to prevent brute force
// search. And using a faster hash allows us to restart the state machines and
// have 1000s of agents log in in a reasonable amount of time.
func AgentPasswordHash(password string) string {
sum := sha512.New()
sum.Write([]byte(password))
h := sum.Sum(nil)
return base64.StdEncoding.EncodeToString(h[:18])
}