Hướng dẫn dùng session_set_cookie_params trong PHP

Xin chào các bạn, ngày hôm nay mình xin chia sẻ cách tạo ra session bảo mật bằng cách sử dụng PHP kết hợp cùng MySQL encrypted cực mạnh bằng cách thức: 256-bit AES encryption.

Đây là một class cần thiết cho tất cả những ai đang phải đối mặt với dự án nhiều “subdomain” như mạng xã hội chẳng hạn, mỗi một sub sẽ tạo ra một session khác nhau nên sẽ làm cho bạn lúng túng mỗi khi muốn kiểm tra “chìa khóa” của người dùng xem có khớp không và có cùng session không, có nhiều cách khác nhau để giải bài toán này nhưng tôi xin chia sẻ cách mà tôi nghĩ là “tốt nhất” cho các bạn.

Bước 1: Tạo bảng:

CREATE DATABASE `secure_sessions` ;

Tôi lấy ví dụ là mình sẽ tạo ra bảng secure_sessions

Bước 2: Tạo một người dùng mới có quyền SELECT, INSERT DELETE

  • User: “sec_user”
  • Password: “eKcGZr59zAa2BEWU”

( đừng dùng mật khẩu ví dụ )

CREATE USER 'sec_user'@'localhost' IDENTIFIED BY 'eKcGZr59zAa2BEWU';
GRANT SELECT, INSERT, UPDATE, DELETE ON `secure_sessions`.* TO 'sec_user'@'localhost';

* Xin lưu ý với các bạn là nếu host của các bạn không cho phép thêm người dùng thì … bó tay nhé !

Bước 3: Tạo một bảng có tên là sessions với các trường (id, set_time, data, session_key).

