Skip to content

Commit

Permalink
Added it.HashPassword & it.VerifyPassword
Browse files Browse the repository at this point in the history
  • Loading branch information
theHamdiz committed Feb 5, 2025
1 parent 2b05f33 commit 9304e92
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 11 deletions.
26 changes: 16 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -467,22 +467,28 @@ choose, err := math.Binomial(20, 10)
fib := math.Fibonacci(42)
```
### GenerateSecret - Probably SecureTM
Ultimate Security Suite – When “Good Enough” Isn’t Good Enough
```go
import "github.com/theHamdiz/it"
// When you need a secret that's totally random*
secret := it.GenerateSecret(32)

// * Usually uses crypto/rand, but if that fails...
// well, let's just say we get creative with time.
// It's like using your birthday as a password,
// but with nanoseconds. Security through obscurity!
// When you need a secret that's totally random*:
secret := it.GenerateSecret(32)
// *Usually uses crypto/rand, but if that fails... well, we get creative with time.
// It's like using your birthday as a password, but with nanoseconds. Security through obscurity!

// When your password is too lazy to protect itself:
hashed, err := it.HashPassword("mySuperSecret", 12)
// Your password is sent to a rigorous bootcamp (bcrypt rounds), emerging as a hardened hash with its own unique salt.
// If the bootcamp fails, you'll get a polite error message.

// Think your password can waltz past the velvet rope?
err = it.VerifyPassword(hashed, "mySuperSecret")
// If err is nil, congratulations—your password made the cut.
// Otherwise, it's like a bouncer telling you, "Not on the list, buddy."
```
Perfect for when you need cryptographic strength secrets, unless you don't, in which case you'll get something that looks cryptographic enough to fool management.
Now go forth and generate secrets that are definitely not predictable (most of the time).
Now go forth and generate cryptographically convincing secrets, hash those passwords like they’re training for a marathon, and verify them with the confidence of a seasoned doorman. Enjoy your Ultimate Security Suite—because sometimes, even security needs a little swagger.
### Config - Because Hardcoding is a Crime
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ require (
require (
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/sys v0.29.0 // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc=
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
20 changes: 20 additions & 0 deletions it.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import (
"github.com/theHamdiz/it/rl"
"github.com/theHamdiz/it/sm"
"github.com/theHamdiz/it/tk"
"golang.org/x/crypto/bcrypt"
)

// ===================================================
Expand Down Expand Up @@ -654,6 +655,25 @@ func GenerateSecret(numBytes int) string {
return hex.EncodeToString(bytes)
}

// HashPassword takes your oh-so-secret password and a cost factor (because apparently, more work equals more security),
// then returns a bcrypt hash or an error if things go sideways. Try not to be shocked by the complexity.
func HashPassword(password string, cost int) ([]byte, error) {
// Convert your string password to bytes because bcrypt is stuck in the dark ages of byte slices.
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), cost)
if err != nil {
// Oops! Something went wrong. Hopefully, you enjoy error messages as much as we do.
return nil, fmt.Errorf("failed to hash password (surprise!): %w", err)
}
return hashedPassword, nil
}

// VerifyPassword compares a stored bcrypt hashed password with the plain text password you (hopefully) remembered.
// Returns nil if they match, otherwise an error. Yes, it's basically a one-way street: you can't decrypt, you can only compare.
func VerifyPassword(hashedPassword []byte, password string) error {
// Compare the hash with the password (converted to bytes, because we're stuck in byte land).
return bcrypt.CompareHashAndPassword(hashedPassword, []byte(password))
}

// =======================================================
// Configuration - Making Things Configurable Since 2025
// =======================================================
Expand Down
75 changes: 74 additions & 1 deletion it_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"time"

"github.com/theHamdiz/it"
"golang.org/x/crypto/bcrypt"
)

// TestRecoverPanicAndContinue tests panic recovery
Expand Down Expand Up @@ -359,7 +360,7 @@ func TestWaitFor(t *testing.T) {
func TestGenerateSecret(t *testing.T) {
for secretLength := 4; secretLength <= 16; secretLength++ {
seenSecrets := make(map[string]struct{})
// Technically, duplicate secrets could be produced even
// Technically, duplicate secrets could be produced even
// if working properly, but it's relatively unlikely.
for i := 0; i < 10; i++ {
secret := it.GenerateSecret(secretLength)
Expand All @@ -375,6 +376,78 @@ func TestGenerateSecret(t *testing.T) {
}
}

// TestHashPassword ensures that HashPassword returns a valid bcrypt hash.
func TestHashPassword(t *testing.T) {
password := "mySecretPassword123"
cost := 12

hashed, err := it.HashPassword(password, cost)
if err != nil {
t.Fatalf("HashPassword returned an error (surprise!): %v", err)
}
if len(hashed) == 0 {
t.Fatal("Expected a non-empty hashed password; did you forget to hash it?")
}

// Check that the resulting hash is valid by using bcrypt's CompareHashAndPassword.
if err := bcrypt.CompareHashAndPassword(hashed, []byte(password)); err != nil {
t.Errorf("bcrypt comparison failed: %v", err)
}
}

// TestVerifyPassword_Correct verifies that VerifyPassword accepts the correct password.
func TestVerifyPassword_Correct(t *testing.T) {
password := "mySecretPassword123"
cost := 12

hashed, err := it.HashPassword(password, cost)
if err != nil {
t.Fatalf("HashPassword returned an error (not again!): %v", err)
}

// The correct password should pass verification.
if err := it.VerifyPassword(hashed, password); err != nil {
t.Errorf("VerifyPassword failed for a correct password: %v", err)
}
}

// TestVerifyPassword_Incorrect ensures that VerifyPassword rejects an incorrect password.
func TestVerifyPassword_Incorrect(t *testing.T) {
password := "mySecretPassword123"
wrongPassword := "wrongPassword"
cost := 12

hashed, err := it.HashPassword(password, cost)
if err != nil {
t.Fatalf("HashPassword returned an error (seriously?): %v", err)
}

// The wrong password should not verify.
if err := it.VerifyPassword(hashed, wrongPassword); err == nil {
t.Error("VerifyPassword accepted an incorrect password (we thought you cared about security)")
}
}

// TestHashProducesDifferentHashes verifies that the same password produces different hashes
// each time due to the random salt. Because if they're equal, then something is very wrong.
func TestHashProducesDifferentHashes(t *testing.T) {
password := "mySecretPassword123"
cost := 12

hashed1, err := it.HashPassword(password, cost)
if err != nil {
t.Fatalf("First HashPassword call failed: %v", err)
}
hashed2, err := it.HashPassword(password, cost)
if err != nil {
t.Fatalf("Second HashPassword call failed: %v", err)
}

if string(hashed1) == string(hashed2) {
t.Error("Two hashes for the same password should not be equal (thanks, salt!)")
}
}

// TestStructuredLogging tests structured logging functionality
func TestStructuredLogging(t *testing.T) {
tmpFile, err := os.CreateTemp("", "structured_log_test")
Expand Down

0 comments on commit 9304e92

Please sign in to comment.