mirror of
https://github.com/wneessen/apg-go.git
synced 2024-11-25 15:20:50 +01:00
Remove old code for the v2 refactor
This commit is contained in:
parent
565a714cc6
commit
befa6c2723
23 changed files with 0 additions and 1530 deletions
23
.cirrus.yml
23
.cirrus.yml
|
@ -1,23 +0,0 @@
|
||||||
container:
|
|
||||||
image: golang:latest
|
|
||||||
|
|
||||||
env:
|
|
||||||
GOPROXY: https://proxy.golang.org
|
|
||||||
|
|
||||||
lint_task:
|
|
||||||
name: GolangCI Lint
|
|
||||||
container:
|
|
||||||
image: golangci/golangci-lint:latest
|
|
||||||
run_script: golangci-lint run -v --timeout 5m0s --out-format json > lint-report.json
|
|
||||||
always:
|
|
||||||
golangci_artifacts:
|
|
||||||
path: lint-report.json
|
|
||||||
type: text/json
|
|
||||||
format: golangci
|
|
||||||
|
|
||||||
build_task:
|
|
||||||
modules_cache:
|
|
||||||
folder: $GOPATH/pkg/mod
|
|
||||||
get_script: go get github.com/wneessen/apg-go/cmd/apg
|
|
||||||
build_script: go build github.com/wneessen/apg-go/cmd/apg
|
|
||||||
test_script: go test github.com/wneessen/apg-go/cmd/apg
|
|
19
.gitignore
vendored
19
.gitignore
vendored
|
@ -1,19 +0,0 @@
|
||||||
# Binaries for programs and plugins
|
|
||||||
*.exe
|
|
||||||
*.exe~
|
|
||||||
*.dll
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
apg
|
|
||||||
bin
|
|
||||||
.go
|
|
||||||
build
|
|
||||||
|
|
||||||
# Test binary, built with `go test -c`
|
|
||||||
*.test
|
|
||||||
|
|
||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
|
||||||
*.out
|
|
||||||
|
|
||||||
# Dependency directories (remove the comment below to include it)
|
|
||||||
# vendor/
|
|
|
@ -1,128 +0,0 @@
|
||||||
# Contributor Covenant Code of Conduct
|
|
||||||
|
|
||||||
## Our Pledge
|
|
||||||
|
|
||||||
We as members, contributors, and leaders pledge to make participation in our
|
|
||||||
community a harassment-free experience for everyone, regardless of age, body
|
|
||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
||||||
identity and expression, level of experience, education, socio-economic status,
|
|
||||||
nationality, personal appearance, race, religion, or sexual identity
|
|
||||||
and orientation.
|
|
||||||
|
|
||||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
||||||
diverse, inclusive, and healthy community.
|
|
||||||
|
|
||||||
## Our Standards
|
|
||||||
|
|
||||||
Examples of behavior that contributes to a positive environment for our
|
|
||||||
community include:
|
|
||||||
|
|
||||||
* Demonstrating empathy and kindness toward other people
|
|
||||||
* Being respectful of differing opinions, viewpoints, and experiences
|
|
||||||
* Giving and gracefully accepting constructive feedback
|
|
||||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
||||||
and learning from the experience
|
|
||||||
* Focusing on what is best not just for us as individuals, but for the
|
|
||||||
overall community
|
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
|
||||||
|
|
||||||
* The use of sexualized language or imagery, and sexual attention or
|
|
||||||
advances of any kind
|
|
||||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
||||||
* Public or private harassment
|
|
||||||
* Publishing others' private information, such as a physical or email
|
|
||||||
address, without their explicit permission
|
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
|
||||||
professional setting
|
|
||||||
|
|
||||||
## Enforcement Responsibilities
|
|
||||||
|
|
||||||
Community leaders are responsible for clarifying and enforcing our standards of
|
|
||||||
acceptable behavior and will take appropriate and fair corrective action in
|
|
||||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
||||||
or harmful.
|
|
||||||
|
|
||||||
Community leaders have the right and responsibility to remove, edit, or reject
|
|
||||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
||||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
||||||
decisions when appropriate.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This Code of Conduct applies within all community spaces, and also applies when
|
|
||||||
an individual is officially representing the community in public spaces.
|
|
||||||
Examples of representing our community include using an official e-mail address,
|
|
||||||
posting via an official social media account, or acting as an appointed
|
|
||||||
representative at an online or offline event.
|
|
||||||
|
|
||||||
## Enforcement
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
||||||
reported to the community leaders responsible for enforcement at
|
|
||||||
github+coc@neessen.net.
|
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the
|
|
||||||
reporter of any incident.
|
|
||||||
|
|
||||||
## Enforcement Guidelines
|
|
||||||
|
|
||||||
Community leaders will follow these Community Impact Guidelines in determining
|
|
||||||
the consequences for any action they deem in violation of this Code of Conduct:
|
|
||||||
|
|
||||||
### 1. Correction
|
|
||||||
|
|
||||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
||||||
unprofessional or unwelcome in the community.
|
|
||||||
|
|
||||||
**Consequence**: A private, written warning from community leaders, providing
|
|
||||||
clarity around the nature of the violation and an explanation of why the
|
|
||||||
behavior was inappropriate. A public apology may be requested.
|
|
||||||
|
|
||||||
### 2. Warning
|
|
||||||
|
|
||||||
**Community Impact**: A violation through a single incident or series
|
|
||||||
of actions.
|
|
||||||
|
|
||||||
**Consequence**: A warning with consequences for continued behavior. No
|
|
||||||
interaction with the people involved, including unsolicited interaction with
|
|
||||||
those enforcing the Code of Conduct, for a specified period of time. This
|
|
||||||
includes avoiding interactions in community spaces as well as external channels
|
|
||||||
like social media. Violating these terms may lead to a temporary or
|
|
||||||
permanent ban.
|
|
||||||
|
|
||||||
### 3. Temporary Ban
|
|
||||||
|
|
||||||
**Community Impact**: A serious violation of community standards, including
|
|
||||||
sustained inappropriate behavior.
|
|
||||||
|
|
||||||
**Consequence**: A temporary ban from any sort of interaction or public
|
|
||||||
communication with the community for a specified period of time. No public or
|
|
||||||
private interaction with the people involved, including unsolicited interaction
|
|
||||||
with those enforcing the Code of Conduct, is allowed during this period.
|
|
||||||
Violating these terms may lead to a permanent ban.
|
|
||||||
|
|
||||||
### 4. Permanent Ban
|
|
||||||
|
|
||||||
**Community Impact**: Demonstrating a pattern of violation of community
|
|
||||||
standards, including sustained inappropriate behavior, harassment of an
|
|
||||||
individual, or aggression toward or disparagement of classes of individuals.
|
|
||||||
|
|
||||||
**Consequence**: A permanent ban from any sort of public interaction within
|
|
||||||
the community.
|
|
||||||
|
|
||||||
## Attribution
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
||||||
version 2.0, available at
|
|
||||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
|
||||||
|
|
||||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
|
||||||
enforcement ladder](https://github.com/mozilla/diversity).
|
|
||||||
|
|
||||||
[homepage]: https://www.contributor-covenant.org
|
|
||||||
|
|
||||||
For answers to common questions about this code of conduct, see the FAQ at
|
|
||||||
https://www.contributor-covenant.org/faq. Translations are available at
|
|
||||||
https://www.contributor-covenant.org/translations.
|
|
20
Dockerfile
20
Dockerfile
|
@ -1,20 +0,0 @@
|
||||||
## Build first
|
|
||||||
FROM golang:latest as builder
|
|
||||||
RUN mkdir /builddir
|
|
||||||
ADD . /builddir/
|
|
||||||
WORKDIR /builddir
|
|
||||||
RUN CGO_ENABLED=0 go build -a -installsuffix cgo -ldflags '-w -s -extldflags "-static"' -o apg-go \
|
|
||||||
github.com/wneessen/apg-go/cmd/apg
|
|
||||||
|
|
||||||
## Create scratch image
|
|
||||||
FROM scratch
|
|
||||||
LABEL maintainer="wn@neessen.net"
|
|
||||||
COPY ["docker-files/passwd", "/etc/passwd"]
|
|
||||||
COPY ["docker-files/group", "/etc/group"]
|
|
||||||
COPY --from=builder ["/etc/ssl/certs/ca-certificates.crt", "/etc/ssl/cert.pem"]
|
|
||||||
COPY --chown=apg-go ["LICENSE", "/apg-go/LICENSE"]
|
|
||||||
COPY --chown=apg-go ["README.md", "/apg-go/README.md"]
|
|
||||||
COPY --from=builder --chown=apg-go ["/builddir/apg-go", "/apg-go/apg-go"]
|
|
||||||
WORKDIR /apg-go
|
|
||||||
USER apg-go
|
|
||||||
ENTRYPOINT ["/apg-go/apg-go"]
|
|
21
LICENSE
21
LICENSE
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2021 Winni Neessen
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
272
README.md
272
README.md
|
@ -1,272 +0,0 @@
|
||||||
# A "Automated Password Generator"-clone
|
|
||||||
[![Go Reference](https://pkg.go.dev/badge/github.com/wneessen/apg-go.svg)](https://pkg.go.dev/github.com/wneessen/apg-go) [![Go Report Card](https://goreportcard.com/badge/github.com/wneessen/apg-go)](https://goreportcard.com/report/github.com/wneessen/apg-go) [![Build Status](https://api.cirrus-ci.com/github/wneessen/apg-go.svg)](https://cirrus-ci.com/github/wneessen/apg-go) ![CodeQL workflow](https://github.com/wneessen/apg-go/actions/workflows/codeql-analysis.yml/badge.svg) <a href="https://ko-fi.com/D1D24V9IX"><img src="https://uploads-ssl.webflow.com/5c14e387dab576fe667689cf/5cbed8a4ae2b88347c06c923_BuyMeACoffee_blue.png" height="20" alt="buy ma a coffee"></a>
|
|
||||||
|
|
||||||
_apg-go_ is a simple APG-like password generator written in Go. It tries to replicate the
|
|
||||||
functionality of the
|
|
||||||
"[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
|
|
||||||
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.
|
|
||||||
|
|
||||||
Since FIPS-181 (pronouncable passwords) has been withdrawn in 2015, apg-go does not follow this standard. Instead
|
|
||||||
it implements the [Koremutake Syllables System](https://shorl.com/koremutake.php) in its pronouncable password mode.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
### Docker
|
|
||||||
There is a ready-to-use Docker image hosted on Github.
|
|
||||||
|
|
||||||
* Download the image:
|
|
||||||
```shell
|
|
||||||
$ docker pull ghcr.io/wneessen/apg-go:main
|
|
||||||
```
|
|
||||||
* Run the image:
|
|
||||||
```shell
|
|
||||||
$ docker run ghcr.io/wneessen/apg-go:main
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ports/Packages
|
|
||||||
#### FreeBSD
|
|
||||||
apg-go can be found as `/security/apg` in the [FreeBSD ports](https://cgit.freebsd.org/ports/tree/security/apg)
|
|
||||||
tree.
|
|
||||||
#### Arch Linux
|
|
||||||
Find apg-go in [Arch Linux AUR](https://aur.archlinux.org/packages/apg-go/). \
|
|
||||||
Alternatively use the [PKGBUILD](https://github.com/wneessen/apg-go/tree/main/buildfiles/arch-linux) file
|
|
||||||
in this git repository
|
|
||||||
### 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
|
|
||||||
$ sudo cp apg /usr/local/bin/apg
|
|
||||||
```
|
|
||||||
|
|
||||||
## Programmatic interface
|
|
||||||
Since v0.4.0 the CLI and the main package functionality have been separated from each other, which makes
|
|
||||||
it easier to use the `apg-go` package in other Go code as well. This way you can make of the password
|
|
||||||
generation in your own code without having to rely on the actual apg-go binary.
|
|
||||||
|
|
||||||
Code examples on how to use the package can be found in the [example-code](example-code) directory.
|
|
||||||
|
|
||||||
## Usage examples
|
|
||||||
### Default behaviour
|
|
||||||
By default apg-go will generate 6 passwords, with a minimum length of 12 characters and a
|
|
||||||
maxiumum length of 20 characters. The generated password will use a character set constructed
|
|
||||||
from lower case, upper case and numeric characters.
|
|
||||||
```shell
|
|
||||||
$ ./apg-go
|
|
||||||
R8rCC8bw5NvJmTUK2g
|
|
||||||
cHB9qogTbfdzFgnH
|
|
||||||
hoHfpWAHHSNa4Q
|
|
||||||
QyjscIsZkQGh
|
|
||||||
904YqsU5SnoqLo2w
|
|
||||||
utdFKXdeiXFzM
|
|
||||||
```
|
|
||||||
### Modifying the character sets
|
|
||||||
#### Old style
|
|
||||||
Let's assume you want to generate a single password, constructed out of upper case, numeric
|
|
||||||
and special characters. Since lower case is part of the default set, you would need to disable them
|
|
||||||
by setting the `-L` parameter. In addition you would set the `-S` parameter to enable special
|
|
||||||
characters. Finally the parameter `-n 1` is needed to keep apg-go from generating more than one
|
|
||||||
password:
|
|
||||||
```shell
|
|
||||||
$ ./apg-go -n 1 -L -S
|
|
||||||
XY7>}H@5U40&_A1*9I$
|
|
||||||
```
|
|
||||||
|
|
||||||
#### New/modern style
|
|
||||||
Since the old style switches can be kind of confusing, it is recommended to use the "new style"
|
|
||||||
parameters instead. The new style is all combined in the `-M` parameter. Using the upper case
|
|
||||||
version of a parameter argument enables a feature, while the lower case version disabled it. The
|
|
||||||
previous example could be represented like this in new style:
|
|
||||||
```shell
|
|
||||||
$ ./apg-go -n 1 -M lUSN
|
|
||||||
$</K?*|M)%8\U$5JA5~
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Human readability
|
|
||||||
Generated passwords can sometimes be a bit hard to read for humans, especially when ambiguous
|
|
||||||
characters are part of the password. Some characters in the ASCII character set look similar to
|
|
||||||
each other. In example it can be hard to differentiate an upper case I from a lower case l.
|
|
||||||
Same applies to the number zero (0) and the upper case O. To not run into issues with human
|
|
||||||
readability, you can set the `-H` parameter to toggle on the "human readable" feature. When the
|
|
||||||
option is set, apg-go will avoid using any of the typical ambiguous characters in the generated
|
|
||||||
passwords.
|
|
||||||
```shell
|
|
||||||
$ ./apg-go -n 1 -M LUSN -H
|
|
||||||
YpranThY3b6b5%\6ARx
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Character exclusion
|
|
||||||
Let's assume, that for whatever reason, your generated password can never include a colon (:) sign. For
|
|
||||||
this specific case, you can use the `-E` parameter to specify a list of characters that are to be excluded
|
|
||||||
from the password generation character set:
|
|
||||||
```shell
|
|
||||||
$ ./apg-go -n 1 -M lUSN -H -E :
|
|
||||||
~B2\%E_|\VV|/5C7EF=
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Complex passwords
|
|
||||||
If you want to generate complex passwords, there is a shortcut for this as well. By setting the `-C`
|
|
||||||
parameter, apg-go will automatically default to the most secure settings. The complex parameter
|
|
||||||
basically implies that the password will use all available characters (lower case, upper case,
|
|
||||||
numeric and special) and will make sure that human readability is disabled.
|
|
||||||
```shell
|
|
||||||
$ ./apg-go -n 1 -C
|
|
||||||
{q6cvz9le5_fo"X7
|
|
||||||
```
|
|
||||||
|
|
||||||
### Password length
|
|
||||||
By default, apg-go will generate a password with a random length between 12 and 20 characters. If you
|
|
||||||
want to be more specific, you can use the `-m` and `-x` parameters to override the defaults. Let's
|
|
||||||
assume you want a single complex password with a length of exactly 32 characters, you can do so by
|
|
||||||
running:
|
|
||||||
```shell
|
|
||||||
$ ./apg-go -n 1 -C -m 32 -x 32
|
|
||||||
5lc&HBvx=!EUY*;'/t&>B|~sudhtyDBu
|
|
||||||
```
|
|
||||||
|
|
||||||
### Password spelling
|
|
||||||
If you need to read out a password, it can be helpful to know the corresponding word for that character in
|
|
||||||
the phonetic alphabet. By setting the `-l` parameter, agp-go will provide you with the phonetic spelling
|
|
||||||
(english language) of your newly created password:
|
|
||||||
```shell
|
|
||||||
$ ./apg-go -n 1 -M LUSN -H -E : -l
|
|
||||||
fUTDKeFsU+zn3r= (foxtrot/Uniform/Tango/Delta/Kilo/echo/Foxtrot/sierra/Uniform/PLUS_SIGN/zulu/november/THREE/romeo/EQUAL_SIGN)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pronouncable passwords
|
|
||||||
Since v0.4.0 apg-go supports pronouncable passwords, anologous to the original c-apg using the `-a 0`
|
|
||||||
flag. The original c-apg implemented FIPS-181, which was withdrawn in 2015 for generating pronouncable
|
|
||||||
passwords. Since the standard is not recommended anymore, `apg-go` instead make use of the
|
|
||||||
[Koremutake Syllables System](https://shorl.com/koremutake.php). Similar to the original apg, `agp-go`
|
|
||||||
will automatically randomly add special characters and number (from the human-readable pool) to each
|
|
||||||
generated pronouncable password. Additionally it will perform a "coinflip" for each Koremutake syllable
|
|
||||||
and decided if it should switch the case of one of the characters to an upper-case character.
|
|
||||||
|
|
||||||
Using the `-t` parameter, `apg-go` will display a spelled out version of the pronouncable password, where
|
|
||||||
each syllable or number/special character is seperated with a "-" (dash) and if the syllable is not a
|
|
||||||
Koremutake syllable the character will be spelled out the same was as with activated `-l` in the
|
|
||||||
non-pronouncable password mode (`-a 1`).
|
|
||||||
|
|
||||||
**Note on password length**: The `-m` and `-x` parameters will work in prouncable password mode, but
|
|
||||||
please keep in mind, that due to the nature how syllables work, your generated password might exceed
|
|
||||||
the desired length by one complete syllable (which can be up to 3 characters long).
|
|
||||||
|
|
||||||
**Security consideration:** Please keep in mind, that pronouncable passwords are less secure then truly
|
|
||||||
randomly created passwords, due to the nature how syllables work. As a rule of thumb, it is recommended
|
|
||||||
to multiply the length of your generated pronouncable passwords by at least 1.5 times, compared to truly
|
|
||||||
randomly generated passwords. It might also be helpful to run the pronoucable password mode with enabled
|
|
||||||
"[HIBP](#have-i-been-pwned)" flag, so that each generated password is automatically checked against "Have I Been Pwned"
|
|
||||||
database.
|
|
||||||
```shell
|
|
||||||
$ ./apg-go -a 0 -n 1
|
|
||||||
KebrutinernMy
|
|
||||||
|
|
||||||
$ ./apg-go -a 0 -n 1 -m 15 -x 15 -t
|
|
||||||
pEnbocydrageT*En (pEn-bo-cy-dra-geT-ASTERISK-En)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Have I Been Pwned
|
|
||||||
Even though, the passwords that apg-go generated for you, are secure, there is a minimal chance, that
|
|
||||||
someone on the planet used exactly the same password before and that this person was part of an
|
|
||||||
internet leak or hack, which exposed the password to the public. Such passwords are not considered
|
|
||||||
secure anymore as they usually land on public available password lists, that are used by crackers.
|
|
||||||
|
|
||||||
To be on the safe side, you can use the `-p` parameter, to enable a HIBP check. When the feature is
|
|
||||||
enabled, apg-go will check the HIBP database at https://haveibeenpwned.com if that password has been
|
|
||||||
leaked before and provide you with a warning if that is the case.
|
|
||||||
|
|
||||||
Please be aware, that this is a live check against the HIBP API, which not only requires internet
|
|
||||||
connectivity, but also might take between 500ms to 1s to complete. When you generating a bigger list
|
|
||||||
of password `-n 100`, the process could take much longer than without the `-p` feature enabled.
|
|
||||||
|
|
||||||
## CLI parameters
|
|
||||||
_apg-go_ replicates most of the parameters of the original c-apg. Some parameters are different though:
|
|
||||||
|
|
||||||
- `-a <algorithm>`: Choose password generation algorithm (Default: 1)
|
|
||||||
- `0`: Pronouncable password generation (Koremutake syllables)
|
|
||||||
- `1`: Random password generation according to password modes/flags
|
|
||||||
- `-m <length>`: The minimum length of the password to be generated (Default: 12)
|
|
||||||
- `-x <length>`: The maximum length of the password to be generated (Default: 20)
|
|
||||||
- `-n <number of passwords>`: The amount of passwords to be generated (Default: 6)
|
|
||||||
- `-E <list of characters>`: Do not use the specified characters in generated passwords
|
|
||||||
- `-M <[LUNSHClunshc]>`: New style password parameters (upper-case enables, lower-case disables)
|
|
||||||
- `-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, O, 0) (Default: off)
|
|
||||||
- `-C`: Generate complex passwords (implies -L -U -N -S and disables -H) (Default: off)
|
|
||||||
- `-l`: Spell generated passwords in random password mode (Default: off)
|
|
||||||
- `-t`: Spell generated passwords in pronouncable password mode (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 a CLI help text
|
|
||||||
- `-v`: Show the version number
|
|
||||||
|
|
||||||
## Contributors
|
|
||||||
Thanks to the following people for contributing to the apg-go codebase:
|
|
||||||
* [Romain Tartière](https://github.com/smortex)
|
|
||||||
* [Abraham Ingersoll](https://github.com/aberoham)
|
|
||||||
* [Vinícius Zavam](https://github.com/egypcio) (Maintaining the FreeBSD port)
|
|
|
@ -1,6 +0,0 @@
|
||||||
# Security Policy
|
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
|
||||||
|
|
||||||
Please send a mail to apg-go@pebcak.de when you found a security issue in apg-go, even when you are not 100% certain
|
|
||||||
that it is actually a security issue. Typically, you will receive an answer within a day or even within a few hours.
|
|
|
@ -1,32 +0,0 @@
|
||||||
# Maintainer: "Winni Neessen (https://pebcak.de)
|
|
||||||
|
|
||||||
pkgname=apg-go
|
|
||||||
pkgver=0.4.1
|
|
||||||
pkgrel=1
|
|
||||||
pkgdesc='A "Automated Password Generator"-clone'
|
|
||||||
arch=('i686' 'x86_64' 'armv6h' 'armv7h' 'aarch64')
|
|
||||||
url='https://github.com/wneessen/apg-go'
|
|
||||||
license=('MIT')
|
|
||||||
makedepends=('go')
|
|
||||||
source=("https://github.com/wneessen/${pkgname}/archive/refs/tags/v${pkgver}.tar.gz")
|
|
||||||
sha256sums=('64769495843e2c59fc5513a106e58f8751f1649ff8bf6c38cd141322523deea8')
|
|
||||||
|
|
||||||
prepare() {
|
|
||||||
cd "${pkgname}-${pkgver}"
|
|
||||||
mkdir -p build/
|
|
||||||
}
|
|
||||||
|
|
||||||
build() {
|
|
||||||
cd "${pkgname}-${pkgver}"
|
|
||||||
go build -ldflags="-s -w" -o build/${pkgname} github.com/wneessen/apg-go/cmd/apg
|
|
||||||
}
|
|
||||||
|
|
||||||
package() {
|
|
||||||
# binary
|
|
||||||
install -D -m755 "${srcdir}/${pkgname}-${pkgver}/build/${pkgname}" \
|
|
||||||
"${pkgdir}/usr/bin/apg"
|
|
||||||
|
|
||||||
# license
|
|
||||||
install -Dm644 "${srcdir}/${pkgname}-${pkgver}/LICENSE" \
|
|
||||||
"${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
# $OpenBSD$
|
|
||||||
|
|
||||||
COMMENT = "automated password generator" clone written in Go
|
|
||||||
|
|
||||||
GH_ACCOUNT = wneessen
|
|
||||||
GH_PROJECT = apg-go
|
|
||||||
GH_TAGNAME = v0.3.2
|
|
||||||
|
|
||||||
CATEGORIES = security
|
|
||||||
|
|
||||||
MAINTAINER = Winni Neessen <wn@neessen.net>
|
|
||||||
|
|
||||||
# MIT
|
|
||||||
PERMIT_PACKAGE = Yes
|
|
||||||
|
|
||||||
MODULES = lang/go
|
|
||||||
MODGO_TYPE = bin
|
|
||||||
|
|
||||||
ALL_TARGET = wneessen/apg-go/cmd/apg
|
|
||||||
|
|
||||||
.include <bsd.port.mk>
|
|
|
@ -1,2 +0,0 @@
|
||||||
SHA256 (apg-go-0.3.2.tar.gz) = QvCC0vVNHLIOHW1jwdkjJVtxEVHJNwQfZBZBgHWM4OQ=
|
|
||||||
SIZE (apg-go-0.3.2.tar.gz) = 20114
|
|
|
@ -1,14 +0,0 @@
|
||||||
apg-go is a simple APG-like password generator written in Go.
|
|
||||||
|
|
||||||
It tries to replicate the functionality of the "Automated Password Generator",
|
|
||||||
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 but requires a lot
|
|
||||||
of dependency packages and doesn't offer the feature-set/flexibility of APG.
|
|
||||||
|
|
||||||
Since FIPS-181 (pronouncable passwords) has been withdrawn in 2015, there is
|
|
||||||
no use in replicating that feature. Therfore apg-go does not support
|
|
||||||
pronouncable passwords.
|
|
||||||
|
|
||||||
For feature requests or bug reports, please create an issue in the Github
|
|
||||||
repository at https://github.com/wneessen/apg-go
|
|
|
@ -1,2 +0,0 @@
|
||||||
@comment $OpenBSD: PLIST,v$
|
|
||||||
@bin bin/apg-go
|
|
|
@ -1,64 +0,0 @@
|
||||||
package chars
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/wneessen/apg-go/config"
|
|
||||||
"regexp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PwLowerCharsHuman is the range of lower-case characters in human-readable mode
|
|
||||||
const PwLowerCharsHuman string = "abcdefghjkmnpqrstuvwxyz"
|
|
||||||
|
|
||||||
// PwUpperCharsHuman is the range of upper-case characters in human-readable mode
|
|
||||||
const PwUpperCharsHuman string = "ABCDEFGHJKMNPQRSTUVWXYZ"
|
|
||||||
|
|
||||||
// PwLowerChars is the range of lower-case characters
|
|
||||||
const PwLowerChars string = "abcdefghijklmnopqrstuvwxyz"
|
|
||||||
|
|
||||||
// PwUpperChars is the range of upper-case characters
|
|
||||||
const PwUpperChars string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
||||||
|
|
||||||
// PwSpecialCharsHuman is the range of special characters in human-readable mode
|
|
||||||
const PwSpecialCharsHuman string = "\"#%*+-/:;=\\_|~"
|
|
||||||
|
|
||||||
// PwSpecialChars is the range of special characters
|
|
||||||
const PwSpecialChars string = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
|
|
||||||
|
|
||||||
// PwNumbersHuman is the range of numbers in human-readable mode
|
|
||||||
const PwNumbersHuman string = "23456789"
|
|
||||||
|
|
||||||
// PwNumbers is the range of numbers
|
|
||||||
const PwNumbers string = "1234567890"
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
pwUpperChars = PwUpperCharsHuman
|
|
||||||
pwLowerChars = PwLowerCharsHuman
|
|
||||||
pwNumbers = PwNumbersHuman
|
|
||||||
pwSpecialChars = PwSpecialCharsHuman
|
|
||||||
}
|
|
||||||
|
|
||||||
var charRange string
|
|
||||||
if config.UseLowerCase {
|
|
||||||
charRange = charRange + pwLowerChars
|
|
||||||
}
|
|
||||||
if config.UseUpperCase {
|
|
||||||
charRange = charRange + pwUpperChars
|
|
||||||
}
|
|
||||||
if config.UseNumber {
|
|
||||||
charRange = charRange + pwNumbers
|
|
||||||
}
|
|
||||||
if config.UseSpecial {
|
|
||||||
charRange = charRange + pwSpecialChars
|
|
||||||
}
|
|
||||||
if config.ExcludeChars != "" {
|
|
||||||
regExp := regexp.MustCompile("[" + regexp.QuoteMeta(config.ExcludeChars) + "]")
|
|
||||||
charRange = regExp.ReplaceAllLiteralString(charRange, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
return charRange
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
package chars
|
|
||||||
|
|
||||||
// KoremutakeSyllables is a slightly modified Koremutake syllables list based on
|
|
||||||
// the mechanism described on https://shorl.com/koremutake.php
|
|
||||||
var KoremutakeSyllables = []string{"ba", "be", "bi", "bo", "bu", "by", "da", "de", "di",
|
|
||||||
"do", "du", "dy", "fe", "fi", "fo", "fu", "fy", "ga", "ge", "gi", "go", "gu",
|
|
||||||
"gy", "ha", "he", "hi", "ho", "hu", "hy", "ja", "je", "ji", "jo", "ju", "jy",
|
|
||||||
"ka", "ke", "ko", "ku", "ky", "la", "le", "li", "lo", "lu", "ly", "ma",
|
|
||||||
"me", "mi", "mo", "mu", "my", "na", "ne", "ni", "no", "nu", "ny", "pa", "pe",
|
|
||||||
"pi", "po", "pu", "py", "ra", "re", "ri", "ro", "ru", "ry", "sa", "se",
|
|
||||||
"si", "so", "su", "sy", "ta", "te", "ti", "to", "tu", "ty", "va", "ve", "vi",
|
|
||||||
"vo", "vu", "vy", "bra", "bre", "bri", "bro", "bru", "bry", "dra", "dre",
|
|
||||||
"dri", "dro", "dru", "dry", "fra", "fre", "fri", "fro", "fru", "fry", "gra",
|
|
||||||
"gre", "gri", "gro", "gru", "gry", "pra", "pre", "pri", "pro", "pru",
|
|
||||||
"pry", "sta", "ste", "sti", "sto", "stu", "sty", "tra", "tre", "er", "ed",
|
|
||||||
"in", "ex", "al", "en", "an", "ad", "or", "at", "ca", "ap", "el", "ci", "an",
|
|
||||||
"et", "it", "ob", "of", "af", "au", "cy", "im", "op", "co", "up", "ing",
|
|
||||||
"con", "ter", "com", "per", "ble", "der", "cal", "man", "est", "for", "mer",
|
|
||||||
"col", "ful", "get", "low", "son", "tle", "day", "pen", "pre", "ten",
|
|
||||||
"tor", "ver", "ber", "can", "ple", "fer", "gen", "den", "mag", "sub", "sur",
|
|
||||||
"men", "min", "out", "tal", "but", "cit", "cle", "cov", "dif", "ern",
|
|
||||||
"eve", "hap", "ket", "nal", "sup", "ted", "tem", "tin", "tro", "tro"}
|
|
148
cmd/apg/apg.go
148
cmd/apg/apg.go
|
@ -1,148 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"github.com/wneessen/apg-go/chars"
|
|
||||||
"github.com/wneessen/apg-go/config"
|
|
||||||
"github.com/wneessen/apg-go/random"
|
|
||||||
"github.com/wneessen/apg-go/spelling"
|
|
||||||
"github.com/wneessen/go-hibp"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// VersionString represents the current version of the apg-go CLI
|
|
||||||
const VersionString string = "0.4.1"
|
|
||||||
|
|
||||||
// Help text
|
|
||||||
const usage = `apg-go // A "Automated Password Generator"-clone
|
|
||||||
Copyright (c) 2021 Winni Neessen
|
|
||||||
|
|
||||||
apg [-a <algo>] [-m <length>] [-x <length>] [-L] [-U] [-N] [-S] [-H] [-C]
|
|
||||||
[-l] [-M mode] [-E char_string] [-n num_of_pass] [-v] [-h] [-t]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-a ALGORITH Choose the password generation algorithm (Default: 1)
|
|
||||||
- 0: pronounceable password generation (koremutake syllables)
|
|
||||||
- 1: random password generation according to password modes/flags
|
|
||||||
-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)
|
|
||||||
- Note: 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 configuration
|
|
||||||
log.SetFlags(log.Ltime | log.Ldate | log.Lshortfile)
|
|
||||||
|
|
||||||
// Read and parse flags
|
|
||||||
flag.Usage = func() { _, _ = fmt.Fprintf(os.Stderr, "%s\n", usage) }
|
|
||||||
var cfgObj = config.New()
|
|
||||||
|
|
||||||
// Show version and exit
|
|
||||||
if cfgObj.ShowVersion {
|
|
||||||
_, _ = os.Stderr.WriteString(`apg-go // A "Automated Password Generator"-clone v` + VersionString + "\n")
|
|
||||||
_, _ = os.Stderr.WriteString("OS: " + runtime.GOOS + " // Arch: " + runtime.GOARCH + " \n")
|
|
||||||
_, _ = os.Stderr.WriteString("(C) 2021 by Winni Neessen\n")
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pwList := make([]string, 0)
|
|
||||||
sylList := map[string][]string{}
|
|
||||||
|
|
||||||
// Choose the type of password generation based on the selected algo
|
|
||||||
for i := 0; i < cfgObj.NumOfPass; i++ {
|
|
||||||
pwLength := config.GetPwLengthFromParams(&cfgObj)
|
|
||||||
|
|
||||||
switch cfgObj.PwAlgo {
|
|
||||||
case 0:
|
|
||||||
pwString := ""
|
|
||||||
pwSyls := make([]string, 0)
|
|
||||||
|
|
||||||
charSylSet := chars.KoremutakeSyllables
|
|
||||||
charSylSet = append(charSylSet,
|
|
||||||
strings.Split(chars.PwNumbersHuman, "")...)
|
|
||||||
charSylSet = append(charSylSet,
|
|
||||||
strings.Split(chars.PwSpecialCharsHuman, "")...)
|
|
||||||
charSylSetLen := len(charSylSet)
|
|
||||||
for len(pwString) < pwLength {
|
|
||||||
randNum, err := random.GetNum(charSylSetLen)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("error generating Koremutake syllable: %s", err)
|
|
||||||
}
|
|
||||||
nextSyl := charSylSet[randNum]
|
|
||||||
if random.CoinFlip() {
|
|
||||||
sylLen := len(nextSyl)
|
|
||||||
charPos, err := random.GetNum(sylLen)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("error generating random number: %s", err)
|
|
||||||
}
|
|
||||||
ucChar := string(nextSyl[charPos])
|
|
||||||
nextSyl = strings.ReplaceAll(nextSyl, ucChar, strings.ToUpper(ucChar))
|
|
||||||
}
|
|
||||||
|
|
||||||
pwString += nextSyl
|
|
||||||
pwSyls = append(pwSyls, nextSyl)
|
|
||||||
}
|
|
||||||
pwList = append(pwList, pwString)
|
|
||||||
sylList[pwString] = pwSyls
|
|
||||||
default:
|
|
||||||
charRange := chars.GetRange(&cfgObj)
|
|
||||||
pwString, err := random.GetChar(charRange, pwLength)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("error generating random character range: %s\n", err)
|
|
||||||
}
|
|
||||||
pwList = append(pwList, pwString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range pwList {
|
|
||||||
switch cfgObj.OutputMode {
|
|
||||||
case 1:
|
|
||||||
spelledPw, err := spelling.String(p)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("error spelling out password: %s\n", err)
|
|
||||||
}
|
|
||||||
fmt.Printf("%v (%v)\n", p, spelledPw)
|
|
||||||
case 2:
|
|
||||||
fmt.Printf("%s", p)
|
|
||||||
if cfgObj.SpellPron {
|
|
||||||
spelledPw, err := spelling.Koremutake(sylList[p])
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("error spelling out password: %s", err)
|
|
||||||
}
|
|
||||||
fmt.Printf(" (%s)", spelledPw)
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
default:
|
|
||||||
fmt.Println(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfgObj.CheckHibp {
|
|
||||||
hc := hibp.New(hibp.WithHTTPTimeout(time.Second*2), hibp.WithPwnedPadding())
|
|
||||||
pwnObj, _, err := hc.PwnedPassAPI.CheckPassword(p)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("unable to check HIBP database: %v", err)
|
|
||||||
}
|
|
||||||
if pwnObj != nil && pwnObj.Count != 0 {
|
|
||||||
fmt.Print("^-- !!WARNING: The previously generated password was found in HIBP database. Do not use it!!\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,288 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/wneessen/apg-go/chars"
|
|
||||||
"github.com/wneessen/apg-go/config"
|
|
||||||
"github.com/wneessen/apg-go/random"
|
|
||||||
"github.com/wneessen/apg-go/spelling"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var cfgObj config.Config
|
|
||||||
|
|
||||||
// Make sure the flags are initialized
|
|
||||||
var _ = func() bool {
|
|
||||||
testing.Init()
|
|
||||||
cfgObj = config.New()
|
|
||||||
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 := random.GetNum(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", config.DefaultMinLength, config.DefaultMaxLength},
|
|
||||||
{"pwLength 0 to 1", 0, 1},
|
|
||||||
{"pwLength 1 to 10", 0, 10},
|
|
||||||
{"pwLength 10 to 100", 10, 100},
|
|
||||||
}
|
|
||||||
|
|
||||||
charRange := chars.GetRange(&cfgObj)
|
|
||||||
for _, testCase := range testTable {
|
|
||||||
t.Run(testCase.testName, func(t *testing.T) {
|
|
||||||
cfgObj.MinPassLen = testCase.minLength
|
|
||||||
cfgObj.MaxPassLen = testCase.maxLength
|
|
||||||
pwLength := config.GetPwLengthFromParams(&cfgObj)
|
|
||||||
for i := 0; i < 1000; i++ {
|
|
||||||
pwString, err := random.GetChar(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 := random.GetChar(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 := random.GetChar(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 := random.GetChar(charRange, -2000)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("Generated random characters expected to fail, but returned a value => %v",
|
|
||||||
randChar)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test getCharRange() with different cfgObj 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) {
|
|
||||||
cfgObj.UseLowerCase = testCase.useLowerCase
|
|
||||||
cfgObj.UseUpperCase = testCase.useUpperCase
|
|
||||||
cfgObj.UseNumber = testCase.useNumber
|
|
||||||
cfgObj.UseSpecial = testCase.useSpecial
|
|
||||||
cfgObj.HumanReadable = testCase.humanReadable
|
|
||||||
charRange := chars.GetRange(&cfgObj)
|
|
||||||
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 := spelling.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) {
|
|
||||||
cfgObj.UseUpperCase = true
|
|
||||||
cfgObj.UseLowerCase = true
|
|
||||||
cfgObj.UseNumber = true
|
|
||||||
cfgObj.UseSpecial = true
|
|
||||||
cfgObj.HumanReadable = false
|
|
||||||
charRange := chars.GetRange(&cfgObj)
|
|
||||||
for _, curChar := range charRange {
|
|
||||||
_, err := spelling.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 := spelling.String(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++ {
|
|
||||||
_, _ = random.GetNum(100000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Benchmark: Random char generation
|
|
||||||
func BenchmarkGetRandChar(b *testing.B) {
|
|
||||||
charRange := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\"#/!\\$%&+-*.,?=()[]{}:;~^|"
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_, _ = random.GetChar(charRange, 20)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Benchmark: Random char generation
|
|
||||||
func BenchmarkConvertChar(b *testing.B) {
|
|
||||||
|
|
||||||
cfgObj.UseUpperCase = true
|
|
||||||
cfgObj.UseLowerCase = true
|
|
||||||
cfgObj.UseNumber = true
|
|
||||||
cfgObj.UseSpecial = true
|
|
||||||
cfgObj.HumanReadable = false
|
|
||||||
charRange := chars.GetRange(&cfgObj)
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
charToConv, _ := random.GetChar(charRange, 1)
|
|
||||||
charBytes := []byte(charToConv)
|
|
||||||
_, _ = spelling.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
|
|
||||||
}
|
|
194
config/config.go
194
config/config.go
|
@ -1,194 +0,0 @@
|
||||||
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 // Minimum password length
|
|
||||||
MaxPassLen int // Maximum password length
|
|
||||||
NumOfPass int // Number of passwords to be generated
|
|
||||||
useComplex bool // Force complex password generation (implies all other Use* Options to be true)
|
|
||||||
UseLowerCase bool // Allow lower-case chars in passwords
|
|
||||||
UseUpperCase bool // Allow upper-case chars in password
|
|
||||||
UseNumber bool // Allow numbers in passwords
|
|
||||||
UseSpecial bool // Allow special chars in passwords
|
|
||||||
HumanReadable bool // Generated passwords use the "human readable" character set
|
|
||||||
CheckHibp bool // Check generated are validated against the HIBP API for possible leaks
|
|
||||||
ExcludeChars string // List of characters to be excluded from the PW generation charset
|
|
||||||
NewStyleModes string // Use the "new style" parameters instead of the single params
|
|
||||||
spellPassword bool // Spell out passwords in the output
|
|
||||||
ShowHelp bool // Display the help message in the CLI
|
|
||||||
ShowVersion bool // Display the version string in the CLI
|
|
||||||
OutputMode int // Interal parameter to control the output mode of the CLI
|
|
||||||
PwAlgo int // PW generation algorithm to use (0: random PW based on flags, 1: pronouncable)
|
|
||||||
SpellPron bool // Spell out the pronouncable password
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
// DefaultPwAlgo reflects the default password generation algorithm
|
|
||||||
const DefaultPwAlgo int = 1
|
|
||||||
|
|
||||||
// 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.IntVar(&config.PwAlgo, "a", DefaultPwAlgo, "Password generation algorithm")
|
|
||||||
flag.BoolVar(&config.SpellPron, "t", false, "In pronouncable password mode, spell out the password")
|
|
||||||
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 &&
|
|
||||||
!config.UseLowerCase &&
|
|
||||||
!config.UseNumber &&
|
|
||||||
!config.UseSpecial {
|
|
||||||
log.Fatalf("No password mode set. Cannot generate password from empty character set.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set output mode
|
|
||||||
switch config.PwAlgo {
|
|
||||||
case 0:
|
|
||||||
config.OutputMode = 2
|
|
||||||
default:
|
|
||||||
config.OutputMode = 0
|
|
||||||
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
|
|
||||||
case 's':
|
|
||||||
config.UseSpecial = false
|
|
||||||
case 'N':
|
|
||||||
config.UseNumber = true
|
|
||||||
case 'n':
|
|
||||||
config.UseNumber = false
|
|
||||||
case 'L':
|
|
||||||
config.UseLowerCase = true
|
|
||||||
case 'l':
|
|
||||||
config.UseLowerCase = false
|
|
||||||
case 'U':
|
|
||||||
config.UseUpperCase = true
|
|
||||||
case 'u':
|
|
||||||
config.UseUpperCase = false
|
|
||||||
case 'H':
|
|
||||||
config.HumanReadable = true
|
|
||||||
case 'h':
|
|
||||||
config.HumanReadable = false
|
|
||||||
case 'C':
|
|
||||||
config.useComplex = true
|
|
||||||
case 'c':
|
|
||||||
config.useComplex = false
|
|
||||||
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 +0,0 @@
|
||||||
apg-go:*:1000:apg-go
|
|
|
@ -1 +0,0 @@
|
||||||
apg-go:*:1000:1000:Automated Password Generator User:/apg-go:/usr/bin/false
|
|
|
@ -1,27 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/wneessen/apg-go/chars"
|
|
||||||
"github.com/wneessen/apg-go/config"
|
|
||||||
"github.com/wneessen/apg-go/random"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
c := config.Config{
|
|
||||||
UseNumber: true,
|
|
||||||
UseSpecial: true,
|
|
||||||
UseUpperCase: true,
|
|
||||||
UseLowerCase: true,
|
|
||||||
PwAlgo: 1,
|
|
||||||
MinPassLen: 15,
|
|
||||||
MaxPassLen: 15,
|
|
||||||
}
|
|
||||||
pl := config.GetPwLengthFromParams(&c)
|
|
||||||
cs := chars.GetRange(&c)
|
|
||||||
pw, err := random.GetChar(cs, pl)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fmt.Println("Your Password:", pw)
|
|
||||||
}
|
|
5
go.mod
5
go.mod
|
@ -1,5 +0,0 @@
|
||||||
module github.com/wneessen/apg-go
|
|
||||||
|
|
||||||
go 1.16
|
|
||||||
|
|
||||||
require github.com/wneessen/go-hibp v1.0.6
|
|
|
@ -1,80 +0,0 @@
|
||||||
package random
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Bitmask sizes for the string generators (based on 93 chars total)
|
|
||||||
const (
|
|
||||||
letterIdxBits = 7 // 7 bits to represent a letter index
|
|
||||||
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
|
|
||||||
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetChar generates random characters based on given character range
|
|
||||||
// and password length
|
|
||||||
func GetChar(cr string, l int) (string, error) {
|
|
||||||
if l < 1 {
|
|
||||||
return "", fmt.Errorf("length is negative")
|
|
||||||
}
|
|
||||||
rs := strings.Builder{}
|
|
||||||
rs.Grow(l)
|
|
||||||
crl := len(cr)
|
|
||||||
|
|
||||||
rp := make([]byte, 8)
|
|
||||||
_, err := rand.Read(rp)
|
|
||||||
if err != nil {
|
|
||||||
return rs.String(), err
|
|
||||||
}
|
|
||||||
for i, c, r := l-1, binary.BigEndian.Uint64(rp), letterIdxMax; i >= 0; {
|
|
||||||
if r == 0 {
|
|
||||||
_, err := rand.Read(rp)
|
|
||||||
if err != nil {
|
|
||||||
return rs.String(), err
|
|
||||||
}
|
|
||||||
c, r = binary.BigEndian.Uint64(rp), letterIdxMax
|
|
||||||
}
|
|
||||||
if idx := int(c & letterIdxMask); idx < crl {
|
|
||||||
rs.WriteByte(cr[idx])
|
|
||||||
i--
|
|
||||||
}
|
|
||||||
c >>= letterIdxBits
|
|
||||||
r--
|
|
||||||
}
|
|
||||||
return rs.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
maxNumBigInt := big.NewInt(int64(maxNum))
|
|
||||||
if !maxNumBigInt.IsUint64() {
|
|
||||||
err := fmt.Errorf("big.NewInt() generation returned negative value: %v", maxNumBigInt)
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
randNum64, err := rand.Int(rand.Reader, maxNumBigInt)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
randNum := int(randNum64.Int64())
|
|
||||||
if randNum < 0 {
|
|
||||||
err := fmt.Errorf("generated random number does not fit as int64: %v", randNum64)
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return randNum, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CoinFlip performs a simple coinflip based on the rand library and returns true or false
|
|
||||||
func CoinFlip() bool {
|
|
||||||
num := big.NewInt(2)
|
|
||||||
cf, _ := rand.Int(rand.Reader, num)
|
|
||||||
r := int(cf.Int64())
|
|
||||||
return r == 1
|
|
||||||
}
|
|
|
@ -1,140 +0,0 @@
|
||||||
package spelling
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/wneessen/apg-go/chars"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
symbNumNames = map[byte]string{
|
|
||||||
'1': "ONE",
|
|
||||||
'2': "TWO",
|
|
||||||
'3': "THREE",
|
|
||||||
'4': "FOUR",
|
|
||||||
'5': "FIVE",
|
|
||||||
'6': "SIX",
|
|
||||||
'7': "SEVEN",
|
|
||||||
'8': "EIGHT",
|
|
||||||
'9': "NINE",
|
|
||||||
'0': "ZERO",
|
|
||||||
33: "EXCLAMATION_POINT",
|
|
||||||
34: "QUOTATION_MARK",
|
|
||||||
35: "CROSSHATCH",
|
|
||||||
36: "DOLLAR_SIGN",
|
|
||||||
37: "PERCENT_SIGN",
|
|
||||||
38: "AMPERSAND",
|
|
||||||
39: "APOSTROPHE",
|
|
||||||
40: "LEFT_PARENTHESIS",
|
|
||||||
41: "RIGHT_PARENTHESIS",
|
|
||||||
42: "ASTERISK",
|
|
||||||
43: "PLUS_SIGN",
|
|
||||||
44: "COMMA",
|
|
||||||
45: "HYPHEN",
|
|
||||||
46: "PERIOD",
|
|
||||||
47: "SLASH",
|
|
||||||
58: "COLON",
|
|
||||||
59: "SEMICOLON",
|
|
||||||
60: "LESS_THAN",
|
|
||||||
61: "EQUAL_SIGN",
|
|
||||||
62: "GREATER_THAN",
|
|
||||||
63: "QUESTION_MARK",
|
|
||||||
64: "AT_SIGN",
|
|
||||||
91: "LEFT_BRACKET",
|
|
||||||
92: "BACKSLASH",
|
|
||||||
93: "RIGHT_BRACKET",
|
|
||||||
94: "CIRCUMFLEX",
|
|
||||||
95: "UNDERSCORE",
|
|
||||||
96: "GRAVE",
|
|
||||||
123: "LEFT_BRACE",
|
|
||||||
124: "VERTICAL_BAR",
|
|
||||||
125: "RIGHT_BRACE",
|
|
||||||
126: "TILDE",
|
|
||||||
}
|
|
||||||
alphabetNames = map[byte]string{
|
|
||||||
'A': "Alfa",
|
|
||||||
'B': "Bravo",
|
|
||||||
'C': "Charlie",
|
|
||||||
'D': "Delta",
|
|
||||||
'E': "Echo",
|
|
||||||
'F': "Foxtrot",
|
|
||||||
'G': "Golf",
|
|
||||||
'H': "Hotel",
|
|
||||||
'I': "India",
|
|
||||||
'J': "Juliett",
|
|
||||||
'K': "Kilo",
|
|
||||||
'L': "Lima",
|
|
||||||
'M': "Mike",
|
|
||||||
'N': "November",
|
|
||||||
'O': "Oscar",
|
|
||||||
'P': "Papa",
|
|
||||||
'Q': "Quebec",
|
|
||||||
'R': "Romeo",
|
|
||||||
'S': "Sierra",
|
|
||||||
'T': "Tango",
|
|
||||||
'U': "Uniform",
|
|
||||||
'V': "Victor",
|
|
||||||
'W': "Whiskey",
|
|
||||||
'X': "X_ray",
|
|
||||||
'Y': "Yankee",
|
|
||||||
'Z': "Zulu",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
returnString = append(returnString, curSpellString)
|
|
||||||
}
|
|
||||||
return strings.Join(returnString, "/"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Koremutake returns the spelling of the Koremutake password with numbers and special
|
|
||||||
// chars spelled out in english language
|
|
||||||
func Koremutake(sylList []string) (string, error) {
|
|
||||||
var returnString []string
|
|
||||||
for _, curSyl := range sylList {
|
|
||||||
isKore := false
|
|
||||||
for _, x := range chars.KoremutakeSyllables {
|
|
||||||
if x == strings.ToLower(curSyl) {
|
|
||||||
isKore = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if isKore {
|
|
||||||
returnString = append(returnString, curSyl)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
curSpellString, err := ConvertCharToName(curSyl[0])
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
returnString = append(returnString, curSpellString)
|
|
||||||
}
|
|
||||||
return strings.Join(returnString, "-"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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]
|
|
||||||
} else if charByte > 96 && charByte < 123 {
|
|
||||||
returnString = strings.ToLower(alphabetNames[charByte-32])
|
|
||||||
} else {
|
|
||||||
returnString = symbNumNames[charByte]
|
|
||||||
}
|
|
||||||
|
|
||||||
if returnString == "" {
|
|
||||||
err := fmt.Errorf("cannot convert to character to name: %q is an unknown character", charByte)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return returnString, nil
|
|
||||||
}
|
|
Loading…
Reference in a new issue