David K. Bainbridge | 528b318 | 2017-01-23 08:51:59 -0800 | [diff] [blame] | 1 | // Copyright 2012, 2013 Canonical Ltd. |
| 2 | // Licensed under the LGPLv3, see LICENCE file for details. |
| 3 | |
| 4 | package utils |
| 5 | |
| 6 | import ( |
| 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 |
| 18 | var CompatSalt = string([]byte{0x75, 0x82, 0x81, 0xca}) |
| 19 | |
| 20 | const 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. |
| 25 | var MinAgentPasswordLength = base64.StdEncoding.EncodedLen(randomPasswordBytes) |
| 26 | |
| 27 | // RandomBytes returns n random bytes. |
| 28 | func 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. |
| 38 | func 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). |
| 49 | func 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. |
| 61 | var FastInsecureHash = false |
| 62 | |
| 63 | // UserPasswordHash returns base64-encoded one-way hash password that is |
| 64 | // computationally hard to crack by iterating through possible passwords. |
| 65 | func 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. |
| 87 | func 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 | } |