Module: Nvoi::Utils::Crypto

Defined in:
lib/nvoi/utils/crypto.rb

Overview

Crypto handles AES-256-GCM encryption/decryption

Constant Summary collapse

KEY_SIZE =

256 bits

32
NONCE_SIZE =

GCM nonce size

12
KEY_HEX_LENGTH =

64 hex characters

KEY_SIZE * 2

Class Method Summary collapse

Class Method Details

.decrypt(ciphertext, hex_key) ⇒ Object

Decrypt ciphertext using AES-256-GCM with the provided hex-encoded key Expects format: [12-byte nonce][16-byte auth tag]



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/nvoi/utils/crypto.rb', line 43

def decrypt(ciphertext, hex_key)
  key = decode_key(hex_key)

  min_size = NONCE_SIZE + 16 # nonce + auth tag
  if ciphertext.bytesize < min_size
    raise Errors::DecryptionError, "ciphertext too short: need at least #{min_size} bytes, got #{ciphertext.bytesize}"
  end

  # Extract nonce and auth tag
  nonce = ciphertext[0, NONCE_SIZE]
  auth_tag = ciphertext[-16, 16]
  encrypted_data = ciphertext[NONCE_SIZE...-16]

  cipher = OpenSSL::Cipher.new("aes-256-gcm")
  cipher.decrypt
  cipher.key = key
  cipher.iv = nonce
  cipher.auth_tag = auth_tag

  begin
    cipher.update(encrypted_data) + cipher.final
  rescue OpenSSL::Cipher::CipherError => e
    raise Errors::DecryptionError, "decryption failed (wrong key or corrupted data): #{e.message}"
  end
end

.encrypt(plaintext, hex_key) ⇒ Object

Encrypt plaintext using AES-256-GCM with the provided hex-encoded key Returns: [12-byte nonce][16-byte auth tag]



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/nvoi/utils/crypto.rb', line 22

def encrypt(plaintext, hex_key)
  key = decode_key(hex_key)

  cipher = OpenSSL::Cipher.new("aes-256-gcm")
  cipher.encrypt
  cipher.key = key

  # Generate random nonce
  nonce = SecureRandom.random_bytes(NONCE_SIZE)
  cipher.iv = nonce

  # Encrypt
  ciphertext = cipher.update(plaintext) + cipher.final
  auth_tag = cipher.auth_tag

  # Return: nonce + ciphertext + auth_tag
  nonce + ciphertext + auth_tag
end

.generate_keyObject

Generate a new random 32-byte key and return it as hex string



16
17
18
# File 'lib/nvoi/utils/crypto.rb', line 16

def generate_key
  SecureRandom.hex(KEY_SIZE)
end

.validate_key(hex_key) ⇒ Object

Validate a hex-encoded key



70
71
72
73
74
75
76
77
78
79
80
# File 'lib/nvoi/utils/crypto.rb', line 70

def validate_key(hex_key)
  unless hex_key.length == KEY_HEX_LENGTH
    raise Errors::InvalidKeyError, "invalid key length: expected #{KEY_HEX_LENGTH} hex characters, got #{hex_key.length}"
  end

  unless hex_key.match?(/\A[0-9a-fA-F]+\z/)
    raise Errors::InvalidKeyError, "invalid hex key: contains non-hex characters"
  end

  true
end