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.") } }