mirror of
https://github.com/wneessen/apg-go.git
synced 2024-12-23 03:30:39 +01:00
Major refactor so that cmd and lib are separated
This commit is contained in:
parent
5ceaf6a777
commit
f6cd374412
9 changed files with 270 additions and 603 deletions
|
@ -1,13 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AutoImportSettings">
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
<option name="autoReloadType" value="ALL" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="fbb0c733-4aa1-4d27-87d5-c7276d8aa613" name="Default Changelist" comment="For some reason, the tests on GH fail">
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/apg_test.go" beforeDir="false" afterPath="$PROJECT_DIR$/apg_test.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/config.go" beforeDir="false" afterPath="$PROJECT_DIR$/config.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/apg.go" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/apg_test.go" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/chars.go" beforeDir="false" afterPath="$PROJECT_DIR$/chars/chars.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/config.go" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/convert.go" beforeDir="false" afterPath="$PROJECT_DIR$/spelling/spelling.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/hibp.go" beforeDir="false" afterPath="$PROJECT_DIR$/hibp/hibp.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/rand.go" beforeDir="false" afterPath="$PROJECT_DIR$/random/random.go" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
|
@ -22,7 +27,7 @@
|
|||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="GOROOT" url="file://$PROJECT_DIR$/../../go1.16.2" />
|
||||
<component name="GOROOT" url="file://$PROJECT_DIR$/../../go1.17.1" />
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
||||
<map>
|
||||
|
@ -57,26 +62,30 @@
|
|||
<property name="WebServerToolWindowFactoryState" value="false" />
|
||||
<property name="configurable..is.expanded" value="false" />
|
||||
<property name="configurable.GoLibrariesConfigurable.is.expanded" value="true" />
|
||||
<property name="go.formatter.settings.were.checked" value="true" />
|
||||
<property name="go.import.settings.migrated" value="true" />
|
||||
<property name="go.modules.go.list.on.any.changes.was.set" value="true" />
|
||||
<property name="go.sdk.automatically.set" value="true" />
|
||||
<property name="go.tried.to.enable.integration.vgo.integrator" value="true" />
|
||||
<property name="last_opened_file_path" value="$USER_HOME$" />
|
||||
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
|
||||
<property name="settings.editor.selected.configurable" value="inlay.hints.go" />
|
||||
</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="MoveFile.RECENT_KEYS">
|
||||
<recent name="C:\Users\Winni Neessen\go\src\apg.go\chars" />
|
||||
<recent name="C:\Users\Winni Neessen\go\src\apg.go\lib" />
|
||||
<recent name="C:\Users\Winni Neessen\go\src\apg.go\test" />
|
||||
<recent name="$PROJECT_DIR$/chars" />
|
||||
<recent name="$PROJECT_DIR$/hibp" />
|
||||
<recent name="$PROJECT_DIR$/spelling" />
|
||||
<recent name="$PROJECT_DIR$/config" />
|
||||
<recent name="$PROJECT_DIR$/random" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunManager" selected="Go Build.Run Application (with newstyle params)">
|
||||
<component name="RunManager" selected="Go Test.Benchmark Application">
|
||||
<configuration name="Run Application (show help text)" type="GoApplicationRunConfiguration" factoryName="Go Application">
|
||||
<module name="apg.go" />
|
||||
<working_directory value="$PROJECT_DIR$" />
|
||||
<parameters value="-h" />
|
||||
<kind value="PACKAGE" />
|
||||
<package value="github.com/wneessen/apg-go" />
|
||||
<package value="github.com/wneessen/apg-go/cmd/apg" />
|
||||
<directory value="$PROJECT_DIR$" />
|
||||
<filePath value="$PROJECT_DIR$" />
|
||||
<method v="2" />
|
||||
|
@ -86,7 +95,7 @@
|
|||
<working_directory value="$PROJECT_DIR$" />
|
||||
<parameters value="-v" />
|
||||
<kind value="PACKAGE" />
|
||||
<package value="github.com/wneessen/apg-go" />
|
||||
<package value="github.com/wneessen/apg-go/cmd/apg" />
|
||||
<directory value="$PROJECT_DIR$" />
|
||||
<filePath value="$PROJECT_DIR$" />
|
||||
<method v="2" />
|
||||
|
@ -96,7 +105,7 @@
|
|||
<working_directory value="$PROJECT_DIR$" />
|
||||
<parameters value="-m 20 -x 20 -C -E abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!]" />
|
||||
<kind value="PACKAGE" />
|
||||
<package value="github.com/wneessen/apg-go" />
|
||||
<package value="github.com/wneessen/apg-go/cmd/apg" />
|
||||
<directory value="$PROJECT_DIR$" />
|
||||
<filePath value="$PROJECT_DIR$" />
|
||||
<method v="2" />
|
||||
|
@ -106,7 +115,7 @@
|
|||
<working_directory value="$PROJECT_DIR$" />
|
||||
<parameters value="-m 10 -x 10 -M SLNU -l -n 6" />
|
||||
<kind value="PACKAGE" />
|
||||
<package value="github.com/wneessen/apg-go" />
|
||||
<package value="github.com/wneessen/apg-go/cmd/apg" />
|
||||
<directory value="$PROJECT_DIR$" />
|
||||
<filePath value="$PROJECT_DIR$" />
|
||||
<method v="2" />
|
||||
|
@ -116,7 +125,7 @@
|
|||
<working_directory value="$PROJECT_DIR$" />
|
||||
<parameters value="-m 10 -x 10 -M SLNU -l" />
|
||||
<kind value="PACKAGE" />
|
||||
<package value="github.com/wneessen/apg-go" />
|
||||
<package value="github.com/wneessen/apg-go/cmd/apg" />
|
||||
<directory value="$PROJECT_DIR$" />
|
||||
<filePath value="$PROJECT_DIR$" />
|
||||
<method v="2" />
|
||||
|
@ -126,7 +135,7 @@
|
|||
<working_directory value="$PROJECT_DIR$" />
|
||||
<parameters value="-M slNU" />
|
||||
<kind value="PACKAGE" />
|
||||
<package value="github.com/wneessen/apg-go" />
|
||||
<package value="github.com/wneessen/apg-go/cmd/apg" />
|
||||
<directory value="$PROJECT_DIR$" />
|
||||
<filePath value="$PROJECT_DIR$" />
|
||||
<method v="2" />
|
||||
|
@ -136,7 +145,7 @@
|
|||
<working_directory value="$PROJECT_DIR$" />
|
||||
<parameters value="-m 20 -x 20 -C" />
|
||||
<kind value="PACKAGE" />
|
||||
<package value="github.com/wneessen/apg-go" />
|
||||
<package value="github.com/wneessen/apg-go/cmd/apg" />
|
||||
<directory value="$PROJECT_DIR$" />
|
||||
<filePath value="$PROJECT_DIR$" />
|
||||
<method v="2" />
|
||||
|
@ -145,7 +154,17 @@
|
|||
<module name="apg.go" />
|
||||
<working_directory value="$PROJECT_DIR$" />
|
||||
<kind value="PACKAGE" />
|
||||
<package value="github.com/wneessen/apg-go" />
|
||||
<package value="github.com/wneessen/apg-go/cmd/apg" />
|
||||
<directory value="$PROJECT_DIR$" />
|
||||
<filePath value="$PROJECT_DIR$" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration default="true" type="GoApplicationRunConfiguration" factoryName="Go Application">
|
||||
<module name="apg.go" />
|
||||
<working_directory value="$PROJECT_DIR$" />
|
||||
<go_parameters value="-i" />
|
||||
<kind value="PACKAGE" />
|
||||
<package value="apg-go" />
|
||||
<directory value="$PROJECT_DIR$" />
|
||||
<filePath value="$PROJECT_DIR$" />
|
||||
<method v="2" />
|
||||
|
@ -154,7 +173,7 @@
|
|||
<module name="apg.go" />
|
||||
<working_directory value="$PROJECT_DIR$" />
|
||||
<kind value="PACKAGE" />
|
||||
<package value="github.com/wneessen/apg-go" />
|
||||
<package value="github.com/wneessen/apg-go/cmd/apg" />
|
||||
<directory value="$PROJECT_DIR$" />
|
||||
<filePath value="$PROJECT_DIR$" />
|
||||
<framework value="gobench" />
|
||||
|
@ -164,7 +183,18 @@
|
|||
<module name="apg.go" />
|
||||
<working_directory value="$PROJECT_DIR$" />
|
||||
<kind value="PACKAGE" />
|
||||
<package value="github.com/wneessen/apg-go" />
|
||||
<package value="github.com/wneessen/apg-go/cmd/apg" />
|
||||
<directory value="$PROJECT_DIR$" />
|
||||
<filePath value="$PROJECT_DIR$" />
|
||||
<framework value="gotest" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration default="true" type="GoTestRunConfiguration" factoryName="Go Test">
|
||||
<module name="apg.go" />
|
||||
<working_directory value="$PROJECT_DIR$" />
|
||||
<go_parameters value="-i" />
|
||||
<kind value="DIRECTORY" />
|
||||
<package value="apg-go" />
|
||||
<directory value="$PROJECT_DIR$" />
|
||||
<filePath value="$PROJECT_DIR$" />
|
||||
<framework value="gotest" />
|
||||
|
@ -239,8 +269,8 @@
|
|||
<integration-enabled>true</integration-enabled>
|
||||
</component>
|
||||
<component name="com.intellij.coverage.CoverageDataManagerImpl">
|
||||
<SUITE FILE_PATH="coverage/apg_go$Test_Application.out" NAME="Test Application Coverage Results" MODIFIED="1616765230426" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="GoCoverage" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" />
|
||||
<SUITE FILE_PATH="coverage/apg_go$TestGetRandNum_in_github_com_wneessen_apg_go__1_.out" NAME="TestGetRandNum in github.com/wneessen/apg.go (1) Coverage Results" MODIFIED="1616594443133" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="GoCoverage" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" />
|
||||
<SUITE FILE_PATH="coverage/apg_go$BenchmarkGetRandNum_in_apg_go.out" NAME="BenchmarkGetRandNum in apg.go Coverage Results" MODIFIED="1616342745320" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="GoCoverage" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" />
|
||||
<SUITE FILE_PATH="coverage/apg_go$Test_Application.out" NAME="Test Application Coverage Results" MODIFIED="1616765230426" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="GoCoverage" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" />
|
||||
</component>
|
||||
</project>
|
113
apg.go
113
apg.go
|
@ -1,113 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Constants
|
||||
const DefaultMinLenght int = 12
|
||||
const DefaultMaxLenght int = 20
|
||||
const VersionString string = "0.3.2"
|
||||
|
||||
type Config struct {
|
||||
minPassLen int
|
||||
maxPassLen int
|
||||
numOfPass int
|
||||
useComplex bool
|
||||
useLowerCase bool
|
||||
useUpperCase bool
|
||||
useNumber bool
|
||||
useSpecial bool
|
||||
humanReadable bool
|
||||
checkHibp bool
|
||||
excludeChars string
|
||||
newStyleModes string
|
||||
spellPassword bool
|
||||
ShowHelp bool
|
||||
showVersion bool
|
||||
outputMode int
|
||||
}
|
||||
|
||||
// Help text
|
||||
const usage = `apg-go // A "Automated Password Generator"-clone
|
||||
Copyright (c) 2021 Winni Neessen
|
||||
|
||||
apg [-m <length>] [-x <length>] [-L] [-U] [-N] [-S] [-H] [-C]
|
||||
[-l] [-M mode] [-E char_string] [-n num_of_pass] [-v] [-h]
|
||||
|
||||
Options:
|
||||
-m LENGTH Minimum length of the password to be generated (Default: 12)
|
||||
-x LENGTH Maximum length of the password to be generated (Default: 20)
|
||||
-n NUMBER Amount of password to be generated (Default: 6)
|
||||
-E CHARS List of characters to be excluded in the generated password
|
||||
-M [LUNSHClunshc] New style password parameters (upper case: on, lower case: off)
|
||||
-L Use lower case characters in passwords (Default: on)
|
||||
-U Use upper case characters in passwords (Default: on)
|
||||
-N Use numeric characters in passwords (Default: on)
|
||||
-S Use special characters in passwords (Default: off)
|
||||
-H Avoid ambiguous characters in passwords (i. e.: 1, l, I, O, 0) (Default: off)
|
||||
-C Enable complex password mode (implies -L -U -N -S and disables -H) (Default: off)
|
||||
-l Spell generated passwords in phonetic alphabet (Default: off)
|
||||
-p Check the HIBP database if the generated passwords was found in a leak before (Default: off)
|
||||
'--> this feature requires internet connectivity
|
||||
-h Show this help text
|
||||
-v Show version string`
|
||||
|
||||
// Main function that generated the passwords and returns them
|
||||
func main() {
|
||||
// Log config
|
||||
log.SetFlags(log.Ltime | log.Ldate | log.Lshortfile)
|
||||
|
||||
// Read and parse flags
|
||||
flag.Usage = func() { _, _ = fmt.Fprintf(os.Stderr, "%s\n", usage) }
|
||||
var config = parseFlags()
|
||||
|
||||
// Show version and exit
|
||||
if config.showVersion {
|
||||
_, _ = os.Stderr.WriteString(`apg-go // A "Automated Password Generator"-clone v` + VersionString + "\n")
|
||||
_, _ = os.Stderr.WriteString("(C) 2021 by Winni Neessen\n")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Set PW length and available characterset
|
||||
charRange := getCharRange(&config)
|
||||
|
||||
// Generate passwords
|
||||
for i := 1; i <= config.numOfPass; i++ {
|
||||
pwLength := getPwLengthFromParams(&config)
|
||||
pwString, err := getRandChar(&charRange, pwLength)
|
||||
if err != nil {
|
||||
log.Fatalf("getRandChar returned an error: %q\n", err)
|
||||
}
|
||||
|
||||
switch config.outputMode {
|
||||
case 1:
|
||||
{
|
||||
spelledPw, err := spellPasswordString(pwString)
|
||||
if err != nil {
|
||||
log.Fatalf("spellPasswordString returned an error: %q\n", err.Error())
|
||||
}
|
||||
fmt.Printf("%v (%v)\n", pwString, spelledPw)
|
||||
break
|
||||
}
|
||||
default:
|
||||
{
|
||||
fmt.Println(pwString)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if config.checkHibp {
|
||||
isPwned, err := checkHibp(pwString)
|
||||
if err != nil {
|
||||
log.Printf("unable to check HIBP database: %v", err)
|
||||
}
|
||||
if isPwned {
|
||||
fmt.Print("^-- !!WARNING: The previously generated password was found in HIPB database. Do not use it!!\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
284
apg_test.go
284
apg_test.go
|
@ -1,284 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var config Config
|
||||
|
||||
// Make sure the flags are initalized
|
||||
var _ = func() bool {
|
||||
testing.Init()
|
||||
config = parseFlags()
|
||||
return true
|
||||
}()
|
||||
|
||||
// Test getRandNum with max 1000
|
||||
func TestGetRandNum(t *testing.T) {
|
||||
testTable := []struct {
|
||||
testName string
|
||||
givenVal int
|
||||
maxRet int
|
||||
minRet int
|
||||
shouldFail bool
|
||||
}{
|
||||
{"randNum up to 1000", 1000, 1000, 0, false},
|
||||
{"randNum should be 1", 1, 1, 0, false},
|
||||
{"randNum should fail on 0", 0, 0, 0, true},
|
||||
{"randNum should fail on negative", -1, 0, 0, true},
|
||||
}
|
||||
|
||||
for _, testCase := range testTable {
|
||||
t.Run(testCase.testName, func(t *testing.T) {
|
||||
randNum, err := getRandNum(testCase.givenVal)
|
||||
if testCase.shouldFail {
|
||||
if err == nil {
|
||||
t.Errorf("Random number generation succeeded but was expected to fail. Given: %v, returned: %v",
|
||||
testCase.givenVal, randNum)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("Random number generation failed: %v", err.Error())
|
||||
}
|
||||
if randNum > testCase.maxRet {
|
||||
t.Errorf("Random number generation returned too big value. Given %v, expected max: %v, got: %v",
|
||||
testCase.givenVal, testCase.maxRet, randNum)
|
||||
}
|
||||
if randNum < testCase.minRet {
|
||||
t.Errorf("Random number generation returned too small value. Given %v, expected max: %v, got: %v",
|
||||
testCase.givenVal, testCase.minRet, randNum)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test Pwlength
|
||||
func TestGenLength(t *testing.T) {
|
||||
testTable := []struct {
|
||||
testName string
|
||||
minLength int
|
||||
maxLength int
|
||||
}{
|
||||
{"pwLength defaults", DefaultMinLenght, DefaultMaxLenght},
|
||||
{"pwLength 0 to 1", 0, 1},
|
||||
{"pwLength 1 to 10", 0, 10},
|
||||
{"pwLength 10 to 100", 10, 100},
|
||||
}
|
||||
|
||||
charRange := getCharRange(&config)
|
||||
for _, testCase := range testTable {
|
||||
t.Run(testCase.testName, func(t *testing.T) {
|
||||
config.minPassLen = testCase.minLength
|
||||
config.maxPassLen = testCase.maxLength
|
||||
pwLength := getPwLengthFromParams(&config)
|
||||
for i := 0; i < 1000; i++ {
|
||||
pwString, err := getRandChar(&charRange, pwLength)
|
||||
if err != nil {
|
||||
t.Errorf("getRandChar returned an error: %q", err)
|
||||
}
|
||||
retLen := len(pwString)
|
||||
if retLen > testCase.maxLength {
|
||||
t.Errorf("Generated password length too long. GivenMin %v, GivenMax: %v, Returned length %v",
|
||||
testCase.minLength, testCase.maxLength, retLen)
|
||||
}
|
||||
if retLen < testCase.minLength {
|
||||
t.Errorf("Generated password length too short. GivenMin %v, GivenMax: %v, Returned length %v",
|
||||
testCase.minLength, testCase.maxLength, retLen)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test getRandChar
|
||||
func TestGetRandChar(t *testing.T) {
|
||||
t.Run("return_value_is_A_B_or_C", func(t *testing.T) {
|
||||
charRange := "ABC"
|
||||
randChar, err := getRandChar(&charRange, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("Random character generation failed => %v", err.Error())
|
||||
}
|
||||
if randChar != "A" && randChar != "B" && randChar != "C" {
|
||||
t.Fatalf("Random character generation failed. Expected A, B or C but got: %v", randChar)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("return_value_has_specific_length", func(t *testing.T) {
|
||||
charRange := "ABC"
|
||||
randChar, err := getRandChar(&charRange, 1000)
|
||||
if err != nil {
|
||||
t.Fatalf("Random character generation failed => %v", err.Error())
|
||||
}
|
||||
if len(randChar) != 1000 {
|
||||
t.Fatalf("Generated random characters with 1000 chars returned wrong amount of chars: %v",
|
||||
len(randChar))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("fail", func(t *testing.T) {
|
||||
charRange := "ABC"
|
||||
randChar, err := getRandChar(&charRange, -2000)
|
||||
if err == nil {
|
||||
t.Fatalf("Generated random characters expected to fail, but returned a value => %v",
|
||||
randChar)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Test getCharRange() with different config settings
|
||||
func TestGetCharRange(t *testing.T) {
|
||||
lowerCaseBytes := []int{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
|
||||
's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}
|
||||
lowerCaseHumanBytes := []int{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r',
|
||||
's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}
|
||||
upperCaseBytes := []int{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
|
||||
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}
|
||||
upperCaseHumanBytes := []int{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q',
|
||||
'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}
|
||||
numberBytes := []int{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
|
||||
numberHumanBytes := []int{'2', '3', '4', '5', '6', '7', '8', '9'}
|
||||
specialBytes := []int{'!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':',
|
||||
';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~'}
|
||||
specialHumanBytes := []int{'"', '#', '%', '*', '+', '-', '/', ':', ';', '=', '\\', '_', '|', '~'}
|
||||
testTable := []struct {
|
||||
testName string
|
||||
allowedBytes []int
|
||||
useLowerCase bool
|
||||
useUpperCase bool
|
||||
useNumber bool
|
||||
useSpecial bool
|
||||
humanReadable bool
|
||||
}{
|
||||
{"lowercase_only", lowerCaseBytes, true, false, false, false, false},
|
||||
{"lowercase_only_human", lowerCaseHumanBytes, true, false, false, false, true},
|
||||
{"uppercase_only", upperCaseBytes, false, true, false, false, false},
|
||||
{"uppercase_only_human", upperCaseHumanBytes, false, true, false, false, true},
|
||||
{"number_only", numberBytes, false, false, true, false, false},
|
||||
{"number_only_human", numberHumanBytes, false, false, true, false, true},
|
||||
{"special_only", specialBytes, false, false, false, true, false},
|
||||
{"special_only_human", specialHumanBytes, false, false, false, true, true},
|
||||
}
|
||||
|
||||
for _, testCase := range testTable {
|
||||
t.Run(testCase.testName, func(t *testing.T) {
|
||||
config.useLowerCase = testCase.useLowerCase
|
||||
config.useUpperCase = testCase.useUpperCase
|
||||
config.useNumber = testCase.useNumber
|
||||
config.useSpecial = testCase.useSpecial
|
||||
config.humanReadable = testCase.humanReadable
|
||||
charRange := getCharRange(&config)
|
||||
for _, curChar := range charRange {
|
||||
searchAllowedBytes := containsByte(testCase.allowedBytes, int(curChar), t)
|
||||
if !searchAllowedBytes {
|
||||
t.Errorf("Character range returned invalid value: %v", string(curChar))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test Conversions
|
||||
func TestConvert(t *testing.T) {
|
||||
testTable := []struct {
|
||||
testName string
|
||||
givenVal byte
|
||||
expVal string
|
||||
shouldFail bool
|
||||
}{
|
||||
{"convert_A_to_Alfa", 'A', "Alfa", false},
|
||||
{"convert_a_to_alfa", 'a', "alfa", false},
|
||||
{"convert_0_to_ZERO", '0', "ZERO", false},
|
||||
{"convert_/_to_SLASH", '/', "SLASH", false},
|
||||
}
|
||||
|
||||
for _, testCase := range testTable {
|
||||
t.Run(testCase.testName, func(t *testing.T) {
|
||||
charToString, err := convertCharToName(testCase.givenVal)
|
||||
if testCase.shouldFail {
|
||||
if err == nil {
|
||||
t.Errorf("Character to string conversion succeeded but was expected to fail. Given: %v, returned: %v",
|
||||
testCase.givenVal, charToString)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("Character to string conversion failed: %v", err.Error())
|
||||
}
|
||||
if charToString != testCase.expVal {
|
||||
t.Errorf("Character to String conversion fail. Given: %q, expected: %q, got: %q",
|
||||
testCase.givenVal, testCase.expVal, charToString)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("all_chars_must_return_a_conversion_string", func(t *testing.T) {
|
||||
config.useUpperCase = true
|
||||
config.useLowerCase = true
|
||||
config.useNumber = true
|
||||
config.useSpecial = true
|
||||
config.humanReadable = false
|
||||
charRange := getCharRange(&config)
|
||||
for _, curChar := range charRange {
|
||||
_, err := convertCharToName(byte(curChar))
|
||||
if err != nil {
|
||||
t.Fatalf("Character to string conversion failed: %v", err.Error())
|
||||
}
|
||||
}
|
||||
})
|
||||
t.Run("spell_Ab!_to_strings", func(t *testing.T) {
|
||||
pwString := "Ab!"
|
||||
spelledString, err := spellPasswordString(pwString)
|
||||
if err != nil {
|
||||
t.Fatalf("password spelling failed: %v", err.Error())
|
||||
}
|
||||
if spelledString != "Alfa/bravo/EXCLAMATION_POINT" {
|
||||
t.Fatalf(
|
||||
"Spelling pwString 'Ab!' is expected to provide 'Alfa/bravo/EXCLAMATION_POINT', but returned: %q",
|
||||
spelledString)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Benchmark: Random number generation
|
||||
func BenchmarkGetRandNum(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = getRandNum(100000)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark: Random char generation
|
||||
func BenchmarkGetRandChar(b *testing.B) {
|
||||
charRange := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\"#/!\\$%&+-*.,?=()[]{}:;~^|"
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = getRandChar(&charRange, 20)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark: Random char generation
|
||||
func BenchmarkConvertChar(b *testing.B) {
|
||||
|
||||
config.useUpperCase = true
|
||||
config.useLowerCase = true
|
||||
config.useNumber = true
|
||||
config.useSpecial = true
|
||||
config.humanReadable = false
|
||||
charRange := getCharRange(&config)
|
||||
for i := 0; i < b.N; i++ {
|
||||
charToConv, _ := getRandChar(&charRange, 1)
|
||||
charBytes := []byte(charToConv)
|
||||
_, _ = convertCharToName(charBytes[0])
|
||||
}
|
||||
}
|
||||
|
||||
// Contains function to search a given slice for values
|
||||
func containsByte(allowedBytes []int, currentChar int, t *testing.T) bool {
|
||||
t.Helper()
|
||||
|
||||
for _, charInt := range allowedBytes {
|
||||
if charInt == currentChar {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
package chars
|
||||
|
||||
import (
|
||||
"github.com/wneessen/apg-go/config"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
|
@ -13,13 +14,13 @@ const PwSpecialChars string = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
|
|||
const PwNumbersHuman string = "23456789"
|
||||
const PwNumbers string = "1234567890"
|
||||
|
||||
// Provide the range of available characters based on provided parameters
|
||||
func getCharRange(config *Config) string {
|
||||
// GetRange provides the range of available characters based on configured parameters
|
||||
func GetRange(config *config.Config) string {
|
||||
pwUpperChars := PwUpperChars
|
||||
pwLowerChars := PwLowerChars
|
||||
pwNumbers := PwNumbers
|
||||
pwSpecialChars := PwSpecialChars
|
||||
if config.humanReadable {
|
||||
if config.HumanReadable {
|
||||
pwUpperChars = PwUpperCharsHuman
|
||||
pwLowerChars = PwLowerCharsHuman
|
||||
pwNumbers = PwNumbersHuman
|
||||
|
@ -27,20 +28,20 @@ func getCharRange(config *Config) string {
|
|||
}
|
||||
|
||||
var charRange string
|
||||
if config.useLowerCase {
|
||||
if config.UseLowerCase {
|
||||
charRange = charRange + pwLowerChars
|
||||
}
|
||||
if config.useUpperCase {
|
||||
if config.UseUpperCase {
|
||||
charRange = charRange + pwUpperChars
|
||||
}
|
||||
if config.useNumber {
|
||||
if config.UseNumber {
|
||||
charRange = charRange + pwNumbers
|
||||
}
|
||||
if config.useSpecial {
|
||||
if config.UseSpecial {
|
||||
charRange = charRange + pwSpecialChars
|
||||
}
|
||||
if config.excludeChars != "" {
|
||||
regExp := regexp.MustCompile("[" + regexp.QuoteMeta(config.excludeChars) + "]")
|
||||
if config.ExcludeChars != "" {
|
||||
regExp := regexp.MustCompile("[" + regexp.QuoteMeta(config.ExcludeChars) + "]")
|
||||
charRange = regExp.ReplaceAllLiteralString(charRange, "")
|
||||
}
|
||||
|
164
config.go
164
config.go
|
@ -1,164 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
)
|
||||
|
||||
// Parse the CLI flags
|
||||
func parseFlags() Config {
|
||||
var switchConf Config
|
||||
defaultSwitches := Config{
|
||||
useLowerCase: true,
|
||||
useUpperCase: true,
|
||||
useNumber: true,
|
||||
useSpecial: false,
|
||||
useComplex: false,
|
||||
humanReadable: false,
|
||||
}
|
||||
config := Config{
|
||||
useLowerCase: defaultSwitches.useLowerCase,
|
||||
useUpperCase: defaultSwitches.useUpperCase,
|
||||
useNumber: defaultSwitches.useNumber,
|
||||
useSpecial: defaultSwitches.useSpecial,
|
||||
useComplex: defaultSwitches.useComplex,
|
||||
humanReadable: defaultSwitches.humanReadable,
|
||||
}
|
||||
|
||||
// Read and set all flags
|
||||
flag.BoolVar(&switchConf.useLowerCase, "L", false, "Use lower case characters in passwords")
|
||||
flag.BoolVar(&switchConf.useUpperCase, "U", false, "Use upper case characters in passwords")
|
||||
flag.BoolVar(&switchConf.useNumber, "N", false, "Use numerich characters in passwords")
|
||||
flag.BoolVar(&switchConf.useSpecial, "S", false, "Use special characters in passwords")
|
||||
flag.BoolVar(&switchConf.useComplex, "C", false, "Generate complex passwords (implies -L -U -N -S, disables -H)")
|
||||
flag.BoolVar(&switchConf.humanReadable, "H", false, "Generate human-readable passwords")
|
||||
flag.BoolVar(&config.spellPassword, "l", false, "Spell generated password")
|
||||
flag.BoolVar(&config.checkHibp, "p", false, "Check the HIBP database if the generated password was leaked before")
|
||||
flag.BoolVar(&config.showVersion, "v", false, "Show version")
|
||||
flag.IntVar(&config.minPassLen, "m", DefaultMinLenght, "Minimum password length")
|
||||
flag.IntVar(&config.maxPassLen, "x", DefaultMaxLenght, "Maxiumum password length")
|
||||
flag.IntVar(&config.numOfPass, "n", 6, "Number of passwords to generate")
|
||||
flag.StringVar(&config.excludeChars, "E", "", "Exclude list of characters from generated password")
|
||||
flag.StringVar(&config.newStyleModes, "M", "",
|
||||
"New style password parameters (higher priority than single parameters)")
|
||||
flag.Parse()
|
||||
|
||||
// Invert-switch the defaults
|
||||
if switchConf.useLowerCase {
|
||||
config.useLowerCase = !defaultSwitches.useLowerCase
|
||||
}
|
||||
if switchConf.useUpperCase {
|
||||
config.useUpperCase = !defaultSwitches.useUpperCase
|
||||
}
|
||||
if switchConf.useNumber {
|
||||
config.useNumber = !defaultSwitches.useNumber
|
||||
}
|
||||
if switchConf.useSpecial {
|
||||
config.useSpecial = !defaultSwitches.useSpecial
|
||||
}
|
||||
if switchConf.useComplex {
|
||||
config.useComplex = !defaultSwitches.useComplex
|
||||
}
|
||||
if switchConf.humanReadable {
|
||||
config.humanReadable = !defaultSwitches.humanReadable
|
||||
}
|
||||
|
||||
// Parse additional parameters and new-style switches
|
||||
parseParams(&config)
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// Parse the parameters and set the according config flags
|
||||
func parseParams(config *Config) {
|
||||
parseNewStyleParams(config)
|
||||
|
||||
// Complex overrides everything
|
||||
if config.useComplex {
|
||||
config.useUpperCase = true
|
||||
config.useLowerCase = true
|
||||
config.useSpecial = true
|
||||
config.useNumber = true
|
||||
config.humanReadable = false
|
||||
}
|
||||
|
||||
if config.useUpperCase == false &&
|
||||
config.useLowerCase == false &&
|
||||
config.useNumber == false &&
|
||||
config.useSpecial == false {
|
||||
log.Fatalf("No password mode set. Cannot generate password from empty character set.")
|
||||
}
|
||||
|
||||
// Set output mode
|
||||
if config.spellPassword {
|
||||
config.outputMode = 1
|
||||
}
|
||||
}
|
||||
|
||||
// Get the password length from the given cli flags
|
||||
func getPwLengthFromParams(config *Config) int {
|
||||
if config.minPassLen > config.maxPassLen {
|
||||
config.maxPassLen = config.minPassLen
|
||||
}
|
||||
lenDiff := config.maxPassLen - config.minPassLen + 1
|
||||
randAdd, err := getRandNum(lenDiff)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generated password length: %v", err)
|
||||
}
|
||||
retVal := config.minPassLen + randAdd
|
||||
if retVal <= 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
return retVal
|
||||
}
|
||||
|
||||
// Parse the new style parameters
|
||||
func parseNewStyleParams(config *Config) {
|
||||
if config.newStyleModes == "" {
|
||||
return
|
||||
}
|
||||
|
||||
for _, curParam := range config.newStyleModes {
|
||||
switch curParam {
|
||||
case 'S':
|
||||
config.useSpecial = true
|
||||
break
|
||||
case 's':
|
||||
config.useSpecial = false
|
||||
break
|
||||
case 'N':
|
||||
config.useNumber = true
|
||||
break
|
||||
case 'n':
|
||||
config.useNumber = false
|
||||
break
|
||||
case 'L':
|
||||
config.useLowerCase = true
|
||||
break
|
||||
case 'l':
|
||||
config.useLowerCase = false
|
||||
break
|
||||
case 'U':
|
||||
config.useUpperCase = true
|
||||
break
|
||||
case 'u':
|
||||
config.useUpperCase = false
|
||||
break
|
||||
case 'H':
|
||||
config.humanReadable = true
|
||||
break
|
||||
case 'h':
|
||||
config.humanReadable = false
|
||||
break
|
||||
case 'C':
|
||||
config.useComplex = true
|
||||
break
|
||||
case 'c':
|
||||
config.useComplex = false
|
||||
break
|
||||
default:
|
||||
log.Fatalf("Unknown password style parameter: %q\n", string(curParam))
|
||||
}
|
||||
}
|
||||
}
|
193
config/config.go
Normal file
193
config/config.go
Normal file
|
@ -0,0 +1,193 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"github.com/wneessen/apg-go/random"
|
||||
"log"
|
||||
)
|
||||
|
||||
// Config is a struct that holds the different config parameters for the apg-go
|
||||
// application
|
||||
type Config struct {
|
||||
MinPassLen int
|
||||
MaxPassLen int
|
||||
NumOfPass int
|
||||
UseComplex bool
|
||||
UseLowerCase bool
|
||||
UseUpperCase bool
|
||||
UseNumber bool
|
||||
UseSpecial bool
|
||||
HumanReadable bool
|
||||
CheckHibp bool
|
||||
ExcludeChars string
|
||||
NewStyleModes string
|
||||
SpellPassword bool
|
||||
ShowHelp bool
|
||||
ShowVersion bool
|
||||
OutputMode int
|
||||
}
|
||||
|
||||
// DefaultMinLength reflects the default minimum length of a generated password
|
||||
const DefaultMinLength int = 12
|
||||
|
||||
// DefaultMaxLength reflects the default maximum length of a generated password
|
||||
const DefaultMaxLength int = 20
|
||||
|
||||
// New parses the CLI flags and returns a new config object
|
||||
func New() Config {
|
||||
var switchConf Config
|
||||
defaultSwitches := Config{
|
||||
UseLowerCase: true,
|
||||
UseUpperCase: true,
|
||||
UseNumber: true,
|
||||
UseSpecial: false,
|
||||
UseComplex: false,
|
||||
HumanReadable: false,
|
||||
}
|
||||
config := Config{
|
||||
UseLowerCase: defaultSwitches.UseLowerCase,
|
||||
UseUpperCase: defaultSwitches.UseUpperCase,
|
||||
UseNumber: defaultSwitches.UseNumber,
|
||||
UseSpecial: defaultSwitches.UseSpecial,
|
||||
UseComplex: defaultSwitches.UseComplex,
|
||||
HumanReadable: defaultSwitches.HumanReadable,
|
||||
}
|
||||
|
||||
// Read and set all flags
|
||||
flag.BoolVar(&switchConf.UseLowerCase, "L", false, "Use lower case characters in passwords")
|
||||
flag.BoolVar(&switchConf.UseUpperCase, "U", false, "Use upper case characters in passwords")
|
||||
flag.BoolVar(&switchConf.UseNumber, "N", false, "Use numerich characters in passwords")
|
||||
flag.BoolVar(&switchConf.UseSpecial, "S", false, "Use special characters in passwords")
|
||||
flag.BoolVar(&switchConf.UseComplex, "C", false, "Generate complex passwords (implies -L -U -N -S, disables -H)")
|
||||
flag.BoolVar(&switchConf.HumanReadable, "H", false, "Generate human-readable passwords")
|
||||
flag.BoolVar(&config.SpellPassword, "l", false, "Spell generated password")
|
||||
flag.BoolVar(&config.CheckHibp, "p", false, "Check the HIBP database if the generated password was leaked before")
|
||||
flag.BoolVar(&config.ShowVersion, "v", false, "Show version")
|
||||
flag.IntVar(&config.MinPassLen, "m", DefaultMinLength, "Minimum password length")
|
||||
flag.IntVar(&config.MaxPassLen, "x", DefaultMaxLength, "Maxiumum password length")
|
||||
flag.IntVar(&config.NumOfPass, "n", 6, "Number of passwords to generate")
|
||||
flag.StringVar(&config.ExcludeChars, "E", "", "Exclude list of characters from generated password")
|
||||
flag.StringVar(&config.NewStyleModes, "M", "",
|
||||
"New style password parameters (higher priority than single parameters)")
|
||||
flag.Parse()
|
||||
|
||||
// Invert-switch the defaults
|
||||
if switchConf.UseLowerCase {
|
||||
config.UseLowerCase = !defaultSwitches.UseLowerCase
|
||||
}
|
||||
if switchConf.UseUpperCase {
|
||||
config.UseUpperCase = !defaultSwitches.UseUpperCase
|
||||
}
|
||||
if switchConf.UseNumber {
|
||||
config.UseNumber = !defaultSwitches.UseNumber
|
||||
}
|
||||
if switchConf.UseSpecial {
|
||||
config.UseSpecial = !defaultSwitches.UseSpecial
|
||||
}
|
||||
if switchConf.UseComplex {
|
||||
config.UseComplex = !defaultSwitches.UseComplex
|
||||
}
|
||||
if switchConf.HumanReadable {
|
||||
config.HumanReadable = !defaultSwitches.HumanReadable
|
||||
}
|
||||
|
||||
// Parse additional parameters and new-style switches
|
||||
parseParams(&config)
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// Parse the parameters and set the according config flags
|
||||
func parseParams(config *Config) {
|
||||
parseNewStyleParams(config)
|
||||
|
||||
// Complex overrides everything
|
||||
if config.UseComplex {
|
||||
config.UseUpperCase = true
|
||||
config.UseLowerCase = true
|
||||
config.UseSpecial = true
|
||||
config.UseNumber = true
|
||||
config.HumanReadable = false
|
||||
}
|
||||
|
||||
if config.UseUpperCase == false &&
|
||||
config.UseLowerCase == false &&
|
||||
config.UseNumber == false &&
|
||||
config.UseSpecial == false {
|
||||
log.Fatalf("No password mode set. Cannot generate password from empty character set.")
|
||||
}
|
||||
|
||||
// Set output mode
|
||||
if config.SpellPassword {
|
||||
config.OutputMode = 1
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the new style parameters
|
||||
func parseNewStyleParams(config *Config) {
|
||||
if config.NewStyleModes == "" {
|
||||
return
|
||||
}
|
||||
|
||||
for _, curParam := range config.NewStyleModes {
|
||||
switch curParam {
|
||||
case 'S':
|
||||
config.UseSpecial = true
|
||||
break
|
||||
case 's':
|
||||
config.UseSpecial = false
|
||||
break
|
||||
case 'N':
|
||||
config.UseNumber = true
|
||||
break
|
||||
case 'n':
|
||||
config.UseNumber = false
|
||||
break
|
||||
case 'L':
|
||||
config.UseLowerCase = true
|
||||
break
|
||||
case 'l':
|
||||
config.UseLowerCase = false
|
||||
break
|
||||
case 'U':
|
||||
config.UseUpperCase = true
|
||||
break
|
||||
case 'u':
|
||||
config.UseUpperCase = false
|
||||
break
|
||||
case 'H':
|
||||
config.HumanReadable = true
|
||||
break
|
||||
case 'h':
|
||||
config.HumanReadable = false
|
||||
break
|
||||
case 'C':
|
||||
config.UseComplex = true
|
||||
break
|
||||
case 'c':
|
||||
config.UseComplex = false
|
||||
break
|
||||
default:
|
||||
log.Fatalf("Unknown password style parameter: %q\n", string(curParam))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetPwLengthFromParams extracts the password length from the given cli flags and stores
|
||||
// in the provided config object
|
||||
func GetPwLengthFromParams(config *Config) int {
|
||||
if config.MinPassLen > config.MaxPassLen {
|
||||
config.MaxPassLen = config.MinPassLen
|
||||
}
|
||||
lenDiff := config.MaxPassLen - config.MinPassLen + 1
|
||||
randAdd, err := random.GetNum(lenDiff)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generated password length: %v", err)
|
||||
}
|
||||
retVal := config.MinPassLen + randAdd
|
||||
if retVal <= 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
return retVal
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package main
|
||||
package hibp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
|
@ -10,7 +10,8 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
func checkHibp(p string) (bool, error) {
|
||||
// Check queries the HIBP database and checks if a given string is was found
|
||||
func Check(p string) (bool, error) {
|
||||
shaSum := fmt.Sprintf("%x", sha1.Sum([]byte(p)))
|
||||
firstPart := shaSum[0:5]
|
||||
secondPart := shaSum[5:]
|
|
@ -1,4 +1,4 @@
|
|||
package main
|
||||
package random
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
|
@ -6,9 +6,9 @@ import (
|
|||
"math/big"
|
||||
)
|
||||
|
||||
// Generate random characters based on given character range
|
||||
// GetChar generates random characters based on given character range
|
||||
// and password length
|
||||
func getRandChar(charRange *string, pwLength int) (string, error) {
|
||||
func GetChar(charRange *string, pwLength int) (string, error) {
|
||||
if pwLength <= 0 {
|
||||
err := fmt.Errorf("provided pwLength value is <= 0: %v", pwLength)
|
||||
return "", err
|
||||
|
@ -17,7 +17,7 @@ func getRandChar(charRange *string, pwLength int) (string, error) {
|
|||
charSlice := []byte(*charRange)
|
||||
returnString := make([]byte, pwLength)
|
||||
for i := 0; i < pwLength; i++ {
|
||||
randNum, err := getRandNum(availCharsLength)
|
||||
randNum, err := GetNum(availCharsLength)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -26,8 +26,8 @@ func getRandChar(charRange *string, pwLength int) (string, error) {
|
|||
return string(returnString), nil
|
||||
}
|
||||
|
||||
// Generate a random number with given maximum value
|
||||
func getRandNum(maxNum int) (int, error) {
|
||||
// GetNum generates a random number with given maximum value
|
||||
func GetNum(maxNum int) (int, error) {
|
||||
if maxNum <= 0 {
|
||||
err := fmt.Errorf("provided maxNum is <= 0: %v", maxNum)
|
||||
return 0, err
|
|
@ -1,4 +1,4 @@
|
|||
package main
|
||||
package spelling
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -80,10 +80,11 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
func spellPasswordString(pwString string) (string, error) {
|
||||
// String returns an english spelled version of the given string
|
||||
func String(pwString string) (string, error) {
|
||||
var returnString []string
|
||||
for _, curChar := range pwString {
|
||||
curSpellString, err := convertCharToName(byte(curChar))
|
||||
curSpellString, err := ConvertCharToName(byte(curChar))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -92,7 +93,9 @@ func spellPasswordString(pwString string) (string, error) {
|
|||
return strings.Join(returnString, "/"), nil
|
||||
}
|
||||
|
||||
func convertCharToName(charByte byte) (string, error) {
|
||||
// ConvertCharToName converts a given ascii byte into the corresponding english spelled
|
||||
// name
|
||||
func ConvertCharToName(charByte byte) (string, error) {
|
||||
var returnString string
|
||||
if charByte > 64 && charByte < 91 {
|
||||
returnString = alphabetNames[charByte]
|
Loading…
Reference in a new issue