Module: Age

Defined in:
lib/age.rb,
lib/age/errors.rb,
lib/age/version.rb,
lib/age/bindings.rb

Overview

Ruby bindings for [age](github.com/FiloSottile/age) using a CGO shared library with FFI bindings.

Age is a simple, modern, and secure file encryption tool, format, and Go library. This gem provides a Ruby interface to age’s encryption and decryption capabilities.

Features:

  • Encrypt and decrypt data using age public/private key pairs

  • Encrypt and decrypt files directly

  • Generate age keypairs programmatically

  • Multiple recipients support for encryption

  • ASCII armor format support for text-safe encrypted output

  • FFI-based integration with Go’s age implementation

  • Binary data handling with proper encoding

Defined Under Namespace

Modules: Bindings Classes: DecryptionError, EncryptionError, GenerateKeyPairError

Constant Summary collapse

VERSION =
'0.1.0'

Class Method Summary collapse

Class Method Details

.decrypt(privkeys, encrypted, armor: false) ⇒ Bytes

Decrypts encrypted data using the provided age private keys.

Parameters:

  • privkeys (Array<String>)

    List of age private keys.

  • encrypted (Bytes)

    Encrypted data to decrypt.

  • armor (Boolean) (defaults to: false)

    Whether the input is armored.

Returns:

  • (Bytes)

    Decrypted plain data.



79
80
81
82
83
84
# File 'lib/age.rb', line 79

def decrypt(privkeys, encrypted, armor: false)
  privkeys = privkeys.join(',') if privkeys.is_a?(Array)
  perform_decryption(encrypted) do |input, output|
    Age::Bindings.decrypt(privkeys, input, output, armor ? 1 : 0)
  end
end

.decrypt_file(privkeys, infile, outfile = nil) ⇒ void

This method returns an undefined value.

Decrypts a file using the provided age private keys.

Parameters:

  • privkeys (Array<String>)

    List of age private keys.

  • infile (String)

    Input file path.

  • outfile (String, nil) (defaults to: nil)

    Output file path. If nil, removes ‘.age` from infile.



166
167
168
169
170
# File 'lib/age.rb', line 166

def decrypt_file(privkeys, infile, outfile = nil)
  perform_file_decryption(infile, outfile) do |encrypted, armor|
    decrypt(privkeys, encrypted, armor:)
  end
end

.decrypt_file_with_passphrase(passphrase, infile, outfile = nil) ⇒ void

This method returns an undefined value.

Decrypts a file using the provided passphrase.

Parameters:

  • passphrase (String)

    Passphrase to use for decryption.

  • infile (String)

    Input file path.

  • outfile (String, nil) (defaults to: nil)

    Output file path. If nil, removes ‘.age` from infile.



180
181
182
183
184
# File 'lib/age.rb', line 180

def decrypt_file_with_passphrase(passphrase, infile, outfile = nil)
  perform_file_decryption(infile, outfile) do |encrypted, armor|
    decrypt_with_passphrase(passphrase, encrypted, armor:)
  end
end

.decrypt_file_with_ssh_keys(ssh_privkeys, infile, outfile = nil) ⇒ void

This method returns an undefined value.

Decrypts a file using the provided SSH private keys.

