bcrypt

package module
v0.0.0-...-198c59c Latest Latest
Warning

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

Go to latest
Published: Jul 17, 2026 License: BSD-3-Clause Imports: 6 Imported by: 0

README

go-ruby-bcrypt/bcrypt

bcrypt — go-ruby-bcrypt

Docs License Go Coverage

A pure-Go (no cgo) reimplementation of Ruby's bcrypt gemBCrypt::Password and BCrypt::Engine — for hashing and verifying passwords with the OpenBSD bcrypt() algorithm. It produces and consumes the same $2a$ hashes the gem does, so a hash created here verifies under the gem's Password#== and a gem-created hash verifies here — without any Ruby runtime.

It is the password-hashing backend for go-embedded-ruby, but is a standalone, reusable module — a sibling of go-ruby-yaml (the Psych port), go-ruby-regexp (the Onigmo engine) and go-ruby-erb (the ERB compiler).

Cross-compatible with the gem, both directions

The crypto core is the OpenBSD bcrypt key schedule over golang.org/x/crypto/blowfish; there is no cgo and no C bcrypt.

  • Our hash → gem. Create emits a $2a$NN$… hash the gem's BCrypt::Password.new(hash) == secret accepts.
  • Gem hash → us. NewPassword(gemHash).EqualString(secret) accepts a hash the gem produced.
  • 72-byte truncation. The gem truncates secrets to 72 bytes before hashing (staying compatible with the older C library). This port truncates identically, so a secret longer than 72 bytes hashes to the same value here and in the gem and cross-verifies at the edge in both directions.

Features

