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 crypt gem (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_for is tracked separately.

Class Method Summary collapse

Class Method Details

.aes_decrypt(text, key) ⇒ Object



93
94
95
96
97
# File 'lib/encryption.rb', line 93

def self.aes_decrypt(text, key)
  aes(:decrypt, text, key)
rescue OpenSSL::Cipher::CipherError
  ''
end

.aes_encrypt(text, key) ⇒ Object



89
90
91
# File 'lib/encryption.rb', line 89

def self.aes_encrypt(text, key)
  aes(:encrypt, text, key)
end

.blowfish_decrypt(text, key) ⇒ Object



66
67
68
69
70
71
# File 'lib/encryption.rb', line 66

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) ⇒ Object



61
62
63
64
# File 'lib/encryption.rb', line 61

def self.blowfish_encrypt(text, key)
  cipher = blowfish_cipher(:encrypt, key)
  cipher.update(text) + cipher.final
end

.decrypt_string(string) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/encryption.rb', line 46

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) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/encryption.rb', line 33

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) ⇒ Object



85
86
87
# File 'lib/encryption.rb', line 85

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) ⇒ Object



81
82
83
# File 'lib/encryption.rb', line 81

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) ⇒ Object



77
78
79
# File 'lib/encryption.rb', line 77

def self.url_decrypt_string(encrypted_string)
  aes_decrypt([encrypted_string].pack('H*'), REST_AUTH_SITE_KEY[0..31])
end

.url_encrypt_string(string) ⇒ Object



73
74
75
# File 'lib/encryption.rb', line 73

def self.url_encrypt_string(string)
  aes_encrypt(string.to_s, REST_AUTH_SITE_KEY[0..31]).unpack1('H*')
end