In this article, we will be discussing various ways how to take passwords as input in Golang (hide the password as typed) with practical examples.
Password is a secret word or phrase of defined length within the system, which is used to allow users to login into either computer, laptop, or web application. It can be of combined characters i.e characters, digits or special characters [*,&,^,%,$,#,@,!], etc. For an example password:- JDoe@124$%
The standard input stdin i.e terminal, the terminal allows the user to provide requested information on the terminal.
In Golang, we can read users' passwords using the following packages:-
- using term package
- using io and os package
Different methods to hide password input in GO
Method 1:- Using the term package
In Golang, the term package provides a function that deals with terminals i.e UNIX system.
An example of the UNIX system is the use of isTerminal, Restore and MakeRaw
function while for non-Unix systems use os.Stdin.Fd().The bcrypt package is used to encrypt or hash algorithm to calculate the hash. The bufio package enables us to read characters from the standard input STDIN
at once. The fmt package is mostly used to handle Inputs and format the Outputs operations while the os package provides low-level system functionalities i.e Open, Write, Read, etc.
The example below demonstrate how to silently enter the password and encrypt it as well.
package main
import (
"bufio"
"fmt"
"golang.org/x/crypto/bcrypt"
"golang.org/x/term"
"os"
"strings"
"syscall"
)
func main() {
company, username, password, hash, match, err := ReadUsersInputs()
if err != nil {
fmt.Println(err)
}
fmt.Println("\n--------- You have provided the following information------------")
fmt.Printf("CompanyName: %s \nUsername: %s \nPassword: %s \nHashedPassword: %s \nMatch: %v \n", company, username, password, string(hash), match)
fmt.Println("---------End------------")
}
func ReadUsersInputs() (string, string, string, []byte, bool, error) {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter Your FirstName: ")
firstName, err := reader.ReadString('\n')
if len(strings.TrimSpace(firstName)) == 0 {
err = fmt.Errorf("Your FirstName can't be empty %v", firstName)
fmt.Println(err.Error())
os.Exit(1)
}
if err != nil {
return "", "", "", nil, false, err
}
fmt.Print("Enter Your LastName: ")
lastName, err := reader.ReadString('\n')
if len(strings.TrimSpace(lastName)) == 0 {
err = fmt.Errorf("Your LastName can't be empty %v", lastName)
fmt.Println(err.Error())
os.Exit(1)
}
if err != nil {
return "", "", "", nil, false, err
}
fmt.Print("Enter Your OrganisationName: ")
companyName, err := reader.ReadString('\n')
if len(strings.TrimSpace(companyName)) == 0 {
err = fmt.Errorf("Your Company Name can't be empty %v", companyName)
fmt.Println(err.Error())
os.Exit(1)
}
if err != nil {
return "", "", "", nil, false, err
}
// create username
username := firstName[0:1] + lastName
fmt.Print("Enter Password: ")
bytePassword, err := term.ReadPassword(syscall.Stdin)
if err != nil {
return "", "", "", nil, false, nil
}
password := string(bytePassword)
// hash the password
hash, err := PasswordHash(bytePassword)
if err != nil {
return "", "", "", nil, false, err
}
//check if matches
passwordMatch := HashPasswordCheck(bytePassword, hash)
return strings.TrimSpace(companyName), strings.TrimSpace(username), strings.TrimSpace(password), hash, passwordMatch, nil
}
func PasswordHash(password []byte) ([]byte, error) {
resBytes, err := bcrypt.GenerateFromPassword(password, 15)
return resBytes, err
}
func HashPasswordCheck(password, hash []byte) bool {
err := bcrypt.CompareHashAndPassword(hash, password)
return err == nil
}
Output:-
Validation of Inputs: You cant provide empty fields,
$ go run main.go
Enter Your FirstName:
Your FirstName can't be empty
exit status 1
Add all required fields
$ go run main.go
Enter Your FirstName: John
Enter Your LastName: Doe
Enter Your OrganisationName: GoCloudLinux
Enter Password:
--------- You have provided the following information------------
CompanyName: GoCloudLinux
Username: JDoe
Password: DJon@1234
HashedPassword: $2a$15$P4Yi9e/BinIgvo9X70j88.GCB7E7LBZyyKRtk7dvTTZW6rGhRI10y
Match: true
---------End------------
Explanation:-
In the above code, we have imported all required packages. main():-
In this piece of code, we are just handling the results from the ReadUsersInputs
function. Using fmt package to format those results into various data types.
ReadUsersInputs():-
this function does not accept any input values but it returns various datatypes from strings to errors. bufio.NewReader(os.Stdin)
reads users inputs into assigned variables. Our major focus is on the password variable bytePassword, err:= term.ReadPassword(syscall.Stdin)
this function from term
the package accepts the field of integer data type and it finally returns the a []bytes and error. Using syscall.Stdin
package syscall contains an interface to the low-level operating system primitives. Finally we hash that password using bcrypt.GenerateFromPassword(password, 15)
.
Method 2:- Using io and os package
In this method, we are focusing on creating a custom function to read masked passwords i.e using asterisks to display entered values. Golang provides a package golang.org/x/crypto/ssh/terminal
using functions provided by terminal packages such as makeRaw
, and Restore
under the terminal.isTerminal
uses the function for the UNIX system.
Below is a detailed example of how to read masked passwords using Golang.
package main
import (
"bufio"
"fmt"
"gitlab.com/david_mbuvi/go_asterisks"
"os"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter Your FirstName: ")
firstName, err := reader.ReadString('\n')
if err != nil {
fmt.Println(err)
}
if len(strings.TrimSpace(firstName)) == 0 {
err = fmt.Errorf("Your FirstName can't be empty %v", firstName)
fmt.Println(err.Error())
os.Exit(1)
}
fmt.Print("Enter Your LastName: ")
lastName, err := reader.ReadString('\n')
if err != nil {
fmt.Println(err)
}
if len(strings.TrimSpace(lastName)) == 0 {
err = fmt.Errorf("Your LastName can't be empty %v", lastName)
fmt.Println(err.Error())
os.Exit(1)
}
fmt.Print("Enter Your OrganisationName: ")
company, err := reader.ReadString('\n')
if err != nil {
fmt.Println(err.Error())
}
if len(strings.TrimSpace(company)) == 0 {
err = fmt.Errorf("Your OrganisationName can't be empty %v", company)
fmt.Println(err.Error())
os.Exit(1)
}
// create username
username := firstName[0:1] + lastName
fmt.Printf("Enter your password: ")
// The password you provided from the terminal, echoing as asterisks.
password, err := go_asterisks.GetUsersPassword("", true, os.Stdin, os.Stdout)
if err != nil {
fmt.Println(err.Error())
}
fmt.Println("\n--------- You have provided the following information------------")
fmt.Printf("CompanyName: %s\nUsername: %s\nPassword: %s\n", company, username, password)
fmt.Println("---------End------------")
}
Output:
$ go run main.go
Enter Your FirstName: Doe
Enter Your LastName: John
Enter Your OrganisationName: AWS
Enter your password: *******
--------- You have provided the following information------------
CompanyName: AWS
Username: DJohn
Password: NJ0@123
---------End------------
Explanation:-
In the above code, we have created the readChunk()
function which is for reading users' inputs in chunks of specific length into the buffer. getUsersPassword(prompt string, masked bool, r FieldReader, w io.Writer)([]byte, error){}
its a function that returns inputs read from the terminal. It validates various variables namely prompt if its empty output is prompted to the user, masked if its true, typing will be matched by asterisks on the screen i.e ******* . the rest will display nothing however they hold the state of the terminal implemented using the if terminal.IsTerminal(int(r.Fd())) {}
.
Summary
In this article, we have discussed and demonstrated two ways of reading users' passwords on the terminal. This is critical because the password is essential for access or authorization into an application or system i.e user login to a Linux machine or software etc. the password should be in asterisks or not displayed. This can help software engineer to develop a command line interface CLI login system on a terminal using Golang.
Reference