Parameters:

  • ssh_privkeys (Array<String>)

    List of SSH private keys (ssh-rsa, ssh-ed25519).

  • infile (String)

    Input file path.

  • outfile (String, nil) (defaults to: nil)

    Output file path. If nil, removes ‘.age` from infile.



194
195
196
197
198
# File 'lib/age.rb', line 194

def decrypt_file_with_ssh_keys(ssh_privkeys, infile, outfile = nil)
  perform_file_decryption(infile, outfile) do |encrypted, armor|
    decrypt_with_ssh_keys(ssh_privkeys, encrypted, armor:)
  end
end

.decrypt_with_passphrase(passphrase, encrypted, armor: false) ⇒ Bytes

Decrypts encrypted data using the provided passphrase.

Parameters:

  • passphrase (String)

    Passphrase to use for decryption.

  • encrypted (Bytes)

    Encrypted data to decrypt.

  • armor (Boolean) (defaults to: false)

    Whether the input is armored.

Returns:

  • (Bytes)

    Decrypted plain data.



94
95
96
97
98
# File 'lib/age.rb', line 94

def decrypt_with_passphrase(passphrase, encrypted, armor: false)
  perform_decryption(encrypted) do |input, output|
    Age::Bindings.decrypt_with_passphrase(passphrase, input, output, armor ? 1 : 0)
  end
end

.decrypt_with_ssh_keys(ssh_privkeys, encrypted, armor: false) ⇒ Bytes

Decrypts encrypted data using the provided SSH private keys.

Parameters:

  • ssh_privkeys (Array<String>)

    List of SSH private keys (ssh-rsa, ssh-ed25519).

  • encrypted (Bytes)

    Encrypted data to decrypt.

  • armor (Boolean) (defaults to: false)

    Whether the input is armored.

Returns:

  • (Bytes)

    Decrypted plain data.



108
109
110
111
112
113
# File 'lib/age.rb', line 108

def decrypt_with_ssh_keys(ssh_privkeys, encrypted, armor: false)
  ssh_privkeys = ssh_privkeys.join(',') if ssh_privkeys.is_a?(Array)
  perform_decryption(encrypted) do |input, output|
    Age::Bindings.decrypt_with_ssh_keys(ssh_privkeys, input, output, armor ? 1 : 0)
  end
end

.detect_armor_format?(encrypted) ⇒ Boolean

Detects if the encrypted data is in armor format.

Parameters:

  • encrypted (String)

    Encrypted data.

Returns:

  • (Boolean)

    True if armored, false otherwise.



330
331
332
333
# File 'lib/age.rb', line 330

def detect_armor_format?(encrypted)
  encrypted.start_with?('-----BEGIN AGE ENCRYPTED FILE-----') &&
    encrypted.end_with?("-----END AGE ENCRYPTED FILE-----\n")
end

.encrypt(pubkeys, plain, armor: false) ⇒ Bytes

Encrypts plain data using the provided age public keys.

Parameters:

  • pubkeys (Array<String>)

    List of age public keys.

  • plain (Bytes)

    Plain data to encrypt.

  • armor (Boolean) (defaults to: false)

    Whether to armor the output.

Returns:

  • (Bytes)

    Encrypted data.



36
37
38
39
40
41
# File 'lib/age.rb', line 36

def encrypt(pubkeys, plain, armor: false)
  pubkeys = pubkeys.join(',') if pubkeys.is_a?(Array)
  perform_encryption(plain) do |input, output|
    Age::Bindings.encrypt(pubkeys, input, output, armor ? 1 : 0)
  end
end

.encrypt_file(pubkeys, infile, outfile = nil, armor: false) ⇒ void

This method returns an undefined value.

Encrypts a file using the provided age public keys.

Parameters:

  • pubkeys (Array<String>)

    List of age public keys.

  • infile (String)

    Input file path.

  • outfile (String, nil) (defaults to: nil)

    Output file path. If nil, appends ‘.age` to infile.

  • armor (Boolean) (defaults to: false)

    Whether to armor the output.



123
124
125
126
127
# File 'lib/age.rb', line 123

def encrypt_file(pubkeys, infile, outfile = nil, armor: false)
  perform_file_encryption(infile, outfile) do |plain|
    encrypt(pubkeys, plain, armor:)
  end
end

.encrypt_file_with_passphrase(passphrase, infile, outfile = nil, armor: false) ⇒ void

This method returns an undefined value.

Encrypts a file using the provided passphrase.

Parameters:

  • passphrase (String)

    Passphrase to use for encryption.

  • infile (String)

    Input file path.

  • outfile (String, nil) (defaults to: nil)

    Output file path. If nil, appends ‘.age` to infile.

  • armor (Boolean) (defaults to: false)

    Whether to armor the output.



138
139
140
141
142
# File 'lib/age.rb', line 138

def encrypt_file_with_passphrase(passphrase, infile, outfile = nil, armor: false)
  perform_file_encryption(infile, outfile) do |plain|
    encrypt_with_passphrase(passphrase, plain, armor:)
  end
end

.encrypt_file_with_ssh_keys(ssh_pubkeys, infile, outfile = nil, armor: false) ⇒ void

This method returns an undefined value.

Encrypts a file using the provided SSH public keys.

Parameters:

  • ssh_pubkeys (Array<String>)

    List of SSH public keys (ssh-rsa, ssh-ed25519).

  • infile (String)

    Input file path.

  • outfile (String, nil) (defaults to: nil)

    Output file path. If nil, appends ‘.age` to infile.

  • armor (Boolean) (defaults to: false)

    Whether to armor the output.



