aes_gcm

AES-GCM Crystal Shard

A Crystal shard providing AES-256-GCM (Galois/Counter Mode) authenticated encryption and decryption.

Features

  • ✅ AES-256-GCM encryption and decryption
  • ✅ Authentication tag support (protects against tampering)
  • Sequel column encryption support - Decrypt Ruby sequel-column-encryption data
  • ✅ Configurable IV and tag sizes
  • ✅ Base64 encoding/decoding helpers
  • ✅ Direct bindings to OpenSSL for full GCM support
  • ✅ Type-safe API with Crystal's type system
  • ✅ 29 comprehensive specs

Why This Shard?

Crystal's standard OpenSSL::Cipher library doesn't expose methods to get/set authentication tags for GCM mode. This shard provides direct bindings to OpenSSL's EVP_CIPHER_CTX_ctrl function to enable full GCM functionality.

Installation

Add this to your application's shard.yml:

dependencies:
  aes_gcm:
    path: ./shard

Then run:

shards install

Usage

Sequel Column Encryption (Quick Start)

If you're working with the Sequel column_encryption plugin in Ruby, you can decrypt data with one line:

require "aes_gcm"

key = "e74aaac7a0b7f97159bb787dd593a3cb" # Your 32-byte (256-bit) encryption key
encrypted = "AAAAAGqIftnXqY76h5bbGh4pnmN8tNsfzGfw4sJcvWkmnRV46FuG762BI0vjoC7quADZ48Su-NfQiHoYHXwuV3v8rPlsM69rREkYReE11m9Y" # Base64 encoded encrypted data

# Simple decryption
plaintext = AesGcm::SequelColumnEncryption.decrypt(encrypted, key)
puts plaintext  # => "Hello, World!"

# Decryption with metadata
info = AesGcm::SequelColumnEncryption.decrypt_with_info(encrypted, key)
puts info[:plaintext]  # => "Hello, World!"
puts info[:format]     # => "not_searchable"
puts info[:searchable] # => false

# Validate format
if AesGcm::SequelColumnEncryption.valid_format?(encrypted)
  puts "Valid Sequel encrypted data"
end

Basic Encryption/Decryption

require "aes_gcm"

cipher = AesGcm::Cipher.new

key = "e74aaac7a0b7f97159bb787dd593a3cb"
plaintext = "Hello, World!"

# Encrypt
encrypted = cipher.encrypt(
  key: key,
  plaintext: plaintext
)

# Access encrypted components
puts encrypted.iv.hexstring         # Initialization vector
puts encrypted.auth_tag.hexstring   # Authentication tag
puts encrypted.ciphertext.hexstring # Encrypted data

# Decrypt
decrypted = cipher.decrypt(encrypted)
puts String.new(decrypted)  # "Hello, World!"

Base64 Encoding

require "aes_gcm"

cipher = AesGcm::Cipher.new
key = "e74aaac7a0b7f97159bb787dd593a3cb"

# Encrypt and encode to base64
encoded = cipher.encrypt_base64(
  key: key,
  plaintext: "secret message"
)

# Decrypt from base64
decoded = cipher.decrypt_base64(
  encoded: encoded,
  key: key
)
puts String.new(decoded)

Custom Configuration

# Custom IV and tag sizes
cipher = AesGcm::Cipher.new(
  iv_size: 12,   # 96-bit IV (default)
  tag_size: 16,  # 128-bit tag (default)
  key_size: 32   # 256-bit key (default)
)

API Documentation

AesGcm::SequelColumnEncryption

Module for decrypting data encrypted with Ruby's sequel-column-encryption gem.

Methods

  • decrypt(data_base64 : String, key : String | Bytes, remove_padding : Bool = true) : String

    • Decrypts Sequel column encryption format data
    • data_base64: Base64-encoded encrypted data
    • key: Encryption key (32 bytes for the master key)
    • remove_padding: Remove Sequel padding (default: true)
    • Returns decrypted plaintext as String
    • Raises DecryptionError if decryption fails
  • decrypt_with_info(data_base64 : String, key : String | Bytes) : NamedTuple

    • Decrypts and returns metadata about the encryption format
    • Returns: {plaintext, flags, key_id, searchable, format}
    • Example:
      info = AesGcm::SequelColumnEncryption.decrypt_with_info(encrypted, key)
      info[:plaintext]  # => "John Doe"
      info[:format]     # => "not_searchable"
      info[:searchable] # => false
      
  • valid_format?(data_base64 : String) : Bool

    • Checks if data appears to be in Sequel encryption format
    • Does not attempt decryption, only validates structure
    • Returns true if data has valid Sequel format

