# app/models/concerns/atributo_cifrado.rb # # Cifra atributos sensíveis (senha SMTP, token do Twilio) em colunas # `_cifrado` usando ActiveSupport::MessageEncryptor (AES-256-GCM). # # POR QUE NÃO ActiveRecord Encryption (`encrypts :campo`): este projeto não usa # `config/credentials.yml.enc` nem `config/master.key` — tudo vem do .env. Ligar # o AR Encryption exigiria configurar 3 chaves novas e depender da ordem de # execução dos initializers do framework. Aqui a chave é DERIVADA do # secret_key_base, sem nada novo para o operador gerar. # # ⚠️ ROTAÇÃO: se o SECRET_KEY_BASE do servidor for trocado, os valores já # gravados viram ilegíveis. O reader devolve nil (nunca estoura 500), o app # cai no fallback do .env e a tela mostra um aviso pedindo para redigitar. # Para ficar imune a isso, defina NOTIFICACAO_SECRET no .env (qualquer string # longa e fixa) — a derivação passa a usar ela em vez do secret_key_base. module AtributoCifrado extend ActiveSupport::Concern SALT = 'reem-notas/notificacao/v1' class_methods do def atributo_cifrado(*nomes) nomes.each do |nome| coluna = :"#{nome}_cifrado" define_method(nome) { AtributoCifrado.decifrar(self[coluna]) } define_method(:"#{nome}=") do |valor| texto = valor.to_s # Sem isso, salvar a tela sem mexer no campo geraria um criptograma # novo (o IV é aleatório) e o dirty tracking acusaria mudança à toa. next if texto == send(nome) self[coluna] = texto.blank? ? nil : AtributoCifrado.cifrar(texto) end define_method(:"#{nome}?") { send(nome).present? } end end end def self.cifrar(texto) cofre.encrypt_and_sign(texto) end def self.decifrar(blob) return nil if blob.blank? cofre.decrypt_and_verify(blob) rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature => e Rails.logger.error("[AtributoCifrado] valor ilegível (SECRET_KEY_BASE mudou?): #{e.class}") nil end # O default do MessageEncryptor no Rails 7.1 é aes-256-gcm → chave de 32 bytes. def self.cofre @cofre ||= ActiveSupport::MessageEncryptor.new(chave) end def self.chave if (segredo = ENV['NOTIFICACAO_SECRET'].presence) ActiveSupport::KeyGenerator .new(segredo, hash_digest_class: OpenSSL::Digest::SHA256) .generate_key(SALT, 32) else Rails.application.key_generator.generate_key(SALT, 32) end end end