Skip to content

Commit

Permalink
Merge pull request #413 from vixns/master
Browse files Browse the repository at this point in the history
add SHA support to htpasswd
  • Loading branch information
mattfarina authored Oct 28, 2024
2 parents fc7fc0d + a27d2ed commit 8cb06fe
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 13 deletions.
25 changes: 23 additions & 2 deletions crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,32 @@ func bcrypt(input string) string {
return string(hash)
}

func htpasswd(username string, password string) string {
func hashSha(password string) string {
s := sha1.New()
s.Write([]byte(password))
passwordSum := []byte(s.Sum(nil))
return base64.StdEncoding.EncodeToString(passwordSum)
}

// HashAlgorithm enum for hashing algorithms
type HashAlgorithm string

const (
// HashBCrypt bcrypt - recommended
HashBCrypt = "bcrypt"
HashSHA = "sha"
)

func htpasswd(username string, password string, hashAlgorithm HashAlgorithm) string {
if strings.Contains(username, ":") {
return fmt.Sprintf("invalid username: %s", username)
}
return fmt.Sprintf("%s:%s", username, bcrypt(password))
switch hashAlgorithm {
case HashSHA:
return fmt.Sprintf("%s:{SHA}%s", username, hashSha(password))
default:
return fmt.Sprintf("%s:%s", username, bcrypt(password))
}
}

func randBytes(count int) (string, error) {
Expand Down
28 changes: 19 additions & 9 deletions crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,29 +65,39 @@ func TestBcrypt(t *testing.T) {
}

type HtpasswdCred struct {
Username string
Password string
Valid bool
Username string
Password string
HashAlgorithm HashAlgorithm
Valid bool
}

func TestHtpasswd(t *testing.T) {
expectations := []HtpasswdCred{
{Username: "myUser", Password: "myPassword", Valid: true},
{Username: "special'o79Cv_*qFe,)<user", Password: "special<j7+3p#6-.Jx2U:m8G;kGypassword", Valid: true},
{Username: "wrongus:er", Password: "doesn'tmatter", Valid: false}, // ':' isn't allowed in the username - https://tools.ietf.org/html/rfc2617#page-6
{Username: "myUser", Password: "myPassword", HashAlgorithm: HashBCrypt, Valid: true},
{Username: "special'o79Cv_*qFe,)<user", Password: "special<j7+3p#6-.Jx2U:m8G;kGypassword", HashAlgorithm: HashBCrypt, Valid: true},
{Username: "wrongus:er", Password: "doesn'tmatter", HashAlgorithm: HashBCrypt, Valid: false}, // ':' isn't allowed in the username - https://tools.ietf.org/html/rfc2617#page-6
{Username: "mySahUser", Password: "myShaPassword", HashAlgorithm: HashSHA, Valid: true},
{Username: "myDefaultUser", Password: "defaulthashpass", Valid: true},
}

for _, credential := range expectations {
out, err := runRaw(`{{htpasswd .Username .Password}}`, credential)
out, err := runRaw(`{{htpasswd .Username .Password .HashAlgorithm}}`, credential)
if err != nil {
t.Error(err)
}
result := strings.Split(out, ":")
if 0 != strings.Compare(credential.Username, result[0]) && credential.Valid {
t.Error("Generated username did not match for:", credential.Username)
}
if bcrypt_lib.CompareHashAndPassword([]byte(result[1]), []byte(credential.Password)) != nil && credential.Valid {
t.Error("Generated hash is not the equivalent for password:", credential.Password)
switch credential.HashAlgorithm {
case HashSHA:
if strings.TrimPrefix(result[1], "{SHA}") != hashSha(credential.Password) {
t.Error("Generated hash is not the equivalent for password:", credential.Password)
}
default:
if bcrypt_lib.CompareHashAndPassword([]byte(result[1]), []byte(credential.Password)) != nil && credential.Valid {
t.Error("Generated hash is not the equivalent for password:", credential.Password)
}
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions docs/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ bcrypt "myPassword"

## htpasswd

The `htpasswd` function takes a `username` and `password` and generates a `bcrypt` hash of the password. The result can be used for basic authentication on an [Apache HTTP Server](https://httpd.apache.org/docs/2.4/misc/password_encryptions.html#basic).
The `htpasswd` function takes a `username`, a `password`, and a `hashAlgorithm` and generates a `bcrypt` (recommended) or a base64 encoded and prefixed `sha` hash of the password. `hashAlgorithm` is optional and defaults to `bcrypt`. The result can be used for basic authentication on an [Apache HTTP Server](https://httpd.apache.org/docs/2.4/misc/password_encryptions.html#basic).

```
htpasswd "myUser" "myPassword"
htpasswd "myUser" "myPassword" ["bcrypt"|"sha"]
```

Note that it is insecure to store the password directly in the template.
Expand Down

0 comments on commit 8cb06fe

Please sign in to comment.