1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
|
package kdf
import (
"encoding/base64"
"strconv"
"strings"
"github.com/pkg/errors"
)
// phcEncoding is the alphabet used to encode/decode the hashes. It's based on
// the PHC string format:
//
// https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
var phcEncoding = base64.RawStdEncoding
// phcAtoi returns the number in the string value or n if value is empty.
func phcAtoi(value string, n int) (int, error) {
if value == "" {
return n, nil
}
return strconv.Atoi(value)
}
// phcParamsToMap parses the parameters in the string s and returns them in a
// map of keys and values.
func phcParamsToMap(s string) map[string]string {
parameters := strings.Split(s, ",")
m := make(map[string]string, len(parameters))
for _, p := range parameters {
subs := strings.SplitN(p, "=", 2)
if len(subs) == 2 {
m[subs[0]] = subs[1]
} else {
m[subs[0]] = ""
}
}
return m
}
// phcEncode creates a string using the PHC format.
func phcEncode(identifier, params string, salt, hash []byte) string {
ret := "$" + identifier
if len(params) > 0 {
ret += "$" + params
}
if len(salt) > 0 {
ret += "$" + phcEncoding.EncodeToString(salt)
}
if len(hash) > 0 {
ret += "$" + phcEncoding.EncodeToString(hash)
}
return ret
}
// phcDecode returns the different parts of a PHC encoded string.
func phcDecode(s string) (id string, version int, params string, salt []byte, hash []byte, err error) {
subs := strings.SplitN(s, "$", 6)
if subs[0] != "" || len(subs) < 2 || (subs[1] == bcryptHash && len(subs) != 4) {
return "", 0, "", nil, nil, errors.New("cannot decode password hash")
}
// Special case for bcrypt
// return just the id and the full hash
if subs[1] == bcryptHash {
return bcryptHash, 0, "", nil, []byte(s), nil
}
switch len(subs) {
case 6: // id + version + params + salt + hash
// version: v=<dec>
m := phcParamsToMap(subs[2])
if version, err = phcAtoi(m["v"], 0); err != nil {
return "", 0, "", nil, nil, err
}
if hash, err = phcEncoding.DecodeString(subs[5]); err != nil {
return "", 0, "", nil, nil, err
}
if salt, err = phcEncoding.DecodeString(subs[4]); err != nil {
return "", 0, "", nil, nil, err
}
id, params = subs[1], subs[3]
case 5: // id + params + salt + hash
if hash, err = phcEncoding.DecodeString(subs[4]); err != nil {
return "", 0, "", nil, nil, err
}
if salt, err = phcEncoding.DecodeString(subs[3]); err != nil {
return "", 0, "", nil, nil, err
}
id, params = subs[1], subs[2]
case 4: // id + salt + hash
if hash, err = phcEncoding.DecodeString(subs[3]); err != nil {
return "", 0, "", nil, nil, err
}
if salt, err = phcEncoding.DecodeString(subs[2]); err != nil {
return "", 0, "", nil, nil, err
}
id = subs[1]
case 3: // id + params
id, params = subs[1], subs[2]
case 2: // id
id = subs[1]
default:
return "", 0, "", nil, nil, errors.New("cannot decode password hash")
}
return
}
|