aboutsummaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'main.go')
-rw-r--r--main.go227
1 files changed, 110 insertions, 117 deletions
diff --git a/main.go b/main.go
index 39b6b13..2dfd051 100644
--- a/main.go
+++ b/main.go
@@ -1,141 +1,134 @@
package main
import (
- "crypto/sha256"
- "encoding/pem"
- "flag"
- "fmt"
- "os"
- "strings"
-
- "github.com/tyler-smith/go-bip39"
- "golang.org/x/crypto/ed25519"
- "golang.org/x/crypto/ssh"
+ "crypto/sha256"
+ "encoding/pem"
+ "flag"
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/tyler-smith/go-bip39"
+ "golang.org/x/crypto/ed25519"
+ "golang.org/x/crypto/ssh"
)
-const helpMessage = `ssh-bip39gen: generate dd25519 ssh keys from a bip-39 mnemonic
-
-this tool creates deterministic ed25519 ssh key pairs using a 24-word bip-39 mnemonic phrase.
-if no mnemonic is provided, it generates a new one with 256-bit entropy. the mnemonic is your
-key to regenerate the same SSH key pair later - keep it safe!
-
-usage:
- ssh-bip39gen [-f output_file] [-mnemonic "24-word phrase"]
-
-examples:
- generate a new key pair (default: id_ed25519):
- ssh-bip39gen
-
- generate with custom output file:
- ssh-bip39gen -f test
-
- regenerate from a mnemonic:
- ssh-bip39gen -f test -mnemonic "abandon ability able about ... actress"
-
-flags:
-`
-
type KeyType string
const (
- ED25519 KeyType = "ed25519"
+ ED25519 KeyType = "ed25519"
)
type seededRand struct {
- seed []byte
- pos int
+ seed []byte
+ pos int
}
func (r *seededRand) Read(p []byte) (n int, err error) {
- for n < len(p) {
- counter := uint32(r.pos / len(r.seed))
- hash := sha256.Sum256(append(r.seed, byte(counter), byte(counter>>8), byte(counter>>16), byte(counter>>24)))
- n += copy(p[n:], hash[:])
- r.pos += len(hash)
- }
- return len(p), nil
+ for n < len(p) {
+ counter := uint32(r.pos / len(r.seed))
+ hash := sha256.Sum256(append(r.seed, byte(counter), byte(counter>>8), byte(counter>>16), byte(counter>>24)))
+ n += copy(p[n:], hash[:])
+ r.pos += len(hash)
+ }
+ return len(p), nil
}
func GenerateEd25519Key(seed []byte) (ed25519.PublicKey, ed25519.PrivateKey, error) {
- if len(seed) < 32 {
- return nil, nil, fmt.Errorf("seed too short for ed25519; need 32 bytes, got %d", len(seed))
- }
- publicKey, privateKey, err := ed25519.GenerateKey(&seededRand{seed: seed[:32]})
- return publicKey, privateKey, err
+ if len(seed) < 32 {
+ return nil, nil, fmt.Errorf("[err] seed too short for ed25519; need 32 bytes, got %d", len(seed))
+ }
+ publicKey, privateKey, err := ed25519.GenerateKey(&seededRand{seed: seed[:32]})
+ return publicKey, privateKey, err
}
func SaveKeys(privateKey ed25519.PrivateKey, privFile, pubFile string) error {
- block, err := ssh.MarshalPrivateKey(privateKey, "")
- if err != nil {
- return fmt.Errorf("failed to marshal ed25519 private key: %v", err)
- }
- privBytes := pem.EncodeToMemory(block)
-
- pub, err := ssh.NewPublicKey(privateKey.Public())
- if err != nil {
- return fmt.Errorf("failed to generate ed25519 public key: %v", err)
- }
- pubBytes := ssh.MarshalAuthorizedKey(pub)
-
- if err := os.WriteFile(privFile, privBytes, 0600); err != nil {
- return fmt.Errorf("failed to write private key to %s: %v", privFile, err)
- }
- if err := os.WriteFile(pubFile, pubBytes, 0644); err != nil {
- return fmt.Errorf("failed to write public key to %s: %v", pubFile, err)
- }
- return nil
+ block, err := ssh.MarshalPrivateKey(privateKey, "")
+ if err != nil {
+ return fmt.Errorf("[err] failed to marshal ed25519 private key: %v", err)
+ }
+ privBytes := pem.EncodeToMemory(block)
+
+ pub, err := ssh.NewPublicKey(privateKey.Public())
+ if err != nil {
+ return fmt.Errorf("[err] failed to generate ed25519 public key: %v", err)
+ }
+ pubBytes := ssh.MarshalAuthorizedKey(pub)
+
+ if err := os.WriteFile(privFile, privBytes, 0600); err != nil {
+ return fmt.Errorf("[err] failed to write private key to %s: %v", privFile, err)
+ }
+ if err := os.WriteFile(pubFile, pubBytes, 0644); err != nil {
+ return fmt.Errorf("[err] failed to write public key to %s: %v", pubFile, err)
+ }
+ return nil
+}
+
+func init() {
+ const usageHeader = `
+command-line tool that generates deterministic ed25519 ssh key pairs from a 24-word bip-39 mnemonic phrase
+
+author: heqnx - https://heqnx.com
+
+`
+ flag.Usage = func() {
+ fmt.Fprint(os.Stderr, usageHeader)
+ fmt.Fprintf(os.Stderr, "usage of %s:\n", os.Args[0])
+ flag.PrintDefaults()
+ }
+ flag.CommandLine.SetOutput(os.Stderr)
}
func main() {
- flag.Usage = func() {
- fmt.Fprint(os.Stderr, helpMessage)
- flag.PrintDefaults()
- }
-
- mnemonic := flag.String("mnemonic", "", "bip-39 mnemonic phrase (leave empty to generate a new 24-word mnemonic)")
- outputFile := flag.String("f", "bip39-id_ed25519", "output file for private key (public key will be <file>.pub)")
- flag.Parse()
-
- privFile := *outputFile
- pubFile := *outputFile + ".pub"
-
- var seed []byte
- if *mnemonic == "" {
- entropy, err := bip39.NewEntropy(256)
- if err != nil {
- fmt.Printf("error generating entropy: %v\n", err)
- os.Exit(1)
- }
- *mnemonic, err = bip39.NewMnemonic(entropy)
- if err != nil {
- fmt.Printf("error generating mnemonic: %v\n", err)
- os.Exit(1)
- }
- fmt.Printf("new mnemonic generated - keep it secure:\n%s\n\n", *mnemonic)
- seed = bip39.NewSeed(*mnemonic, "")
- } else {
- wordCount := len(strings.Fields(*mnemonic))
- if wordCount != 24 {
- fmt.Printf("error: mnemonic must contain exactly 24 words, got %d\n", wordCount)
- os.Exit(1)
- }
- if !bip39.IsMnemonicValid(*mnemonic) {
- fmt.Println("error: invalid mnemonic phrase")
- os.Exit(1)
- }
- seed = bip39.NewSeed(*mnemonic, "")
- }
-
- _, priv, err := GenerateEd25519Key(seed)
- if err != nil {
- fmt.Printf("error generating ed25519 key: %v\n", err)
- os.Exit(1)
- }
-
- err = SaveKeys(priv, privFile, pubFile)
- if err != nil {
- fmt.Printf("error saving keys: %v\n", err)
- os.Exit(1)
- }
- fmt.Printf("ed25519 generated keys:\n - %s (private)\n - %s (public)\n", privFile, pubFile)
+ mnemonic := flag.String("mnemonic", "", "bip-39 mnemonic phrase (leave empty to generate a new 24-word mnemonic)")
+ outputFile := flag.String("f", "", "output file for private key (public key will be <file>.pub)")
+ flag.Parse()
+
+ if flag.NFlag() == 0 && flag.NArg() == 0 {
+ flag.Usage()
+ os.Exit(1)
+ }
+
+ privFile := *outputFile
+ pubFile := *outputFile + ".pub"
+
+ var seed []byte
+ if *mnemonic == "" {
+ entropy, err := bip39.NewEntropy(256)
+ if err != nil {
+ fmt.Errorf("[err] error generating entropy: %v\n", err)
+ os.Exit(1)
+ }
+ *mnemonic, err = bip39.NewMnemonic(entropy)
+ if err != nil {
+ fmt.Errorf("[err] error generating mnemonic: %v\n", err)
+ os.Exit(1)
+ }
+ fmt.Printf("[inf] new mnemonic generated - keep it secure:\n%s\n\n", *mnemonic)
+ seed = bip39.NewSeed(*mnemonic, "")
+ } else {
+ wordCount := len(strings.Fields(*mnemonic))
+ if wordCount != 24 {
+ fmt.Errorf("[err] mnemonic must contain exactly 24 words, got %d\n", wordCount)
+ os.Exit(1)
+ }
+ if !bip39.IsMnemonicValid(*mnemonic) {
+ fmt.Errorf("[err] invalid mnemonic phrase\n")
+ os.Exit(1)
+ }
+ seed = bip39.NewSeed(*mnemonic, "")
+ }
+
+ _, priv, err := GenerateEd25519Key(seed)
+ if err != nil {
+ fmt.Errorf("[err] error generating ed25519 key: %v\n", err)
+ os.Exit(1)
+ }
+
+ err = SaveKeys(priv, privFile, pubFile)
+ if err != nil {
+ fmt.Errorf("[err] error saving keys: %v\n", err)
+ os.Exit(1)
+ }
+ fmt.Printf("[inf] ed25519 generated keys:\n - %s (private)\n - %s (public)\n", privFile, pubFile)
}