github.com/tommi2day/gomodules@v1.13.2-0.20240423190010-b7d55d252a27/pwlib/scram.go (about)

     1  package pwlib
     2  
     3  // Origin: https://github.com/tv42/scram-password/tree/main/internal/scramble
     4  // License: https://github.com/tv42/scram-password/blob/main/LICENSE
     5  
     6  import (
     7  	"crypto/rand"
     8  	"encoding/base64"
     9  	"fmt"
    10  
    11  	"github.com/xdg-go/scram"
    12  )
    13  
    14  func makeSalt(size int) ([]byte, error) {
    15  	salt := make([]byte, size)
    16  	if _, err := rand.Read(salt); err != nil {
    17  		return nil, err
    18  	}
    19  	return salt, nil
    20  }
    21  
    22  func hashWithKF(username string, password string, kf scram.KeyFactors) (string, error) {
    23  	// We could expose this as a command-line flag, but first need a use case we can test against.
    24  	const authID = ""
    25  	// We could make the algorithm a command-line flag.
    26  	client, err := scram.SHA256.NewClient(username, password, authID)
    27  	if err != nil {
    28  		return "", err
    29  	}
    30  
    31  	credentials := client.GetStoredCredentials(kf)
    32  
    33  	// SCRAM-SHA-256$<iter>:<salt>$<StoredKey>:<ServerKey>
    34  	hashed := fmt.Sprintf("SCRAM-SHA-256$%d:%s$%s:%s",
    35  		credentials.Iters,
    36  		base64.StdEncoding.EncodeToString([]byte(credentials.Salt)),
    37  		base64.StdEncoding.EncodeToString(credentials.StoredKey),
    38  		base64.StdEncoding.EncodeToString(credentials.ServerKey),
    39  	)
    40  	return hashed, nil
    41  }
    42  
    43  // ScramPassword returns a SCRAM-SHA-256 password hash for the given username and password as used by postgresql11+
    44  func ScramPassword(username string, password string) (string, error) {
    45  	// We could take a known salt (as base64) as a command-line flag.
    46  
    47  	// We could take salt size as a command-line flag.
    48  	//
    49  	// Postgres 14 uses salt size 16.
    50  	// We'd rather be ahead of the curve than behind.
    51  	const saltSize = 24
    52  	salt, err := makeSalt(saltSize)
    53  	if err != nil {
    54  		return "", err
    55  	}
    56  	kf := scram.KeyFactors{
    57  		Salt: string(salt),
    58  		// We could take iterations as a command-line flag.
    59  		Iters: 4096,
    60  	}
    61  	return hashWithKF(username, password, kf)
    62  }