12/08/2018, 17:47

Cách đơn giản để mã hóa thuộc tính của model trong Rails

Giới thiệu Khi lưu trữ project trên các public repo việc để các dữ liệu ở plain text thì mình thấy không an tâm lắm, nên mình sẽ tìm cách mã hóa chúng. Trong bài viết này, mình xin giới thiệu cách sử dụng module Cipher của thư viện openssl được cung cấp mặc định trong Ruby để mã hóa các dữ liệu ...

Giới thiệu

Khi lưu trữ project trên các public repo việc để các dữ liệu ở plain text thì mình thấy không an tâm lắm, nên mình sẽ tìm cách mã hóa chúng. Trong bài viết này, mình xin giới thiệu cách sử dụng module Cipher của thư viện openssl được cung cấp mặc định trong Ruby để mã hóa các dữ liệu khi lưu xuống database.

Cách cài đặt

Ở đây mình sử dụng module Cipher của thư viện OpenSSL cung cấp mặc định của Ruby nên cũng không cần cài đặt gì thêm

Cách sử dụng

Ví dụ mình có một model Staff gồm các thông tin name, email, staff_code.

create_table :staffs do |t|
      t.string :email,
      t.string :name,
      t.staff :code
end

Đây là dữ liệu nhạy cảm nên mình muốn mã hóa chúng trước khi lưu xuống csdl. Đầu tiên tạo file lib/crypt.rb như sau:

module Crypt
  class << self
    ALGO = "aes-128-cbc"

    def encrypt value
      crypt :encrypt, value
    end

    def decrypt value
      crypt :decrypt, value
    end

    def encryption_key
      ENV["ENCRYPTION_KEY"].to_s
    end

    def crypt cipher_method, value
      cipher = OpenSSL::Cipher.new ALGO
      cipher.send cipher_method
      cipher.pkcs5_keyivgen encryption_key
      result = cipher.update value
      result << cipher.final
    end
  end
end

Ở đây file crypt.rb là file dùng để mã hóa và giải mã. Cụ thể như sau:

  1. ALGO = "aes-128-cbc" : Khai báo constain kểu của mã hóa aes (Advanced Encryption Standard), độ dài 128 bit, AES thường hoạt động ở bốn chế độ cơ bản của mã khối n-bit (ECB, CBC, CFB và OFB) đặc tả bởi tiêu chuẩn ISO/IEC 10116:1997 Information technology– Security techniques – Modes of operation for an n-bit cipher (Công nghệ thông tin- kỹ thuật an toàn- chế độ hoạt động của mã hóa nbit).
  2. encrypt(value) là phương thức mã hóa một plain text truyền vào và trả kết quả là một chuỗi đã được mã hóa(Cipher text).
  3. decrypt(value) là phương thức giải mã một chuỗi truyền vào thành plain text.
  4. encryption_key la phương thức lấy key dùng để mã hóa cũng như giải mã. Để lấy được key này chúng ta cần phải khai báo một environtment variable ENCRYPTION_KEY
  5. crypt(cipher_method, value) đây là phương thức chính dùng để mã hóa cũng như giải mã.

Vậy là chúng ta đã có một module để mã hóa và giải mã.

Công việc tiếp theo là tạo file lib/encrypted_coder.rb như sau:

class EncryptedCoder
  def load value
    return if value.blank?
    Marshal.load Crypt.decrypt(Base64.decode64(value))
  end

  def dump value
    return if value.blank?
    Base64.encode64 Crypt.encrypt(Marshal.dump(value))
  end
end

Ở đây có 2 phương thức là load và dump mình ý nghĩa tên phương thức ở phần dưới.

  1. load dùng để lấy kết quả từ một chuỗi đã được mã hóa.
  2. 'dump' dủng để mã hóa một chuỗi đầu vào. Để tăng tính bảo mật thì trước khi mã hóa chuỗi đầu vào, chúng ta dùng Marshal.dump(value) tức là dùng module Marshal của ruby để đưa 1 object thành luồng byte. Và Base64.encode64 Crypt.encrypt(Marshal.dump(value)) chuyển thành base64 chuỗi đã được mã hóa. Khi muốn giải mã thì chúng ta chỉ cần làm ngược lại theo trình tự đã làm.

Công việc tiếp theo là đến model Staff.

class Staff < ApplicationRecord
 serialize :name, EncryptedCoder.new
 serialize :email, EncryptedCoder.new
 serialize :staff_code, EncryptedCoder.new
end

Trong class Staff chúng ta chỉ cần khai báo serialize cho các thuộc tính cần được mã hóa là name, email và staff_code. Đến đây mình xin giải thích vì sao phải dùng phương thức load và dump trong file encrypted_coder.rb. Để dùng đượng serialize thì phải có phương thức load và dump để đọc và lưu dữ liệu vào database, nếu không có thì không dùng được.

Kết quả

staff = Staff.new name: "Nguyen Van A", staff_code: "123456", email: "nguyen.van.a@gmail.com"
=> #<Staff id: nil, email: "nguyen.van.a@gmail.com", staff_code: "123456", name: "Nguyen Van A">
staff.save
=> true
Staff.find_by email: "nguyen.van.a@gmail.com"
Staff Load (0.4ms)  SELECT  `staffs`.* FROM `staffs` WHERE `staffs`.`email` = '5h1uBwsCxoQz4G8zqb06sI69YgVyrAzQv5YMPHZaI546uj24DkW4Bi8Pp0F1
aF6O
' LIMIT 1
=> #<Staff id: 94, email: "nguyen.van.a@gmail.com", staff_code: "123456", name: "Nguyen Van A" >

Đây là kết quả sau khi lưu vào database SELECT name, email, staff_code FROM staffs;

Ưu điểm

Cách này đơn giản, dễ hiểu, dễ thực hiện. Có thể search được object

Nhược điểm

Có thể không chính xác với các trường ngày tháng. Cần phải chỉnh sửa lại.

Cám ơn các bạn đã đọc bài viết của mình. Chúc các bạn làm việc thật tốt.

0