AesGcm::Cipher

Main cipher class for AES-256-GCM operations.

Methods

  • encrypt(key, plaintext, iv = nil) : EncryptedData

    • Encrypts plaintext and returns encrypted data with IV and auth tag
    • key: 32-byte encryption key (String or Bytes)
    • plaintext: Data to encrypt (String or Bytes)
    • iv: Optional IV (defaults to random)
  • decrypt(key, ciphertext, iv, auth_tag) : Bytes

    • Decrypts ciphertext and verifies authenticity
    • Raises OpenSSL::Cipher::Error if authentication fails
  • decrypt(encrypted : EncryptedData) : Bytes

    • Convenience method to decrypt from EncryptedData struct
  • encrypt_base64(key, plaintext) : String

    • Encrypts and returns URL-safe base64 encoded string
  • decrypt_base64(encoded, key) : Bytes

    • Decrypts from URL-safe base64 encoded string

AesGcm::EncryptedData

Struct containing all components needed for decryption.

Properties

  • ciphertext : Bytes - Encrypted data
  • iv : Bytes - Initialization vector
  • auth_tag : Bytes - Authentication tag
  • key : Bytes - Encryption key

Methods

  • to_base64 : String - Encode all components to URL-safe base64
  • self.from_base64(encoded, key, iv_size = 12, tag_size = 16) : EncryptedData

Security Notes

  1. Key Management: Never hardcode encryption keys in your source code. Use environment variables or secure key management systems.

  2. Authentication: GCM mode provides authenticated encryption. If decryption fails with a OpenSSL::Cipher::Error, it means:

    • The data was tampered with
    • Wrong encryption key was used
    • Data is corrupted
  3. IV Reuse: Never reuse the same IV with the same key. This implementation generates random IVs by default.

  4. Key Size: This shard uses AES-256 (32-byte keys). Ensure your keys have sufficient entropy.

Examples

See the examples/ directory for more examples:

  • basic_usage.cr - Basic encryption/decryption examples
  • sequel_column_encryption.cr - Decrypting Sequel column encryption format

Run examples:

cd shard
crystal run examples/basic_usage.cr
crystal run examples/sequel_column_encryption.cr

Technical Details

This shard uses direct C bindings to OpenSSL's EVP_CIPHER_CTX_ctrl function to access GCM-specific operations:

  • EVP_CTRL_GCM_SET_TAG (0x11) - Set authentication tag for decryption
  • EVP_CTRL_GCM_GET_TAG (0x10) - Get authentication tag after encryption
  • EVP_CTRL_GCM_SET_IVLEN (0x9) - Set custom IV length

Note on Linking: We don't use @[Link("crypto")] in our C bindings because Crystal's OpenSSL module already links to libcrypto. Adding it would cause duplicate library warnings on macOS and other platforms.

Limitations

  1. AAD is not supported: Additional Authenticated Data (AAD) is not supported.

  2. Algorithm: Only AES-256-GCM is supported. Other GCM variants (AES-128-GCM, AES-192-GCM) could be added in future versions.

Troubleshooting

"ld: warning: ignoring duplicate libraries: '-lcrypto'" on macOS

This warning has been fixed in version 0.1.0. If you see this warning in older versions, it's because Crystal's OpenSSL module already links to libcrypto. The warning is harmless but can be fixed by removing the @[Link("crypto")] annotation.

Decryption fails with "authentication verification failed"

This error means:

  • Wrong encryption key
  • Data has been tampered with
  • Corrupted ciphertext or auth tag

"Key must be 32 bytes" error

AES-256 requires exactly 32 bytes (256 bits) for the key. Ensure your key string is exactly 32 bytes:

key = "12345678901234567890123456789012"  # Exactly 32 bytes
puts key.bytesize  # Should print 32

Contributing

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

License

MIT License

Credits

Inspired by the need to decrypt Sequel column encryption data in Crystal.

Repository

aes_gcm

Owner
Statistic
  • 0
  • 0
  • 0
  • 0
  • 0
  • about 14 hours ago
  • November 9, 2025
License

MIT License

Links
Synced at

Fri, 06 Mar 2026 20:02:28 GMT

Languages