Initial commit
This commit is contained in:
commit
40d6bfd9d9
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
|
||||
# By Reza
|
||||
build/
|
||||
.archive/
|
||||
.vagrant/
|
||||
.env
|
||||
.keys
|
||||
*_[0-9]
|
||||
*_[0-9][0-9]
|
||||
*_????-??-??
|
||||
*.zip
|
||||
*.argon2
|
26
Makefile
Normal file
26
Makefile
Normal file
@ -0,0 +1,26 @@
|
||||
# Makefile for PassPractice
|
||||
|
||||
# Go parameters
|
||||
GOCMD=go
|
||||
GOBUILD=$(GOCMD) build
|
||||
GOCLEAN=$(GOCMD) clean
|
||||
GOTEST=$(GOCMD) test
|
||||
GOGET=$(GOCMD) get
|
||||
BINARY_NAME=PassPractice
|
||||
BINARY_DIR=./build
|
||||
|
||||
all: run
|
||||
|
||||
build:
|
||||
mkdir -p $(BINARY_DIR)
|
||||
$(GOBUILD) -o $(BINARY_DIR)/$(BINARY_NAME) -v
|
||||
|
||||
run: build
|
||||
$(BINARY_DIR)/$(BINARY_NAME)
|
||||
|
||||
test:
|
||||
$(GOTEST) -v ./...
|
||||
|
||||
clean:
|
||||
$(GOCLEAN)
|
||||
rm -rf $(BINARY_DIR)
|
9
go.mod
Normal file
9
go.mod
Normal file
@ -0,0 +1,9 @@
|
||||
module git.behzadan.ir/p/PassPractice.git
|
||||
|
||||
go 1.21.6
|
||||
|
||||
require (
|
||||
golang.org/x/crypto v0.18.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
golang.org/x/term v0.16.0 // indirect
|
||||
)
|
6
go.sum
Normal file
6
go.sum
Normal file
@ -0,0 +1,6 @@
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
178
main.go
Normal file
178
main.go
Normal file
@ -0,0 +1,178 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/argon2"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
const (
|
||||
version = "1.0.0"
|
||||
helpText = `Usage: PassPractice [options]
|
||||
Options:
|
||||
-s, --store Enter Store Mode to store a new password
|
||||
-v, --version Display version information
|
||||
-h, --help Display this help text
|
||||
Run without options to enter Verify Mode.`
|
||||
filename = "password.argon2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
storeFlag := flag.Bool("store", false, "Enter Store Mode to store a new password")
|
||||
storeFlagShort := flag.Bool("s", false, "Enter Store Mode to store a new password (short)")
|
||||
versionFlag := flag.Bool("version", false, "Display version information")
|
||||
versionFlagShort := flag.Bool("v", false, "Display version information (short)")
|
||||
helpFlag := flag.Bool("help", false, "Display help text")
|
||||
helpFlagShort := flag.Bool("h", false, "Display help text (short)")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *versionFlag || *versionFlagShort {
|
||||
fmt.Println("Version:", version)
|
||||
return
|
||||
}
|
||||
|
||||
if *helpFlag || *helpFlagShort {
|
||||
fmt.Println(helpText)
|
||||
return
|
||||
}
|
||||
|
||||
if *storeFlag || *storeFlagShort {
|
||||
storeMode()
|
||||
} else {
|
||||
verifyMode()
|
||||
}
|
||||
}
|
||||
|
||||
func getPasswordFromUser(prompt string) string {
|
||||
fmt.Print(prompt)
|
||||
|
||||
// Read the password securely without echoing
|
||||
password, err := terminal.ReadPassword(int(syscall.Stdin))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Print a new line after user input
|
||||
fmt.Println()
|
||||
|
||||
// Convert byte slice to string
|
||||
passwordString := strings.TrimSpace(string(password))
|
||||
|
||||
return passwordString
|
||||
}
|
||||
|
||||
func storeMode() {
|
||||
password := getPasswordFromUser("Enter password to store: ")
|
||||
|
||||
hash := argon2.IDKey([]byte(password), []byte("somesalt"), 1, 64*1024, 4, 32)
|
||||
err := ioutil.WriteFile(filename, hash, 0644)
|
||||
if err != nil {
|
||||
fmt.Println("Error saving password:", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("Password stored successfully.")
|
||||
}
|
||||
|
||||
func verifyMode() {
|
||||
hash, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading stored password:", err)
|
||||
return
|
||||
}
|
||||
|
||||
streak := 0
|
||||
|
||||
for {
|
||||
|
||||
password, elapsedTime := getPasswordFromUserAndTimeIt("Enter password to verify (or press enter to exit): ")
|
||||
|
||||
// Check if the password is empty and exit the program
|
||||
if len(strings.TrimSpace(password)) == 0 {
|
||||
fmt.Println("Exiting PassPractice.")
|
||||
break
|
||||
}
|
||||
|
||||
charsPerSecond := float64(len(password)) / elapsedTime.Seconds()
|
||||
|
||||
// Round the time and charsPerSecond to two decimal places
|
||||
roundedTime := math.Round(elapsedTime.Seconds()*100) / 100
|
||||
roundedCharsPerSecond := math.Round(charsPerSecond*100) / 100
|
||||
|
||||
hashedInput := argon2.IDKey([]byte(password), []byte("somesalt"), 1, 64*1024, 4, 32)
|
||||
|
||||
if string(hashedInput) == string(hash) {
|
||||
streak++
|
||||
fmt.Printf("Correct! Streak: %d | Time taken: %.2fs | Chars/sec: %.2f\n", streak, roundedTime, roundedCharsPerSecond)
|
||||
} else {
|
||||
streak = 0
|
||||
fmt.Printf("Incorrect. Streak reset to 0. | Time taken: %.2fs | Chars/sec: %.2f\n", roundedTime, roundedCharsPerSecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getPasswordFromUser reads password input character by character
|
||||
// Returns the entered password and the time taken to type it
|
||||
func getPasswordFromUserAndTimeIt(prompt string) (string, time.Duration) {
|
||||
fmt.Print(prompt)
|
||||
|
||||
state, err := term.MakeRaw(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer term.Restore(int(os.Stdin.Fd()), state)
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
|
||||
var startTime time.Time
|
||||
var password []rune
|
||||
firstChar := true
|
||||
|
||||
for {
|
||||
char, _, err := reader.ReadRune()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Start the timer on the first character entered
|
||||
if firstChar {
|
||||
startTime = time.Now()
|
||||
firstChar = false
|
||||
}
|
||||
|
||||
// Enter key pressed
|
||||
if char == '\r' || char == '\n' {
|
||||
break
|
||||
}
|
||||
|
||||
// Check for Ctrl+C or Ctrl+D
|
||||
if char == 3 || char == 4 { // Ctrl+C or Ctrl+D
|
||||
fmt.Println("\r")
|
||||
return "", 0.0
|
||||
}
|
||||
|
||||
// Backspace or delete key pressed
|
||||
if char == '\b' || char == 0x7F {
|
||||
if len(password) > 0 {
|
||||
password = password[:len(password)-1]
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
password = append(password, char)
|
||||
}
|
||||
fmt.Println("\r")
|
||||
|
||||
return string(password), time.Since(startTime)
|
||||
}
|
Loading…
Reference in New Issue
Block a user