specialpass/password.go

176 lines
5.0 KiB
Go
Raw Permalink Normal View History

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