Class: Rubino::OAuth::TokenEncryptor
- Inherits:
-
Object
- Object
- Rubino::OAuth::TokenEncryptor
- Defined in:
- lib/rubino/oauth/token_encryptor.rb
Overview
AES-256-GCM symmetric encryption for OAuth tokens at rest.
Key supplied via RUBINO_ENCRYPTION_KEY env (32 raw bytes encoded as standard base64). Generate one with:
ruby -rsecurerandom -rbase64 -e 'puts Base64.strict_encode64(SecureRandom.random_bytes(32))'
Wire format is Base64(IV || ciphertext || tag) with a 12-byte IV and a 16-byte GCM auth tag. TokenEncryptor.from_env raises KeyMissingError when the env var is missing or not a 32-byte key; #decrypt raises InvalidCiphertextError on tampered or truncated payloads.
Defined Under Namespace
Classes: InvalidCiphertextError, KeyMissingError
Constant Summary collapse
- CIPHER =
"aes-256-gcm"- IV_LEN =
12- TAG_LEN =
16
Class Method Summary collapse
-
.from_env ⇒ TokenEncryptor
Build an encryptor using the key in RUBINO_ENCRYPTION_KEY.
Instance Method Summary collapse
-
#decrypt(payload) ⇒ String?
The original plaintext, or nil when payload is nil.
-
#encrypt(plaintext) ⇒ String?
Base64(IV || ciphertext || tag), or nil when plaintext is nil (so nullable token columns round-trip unchanged).
-
#initialize(key) ⇒ TokenEncryptor
constructor
A new instance of TokenEncryptor.
Constructor Details
#initialize(key) ⇒ TokenEncryptor
Returns a new instance of TokenEncryptor.
44 45 46 47 48 |
# File 'lib/rubino/oauth/token_encryptor.rb', line 44 def initialize(key) raise ArgumentError, "key must be 32 bytes" unless key.bytesize == 32 @key = key end |
Class Method Details
.from_env ⇒ TokenEncryptor
Build an encryptor using the key in RUBINO_ENCRYPTION_KEY.
34 35 36 37 38 39 40 41 42 |
# File 'lib/rubino/oauth/token_encryptor.rb', line 34 def self.from_env raw = ENV.fetch("RUBINO_ENCRYPTION_KEY", nil) raise KeyMissingError, "RUBINO_ENCRYPTION_KEY not set" if raw.nil? || raw.empty? key = Base64.strict_decode64(raw) raise KeyMissingError, "RUBINO_ENCRYPTION_KEY must decode to 32 bytes" unless key.bytesize == 32 new(key) end |
Instance Method Details
#decrypt(payload) ⇒ String?
Returns the original plaintext, or nil when payload is nil.
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/rubino/oauth/token_encryptor.rb', line 67 def decrypt(payload) return nil if payload.nil? bytes = Base64.strict_decode64(payload) raise InvalidCiphertextError, "payload too short" if bytes.bytesize <= IV_LEN + TAG_LEN iv = bytes.byteslice(0, IV_LEN) tag = bytes.byteslice(-TAG_LEN, TAG_LEN) ciphertext = bytes.byteslice(IV_LEN, bytes.bytesize - IV_LEN - TAG_LEN) cipher = OpenSSL::Cipher.new(CIPHER).decrypt cipher.key = @key cipher.iv = iv cipher.auth_tag = tag cipher.update(ciphertext) + cipher.final rescue OpenSSL::Cipher::CipherError => e raise InvalidCiphertextError, "decryption failed: #{e.}" end |
#encrypt(plaintext) ⇒ String?
Returns Base64(IV || ciphertext || tag), or nil when plaintext is nil (so nullable token columns round-trip unchanged).
53 54 55 56 57 58 59 60 61 |
# File 'lib/rubino/oauth/token_encryptor.rb', line 53 def encrypt(plaintext) return nil if plaintext.nil? cipher = OpenSSL::Cipher.new(CIPHER).encrypt cipher.key = @key iv = cipher.random_iv ciphertext = cipher.update(plaintext.to_s) + cipher.final Base64.strict_encode64(iv + ciphertext + cipher.auth_tag) end |