qasago

package module
v1.2.0 Latest Latest
Warning

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

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

README

QasaGo

A secure Go cryptography utility library providing AES-256-GCM encryption with authenticated encryption and associated data (AEAD).

Features

  • AES-256-GCM Encryption: Industry-standard authenticated encryption
  • Secure Key Generation: Cryptographically secure key generation utilities
  • Base64 Encoding: Safe text storage and transmission of encrypted data
  • Comprehensive Error Handling: Clear error messages for debugging
  • Zero Dependencies: Uses only Go standard library
  • Production Ready: Complete implementation with security best practices

Requirements

  • Go 1.25.3 or higher

Installation

go get github.com/djwarf/qasago

Quick Start

package main

import (
    "fmt"
    "log"

    "github.com/djwarf/qasago"
)

func main() {
    // Generate a secure encryption key (do this once and store securely)
    key, err := qasago.GenerateEncryptionKey()
    if err != nil {
        log.Fatal(err)
    }

    // Encrypt data
    plaintext := "Secret message to encrypt"
    encrypted, err := qasago.Encrypt(plaintext, key)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Encrypted: %s\n", encrypted)

    // Decrypt data
    decrypted, err := qasago.Decrypt(encrypted, key)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Decrypted: %s\n", decrypted)
}

API Documentation

Types
EncryptionKey

A type-safe representation of a 256-bit AES encryption key with helper methods.

type EncryptionKey []byte

Methods:

  • Validate() error - Validates the key for AES-256 encryption
  • String() string - Returns base64-encoded representation
  • Bytes() []byte - Returns raw byte representation
CipherText

Represents encrypted data in base64 format.

type CipherText string
PlainText

Represents unencrypted text data.

type PlainText string
Config

Encryption configuration for object-oriented usage.

type Config struct {
    Key EncryptionKey
}

Methods:

  • NewConfig(key []byte) (*Config, error) - Creates new configuration
  • Encrypt(plaintext string) (CipherText, error) - Encrypts using configured key
  • Decrypt(ciphertext CipherText) (PlainText, error) - Decrypts using configured key
Encryption Functions
Encrypt(plaintext string, key []byte) (string, error)

Encrypts plaintext using AES-256-GCM with a random nonce.

  • Parameters:
    • plaintext: The data to encrypt (cannot be empty)
    • key: Exactly 32 bytes for AES-256 encryption
  • Returns:
    • Base64-encoded ciphertext with prepended nonce
    • Error if encryption fails
Decrypt(encryptedData string, key []byte) (string, error)

Decrypts a base64-encoded AES-256-GCM ciphertext.

  • Parameters:
    • encryptedData: Base64-encoded nonce + ciphertext + auth tag
    • key: The same 32-byte key used for encryption
  • Returns:
    • Decrypted plaintext
    • Error if decryption or authentication fails
Key Management Functions
GenerateEncryptionKey() ([]byte, error)

Creates a cryptographically secure 256-bit key.

  • Returns:
    • 32-byte encryption key
    • Error if generation fails
ValidateEncryptionKey(key []byte) error

Validates if a key is suitable for AES-256 encryption.

  • Parameters:
    • key: The encryption key to validate
  • Returns:
    • Error if key is invalid, nil otherwise
EncodeKey(key []byte) string

Converts a binary key to base64 for configuration storage.

  • Parameters:
    • key: Binary encryption key
  • Returns:
    • Base64-encoded key string
DecodeKey(encoded string) ([]byte, error)

Converts a base64-encoded key back to binary.

  • Parameters:
    • encoded: Base64-encoded key string
  • Returns:
    • Binary encryption key
    • Error if decoding fails

Usage Examples

Using the Config Type (Object-Oriented Approach)
package main

import (
    "fmt"
    "log"

    "github.com/djwarf/qasago"
)

func main() {
    // Generate a key
    key, _ := qasago.GenerateEncryptionKey()

    // Create a config with the key
    config, err := qasago.NewConfig(key)
    if err != nil {
        log.Fatal(err)
    }

    // Encrypt data
    ciphertext, err := config.Encrypt("Secret message")
    if err != nil {
        log.Fatal(err)
    }

    // Decrypt data
    plaintext, err := config.Decrypt(ciphertext)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Decrypted: %s\n", plaintext)
}
Environment Variable Storage
package main