Faithful port of the gem's public surface, validated against the bcrypt gem on every platform that has it:

  • PasswordCreate / CreateString (default cost 12, $2a$), NewPassword, constant-time Equal / EqualString, and Cost / Salt / Version / Checksum / String accessors, matching BCrypt::Password.
  • EngineHashSecret, GenerateSalt(cost), ValidSecret, ValidSalt, ValidHash, AutodetectCost, and the MinCost / MaxCost / DefaultCost / MaxSecretBytesize / MaxSaltLength bounds, matching BCrypt::Engine.
  • Gem-faithful cost handling — a cost below MinCost (4) is raised to 4, a cost of 0 or below is rejected (ErrInvalidCost), and a cost above MaxCost (31) is rejected (ErrCostTooHigh, the gem's ArgumentError).
  • Errors — sentinel errors mirroring BCrypt::Errors (ErrInvalidSecret, ErrInvalidCost, ErrInvalidSalt, ErrInvalidHash), matchable with errors.Is.

CGO-free, 100% test coverage, gofmt + go vet clean, and green across the six 64-bit Go targets (amd64, arm64, riscv64, loong64, ppc64le, s390x).

Install

go get github.com/go-ruby-bcrypt/bcrypt

Usage

package main

import (
	"fmt"

	"github.com/go-ruby-bcrypt/bcrypt"
)

func main() {
	// Hash a secret (default cost 12; WithCost overrides).
	pw, _ := bcrypt.CreateString("my grand secret", bcrypt.WithCost(12))
	fmt.Println(pw.String()) // $2a$12$C5.FIvVDS9W4AYZ/Ib37Yu…

	// Verify, in constant time.
	fmt.Println(pw.EqualString("my grand secret")) // true
	fmt.Println(pw.EqualString("a paltry guess"))  // false

	// Parse a stored hash back.
	stored, _ := bcrypt.NewPassword(pw.String())
	fmt.Println(stored.Cost(), stored.Version()) // 12 2a
	fmt.Println(stored.Salt())                   // $2a$12$C5.FIvVDS9W4AYZ/Ib37Yu

	// Low-level engine: hash a secret against a specific salt.
	salt, _ := bcrypt.GenerateSalt(10)
	h, _ := bcrypt.HashSecret([]byte("secret"), salt)
	fmt.Println(h)
}

API

// Password (BCrypt::Password)
func Create(secret []byte, opts ...CreateOption) (*Password, error)
func CreateString(secret string, opts ...CreateOption) (*Password, error)
func NewPassword(rawHash string) (*Password, error)
func WithCost(cost int) CreateOption

func (p *Password) Equal(secret []byte) bool
func (p *Password) EqualString(secret string) bool
func (p *Password) String() string   // to_s
func (p *Password) Version() string  // "2a"
func (p *Password) Cost() int
func (p *Password) Salt() string
func (p *Password) Checksum() string

// Engine (BCrypt::Engine)
func HashSecret(secret []byte, salt string) (string, error)
func GenerateSalt(cost int) (string, error)
func ValidSecret(secret []byte) bool
func ValidSalt(salt string) bool
func ValidHash(h string) bool
func AutodetectCost(salt string) int

const (
	DefaultCost       = 12 // DEFAULT_COST
	MinCost           = 4  // MIN_COST
	MaxCost           = 31 // MAX_COST
	MaxSecretBytesize = 72 // MAX_SECRET_BYTESIZE
	MaxSaltLength     = 16 // MAX_SALT_LENGTH
)

// Errors (BCrypt::Errors), matchable with errors.Is.
var ErrInvalidSecret, ErrInvalidCost, ErrInvalidSalt, ErrInvalidHash, ErrCostTooHigh error

Tests & coverage

The suite pairs deterministic, ruby-free golden vectors (fixed salt + secret → hash, captured from the gem; these alone hold coverage at 100%, so the qemu cross-arch and Windows lanes pass the gate) with a differential gem oracle gated on RUBY_VERSION >= "4.0": hashes made here are verified by the gem's Password#==, gem-made hashes are verified here, .cost / .salt / .version / .checksum parsing is compared against the gem, and the 72-byte truncation edge is crossed in both directions. The oracle scripts $stdout.binmode so Windows text-mode never pollutes the bytes, and skip themselves where ruby or the gem is absent.

COVERPKG=$(go list ./... | paste -sd, -)
go test -race -coverpkg="$COVERPKG" -coverprofile=cover.out ./...
go tool cover -func=cover.out | tail -1   # 100.0%

License

BSD-3-Clause — see LICENSE. Copyright the go-ruby-bcrypt/bcrypt authors.

WebAssembly

Being pure Go (CGO=0), this library also compiles to WebAssembly — both GOOS=js GOARCH=wasm (browser / Node.js) and GOOS=wasip1 GOARCH=wasm (WASI). CI builds both targets on every push, alongside the six 64-bit native/qemu arches.

GOOS=js     GOARCH=wasm go build ./...   # browser / Node
GOOS=wasip1 GOARCH=wasm go build ./...   # WASI (wasmtime, wasmer, wasmedge, …)

Documentation

Overview

Package bcrypt is a pure-Go (no cgo) reimplementation of Ruby's bcrypt gem — BCrypt::Password and BCrypt::Engine — for hashing and verifying passwords with the OpenBSD bcrypt() algorithm.

It produces and consumes the same "$2a$NN$...." hashes the gem does: a hash Create emits verifies under the gem's Password#==, and a gem-emitted hash verifies under Password.Equal. Secrets longer than 72 bytes are truncated exactly like the gem (and the older C libraries it stays compatible with), so hashes cross-verify in both directions at the 72-byte edge.

The crypto core is the OpenBSD bcrypt key schedule over golang.org/x/crypto/blowfish; there is no cgo and no C bcrypt. It is the password-hashing backend for go-embedded-ruby, but is a standalone, reusable module — a sibling of go-ruby-yaml, go-ruby-regexp and go-ruby-erb.

Quick start

pw, _ := bcrypt.CreateString("secret", bcrypt.WithCost(12))
pw.EqualString("secret") // true
pw.EqualString("nope")   // false

stored, _ := bcrypt.NewPassword(pw.String())
stored.Cost()    // 12
stored.Version() // "2a"

Index

Constants

View Source
const (
	// DefaultCost is the cost used when none is given (BCrypt::Engine::DEFAULT_COST).
	DefaultCost = 12
	// MinCost is the minimum cost the algorithm supports (BCrypt::Engine::MIN_COST).
	MinCost = 4
	// MaxCost is the maximum cost the algorithm supports (BCrypt::Engine::MAX_COST).
	MaxCost = 31
	// MaxSecretBytesize is the byte length secrets are truncated to before
	// hashing (BCrypt::Engine::MAX_SECRET_BYTESIZE). Older bcrypt libraries
	// truncated here and the gem keeps doing so for forward compatibility.
	MaxSecretBytesize = 72
	// MaxSaltLength is the raw salt length in bytes (BCrypt::Engine::MAX_SALT_LENGTH).
	MaxSaltLength = 16
)

Engine cost constants, matching BCrypt::Engine.

View Source
const Version = "0.1.0"

Version is the version of this Go port. It is independent of the upstream Ruby gem's version.

Variables

View Source
var (
	// ErrInvalidSecret is returned when a secret cannot be coerced to a string
	// (BCrypt::Errors::InvalidSecret). In this Go port a []byte / string secret
	// is always valid, so this only surfaces from the low-level engine guard.
	ErrInvalidSecret = errors.New("bcrypt: invalid secret")

	// ErrInvalidCost is returned when a cost is not numeric and > 0
	// (BCrypt::Errors::InvalidCost), matching Engine.generate_salt.
	ErrInvalidCost = errors.New("bcrypt: cost must be numeric and > 0")

	// ErrInvalidSalt is returned when a salt does not match the bcrypt salt
	// grammar (BCrypt::Errors::InvalidSalt).
	ErrInvalidSalt = errors.New("bcrypt: invalid salt")

	// ErrInvalidHash is returned when a stored hash does not match the bcrypt
	// hash grammar (BCrypt::Errors::InvalidHash).
	ErrInvalidHash = errors.New("bcrypt: invalid hash")

	// ErrCostTooHigh is returned by Password.create when the requested cost
	// exceeds MaxCost. The gem raises a bare ArgumentError here.
	ErrCostTooHigh = errors.New("bcrypt: cost exceeds the maximum allowed")
)

The package's sentinel errors mirror the exception hierarchy of the Ruby bcrypt gem's BCrypt::Errors module: InvalidSecret, InvalidCost, InvalidSalt and InvalidHash. A caller can match them with errors.Is.

Functions

func AutodetectCost

func AutodetectCost(salt string) int

AutodetectCost extracts the cost embedded in a salt or hash string (BCrypt::Engine.autodetect_cost: salt[4..5].to_i). It returns 0 for a string too short to carry a cost.

func GenerateSalt

func GenerateSalt(cost int) (string, error)

GenerateSalt returns a random salt of the form "$2a$NN$...." for the given cost. A cost <= 0 returns ErrInvalidCost; a cost below MinCost is raised to MinCost, matching BCrypt::Engine.generate_salt.

func HashSecret

func HashSecret(secret []byte, salt string) (string, error)

HashSecret hashes secret against a valid salt, returning the full "$2a$NN$...."-form hash. Secrets longer than MaxSecretBytesize are truncated, exactly like BCrypt::Engine.hash_secret. An invalid salt returns ErrInvalidSalt.

func ValidHash

func ValidHash(h string) bool

ValidHash reports whether h matches the bcrypt hash grammar (BCrypt::Password.valid_hash?).

func ValidSalt

func ValidSalt(salt string) bool

ValidSalt reports whether salt matches the bcrypt salt grammar (BCrypt::Engine.valid_salt?).

func ValidSecret

func ValidSecret(secret []byte) bool

ValidSecret reports whether secret is acceptable, mirroring BCrypt::Engine.valid_secret? (which accepts anything coercible to a string). A Go []byte is always valid.

Types

type CreateOption

type CreateOption func(*createOptions)

CreateOption configures Create; the only knob the gem exposes is :cost.

func WithCost

func WithCost(cost int) CreateOption

WithCost sets the cost factor for Create (the gem's :cost option).

type Engine

type Engine struct{}

Engine mirrors Ruby's BCrypt::Engine: the low-level surface that hashes a secret against a salt, generates salts, and validates secrets and salts. Its methods are exposed as package functions on the Engine value returned by DefaultEngine, but are also available directly as package-level functions.

type Password

type Password struct {
	// contains filtered or unexported fields
}

Password is a parsed bcrypt password hash, mirroring Ruby's BCrypt::Password (which subclasses String). Its String form is the stored "$2a$NN$...." hash; Equal / EqualString compare a candidate secret against it in constant time.

func Create

func Create(secret []byte, opts ...CreateOption) (*Password, error)

Create hashes secret and returns a *Password, mirroring BCrypt::Password.create. Without WithCost it uses DefaultCost. A cost above MaxCost returns ErrCostTooHigh (the gem raises ArgumentError); a cost below MinCost is raised to MinCost by the salt generator.

func CreateString

func CreateString(secret string, opts ...CreateOption) (*Password, error)

CreateString is Create for a string secret.

func NewPassword

func NewPassword(rawHash string) (*Password, error)

NewPassword parses a stored hash into a *Password, mirroring BCrypt::Password.new. A string that is not a valid bcrypt hash returns ErrInvalidHash.

func (*Password) Checksum

func (p *Password) Checksum() string

Checksum returns the trailing digest (BCrypt::Password#checksum).

func (*Password) Cost

func (p *Password) Cost() int

Cost returns the cost factor (BCrypt::Password#cost).

func (*Password) Equal

func (p *Password) Equal(secret []byte) bool

Equal reports whether secret is the secret this hash was derived from, comparing in constant time (BCrypt::Password#==). It re-hashes secret against this password's salt and compares the result to the stored hash.

func (*Password) EqualString

func (p *Password) EqualString(secret string) bool

EqualString is Equal for a string secret.

func (*Password) Salt

func (p *Password) Salt() string

Salt returns the salt portion, the first 29 chars including version and cost (BCrypt::Password#salt).

func (*Password) String

func (p *Password) String() string

String returns the stored hash (BCrypt::Password#to_s).

func (*Password) Version

func (p *Password) Version() string

Version returns the algorithm version, e.g. "2a" (BCrypt::Password#version).

Jump to

Keyboard shortcuts

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