Compare commits

...

6 Commits
v1.0.1 ... main

Author SHA1 Message Date
akulij
b47de3e1b6 implement hex/raw input 2025-03-03 00:18:43 +00:00
akulij
9cde36c5c2 rename 'sum' variable that contains hashed password to more appropriate name 'secretKey' 2025-03-03 00:18:43 +00:00
akulij
181ac703ae add --input-type flag 2025-03-03 00:18:37 +00:00
akulij
b6be7799b9 README: fix wrong todo tasks formating 2025-02-28 04:21:59 +00:00
akulij
ecaa3c8a9a README: remove unnecessary decription header 2025-02-28 01:08:30 +00:00
akulij
f921077964 add option to build and run this repo using nix 2025-02-28 00:15:27 +00:00
3 changed files with 122 additions and 14 deletions

View File

@ -1,6 +1,5 @@
# Generate age keys from passphrase
## Description
This utility (age-passgen) generates secret and public keys (into stdout) from your entered passphrase or piped stdin
Strong password highly recomended
@ -10,6 +9,6 @@ Exact amount of required characters can be calculated by formula: $\lceil 256 /
## TODO
[X] piped/terminal output as raw/verbose
[X] handle broken pipe signal since program will so much depend on pipes
[ ] option to read hex number that will be used instead of hash of password (usefull if user already has sha 256 hash of smth or want to use something else as input instead)
- [X] piped/terminal output as raw/verbose
- [X] handle broken pipe signal since program will so much depend on pipes
- [ ] option to read hex number that will be used instead of hash of password (usefull if user already has sha 256 hash of smth or want to use something else as input instead)

View File

