mirror of
https://github.com/wneessen/apg-go.git
synced 2024-12-22 19:20:39 +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