Skip to content

Commit c96446b

Browse files
committed
add bcrypt as a supported hashing method for htpasswd passwords
1 parent 31dfb6e commit c96446b

File tree

4 files changed

+40
-11
lines changed

4 files changed

+40
-11
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ require (
1212
github.com/mreiferson/go-options v0.0.0-20161229190002-77551d20752b
1313
github.com/openshift/library-go v0.0.0-20190731063920-9fac0f4cee90
1414
github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997
15+
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586
1516
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9
1617
golang.org/x/text v0.3.3 // indirect
1718
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect

htpasswd.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import (
77
"io"
88
"log"
99
"os"
10+
11+
"golang.org/x/crypto/bcrypt"
1012
)
1113

12-
// lookup passwords in a htpasswd file
13-
// The entries must have been created with -s for SHA encryption
14+
// Lookup passwords in a htpasswd file
15+
// Passwords must be generated with -B for bcrypt or -s for SHA1.
1416

1517
type HtpasswdFile struct {
1618
Users map[string]string
@@ -47,14 +49,19 @@ func (h *HtpasswdFile) Validate(user string, password string) bool {
4749
if !exists {
4850
return false
4951
}
50-
if realPassword[:5] == "{SHA}" {
52+
shaPrefix := realPassword[:5]
53+
if shaPrefix == "{SHA}" {
54+
shaValue := realPassword[5:]
5155
d := sha1.New()
5256
d.Write([]byte(password))
53-
if realPassword[5:] == base64.StdEncoding.EncodeToString(d.Sum(nil)) {
54-
return true
55-
}
56-
} else {
57-
log.Printf("Invalid htpasswd entry for %s. Must be a SHA entry.", user)
57+
return shaValue == base64.StdEncoding.EncodeToString(d.Sum(nil))
58+
}
59+
60+
bcryptPrefix := realPassword[:4]
61+
if bcryptPrefix == "$2a$" || bcryptPrefix == "$2x$" || bcryptPrefix == "$2y$" {
62+
return bcrypt.CompareHashAndPassword([]byte(realPassword), []byte(password)) == nil
5863
}
64+
65+
log.Printf("Invalid htpasswd entry for %s. Must be a SHA or bcrypt entry.", user)
5966
return false
6067
}

htpasswd_test.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ package main
22

33
import (
44
"bytes"
5-
"github.com/bmizerany/assert"
5+
"fmt"
66
"testing"
7+
8+
"github.com/bmizerany/assert"
9+
"golang.org/x/crypto/bcrypt"
710
)
811

9-
func TestHtpasswd(t *testing.T) {
12+
func TestSHA(t *testing.T) {
1013
file := bytes.NewBuffer([]byte("testuser:{SHA}PaVBVZkYqAjCQCu6UBL2xgsnZhw=\nfoo:{SHA}rjXz/gOeuoMRiEa7Get6eHtKkX0=\n"))
1114
h, err := NewHtpasswd(file)
1215
assert.Equal(t, err, nil)
@@ -20,3 +23,21 @@ func TestHtpasswd(t *testing.T) {
2023
valid = h.Validate("nobody", "ghjk")
2124
assert.Equal(t, valid, false)
2225
}
26+
27+
func TestBcrypt(t *testing.T) {
28+
hash1, err := bcrypt.GenerateFromPassword([]byte("password"), 1)
29+
hash2, err := bcrypt.GenerateFromPassword([]byte("top-secret"), 2)
30+
assert.Equal(t, err, nil)
31+
32+
contents := fmt.Sprintf("testuser1:%s\ntestuser2:%s\n", hash1, hash2)
33+
file := bytes.NewBuffer([]byte(contents))
34+
35+
h, err := NewHtpasswd(file)
36+
assert.Equal(t, err, nil)
37+
38+
valid := h.Validate("testuser1", "password")
39+
assert.Equal(t, valid, true)
40+
41+
valid = h.Validate("testuser2", "top-secret")
42+
assert.Equal(t, valid, true)
43+
}

main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func main() {
6161
flagSet.String("client-secret", "", "the OAuth Client Secret")
6262
flagSet.String("client-secret-file", "", "a file containing the client-secret")
6363
flagSet.String("authenticated-emails-file", "", "authenticate against emails via file (one per line)")
64-
flagSet.String("htpasswd-file", "", "additionally authenticate against a htpasswd file. Entries must be created with \"htpasswd -s\" for SHA encryption")
64+
flagSet.String("htpasswd-file", "", "additionally authenticate against a htpasswd file. Entries must be created with \"htpasswd -s\" for SHA password hashes or \"htpasswd -B\" for bcrypt hashes")
6565
flagSet.Bool("display-htpasswd-form", true, "display username / password login form if an htpasswd file is provided")
6666
flagSet.String("custom-templates-dir", "", "path to custom html templates")
6767
flagSet.String("footer", "", "custom footer string. Use \"-\" to disable default footer.")

0 commit comments

Comments
 (0)