unixcrypt

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 9, 2026 License: BSD-3-Clause Imports: 9 Imported by: 0

README

go-encryptions/unixcrypt

go-encryptions/unixcrypt

ci Go Reference

Pure-Go (CGO_ENABLED=0) implementation of the crypt(3)/MCF (Modular Crypt Format) password hash algorithms: MD5-crypt ($1$, Poul-Henning Kamp's original FreeBSD algorithm), SHA-256-crypt/SHA-512-crypt ($5$/$6$, per Ulrich Drepper's specification), and bcrypt ($2a$/$2b$/$2x$/$2y$, via the pure-Go blowfish primitive). No component links against libc's own crypt(3) or uses cgo, so it cross-compiles and produces identical output on every Go target, including big-endian s390x.

Every algorithm's output was validated against real openssl passwd and the canonical OpenBSD bcrypt.c test vectors.

This package was extracted from go-puppet/puppet's own pw_hash() implementation to be shared, rather than reimplemented, by every consumer that needs it — currently go-puppet/puppet (Puppet's pw_hash() stdlib function) and go-ansible/template (Ansible's password_hash filter).

Install

go get github.com/go-encryptions/unixcrypt

API

func MD5Crypt(password, salt string) string
func SHA256Crypt(password, salt string, rounds int) string
func SHA512Crypt(password, salt string, rounds int) string
func Bcrypt(password, prefix string, cost int, salt []byte) (string, error)
func BcryptFromMCF(password, prefix, mcfSalt string) (string, error)

func RandomSalt(n int) (string, error)
func ValidSaltChars(s string) bool

const DefaultRounds = 5000
const MinRounds = 1000
const MaxRounds = 999999999
  • MD5Crypt/SHA256Crypt/SHA512Crypt take a plain salt string and return the full $id$salt$hash (or, for SHA-crypt with an explicit round count, $id$rounds=N$salt$hash) MCF string.
  • SHA256Crypt/SHA512Crypt's rounds parameter matches real crypt(3) exactly: 0 means "unspecified" — the algorithm's own default of 5000 applies internally and the output omits the rounds= prefix entirely. Any other value (even 5000 given explicitly) is clamped into [MinRounds, MaxRounds] and the clamped value is always printed, even if clamping left it at 5000 — a caller that explicitly asked for 5000 rounds gets a different string than one that didn't specify rounds at all, even though the digest itself is identical either way. This distinction is real crypt(3) behavior, confirmed against openssl passwd, not an invented convenience.
  • Bcrypt takes a raw 16-byte salt directly; BcryptFromMCF is a convenience for a caller that already has a pre-formed "<cost>$<22-char-base64>" salt (the shape Puppet's own pw_hash(password, type, salt) takes as its third argument).
  • RandomSalt generates a cryptographically random salt from crypt(3)'s own accepted charset ([A-Za-z0-9./]) via crypto/randnot appropriate for bcrypt, whose salt is raw bytes rather than that printable charset; use crypto/rand.Read directly for a bcrypt salt.

Example

package main

import (
	"fmt"

	"github.com/go-encryptions/unixcrypt"
)

func main() {
	salt, _ := unixcrypt.RandomSalt(16)
	fmt.Println(unixcrypt.SHA512Crypt("hunter2", salt, 0))       // real crypt(3) default: 5000 rounds, no prefix
	fmt.Println(unixcrypt.SHA512Crypt("hunter2", salt, 100000))  // $6$rounds=100000$...
}

Scope

Deliberately narrow: the four crypt(3)/MCF formats above, nothing else. apr1 (Apache's own MD5-crypt variant, $apr1$ instead of $1$ — same algorithm, different magic string) is not implemented, since neither current consumer needs it; it would be a one-line addition (MD5Crypt already takes its magic string as an internal constant) if a future consumer does.

Documentation

Overview

Package unixcrypt implements crypt(3)/MCF (Modular Crypt Format) password hash algorithms in pure Go (CGO=0): MD5-crypt ($1$), SHA-256-crypt ($5$) and SHA-512-crypt ($6$) per Ulrich Drepper's specification (https://www.akkadia.org/drepper/SHA-crypt.txt), and bcrypt ($2a$/$2b$/ $2x$/$2y$) via the pure-Go blowfish primitive. No component depends on libc's own crypt(3) or cgo; every output was validated against real `openssl passwd` and the canonical OpenBSD bcrypt test vectors.

Index

Constants

View Source
const (
	MinRounds = 1000
	MaxRounds = 999999999
)

MinRounds and MaxRounds are Drepper's spec's own clamp bounds: an explicitly requested round count outside this range is silently clamped to the nearer bound, matching real crypt(3)/libxcrypt exactly.

View Source
const DefaultRounds = 5000

DefaultRounds is SHA-crypt's own implicit round count, used whenever a caller passes rounds == 0 to SHA256Crypt/SHA512Crypt.

View Source
const SaltChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"

SaltChars is the character set every crypt(3) implementation accepts in a salt: base64-like, but with "./" instead of "+/".

Variables

This section is empty.

Functions

func Bcrypt

func Bcrypt(password, prefix string, cost int, salt []byte) (string, error)

Bcrypt computes a crypt(3)-format bcrypt hash: "$<prefix>$<cost>$<22-char salt><31-char hash>". prefix selects the variant ("2a", "2b", "2x", or "2y" — "2b" is the modern default every current implementation, including Ansible's own, uses); cost is the work factor, 4-31; salt must be exactly 16 raw bytes (bcrypt's own fixed salt size — RandomSalt is NOT the right source here, since bcrypt's salt is raw bytes, not crypt(3)'s usual printable charset; use crypto/rand directly).

func BcryptFromMCF

func BcryptFromMCF(password, prefix, mcfSalt string) (string, error)

BcryptFromMCF reproduces crypt(3) bcrypt for a caller that already has a pre-formed MCF salt of the form "<cost>$<22-char base64>" — the shape Puppet's own pw_hash(password, type, salt) function takes as its third argument.

func MD5Crypt

func MD5Crypt(password, salt string) string

MD5Crypt computes the classic MD5-crypt ($1$) hash of password with salt (created by Poul-Henning Kamp for FreeBSD, later adopted by glibc).

func RandomSalt

func RandomSalt(n int) (string, error)

RandomSalt returns a cryptographically random salt string of length n drawn from SaltChars, using crypto/rand.

func SHA256Crypt

func SHA256Crypt(password, salt string, rounds int) string

SHA256Crypt computes SHA-256-crypt ($5$) per Drepper's spec.

rounds == 0 means "unspecified": the algorithm's own DefaultRounds (5000) applies internally and the output omits the "rounds=N$" prefix — matching real crypt(3), which only prints that prefix when the caller explicitly requested a round count. Any other rounds value (even 5000 given explicitly) is clamped to [MinRounds, MaxRounds] and the clamped value is always printed, even if clamping left it at 5000.

func SHA512Crypt

func SHA512Crypt(password, salt string, rounds int) string

SHA512Crypt is SHA256Crypt's sibling for $6$.

func ValidSaltChars

func ValidSaltChars(s string) bool

ValidSaltChars reports whether every character in s is a valid crypt(3) salt character.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL