feat: add a new subcommand - encrypt (#16)
This commit is contained in:
parent
f59e1db9a8
commit
7632df40da
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
101
main.go
|
@ -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: ")
|
||||||
|
|
||||||
|
|
Reference in New Issue