Commit: 4c4ac0021e9660d6be6978e5506bffe33bbb6b92 Author: Vi Grey Date: 2023-07-06 09:57 UTC Summary: Initial commit .gitignore | 1 + LICENSE | 24 ++++++++++++ Makefile | 11 ++++++ README.txt | 68 ++++++++++++++++++++++++++++++++ go.mod | 3 ++ src/bonewords.go | 170 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 277 insertions(+) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..91c0826 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +Copyright (C) 2023, Vi Grey +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c2b3047 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +PKG_NAME := bonewords +CURRENTDIR := $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) + +all: + mkdir -p $(CURRENTDIR)build/bin; \ + cd $(CURRENTDIR)src; \ + go build -ldflags="-s -w" -o $(CURRENTDIR)build/bin/$(PKG_NAME); \ + cd $(CURRENTDIR); \ + +clean: + rm -rf -- $(CURRENTDIR)build; \ diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..8e9540d --- /dev/null +++ b/README.txt @@ -0,0 +1,68 @@ +# Bonewords + +Password generator that is compatible with a method for generating +passwords using dice rolls. + + +## Description + +This is a software program for generating random passwords that is +compatible with a method I wrote about in a blog post titled "Using +Dice to Generate Human Readable Passwords" which is linked below +(blog post available over HTTP(S), Gopher, and Gemini). This program +is called "Bonewords" because "bones" are another word for "dice" and +"words" comes from "passwords" in this case, so (dice) + pass"words" +is "bonewords". + +### HTTP(S) links to blog post +http://vigrey.com/blog/using-dice-to-generate-human-readable-passwords +https://vigrey.com/blog/using-dice-to-generate-human-readable-passwords + +### HTTP(S) "text-only" links to blog post +http://text.vigrey.com/blog/using-dice-to-generate-human-readable-passwords +https://text.vigrey.com/blog/using-dice-to-generate-human-readable-passwords + +### Gopher link to blog post +gopher://vigrey.com/blog/using-dice-to-generate-human-readable-passwords + +### Gemini link to blog post +gemini://vigrey.com/blog/using-dice-to-generate-human-readable-passwords + + +## OS Dependencies + +Bonewords is meant to build and run on Linux and Plan9, although it +will likely run just fine on macOS, the BSDs, and Windows as well. + + +## Build Dependencies + +- go >= 1.20 + + +## Build Bonewords on Linux + +To build Bonewords on Linux, from the root directory of this +repository, run the following command: + +``` +make +``` + +The resulting binary file will be at `build/bin/bonewords`. + + +## Build Bonewords on Plan9 + +To build Bonewords on Plan9, from the root directory of this +repository, run the following commands: + +``` +mkdir -p build/bin +cd src +go build +mv src ../build/bin/bonewords +cd .. +``` + +The resulting binary file will be at `build/bin/bonewords`. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..532a223 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module bonewords + +go 1.20 diff --git a/src/bonewords.go b/src/bonewords.go new file mode 100644 index 0000000..ee0225a --- /dev/null +++ b/src/bonewords.go @@ -0,0 +1,170 @@ +package main + +import ( + "crypto/rand" + "errors" + "fmt" + "math/big" + "os" + "strconv" + "strings" +) + +var ( + possibleChars = []string{ + "abcdef", + "ghikmn", + "oprswz", + "AEFHLY", + "234678", + "+,-/?^", + } + diceSize = big.NewInt(6) + passwordLen = 16 + rollsFlag bool + helpFlag bool +) + +type Password struct { + Password string `json:"password"` + DiceRolls []int64 `json:"dice_rolls"` +} + +func handleFlags(args []string) (err error) { + for x := 0; x < len(args); x++ { + arg := args[x] + switch strings.ToLower(arg) { + case "--help", "-h": + helpFlag = true + break + case "--rolls", "-r": + rollsFlag = true + default: + // Valid if passwordLen is greater than or equal to 3, otherwise error + if passwordLen, err = strconv.Atoi(arg); passwordLen < 3 || err != nil { + err = errors.New(fmt.Sprintf("Invalid flag or password length \"%s\"", arg)) + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + } + } + if helpFlag { + displayHelp() + os.Exit(0) + } + return +} + +// Print help +func displayHelp() { + fmt.Printf("Usage: bonewords [ OPTIONS ]... \r\n\r\nOptions:\r\n" + + " -h, --help Print Help (this message) and exit\r\n" + + " -r, --rolls Print dice rolls in separate after password\r\n" + + " Integer value that MUST be greater than 2\r\n" + + " ( defaults to 16)\r\n\r\n" + + "Examples:\r\n" + + " bonewords -r 12\r\n" + + " (This example prints out a 12 character password on a line followed by\r\n" + + " the 23 dice rolls that created that password on a line.)\r\n\r\n" + + " bonewords\r\n" + + " (This example prints out a 16 character password on a line.)\r\n") +} + +// Get single character and the dice rolls required for password +func getPasswordCharacter() (character, dice string) { + // First dice roll (0-5) + x, err := rand.Int(rand.Reader, diceSize) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + xInt64 := x.Int64() + // Second dice roll (0-5) + y, err := rand.Int(rand.Reader, diceSize) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + yInt64 := y.Int64() + character = string(possibleChars[xInt64][yInt64]) + dice += strconv.FormatInt(xInt64+1, 10) + strconv.FormatInt(yInt64+1, 10) + return +} + +// Get the last 3 characters and dice rolls required for password +func getLast3PasswordCharacters() (characters, dice string) { + // 3rd to last character group dice roll (0-5) + x1, err := rand.Int(rand.Reader, diceSize) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + x1Int64 := x1.Int64() + var remainingGroups []int + switch x1Int64 { + case 0, 3: + remainingGroups = []int{1, 2} + case 1, 4: + remainingGroups = []int{0, 2} + case 2, 5: + remainingGroups = []int{0, 1} + } + // 3rd to last character group character dice roll (0-5) + y1, err := rand.Int(rand.Reader, diceSize) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + y1Int64 := y1.Int64() + // 2nd to last character group dice roll (0-5) + x2, err := rand.Int(rand.Reader, diceSize) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + x2Int64 := x2.Int64() + // 2nd to last character group character dice roll (0-5) + y2, err := rand.Int(rand.Reader, diceSize) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + y2Int64 := y2.Int64() + // last character group character dice roll (0-5) + y3, err := rand.Int(rand.Reader, diceSize) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + y3Int64 := y3.Int64() + characters = string(possibleChars[3+x1Int64%3][y1Int64]) + + string(possibleChars[3+remainingGroups[x2Int64%2]][y2Int64]) + + string(possibleChars[3+remainingGroups[(x2Int64+1)%2]][y3Int64]) + dice = strconv.FormatInt(x1Int64+1, 10) + strconv.FormatInt(y1Int64+1, 10) + + strconv.FormatInt(x2Int64+1, 10) + strconv.FormatInt(y2Int64+1, 10) + + strconv.FormatInt(y3Int64+1, 10) + return +} + +func main() { + if err := handleFlags(os.Args[1:]); err != nil { + fmt.Fprintln(os.Stderr, err) + return + } + var password, dice string + // First n - 3 characters + for i := 0; i < passwordLen-3; i++ { + passwordChar, diceNumbers := getPasswordCharacter() + password += passwordChar + dice += diceNumbers + } + // last 3 characters + passwordChars, diceNumbers := getLast3PasswordCharacters() + password += passwordChars + dice += diceNumbers + fmt.Println(password) + // Print dice rolls if rolls flag is true + if rollsFlag { + fmt.Println(dice) + } +}