## # Secure Column 提供定义加密列的方法。 module Unidom::Common::Concerns::SecureColumn extend ActiveSupport::Concern include Unidom::Common::Concerns::ExactColumn include Unidom::Common::Concerns::Aes256Cryptor included do |includer| cattr_accessor :secure_columns ## # 对指定的安全列进行加密操作。如: # do_encrypt_secure_column :secure_identity_card def do_encrypt_secure_column(name) name = name.to_s content = { 'nonce' => SecureRandom.hex(8), 'timestamp' => Time.now.to_i } secure_columns[name].each do |field| content[field.to_s] = send(field) end content = content.sort.to_h.to_json aes_key = Digest::SHA512::digest self.class.exact_signature(self.class, name, '') encoded = hex_encrypt content, key: aes_key json = { encoded: encoded, signature: Unidom::Common::Numeration.hex(self.class.exact_signature self.class, name, content), encryption_algorithm: self.class.encryption_algorithm } send "#{name}=", json end ## # 对指定的安全列进行解密操作。如: # do_encrypt_secure_column :secure_identity_card def do_decrypt_secure_column(name) name = name.to_sym return unless respond_to? name json = send(name) return if json.blank?||json['encoded'].blank?||json['signature'].blank?||json['encryption_algorithm'].blank? return if self.class.encryption_algorithm!=json['encryption_algorithm'] aes_key = Digest::SHA512::digest self.class.exact_signature(self.class, name, '') content = decrypt Unidom::Common::Numeration.rev_hex(json['encoded']), key: aes_key actual_signature = self.class.exact_signature(self.class, name, content) return if Unidom::Common::Numeration.rev_hex(json['signature'])!=actual_signature parsed = JSON.parse content parsed.each do |key, value| send "#{key}=", value unless [ 'nonce', 'timestamp' ].include? key end end end module ClassMethods ## # 配置加密列。如: # class YourModel < ApplicationRecord # attr_accessor :identity_card_name, :identity_card_address, :passport_name, :passport_address # include Unidom::Common::Concerns::SecureColumn # secure_column :secure_identity_card, fields: [ :identity_card_name, :identity_card_address ] # secure_column :secure_passport, fields: [ :passport_name, :passport_address ] # end # # model = YourModel.create! identity_card_name: '张三', identity_card_address: '地址1', # passport_name: '李四', passport_address: '地址2' # #identity_card_name 和 #identity_card_address 会被加密后保存到 #secure_identity_card 字段中。 # #passport_name 和 #passport_address 会被加密后保存到 #secure_passport 字段中。 # model_2 = YourModel.find model.id # model_2.identity_card_name # 张三 # #secure_identity_card 中存储的加密信息会被自动解密,并存储到 #identity_card_name 和 #identity_card_address 中。 def secure_column(name, fields: []) name = name.to_s self.secure_columns = self.secure_columns||{} if secure_columns[name].present? raise ArgumentError.new("The #{name} column was defined as a secure column already.") else secure_columns[name] = fields end fields.each do |field| attr_accessor field.to_sym if columns_hash[field.to_s].blank? end instance_eval do before_save do do_encrypt_secure_column name end after_find do do_decrypt_secure_column name.to_sym end end end end end