import (
    "os"
    "log"

    "github.com/djwarf/qasago"
)

func main() {
    // Generate and encode a key for storage
    key, _ := qasago.GenerateEncryptionKey()
    encodedKey := qasago.EncodeKey(key)

    // Store in environment variable
    os.Setenv("ENCRYPTION_KEY", encodedKey)

    // Later, retrieve and decode the key
    encodedKey = os.Getenv("ENCRYPTION_KEY")
    key, err := qasago.DecodeKey(encodedKey)
    if err != nil {
        log.Fatal("Invalid encryption key:", err)
    }

    // Use the key for encryption/decryption
    encrypted, _ := qasago.Encrypt("sensitive data", key)
    decrypted, _ := qasago.Decrypt(encrypted, key)
}
Database Field Encryption
package main

import (
    "database/sql"
    "log"

    "github.com/djwarf/qasago"
)

type User struct {
    ID    int
    Email string
    // Store encrypted sensitive data
    EncryptedSSN string
}

func SaveUser(db *sql.DB, user User, encryptionKey []byte) error {
    // Encrypt sensitive data before storing
    encryptedSSN, err := qasago.Encrypt(user.EncryptedSSN, encryptionKey)
    if err != nil {
        return err
    }

    _, err = db.Exec(
        "INSERT INTO users (email, ssn_encrypted) VALUES (?, ?)",
        user.Email, encryptedSSN,
    )
    return err
}

func GetUser(db *sql.DB, id int, encryptionKey []byte) (*User, error) {
    var user User
    var encryptedSSN string

    err := db.QueryRow(
        "SELECT id, email, ssn_encrypted FROM users WHERE id = ?",
        id,
    ).Scan(&user.ID, &user.Email, &encryptedSSN)

    if err != nil {
        return nil, err
    }

    // Decrypt sensitive data after retrieval
    ssn, err := qasago.Decrypt(encryptedSSN, encryptionKey)
    if err != nil {
        return nil, err
    }
    user.EncryptedSSN = ssn

    return &user, nil
}
Error Handling
package main

import (
    "errors"
    "fmt"
    "log"

    "github.com/djwarf/qasago"
)

func main() {
    key := make([]byte, 16) // Wrong size key

    _, err := qasago.Encrypt("data", key)
    if errors.Is(err, qasago.ErrInvalidKeySize) {
        log.Printf("Key size error: %v", err)
        // Handle invalid key size
    }

    _, err = qasago.Decrypt("invalid-base64!", key)
    if errors.Is(err, qasago.ErrInvalidCiphertext) {
        log.Printf("Ciphertext error: %v", err)
        // Handle invalid ciphertext
    }
}

Security Considerations

  1. Key Storage: Never hard-code encryption keys in source code. Use environment variables or secure key management systems.

  2. Key Rotation: Implement key rotation policies for long-term data storage. Example key rotation pattern:

func rotateEncryptionKey(oldKey, newKey []byte, ciphertext string) (string, error) {
    // Decrypt with old key
    plaintext, err := qasago.Decrypt(ciphertext, oldKey)
    if err != nil {
        return "", fmt.Errorf("failed to decrypt with old key: %w", err)
    }

    // Re-encrypt with new key
    newCiphertext, err := qasago.Encrypt(plaintext, newKey)
    if err != nil {
        return "", fmt.Errorf("failed to encrypt with new key: %w", err)
    }

    return newCiphertext, nil
}
  1. Nonce Uniqueness: The library automatically generates unique nonces for each encryption operation.

  2. Authentication: GCM mode provides built-in authentication, detecting any tampering with the ciphertext.

  3. Error Messages: The library returns generic error messages for security-sensitive operations to prevent information leakage.

  4. Thread Safety: All encryption and decryption operations are stateless and thread-safe. Multiple goroutines can safely call Encrypt() and Decrypt() concurrently without any synchronisation required.

Performance

  • AES-256-GCM is hardware-accelerated on most modern CPUs
  • Encryption: ~500 ns/op with 1728 B/op and 7 allocs/op
  • Decryption: ~386 ns/op with 1536 B/op and 5 allocs/op
  • Base64 encoding adds ~33% overhead to ciphertext size
  • Suitable for encrypting small to medium-sized data (< 1MB)
  • For large files, consider streaming encryption approaches

