cryptohelpers

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Nov 17, 2025 License: MIT Imports: 6 Imported by: 0

README

cryptohelpers

Cryptographic utilities for Ed25519 signing and Curve25519 encryption in Go

CI Go Reference Go Report Card Go Version

Overview

cryptohelpers is a lightweight, focused library providing essential cryptographic operations for Go applications. It offers:

  • Ed25519 digital signatures for authentication and integrity verification
  • Curve25519 encryption (NaCl box) for secure data transmission
  • Key management utilities for loading base64-encoded keys from files
  • Bundle operations for combining payloads with signatures

The library is designed for developers who need reliable, easy-to-use cryptographic primitives without the complexity of managing low-level crypto APIs directly.

Target audience: Backend developers, security engineers, and application architects building secure systems that require message signing, verification, and encryption.

Features

  • Ed25519 signing and verification with deterministic signatures
  • NaCl box encryption/decryption (Curve25519 + XSalsa20-Poly1305)
  • Key loading utilities for Ed25519 and Curve25519 keys from base64-encoded files
  • Bundle operations to combine and split payload+signature bundles
  • Zero dependencies beyond Go standard library and golang.org/x/crypto
  • Well-tested with comprehensive test coverage
  • Simple API designed for ease of use and safety

Requirements

  • Go 1.25+ (tested on 1.25.x)

Installation

Add the library to your Go module:

go get github.com/go-extras/cryptohelpers@latest

Quick Start

Signing and Verifying Messages
package main

import (
    "crypto/ed25519"
    "crypto/rand"
    "fmt"
    
    "github.com/go-extras/cryptohelpers"
)

func main() {
    // Generate a key pair
    pub, priv, _ := ed25519.GenerateKey(rand.Reader)
    
    // Sign a message
    message := []byte("Hello, World!")
    signature := cryptohelpers.SignPayload(priv, message)
    
    // Verify the signature
    valid := cryptohelpers.VerifyPayload(pub, message, signature)
    fmt.Printf("Signature valid: %v\n", valid) // Output: Signature valid: true
}
Encrypting and Decrypting Data
package main

import (
    "crypto/rand"
    "fmt"
    
    "github.com/go-extras/cryptohelpers"
    "golang.org/x/crypto/nacl/box"
)

func main() {
    // Generate recipient key pair
    recipientPub, recipientPriv, _ := box.GenerateKey(rand.Reader)
    
    // Encrypt data
    data := []byte("Secret message")
    encrypted := cryptohelpers.EncryptBundle(data, recipientPub)
    
    // Decrypt data
    decrypted, err := cryptohelpers.DecryptBundle(encrypted, recipientPriv)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Decrypted: %s\n", decrypted) // Output: Decrypted: Secret message
}
Loading Keys from Files
package main

import (
    "github.com/go-extras/cryptohelpers"
)

func main() {
    // Load Ed25519 keys
    privKey := cryptohelpers.LoadEd25519Private("path/to/ed25519_private.key")
    pubKey := cryptohelpers.LoadEd25519Public("path/to/ed25519_public.key")
    
    // Load Curve25519 keys
    encPrivKey := cryptohelpers.LoadCurve25519Private("path/to/curve25519_private.key")
    encPubKey := cryptohelpers.LoadCurve25519Public("path/to/curve25519_public.key")
    
    // Use the keys...
}
Working with Bundles
package main

import (
    "crypto/ed25519"
    "crypto/rand"
    "fmt"
    
    "github.com/go-extras/cryptohelpers"
)

func main() {
    pub, priv, _ := ed25519.GenerateKey(rand.Reader)
    
    // Create a signed bundle
    payload := []byte("Important data")
    signature := cryptohelpers.SignPayload(priv, payload)
    bundle := append(payload, signature...)
    
    // Split the bundle
    extractedPayload, extractedSignature, err := cryptohelpers.SplitBundle(bundle)
    if err != nil {
        panic(err)
    }
    
    // Verify the extracted signature
    valid := cryptohelpers.VerifyPayload(pub, extractedPayload, extractedSignature)
    fmt.Printf("Bundle signature valid: %v\n", valid) // Output: Bundle signature valid: true
}

API Documentation

Full API documentation is available at pkg.go.dev/github.com/go-extras/cryptohelpers.

