feat: add a new subcommand - encrypt (#16)

This commit is contained in:
Dawid Wysokiński 2022-05-22 09:13:24 +02:00 committed by GitHub
parent f59e1db9a8
commit 7632df40da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 68 additions and 47 deletions

View File

@ -22,7 +22,7 @@ const (
minIterations = 140000 minIterations = 140000
) )
func Encrypt(password, plaintext []byte) ([]byte, error) { func Encrypt(plaintext, password []byte) ([]byte, error) {
iter := make([]byte, iterationLen) iter := make([]byte, iterationLen)
iv := make([]byte, ivLen) iv := make([]byte, ivLen)
salt := make([]byte, saltLen) salt := make([]byte, saltLen)
@ -74,7 +74,7 @@ func randIterations() (int, error) {
return int(iterations.Int64() + minIterations), nil return int(iterations.Int64() + minIterations), nil
} }
func Decrypt(password, text []byte) ([]byte, error) { func Decrypt(text, password []byte) ([]byte, error) {
iterations := text[:iterationLen] iterations := text[:iterationLen]
salt := text[iterationLen : iterationLen+saltLen] salt := text[iterationLen : iterationLen+saltLen]
iv := text[iterationLen+saltLen : iterationLen+saltLen+ivLen] iv := text[iterationLen+saltLen : iterationLen+saltLen+ivLen]
@ -112,8 +112,8 @@ type Entry struct {
Type string `json:"type"` Type string `json:"type"`
} }
func DecryptAsEntries(password, text []byte) ([]Entry, error) { func DecryptAsEntries(text, password []byte) ([]Entry, error) {
result, err := Decrypt(password, text) result, err := Decrypt(text, password)
if err != nil { if err != nil {
return nil, fmt.Errorf("decrypt: %w", err) return nil, fmt.Errorf("decrypt: %w", err)
} }

View File

@ -28,17 +28,17 @@ func TestEncryptDecrypt(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
password := []byte("password22231") password := []byte("password22231")
encrypted, err := internal.Encrypt(password, entriesJSON) encrypted, err := internal.Encrypt(entriesJSON, password)
assert.Nil(t, err) assert.Nil(t, err)
decrypted, err := internal.Decrypt(password, encrypted) decrypted, err := internal.Decrypt(encrypted, password)
assert.Nil(t, err) assert.Nil(t, err)
var result []internal.Entry var result []internal.Entry
err = json.Unmarshal(decrypted, &result) err = json.Unmarshal(decrypted, &result)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, entries, result) assert.Equal(t, entries, result)
decryptedEntries, err := internal.DecryptAsEntries(password, encrypted) decryptedEntries, err := internal.DecryptAsEntries(encrypted, password)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, entries, decryptedEntries) assert.Equal(t, entries, decryptedEntries)
} }

101
main.go
View File

@ -37,11 +37,7 @@ func newApp() (*cli.App, error) {
Usage: "2FA App compatible with andOTP backup file format", Usage: "2FA App compatible with andOTP backup file format",
Version: Version, Version: Version,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
var err error password, err := getPassword(c)
password := []byte(c.String("password"))
if len(password) == 0 {
password, err = readPasswordFromStdin()
}
if err != nil { if err != nil {
return err return err
} }
@ -51,7 +47,7 @@ func newApp() (*cli.App, error) {
return fmt.Errorf("something went wrong while reading file: %w", err) 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 { if err != nil {
return fmt.Errorf("something went wrong while decrypting file: %w", err) return fmt.Errorf("something went wrong while decrypting file: %w", err)
} }
@ -67,7 +63,7 @@ func newApp() (*cli.App, error) {
&cli.StringFlag{ &cli.StringFlag{
Name: "path", Name: "path",
Aliases: []string{"p"}, Aliases: []string{"p"},
Usage: "path to andotp backup file", Usage: "path to andOTP backup file",
Required: false, Required: false,
DefaultText: "$HOME/.otp_accounts.json", DefaultText: "$HOME/.otp_accounts.json",
Value: path.Join(dirname, ".otp_accounts.json"), Value: path.Join(dirname, ".otp_accounts.json"),
@ -80,6 +76,7 @@ func newApp() (*cli.App, error) {
}, },
EnableBashCompletion: true, EnableBashCompletion: true,
Commands: []*cli.Command{ Commands: []*cli.Command{
newEncryptCommand(),
newDecryptCommand(), newDecryptCommand(),
}, },
}, nil }, nil
@ -87,39 +84,9 @@ func newApp() (*cli.App, error) {
func newDecryptCommand() *cli.Command { func newDecryptCommand() *cli.Command {
return &cli.Command{ return &cli.Command{
Name: "decrypt", Name: "decrypt",
Usage: "Decrypts backup file generated by andotp", Usage: "Decrypts the specified backup file",
Action: func(c *cli.Context) error { Action: newEncryptDecryptActionFunc(internal.Decrypt),
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
},
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "output", 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) { func readPasswordFromStdin() ([]byte, error) {
fmt.Print("Password: ") fmt.Print("Password: ")