Skip to content

Commit 1fa2fb0

Browse files
jespersoderlundJesper Söderlund
and
Jesper Söderlund
authored
Adding Crypt with SHA-256 and SHA-512 algorithm support (#6)
* Adding Crypt with SHA-256 and SHA-512 algorithm support * Stating Crypt as supported Co-authored-by: Jesper Söderlund <[email protected]>
1 parent 148c35f commit 1fa2fb0

7 files changed

+544
-8
lines changed

README.md

+4-6
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
[![Go Report Card](https://goreportcard.com/badge/github.com/tg123/go-htpasswd)](https://goreportcard.com/report/github.com/tg123/go-htpasswd)
66

77

8-
This is a libary to validate user credentials against an HTTPasswd file.
8+
This is a libary to validate user credentials against an HTTPasswd file.
99

10-
This was forked from <https://github.com/jimstudt/http-authentication/tree/master/basic>
11-
with modifications by @brian-avery to support SSHA, Md5Crypt, and Bcrypt.
10+
This was forked from <https://github.com/jimstudt/http-authentication/tree/master/basic>
11+
with modifications by @brian-avery to support SSHA, Md5Crypt, and Bcrypt and @jespersoderlund to support Crypt with SHA-256 and SHA-512 support.
1212

1313
## Currently, this supports:
1414
* SSHA
@@ -17,6 +17,4 @@ with modifications by @brian-avery to support SSHA, Md5Crypt, and Bcrypt.
1717
* SHA
1818
* Bcrypt
1919
* Plain text
20-
21-
## Not supported:
22-
* Crypt
20+
* Crypt with SHA-256 and SHA-512

cryptsha.go

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package htpasswd
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/GehirnInc/crypt"
8+
_ "github.com/GehirnInc/crypt/sha256_crypt"
9+
_ "github.com/GehirnInc/crypt/sha512_crypt"
10+
)
11+
12+
type cryptPassword struct {
13+
salt string
14+
hashed string
15+
prefix string
16+
}
17+
18+
// Prefixes
19+
const PrefixCryptSha256 = "$5$"
20+
const PrefixCryptSha512 = "$6$"
21+
22+
// Accepts valid passwords
23+
func AcceptCryptSha(src string) (EncodedPasswd, error) {
24+
if !strings.HasPrefix(src, PrefixCryptSha256) && !strings.HasPrefix(src, PrefixCryptSha512) {
25+
return nil, nil
26+
}
27+
28+
prefix := PrefixCryptSha512
29+
if strings.HasPrefix(src, PrefixCryptSha256) {
30+
prefix = PrefixCryptSha256
31+
}
32+
33+
rest := strings.TrimPrefix(src, prefix)
34+
mparts := strings.SplitN(rest, "$", 2)
35+
if len(mparts) != 2 {
36+
return nil, fmt.Errorf("malformed crypt-SHA password: %s", src)
37+
}
38+
39+
salt, hashed := mparts[0], mparts[1]
40+
if len(salt) > 16 {
41+
salt = salt[0:16]
42+
}
43+
return &cryptPassword{salt, hashed, prefix}, nil
44+
}
45+
46+
// RejectCryptSha known indexes
47+
func RejectCryptSha(src string) (EncodedPasswd, error) {
48+
if !strings.HasPrefix(src, PrefixCryptSha512) && !strings.HasPrefix(src, PrefixCryptSha256) {
49+
return nil, nil
50+
}
51+
return nil, fmt.Errorf("crypt-sha password rejected: %s", src)
52+
}
53+
54+
func shaCrypt(password string, salt string, prefix string) string {
55+
56+
var ret string
57+
var sb strings.Builder
58+
sb.WriteString(prefix)
59+
sb.WriteString(salt)
60+
totalSalt := sb.String()
61+
62+
if prefix == PrefixCryptSha512 {
63+
crypt := crypt.SHA512.New()
64+
ret, _ = crypt.Generate([]byte(password), []byte(totalSalt))
65+
66+
} else if prefix == PrefixCryptSha256 {
67+
crypt := crypt.SHA256.New()
68+
ret, _ = crypt.Generate([]byte(password), []byte(totalSalt))
69+
}
70+
71+
return ret[len(totalSalt)+1:]
72+
}
73+
74+
func (m *cryptPassword) MatchesPassword(pw string) bool {
75+
hashed := shaCrypt(pw, m.salt, m.prefix)
76+
return constantTimeEquals(hashed, m.hashed)
77+
}

cryptsha_test.go

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package htpasswd
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
)
7+
8+
type shacryptDatum struct {
9+
password string
10+
salt string
11+
hashed string
12+
prefix string
13+
}
14+
15+
var sha256TestData = []shacryptDatum{
16+
{"mickey", "123456", "2hClNSDw3lZ0X/9PFBSI2eCGMOS06v6IbChiRsjy6tA", PrefixCryptSha256},
17+
{"paul1", "654321", "yXh20wwTHRwjSLcw20kQtiViO9n7HXDgEvzWf.cDks4", PrefixCryptSha256},
18+
{"princessbuttercup", "gildor", "/96zrUL6Si5ApMDxKlIvMHefBZz04JXJeg.Lp1fjhg1", PrefixCryptSha256},
19+
}
20+
21+
var sha512TestData = []shacryptDatum{
22+
{"vinnie6", "123456", "By3XGEfRf2RwFvWYR0kHRVJGq2/IKwLEGQxwyncoP88TGiBzHMBmvrTNxHgyqrmhZ/M7CGtkfIw0rBRfewW.y1", PrefixCryptSha512},
23+
{"mickey5", "iklkG8zV969+0x+f", "XKxre3pm8QNHezNxyEXj51AkNy5AXJQKifFhVWqhVaLLUAUAZkDy6Dp1Th/mTE/e/MkImK30.pByqu0CGsQZW1", PrefixCryptSha512},
24+
{"andrew1", "654321", "Qro3QWOs61UCarx1PAwAlL1.vJgZJXsIXml3.3vVhV.2xUwIRBmmyQzK9yFAqYY5iD1wkAdhUko6hl6T9N7s5.", PrefixCryptSha512},
25+
{"dreadpirateroberts", "98765432101234567890", "FPU3HtJ9RcPVUvxifkIJ/AlrBxWLqJQvyxK2f8x4qDX/A1RpcIvgjToU5erVkR6XUl7qwPsm7idpbMH5f/pBn0", PrefixCryptSha512},
26+
}
27+
28+
func Test_CryptSha(t *testing.T) {
29+
30+
for _, v := range sha256TestData {
31+
text := fmt.Sprintf(v.prefix+"%s$%s", v.salt, v.hashed)
32+
testParserGood(t, "crypt-sha512", AcceptCryptSha, RejectCryptSha, text, v.password)
33+
}
34+
35+
for _, v := range sha512TestData {
36+
text := fmt.Sprintf(v.prefix+"%s$%s", v.salt, v.hashed)
37+
testParserGood(t, "crypt-sha512", AcceptCryptSha, RejectCryptSha, text, v.password)
38+
}
39+
40+
testParserBad(t, "crypt-sha256", AcceptCryptSha, RejectCryptSha, "$5$nosalt")
41+
testParserBad(t, "crypt-sha512", AcceptCryptSha, RejectCryptSha, "$6$nosalt")
42+
testParserNot(t, "crypt-sha512", AcceptCryptSha, RejectCryptSha, "plain")
43+
testParserNot(t, "crypt-sha512", AcceptCryptSha, RejectCryptSha, "{SHA}plain")
44+
}

go.mod

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@ module github.com/tg123/go-htpasswd
22

33
go 1.12
44

5-
require golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25
5+
require (
6+
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
7+
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25
8+
)

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 h1:KeNholpO2xKjgaaSyd+DyQRrsQjhbSeS7qe4nEw8aQw=
2+
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962/go.mod h1:kC29dT1vFpj7py2OvG1khBdQpo3kInWP+6QipLbdngo=
13
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25 h1:jsG6UpNLt9iAsb0S2AGW28DveNzzgmbXR+ENoPjUeIU=
24
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
35
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

htpasswd.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ type File struct {
5757
}
5858

5959
// DefaultSystems is an array of PasswdParser including all builtin parsers. Notice that Plain is last, since it accepts anything
60-
var DefaultSystems = []PasswdParser{AcceptMd5, AcceptSha, AcceptBcrypt, AcceptSsha, AcceptPlain}
60+
var DefaultSystems = []PasswdParser{AcceptMd5, AcceptSha, AcceptBcrypt, AcceptSsha, AcceptCryptSha, AcceptPlain}
6161

6262
// New creates an File from an Apache-style htpasswd file for HTTP Basic Authentication.
6363
//

0 commit comments

Comments
 (0)