Core Functions
Signing Operations
  • SignPayload(priv ed25519.PrivateKey, payload []byte) []byte
    Creates an Ed25519 signature (64 bytes) for the provided payload.

  • VerifyPayload(pub ed25519.PublicKey, payload, signature []byte) bool
    Verifies an Ed25519 signature. Returns true if valid, false otherwise.

  • SplitBundle(bundle []byte) (payload, signature []byte, err error)
    Separates a payload+signature bundle into its components.

Encryption Operations
  • EncryptBundle(bundle []byte, recipientPub *[32]byte) []byte
    Encrypts data using NaCl box (Curve25519 + XSalsa20-Poly1305).
    Output format: [24 bytes nonce] | [32 bytes ephemeralPub] | [ciphertext]

  • DecryptBundle(blob []byte, recipientPriv *[32]byte) ([]byte, error)
    Decrypts a blob produced by EncryptBundle.

Key Loading
  • LoadEd25519Private(path string) ed25519.PrivateKey
    Loads an Ed25519 private key from a base64-encoded file. Panics on failure.

  • LoadEd25519Public(path string) ed25519.PublicKey
    Loads an Ed25519 public key from a base64-encoded file. Panics on failure.

  • LoadCurve25519Private(path string) *[32]byte
    Loads a Curve25519 private key from a base64-encoded file. Panics on failure.

  • LoadCurve25519Public(path string) *[32]byte
    Loads a Curve25519 public key from a base64-encoded file. Panics on failure.

Use Cases

  • Secure configuration management: Sign and encrypt configuration files
  • API authentication: Sign requests with Ed25519 for tamper-proof authentication
  • Encrypted data storage: Encrypt sensitive data before storing
  • Secure messaging: Sign and encrypt messages between services
  • Key distribution: Load and manage cryptographic keys from files

Security Considerations

  • Ed25519 signatures are deterministic: The same message and key always produce the same signature
  • Key storage: Store private keys securely with appropriate file permissions (e.g., 0600)
  • Ephemeral keys: EncryptBundle generates a new ephemeral key pair for each encryption
  • No key derivation: This library does not provide password-based key derivation; use appropriate KDF functions if needed
  • Panic on key loading errors: Key loading functions panic on failure; handle appropriately in production code

Testing

Run the test suite:

# Run all tests
go test ./...

# Run tests with race detection
go test -race ./...

# Run tests with coverage
go test -cover ./...

Contributing

Contributions are welcome! Please:

  • Open issues for bugs, feature requests, or questions
  • Submit pull requests with clear descriptions and tests
  • Follow the existing code style and conventions
  • Ensure all tests pass and maintain test coverage

License

MIT © 2025 Denis Voytyuk — see LICENSE for details.

Acknowledgments

This library builds upon:

  • Go's standard crypto/ed25519 package
  • The golang.org/x/crypto/nacl/box package for NaCl box encryption

Documentation

Overview

Package cryptohelpers provides utilities for loading and handling cryptographic keys used for Ed25519 signing and Curve25519 encryption in the ppatcher system.

Example

Example demonstrates a complete workflow: sign, encrypt, decrypt, and verify.

package main

import (
	"crypto/ed25519"
	"crypto/rand"
	"fmt"

	"golang.org/x/crypto/nacl/box"

	"github.com/go-extras/cryptohelpers"
)

func main() {
	// Generate Ed25519 keys for signing
	signPub, signPriv, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		panic(err)
	}

	// Generate Curve25519 keys for encryption
	encPub, encPriv, err := box.GenerateKey(rand.Reader)
	if err != nil {
		panic(err)
	}

	// Original message
	message := []byte("Secure message")

	// Step 1: Sign the message
	signature := cryptohelpers.SignPayload(signPriv, message)
	fmt.Printf("1. Message signed (%d bytes)\n", len(signature))

	// Step 2: Create a bundle (message + signature)
	bundle := append(message, signature...)
	fmt.Printf("2. Bundle created (%d bytes)\n", len(bundle))

	// Step 3: Encrypt the bundle
	encrypted := cryptohelpers.EncryptBundle(bundle, encPub)
	fmt.Printf("3. Bundle encrypted (%d bytes)\n", len(encrypted))

	// Step 4: Decrypt the bundle
	decrypted, err := cryptohelpers.DecryptBundle(encrypted, encPriv)
	if err != nil {
		panic(err)
	}
	fmt.Printf("4. Bundle decrypted (%d bytes)\n", len(decrypted))

	// Step 5: Split the bundle
	extractedMessage, extractedSignature, err := cryptohelpers.SplitBundle(decrypted)
	if err != nil {
		panic(err)
	}
	fmt.Printf("5. Bundle split (message: %d bytes, signature: %d bytes)\n",
		len(extractedMessage), len(extractedSignature))

	// Step 6: Verify the signature
	valid := cryptohelpers.VerifyPayload(signPub, extractedMessage, extractedSignature)
	fmt.Printf("6. Signature verified: %v\n", valid)
	fmt.Printf("7. Message: %s\n", extractedMessage)

}
Output:

1. Message signed (64 bytes)
2. Bundle created (78 bytes)
3. Bundle encrypted (150 bytes)
4. Bundle decrypted (78 bytes)
5. Bundle split (message: 14 bytes, signature: 64 bytes)
6. Signature verified: true
7. Message: Secure message

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func DecryptBundle

func DecryptBundle(blob []byte, recipientPriv *[32]byte) ([]byte, error)

DecryptBundle decrypts a blob produced by EncryptBundle.

The blob must contain:

  • 24 bytes: nonce
  • 32 bytes: ephemeral sender public key
  • remaining bytes: ciphertext

The function returns the decrypted plaintext or an error on failure.

Example

ExampleDecryptBundle demonstrates how to decrypt data encrypted with EncryptBundle.

package main

import (
	"crypto/rand"
	"fmt"

	"golang.org/x/crypto/nacl/box"

	"github.com/go-extras/cryptohelpers"
)

func main() {
	// Generate recipient key pair
	recipientPub, recipientPriv, err := box.GenerateKey(rand.Reader)
	if err != nil {
		panic(err)
	}

	// Encrypt some data
	originalData := []byte("Confidential information")
	encrypted := cryptohelpers.EncryptBundle(originalData, recipientPub)

	// Decrypt the data
	decrypted, err := cryptohelpers.DecryptBundle(encrypted, recipientPriv)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Decrypted: %s\n", decrypted)
	fmt.Printf("Match: %v\n", string(decrypted) == string(originalData))

}
Output:

Decrypted: Confidential information
Match: true

func EncryptBundle

func EncryptBundle(bundle []byte, recipientPub *[32]byte) []byte

EncryptBundle encrypts a signed bundle using NaCl box (Curve25519 + XSalsa20-Poly1305).

The function automatically generates a new ephemeral key pair and a 24-byte nonce. The output format is:

[24 bytes nonce] | [32 bytes ephemeralPub] | [ciphertext]

The function returns the encrypted blob or panics on failure.

Example

ExampleEncryptBundle demonstrates how to encrypt data using NaCl box.

package main

import (
	"crypto/rand"
	"fmt"

	"golang.org/x/crypto/nacl/box"

	"github.com/go-extras/cryptohelpers"
)

func main() {
	// Generate recipient key pair
	recipientPub, recipientPriv, err := box.GenerateKey(rand.Reader)
	if err != nil {
		panic(err)
	}

	// Encrypt data
	data := []byte("Secret message")
	encrypted := cryptohelpers.EncryptBundle(data, recipientPub)

	fmt.Printf("Original data: %s\n", data)
	fmt.Printf("Encrypted size: %d bytes\n", len(encrypted))

	// Decrypt data
	decrypted, err := cryptohelpers.DecryptBundle(encrypted, recipientPriv)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Decrypted data: %s\n", decrypted)

}
Output:

Original data: Secret message
Encrypted size: 86 bytes
Decrypted data: Secret message

func LoadCurve25519Private

func LoadCurve25519Private(path string) *[32]byte

LoadCurve25519Private loads a Curve25519 (X25519) private key from a base64-encoded file.

The resulting key must be exactly 32 bytes long. The function returns a pointer to an array suitable for use with nacl/box operations, or panics if the key is invalid or cannot be loaded.

Example

ExampleLoadCurve25519Private demonstrates how to load a Curve25519 private key from a file.

package main

import (
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"os"
	"path/filepath"

	"golang.org/x/crypto/nacl/box"

	"github.com/go-extras/cryptohelpers"
)

