Golang hide password input [Replace input with asterisk]


GO

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

syscall

 

Deepak Prasad

Deepak Prasad

He is the founder of GoLinuxCloud and brings over a decade of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels in various domains, from development to DevOps, Networking, and Security, ensuring robust and efficient solutions for diverse projects. You can connect with him on his LinkedIn profile.

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can send mail to admin@golinuxcloud.com

Thank You for your support!!

Leave a Comment