Merge pull request #3 from wneessen/dev

v0.2.5
This commit is contained in:
Winni Neessen 2021-03-21 18:32:33 +01:00 committed by GitHub
commit c32a94d195
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 128 additions and 43 deletions

8
.gitignore vendored
View file

@ -5,10 +5,10 @@
*.so *.so
*.dylib *.dylib
apg apg
bin/ bin
.go/ .go
build/ build
.idea/ .idea
# Test binary, built with `go test -c` # Test binary, built with `go test -c`
*.test *.test

120
README.md
View file

@ -1,44 +1,102 @@
# apg.go # Winni's Advanced Password Generator Clone
_apg.go_ is a simple APG-like password generator script written in Go. It tries to replicate the _apg.go_ is a simple APG-like password generator script written in Go. It tries to replicate the
functionality of the functionality of the
"[Automated Password Generator](https://web.archive.org/web/20130313042424/http://www.adel.nursat.kz:80/apg)", "[Automated Password Generator](https://web.archive.org/web/20130313042424/http://www.adel.nursat.kz:80/apg)",
which hasn't been maintained since 2003. Since more and more Unix distributions are abondoning the tool, I was which hasn't been maintained since 2003. Since more and more Unix distributions are abondoning the tool, I was
looking for an alternative. FreeBSD for example recommends "security/makepasswd", which is written in Perl looking for an alternative. FreeBSD for example recommends "security/makepasswd", which is written in Perl
but requires a lot of dependency packages and doesn't offer the feature-set/flexibility of APG. Therefore, as a but requires a lot of dependency packages and doesn't offer the feature-set/flexibility of APG.
first attempt, I decided to write [my own implementation in Perl](https://github.com/wneessen/passwordGen), but since
I just started learning Go, I gave it another try and reproduced apg.pl in Go as apg.go. Again, as I never used
the "pronouncable password" functionality, I left this out in this version.
## Usage Therefore, as a first attempt, I decided to write
Either use the binary releases, unzip them and simply execute them: [my own implementation in Perl](https://github.com/wneessen/passwordGen), but since I just started learning Go,
```sh I gave it another try and reproduced apg.pl in Go as apg.go.
$ unzip apg_v0.2.0_linux_amd64.zip
$ chmod +x apg
$ ./apg
```
Or download the sources and build the binary yourselves:
```sh
$ go build apg.go
$ ./apg
```
## Systemwide installation Since FIPS-181 (pronouncable passwords) has been withdrawn in 2015, I didn't see any use in replicating that
To be a proper APG replacement, i suggest to install it into a directory in your PATH and symlink it to "apg": feature. Therfore apg.go does not support pronouncable passwords.
## Installation
### Binary releases
#### Linux/BSD/MacOS
* Download release
```sh
$ curl -LO https://github.com/wneessen/apg.go/releases/download/v<version>/apg-v<version>-<os>-<architecture>.tar.gz
$ curl -LO https://github.com/wneessen/apg.go/releases/download/v<version>/apg-v<version>-<os>-<architecture>.tar.gz.sha256
```
* Verify the checksum
```sh
$ sha256 apg-v<version>-<os>-<architecture>.tar.gz
$ cat apg-v<version>-<os>-<architecture>.tar.gz.sha256
```
**Make sure the checksum of the downloaded file and the checksum in the .sha256 match**
* Extract archive
```sh
$ tar xzf apg-v<version>-<os>-<architecture>.tar.gz
```
* Execute
```sh
$ ./apg
```
#### Windows
* Download release
```PowerShell
PS> Invoke-RestMethod -Uri https://github.com/wneessen/apg.go/releases/download/v<version>/apg-v<version>-windows-<architecture>.zip -OutFile apg-v<version>-windows-<architecure>.zip
PS> Invoke-RestMethod -Uri https://github.com/wneessen/apg.go/releases/download/v<version>/apg-v<version>-windows-<architecture>.zip.sha256 -OutFile apg-v<version>-windows-<architecure>.zip.sha256
```
* Verify the checksum
```PowerShell
PS> Get-FileHash apg-v<version>-windows-<architecture>.zip | Format-List
PS> type apg-v<version>-windows-<architecture>.zip.sha256
```
**Make sure the checksum of the downloaded file and the checksum in the .sha256 match**
* Extract archive
```PowerShell
PS> Expand-Archive -LiteralPath apg-v<version>-windows-<architecture>
```
* Execute
```PowerShell
PS> cd apg-v<version>-windows-<architecture>
PS> apg.exe
```
### Sources
* Download sources
```sh
$ curl -LO https://github.com/wneessen/apg.go/archive/refs/tags/v<version>.tar.gz
```
* Extract source
```sh
$ tar xzf v<version>.tar.gz
```
* Build binary
```sh
$ cd apg.go-<version>
$ go build -o apg ./...
```
* Execute the brand new binary
```sh
$ ./apg
```
### Systemwide installation
It is recommed to install apg in a directory of your ```$PATH``` environment. To do so run:
(In this example we use ```/usr/local/bin``` as system-wide binary path. YMMV)
```sh ```sh
$ sudo cp apg /usr/local/bin/apg $ sudo cp apg /usr/local/bin/apg
``` ```
## CLI options ## CLI parameters
_apg.go_ replicates some of the parameters of the original APG. Some parameters are different though: _apg.go_ replicates some of the parameters of the original APG. Some parameters are different though:
- ```-m, --minpasslen <length>```: The minimum length of the password to be generated - ```-m <length>```: The minimum length of the password to be generated (Default: 20)
- ```-x, --maxpasslen <length>```: The maximum length of the password to be generated - ```-x <length>```: The maximum length of the password to be generated (Default: 20)
- ```-n, --numofpass <number of passwords>```: The amount of passwords to be generated - ```-n <number of passwords>```: The amount of passwords to be generated (Default: 1)
- ```-E, --exclude <list of characters>```: Do not use the specified characters in generated passwords - ```-E <list of characters>```: Do not use the specified characters in generated passwords
- ```-U, --uppercase```: Use uppercase characters in passwords - ```-M <[LUNSHClunshc]>```: New style password parameters (upper-case enables, lower-case disables)
- ```-N, --numbers```: Use numeric characters in passwords - ```-L```: Use lower-case characters in passwords (Default: on)
- ```-S, --special```: Use special characters in passwords - ```-U```: Use upper-case characters in passwords (Default: on)
- ```-H, --human```: Avoid ambiguous characters in passwords (i. e.: 1, l, I, o, O, 0) - ```-N```: Use numeric characters in passwords (Default: on)
- ```-c, --complex```: Generate complex passwords (implies -U -N -S and disables -H) - ```-S```: Use special characters in passwords (Default: off)
- ```-h, --help```: Show a CLI help text - ```-H```: Avoid ambiguous characters in passwords (i. e.: 1, l, I, o, O, 0) (Default: off)
- ```-v, --version```: Show the version number - ```-C```: Generate complex passwords (implies -L -U -N -S and disables -H) (Default: off)
- ```-l```: Spell generated passwords (Default: off)
- ```-h```: Show a CLI help text
- ```-v```: Show the version number

9
apg.go
View file

@ -8,7 +8,7 @@ import (
// Constants // Constants
const DefaultPwLenght int = 20 const DefaultPwLenght int = 20
const VersionString string = "0.2.4" const VersionString string = "0.2.5"
const PwLowerCharsHuman string = "abcdefghjkmnpqrstuvwxyz" const PwLowerCharsHuman string = "abcdefghjkmnpqrstuvwxyz"
const PwUpperCharsHuman string = "ABCDEFGHJKMNPQRSTUVWXYZ" const PwUpperCharsHuman string = "ABCDEFGHJKMNPQRSTUVWXYZ"
const PwLowerChars string = "abcdefghijklmnopqrstuvwxyz" const PwLowerChars string = "abcdefghijklmnopqrstuvwxyz"
@ -42,8 +42,8 @@ var config cliOpts
func init() { func init() {
// Bool flags // Bool flags
flag.BoolVar(&config.useLowerCase, "L", true, "Use lower case characters in passwords") flag.BoolVar(&config.useLowerCase, "L", true, "Use lower case characters in passwords")
flag.BoolVar(&config.useUpperCase, "U", false, "Use upper case characters in passwords") flag.BoolVar(&config.useUpperCase, "U", true, "Use upper case characters in passwords")
flag.BoolVar(&config.useNumber, "N", false, "Use numbers in passwords") flag.BoolVar(&config.useNumber, "N", true, "Use numbers in passwords")
flag.BoolVar(&config.useSpecial, "S", false, "Use special characters in passwords") flag.BoolVar(&config.useSpecial, "S", false, "Use special characters in passwords")
flag.BoolVar(&config.useComplex, "C", false, "Generate complex passwords (implies -L -U -N -S, disables -H)") flag.BoolVar(&config.useComplex, "C", false, "Generate complex passwords (implies -L -U -N -S, disables -H)")
flag.BoolVar(&config.spellPassword, "l", false, "Spell generated password") flag.BoolVar(&config.spellPassword, "l", false, "Spell generated password")
@ -51,7 +51,7 @@ func init() {
flag.BoolVar(&config.showVersion, "v", false, "Show version") flag.BoolVar(&config.showVersion, "v", false, "Show version")
// Int flags // Int flags
flag.IntVar(&config.minPassLen, "m", 10, "Minimum password length") flag.IntVar(&config.minPassLen, "m", DefaultPwLenght, "Minimum password length")
flag.IntVar(&config.maxPassLen, "x", DefaultPwLenght, "Maxiumum password length") flag.IntVar(&config.maxPassLen, "x", DefaultPwLenght, "Maxiumum password length")
flag.IntVar(&config.numOfPass, "n", 1, "Number of passwords to generate") flag.IntVar(&config.numOfPass, "n", 1, "Number of passwords to generate")
@ -63,6 +63,7 @@ func init() {
flag.Parse() flag.Parse()
if config.showVersion { if config.showVersion {
_, _ = os.Stderr.WriteString("Winni's Advanced Password Generator Clone (apg.go) v" + VersionString + "\n") _, _ = os.Stderr.WriteString("Winni's Advanced Password Generator Clone (apg.go) v" + VersionString + "\n")
_, _ = os.Stderr.WriteString("© 2021 by Winni Neessen\n")
os.Exit(0) os.Exit(0)
} }
} }

View file

@ -88,6 +88,10 @@ func TestGetCharRange(t *testing.T) {
allowedBytes := []int{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', allowedBytes := []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'} 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}
config.useLowerCase = true config.useLowerCase = true
config.useUpperCase = false
config.useNumber = false
config.useSpecial = false
config.humanReadable = false
charRange := getCharRange() charRange := getCharRange()
for _, curChar := range charRange { for _, curChar := range charRange {
searchAllowedBytes := containsByte(allowedBytes, int(curChar)) searchAllowedBytes := containsByte(allowedBytes, int(curChar))
@ -102,6 +106,10 @@ func TestGetCharRange(t *testing.T) {
t.Run("lower_case_only_human_readable", func(t *testing.T) { t.Run("lower_case_only_human_readable", func(t *testing.T) {
allowedBytes := []int{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', allowedBytes := []int{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z'} 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}
config.useLowerCase = true
config.useUpperCase = false
config.useNumber = false
config.useSpecial = false
config.humanReadable = true config.humanReadable = true
charRange := getCharRange() charRange := getCharRange()
for _, curChar := range charRange { for _, curChar := range charRange {
@ -117,9 +125,11 @@ func TestGetCharRange(t *testing.T) {
t.Run("upper_case_only", func(t *testing.T) { t.Run("upper_case_only", func(t *testing.T) {
allowedBytes := []int{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', allowedBytes := []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'} 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}
config.humanReadable = false
config.useLowerCase = false config.useLowerCase = false
config.useUpperCase = true config.useUpperCase = true
config.useNumber = false
config.useSpecial = false
config.humanReadable = false
charRange := getCharRange() charRange := getCharRange()
for _, curChar := range charRange { for _, curChar := range charRange {
searchAllowedBytes := containsByte(allowedBytes, int(curChar)) searchAllowedBytes := containsByte(allowedBytes, int(curChar))
@ -134,6 +144,10 @@ func TestGetCharRange(t *testing.T) {
t.Run("upper_case_only_human_readable", func(t *testing.T) { t.Run("upper_case_only_human_readable", func(t *testing.T) {
allowedBytes := []int{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', allowedBytes := []int{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q',
'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'} 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}
config.useLowerCase = false
config.useUpperCase = true
config.useNumber = false
config.useSpecial = false
config.humanReadable = true config.humanReadable = true
charRange := getCharRange() charRange := getCharRange()
for _, curChar := range charRange { for _, curChar := range charRange {
@ -148,9 +162,11 @@ func TestGetCharRange(t *testing.T) {
// Numbers only // Numbers only
t.Run("numbers_only", func(t *testing.T) { t.Run("numbers_only", func(t *testing.T) {
allowedBytes := []int{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'} allowedBytes := []int{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
config.humanReadable = false config.useLowerCase = false
config.useUpperCase = false config.useUpperCase = false
config.useNumber = true config.useNumber = true
config.useSpecial = false
config.humanReadable = false
charRange := getCharRange() charRange := getCharRange()
for _, curChar := range charRange { for _, curChar := range charRange {
searchAllowedBytes := containsByte(allowedBytes, int(curChar)) searchAllowedBytes := containsByte(allowedBytes, int(curChar))
@ -164,6 +180,10 @@ func TestGetCharRange(t *testing.T) {
// Numbers only (human readable) // Numbers only (human readable)
t.Run("numbers_only_human_readable", func(t *testing.T) { t.Run("numbers_only_human_readable", func(t *testing.T) {
allowedBytes := []int{'2', '3', '4', '5', '6', '7', '8', '9'} allowedBytes := []int{'2', '3', '4', '5', '6', '7', '8', '9'}
config.useLowerCase = false
config.useUpperCase = false
config.useNumber = true
config.useSpecial = false
config.humanReadable = true config.humanReadable = true
charRange := getCharRange() charRange := getCharRange()
for _, curChar := range charRange { for _, curChar := range charRange {
@ -179,9 +199,11 @@ func TestGetCharRange(t *testing.T) {
t.Run("special_chars_only", func(t *testing.T) { t.Run("special_chars_only", func(t *testing.T) {
allowedBytes := []int{'!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', allowedBytes := []int{'!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':',
';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~'} ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~'}
config.humanReadable = false config.useLowerCase = false
config.useUpperCase = false
config.useNumber = false config.useNumber = false
config.useSpecial = true config.useSpecial = true
config.humanReadable = false
charRange := getCharRange() charRange := getCharRange()
for _, curChar := range charRange { for _, curChar := range charRange {
searchAllowedBytes := containsByte(allowedBytes, int(curChar)) searchAllowedBytes := containsByte(allowedBytes, int(curChar))
@ -195,6 +217,10 @@ func TestGetCharRange(t *testing.T) {
// Special characters only (human readable) // Special characters only (human readable)
t.Run("special_chars_only_human_readable", func(t *testing.T) { t.Run("special_chars_only_human_readable", func(t *testing.T) {
allowedBytes := []int{'"', '#', '%', '*', '+', '-', '/', ':', ';', '=', '\\', '_', '|', '~'} allowedBytes := []int{'"', '#', '%', '*', '+', '-', '/', ':', ';', '=', '\\', '_', '|', '~'}
config.useLowerCase = false
config.useUpperCase = false
config.useNumber = false
config.useSpecial = true
config.humanReadable = true config.humanReadable = true
charRange := getCharRange() charRange := getCharRange()
for _, curChar := range charRange { for _, curChar := range charRange {

View file

@ -29,7 +29,7 @@ func getCharRange() string {
charRange = charRange + pwSpecialChars charRange = charRange + pwSpecialChars
} }
if config.excludeChars != "" { if config.excludeChars != "" {
regExp := regexp.MustCompile("[" + config.excludeChars + "]") regExp := regexp.MustCompile("[" + regexp.QuoteMeta(config.excludeChars) + "]")
charRange = regExp.ReplaceAllLiteralString(charRange, "") charRange = regExp.ReplaceAllLiteralString(charRange, "")
} }