First commit

This commit is contained in:
GRMrGecko 2024-09-07 18:39:33 -05:00
commit dc9b8fee92
17 changed files with 1794 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
test
.kdev4
.vscode

19
LICENSE.txt Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2024 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

55
README.md Normal file
View File

@ -0,0 +1,55 @@
# go-passwd
This is a libxcrypt compatible password hashing library for the Go language. The passwords generated with this library is fully compatible with libxcrypt which can be used to generate or test passwords in use by software such as MySQL or the Linux shadow system.
## Install
```
go get github.com/GRMrGecko/go-passwd
```
## Example
```go
package main
import (
"github.com/GRMrGecko/go-passwd"
"log"
)
func main() {
result, err := passwd.CheckPassword([]byte("$y$j9T$Q3N1jZa3Cp.yNINNDt5dDgYkHU7k$9o7WJJB5F.tTEhZdz6T6LMWY/0C3JkhvmcNyUPvUBlC"), []byte("Test"))
if err != nil {
log.Fatalln(err)
}
if result {
log.Println("Password confirmed, saving new password.")
pw := passwd.NewSHA512CryptPasswd()
hash, err := pw.HashPassword([]byte("New Password!!!"))
if err != nil {
log.Fatalln(err)
}
log.Println("The new password hash to save is:", string(hash))
}
}
```
Example output:
```
$ ./test
2024/09/07 18:42:35 Password confirmed, saving new password.
2024/09/07 18:42:35 The new password hash to save is: $6$4Eu/l5e.otcRj0rJ$YAlwxJD9pZY9.Z2TjseCbkXiUIrFU2AXh9DPEm5Z1SagxP..xaQCsz7jAgfW4nmUbLh.o23pEZGvvxPCLltf11
```
## Docs
[https://pkg.go.dev/github.com/GRMrGecko/go-passwd](https://pkg.go.dev/github.com/GRMrGecko/go-passwd)
## Known issues
- It is possible to generate password hashes that are incompatible with libxcrypt by setting a large round count. This may be mitigated in the future by adding an option to disable compatibility and otherwise require compatible parameters to be set.
- The bcrypt hashing algorithms are not implemented yet, it may be implemented in the near futre.

11
go.mod Normal file
View File

@ -0,0 +1,11 @@
module github.com/GRMrGecko/go-passwd
go 1.22.4
toolchain go1.23.1
require (
github.com/openwall/yescrypt-go v1.0.0
github.com/pedroalbanese/gogost v0.0.0-20240430171730-f95129c7a5af
golang.org/x/crypto v0.25.0
)

6
go.sum Normal file
View File

@ -0,0 +1,6 @@
github.com/openwall/yescrypt-go v1.0.0 h1:jsGk48zkFvtUjGVOhYPGh+CS595JmTRcKnpggK2AON4=
github.com/openwall/yescrypt-go v1.0.0/go.mod h1:e6CWtFizUEOUttaOjeVMiv1lJaJie3mfOtLJ9CCD6sA=
github.com/pedroalbanese/gogost v0.0.0-20240430171730-f95129c7a5af h1:8jbTN9e84FOzAJtCPdy/NEz8983YdD7nqTBMQlTRP4w=
github.com/pedroalbanese/gogost v0.0.0-20240430171730-f95129c7a5af/go.mod h1:A4x4C7B6z2POO1x5CZzKXZVCOFPfjzxxVUbWl2Thhp0=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=

81
gost_yes_crypt.go Normal file
View File

@ -0,0 +1,81 @@
package passwd
import (
"crypto/hmac"
"fmt"
"github.com/openwall/yescrypt-go"
"github.com/pedroalbanese/gogost/gost34112012256"
)
type GostYesCrypt struct {
Passwd
}
// Make an MD5Crypt password instance.
func NewGostYesCryptPasswd() PasswdInterface {
m := new(GostYesCrypt)
m.Magic = GOST_YES_CRYPT_MAGIC
m.SetSCryptParams(11, 31)
m.SaltLength = 22
// Set the interface to allow parents to call overriden functions.
m.i = m
return m
}
// Sets the SCrypt params using integers.
func (a *GostYesCrypt) SetSCryptParams(N, r int) (err error) {
Nval, err := IToA64(N)
if err != nil {
return
}
rval, err := IToA64(r)
if err != nil {
return
}
a.Params = fmt.Sprintf("j%c%c", Nval, rval)
return
}
// Decode SCrypt params.
func (a *GostYesCrypt) DecodeSCriptParams() (N, r int) {
b64 := []byte(a.Params)
if len(b64) != 3 {
return
}
N = AToI64(b64[1])
r = AToI64(b64[2])
return
}
// Hash a password with salt using gost yes crypt standard.
func (a *GostYesCrypt) Hash(password []byte, salt []byte) (hash []byte, err error) {
output := []byte(fmt.Sprintf("%s%s$%s", YES_CRYPT_MAGIC, a.Params, salt))
yescryptHash, err := yescrypt.Hash(password, output)
if err != nil {
return
}
bytes := SCryptBase64Decode(yescryptHash[len(output)+1:])
h := gost34112012256.New()
h.Write(password)
hmacKey := h.Sum(nil)
settings := []byte(fmt.Sprintf("%s%s$%s", a.Magic, a.Params, salt))
hm := hmac.New(gost34112012256.New, hmacKey)
hm.Write(settings)
hmacKey = hm.Sum(nil)
hm = hmac.New(gost34112012256.New, hmacKey)
hm.Write(bytes)
b64 := SCryptBase64Encode(hm.Sum(nil))
hash = append(settings, '$')
hash = append(hash, b64...)
return
}
// Override the passwd hash with salt function to hash with gost yes crypt.
func (a *GostYesCrypt) HashPasswordWithSalt(password []byte, salt []byte) (hash []byte, err error) {
hash, err = a.Hash(password, salt)
return
}

110
md5_crypt.go Normal file
View File

@ -0,0 +1,110 @@
package passwd
import "crypto/md5"
type MD5Crypt struct {
Passwd
}
// Make an MD5Crypt password instance.
func NewMD5CryptPasswd() PasswdInterface {
m := new(MD5Crypt)
m.Magic = MD5_CRYPT_MAGIC
// Max of 8 characters in the salt per the spec.
m.SaltLength = 8
// Set the interface to allow parents to call overriden functions.
m.i = m
return m
}
// Hash a password with salt using MD5 crypt standard.
func (a *MD5Crypt) Hash(password []byte, salt []byte) (hash []byte) {
magic := []byte(a.Magic)
// Salt should be a maximum of 8 characters.
if len(salt) > 8 {
salt = salt[0:8]
}
// Encode pass, salt, pass hash to feed into the next hash.
h := md5.New()
h.Write(password)
h.Write(salt)
h.Write(password)
result := h.Sum(nil)
// Encode pass, magic, salt, and some extra stuff to help limit brute force attacks.
h.Reset()
h.Write(password)
h.Write(magic)
h.Write(salt)
// Append characters from the prior encode until it equals the length of the password.
HashBlockRecycle(h, result, len(password))
// For compatibility: Every 1 bit of the password length, append null.
// Every 0 bit of the password length, append the first character of the
// password. Yes, this is a weird thing. But think, weird thing equals
// harder for brute forcers.
result = []byte{'\000'}
var cnt int
for cnt = len(password); cnt > 0; cnt >>= 1 {
if cnt&1 != 0 {
h.Write(result[:1])
} else {
h.Write(password[:1])
}
}
// Compute the hash to feed into the 1000 iterations.
result = h.Sum(nil)
// For 1000 iterations, make a new hash feeding the prior hash,
// password, and salt at different points. This is designed to
// limit brute force attempts, although todays tech is fast.
for cnt = 0; cnt < 1000; cnt++ {
h.Reset()
// Add pass or prior result depending on bit of current iteration.
if cnt&1 != 0 {
h.Write(password)
} else {
h.Write(result)
}
// Add salt for numbers not divisible by 3.
if cnt%3 != 0 {
h.Write(salt)
}
// Add password for numbers not divisible by 7.
if cnt%7 != 0 {
h.Write(password)
}
// Add the reverse of the above pass or prior result.
// This ensures we at a minimum have both the password,
// and the prior result in the hash calculation for the round.
if cnt&1 != 0 {
h.Write(result)
} else {
h.Write(password)
}
// Compute hash for next round.
result = h.Sum(nil)
}
// Create hash with result.
b64 := MD5Base64Encode(result)
hash = append(magic, salt...)
hash = append(hash, '$')
hash = append(hash, b64...)
return
}
// Override the passwd hash with salt function to hash with MD5 crypt.
func (a *MD5Crypt) HashPasswordWithSalt(password []byte, salt []byte) (hash []byte, err error) {
hash = a.Hash(password, salt)
return
}

84
nt_hash.go Normal file
View File

@ -0,0 +1,84 @@
package passwd
import (
"encoding/binary"
"encoding/hex"
"unicode/utf16"
"unicode/utf8"
"golang.org/x/crypto/md4"
)
type NTHash struct {
Passwd
}
// Make NTHash password interface.
func NewNTPasswd() PasswdInterface {
m := new(NTHash)
m.Magic = NT_HASH_MAGIC
// NT hashes has no salt, so we disable it.
m.SaltLength = -1
// Set the interface to allow parents to call overriden functions.
m.i = m
return m
}
// Encode UTF-8 bytes to UCS-2LE bytes.
// The NT hash uses UCS-2LE, so we need to convert for compatibility.
func (a *NTHash) UTF8ToUCS2LE(src []byte) []byte {
// If there is no source data, return nil.
if len(src) == 0 {
return nil
}
// Convert bytes to UTF-8 runes.
var runes []rune
for len(src) > 0 {
r, size := utf8.DecodeRune(src)
runes = append(runes, r)
src = src[size:]
}
// Re-encode UTF-8 to UTF-16.
u := utf16.Encode(runes)
// Setup new byte array to match length of UCS-2LE.
dst := make([]byte, len(u)*2)
// Index for inserting new bytes.
i := 0
// Convert each UTF-16 byte to UCS-2LE.
for _, r := range u {
binary.LittleEndian.PutUint16(dst[i:], r)
i += 2
}
return dst
}
// Hash an NT compatible hash.
func (a *NTHash) Hash(password []byte) (hash []byte) {
// Convert to UCS-2.
ucsPw := a.UTF8ToUCS2LE(password)
// Encoe MD4 hash with UCS-2LE bytes.
h := md4.New()
h.Write(ucsPw)
buf := h.Sum(nil)
// Hex encode MD4 hash.
dst := make([]byte, hex.EncodedLen(len(buf)))
hex.Encode(dst, buf)
// Make crypt compatible hash from encoded hash.
hash = append([]byte(a.Magic), '$')
hash = append(hash, dst...)
return
}
// Override the hash with salt function with one that encodes the NT hash, ignoring the salt.
func (a *NTHash) HashPasswordWithSalt(password []byte, salt []byte) (hash []byte, err error) {
hash = a.Hash(password)
return
}

325
passwd.go Normal file
View File

@ -0,0 +1,325 @@
package passwd
import (
"bytes"
"crypto/rand"
"errors"
"fmt"
"strconv"
"strings"
)
const (
SHA1_CRYPT_MAGIC = "$sha1$"
SHA1_SIZE = 20
SUN_MD5_MAGIC = "$md5"
MD5_CRYPT_MAGIC = "$1$"
MD5_SIZE = 16
NT_HASH_MAGIC = "$3$"
MD4_SIZE = 16
SHA256_CRYPT_MAGIC = "$5$"
SHA256_SIZE = 32
SHA512_CRYPT_MAGIC = "$6$"
SHA512_SIZE = 64
S_CRYPT_MAGIC = "$7$"
YES_CRYPT_MAGIC = "$y$"
GOST_YES_CRYPT_MAGIC = "$gy$"
)
// Standard protocol for working with all hash algorithms.
type PasswdInterface interface {
SetParams(p string)
SetSalt(s []byte)
GenerateSalt() ([]byte, error)
HashPassword(password []byte) (hash []byte, err error)
HashPasswordWithSalt(password []byte, salt []byte) (hash []byte, err error)
}
// Base structure.
type Passwd struct {
Magic string
Params string
SaltLength int
Salt []byte
i PasswdInterface
}
// Get a password interface based on hash settings string.
func NewPasswd(settings string) (PasswdInterface, error) {
// SHA1 $sha1$<iterations>$<salt>[$]
if strings.HasPrefix(settings, SHA1_CRYPT_MAGIC) {
// Split by $ to get options.
s := strings.Split(settings[len(SHA1_CRYPT_MAGIC):], "$")
// If less than 2 options, this is not a valid setting.
if len(s) < 2 {
return nil, errors.New("Too few parameters for SHA1 hash")
}
// Confirm that the iterations can be parsed.
iterations, err := strconv.ParseUint(s[0], 10, 64)
if err != nil {
return nil, err
}
// Make the interface.
passwd := NewSHA1Passwd()
passwd.SetParams(strconv.FormatUint(iterations, 10))
passwd.SetSalt([]byte(s[1]))
return passwd, nil
}
// Sun MD5 $md5[,rounds=<iterations>]$<salt>[$]
if strings.HasPrefix(settings, SUN_MD5_MAGIC) {
s := strings.Split(settings[len(SUN_MD5_MAGIC):], "$")
// If less than 2 options, this is not a valid setting.
if len(s) < 2 {
return nil, errors.New("Too few parameters for Sun MD5 hash")
}
// Parse iterations from parameter.
if s[0] != "" && s[0][0] == ',' {
s[0] = s[0][1:]
}
var iterations uint64
if s[0] != "" {
_, err := fmt.Sscanf(s[0], "rounds=%d", &iterations)
if err != nil {
return nil, err
}
}
// Make the interface.
passwd := NewSunMD5Passwd()
passwd.SetParams(s[0])
passwd.SetSalt([]byte(s[1]))
return passwd, nil
}
// MD5 $1$<salt>[$]
if strings.HasPrefix(settings, MD5_CRYPT_MAGIC) {
s := strings.Split(settings[len(MD5_CRYPT_MAGIC):], "$")
// If less than 2 options, this is not a valid setting.
if len(s) < 1 {
return nil, errors.New("Too few parameters for MD5 hash")
}
// Make the interface.
passwd := NewMD5CryptPasswd()
passwd.SetSalt([]byte(s[0]))
return passwd, nil
}
// NT $3$[$]
if strings.HasPrefix(settings, NT_HASH_MAGIC) {
// Make the interface.
passwd := NewNTPasswd()
return passwd, nil
}
// SHA256 $5$[rounds=<iterations>$]<salt>[$]
if strings.HasPrefix(settings, SHA256_CRYPT_MAGIC) {
s := strings.Split(settings[len(SHA256_CRYPT_MAGIC):], "$")
// If less than 2 options, this is not a valid setting.
if len(s) < 1 {
return nil, errors.New("Too few parameters for SHA256 hash")
}
// If rounds set, parse it.
var iterations uint64
if strings.HasPrefix(s[0], "rounds=") {
_, err := fmt.Sscanf(s[0], "rounds=%d", &iterations)
if err != nil {
return nil, err
}
if len(s) < 2 {
return nil, errors.New("Too few parameters for SHA256 hash")
}
s[0] = s[1]
}
// Make the interface.
passwd := NewSHA256CryptPasswd()
if iterations != 0 {
passwd.SetParams(fmt.Sprintf("rounds=%d", iterations))
}
passwd.SetSalt([]byte(s[0]))
return passwd, nil
}
// SHA512 $6$[rounds=<iterations>$]<salt>[$]
if strings.HasPrefix(settings, SHA512_CRYPT_MAGIC) {
s := strings.Split(settings[len(SHA512_CRYPT_MAGIC):], "$")
// If less than 2 options, this is not a valid setting.
if len(s) < 1 {
return nil, errors.New("Too few parameters for SHA512 hash")
}
// If rounds set, parse it.
var iterations uint64
if strings.HasPrefix(s[0], "rounds=") {
_, err := fmt.Sscanf(s[0], "rounds=%d", &iterations)
if err != nil {
return nil, err
}
if len(s) < 2 {
return nil, errors.New("Too few parameters for SHA512 hash")
}
s[0] = s[1]
}
// Make the interface.
passwd := NewSHA512CryptPasswd()
if iterations != 0 {
passwd.SetParams(fmt.Sprintf("rounds=%d", iterations))
}
passwd.SetSalt([]byte(s[0]))
return passwd, nil
}
// SCrypt $7$<N><r><p><salt>[$]
if strings.HasPrefix(settings, S_CRYPT_MAGIC) {
s := strings.Split(settings[len(S_CRYPT_MAGIC):], "$")
// If less than 2 options, this is not a valid setting.
if len(s) < 1 {
return nil, errors.New("Too few parameters for SCrypt hash")
}
if len(s[0]) < 12 {
return nil, errors.New("Too few characters in salt for SCrypt")
}
params := s[0][:11]
salt := s[0][11:]
// Make the interface.
passwd := NewSCryptPasswd()
passwd.SetParams(params)
passwd.SetSalt([]byte(salt))
return passwd, nil
}
// Yes Crypt $y$j<N><r>$<salt>[$]
if strings.HasPrefix(settings, YES_CRYPT_MAGIC) {
s := strings.Split(settings[len(YES_CRYPT_MAGIC):], "$")
// If less than 2 options, this is not a valid setting.
if len(s) < 2 {
return nil, errors.New("Too few parameters for Yes Crypt hash")
}
if len(s[0]) != 3 {
return nil, errors.New("Invalid length for Yes Crypt parameters")
}
// Make the interface.
passwd := NewYesCryptPasswd()
passwd.SetParams(s[0])
passwd.SetSalt([]byte(s[1]))
return passwd, nil
}
// Gost Yes Crypt $gy$j<N><r>$<salt>[$]
if strings.HasPrefix(settings, GOST_YES_CRYPT_MAGIC) {
s := strings.Split(settings[len(GOST_YES_CRYPT_MAGIC):], "$")
// If less than 2 options, this is not a valid setting.
if len(s) < 2 {
return nil, errors.New("Too few parameters for Gost Yes Crypt hash")
}
if len(s[0]) != 3 {
return nil, errors.New("Invalid length for Gost Yes Crypt parameters")
}
// Make the interface.
passwd := NewGostYesCryptPasswd()
passwd.SetParams(s[0])
passwd.SetSalt([]byte(s[1]))
return passwd, nil
}
// End of the line.
return nil, errors.New("No valid matching algorithm")
}
// Check a password hash against a password.
func CheckPassword(hash []byte, password []byte) (bool, error) {
passwd, err := NewPasswd(string(hash))
if err != nil {
return false, err
}
newHash, err := passwd.HashPassword(password)
if err != nil {
return false, err
}
if bytes.Equal(hash, newHash) {
return true, nil
}
return false, nil
}
// Used internally for salt generation.
func generateRandomBytes(n uint) ([]byte, error) {
b := make([]byte, n)
_, err := rand.Read(b)
if err != nil {
return nil, err
}
return b, nil
}
// Set parameters for password generation. Typically used for iterations, but also used for yes crypt configuration.
func (a *Passwd) SetParams(p string) {
a.Params = p
}
// Set a salt for hashing, an empty salt will generate a new one.
func (a *Passwd) SetSalt(s []byte) {
a.Salt = s
}
// Generate a salt based on configs for this paassword algorithm.
func (a *Passwd) GenerateSalt() ([]byte, error) {
var salt []byte
if a.SaltLength > -1 {
if a.SaltLength == 0 {
a.SaltLength = 16
}
rawSalt, err := generateRandomBytes(uint(a.SaltLength))
if err != nil {
return nil, err
}
salt = Base64Encode(rawSalt)
}
return salt, nil
}
// Hash a password.
func (a *Passwd) HashPassword(password []byte) (hash []byte, err error) {
if len(a.Salt) == 0 {
salt, err := a.GenerateSalt()
if err != nil {
return nil, err
}
a.Salt = salt
}
if a.i != nil {
hash, err = a.i.HashPasswordWithSalt(password, a.Salt)
} else {
hash, err = a.HashPasswordWithSalt(password, a.Salt)
}
return
}
// Hash a password with a custom salt.
func (a *Passwd) HashPasswordWithSalt(password []byte, salt []byte) (hash []byte, err error) {
err = errors.New("hash algorithm is not implemented")
return
}

162
passwd_test.go Normal file
View File

@ -0,0 +1,162 @@
package passwd
import (
"fmt"
"testing"
)
func TestPasswd(t *testing.T) {
password := []byte("Test")
var res bool
var err error
// Confirm password hashes conform to libcrypt standards.
res, err = CheckPassword([]byte("$sha1$245081$NabW/sfk3ZVVQc4BnZ/3$YoV1Iva6GK4tkxwahBmyH0TRCwBO"), password)
if err != nil {
t.Fatalf("sha1 error: %s", err)
}
if !res {
t.Fatalf("Password check for sha1 failed")
}
res, err = CheckPassword([]byte("$md5$lORrojKC$$RD9p64URLn3Wkv4Wa2xOW0"), password)
if err != nil {
t.Fatalf("sun md5 error: %s", err)
}
if !res {
t.Fatalf("Password check for sun md5 failed")
}
res, err = CheckPassword([]byte("$md5,rounds=53125$qrDebYUd$$3pJWS.a6VTC/cGehIfQb30"), password)
if err != nil {
t.Fatalf("sun md5 with rounds error: %s", err)
}
if !res {
t.Fatalf("Password check for sun md5 with rounds failed")
}
res, err = CheckPassword([]byte("$1$wuIXYcHV$1ufSGHoD0EkWPr75i52ST/"), password)
if err != nil {
t.Fatalf("md5 error: %s", err)
}
if !res {
t.Fatalf("Password check for md5 failed")
}
res, err = CheckPassword([]byte("$3$$4a1fab8f6b5441e0493dc7d41304bfb6"), password)
if err != nil {
t.Fatalf("nt error: %s", err)
}
if !res {
t.Fatalf("Password check for nt failed")
}
res, err = CheckPassword([]byte("$5$AsETvlsIoaTP3w6G$OZY9mWRFXR9Pz0Xv1pS2TS/QCpxECLEG/dru/Y.nba/"), password)
if err != nil {
t.Fatalf("sha256 error: %s", err)
}
if !res {
t.Fatalf("Password check for sha256 failed")
}
res, err = CheckPassword([]byte("$5$rounds=243006$oCvhLw/Nn9HuQIm4$VPKzWx9t.NHgmNpVHeSpzQ5y01z4BE14J.bvG8g2yi."), password)
if err != nil {
t.Fatalf("sha256 with rounds error: %s", err)
}
if !res {
t.Fatalf("Password check for sha256 with rounds failed")
}
res, err = CheckPassword([]byte("$6$zt7D9I3Uu.EhrzEv$j50OCJ3oNdO2Ee7RE9XTDF7dhvrgRwc9NmjJUouk7czn4JTc/A6qLJIT1pMk7FUlTCYCLl6uBHm5NoEboAzIo0"), password)
if err != nil {
t.Fatalf("sha512 error: %s", err)
}
if !res {
t.Fatalf("Password check for sha512 failed")
}
res, err = CheckPassword([]byte("$6$rounds=523044$.zMtRwbPP2sDg5a5$YgKUnqEda6wxkvDMbJoNjNBiFNpX7nP/uDFV3jV4ngmrXlFBua3n8oIi5St/Re8H3WOksLaody3eAhaGtAN0c/"), password)
if err != nil {
t.Fatalf("sha512 with rounds error: %s", err)
}
if !res {
t.Fatalf("Password check for sha512 with rounds failed")
}
res, err = CheckPassword([]byte("$7$CU..../....PpL3ULxY5DvYyvasS/a4a0$jqgg90svZLt5KQqFTwegHSn1pXU.aKDavZ3Eq8t2wx9"), password)
if err != nil {
t.Fatalf("scrypt error: %s", err)
}
if !res {
t.Fatalf("Password check for scrypt failed")
}
res, err = CheckPassword([]byte("$y$j9T$G/uoZu1orhwOE/lUtohEa.$SMu/wxtyhBLa5xeRLVnznBx5vE0/VxY7rJZlQX27N84"), password)
if err != nil {
t.Fatalf("yes crypt error: %s", err)
}
if !res {
t.Fatalf("Password check for yes crypt failed")
}
res, err = CheckPassword([]byte("$gy$j9T$etkZHzB483TIuw/58Df.N/$7DjHx/8jx.E/VLdyzMIIOJULHoZJ1PNlFl71KXaf0s7"), password)
if err != nil {
t.Fatalf("gost yes crypt error: %s", err)
}
if !res {
t.Fatalf("Password check for gost yes crypt failed")
}
// Confirm new password generation works.
var passwd PasswdInterface
var hash []byte
passwd = NewSHA1Passwd()
hash, err = passwd.HashPassword(password)
if err != nil {
t.Fatalf("sha1 error: %s", err)
}
fmt.Println("sha1:", string(hash))
passwd = NewSunMD5Passwd()
hash, err = passwd.HashPassword(password)
if err != nil {
t.Fatalf("sun md5 error: %s", err)
}
fmt.Println("sun md5:", string(hash))
passwd = NewSHA256CryptPasswd()
hash, err = passwd.HashPassword(password)
if err != nil {
t.Fatalf("sha256 error: %s", err)
}
fmt.Println("sha256:", string(hash))
passwd = NewSHA512CryptPasswd()
hash, err = passwd.HashPassword(password)
if err != nil {
t.Fatalf("sha512 error: %s", err)
}
fmt.Println("sha512:", string(hash))
passwd = NewSCryptPasswd()
hash, err = passwd.HashPassword(password)
if err != nil {
t.Fatalf("scrypt error: %s", err)
}
fmt.Println("scrypt:", string(hash))
passwd = NewYesCryptPasswd()
hash, err = passwd.HashPassword(password)
if err != nil {
t.Fatalf("yes crypt error: %s", err)
}
fmt.Println("yes crypt:", string(hash))
passwd = NewGostYesCryptPasswd()
hash, err = passwd.HashPassword(password)
if err != nil {
t.Fatalf("gost yes crypterror: %s", err)
}
fmt.Println("gost yes crypt:", string(hash))
}

70
pbkdf1_sha1_crypt.go Normal file
View File

@ -0,0 +1,70 @@
package passwd
import (
"crypto/hmac"
"crypto/sha1"
"fmt"
"strconv"
)
type SHA1Crypt struct {
Passwd
}
func NewSHA1Passwd() PasswdInterface {
m := new(SHA1Crypt)
m.Magic = SHA1_CRYPT_MAGIC
m.Params = "262144"
m.i = m
return m
}
// PBKDF1 with SHA1 crypt algorithm.
func (a *SHA1Crypt) Hash(password []byte, salt []byte, iterations uint64) (hash []byte) {
// We store the magic bytes as a string as we use sprintf to
// encode the outputs and easily translate the iterations
// from an uint64 to a string.
magic := a.Magic
// The first bit we encode into the hmac is the salt,
// magic string, and iterations of hmac rounds.
output := fmt.Sprintf("%s%s%d", salt, magic, iterations)
// Setup hmac with the password as the key.
hm := hmac.New(sha1.New, password)
// Write the salt and parameters to the hmac.
hm.Write([]byte(output))
// Get the first sum for the iterrations.
buf := hm.Sum(nil)
// Iterate the hmac to the specified number of iterations.
for i := uint64(1); i < iterations; i++ {
// Setup the hmac for this iteration.
hm.Reset()
// Feed back in the buffer from the last iteration.
hm.Write(buf)
// Get the buffer from this iteration.
buf = hm.Sum(nil)
}
// Create hash with result.
b64 := Base64Encode(buf)
hash = []byte(fmt.Sprintf("%s%d$%s$", magic, iterations, salt))
hash = append(hash, b64...)
return
}
// Override the hash with salt function to encode PBKDF1 with SHA1 hash.
func (a *SHA1Crypt) HashPasswordWithSalt(password []byte, salt []byte) (hash []byte, err error) {
iterations, err := strconv.ParseUint(a.Params, 10, 64)
if err != nil {
return nil, err
}
hash = a.Hash(password, salt, iterations)
return
}

61
s_crypt.go Normal file
View File

@ -0,0 +1,61 @@
package passwd
import (
"fmt"
"github.com/openwall/yescrypt-go"
)
type SCrypt struct {
Passwd
}
// Make an MD5Crypt password instance.
func NewSCryptPasswd() PasswdInterface {
m := new(SCrypt)
m.Magic = S_CRYPT_MAGIC
m.SetSCryptParams(14, 32, 1)
m.SaltLength = 22
// Set the interface to allow parents to call overriden functions.
m.i = m
return m
}
// Sets the SCrypt params using integers.
func (a *SCrypt) SetSCryptParams(N, r, p int) (err error) {
var b64 []byte
b64 = append(b64, iota64Encoding[N])
b64 = append(b64, Base64Uint32Encode(uint32(r), 30)...)
b64 = append(b64, Base64Uint32Encode(uint32(p), 30)...)
a.Params = string(b64)
return
}
// Decode SCrypt params.
func (a *SCrypt) DecodeSCriptParams() (N, r, p int) {
b64 := []byte(a.Params)
if len(b64) != 11 {
return
}
N = AToI64(b64[0])
r = int(Base64Uint32Decode(b64[1:6], 30))
p = int(Base64Uint32Decode(b64[6:11], 30))
return
}
// Hash a password with salt using scrypt standard.
func (a *SCrypt) Hash(password []byte, salt []byte) (hash []byte, err error) {
N, r, p := a.DecodeSCriptParams()
scryptHash, err := yescrypt.ScryptKey(password, salt, 1<<N, r, p, 32)
b64 := SCryptBase64Encode(scryptHash)
hash = []byte(fmt.Sprintf("%s%s%s$", a.Magic, a.Params, salt))
hash = append(hash, b64...)
return
}
// Override the passwd hash with salt function to hash with scrypt.
func (a *SCrypt) HashPasswordWithSalt(password []byte, salt []byte) (hash []byte, err error) {
hash, err = a.Hash(password, salt)
return
}

141
sha256_crypt.go Normal file
View File

@ -0,0 +1,141 @@
package passwd
import (
"crypto/sha256"
"fmt"
)
type SHA256Crypt struct {
Passwd
}
// Make an MD5Crypt password instance.
func NewSHA256CryptPasswd() PasswdInterface {
m := new(SHA256Crypt)
m.Magic = SHA256_CRYPT_MAGIC
// Set the interface to allow parents to call overriden functions.
m.i = m
return m
}
// Hash a password with salt using SHA256 crypt standard.
func (a *SHA256Crypt) Hash(password []byte, salt []byte, iterations uint64) (hash []byte) {
// Salt should be a maximum of 16 characters.
if len(salt) > 16 {
salt = salt[0:16]
}
passwordLen := len(password)
saltLen := len(salt)
customIterations := true
if iterations == 0 {
customIterations = false
iterations = 5000
}
// Encode pass, salt, pass hash to feed into the next hash.
h := sha256.New()
h.Write(password)
h.Write(salt)
h.Write(password)
result := h.Sum(nil)
// Encod the password and salt, and recycle bytes from prior hash.
h.Reset()
h.Write(password)
h.Write(salt)
// Append characters from the prior encode until it equals the length of the password.
HashBlockRecycle(h, result, passwordLen)
// Alternate the prior encode with the password for the binary length of the password.
var cnt uint64
for cnt = uint64(passwordLen); cnt > 0; cnt >>= 1 {
if cnt&1 != 0 {
h.Write(result)
} else {
h.Write(password)
}
}
// Calculate sum for iterations.
result = h.Sum(nil)
// Calculate a hash of password added for each character of the password for recycling in iterations.
h.Reset()
for cnt = 0; cnt < uint64(passwordLen); cnt++ {
h.Write(password)
}
p_bytes := h.Sum(nil)
// For maximum salt size plus the integer representation of the first byte of the prior hash,
// write the entire salt to the hash for recycling in iterations.
h.Reset()
for cnt = 0; cnt < 16+uint64(result[0]); cnt++ {
h.Write(salt)
}
s_bytes := h.Sum(nil)
// For the defined number of interations, hash using bytes from
// the above password and salt hashes and prior hash iteration.
for cnt = 0; cnt < iterations; cnt++ {
h.Reset()
// Add pass or prior result depending on bit of current iteration.
if cnt&1 != 0 {
HashBlockRecycle(h, p_bytes, passwordLen)
} else {
h.Write(result)
}
// Add salt for numbers not divisible by 3.
if cnt%3 != 0 {
HashBlockRecycle(h, s_bytes, saltLen)
}
// Add password for numbers not divisible by 7.
if cnt%7 != 0 {
HashBlockRecycle(h, p_bytes, passwordLen)
}
// Add the reverse of the above pass or prior result.
// This ensures we at a minimum have both the password,
// and the prior result in the hash calculation for the round.
if cnt&1 != 0 {
h.Write(result)
} else {
HashBlockRecycle(h, p_bytes, passwordLen)
}
// Compute hash for next round.
result = h.Sum(nil)
}
output := fmt.Sprintf("%s%s$", a.Magic, salt)
if customIterations {
output = fmt.Sprintf("%srounds=%d$%s$", a.Magic, iterations, salt)
}
// Create hash with result.
b64 := Base64RotateEncode(result, false)
hash = []byte(output)
hash = append(hash, b64...)
return
}
// Override the passwd hash with salt function to hash with SHA256 crypt.
func (a *SHA256Crypt) HashPasswordWithSalt(password []byte, salt []byte) (hash []byte, err error) {
// Parse iterations from parameter.
var iterations uint64
if a.Params != "" {
_, err = fmt.Sscanf(a.Params, "rounds=%d", &iterations)
if err != nil {
return
}
}
// Compute hash.
hash = a.Hash(password, salt, iterations)
return
}

141
sha512_crypt.go Normal file
View File

@ -0,0 +1,141 @@
package passwd
import (
"crypto/sha512"
"fmt"
)
type SHA512Crypt struct {
Passwd
}
// Make an MD5Crypt password instance.
func NewSHA512CryptPasswd() PasswdInterface {
m := new(SHA512Crypt)
m.Magic = SHA512_CRYPT_MAGIC
// Set the interface to allow parents to call overriden functions.
m.i = m
return m
}
// Hash a password with salt using SHA512 crypt standard.
func (a *SHA512Crypt) Hash(password []byte, salt []byte, iterations uint64) (hash []byte) {
// Salt should be a maximum of 16 characters.
if len(salt) > 16 {
salt = salt[0:16]
}
passwordLen := len(password)
saltLen := len(salt)
customIterations := true
if iterations == 0 {
customIterations = false
iterations = 5000
}
// Encode pass, salt, pass hash to feed into the next hash.
h := sha512.New()
h.Write(password)
h.Write(salt)
h.Write(password)
result := h.Sum(nil)
// Encod the password and salt, and recycle bytes from prior hash.
h.Reset()
h.Write(password)
h.Write(salt)
// Append characters from the prior encode until it equals the length of the password.
HashBlockRecycle(h, result, passwordLen)
// Alternate the prior encode with the password for the binary length of the password.
var cnt uint64
for cnt = uint64(passwordLen); cnt > 0; cnt >>= 1 {
if cnt&1 != 0 {
h.Write(result)
} else {
h.Write(password)
}
}
// Calculate sum for iterations.
result = h.Sum(nil)
// Calculate a hash of password added for each character of the password for recycling in iterations.
h.Reset()
for cnt = 0; cnt < uint64(passwordLen); cnt++ {
h.Write(password)
}
p_bytes := h.Sum(nil)
// For maximum salt size plus the integer representation of the first byte of the prior hash,
// write the entire salt to the hash for recycling in iterations.
h.Reset()
for cnt = 0; cnt < 16+uint64(result[0]); cnt++ {
h.Write(salt)
}
s_bytes := h.Sum(nil)
// For the defined number of interations, hash using bytes from
// the above password and salt hashes and prior hash iteration.
for cnt = 0; cnt < iterations; cnt++ {
h.Reset()
// Add pass or prior result depending on bit of current iteration.
if cnt&1 != 0 {
HashBlockRecycle(h, p_bytes, passwordLen)
} else {
h.Write(result)
}
// Add salt for numbers not divisible by 3.
if cnt%3 != 0 {
HashBlockRecycle(h, s_bytes, saltLen)
}
// Add password for numbers not divisible by 7.
if cnt%7 != 0 {
HashBlockRecycle(h, p_bytes, passwordLen)
}
// Add the reverse of the above pass or prior result.
// This ensures we at a minimum have both the password,
// and the prior result in the hash calculation for the round.
if cnt&1 != 0 {
h.Write(result)
} else {
HashBlockRecycle(h, p_bytes, passwordLen)
}
// Compute hash for next round.
result = h.Sum(nil)
}
output := fmt.Sprintf("%s%s$", a.Magic, salt)
if customIterations {
output = fmt.Sprintf("%srounds=%d$%s$", a.Magic, iterations, salt)
}
// Create hash with result.
b64 := Base64RotateEncode(result, true)
hash = []byte(output)
hash = append(hash, b64...)
return
}
// Override the passwd hash with salt function to hash with SHA512 crypt.
func (a *SHA512Crypt) HashPasswordWithSalt(password []byte, salt []byte) (hash []byte, err error) {
// Parse iterations from parameter.
var iterations uint64
if a.Params != "" {
_, err = fmt.Sscanf(a.Params, "rounds=%d", &iterations)
if err != nil {
return
}
}
// Compute hash.
hash = a.Hash(password, salt, iterations)
return
}

178
sun_md5.go Normal file
View File

@ -0,0 +1,178 @@
package passwd
import (
"crypto/md5"
"fmt"
"strconv"
)
type SunMD5 struct {
Passwd
}
// Make an MD5Crypt password instance.
func NewSunMD5Passwd() PasswdInterface {
m := new(SunMD5)
m.Magic = SUN_MD5_MAGIC
// Max of 8 characters in the salt per the spec.
m.SaltLength = 8
// Set the interface to allow parents to call overriden functions.
m.i = m
return m
}
/*
At each round of the algorithm, this string (including the trailing
NUL) may or may not be included in the input to MD5, depending on a
pseudorandom coin toss. It is Hamlet's famous soliloquy from the
play of the same name, which is in the public domain. Text from
<https://www.gutenberg.org/files/1524/old/2ws2610.tex> with double
blank lines replaced with `\n`. Note that more recent Project
Gutenberg editions of _Hamlet_ are punctuated differently.
*/
const hamlet_quotation string = "To be, or not to be,--that is the question:--\n" +
"Whether 'tis nobler in the mind to suffer\n" +
"The slings and arrows of outrageous fortune\n" +
"Or to take arms against a sea of troubles,\n" +
"And by opposing end them?--To die,--to sleep,--\n" +
"No more; and by a sleep to say we end\n" +
"The heartache, and the thousand natural shocks\n" +
"That flesh is heir to,--'tis a consummation\n" +
"Devoutly to be wish'd. To die,--to sleep;--\n" +
"To sleep! perchance to dream:--ay, there's the rub;\n" +
"For in that sleep of death what dreams may come,\n" +
"When we have shuffled off this mortal coil,\n" +
"Must give us pause: there's the respect\n" +
"That makes calamity of so long life;\n" +
"For who would bear the whips and scorns of time,\n" +
"The oppressor's wrong, the proud man's contumely,\n" +
"The pangs of despis'd love, the law's delay,\n" +
"The insolence of office, and the spurns\n" +
"That patient merit of the unworthy takes,\n" +
"When he himself might his quietus make\n" +
"With a bare bodkin? who would these fardels bear,\n" +
"To grunt and sweat under a weary life,\n" +
"But that the dread of something after death,--\n" +
"The undiscover'd country, from whose bourn\n" +
"No traveller returns,--puzzles the will,\n" +
"And makes us rather bear those ills we have\n" +
"Than fly to others that we know not of?\n" +
"Thus conscience does make cowards of us all;\n" +
"And thus the native hue of resolution\n" +
"Is sicklied o'er with the pale cast of thought;\n" +
"And enterprises of great pith and moment,\n" +
"With this regard, their currents turn awry,\n" +
"And lose the name of action.--Soft you now!\n" +
"The fair Ophelia!--Nymph, in thy orisons\n" +
"Be all my sins remember'd.\n\000"
func (a *SunMD5) get_nth_bit(digest []byte, n uint64) uint {
b := (n % 128) / 8
bit := (n % 128) % 8
output := digest[b] & (1 << bit)
if output == 0 {
return 0
}
return 1
}
func (s *SunMD5) MuffetCoinToss(digest []byte, iteration uint64) bool {
var x, y, a, b, r, v, i uint = 0, 0, 0, 0, 0, 0, 0
for ; i < 8; i++ {
a = uint(digest[(i+0)%16])
b = uint(digest[(i+3)%16])
r = a >> (b % 5)
v = uint(digest[r%16])
if (b & (1 << (a % 8))) != 0 {
v /= 2
}
x |= s.get_nth_bit(digest, uint64(v)) << i
a = uint(digest[(i+8)%16])
b = uint(digest[(i+11)%16])
r = a >> (b % 5)
v = uint(digest[r%16])
if (b & (1 << (a % 8))) != 0 {
v /= 2
}
y |= s.get_nth_bit(digest, uint64(v)) << i
}
if s.get_nth_bit(digest, iteration) == 1 {
x /= 2
}
if s.get_nth_bit(digest, iteration+64) == 1 {
y /= 2
}
output := s.get_nth_bit(digest, uint64(x)) ^ s.get_nth_bit(digest, uint64(y))
return output != 0
}
// Hash a password with salt using MD5 crypt standard.
func (a *SunMD5) Hash(password []byte, salt []byte, additionalIterations uint64) (hash []byte) {
// Salt should be a maximum of 8 characters.
if len(salt) > 8 {
salt = salt[0:8]
}
customIterations := false
var iterations uint64 = 4096
if additionalIterations != 0 {
customIterations = true
iterations += additionalIterations
}
quoteBytes := []byte(hamlet_quotation)
output := fmt.Sprintf("%s$%s$", a.Magic, salt)
if customIterations {
output = fmt.Sprintf("%s,rounds=%d$%s$", a.Magic, additionalIterations, salt)
}
// Encode pass, salt, pass hash to feed into the next hash.
h := md5.New()
h.Write(password)
h.Write([]byte(output))
result := h.Sum(nil)
// Perform iterations.
var cnt uint64
for cnt = 0; cnt < iterations; cnt++ {
h.Reset()
h.Write(result)
if a.MuffetCoinToss(result, cnt) {
h.Write(quoteBytes)
}
iterationS := strconv.FormatUint(cnt, 10)
h.Write([]byte(iterationS))
// Compute hash for next round.
result = h.Sum(nil)
}
// Create hash with result.
b64 := MD5Base64Encode(result)
hash = []byte(output)
hash = append(hash, '$')
hash = append(hash, b64...)
return
}
// Override the passwd hash with salt function to hash with Sun MD5.
func (a *SunMD5) HashPasswordWithSalt(password []byte, salt []byte) (hash []byte, err error) {
// Parse iterations from parameter.
var iterations uint64
if a.Params != "" {
_, err = fmt.Sscanf(a.Params, "rounds=%d", &iterations)
if err != nil {
return
}
}
// Compute hash.
hash = a.Hash(password, salt, iterations)
return
}

287
utils.go Normal file
View File

@ -0,0 +1,287 @@
package passwd
import (
"errors"
"hash"
)
// The non-standard alphabet for crypt base64 encoding.
const iota64Encoding = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
// Base64 to integer encoding table.
var atoi64Partial = [...]byte{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
64, 64, 64, 64, 64, 64, 64,
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,
64, 64, 64, 64, 64, 64,
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,
}
// Append base64 for provided uint.
func Base64Append(dst []byte, v uint, n int) []byte {
// Until we finish the number of rounds specified,
// loop and encode to base64.
for n > 0 {
// Append base64 of current bit.
dst = append(dst, iota64Encoding[v&0x3F])
// Jump to the next bit for encoding.
v >>= 6
n -= 1
}
// Return new byte array.
return dst
}
// Encode to crypt base64.
func Base64Encode(src []byte) []byte {
size := len(src)
var b64 []byte
var i int
for i = 0; i < size-3; i += 3 {
l := uint(src[i])<<16 |
uint(src[i+1])<<8 |
uint(src[i+2])
b64 = Base64Append(b64, l, 4)
}
var l uint
if size-i == 2 {
l = uint(src[i])<<16 |
uint(src[i+1])<<8 |
uint(src[0])
b64 = Base64Append(b64, l, 4)
}
return b64
}
// Takes a prior hash, and recycles bytes until the length provided is covered.
func HashBlockRecycle(h hash.Hash, block []byte, len int) {
size := h.BlockSize()
var cnt int
for cnt = len; cnt > size; cnt -= size {
h.Write(block)
}
// Remaining characters of the length, add sub slice here.
h.Write(block[:cnt])
}
// Convert base64 byte to integer value.
func AToI64(c byte) (val int) {
if c >= '.' && c <= 'z' {
val = int(atoi64Partial[c-'.'])
}
return
}
// Convert integer to bae64.
func IToA64(N int) (val byte, err error) {
if N > 64 {
err = errors.New("maximum itoa64 value is 64")
return
}
Nb := byte(N)
for i, b := range atoi64Partial {
if b == Nb {
val = '.' + byte(i)
}
}
return
}
// Get the power of 2 value.
func N2log2(N uint64) (N_log2 int) {
if N < 2 {
return
}
// Find power by bit shifting until shifting results in 0.
N_log2 = 2
for N>>N_log2 != 0 {
N_log2++
}
N_log2--
// If the result of removing one power level ends up resulting in a shift of not 1, return 0.
if N>>N_log2 != 1 {
return 0
}
return
}
// Encode uint32 into base64 at a fixed length.
func Base64Uint32Encode(src, srcbits uint32) (b64 []byte) {
var bits uint32
for bits = 0; bits < srcbits; bits += 6 {
b64 = append(b64, iota64Encoding[src&0x3F])
src >>= 6
}
if src != 0 {
return []byte{}
}
return
}
// Decode uint32 from base64 at a fixed length.
func Base64Uint32Decode(src []byte, dstbits uint32) (dst uint32) {
var bits uint32
var i uint
for bits = 0; bits < dstbits; bits += 6 {
c := AToI64(src[i])
i++
if c > 63 {
return 0
}
dst |= uint32(c << bits)
}
return
}
// Encode base64 in the format used for SCrypt hashes.
func SCryptBase64Encode(src []byte) []byte {
dst := make([]byte, 0, (len(src)*8+5)/6)
for i := 0; i < len(src); {
var val uint32
var bits int32
for ; bits < 24 && i < len(src); bits += 8 {
val |= uint32(src[i]) << bits
i++
}
for ; bits > 0; bits -= 6 {
dst = append(dst, iota64Encoding[val&0x3F])
val >>= 6
}
}
return dst
}
// Decode base64 in the format used for SCrypt hashes.
func SCryptBase64Decode(src []byte) []byte {
dst := make([]byte, 0, len(src)*3/4)
for i := 0; i < len(src); {
var val uint32
var bits int32
for ; bits < 24 && i < len(src); bits += 6 {
c := AToI64(src[i])
if c > 63 {
return nil
}
i++
val |= uint32(c) << bits
}
if bits < 12 {
return nil
}
for ; bits >= 8; bits -= 8 {
dst = append(dst, byte(val))
val >>= 8
}
if val != 0 {
return nil
}
}
return dst
}
// Encode MD5 result to MD5 crypt base64.
func MD5Base64Encode(src []byte) []byte {
// The way the crypt standards work with base64 encoding of MD5 is odd, because the
// last round rotates some of the hash bytes positions. So we must have this custom
// function just to encode MD5 hashes to base64.
var b64 []byte
l := uint(src[0])<<16 | uint(src[6])<<8 | uint(src[12])
b64 = Base64Append(b64, l, 4)
l = uint(src[1])<<16 | uint(src[7])<<8 | uint(src[13])
b64 = Base64Append(b64, l, 4)
l = uint(src[2])<<16 | uint(src[8])<<8 | uint(src[14])
b64 = Base64Append(b64, l, 4)
l = uint(src[3])<<16 | uint(src[9])<<8 | uint(src[15])
b64 = Base64Append(b64, l, 4)
l = uint(src[4])<<16 | uint(src[10])<<8 | uint(src[5])
b64 = Base64Append(b64, l, 4)
l = uint(src[11])
b64 = Base64Append(b64, l, 2)
return b64
}
// The crypt standard likes to rotate bits in base64,
// although it doesn't really do anything for brute force protection.
// This performs the rotation algorithm.
func Base64RotateEncode(src []byte, order bool) []byte {
var b64 []byte
l := len(src)
// Setup indexes.
// Used for the loop.
i := 0
// Index A.
ia := 0
// Index C, should be byte length divided by 3 to ensure we start a the 3rd point.
ib := l / 3
// Index C is just B doubled.
ic := ib + ib
// Index D is used to determine which iteration we're on.
id := 0
// Loop until we reach the last index that fits all 3 values to b64.
for ; i < l-3; i += 3 {
var a, b, c int
// Depending on index D, rotate the A, B, and C indexes.
// I am not sure why we are rotating byte input, it doesn't do anything
// with regards to brute force protection. Someone can just reverse the
// byte order to decode the base64 back down to binary, then use the binary
// for brute force attacks.
if order {
switch id % 3 {
case 0:
a = ia
b = ib
c = ic
case 1:
a = ib
b = ic
c = ia
case 2:
a = ic
b = ia
c = ib
}
} else {
switch id % 3 {
case 0:
a = ia
b = ib
c = ic
case 1:
a = ic
b = ia
c = ib
case 2:
a = ib
b = ic
c = ia
}
}
// For this round, append the base64.
l := uint(src[a])<<16 | uint(src[b])<<8 | uint(src[c])
b64 = Base64Append(b64, l, 4)
// Increment the indexes.
ia++
ib++
ic++
id++
}
// For the remaining bytes, append as needed.
if l-i == 2 {
l := uint(0)<<16 | uint(src[l-1])<<8 | uint(src[l-2])
b64 = Base64Append(b64, l, 3)
} else {
l := uint(0)<<16 | uint(0)<<8 | uint(src[l-1])
b64 = Base64Append(b64, l, 2)
}
// Return the base64.
return b64
}

60
yes_crypt.go Normal file
View File

@ -0,0 +1,60 @@
package passwd
import (
"fmt"
"github.com/openwall/yescrypt-go"
)
type YesCrypt struct {
Passwd
}
// Make an MD5Crypt password instance.
func NewYesCryptPasswd() PasswdInterface {
m := new(YesCrypt)
m.Magic = YES_CRYPT_MAGIC
m.SetSCryptParams(11, 31)
m.SaltLength = 22
// Set the interface to allow parents to call overriden functions.
m.i = m
return m
}
// Sets the SCrypt params using integers.
func (a *YesCrypt) SetSCryptParams(N, r int) (err error) {
Nval, err := IToA64(N)
if err != nil {
return
}
rval, err := IToA64(r)
if err != nil {
return
}
a.Params = fmt.Sprintf("j%c%c", Nval, rval)
return
}
// Decode SCrypt params.
func (a *YesCrypt) DecodeSCriptParams() (N, r int) {
b64 := []byte(a.Params)
if len(b64) != 3 {
return
}
N = AToI64(b64[1])
r = AToI64(b64[2])
return
}
// Hash a password with salt using yes crypt standard.
func (a *YesCrypt) Hash(password []byte, salt []byte) (hash []byte, err error) {
output := fmt.Sprintf("%s%s$%s", a.Magic, a.Params, salt)
hash, err = yescrypt.Hash(password, []byte(output))
return
}
// Override the passwd hash with salt function to hash with yes crypt.
func (a *YesCrypt) HashPasswordWithSalt(password []byte, salt []byte) (hash []byte, err error) {
hash, err = a.Hash(password, salt)
return
}