153
154
155
156
157
# File 'lib/age.rb', line 153

def encrypt_file_with_ssh_keys(ssh_pubkeys, infile, outfile = nil, armor: false)
  perform_file_encryption(infile, outfile) do |plain|
    encrypt_with_ssh_keys(ssh_pubkeys, plain, armor:)
  end
end

.encrypt_with_passphrase(passphrase, plain, armor: false) ⇒ Bytes

Encrypts plain data using the provided passphrase.

Parameters:

  • passphrase (String)

    Passphrase to use for encryption.

  • plain (Bytes)

    Plain data to encrypt.

  • armor (Boolean) (defaults to: false)

    Whether to armor the output.

Returns:

  • (Bytes)

    Encrypted data.



50
51
52
53
54
# File 'lib/age.rb', line 50

def encrypt_with_passphrase(passphrase, plain, armor: false)
  perform_encryption(plain) do |input, output|
    Age::Bindings.encrypt_with_passphrase(passphrase, input, output, armor ? 1 : 0)
  end
end

.encrypt_with_ssh_keys(ssh_pubkeys, plain, armor: false) ⇒ Bytes

Encrypts plain data using the provided SSH public keys.

Parameters:

  • ssh_pubkeys (Array<String>)

    List of SSH public keys (ssh-rsa, ssh-ed25519).

  • plain (Bytes)

    Plain data to encrypt.

  • armor (Boolean) (defaults to: false)

    Whether to armor the output.

Returns:

  • (Bytes)

    Encrypted data.



64
65
66
67
68
69
# File 'lib/age.rb', line 64

def encrypt_with_ssh_keys(ssh_pubkeys, plain, armor: false)
  ssh_pubkeys = ssh_pubkeys.join(',') if ssh_pubkeys.is_a?(Array)
  perform_encryption(plain) do |input, output|
    Age::Bindings.encrypt_with_ssh_keys(ssh_pubkeys, input, output, armor ? 1 : 0)
  end
end

.generate_keypair(postquantum: false) ⇒ Hash{Symbol => String}

Generates a new age key pair.

Parameters:

  • postquantum (Boolean) (defaults to: false)

    Whether to generate a post-quantum key pair.

Returns:

  • (Hash{Symbol => String})

    A hash containing :public_key and :private_key.



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/age.rb', line 206

def generate_keypair(postquantum: false)
  pubkey_ptr = FFI::MemoryPointer.new(:pointer)
  privkey_ptr = FFI::MemoryPointer.new(:pointer)
  keypair = Age::Bindings::AgeKeyPair.new
  keypair[:public_key] = pubkey_ptr
  keypair[:private_key] = privkey_ptr

  err_ptr = Age::Bindings.generate_keypair(keypair, postquantum ? 1 : 0)
  unless err_ptr.null?
    err_msg = read_string_from_pointer(err_ptr)
    raise GenerateKeyPairError, err_msg
  end

  pubkey = read_string_from_pointer(pubkey_ptr.read_pointer)
  privkey = read_string_from_pointer(privkey_ptr.read_pointer)

  { public_key: pubkey, private_key: privkey }
end

.perform_decryption(encrypted) {|input, output| ... } ⇒ Bytes

Performs decryption operation with common FFI setup.

Parameters:

  • encrypted (Bytes)

    Encrypted data to decrypt.

Yields:

  • (input, output)

    Block that performs the actual decryption call.

Returns:

  • (Bytes)

    Decrypted plain data.



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/age.rb', line 266

