176 lines
5.0 KiB
Go
176 lines
5.0 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"crypto/sha512"
|
||
|
"io"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
const LOWERCASE_LETTERS = "abcdefghijklmnopqrstuvwxyz"
|
||
|
const UPPERCASE_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||
|
const NUMBERS = "0123456789"
|
||
|
|
||
|
type Password struct {
|
||
|
restrictions *PasswordRestrictions
|
||
|
maxValue int
|
||
|
curAlphaLower int
|
||
|
curAlphaUpper int
|
||
|
curNumber int
|
||
|
curSpecial int
|
||
|
password string
|
||
|
completedOps int /*Simplistic infinite loop detection*/
|
||
|
|
||
|
hashChan chan int
|
||
|
}
|
||
|
|
||
|
func NewPassword(restrictions *PasswordRestrictions, initialPassword string) *Password {
|
||
|
pw := new(Password)
|
||
|
pw.restrictions = restrictions
|
||
|
|
||
|
//setup the goroutine to generate the bytes we need
|
||
|
pw.hashChan = make(chan int)
|
||
|
go func() {
|
||
|
h := sha512.New()
|
||
|
io.WriteString(h, initialPassword+strings.ToLower(restrictions.name))
|
||
|
hashBytes := h.Sum(nil)
|
||
|
|
||
|
for {
|
||
|
for _, b := range hashBytes {
|
||
|
pw.hashChan <- int(b)
|
||
|
}
|
||
|
h.Write(hashBytes)
|
||
|
hashBytes = h.Sum(nil)
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
pw.curAlphaLower = 0
|
||
|
pw.curAlphaUpper = 0
|
||
|
pw.curNumber = 0
|
||
|
pw.curSpecial = 0
|
||
|
pw.password = ""
|
||
|
|
||
|
pw.maxValue = 62 /*alphanumeric*/ + len(restrictions.allowedSpecial)
|
||
|
|
||
|
return pw
|
||
|
}
|
||
|
|
||
|
func (pw *Password) generateCharacter(fillMinFirst bool) (string) {
|
||
|
//if we're trying to fill the minimum requirements, do so, but only if they're not already met
|
||
|
for (fillMinFirst && (pw.curAlphaLower < pw.restrictions.alphaLowerMin || pw.curAlphaUpper < pw.restrictions.alphaUpperMin || pw.curNumber < pw.restrictions.numberMin || pw.curSpecial < pw.restrictions.specialMin)) {
|
||
|
val := <-pw.hashChan % pw.maxValue
|
||
|
switch {
|
||
|
case val >= 0 && val < 26:
|
||
|
if pw.curAlphaLower < pw.restrictions.alphaLowerMin {
|
||
|
pw.curAlphaLower++
|
||
|
return LOWERCASE_LETTERS[val : val+1]
|
||
|
}
|
||
|
case val >= 26 && val < 52:
|
||
|
if pw.curAlphaUpper < pw.restrictions.alphaUpperMin {
|
||
|
pw.curAlphaUpper++
|
||
|
return UPPERCASE_LETTERS[val-26 : val-25]
|
||
|
}
|
||
|
case val >= 52 && val < 62:
|
||
|
if pw.curNumber < pw.restrictions.numberMin {
|
||
|
pw.curNumber++
|
||
|
return NUMBERS[val-52 : val-51]
|
||
|
}
|
||
|
default:
|
||
|
if pw.curSpecial < pw.restrictions.specialMin {
|
||
|
pw.curSpecial++
|
||
|
return pw.restrictions.allowedSpecial[val-62 : val-61]
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//if the minimum requirements are met, generate any character
|
||
|
val := <-pw.hashChan % pw.maxValue
|
||
|
switch {
|
||
|
case val >= 0 && val < 26:
|
||
|
if pw.curAlphaLower+1 <= pw.restrictions.alphaLowerMax {
|
||
|
pw.curAlphaLower++
|
||
|
return LOWERCASE_LETTERS[val : val+1]
|
||
|
}
|
||
|
case val >= 26 && val < 52:
|
||
|
if pw.curAlphaUpper+1 <= pw.restrictions.alphaUpperMax {
|
||
|
pw.curAlphaUpper++
|
||
|
return UPPERCASE_LETTERS[val-26 : val-25]
|
||
|
}
|
||
|
case val >= 52 && val < 62:
|
||
|
if pw.curNumber+1 <= pw.restrictions.numberMax {
|
||
|
pw.curNumber++
|
||
|
return NUMBERS[val-52 : val-51]
|
||
|
}
|
||
|
default:
|
||
|
if pw.curSpecial+1 <= pw.restrictions.specialMax {
|
||
|
pw.curSpecial++
|
||
|
return pw.restrictions.allowedSpecial[val-62 : val-61]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
panic("Generated invalid character code")
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
func (pw *Password) removeCharacterAt(i int) {
|
||
|
removedChar := pw.password[:i]
|
||
|
pw.password = pw.password[:i] + pw.password[i+1:]
|
||
|
|
||
|
switch {
|
||
|
case strings.Contains(LOWERCASE_LETTERS, removedChar):
|
||
|
pw.curAlphaLower--
|
||
|
case strings.Contains(UPPERCASE_LETTERS, removedChar):
|
||
|
pw.curAlphaUpper--
|
||
|
case strings.Contains(NUMBERS, removedChar):
|
||
|
pw.curNumber--
|
||
|
case strings.Contains(pw.restrictions.allowedSpecial, removedChar):
|
||
|
pw.curSpecial--
|
||
|
default:
|
||
|
panic("Unknown character made its way into the password")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (pw *Password) InitialRandom() {
|
||
|
pw.doOp()
|
||
|
//build up the initial string, making sure we don't go over the max allowed for each type
|
||
|
for length := 0; length < pw.restrictions.maxLength; length++ {
|
||
|
pw.password += pw.generateCharacter(false)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (pw *Password) FixupMinimumRequirements() {
|
||
|
//now, make sure we meet the minimum requirements. If we don't, start replacing items in the password
|
||
|
for (pw.curAlphaLower < pw.restrictions.alphaLowerMin || pw.curAlphaUpper < pw.restrictions.alphaUpperMin || pw.curNumber < pw.restrictions.numberMin || pw.curSpecial < pw.restrictions.specialMin){
|
||
|
pw.doOp()
|
||
|
pw.removeCharacterAt(0)
|
||
|
pw.password += pw.generateCharacter(true)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (pw *Password) RandomizeOrder() {
|
||
|
pw.doOp()
|
||
|
//finally, (pseudo-)randomize the password ordering, one element at a time
|
||
|
for i := 0; i < len(pw.password); i++ {
|
||
|
position := (<-pw.hashChan % (len(pw.password) - i)) + i
|
||
|
pw.password = pw.password[position:position+1] + pw.password[0:position] + pw.password[position+1:]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (pw *Password) FixupAdditionalRequirements() {
|
||
|
if pw.restrictions.additionalRequirements == nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
for badChar, i := pw.restrictions.additionalRequirements(pw.password); !badChar; badChar, i = pw.restrictions.additionalRequirements(pw.password) {
|
||
|
pw.doOp()
|
||
|
pw.removeCharacterAt(i)
|
||
|
pw.password = pw.password[:i] + pw.generateCharacter(true) + pw.password[i:]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (pw *Password) doOp() {
|
||
|
pw.completedOps++
|
||
|
if pw.completedOps > 1000 {
|
||
|
panic("Error: Attempting to generate your password has taken 1000 'operations'. It is likely it is stuck in an infinite loop. Please contact the authors of this package if you feel this is in error.")
|
||
|
}
|
||
|
}
|