package security import ( "crypto/rand" "crypto/sha512" "crypto/subtle" "encoding/base64" "fmt" "io" "strings" "golang.org/x/crypto/argon2" ) const ( argonTime = 3 argonMemory = 64 * 1024 argonThreads = 2 argonSaltLen = 16 argonKeyLen = 64 ) func HashFileReader(r io.Reader) (string, error) { h := sha512.New() if _, err := io.Copy(h, r); err != nil { return "", err } // return in b64 encoding return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil } func HashKey(key string) (string, error) { salt := make([]byte, argonSaltLen) rand.Read(salt) hash := argon2.IDKey([]byte(key), salt, argonTime, argonMemory, argonThreads, argonKeyLen) // encode internally (NOT in handler) b64Salt := base64.RawStdEncoding.EncodeToString(salt) b64Hash := base64.RawStdEncoding.EncodeToString(hash) // single stored string encoded := fmt.Sprintf("v1:%s:%s", b64Salt, b64Hash) return encoded, nil } func CompareKey(key, candidate string) bool { parts := strings.Split(candidate, ":") if len(parts) != 3 { return false } //version := parts[0] b64Salt := parts[1] b64Hash := parts[2] salt, err := base64.RawStdEncoding.DecodeString(b64Salt) if err != nil { return false } expected, err := base64.RawStdEncoding.DecodeString(b64Hash) if err != nil { return false } actual := argon2.IDKey([]byte(key), salt, argonTime, argonMemory, argonThreads, argonKeyLen) return subtle.ConstantTimeCompare(actual, expected) == 1 }