func main() {
	// Generate a key pair
	_, priv, err := box.GenerateKey(rand.Reader)
	if err != nil {
		panic(err)
	}

	// Create a temporary file
	tempDir, err := os.MkdirTemp("", "cryptohelpers-example")
	if err != nil {
		panic(err)
	}
	defer os.RemoveAll(tempDir)

	keyPath := filepath.Join(tempDir, "curve25519_private.key")

	// Save the key in base64 format
	encoded := base64.StdEncoding.EncodeToString(priv[:])
	err = os.WriteFile(keyPath, []byte(encoded), 0600)
	if err != nil {
		panic(err)
	}

	// Load the key
	loadedKey := cryptohelpers.LoadCurve25519Private(keyPath)

	fmt.Printf("Curve25519 private key loaded successfully\n")
	fmt.Printf("Key length: %d bytes\n", len(loadedKey))

}
Output:

Curve25519 private key loaded successfully
Key length: 32 bytes

func LoadCurve25519Public

func LoadCurve25519Public(path string) *[32]byte

LoadCurve25519Public loads a Curve25519 (X25519) public key from a base64-encoded file.

The resulting key must be exactly 32 bytes long. The function returns a pointer to an array suitable for nacl/box encryption, or panics if the key is invalid or cannot be loaded.

Example

ExampleLoadCurve25519Public demonstrates how to load a Curve25519 public key from a file.

package main

import (
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"os"
	"path/filepath"

	"golang.org/x/crypto/nacl/box"

	"github.com/go-extras/cryptohelpers"
)

func main() {
	// Generate a key pair
	pub, _, err := box.GenerateKey(rand.Reader)
	if err != nil {
		panic(err)
	}

	// Create a temporary file
	tempDir, err := os.MkdirTemp("", "cryptohelpers-example")
	if err != nil {
		panic(err)
	}
	defer os.RemoveAll(tempDir)

	keyPath := filepath.Join(tempDir, "curve25519_public.key")

	// Save the key in base64 format
	encoded := base64.StdEncoding.EncodeToString(pub[:])
	err = os.WriteFile(keyPath, []byte(encoded), 0600)
	if err != nil {
		panic(err)
	}

	// Load the key
	loadedKey := cryptohelpers.LoadCurve25519Public(keyPath)

	fmt.Printf("Curve25519 public key loaded successfully\n")
	fmt.Printf("Key length: %d bytes\n", len(loadedKey))

}
Output:

Curve25519 public key loaded successfully
Key length: 32 bytes

func LoadEd25519Private

func LoadEd25519Private(path string) ed25519.PrivateKey

LoadEd25519Private loads an Ed25519 private key from a base64-encoded file.

The file must contain exactly one base64 string representing a 64-byte Ed25519 private key. The function returns an ed25519.PrivateKey or panics on any failure.

Example

ExampleLoadEd25519Private demonstrates how to load an Ed25519 private key from a file.

package main

import (
	"crypto/ed25519"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"os"
	"path/filepath"

	"github.com/go-extras/cryptohelpers"
)

func main() {
	// Generate a key pair
	_, priv, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		panic(err)
	}

	// Create a temporary file
	tempDir, err := os.MkdirTemp("", "cryptohelpers-example")
	if err != nil {
		panic(err)
	}
	defer os.RemoveAll(tempDir)

	keyPath := filepath.Join(tempDir, "ed25519_private.key")

	// Save the key in base64 format
	encoded := base64.StdEncoding.EncodeToString(priv)
	err = os.WriteFile(keyPath, []byte(encoded), 0600)
	if err != nil {
		panic(err)
	}

	// Load the key
	loadedKey := cryptohelpers.LoadEd25519Private(keyPath)

	fmt.Printf("Key loaded successfully\n")
	fmt.Printf("Key length: %d bytes\n", len(loadedKey))

}
Output:

Key loaded successfully
Key length: 64 bytes

func LoadEd25519Public

func LoadEd25519Public(path string) ed25519.PublicKey

LoadEd25519Public loads an Ed25519 public key from a base64-encoded file.

The file must contain exactly one base64 string representing a 32-byte Ed25519 public key. The function returns an ed25519.PublicKey or panics on any failure.

Example

ExampleLoadEd25519Public demonstrates how to load an Ed25519 public key from a file.

package main

import (
	"crypto/ed25519"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"os"
	"path/filepath"

	"github.com/go-extras/cryptohelpers"
)

