libcipher

package
v0.40.5 Latest Latest
Warning

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

Go to latest
Published: Aug 15, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package libcipher provides a collection of cryptographic utilities for encryption, decryption, integrity verification, and secure key generation. It includes implementations for AES-GCM (authenticated encryption) and AES-CBC combined with HMAC (for encryption with integrity verification), as well as functions for sealed HMAC hash creation and constant-time comparison.

The package offers the following functionalities:

  • AES-GCM based encryption/decryption, which provides both confidentiality and authenticity.
  • AES-CBC with HMAC for scenarios where nonce collisions are a concern, especially in high-volume or distributed environments. This mode encrypts data using AES-CBC (with PKCS#7 padding) and ensures integrity via an HMAC over the encrypted payload and additional data.
  • Sealed HMAC hash creation and comparison, where a unique salt is automatically added and the resulting JSON-encoded object encapsulates both the computed HMAC digest and the salt.
  • Cryptographically secure key generation.
  • Ed25519 signing keys: generation, seed and public-key parsing/formatting, signing and verification. These live here rather than at the call site so that one encoding and one set of length checks serve every component that handles a key.

Security Considerations:

  • The encryption key and integrity key must be kept secret and must be distinct. Reusing keys for different purposes can compromise security.
  • An Ed25519 keypair is an identity, not a transport secret: what it signs is defined by the protocol using it, and that definition belongs in that protocol's package. This package signs the bytes it is given and nothing more.
  • When using AES-CBC with HMAC, ensure that the entire ciphertext fits in memory as the HMAC is computed over the complete message. For high-volume systems, AES-GCM may be preferred.

Index

Constants

View Source
const (
	SigningSeedSize       = ed25519.SeedSize       // 32, the private half's canonical storage form
	SigningPublicKeySize  = ed25519.PublicKeySize  // 32
	SigningPrivateKeySize = ed25519.PrivateKeySize // 64, seed ‖ public key
	SignatureSize         = ed25519.SignatureSize  // 64
)

Ed25519 signing keys live in this package so that every component that has to read, write, or check one agrees on a single set of rules. The alternative — each caller reaching for crypto/ed25519 and encoding/base64 on its own — is how two ends of the same protocol end up with two encodings, and a key that round-trips locally but fails every connection in the field.

Sizes are re-exported so callers can validate lengths without importing crypto/ed25519 themselves.

View Source
const (
	ErrBadPublicKey  = SigningKeyError("not an ed25519 public key")
	ErrBadPrivateKey = SigningKeyError("not an ed25519 private key")
	ErrBadSeed       = SigningKeyError("not an ed25519 seed")
	ErrKeyGeneration = SigningKeyError("error generating signing key")
)

Signing-key failures. They are all "this is not usable key material", which a caller treats as fatal: a malformed key does not become well-formed on retry.

Variables

View Source
var SigningKeyEncoding = base64.StdEncoding

SigningKeyEncoding is the one encoding this package writes.

Standard base64 with padding, because that is what encoding/json already produces for a []byte field: a service that marshals its key into an enrolment payload and a tool that prints one agree without a second convention, and no call site has to guess. Note that this deliberately differs from GenerateKey, which hex-encodes symmetric key material; hex doubles the length, and these keys travel in JSON documents and command lines where the shorter form is what people already see.

Security Considerations:

  • The encoding is not a security property. ParsePublicKey and ParseSigningSeed therefore read the unpadded and URL-safe base64 variants and lowercase hex as well, since a key is text a human may have moved between machines. Widening what is *read* is safe in a way that widening what is *accepted as valid* is not: the signature check is unchanged by how the key was spelled, and every accepted spelling must still decode to exactly the right number of bytes.

Functions

func CheckHash

func CheckHash(signingKey string, salt string, shouldBe string, hash []byte) (bool, error)

CheckHash verifies the shouldBe string against the hash. params: - signingKey (string) - the signing key for the hash - salt (string) - the salt used for the hash - password (string) - the password to verify - hash ([]byte) - the stored hash to compare against returns: (bool, error) - whether the password matches the hash and an error if any

Note: Think twice, maybe bycrypt is what you need.

func Equal

func Equal(sealedHash1, sealedHash2 []byte) bool

Equal compares two JSON-encoded sealed hashes in constant time. It unmarshals each sealed hash into a SealedHash struct and then compares both the Hash and Salt fields using hmac.Equal.

Usage:

ok := Equal(sealedHash1, sealedHash2)
if !ok {
    // the sealed hashes do not match
}

Returns true when the two sealed hashes are byte-identical. Since a sealed hash is the JSON encoding of both the digest and its salt, comparing the encoded forms compares both components — no unmarshalling is needed, and not unmarshalling is what keeps the comparison constant-time end to end.

func EqualSigningKey added in v0.38.0

func EqualSigningKey(a, b SigningPrivateKey) bool

EqualSigningKey compares two private keys in constant time.

Usage:

if !EqualSigningKey(configured, loaded) {
    // the process was handed two different identities
}

hmac.Equal, not bytes.Equal, for the same reason Equal uses it: the inputs are secret, so the comparison must not leak where they first differ. Keys of differing length simply compare unequal.

func FormatPublicKey added in v0.38.0

func FormatPublicKey(pub SigningPublicKey) string

FormatPublicKey renders a public key for storage or for handing to a peer, in SigningKeyEncoding. It returns the empty string for a key of the wrong length rather than emitting text that would never parse back.

func FormatSigningSeed added in v0.38.0

func FormatSigningSeed(priv SigningPrivateKey) (string, error)

FormatSigningSeed renders the private half of a keypair as its 32-byte seed in SigningKeyEncoding. The seed is the storage form: the full 64-byte private key is the seed with the public key appended, so persisting the seed loses nothing and halves what has to be kept secret.

Security Considerations:

  • The returned string is secret material. Do not log it, and prefer passing it through a channel that does not end up in shell history or a crash dump.

func GenerateKey

func GenerateKey(keyLength int) (string, error)

GenerateKey generates a cryptographically random key with the specified length.

func GenerateSigningKey added in v0.38.0

func GenerateSigningKey() (SigningPublicKey, SigningPrivateKey, error)

GenerateSigningKey returns a fresh Ed25519 keypair from the system CSPRNG.

When to use: Use it when a component needs an identity to sign with — a relay proving itself to the instances paired with it, for example. The public half is meant to be published with FormatPublicKey; the private half is a secret and must be stored with FormatSigningSeed somewhere only that component can read.

Security Considerations:

  • The keypair is long-lived by intent, so where the seed is written matters more than how it was generated. This package deliberately does not read or write files or environment variables; that is a deployment concern.

func NewHash

func NewHash(args GenerateHashArgs, hashfn func() hash.Hash) ([]byte, error)

NewHash generates a sealed hash from the provided arguments and hash function. It computes an HMAC digest over the concatenation of the input data and a unique salt, and packages the result along with the salt in a JSON-encoded SealedHash object.

Usage:

sealed, err := NewHash(GenerateHashArgs{
    Hash:       data,
    SigningKey: key,
}, sha256.New)
if err != nil {
    // handle error
}

When to use: Use NewHash when you need to securely generate a hash that verifies the integrity and authenticity of data. A unique salt is automatically added so that identical inputs produce distinct outputs, and the signing key is applied during the HMAC computation. The signing key is not required during verification since it is already embedded in the computed HMAC digest.

func ParseSigningSeed added in v0.38.0

func ParseSigningSeed(s string) (SigningPublicKey, SigningPrivateKey, error)

ParseSigningSeed reconstructs a keypair from a seed produced by FormatSigningSeed, accepting the same spellings ParsePublicKey does.

When to use: Use it at start-up, on whatever a deployment hands the process as its signing identity. It is the counterpart of FormatSigningSeed and the only supported way to turn stored text back into a key — parsing a seed at the call site is exactly the duplication this package exists to remove.

The seed determines the public key, so a caller that also received a public key out of band should compare the two and refuse to start on a mismatch: signing with a key nobody pinned produces signatures nobody can verify.

func Sign added in v0.38.0

func Sign(priv SigningPrivateKey, message []byte) ([]byte, error)

Sign returns a detached signature over message. The message is signed as it is given: any domain separation or framing is the caller's protocol and belongs in the caller, not here.

It returns an error wrapping ErrBadPrivateKey rather than panicking on a key of the wrong length, which is what crypto/ed25519 would do — a key usually arrives from configuration, and bad configuration should surface as an error an operator can read.

func Verify added in v0.38.0

func Verify(pub SigningPublicKey, message, sig []byte) bool

Verify reports whether sig is a valid signature of message by pub. It is total: a key, message, or signature of any length or contents yields false rather than a panic, because everything it is handed came off a wire or out of a config file and none of it is trusted.

Security Considerations:

  • A false result must be treated as an authentication failure, never as a transient error to retry past.

Types

type CipherTextError

type CipherTextError string

func (CipherTextError) Error

func (e CipherTextError) Error() string

type Decryptor

type Decryptor interface {
	// Encrypts/Decrypts a message, misuse may lead to a panic.
	Crypt(cipherpackage []byte) ([]byte, []byte, error)
}

provides a method to crypt a cipher package. Misuse of this method may lead to a panic.

func NewCBCHMACDecryptor

func NewCBCHMACDecryptor(encryptionKey []byte, integrityKey []byte, calculateMAC func() hash.Hash) (Decryptor, error)

Configure & init the AES-CBC+HMAC cryptor in decryption mode.

func NewGCMDecryptor

func NewGCMDecryptor(encryptionKey []byte) (Decryptor, error)

NewGCMDecryptor creates a new Decryptor using AES-GCM with the given key.

type EncryptionKeyError

type EncryptionKeyError string

func (EncryptionKeyError) Error

func (e EncryptionKeyError) Error() string

type Encryptor

type Encryptor interface {
	// Encrypts/Decrypts a message, misuse may lead to a panic.
	Crypt(message []byte, additionalData []byte) ([]byte, error)
}

provides a method to crypt a message with additional data. Misuse of this method may lead to a panic.

func NewCBCHMACEncryptor

func NewCBCHMACEncryptor(encryptionKey []byte, integrityKey []byte, calculateMAC func() hash.Hash, rand io.Reader) (Encryptor, error)

Configure & init the AES-CBC+HMAC cryptor in encryption mode. AES-CBC with PKCS7 padding HMAC for integrity.

The final encrypted string format:
[ MAC | AD-Length | AD | Initialization Vector | Block 1 | Block 2 | ... ]
uses rand from the arguments for introducing randomness.

Don't use this for big messages, the whole cypher has to be in mem for computing the Hmac.

The encryption key and integrity key must be distinct. Both keys have to be kept secret. Rotating must be done to both keys simultaneously.

Compromised Encryption Key:

An attacker, possessing the encryption key, could decrypt sensitive data.
If you rotate only the integrity key, they still have access to the previously encrypted data.

Compromised Integrity Key:

An attacker with the integrity key could potentially modify encrypted data,
forge HMACs, and tamper with the system without detection.
Even if you rotate the encryption key, the integrity of past data is compromised.

the MAC is calculated from ( AD-Length | AD | Initialization Vector | Block 1 | Block 2 | ... )

GCM Comparison:

	Use CBC with HMAC over GCM (or any stream cipher) when avoiding nonce collisions can be challenging is a problem.
	This is the case if you deal with:
	- high-volume systems (the probability of nonce collisions increases, especially if the nonce space is limited).
	- distributed environments (coordinating nonce generation across nodes and ensuring uniqueness becomes even more complex).
	- or scenarios where encrypted data needs to be stored persistently. For example, if encrypted data is stored in a database.
      and later retrieved and re-encrypted, ensuring that a new, unique nonce is used each time can be challenging.

Since this is a one-person project, ensure you review the code before using it to validate its security and correctness.

func NewGCMEncryptor

func NewGCMEncryptor(encryptionKey []byte, rand io.Reader) (Encryptor, error)

NewGCMEncryptor creates a new Encryptor using AES-GCM with the given key.

type GenerateHashArgs

type GenerateHashArgs struct {
	Payload    []byte
	SigningKey []byte
	Salt       []byte
}

GenerateHashArgs contains the input parameters for generating a sealed hash. The Payload field is the data to be hashed, and SigningKey is the key used to compute the HMAC digest. The SigningKey should be kept secret.

type HashError

type HashError string

HashError represents an error during hash generation.

func (HashError) Error

func (e HashError) Error() string

type IntegrityKeyError

type IntegrityKeyError string

func (IntegrityKeyError) Error

func (e IntegrityKeyError) Error() string

type InvalidUsageError

type InvalidUsageError string

func (InvalidUsageError) Error

func (e InvalidUsageError) Error() string

type KeyGenerationError

type KeyGenerationError string

func (KeyGenerationError) Error

func (e KeyGenerationError) Error() string

type MessageError

type MessageError string

func (MessageError) Error

func (e MessageError) Error() string

type SigningKeyError added in v0.38.0

type SigningKeyError string

SigningKeyError represents an error while generating, parsing, or using an Ed25519 key. Every value is a fixed sentinel wrapped with detail, so callers can match with errors.Is and still print something an operator can act on.

func (SigningKeyError) Error added in v0.38.0

func (e SigningKeyError) Error() string

type SigningPrivateKey added in v0.38.0

type SigningPrivateKey = ed25519.PrivateKey

SigningPublicKey and SigningPrivateKey are aliases, not new types, so a value obtained from crypto/ed25519 elsewhere stays assignable here and callers are never forced to convert. The alias exists to spell the intent — these are signing keys, distinct from the symmetric material GenerateKey produces — not to wrap the standard library.

type SigningPublicKey added in v0.38.0

type SigningPublicKey = ed25519.PublicKey

SigningPublicKey and SigningPrivateKey are aliases, not new types, so a value obtained from crypto/ed25519 elsewhere stays assignable here and callers are never forced to convert. The alias exists to spell the intent — these are signing keys, distinct from the symmetric material GenerateKey produces — not to wrap the standard library.

func ParsePublicKey added in v0.38.0

func ParsePublicKey(s string) (SigningPublicKey, error)

ParsePublicKey reads a key produced by FormatPublicKey. It also accepts the unpadded and URL-safe base64 variants and lowercase hex — see SigningKeyEncoding for why reading is lenient where writing is not. The result is always exactly SigningPublicKeySize bytes or an error wrapping ErrBadPublicKey; it never panics, whatever the input.

Jump to

Keyboard shortcuts

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