From 7632df40dacb95e8a850f104de29315cf69911e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Wysoki=C5=84ski?= Date: Sun, 22 May 2022 09:13:24 +0200 Subject: [PATCH] feat: add a new subcommand - encrypt (#16) --- internal/andotp.go | 8 ++-- internal/andotp_test.go | 6 +-- main.go | 101 ++++++++++++++++++++++++---------------- 3 files changed, 68 insertions(+), 47 deletions(-) diff --git a/internal/andotp.go b/internal/andotp.go index e21cc66..e1fb54f 100644 --- a/internal/andotp.go +++ b/internal/andotp.go @@ -22,7 +22,7 @@ const ( minIterations = 140000 ) -func Encrypt(password, plaintext []byte) ([]byte, error) { +func Encrypt(plaintext, password []byte) ([]byte, error) { iter := make([]byte, iterationLen) iv := make([]byte, ivLen) salt := make([]byte, saltLen) @@ -74,7 +74,7 @@ func randIterations() (int, error) { return int(iterations.Int64() + minIterations), nil } -func Decrypt(password, text []byte) ([]byte, error) { +func Decrypt(text, password []byte) ([]byte, error) { iterations := text[:iterationLen] salt := text[iterationLen : iterationLen+saltLen] iv := text[iterationLen+saltLen : iterationLen+saltLen+ivLen] @@ -112,8 +112,8 @@ type Entry struct { Type string `json:"type"` } -func DecryptAsEntries(password, text []byte) ([]Entry, error) { - result, err := Decrypt(password, text) +func DecryptAsEntries(text, password []byte) ([]Entry, error) { + result, err := Decrypt(text, password) if err != nil { return nil, fmt.Errorf("decrypt: %w", err) } diff --git a/internal/andotp_test.go b/internal/andotp_test.go index f3f3a8a..5c02427 100644 --- a/internal/andotp_test.go +++ b/internal/andotp_test.go @@ -28,17 +28,17 @@ func TestEncryptDecrypt(t *testing.T) { assert.Nil(t, err) password := []byte("password22231") - encrypted, err := internal.Encrypt(password, entriesJSON) + encrypted, err := internal.Encrypt(entriesJSON, password) assert.Nil(t, err) - decrypted, err := internal.Decrypt(password, encrypted) + decrypted, err := internal.Decrypt(encrypted, password) assert.Nil(t, err) var result []internal.Entry err = json.Unmarshal(decrypted, &result) assert.Nil(t, err) assert.Equal(t, entries, result) - decryptedEntries, err := internal.DecryptAsEntries(password, encrypted) + decryptedEntries, err := internal.DecryptAsEntries(encrypted, password) assert.Nil(t, err) assert.Equal(t, entries, decryptedEntries) } diff --git a/main.go b/main.go index faa4494..685a569 100644 --- a/main.go +++ b/main.go @@ -37,11 +37,7 @@ func newApp() (*cli.App, error) { Usage: "2FA App compatible with andOTP backup file format", Version: Version, Action: func(c *cli.Context) error { - var err error - password := []byte(c.String("password")) - if len(password) == 0 { - password, err = readPasswordFromStdin() - } + password, err := getPassword(c) if err != nil { return err } @@ -51,7 +47,7 @@ func newApp() (*cli.App, error) { return fmt.Errorf("something went wrong while reading file: %w", err) } - entries, err := internal.DecryptAsEntries(password, b) + entries, err := internal.DecryptAsEntries(b, password) if err != nil { return fmt.Errorf("something went wrong while decrypting file: %w", err) } @@ -67,7 +63,7 @@ func newApp() (*cli.App, error) { &cli.StringFlag{ Name: "path", Aliases: []string{"p"}, - Usage: "path to andotp backup file", + Usage: "path to andOTP backup file", Required: false, DefaultText: "$HOME/.otp_accounts.json", Value: path.Join(dirname, ".otp_accounts.json"), @@ -80,6 +76,7 @@ func newApp() (*cli.App, error) { }, EnableBashCompletion: true, Commands: []*cli.Command{ + newEncryptCommand(), newDecryptCommand(), }, }, nil @@ -87,39 +84,9 @@ func newApp() (*cli.App, error) { func newDecryptCommand() *cli.Command { return &cli.Command{ - Name: "decrypt", - Usage: "Decrypts backup file generated by andotp", - Action: func(c *cli.Context) error { - var err error - password := []byte(c.String("password")) - if len(password) == 0 { - password, err = readPasswordFromStdin() - } - if err != nil { - return err - } - - b, err := os.ReadFile(c.String("path")) - if err != nil { - return fmt.Errorf("something went wrong while reading file: %w", err) - } - - result, err := internal.Decrypt(password, b) - if err != nil { - return fmt.Errorf("something went wrong while decrypting file: %w", err) - } - - output := c.String("output") - if output != "" { - if err := os.WriteFile(output, result, 0600); err != nil { - return fmt.Errorf("something went wrong while saving file: %w", err) - } - } else { - fmt.Print(string(result)) - } - - return nil - }, + Name: "decrypt", + Usage: "Decrypts the specified backup file", + Action: newEncryptDecryptActionFunc(internal.Decrypt), Flags: []cli.Flag{ &cli.StringFlag{ Name: "output", @@ -131,6 +98,60 @@ func newDecryptCommand() *cli.Command { } } +func newEncryptCommand() *cli.Command { + return &cli.Command{ + Name: "encrypt", + Usage: "Encrypts the specified file file using AES-256", + Action: newEncryptDecryptActionFunc(internal.Encrypt), + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "output", + Usage: "Write to file instead of stdout", + Aliases: []string{"o"}, + Required: false, + }, + }, + } +} + +func newEncryptDecryptActionFunc(fn func(text, password []byte) ([]byte, error)) cli.ActionFunc { + return func(c *cli.Context) error { + password, err := getPassword(c) + if err != nil { + return err + } + + b, err := os.ReadFile(c.String("path")) + if err != nil { + return fmt.Errorf("something went wrong while reading file: %w", err) + } + + result, err := fn(b, password) + if err != nil { + return fmt.Errorf("something went wrong while processing file: %w", err) + } + + output := c.String("output") + if output != "" { + if err := os.WriteFile(output, result, 0600); err != nil { + return fmt.Errorf("something went wrong while saving result: %w", err) + } + } else { + fmt.Print(string(result)) + } + + return nil + } +} + +func getPassword(c *cli.Context) ([]byte, error) { + password := []byte(c.String("password")) + if len(password) == 0 { + return readPasswordFromStdin() + } + return password, nil +} + func readPasswordFromStdin() ([]byte, error) { fmt.Print("Password: ")