├── README.md ├── .gitignore ├── phpass-hash ├── main.go └── README.md ├── phpass-check ├── README.md └── main.go ├── config.go ├── LICENSE ├── pkg_test.go └── hash.go /README.md: -------------------------------------------------------------------------------- 1 | phpass 2 | ====== 3 | 4 | A go implementation of the PHPass password hashing standard (http://www.openwall.com/phpass/) 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | -------------------------------------------------------------------------------- /phpass-hash/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/apokalyptik/phpass" 9 | ) 10 | 11 | var pw = "" 12 | 13 | func main() { 14 | flag.StringVar(&pw, "pw", "", "Password to hash") 15 | flag.Parse() 16 | if pw == "" { 17 | pw = strings.Join(flag.Args(), " ") 18 | } 19 | var h = phpass.New(nil) 20 | hash, _ := h.Hash([]byte(pw)) 21 | fmt.Println(string(hash)) 22 | } 23 | -------------------------------------------------------------------------------- /phpass-hash/README.md: -------------------------------------------------------------------------------- 1 | A command line tool for creating PHPass hashed passwords. 2 | 3 | Installation: 4 | 5 | ```bash 6 | go get github.com/apokalyptik/phpass/phpass-hash 7 | ``` 8 | 9 | or download a build from http://gobuild.io/download/github.com/apokalyptik/phpass/phpass-hash 10 | 11 | Usage: 12 | 13 | ```bash 14 | phpass-hash -pw="myunhashedpassword" 15 | ``` 16 | 17 | or 18 | 19 | ```bash 20 | phpass-hash 'myunhashedpassword' 21 | ``` 22 | -------------------------------------------------------------------------------- /phpass-check/README.md: -------------------------------------------------------------------------------- 1 | A command line tool to check a PHPass hashed password 2 | 3 | Installation: 4 | 5 | ```bash 6 | go get github.com/apokalyptik/phpass/phpass-check 7 | ``` 8 | 9 | or download a build from http://gobuild.io/download/github.com/apokalyptik/phpass/phpass-check 10 | 11 | Usage: 12 | 13 | ```bash 14 | phpass-check -hash="$P$passwordhash" -pw="myunhashedpassword" 15 | ``` 16 | 17 | or 18 | 19 | ```bash 20 | phpass-check '$P$passwordhash' 'myunhashedpassword' 21 | ``` 22 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package phpass 2 | 3 | // Config represents the configuration options that PHPass takes 4 | // Note that these are, essentially, ignored right now. 5 | type Config struct { 6 | Count int 7 | Portable bool 8 | Algorithm string 9 | Itoa string 10 | } 11 | 12 | // NewConfig returnes a new defaulted config struct for use with New() 13 | func NewConfig() *Config { 14 | return &Config{ 15 | Count: 8, 16 | Portable: true, 17 | Algorithm: "bcrypt", 18 | Itoa: "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /phpass-check/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "github.com/apokalyptik/phpass" 10 | ) 11 | 12 | var pw = "" 13 | var hash = "" 14 | 15 | func main() { 16 | flag.StringVar(&hash, "hash", "", "Hash to validate against") 17 | flag.StringVar(&pw, "pw", "", "Password validate") 18 | flag.Parse() 19 | if len(flag.Args()) > 1 { 20 | if pw == "" { 21 | pw = strings.Join(flag.Args()[1:], " ") 22 | } 23 | if hash == "" { 24 | hash = flag.Args()[0] 25 | } 26 | } 27 | var h = phpass.New(nil) 28 | if h.Check([]byte(pw), []byte(hash)) { 29 | fmt.Println("true") 30 | } else { 31 | fmt.Println("false") 32 | os.Exit(1) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 apokalyptik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pkg_test.go: -------------------------------------------------------------------------------- 1 | package phpass 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" 6 | "testing" 7 | ) 8 | 9 | func TestEncode(t *testing.T) { 10 | var h = New(nil) 11 | if v := h.encode([]byte("foobar"), 6); string(v) != "axqPW3aQ" { 12 | t.Errorf("expected 'axqPW3aQ', got %q", string(v)) 13 | } 14 | 15 | var m = md5.New() 16 | m.Write([]byte("blah")) 17 | if v := h.encode(m.Sum(nil), 16); string(v) != `jt/o0gOJJK6YIgCwJKV6N1` { 18 | t.Errorf("expected 'jt/o0gOJJK6YIgCwJKV6N1', got %q", string(v)) 19 | } 20 | } 21 | 22 | func TestSalt(t *testing.T) { 23 | var h = New(nil) 24 | s1 := h.salt() 25 | s2 := h.salt() 26 | if 0 == bytes.Compare(s1, s2) { 27 | t.Errorf("expected different salts, got %q both times", string(s1)) 28 | } 29 | } 30 | 31 | func TestCrypt(t *testing.T) { 32 | var h = New(nil) 33 | v, e := h.crypt([]byte("QqWwEeRrTtYy"), []byte(`$P$BU7c29K11fx.vbNBsqafkEDO8GC/280`)) 34 | if e != nil { 35 | t.Errorf("Expected no error, got %s", e.Error()) 36 | } 37 | if !bytes.Equal([]byte(`$P$BU7c29K11fx.vbNBsqafkEDO8GC/280`), v) { 38 | t.Errorf("Expected returned hash to match, got %s", v) 39 | } 40 | 41 | v, e = h.crypt([]byte("QqWwEeRrTtYy"), []byte(`$H$BU7c29K11fx.vbNBsqafkEDO8GC/280`)) 42 | if e != nil { 43 | t.Errorf("Expected no error, got %s", e.Error()) 44 | } 45 | if !bytes.Equal([]byte(`$H$BU7c29K11fx.vbNBsqafkEDO8GC/280`), v) { 46 | t.Errorf("Expected returned hash to match, got %s", v) 47 | } 48 | 49 | v, e = h.crypt([]byte("QqWwEeRrTtYy"), []byte(`$P$zU7c29K11fx.vbNBsqafkEDO8GC/280`)) 50 | if e != errBadCount { 51 | t.Errorf("Expected errBadCount, got %#v", e) 52 | } 53 | if !bytes.Equal([]byte(`*0`), v) { 54 | t.Errorf("Expected *0, got %s", v) 55 | } 56 | 57 | v, e = h.crypt([]byte("QqWwEeRrTtYy"), []byte(`$Z$BU7c29K11fx.vbNBsqafkEDO8GC/280`)) 58 | if e != errBadID { 59 | t.Errorf("Expected errBadID, got %#v", e) 60 | } 61 | if !bytes.Equal([]byte(`*0`), v) { 62 | t.Errorf("Expected *0, got %s", v) 63 | } 64 | 65 | v, e = h.crypt([]byte("QqWwEeRrTtYy"), []byte(`*0$BU7c29K11fx.vbNBsqafkEDO8GC/280`)) 66 | if e != errBadID { 67 | t.Errorf("Expected no errBadId, got %#v", e) 68 | } 69 | if !bytes.Equal([]byte(`*1`), v) { 70 | t.Errorf("Expected *1, got %s", v) 71 | } 72 | } 73 | 74 | func TestCheck(t *testing.T) { 75 | var h = New(nil) 76 | if v := h.Check([]byte("QqWwEeRrTtYy"), []byte(`$P$BU7c29K11fx.vbNBsqafkEDO8GC/280`)); !v { 77 | t.Errorf("Expected true, got false") 78 | } 79 | if v := h.Check([]byte("qqWwEeRrTtYy"), []byte(`$P$BU7c29K11fx.vbNBsqafkEDO8GC/280`)); v { 80 | t.Errorf("Expected false, got true") 81 | } 82 | } 83 | 84 | func TestHash(t *testing.T) { 85 | var h = New(nil) 86 | hashedPW, err := h.Hash([]byte("345jkhrlfkwhpf98q3u4pljhqenwlfkjashd")) 87 | if err != nil { 88 | t.Errorf("Expected no error, got %s", err.Error()) 89 | } 90 | if !h.Check([]byte("345jkhrlfkwhpf98q3u4pljhqenwlfkjashd"), hashedPW) { 91 | t.Errorf("Expected Check to return true for valid password and generated hash") 92 | } 93 | if h.Check([]byte("345jkhrlfkw_____________qenwlfkjashd"), hashedPW) { 94 | t.Errorf("Expected Check to return false for invalid password and generated hash") 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /hash.go: -------------------------------------------------------------------------------- 1 | // Package phpass Provides the ability to create and validate PHPass hashed 2 | // passwords. See http://www.openwall.com/phpass/ for more details. The code 3 | // here is more or less a direct port of the PHP implimentation found inside 4 | // the official download. Or will be once it has been completed. 5 | // 6 | // The code as it stands is not 100% complete in that it does not work with 7 | // all of the options that can/should be speficied. It does work with the 8 | // default options, and is compatible with WordPress's use of PHPass for 9 | // hasing passwords in the database 10 | package phpass 11 | 12 | // BUG(): Non-Default configurations are not supported at this time. Obviously 13 | // they are planned, but have not been gotten around to yet. 14 | 15 | import ( 16 | "bytes" 17 | "crypto/md5" 18 | "crypto/rand" 19 | "errors" 20 | "hash" 21 | "strings" 22 | ) 23 | 24 | var ( 25 | errBadID = errors.New("bad ID") 26 | errBadCount = errors.New("bad count") 27 | ) 28 | 29 | // Hash allows you to hash, and validate PHPass hashed passwords. The Hash 30 | // structure is not thread safe. If you plan to use a single hasher you'll want 31 | // to synchronize with your own syc.Mutex 32 | type Hash struct { 33 | Config *Config 34 | MD5er hash.Hash 35 | } 36 | 37 | // Hash takes a returns a PHPass hash given the input password 38 | func (h *Hash) Hash(pw []byte) ([]byte, error) { 39 | // $random = $this->get_random_bytes(6); 40 | // $this->crypt_private($password, $this->gensalt_private($random)); 41 | return h.crypt(pw, h.salt()) 42 | } 43 | 44 | // Check validates the given password against the given hash, returning true if 45 | // they match, otherwise false 46 | func (h *Hash) Check(pw, pwhash []byte) bool { 47 | generated, err := h.crypt(pw, pwhash) 48 | if err != nil { 49 | return false 50 | } 51 | //if generated[0] == 42 { 52 | // generated = ?crypt?(password, hash) 53 | //} 54 | return bytes.Equal(generated, pwhash) 55 | } 56 | 57 | func (h *Hash) crypt(pw, pwhash []byte) ([]byte, error) { 58 | var rval []byte 59 | if bytes.Equal(pwhash[0:2], []byte("*0")) { 60 | rval = []byte("*1") 61 | } else { 62 | rval = []byte("*0") 63 | } 64 | 65 | var id = string(pwhash[0:3]) 66 | if id != "$P$" && id != "$H$" { 67 | return rval, errBadID 68 | } 69 | 70 | var count = uint(strings.IndexByte(h.Config.Itoa, pwhash[3])) 71 | if count < 7 || count > 30 { 72 | return rval, errBadCount 73 | } 74 | count = 1 << count 75 | 76 | var salt = pwhash[4:12] 77 | h.MD5er.Reset() 78 | h.MD5er.Write(salt) 79 | h.MD5er.Write(pw) 80 | var checksum = h.MD5er.Sum(nil) 81 | for i := uint(0); i < count; i++ { 82 | h.MD5er.Reset() 83 | h.MD5er.Write(checksum) 84 | h.MD5er.Write(pw) 85 | checksum = h.MD5er.Sum(nil) 86 | } 87 | rval = []byte{} 88 | rval = append(rval, pwhash[:12]...) 89 | rval = append(rval, h.encode(checksum, 16)...) 90 | return rval, nil 91 | } 92 | 93 | func (h *Hash) encode(input []byte, count int) []byte { 94 | var rval = []byte{} 95 | var i = 0 96 | for { 97 | value := int(input[i]) 98 | i++ 99 | rval = append(rval, h.Config.Itoa[(value&0x3f)]) 100 | 101 | if i < count { 102 | value = value | int(input[i])<<8 103 | } 104 | rval = append(rval, h.Config.Itoa[((value>>6)&0x3f)]) 105 | 106 | if i >= count { 107 | break 108 | } 109 | i++ 110 | 111 | if i < count { 112 | value = value | int(input[i])<<16 113 | } 114 | rval = append(rval, h.Config.Itoa[((value>>12)&0x3f)]) 115 | 116 | if i >= count { 117 | break 118 | } 119 | i++ 120 | 121 | rval = append(rval, h.Config.Itoa[((value>>18)&0x3f)]) 122 | if i >= count { 123 | break 124 | } 125 | } 126 | return rval 127 | } 128 | 129 | func (h *Hash) salt() []byte { 130 | var input = make([]byte, 6) 131 | if _, err := rand.Read(input); err != nil { 132 | panic(err) 133 | } 134 | var i = h.Config.Count + 5 135 | if i > 30 { 136 | i = 30 137 | } 138 | return append([]byte("$P$"), append([]byte{h.Config.Itoa[i]}, h.encode(input, 6)...)...) 139 | } 140 | 141 | // New returns a new Hash structure against which you can make Hash() and 142 | // Check() calls for creating and validating PHPass hashed passwords. If 143 | // you pass nil as the config a default config will be provided for you. 144 | func New(config *Config) *Hash { 145 | if config == nil { 146 | config = NewConfig() 147 | } 148 | if config.Count < 4 || config.Count > 31 { 149 | config.Count = 8 150 | } 151 | return &Hash{ 152 | Config: config, 153 | MD5er: md5.New(), 154 | } 155 | } 156 | --------------------------------------------------------------------------------