@ -1,6 +1,7 @@
package main
import (
"encoding/hex"
"errors"
"flag"
"fmt"
@ -10,6 +11,7 @@ import (
"os/signal"
"slices"
"strconv"
"strings"
"syscall"
"time"
"unsafe"
@ -33,6 +35,7 @@ Options:
-o, --output OUTPUT Write the result to the file at path OUTPUT.
--raw-output Print stripped keys (without additional text or comments)
--entropy-level VALUE Manages required strenght of password (more info down below)
--input-type TYPE Type of input from stdin. Can be 'password' (default), 'hash', 'raw'
Mostly similar to age-keygen
Required password strenght can be changes via --entropy-level flag. Possible values
@ -46,10 +49,19 @@ Each word or number is mapped following this list:
- stupid - no limit
`
type InputType int
const (
InputPassword InputType = iota
InputHash
InputRaw
)
type Flags struct {
RawOutput bool
OutputFile string
EntropyLevel int
InputType InputType
}
func main() {
@ -59,18 +71,27 @@ func main() {
errorf("error while parsing arguments: %s\n", err)
}
passbytes, err := getPasswordBytes()
var secretKey [curve25519.ScalarSize]byte
if flags.InputType == InputPassword {
secretKey, err = getInputPassword(flags.EntropyLevel)
if err != nil {
errorf("Failed to get password, error: %s\n", err)
}
valid := isEntropyValid(passbytes, flags.EntropyLevel)
if !valid {
errorf("You should choose stroger password!!! (or change entropy level, read more with --help)\n")
} else if flags.InputType == InputHash {
secretKey, err = getInputHash()
if err != nil {
errorf("Failed to read hash, error: %s\n", err)
}
} else if flags.InputType == InputRaw {
secretKey, err = getInputRaw()
if err != nil {
errorf("Failed to read raw data, error: %s\n", err)
}
} else {
errorf("No such input type implemented!!!")
}
sum := sha256.Sum256(passbytes)
k, err := newX25519IdentityFromScalar(sum[:])
k, err := newX25519IdentityFromScalar(secretKey[:])
if err != nil {
errorf("internal error: %v", err)
}
@ -100,6 +121,57 @@ func main() {
}
}
func getInputPassword(entropyLevel int) ([curve25519.ScalarSize]byte, error) {
passbytes, err := getPasswordBytes()
if err != nil {
return [curve25519.ScalarSize]byte{}, err
}
valid := isEntropyValid(passbytes, entropyLevel)
if !valid {
return [curve25519.ScalarSize]byte{}, errors.New("You should choose stroger password!!! (or change entropy level, read more with --help)\n")
}
return sha256.Sum256(passbytes), nil
}
func getInputHash() ([curve25519.ScalarSize]byte, error) {
hashStringBytes, err := getPasswordBytes()
if err != nil {
return [curve25519.ScalarSize]byte{}, err
}
hashString := strings.TrimSpace(string(hashStringBytes))
passbytes, err := hex.DecodeString(hashString)
if err != nil {
fmt.Printf("HEXSTR:%s|\n", hashString)
return [curve25519.ScalarSize]byte{}, errors.New(fmt.Sprintf("Unable to decode hash, error: %s\n", err))
}
if len(passbytes) != curve25519.ScalarSize {
return [curve25519.ScalarSize]byte{}, errors.New(fmt.Sprintf("Wrong input lenght of sha256 hash! (may be it is not a hash at all) Expected %d bytes, got: %d\n", curve25519.ScalarSize, len(passbytes)))
}
// making `possibly` stack allocated out of the one in heap
var key [curve25519.ScalarSize]byte
copy(key[:], passbytes)
return key, nil
}
func getInputRaw() ([curve25519.ScalarSize]byte, error) {
passbytes, err := getPasswordBytes()
if err != nil {
return [curve25519.ScalarSize]byte{}, err
}
if len(passbytes) != curve25519.ScalarSize {
return [curve25519.ScalarSize]byte{}, errors.New(fmt.Sprintf("Wrong amount of entered data! Expected %d bytes, got: %d\n", curve25519.ScalarSize, len(passbytes)))
}
// making `possibly` stack allocated out of the one in heap
var key [curve25519.ScalarSize]byte
copy(key[:], passbytes)
return key, nil
}
func setSystemSignalHandlers() {
go handleSigpipe()
}
@ -123,12 +195,14 @@ func parseFlags() (*Flags, error) {
rawOutput bool
outputFile string
entropyLevel string
inputType string
)
flag.BoolVar(&rawOutput, "raw-output", false, "Print stripped keys (without additional text or comments)")
flag.StringVar(&outputFile, "o", "", "Write the result to the file at path OUTPUT")
flag.StringVar(&outputFile, "output", "", "Write the result to the file at path OUTPUT")
flag.StringVar(&entropyLevel, "entropy-level", "medium", "Manages required strenght of password. Read more in --help")
flag.StringVar(&inputType, "input-type", "password", "Type of input from stdin. Can be 'password' (default), 'hash', 'raw'")
flag.Parse()
eLevel, err := parseEntropyLevel(entropyLevel)
@ -136,13 +210,34 @@ func parseFlags() (*Flags, error) {
return nil, err
}
iType, err := parseInputType(inputType)
if err != nil {
return nil, err
}
return &Flags{
RawOutput: rawOutput,
OutputFile: outputFile,
EntropyLevel: eLevel,
InputType: iType,
}, nil
}
func parseInputType(inputType string) (InputType, error) {
m := map[string]InputType{
"password": InputPassword,
"hash": InputHash,
"raw": InputRaw,
}
iType, ok := m[inputType]
if !ok {
return InputPassword, errors.New("wrong input type")
}
return iType, nil
}
func parseEntropyLevel(entropyLevel string) (int, error) {
if i, err := strconv.Atoi(entropyLevel); err == nil {
if i == 0 {

View File

@ -8,6 +8,20 @@
let pkgs = nixpkgs.legacyPackages.${system}; in
{
devShells.default = import ./shell.nix { inherit pkgs; };
packages.default = pkgs.buildGoModule {
pname = "age-passgen";
version = "unversioned";
src = ./.;
vendorHash = "sha256-Y6R8c9PzRq0tJ0b06f0LuFfrdFvxQ7h/86a6gg6UOro=";
};
apps.default = {
type = "app";
program = "${self.packages.${system}.default}/bin/age-passgen";
};
}
);
}