Testing

The library has comprehensive test coverage (91.4%) including:

  • Unit tests for all functions
  • Edge case testing
  • Tamper detection validation
  • Concurrent operation safety
  • Performance benchmarks

Run the test suite:

go test ./...

Run with coverage:

go test -cover ./...

Run benchmarks:

go test -bench=. -benchmem ./...

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • Uses Go's standard crypto/aes and crypto/cipher packages
  • Follows NIST recommendations for GCM mode operation
  • Implements OWASP cryptographic storage best practices

Support

For issues, questions, or suggestions, please open an issue on GitHub.

Documentation

Overview

Package qasago contains secure cryptographic utility functions This file provides secure encryption and decryption using AES-256-GCM AES-256-GCM provides authenticated encryption with associated data (AEAD)

Example

Example demonstrates basic encryption and decryption

package main

import (
	"fmt"
	"log"

	"github.com/djwarf/qasago"
)

func main() {
	// Generate a new encryption key
	key, err := qasago.GenerateEncryptionKey()
	if err != nil {
		log.Fatal(err)
	}

	// Encrypt some sensitive data
	plaintext := "my-secret-password"
	ciphertext, err := qasago.Encrypt(plaintext, key)
	if err != nil {
		log.Fatal(err)
	}

	// Decrypt the data
	decrypted, err := qasago.Decrypt(ciphertext, key)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Original: %s\nDecrypted: %s\n", plaintext, decrypted)
}
Output:

Original: my-secret-password
Decrypted: my-secret-password
Example (ErrorHandling)

Example_errorHandling demonstrates proper error handling

package main

import (
	"fmt"

	"github.com/djwarf/qasago"
)

func main() {
	// Wrong key size
	wrongKey := make([]byte, 16) // Should be 32 bytes

	_, err := qasago.Encrypt("data", wrongKey)
	if err == qasago.ErrInvalidKeySize {
		fmt.Println("Error: Key must be 32 bytes for AES-256")
	}

	// Invalid ciphertext
	validKey := make([]byte, 32)
	_, err = qasago.Decrypt("not-valid-base64!", validKey)
	if err != nil {
		fmt.Println("Error: Invalid ciphertext format")
	}

}
Output:

Error: Key must be 32 bytes for AES-256
Error: Invalid ciphertext format
Example (KeyRotation)

Example_keyRotation demonstrates how to rotate encryption keys

package main

import (
	"fmt"

	"github.com/djwarf/qasago"
)

func main() {
	// Generate old and new keys
	oldKey, _ := qasago.GenerateEncryptionKey()
	newKey, _ := qasago.GenerateEncryptionKey()

	// Encrypt with old key
	plaintext := "sensitive data"
	ciphertext, _ := qasago.Encrypt(plaintext, oldKey)

	// Rotate: decrypt with old key, re-encrypt with new key
	decrypted, _ := qasago.Decrypt(ciphertext, oldKey)
	newCiphertext, _ := qasago.Encrypt(decrypted, newKey)

	// Verify with new key
	verified, _ := qasago.Decrypt(newCiphertext, newKey)

	fmt.Printf("Data successfully rotated: %s\n", verified)
}
Output:

Data successfully rotated: sensitive data

Index

Examples

Constants

View Source
const (
	// KeySize is the required size for AES-256 encryption (256 bits = 32 bytes)
	KeySize = 32
	// NonceSize is the size of the nonce for GCM mode (96 bits = 12 bytes)
	// GCM recommends 96-bit nonces for optimal performance
	NonceSize = 12
)

Constants for encryption configuration

Variables

View Source
var (
	// ErrInvalidKeySize indicates the encryption key is not 32 bytes
	ErrInvalidKeySize = errors.New("encryption key must be exactly 32 bytes for AES-256")
	// ErrEmptyPlaintext indicates attempt to encrypt empty data
	ErrEmptyPlaintext = errors.New("plaintext cannot be empty")
	// ErrInvalidCiphertext indicates the ciphertext is too short or corrupted
	ErrInvalidCiphertext = errors.New("invalid ciphertext format or length")
	// ErrDecryptionFailed indicates authentication tag verification failed
	ErrDecryptionFailed = errors.New("decryption failed: data may be corrupted or tampered")
)

