File: phc.go

package info (click to toggle)
golang-github-smallstep-cli 0.15.16%2Bds-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 4,404 kB
  • sloc: sh: 512; makefile: 99
file content (108 lines) | stat: -rw-r--r-- 2,936 bytes parent folder | download | duplicates (2)
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
}