blob: 914e8e4342f6f8abc6665a86628b19bee66edbd6 [file] [log] [blame]
David K. Bainbridge528b3182017-01-23 08:51:59 -08001// Copyright 2012, 2013 Canonical Ltd.
2// Licensed under the LGPLv3, see LICENCE file for details.
3
4package utils
5
6import (
7 "crypto/rand"
8 "crypto/sha512"
9 "encoding/base64"
10 "fmt"
11 "io"
12
13 "golang.org/x/crypto/pbkdf2"
14)
15
16// CompatSalt is because Juju 1.16 and older used a hard-coded salt to compute
17// the password hash for all users and agents
18var CompatSalt = string([]byte{0x75, 0x82, 0x81, 0xca})
19
20const randomPasswordBytes = 18
21
22// MinAgentPasswordLength describes how long agent passwords should be. We
23// require this length because we assume enough entropy in the Agent password
24// that it is safe to not do extra rounds of iterated hashing.
25var MinAgentPasswordLength = base64.StdEncoding.EncodedLen(randomPasswordBytes)
26
27// RandomBytes returns n random bytes.
28func RandomBytes(n int) ([]byte, error) {
29 buf := make([]byte, n)
30 _, err := io.ReadFull(rand.Reader, buf)
31 if err != nil {
32 return nil, fmt.Errorf("cannot read random bytes: %v", err)
33 }
34 return buf, nil
35}
36
37// RandomPassword generates a random base64-encoded password.
38func RandomPassword() (string, error) {
39 b, err := RandomBytes(randomPasswordBytes)
40 if err != nil {
41 return "", err
42 }
43 return base64.StdEncoding.EncodeToString(b), nil
44}
45
46// RandomSalt generates a random base64 data suitable for using as a password
47// salt The pbkdf2 guideline is to use 8 bytes of salt, so we do 12 raw bytes
48// into 16 base64 bytes. (The alternative is 6 raw into 8 base64).
49func RandomSalt() (string, error) {
50 b, err := RandomBytes(12)
51 if err != nil {
52 return "", err
53 }
54 return base64.StdEncoding.EncodeToString(b), nil
55}
56
57// FastInsecureHash specifies whether a fast, insecure version of the hash
58// algorithm will be used. Changing this will cause PasswordHash to
59// produce incompatible passwords. It should only be changed for
60// testing purposes - to make tests run faster.
61var FastInsecureHash = false
62
63// UserPasswordHash returns base64-encoded one-way hash password that is
64// computationally hard to crack by iterating through possible passwords.
65func UserPasswordHash(password string, salt string) string {
66 if salt == "" {
67 panic("salt is not allowed to be empty")
68 }
69 iter := 8192
70 if FastInsecureHash {
71 iter = 1
72 }
73 // Generate 18 byte passwords because we know that MongoDB
74 // uses the MD5 sum of the password anyway, so there's
75 // no point in using more bytes. (18 so we don't get base 64
76 // padding characters).
77 h := pbkdf2.Key([]byte(password), []byte(salt), iter, 18, sha512.New)
78 return base64.StdEncoding.EncodeToString(h)
79}
80
81// AgentPasswordHash returns base64-encoded one-way hash of password. This is
82// not suitable for User passwords because those will have limited entropy (see
83// UserPasswordHash). However, since we generate long random passwords for
84// agents, we can trust that there is sufficient entropy to prevent brute force
85// search. And using a faster hash allows us to restart the state machines and
86// have 1000s of agents log in in a reasonable amount of time.
87func AgentPasswordHash(password string) string {
88 sum := sha512.New()
89 sum.Write([]byte(password))
90 h := sum.Sum(nil)
91 return base64.StdEncoding.EncodeToString(h[:18])
92}