Common errors returned by encryption functions

Functions

func DecodeKey

func DecodeKey(encoded string) ([]byte, error)

DecodeKey converts a base64-encoded key back to binary. Use this when loading keys from environment variables or config files.

Parameters:

  • encoded: Base64-encoded key string

Returns:

  • []byte: Binary encryption key
  • error: Any decoding error
Example

ExampleDecodeKey demonstrates decoding a stored key

package main

import (
	"fmt"

	"os"

	"github.com/djwarf/qasago"
)

func main() {
	// In practice, this would come from an environment variable
	encodedKey := os.Getenv("ENCRYPTION_KEY")
	if encodedKey == "" {
		// For example purposes, use a dummy value
		// This is a valid 32-byte key encoded in base64
		key := make([]byte, 32)
		for i := range key {
			key[i] = byte(i)
		}
		encodedKey = qasago.EncodeKey(key)
	}

	key, err := qasago.DecodeKey(encodedKey)
	if err != nil {
		fmt.Printf("Failed to decode key: %v\n", err)
		return
	}

	fmt.Printf("Key decoded successfully, length: %d bytes\n", len(key))
}
Output:

Key decoded successfully, length: 32 bytes

func Decrypt

func Decrypt(encryptedData string, key []byte) (string, error)

Decrypt decrypts a base64-encoded AES-256-GCM ciphertext. The function expects the nonce to be prepended to the ciphertext. Authentication tag verification ensures data integrity.

Parameters:

  • encryptedData: Base64-encoded nonce + ciphertext + auth tag
  • key: The same 32-byte key used for encryption

Returns:

  • string: The decrypted plaintext
  • error: Any decryption or authentication error

Security: Returns error if authentication fails (tampering detected)

Example

ExampleDecrypt demonstrates decrypting data

package main

import (
	"fmt"

	"github.com/djwarf/qasago"
)

func main() {
	// In production, retrieve your key securely
	key := make([]byte, 32)

	// Example ciphertext (in practice, this would be retrieved from storage)
	// This is just for demonstration - it won't actually decrypt
	ciphertext := "base64-encoded-ciphertext-here"

	_, err := qasago.Decrypt(ciphertext, key)
	if err != nil {
		// Handle decryption error
		fmt.Println("Decryption failed: invalid ciphertext or wrong key")
	}
}
Output:

Decryption failed: invalid ciphertext or wrong key

func EncodeKey

func EncodeKey(key []byte) string

EncodeKey converts a binary key to base64 for configuration storage. Use this when storing keys in environment variables or config files.

Parameters:

  • key: Binary encryption key

Returns:

  • string: Base64-encoded key safe for text storage
Example

ExampleEncodeKey demonstrates encoding a key for storage

package main

import (
	"fmt"
	"log"

	"github.com/djwarf/qasago"
)

func main() {
	key, err := qasago.GenerateEncryptionKey()
	if err != nil {
		log.Fatal(err)
	}

	// Encode for storage (e.g., in environment variable)
	encoded := qasago.EncodeKey(key)

	// The encoded key is base64
	fmt.Printf("Encoded key ready for storage: %t\n", len(encoded) > 0)
}
Output:

Encoded key ready for storage: true

func Encrypt

func Encrypt(plaintext string, key []byte) (string, error)

Encrypt securely encrypts plaintext using AES-256-GCM with a random nonce. The function prepends the nonce to the ciphertext for self-contained storage. The result is base64-encoded for safe storage in text fields.

Parameters:

  • plaintext: The data to encrypt (cannot be empty)
  • key: Exactly 32 bytes for AES-256 encryption

Returns:

  • string: Base64-encoded nonce + ciphertext + auth tag
  • error: Any encryption error

Format: base64(nonce[12] || ciphertext[...] || authTag[16])

Example

ExampleEncrypt demonstrates encrypting data

package main

import (
	"fmt"
	"log"

	"github.com/djwarf/qasago"
)

