A Go password manager for the terminal

Programming Snapshot – Go Password Manager

© Lead Image © Tatiana Venkova, 123RF.com

© Lead Image © Tatiana Venkova, 123RF.com

Article from Issue 267/2023

A Go application for the terminal helps Mike Schilli remember his passwords.

Whether it's on a Post-it note under the screen or in a commercial application like OnePass, users have to write down their passwords somewhere. The Go application for the terminal presented in this article encrypts the sensitive data for storage on the hard drive and displays selected entries after entering the master password. The secret data leaves traces only in the computer's memory, with the traces vanishing when the program is closed.

Resourceful users could simply put all account names and passwords in a text file and encrypt it. However, to add new entries, the file would have to be decrypted and then re-encrypted after editing. To ensure that no unencrypted data remains on the drive, you would then need to run a scrub command to overwrite the deleted file after every edit. After decryption, all of the passwords would come up at the same time, emblazoned on the screen, where a passing colleague with sharp eyesight might catch a glimpse of one or more of them.

A number of password apps manage passwords in an exemplary manner, but do you really want to trust a random company with your confidential data and rely on them not to make mistakes during encryption or data management? Let's not forget that apps like OnePass hit you with significant monthly fees, and a guy like me has to count my pennies. The passview program (pv) presented in this article manages an encrypted collection of passwords and displays a selected entry in a terminal user interface (UI) after entering the master password (Figure 1). You can scroll through the entries and pick out the one you want before its sensitive data actually appears at the push of a button.

Figure 1: The pv program in action.

When you press Enter, pv removes the asterisks for a selected entry, revealing the secret account and password details. If you move up or down the list with the cursor keys K and J (like in Vim of Vi), pv again masks the released entry with asterisks. Pressing Q quits the program and clears the terminal window. No sensitive data remains; on the hard disk you are just left with the encrypted password file.


As a Go binary, the program already contains everything it needs for running on similar architectures. All you have to do is copy the binary and the encrypted password file to systems where you want access to the passwords. To add new entries, just invoke the program with the --add option. pv then asks you for the master password. If you enter it correctly, you are allowed to append a new line to the encrypted file at the New entry: prompt (Listing 1).

Listing 1

New Password

$ pv --add
Password: ***
New entry: gmail bodo@gmail.com hunter123

The first word of the newly added entry is the service associated with the password (gmail in this example). Its name also appears in the masked version of the line in the UI (like in the first line of Figure 1). The name acts as a navigation aid to help you find, select, and display the desired entry. The rest of the newly inserted line contains the username and password. You can freely choose the format and, for example, save only mnemonics instead of the complete data.

Figure 2: The Age encryption project.

After entering the new data (or if you call pv without options) the terminal UI appears with the scrollable listbox revealing selected entries if desired.

Crypto Genius

The password safe uses symmetric encryption. That means that it uses the same master password to encrypt and decrypt the file. The Age [1] project on GitHub provides a ready-made Go library written by a Google engineer for encrypting and decrypting data. It mainly uses public key methods, but symmetric encryption is also on the list. By the way, according to the project page, Age is pronounced like the Italian word "aghe" (needles).

Symmetrically Encrypted

Symmetric encryption is the most practical solution for a file that is only accessed by one user at any given time. If multiple people share access, public-private key pairs (also using methods from the Age library) could be used to implement a solution that gives different users the ability to access a shared file with their own passwords.

Listing 2 shows the writeEnc() and readEnc() functions used by the main program to later encrypt and decrypt the plaintext data of the password file. Line 9 uses test.age to define the name of the encrypted password file on disk. The .age extension indicates that it was encrypted by the Age library.

Listing 2


01 package main
02 import (
03   "bytes"
04   "filippo.io/age"
05   "filippo.io/age"
06   "io"
07   "os"
08 )
09 const secFile string = "test.age"
10 func writeEnc(txt string, pass string) error {
11   recipient, err := age.NewScryptRecipient(pass)
12   if err != nil {
13     return 0, err
14   }
15   out, err := os.OpenFile(secFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
16   if err != nil {
17     return 0, err
18   }
19   defer out.Close()
20   armorWriter := armor.NewWriter(out)
21   defer armorWriter.Close()
22   w, err := age.Encrypt(armorWriter, recipient)
23   if err != nil {
24     return 0, err
25   }
26   defer w.Close()
27   if _, err := io.WriteString(w, txt); err != nil {
28     return 0, err
29   }
30   return nil
31 }
32 func readEnc(pass string) (string, error) {
33   identity, err := age.NewScryptIdentity(pass)
34   if err != nil {
35     return "", err
36   }
37   out := &bytes.Buffer{}
38   in, err := os.Open(secFile)
39   if err != nil {
40     return "", err
41   }
42   defer in.Close()
43   armorReader := armor.NewReader(in)
44   r, err := age.Decrypt(armorReader, identity)
45   if err != nil {
46     return "", err
47   }
48   if _, err := io.Copy(out, r); err != nil {
49     return "", err
50   }
51   return out.String(), nil
52 }

The Age library uses an object of the Recipient type for writing (i.e., encrypting). In other words, the recipient is the entity to which encrypted data is sent. The call to the NewScryptRecipient() function in line 11 takes the password as the only parameter, and Scrypt points to the symmetric crypt function that Age implements. Line 15 opens the encrypted password file for writing, using O_CREATE to create the file if it does not already exist.

By the way, the same file opening option exists in the C programming language on Unix, where it is missing the last "E" because it's called O_CREAT there. Ken Thompson, one of the Unix founding fathers, was once asked what he would do better if he had to design Unix again, and he promptly said: "I'd spell 'creat' with an 'e'" [2]. Go now obviously granted him this wish.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

comments powered by Disqus
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters

Support Our Work

Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.

Learn More