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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 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)
}
|