func main() {
	// In production, generate or retrieve your key securely
	key := make([]byte, 32)
	// ... populate key from secure storage ...

	plaintext := "sensitive information"
	ciphertext, err := qasago.Encrypt(plaintext, key)
	if err != nil {
		log.Fatal(err)
	}

	// The ciphertext is base64 encoded and safe to store as text
	fmt.Printf("Ciphertext is base64 encoded: %t\n", len(ciphertext) > 0)
}
Output:

Ciphertext is base64 encoded: true

func GenerateEncryptionKey

func GenerateEncryptionKey() ([]byte, error)

GenerateEncryptionKey creates a cryptographically secure 256-bit key. This should be called once during initial setup and the key stored securely.

Returns:

  • []byte: 32-byte encryption key
  • error: Any generation error

Security: Store this key securely (environment variable, secrets manager) WARNING: Losing this key means encrypted data cannot be recovered

Example

ExampleGenerateEncryptionKey demonstrates key generation

package main

import (
	"fmt"
	"log"

	"github.com/djwarf/qasago"
)

func main() {
	key, err := qasago.GenerateEncryptionKey()
	if err != nil {
		log.Fatal(err)
	}

	// Key is 32 bytes for AES-256
	fmt.Printf("Key length: %d bytes\n", len(key))
}
Output:

Key length: 32 bytes

func ValidateEncryptionKey

func ValidateEncryptionKey(key []byte) error

ValidateEncryptionKey checks if a key is suitable for AES-256 encryption. This helps catch configuration errors early.

Parameters:

  • key: The encryption key to validate

Returns:

  • error: nil if valid, error describing the issue otherwise

Types

type CipherText added in v1.1.0

type CipherText string

CipherText represents encrypted data in base64 format. It contains the nonce, ciphertext, and authentication tag.

func (CipherText) String added in v1.1.0

func (c CipherText) String() string

String returns the string representation of the ciphertext.

type Config added in v1.1.0

type Config struct {
	// Key is the AES-256 encryption key (must be 32 bytes)
	Key EncryptionKey
}

Config represents encryption configuration options. This type can be extended in the future to support additional encryption modes.

Example

ExampleConfig demonstrates using the Config type

package main

import (
	"fmt"
	"log"

	"github.com/djwarf/qasago"
)

func main() {
	// Generate a new key
	key, err := qasago.GenerateEncryptionKey()
	if err != nil {
		log.Fatal(err)
	}

	// Create a config instance
	config := qasago.Config{
		Key: key,
	}

	// Use the config for encryption
	plaintext := "database-connection-string"
	ciphertext, err := config.Encrypt(plaintext)
	if err != nil {
		log.Fatal(err)
	}

	// Use the same config for decryption
	decrypted, err := config.Decrypt(ciphertext)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Successfully encrypted and decrypted: %s\n", decrypted)
}
Output:

Successfully encrypted and decrypted: database-connection-string

func NewConfig added in v1.1.0

func NewConfig(key []byte) (*Config, error)

NewConfig creates a new encryption configuration with the provided key.

func (*Config) Decrypt added in v1.1.0

func (c *Config) Decrypt(ciphertext CipherText) (PlainText, error)

Decrypt decrypts ciphertext using the configured key.

func (*Config) Encrypt added in v1.1.0

func (c *Config) Encrypt(plaintext string) (CipherText, error)

Encrypt encrypts plaintext using the configured key.

type EncryptionKey added in v1.1.0

type EncryptionKey []byte

EncryptionKey represents a 256-bit AES encryption key. It provides type safety and clarity when working with encryption keys.

func (EncryptionKey) Bytes added in v1.1.0

func (k EncryptionKey) Bytes() []byte

Bytes returns the raw byte representation of the key.

func (EncryptionKey) String added in v1.1.0

func (k EncryptionKey) String() string

String returns the base64-encoded representation of the key.

func (EncryptionKey) Validate added in v1.1.0

func (k EncryptionKey) Validate() error

Validate checks if the encryption key is valid for AES-256.

type PlainText added in v1.1.0

type PlainText string

PlainText represents unencrypted text data.

func (PlainText) String added in v1.1.0

func (p PlainText) String() string

String returns the string representation of the plaintext.

Jump to

Keyboard shortcuts

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