CREATE TABLE `sessions` (
  `id` CHAR(128) NOT NULL,
  `set_time` CHAR(10) NOT NULL,
  `data` text NOT NULL,
  `session_key` CHAR(128) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CHARSET bạn có thể đặt latin1 cho tiết kiệm bộ nhớ, hơn là utf8-general-ci, sử dụng kiểu dữ liệu CHAR() nhằm tiết kiệm bộ nhớ cho cơ sở dữ liệu đấy 😛

Bước 4: Tạo file session.class.php

Bạn hãy tạo một class tên là session, cách tạo class thì các bạn search trên mạng nhé, à quên, nếu không biết tạo class thì bạn nên dừng lại ở đây và chuyển sang đọc bài khác cho tiết kiệm thời gian.

function __construct() {
   // set our custom session functions.
   session_set_save_handler(array($this, 'open'), array($this, 'close'), array($this, 'read'), array($this, 'write'), array($this, 'destroy'), array($this, 'gc'));

   // This line prevents unexpected effects when using objects as save handlers.
   register_shutdown_function('session_write_close');
}

Khởi động class bằng __construct() như trên.

Tiếp theo, tạo function session_start() trong class này:

function start_session($session_name, $secure) {
   // Make sure the session cookie is not accessable via javascript.
   $httponly = true;

   // Hash algorithm to use for the sessionid. (use hash_algos() to get a list of available hashes.)
   $session_hash = 'sha512';

   // Check if hash is available
   if (in_array($session_hash, hash_algos())) {
      // Set the has function.
      ini_set('session.hash_function', $session_hash);
   }
   // How many bits per character of the hash.
   // The possible values are '4' (0-9, a-f), '5' (0-9, a-v), and '6' (0-9, a-z, A-Z, "-", ",").
   ini_set('session.hash_bits_per_character', 5);

   // Force the session to only use cookies, not URL variables.
   ini_set('session.use_only_cookies', 1);

   // Get session cookie parameters 
   $cookieParams = session_get_cookie_params(); 
   // Set the parameters
   session_set_cookie_params($cookieParams["lifetime"], $cookieParams["path"], $cookieParams["domain"], $secure, $httponly); 
   // Change the session name 
   session_name($session_name);
   // Now we cat start the session
   session_start();
   // This line regenerates the session and delete the old one. 
   // It also generates a new encryption key in the database. 
   session_regenerate_id(true); 
}

Tiếp theo: tạo function Open:

function open() {
   $host = 'localhost';
   $user = 'sec_user';
   $pass = 'eKcGZr59zAa2BEWU';
   $name = 'secure_sessions';
   $mysqli = new mysqli($host, $user, $pass, $name);
   $this->db = $mysqli;
   return true;
}

Sau đó phải có đóng lại chứ:

function close() {
   $this->db->close();
   return true;
}

Tiếp theo, ta tạo function read, hàm này sẽ được thực thi bởi php khi ta gọi $_SESSION['something'];

function read($id) {
   if(!isset($this->read_stmt)) {
      $this->read_stmt = $this->db->prepare("SELECT data FROM sessions WHERE id = ? LIMIT 1");
   }
   $this->read_stmt->bind_param('s', $id);
   $this->read_stmt->execute();
   $this->read_stmt->store_result();
   $this->read_stmt->bind_result($data);
   $this->read_stmt->fetch();
   $key = $this->getkey($id);
   $data = $this->decrypt($data, $key);
   return $data;
}

a* Lưu ý: hàm này thực thi nhiều lần, sẽ là nhiều lần đấy, nên ngoài nghĩ tới tối ưu ra ta còn phải nghĩ tới tốc độ tải của server, và trong hàm trên ta đang giải nén key từ cơ sở dữ liệu sử dụng mã hóa 256-bit AES encryption.

Tiếp theo ta tại function write:

function write($id, $data) {
   // Get unique key
   $key = $this->getkey($id);
   // Encrypt the data
   $data = $this->encrypt($data, $key);

   $time = time();
   if(!isset($this->w_stmt)) {
      $this->w_stmt = $this->db->prepare("REPLACE INTO sessions (id, set_time, data, session_key) VALUES (?, ?, ?, ?)");
   }

   $this->w_stmt->bind_param('siss', $id, $time, $data, $key);
   $this->w_stmt->execute();
   return true;
}

* Lưu ý: Hàm này được gọi khi chúng ta gán một giá trị vào Session, ví dụ như : $_SESSION['something'] = 'something else'; Hàm này sẽ mã hóa toàn bộ giá trị và ghi vào cơ sở dữ liệu.

Tiếp theo là hàm __destroy()

function destroy($id) {
   if(!isset($this->delete_stmt)) {
      $this->delete_stmt = $this->db->prepare("DELETE FROM sessions WHERE id = ?");
   }
   $this->delete_stmt->bind_param('s', $id);
   $this->delete_stmt->execute();
   return true;
}

* Hàm này sẽ xóa toàn bộ dữ liệu mã hóa của session khi chúng ta gọi session__destroy(); trong PHP.

Bước tiếp theo ta tạo hàm gc (garbage collector);

function gc($max) {
   if(!isset($this->gc_stmt)) {
      $this->gc_stmt = $this->db->prepare("DELETE FROM sessions WHERE set_time < ?");
   }
   $old = time() - $max;
   $this->gc_stmt->bind_param('s', $old);
   $this->gc_stmt->execute();
   return true;
}

* Lưu ý: Hàm này là “garbage collecter” và được gọi để xóa các session cũ trong cơ sở dữ liệu, tần xuất được gọi tới của hàm này được quyết định bởi cấu hình của session.gc_probabilitysession.gc_divisor nhé !

Tiếp theo, ta tạo hàm getkey():

private function getkey($id) {
   if(!isset($this->key_stmt)) {
      $this->key_stmt = $this->db->prepare("SELECT session_key FROM sessions WHERE id = ? LIMIT 1");
   }
   $this->key_stmt->bind_param('s', $id);
   $this->key_stmt->execute();
   $this->key_stmt->store_result();
   if($this->key_stmt->num_rows == 1) { 
      $this->key_stmt->bind_result($key);
      $this->key_stmt->fetch();
      return $key;
   } else {
      $random_key = hash('sha512', uniqid(mt_rand(1, mt_getrandmax()), true));
      return $random_key;
   }
}

* Hàm này dùng để lấy một key duy nhất để mã hóa theo phương thức mã hóa trên, được lấy từ bảng session. Nếu không có thì nó tự tạo ra một key hoàn toàn mới theo dạng “random” …

Bước tiếp theo là ta sẽ tạo ra hai hàm để mã hóa và giải mã theo cách thức mã hóa mà chúng ta chọn:

private function encrypt($data, $key) {
   $salt = 'cH!swe!retReGu7W6bEDRup7usuDUh9THeD2CHeGE*ewr4n39=E@rAsp7c-Ph@pH';
   $key = substr(hash('sha256', $salt.$key.$salt), 0, 32);
   $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
   $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
   $encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_ECB, $iv));
   return $encrypted;
}
private function decrypt($data, $key) {
   $salt = 'cH!swe!retReGu7W6bEDRup7usuDUh9THeD2CHeGE*ewr4n39=E@rAsp7c-Ph@pH';
   $key = substr(hash('sha256', $salt.$key.$salt), 0, 32);
   $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
   $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
   $decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, base64_decode($data), MCRYPT_MODE_ECB, $iv);
   return $decrypted;
}

* Hai hàm này mã hóa dữ liệu của bảng session, sử dụng key mã hóa từ cơ sở dữ liệu và mỗi session đều có giá trị riêng của nó. Vậy là chúng ta không những sử dụng key đã được mã hóa mà còn làm cho key này bảo mật hơn-cả-random 🙂

Bước 5: Hoàn thành class, gọi trong bất kỳ một file PHP nào:

require('session.class.php');
$session = new session();
// Set to true if using https
$session->start_session('_s', false);

$_SESSION['something'] = 'A value.';
echo $_SESSION['something'];

* Trên là cách gọi session hoặc gán session vào một giá trị.

Chúc mừng các bạn, bây giờ các bạn đã có một bảng session ngang ngửa với của PHP tạo ra, ngoài ra chúng ta còn có thể đếm được số người đang trực tuyến, hoặc class session này sẽ cực kỳ hữu dụng cho những ai tạo ra các dự án có nhiều subdomain kiểu như mạng xã hội chẳng hạn, vì đặc điểm của PHP là tạo ra session mới cho mỗi subdomain nên cần phải sử dụng class này để giải quyết triệt để vấn đề.

Chúc các bạn thành công !