func main() {
	// Generate a key pair
	pub, _, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		panic(err)
	}

	// Create a temporary file
	tempDir, err := os.MkdirTemp("", "cryptohelpers-example")
	if err != nil {
		panic(err)
	}
	defer os.RemoveAll(tempDir)

	keyPath := filepath.Join(tempDir, "ed25519_public.key")

	// Save the key in base64 format
	encoded := base64.StdEncoding.EncodeToString(pub)
	err = os.WriteFile(keyPath, []byte(encoded), 0600)
	if err != nil {
		panic(err)
	}

	// Load the key
	loadedKey := cryptohelpers.LoadEd25519Public(keyPath)

	fmt.Printf("Public key loaded successfully\n")
	fmt.Printf("Key length: %d bytes\n", len(loadedKey))

}
Output:

Public key loaded successfully
Key length: 32 bytes

func SignPayload

func SignPayload(priv ed25519.PrivateKey, payload []byte) []byte

SignPayload creates an Ed25519 signature for the provided payload.

The function returns a 64-byte signature. It panics if the private key is invalid. No hashing is required because Ed25519 operates on the message directly.

Example

ExampleSignPayload demonstrates how to sign a message using Ed25519.

package main

import (
	"crypto/ed25519"
	"crypto/rand"
	"fmt"

	"github.com/go-extras/cryptohelpers"
)

func main() {
	// Generate a key pair
	pub, priv, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		panic(err)
	}

	// Sign a message
	message := []byte("Hello, World!")
	signature := cryptohelpers.SignPayload(priv, message)

	// Verify the signature
	valid := cryptohelpers.VerifyPayload(pub, message, signature)
	fmt.Printf("Signature valid: %v\n", valid)
	fmt.Printf("Signature length: %d bytes\n", len(signature))

}
Output:

Signature valid: true
Signature length: 64 bytes

func SplitBundle

func SplitBundle(bundle []byte) (payload, signature []byte, err error)

SplitBundle separates a payload+signature bundle into its components.

The bundle must contain at least one Ed25519 signature (64 bytes) appended to the payload. The function returns payload, signature, or an error if the bundle is malformed.

Example

ExampleSplitBundle demonstrates how to split a payload+signature bundle.

package main

import (
	"crypto/ed25519"
	"crypto/rand"
	"fmt"

	"github.com/go-extras/cryptohelpers"
)

func main() {
	// Generate a key pair
	pub, priv, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		panic(err)
	}

	// Create a signed bundle
	payload := []byte("Important data")
	signature := cryptohelpers.SignPayload(priv, payload)
	bundle := append(payload, signature...)

	fmt.Printf("Bundle size: %d bytes\n", len(bundle))

	// Split the bundle
	extractedPayload, extractedSignature, err := cryptohelpers.SplitBundle(bundle)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Payload: %s\n", extractedPayload)
	fmt.Printf("Signature length: %d bytes\n", len(extractedSignature))

	// Verify the extracted signature
	valid := cryptohelpers.VerifyPayload(pub, extractedPayload, extractedSignature)
	fmt.Printf("Signature valid: %v\n", valid)

}
Output:

Bundle size: 78 bytes
Payload: Important data
Signature length: 64 bytes
Signature valid: true

func VerifyPayload

func VerifyPayload(pub ed25519.PublicKey, payload, signature []byte) bool

VerifyPayload checks whether the given Ed25519 signature is valid for a payload.

It returns true for a valid signature and false otherwise. The function does not panic. This method is intended for validating the authenticity of decrypted configuration data.

Example

ExampleVerifyPayload demonstrates how to verify an Ed25519 signature.

package main

import (
	"crypto/ed25519"
	"crypto/rand"
	"fmt"

	"github.com/go-extras/cryptohelpers"
)

func main() {
	// Generate a key pair
	pub, priv, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		panic(err)
	}

	message := []byte("Important message")
	signature := cryptohelpers.SignPayload(priv, message)

	// Verify with correct data
	valid := cryptohelpers.VerifyPayload(pub, message, signature)
	fmt.Printf("Valid signature: %v\n", valid)

	// Verify with tampered data
	tamperedMessage := []byte("Tampered message")
	valid = cryptohelpers.VerifyPayload(pub, tamperedMessage, signature)
	fmt.Printf("Tampered message valid: %v\n", valid)

}
Output:

Valid signature: true
Tampered message valid: false

Types

This section is empty.

Jump to

Keyboard shortcuts

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