def perform_decryption(encrypted)
  encrypted = encrypted.b if encrypted.respond_to?(:b)

  encrypted_ptr = FFI::MemoryPointer.new(:char, encrypted.bytesize)
  encrypted_ptr.put_bytes(0, encrypted)

  input = Age::Bindings::AgeInput.new
  input[:data] = encrypted_ptr
  input[:length] = encrypted.bytesize

  plain_ptr = FFI::MemoryPointer.new(:pointer)
  plain_len_ptr = FFI::MemoryPointer.new(:int)

  output = Age::Bindings::AgeOutput.new
  output[:data] = plain_ptr
  output[:length] = plain_len_ptr

  err_ptr = yield(input, output)
  unless err_ptr.null?
    err_msg = read_string_from_pointer(err_ptr)
    raise DecryptionError, err_msg
  end

  bytes = read_bytes_from_pointer(plain_ptr.read_pointer, plain_len_ptr.read_int)
  bytes.force_encoding('BINARY')
end

.perform_encryption(plain) {|input, output| ... } ⇒ Bytes

Performs encryption operation with common FFI setup.

Parameters:

  • plain (Bytes)

    Plain data to encrypt.

Yields:

  • (input, output)

    Block that performs the actual encryption call.

Returns:

  • (Bytes)

    Encrypted data.



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/age.rb', line 232

def perform_encryption(plain)
  plain = plain.b if plain.respond_to?(:b)

  plain_ptr = FFI::MemoryPointer.new(:char, plain.bytesize)
  plain_ptr.put_bytes(0, plain)

  input = Age::Bindings::AgeInput.new
  input[:data] = plain_ptr
  input[:length] = plain.bytesize

  encrypted_ptr = FFI::MemoryPointer.new(:pointer)
  encrypted_len_ptr = FFI::MemoryPointer.new(:int)

  output = Age::Bindings::AgeOutput.new
  output[:data] = encrypted_ptr
  output[:length] = encrypted_len_ptr

  err_ptr = yield(input, output)
  unless err_ptr.null?
    err_msg = read_string_from_pointer(err_ptr)
    raise EncryptionError, err_msg
  end

  bytes = read_bytes_from_pointer(encrypted_ptr.read_pointer, encrypted_len_ptr.read_int)
  bytes.force_encoding('BINARY')
end

.perform_file_decryption(infile, outfile) {|encrypted, armor| ... } ⇒ void

This method returns an undefined value.

Performs file decryption with common file handling and armor detection.

Parameters:

  • infile (String)

    Input file path.

  • outfile (String, nil)

    Output file path.

Yields:

  • (encrypted, armor)

    Block that performs the actual decryption.



316
317
318
319
320
321
322
# File 'lib/age.rb', line 316

def perform_file_decryption(infile, outfile)
  outfile ||= File.basename(infile, '.*')
  encrypted = File.binread(infile)
  armor = detect_armor_format?(encrypted)
  plain = yield(encrypted, armor)
  File.binwrite(outfile, plain)
end

.perform_file_encryption(infile, outfile) {|plain| ... } ⇒ void

This method returns an undefined value.

Performs file encryption with common file handling.

Parameters:

  • infile (String)

    Input file path.

  • outfile (String, nil)

    Output file path.

Yields:

  • (plain)

    Block that performs the actual encryption.



301
302
303
304
305
306
# File 'lib/age.rb', line 301

def perform_file_encryption(infile, outfile)
  outfile ||= "#{infile}.age"
  plain = File.binread(infile)
  encrypted = yield(plain)
  File.binwrite(outfile, encrypted)
end

.read_bytes_from_pointer(ptr, length) ⇒ String?

Reads bytes from a pointer and frees the memory.

Parameters:

  • ptr (FFI::Pointer)

    Pointer to the bytes.

  • length (Integer)

    Length of the bytes to read.

Returns:

  • (String, nil)

    The bytes read from the pointer, or nil.



357
358
359
360
361
362
363
364
# File 'lib/age.rb', line 357

def read_bytes_from_pointer(ptr, length)
  return nil if ptr.null? || length.zero?

  bytes = ptr.read_bytes(length)
  Age::Bindings.free_memory(ptr)

  bytes
end

.read_string_from_pointer(ptr) ⇒ String?

Reads a string from a pointer and frees the memory.

Parameters:

  • ptr (FFI::Pointer)

    Pointer to the string.

Returns:

  • (String, nil)

    The string read from the pointer, or nil.



341
342
343
344
345
346
347
348
# File 'lib/age.rb', line 341

def read_string_from_pointer(ptr)
  return nil if ptr.null?

  str = ptr.read_string
  Age::Bindings.free_memory(ptr)

  str
end