Last active
October 6, 2023 14:30
-
-
Save pythonhacker/badf45b2de3e3b1b5c2606bbcfc79a4b to your computer and use it in GitHub Desktop.
Testing password hashing using Argon2id cryptographic password hashing algorithm
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Storing cryptographic password hashes in database or memory | |
// and comparing with password. | |
package main | |
import ( | |
"log" | |
"errors" | |
"strings" | |
"crypto/subtle" | |
"encoding/base64" | |
"fmt" | |
"golang.org/x/crypto/argon2" | |
crand "crypto/rand" | |
) | |
var ( | |
ErrInvalidHash = errors.New("the encoded hash is not in the correct format") | |
ErrIncompatibleVersion = errors.New("incompatible version of argon2") | |
) | |
type params struct { | |
memory uint32 | |
iterations uint32 | |
parallelism uint8 | |
saltLength uint32 | |
keyLength uint32 | |
} | |
func comparePasswordAndHash(password, encodedHash string) (match bool, err error) { | |
// Extract the parameters, salt and derived key from the encoded password | |
// hash. | |
p, salt, hash, err := decodeHash(encodedHash) | |
if err != nil { | |
return false, err | |
} | |
// Derive the key from the other password using the same parameters. | |
otherHash := argon2.IDKey([]byte(password), salt, p.iterations, p.memory, p.parallelism, p.keyLength) | |
// Check that the contents of the hashed passwords are identical. Note | |
// that we are using the subtle.ConstantTimeCompare() function for this | |
// to help prevent timing attacks. | |
if subtle.ConstantTimeCompare(hash, otherHash) == 1 { | |
return true, nil | |
} | |
return false, nil | |
} | |
func decodeHash(encodedHash string) (p *params, salt, hash []byte, err error) { | |
vals := strings.Split(encodedHash, "$") | |
if len(vals) != 6 { | |
return nil, nil, nil, ErrInvalidHash | |
} | |
var version int | |
_, err = fmt.Sscanf(vals[2], "v=%d", &version) | |
if err != nil { | |
return nil, nil, nil, err | |
} | |
if version != argon2.Version { | |
return nil, nil, nil, ErrIncompatibleVersion | |
} | |
p = ¶ms{} | |
_, err = fmt.Sscanf(vals[3], "m=%d,t=%d,p=%d", &p.memory, &p.iterations, &p.parallelism) | |
if err != nil { | |
return nil, nil, nil, err | |
} | |
salt, err = base64.RawStdEncoding.Strict().DecodeString(vals[4]) | |
if err != nil { | |
return nil, nil, nil, err | |
} | |
p.saltLength = uint32(len(salt)) | |
hash, err = base64.RawStdEncoding.Strict().DecodeString(vals[5]) | |
if err != nil { | |
return nil, nil, nil, err | |
} | |
p.keyLength = uint32(len(hash)) | |
return p, salt, hash, nil | |
} | |
func generateRandomBytes(n uint32) ([]byte, error) { | |
b := make([]byte, n) | |
_, err := crand.Read(b) | |
if err != nil { | |
return nil, err | |
} | |
return b, nil | |
} | |
func generateFromPassword(password string, p *params) (string, error) { | |
salt, err := generateRandomBytes(p.saltLength) | |
if err != nil { | |
return "", err | |
} | |
hash := argon2.IDKey([]byte(password), salt, p.iterations, p.memory, p.parallelism, p.keyLength) | |
// Base64 encode the salt and hashed password. | |
b64Salt := base64.RawStdEncoding.EncodeToString(salt) | |
b64Hash := base64.RawStdEncoding.EncodeToString(hash) | |
// Return a string using the standard encoded hash representation. | |
encodedHash := fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, p.memory, p.iterations, p.parallelism, b64Salt, b64Hash) | |
return encodedHash, nil | |
} | |
func readPasswd() string { | |
fmt.Printf("Enter a password: ") | |
var pwd string | |
_, err := fmt.Scan(&pwd) | |
if err != nil { | |
log.Println(err) | |
} | |
return pwd | |
} | |
func main() { | |
passwd := readPasswd() | |
p := ¶ms{ | |
memory: 64 * 1024, | |
iterations: 3, | |
parallelism: 2, | |
saltLength: 16, | |
keyLength: 32, | |
} | |
encodedHash, err := generateFromPassword(passwd, p) | |
fmt.Printf("encoded hash => %s\n", encodedHash) | |
if err != nil { | |
log.Fatal(err) | |
} | |
passwd2 := readPasswd() | |
// Use a different password if you want... | |
match, err := comparePasswordAndHash(passwd2, encodedHash) | |
if err != nil { | |
log.Fatal(err) | |
} | |
fmt.Printf("Match: %v\n", match) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment