| // 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]) |
| } |