aboutsummaryrefslogblamecommitdiff
path: root/cmd/monfront/pw.go
blob: d06e9f2cca07e89ead30f7c670c667ad997a4b0f (plain) (tree)


































































































                                                                              
package main

import (
	"bytes"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"strings"

	"golang.org/x/crypto/scrypt"
)

type (
	pwHash struct {
		salt []byte
		hash []byte
	}
)

// Create a new password hash.
func newHash(pw string) (*pwHash, error) {
	hash := pwHash{}
	if err := hash.genSalt(); err != nil {
		return nil, err
	}
	h, err := hash.Hash(pw)
	if err != nil {
		return nil, err
	}
	hash.hash = h
	return &hash, nil
}

// generate a hash for the given salt and password
func (p *pwHash) Hash(pw string) ([]byte, error) {
	if len(p.salt) == 0 {
		return []byte{}, fmt.Errorf("salt not initialized")
	}
	// constants taken from https://godoc.org/golang.org/x/crypto/scrypt
	hash, err := scrypt.Key([]byte(pw), p.salt, 32768, 8, 1, 32)
	if err != nil {
		return []byte{}, fmt.Errorf("could not compute hash: %s", err)
	}
	return hash, nil
}

// genSalt generates 8 bytes of salt.
func (p *pwHash) genSalt() error {
	salt := make([]byte, 8)
	_, err := rand.Read(salt)
	p.salt = salt
	return err
}

// compare a hash to a password and return true, when it matches.
func (p *pwHash) compare(pw string) (bool, error) {
	hash, err := p.Hash(pw)
	if err != nil {
		return false, fmt.Errorf("could not check password")
	}
	if bytes.Compare(p.hash, hash) == 0 {
		return true, nil
	}
	return false, nil
}

// Encode a hash and salt to a string.
func (p *pwHash) String() string {
	return fmt.Sprintf(
		"1$%s$%s",
		base64.StdEncoding.EncodeToString(p.salt),
		base64.StdEncoding.EncodeToString(p.hash),
	)
}

// Parse a hash from a file or anywhere.
func (p *pwHash) Parse(raw string) error {
	if len(raw) == 0 {
		return fmt.Errorf("no hash found")
	}
	parts := strings.Split(raw, "$")
	if len(parts) != 3 {
		return fmt.Errorf("format error")
	}
	if parts[0] != "1" {
		return fmt.Errorf("unknown hash version")
	}
	salt, err := base64.StdEncoding.DecodeString(parts[1])
	if err != nil {
		return fmt.Errorf("could not parse salt: %s", err)
	}
	hash, err := base64.StdEncoding.DecodeString(parts[2])
	if err != nil {
		return fmt.Errorf("could not parse salt: %s", err)
	}
	p.salt = salt
	p.hash = hash
	return nil
}