143
vendor/github.com/jimstudt/http-authentication/basic/md5.go
generated
vendored
Normal file
143
vendor/github.com/jimstudt/http-authentication/basic/md5.go
generated
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type md5Password struct {
|
||||
salt string
|
||||
hashed string
|
||||
}
|
||||
|
||||
// Accept valid MD5 encoded passwords
|
||||
func AcceptMd5(src string) (EncodedPasswd, error) {
|
||||
if !strings.HasPrefix(src, "$apr1$") {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
rest := strings.TrimPrefix(src, "$apr1$")
|
||||
mparts := strings.SplitN(rest, "$", 2)
|
||||
if len(mparts) != 2 {
|
||||
return nil, fmt.Errorf("malformed md5 password: %s", src)
|
||||
}
|
||||
|
||||
salt, hashed := mparts[0], mparts[1]
|
||||
return &md5Password{salt, hashed}, nil
|
||||
}
|
||||
|
||||
// Reject any MD5 encoded password
|
||||
func RejectMd5(src string) (EncodedPasswd, error) {
|
||||
if !strings.HasPrefix(src, "$apr1$") {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("md5 password rejected: %s", src)
|
||||
}
|
||||
|
||||
// This is the MD5 hashing function out of Apache's htpasswd program. The algorithm
|
||||
// is insane, but we have to match it. Mercifully I found a PHP variant of it at
|
||||
// http://stackoverflow.com/questions/2994637/how-to-edit-htpasswd-using-php
|
||||
// in an answer. That reads better than the original C, and is easy to instrument.
|
||||
// We will eventually go back to the original apr_md5.c for inspiration when the
|
||||
// PHP gets too weird.
|
||||
// The algorithm makes more sense if you imagine the original authors in a pub,
|
||||
// drinking beer and rolling dice as the fundamental design process.
|
||||
func apr1Md5(password string, salt string) string {
|
||||
|
||||
// start with a hash of password and salt
|
||||
initBin := md5.Sum([]byte(password + salt + password))
|
||||
|
||||
// begin an initial string with hash and salt
|
||||
initText := bytes.NewBufferString(password + "$apr1$" + salt)
|
||||
|
||||
// add crap to the string willy-nilly
|
||||
for i := len(password); i > 0; i -= 16 {
|
||||
lim := i
|
||||
if lim > 16 {
|
||||
lim = 16
|
||||
}
|
||||
initText.Write(initBin[0:lim])
|
||||
}
|
||||
|
||||
// add more crap to the string willy-nilly
|
||||
for i := len(password); i > 0; i >>= 1 {
|
||||
if (i & 1) == 1 {
|
||||
initText.WriteByte(byte(0))
|
||||
} else {
|
||||
initText.WriteByte(password[0])
|
||||
}
|
||||
}
|
||||
|
||||
// Begin our hashing in earnest using our initial string
|
||||
bin := md5.Sum(initText.Bytes())
|
||||
|
||||
n := bytes.NewBuffer([]byte{})
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
// prepare to make a new muddle
|
||||
n.Reset()
|
||||
|
||||
// alternate password+crap+bin with bin+crap+password
|
||||
if (i & 1) == 1 {
|
||||
n.WriteString(password)
|
||||
} else {
|
||||
n.Write(bin[:])
|
||||
}
|
||||
|
||||
// usually add the salt, but not always
|
||||
if i%3 != 0 {
|
||||
n.WriteString(salt)
|
||||
}
|
||||
|
||||
// usually add the password but not always
|
||||
if i%7 != 0 {
|
||||
n.WriteString(password)
|
||||
}
|
||||
|
||||
// the back half of that alternation
|
||||
if (i & 1) == 1 {
|
||||
n.Write(bin[:])
|
||||
} else {
|
||||
n.WriteString(password)
|
||||
}
|
||||
|
||||
// replace bin with the md5 of this muddle
|
||||
bin = md5.Sum(n.Bytes())
|
||||
}
|
||||
|
||||
// At this point we stop transliterating the PHP code and flip back to
|
||||
// reading the Apache source. The PHP uses their base64 library, but that
|
||||
// uses the wrong character set so needs to be repaired afterwards and reversed
|
||||
// and it is just really weird to read.
|
||||
|
||||
result := bytes.NewBuffer([]byte{})
|
||||
|
||||
// This is our own little similar-to-base64-but-not-quite filler
|
||||
fill := func(a byte, b byte, c byte) {
|
||||
v := (uint(a) << 16) + (uint(b) << 8) + uint(c) // take our 24 input bits
|
||||
|
||||
for i := 0; i < 4; i++ { // and pump out a character for each 6 bits
|
||||
result.WriteByte("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"[v&0x3f])
|
||||
v >>= 6
|
||||
}
|
||||
}
|
||||
|
||||
// The order of these indices is strange, be careful
|
||||
fill(bin[0], bin[6], bin[12])
|
||||
fill(bin[1], bin[7], bin[13])
|
||||
fill(bin[2], bin[8], bin[14])
|
||||
fill(bin[3], bin[9], bin[15])
|
||||
fill(bin[4], bin[10], bin[5]) // 5? Yes.
|
||||
fill(0, 0, bin[11])
|
||||
|
||||
resultString := string(result.Bytes()[0:22]) // we wrote two extras since we only need 22.
|
||||
|
||||
return resultString
|
||||
}
|
||||
|
||||
func (m *md5Password) MatchesPassword(pw string) bool {
|
||||
hashed := apr1Md5(pw, m.salt)
|
||||
return constantTimeEquals(hashed, m.hashed)
|
||||
}
|
||||
Reference in New Issue
Block a user