Module: RubynCode::Auth::KeyEncryption

Defined in:
lib/rubyn_code/auth/key_encryption.rb

Overview

Encrypts and decrypts provider API keys at rest using AES-256-GCM.

The encryption key is derived via PBKDF2 from machine-specific identifiers (username, hostname, home directory) combined with a random salt stored in ~/.rubyn-code/.encryption_salt. This means keys are only decryptable on the same machine by the same user.

Encrypted values are prefixed with “enc:v1:” so plaintext values from older versions are transparently migrated on first read.

Constant Summary collapse

CIPHER =
'aes-256-gcm'
PREFIX =
'enc:v1:'
IV_LENGTH =
12
TAG_LENGTH =
16
PBKDF2_ITERATIONS =
100_000
KEY_LENGTH =
32
SALT_LENGTH =
32

Class Method Summary collapse

Class Method Details

.decrypt(value) ⇒ Object



45
46
47
48
49
50
51
52
53
# File 'lib/rubyn_code/auth/key_encryption.rb', line 45

def decrypt(value)
  return nil unless value
  return value unless encrypted?(value)

  raw = Base64.strict_decode64(value.delete_prefix(PREFIX))
  decrypt_raw(raw)
rescue OpenSSL::Cipher::CipherError, ArgumentError
  nil
end

.encrypt(plaintext) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/rubyn_code/auth/key_encryption.rb', line 30

def encrypt(plaintext)
  return nil unless plaintext

  cipher = OpenSSL::Cipher.new(CIPHER).encrypt
  key = derive_key
  cipher.key = key
  iv = cipher.random_iv

  ciphertext = cipher.update(plaintext) + cipher.final
  tag = cipher.auth_tag(TAG_LENGTH)

  encoded = Base64.strict_encode64(iv + ciphertext + tag)
  "#{PREFIX}#{encoded}"
end

.encrypted?(value) ⇒ Boolean

Returns:

  • (Boolean)


55
56
57
# File 'lib/rubyn_code/auth/key_encryption.rb', line 55

def encrypted?(value)
  value.is_a?(String) && value.start_with?(PREFIX)
end