Module: Encryption
- Defined in:
- lib/encryption.rb
Overview
Two-way encryption helpers for short URL tokens (order/quote/locator IDs).
Two formats produced by encrypt_string:
- length(plain) <= 8 → Blowfish ECB on a single 8-byte block.
Hex-encoded. Output: 16 hex chars. - length(plain) > 8 → AES-256-CBC, hex-encoded (
url_encrypt_string).
decrypt_string recognises three on-the-wire shapes:
- length 16 → Blowfish path
- length 17–N → AES path (
url_decrypt_string); falls back to
legacy_decrypt_string(Base64+CGI-escape from
an even older format).
Blowfish notes
- This module previously depended on the
cryptgem (Crypt::Blowfish,
last released in 2007). It now uses Ruby's stdlib OpenSSL bf-ecb
cipher which produces byte-identical ciphertext for the same key +
8-byte plaintext block. Verified end-to-end (encrypt+decrypt and
cross-decrypt) against Crypt::Blowfish output before the switch. - OpenSSL 3.x moved Blowfish to the "legacy" provider; we load it on
demand at the call site so this works on both OpenSSL 1.1.x (no
legacy concept) and OpenSSL 3.x (legacy provider required). - Blowfish has a 64-bit block size and is considered legacy. We keep
it only for backward-compatibility with URL tokens already in
circulation (links inside customer emails, support tickets, etc.).
New token issuance for short IDs continues to flow through this
path so that wire format stays stable; a future migration to
signed_id/generates_token_foris tracked separately.
Class Method Summary collapse
-
.aes_decrypt(text, key) ⇒ String
The decrypted text ("" on cipher error).
-
.aes_encrypt(text, key) ⇒ String
The AES ciphertext.
-
.blowfish_decrypt(text, key) ⇒ String
The decrypted text ("" on cipher error).
-
.blowfish_encrypt(text, key) ⇒ String
The Blowfish ciphertext.
-
.decrypt_string(string) ⇒ String?
The decrypted string, or nil when undecryptable.
-
.encrypt_string(string) ⇒ String
The encrypted string, hex-encoded.
-
.legacy_decrypt_string(encrypted_string) ⇒ String
The decrypted string.
-
.legacy_encrypt_string(string) ⇒ String
The Base64+CGI-escaped encrypted string.
-
.url_decrypt_string(encrypted_string) ⇒ String
The decrypted string.
-
.url_encrypt_string(string) ⇒ String
The AES-encrypted string, hex-encoded.
Class Method Details
.aes_decrypt(text, key) ⇒ String
Returns the decrypted text ("" on cipher error).
103 104 105 106 107 |
# File 'lib/encryption.rb', line 103 def self.aes_decrypt(text, key) aes(:decrypt, text, key) rescue OpenSSL::Cipher::CipherError '' end |
.aes_encrypt(text, key) ⇒ String
Returns the AES ciphertext.
98 99 100 |
# File 'lib/encryption.rb', line 98 def self.aes_encrypt(text, key) aes(:encrypt, text, key) end |
.blowfish_decrypt(text, key) ⇒ String
Returns the decrypted text ("" on cipher error).
70 71 72 73 74 75 |
# File 'lib/encryption.rb', line 70 def self.blowfish_decrypt(text, key) cipher = blowfish_cipher(:decrypt, key) cipher.update(text) + cipher.final rescue OpenSSL::Cipher::CipherError '' end |
.blowfish_encrypt(text, key) ⇒ String
Returns the Blowfish ciphertext.
64 65 66 67 |
# File 'lib/encryption.rb', line 64 def self.blowfish_encrypt(text, key) cipher = blowfish_cipher(:encrypt, key) cipher.update(text) + cipher.final end |
.decrypt_string(string) ⇒ String?
Returns the decrypted string, or nil when undecryptable.
48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/encryption.rb', line 48 def self.decrypt_string(string) str = string.to_s res = nil if str.length > 16 # AES path; fall through to the very old Base64+CGI format if needed. res = url_decrypt_string(string) res = legacy_decrypt_string(string) if res.blank? elsif str.length == 16 enc_str = str.each_line.to_a.pack('H*') padded_string = blowfish_decrypt(enc_str, REST_AUTH_SITE_KEY) res = padded_string.delete("\000") end res end |
.encrypt_string(string) ⇒ String
Returns the encrypted string, hex-encoded.
34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/encryption.rb', line 34 def self.encrypt_string(string) if string.to_s.length > 8 url_encrypt_string(string) else # Pad to an exact 8-byte block with NUL. len = string.length mod = len == 8 ? 0 : 8 - (len % 8) padded_string = string.ljust(len + mod, "\0") enc_str = blowfish_encrypt(padded_string, REST_AUTH_SITE_KEY) enc_str.unpack1('H*') end end |
.legacy_decrypt_string(encrypted_string) ⇒ String
Returns the decrypted string.
93 94 95 |
# File 'lib/encryption.rb', line 93 def self.legacy_decrypt_string(encrypted_string) aes_decrypt(Base64.decode64(CGI.unescape(encrypted_string)), REST_AUTH_SITE_KEY[0..31]) end |
.legacy_encrypt_string(string) ⇒ String
Returns the Base64+CGI-escaped encrypted string.
88 89 90 |
# File 'lib/encryption.rb', line 88 def self.legacy_encrypt_string(string) CGI.escape(Base64.encode64(aes_encrypt(string.to_s, REST_AUTH_SITE_KEY[0..31]))) end |
.url_decrypt_string(encrypted_string) ⇒ String
Returns the decrypted string.
83 84 85 |
# File 'lib/encryption.rb', line 83 def self.url_decrypt_string(encrypted_string) aes_decrypt([encrypted_string].pack('H*'), REST_AUTH_SITE_KEY[0..31]) end |
.url_encrypt_string(string) ⇒ String
Returns the AES-encrypted string, hex-encoded.
78 79 80 |
# File 'lib/encryption.rb', line 78 def self.url_encrypt_string(string) aes_encrypt(string.to_s, REST_AUTH_SITE_KEY[0..31]).unpack1('H*') end |