146 lines
3.2 KiB
Go
146 lines
3.2 KiB
Go
package passgen
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"math/big"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"unicode/utf16"
|
|
|
|
"ekyu.moe/base91"
|
|
"github.com/aead/skein"
|
|
)
|
|
|
|
func (p *Passgen) Generate() string {
|
|
switch p.Version {
|
|
case 1:
|
|
return p.V1()
|
|
default:
|
|
return p.V2()
|
|
}
|
|
}
|
|
|
|
func (p *Passgen) V2() string {
|
|
charset := p.createCharset()
|
|
var out string
|
|
|
|
for i := 0; true; i++ {
|
|
if len(out) >= p.Length { // password is long enough
|
|
if p.containsNecessaryCharacters(out[0:p.Length]) { // password contains all necessary character classes
|
|
break
|
|
} else { // password is missing character classes, keep second half and keep generating if necessary
|
|
out = out[int(len(out)/2):]
|
|
}
|
|
}
|
|
|
|
passphrasePart := strings.Repeat(p.Passphrase, i+1)
|
|
hash := sha256.Sum256([]byte(passphrasePart + p.Salt))
|
|
|
|
// convert hash array of bytes into bigint
|
|
num := new(big.Int)
|
|
num.SetString(hex.EncodeToString(hash[:]), 16)
|
|
// encode bigint in custom character set
|
|
encoded := encodeToCustomCharset(num, charset)
|
|
|
|
out = fmt.Sprintf("%s%s", out, encoded)
|
|
}
|
|
|
|
return out[0:p.Length]
|
|
}
|
|
|
|
func (p *Passgen) createCharset() string {
|
|
charset := ""
|
|
if !p.NoUppers {
|
|
charset += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
}
|
|
charset += "abcdefghijklmnopqrstuvwxyz"
|
|
if !p.NoNumbers {
|
|
charset += "0123456789"
|
|
}
|
|
if !p.NoSpecials {
|
|
charset += p.CustomSpecials
|
|
}
|
|
|
|
return charset
|
|
}
|
|
|
|
func (p *Passgen) containsNecessaryCharacters(password string) bool {
|
|
if !p.NoNumbers {
|
|
if matches, _ := regexp.MatchString("[0-9]", password); !matches {
|
|
return false
|
|
}
|
|
}
|
|
if !p.NoUppers {
|
|
if matches, _ := regexp.MatchString("[A-Z]", password); !matches {
|
|
return false
|
|
}
|
|
}
|
|
if !p.NoSpecials {
|
|
customSpecials := p.CustomSpecials
|
|
escapeChars := []string{"-", "[", "]", "^"}
|
|
for _, e := range escapeChars {
|
|
customSpecials = strings.ReplaceAll(customSpecials, e, "\\"+e)
|
|
}
|
|
if matches, _ := regexp.MatchString(fmt.Sprintf("[%s]", customSpecials), password); !matches {
|
|
return false
|
|
}
|
|
}
|
|
if matches, _ := regexp.MatchString("[a-z]", password); !matches {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func encodeToCustomCharset(v *big.Int, charset string) string {
|
|
charsetLength := big.NewInt(int64(len(charset)))
|
|
var ret string
|
|
for {
|
|
if v.Cmp(big.NewInt(0)) == 0 {
|
|
break
|
|
}
|
|
remainder := new(big.Int).Mod(v, charsetLength)
|
|
v = new(big.Int).Div(v, charsetLength)
|
|
|
|
ret = fmt.Sprintf("%c%s", charset[remainder.Uint64()], ret)
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (p *Passgen) V1() string {
|
|
encoded := utf16.Encode([]rune(p.Passphrase + p.Salt))
|
|
var hashOut [64]byte
|
|
skein.Sum512(&hashOut, convertUTF16ToLittleEndianBytes(encoded), nil)
|
|
hashHex := hex.EncodeToString(hashOut[:])
|
|
|
|
start := hexToInt(string(hashHex[0])) + hexToInt(string(hashHex[1])) + hexToInt(string(hashHex[2]))
|
|
end := start + p.Length
|
|
|
|
encoded91 := base91.EncodeToString([]byte(hashHex))
|
|
|
|
for {
|
|
if len(encoded91) > end {
|
|
break
|
|
}
|
|
encoded91 = encoded91 + encoded91
|
|
}
|
|
|
|
return encoded91[start:end]
|
|
}
|
|
|
|
func convertUTF16ToLittleEndianBytes(u []uint16) []byte {
|
|
b := make([]byte, 2*len(u))
|
|
for index, value := range u {
|
|
binary.LittleEndian.PutUint16(b[index*2:], value)
|
|
}
|
|
return b
|
|
}
|
|
|
|
func hexToInt(hexString string) int {
|
|
i, _ := strconv.ParseInt(hexString, 16, 0)
|
|
return int(i)
|
|
}
|