Hướng dẫn dùng salting security trong PHP

Tìm hiểu khi sử dụng các hàm băm tạo dữ liệu lưu trữ password

Khi lưu trữ password vào CSDL thường sẽ sử dụng các hàm băm khác nhau được hỗ trợ bởi hệ CSDL hoặc ngôn ngữ lập trình (như MD5, SHA1 ...) để tạo dữ liệu mã hóa, dữ liệu mã hóa đó được lưu vào CSDL. Ví dụ:

$raw_password = 'abc123';
$crypt = md5($raw_password); //e99a18c428cb38d5f260853678922e03

Ví dụ trên, đã sử dụng hàm băm của PHP là md5 để mã hóa password abc123, kết quả mã hóa là e99a18c428cb38d5f260853678922e03

Bởi vì hàm băm tạo ra các giá trị không thể dịch ngược (không có thuật toán để giải giá trị hash e99a18c428cb38d5f260853678922e03 là chuỗi abc123, chỉ duy nhất một cách là thử), nên có cảm giác sẽ an toàn. Tuy nhiên với các mật khẩu yếu, nó có thể bị dò ra dựa trên giá trị băm của các mật khẩu phổ biến biết trước. Như trường hợp trên khi thấy e99a18c428cb38d5f260853678922e03 thì đoán được password là abc123. Để khắc phục điều này có thể sử dụng đến salt

Sử dụng Salt tăng cường an toàn cho mật khẩu

Để phức tạp hóa mật khẩu lưu trữ, thì các mật khẩu gốc trước khi mã hóa được nối thêm các chuỗi, các chuỗi thêm này gọi là salt

Ví dụ:

$raw_password = 'abc123';

//Sinh ra chuỗi dài 32 ngẫu nhiên, cũng cần lưu chuỗi này vào một cột trong DB
$salt = random_bytes(32);

//Sử dụng thêm một salt cố định
$staticSalt = 'G4334#';


$crypt = md5($staticSalt.$raw_password.$salt);

Giờ mật khẩu lưu trữ ở trên phức tạp hơn rất nhiều. Biết được $crypt đoán ra $raw_password là rất khó, kể cả khi là password yếu. Khó mà xây dựng được một từ điển chứa các mã hóa tương ứng với password.

Trước khi bạn làm bất cứ điều gì thêm, hãy tìm hiểu sự khác biệt giữa mã hóaxác thực , và tại sao bạn có thể muốn mã hóa được xác thực hơn là chỉ mã hóa .

Để thực hiện mã hóa đã xác thực, bạn muốn Mã hóa rồi MAC. Thứ tự mã hóa và xác thực là rất quan trọng! Một trong những câu trả lời hiện có cho câu hỏi này đã mắc lỗi này; cũng như nhiều thư viện mật mã được viết bằng PHP.

Bạn nên tránh triển khai mật mã của riêng mình và thay vào đó hãy sử dụng thư viện an toàn được viết bởi các chuyên gia mật mã.

Cập nhật: PHP 7.2 hiện cung cấp libsodium ! Để bảo mật tốt nhất, hãy cập nhật hệ thống của bạn để sử dụng PHP 7.2 trở lên và chỉ làm theo lời khuyên libsodium trong câu trả lời này.

Sử dụng libsodium nếu bạn có quyền truy cập PECL (hoặc sodium_compat nếu bạn muốn libsodium mà không có PECL); nếu không thì ...
Sử dụng mã hóa defuse / php ; không cuộn mật mã của riêng bạn!

Cả hai thư viện được liên kết ở trên giúp bạn dễ dàng triển khai mã hóa đã xác thực vào thư viện của riêng mình.

Nếu bạn vẫn muốn viết và triển khai thư viện mật mã của riêng mình, chống lại sự khôn ngoan thông thường của mọi chuyên gia mật mã trên Internet, đây là các bước bạn sẽ phải thực hiện.

Mã hóa:

  1. Mã hóa bằng AES ở chế độ CTR. Bạn cũng có thể sử dụng GCM (loại bỏ sự cần thiết phải có MAC riêng biệt). Ngoài ra, ChaCha20 và Salsa20 (do libsodium cung cấp ) là mật mã luồng và không cần các chế độ đặc biệt.
  2. Trừ khi bạn chọn GCM ở trên, bạn nên xác thực bản mã bằng HMAC-SHA-256 (hoặc, đối với mật mã luồng, Poly1305 - hầu hết các API libsodium đều làm việc này cho bạn). MAC nên bao phủ IV cũng như bản mã!

Giải mã:

  1. Trừ khi sử dụng Poly1305 hoặc GCM, hãy tính toán lại MAC của bản mã và so sánh nó với MAC đã được gửi bằng cách sử dụng hash_equals(). Nếu nó không thành công, hãy hủy bỏ.
  2. Giải mã tin nhắn.

Cân nhắc thiết kế khác:

  1. Không nén bất cứ điều gì bao giờ. Ciphertext không thể nén được; nén bản rõ trước khi mã hóa có thể dẫn đến rò rỉ thông tin (ví dụ: CRIME và BREACH trên TLS).
  2. Đảm bảo rằng bạn sử dụng mb_strlen()mb_substr()sử dụng '8bit'chế độ đặt ký tự để ngăn chặn sự mbstring.func_overloadcố.
  3. IV phải được tạo bằng CSPRNG ; Nếu bạn đang sử dụng mcrypt_create_iv(), KHÔNG SỬ DỤNGMCRYPT_RAND !
    • Cũng kiểm tra random_compat .
  4. Trừ khi bạn đang sử dụng cấu trúc AEAD, LUÔN LUÔN mã hóa MAC!
  5. bin2hex(), base64_encode()v.v. có thể làm rò rỉ thông tin về các khóa mã hóa của bạn thông qua định thời bộ đệm. Tránh chúng nếu có thể.

Ngay cả khi bạn làm theo lời khuyên được đưa ra ở đây, rất nhiều điều có thể xảy ra với tiền mã hóa. Luôn có chuyên gia mật mã xem xét việc triển khai của bạn. Nếu bạn không đủ may mắn để làm bạn với một sinh viên mật mã tại trường đại học địa phương của bạn, bạn luôn có thể thử diễn đàn Cryptography Stack Exchange để được tư vấn.

Nếu bạn cần một phân tích chuyên nghiệp về việc triển khai của mình, bạn luôn có thể thuê một nhóm tư vấn bảo mật có uy tín để xem xét mã mật mã PHP của bạn (tiết lộ: công ty của tôi).

Quan trọng: Khi nào không sử dụng mã hóa

Không mã hóa mật khẩu . Thay vào đó,bạn muốn băm chúng bằng cách sử dụng một trong các thuật toán băm mật khẩu sau:

  • Argon2
  • mã hóa
  • bcrypt
  • PBKDF2-SHA256 với 86.000 lần lặp

Không bao giờ sử dụng hàm băm có mục đích chung (MD5, SHA256) để lưu trữ mật khẩu.

Không mã hóa Tham số URL . Đó là công cụ sai cho công việc.

Ví dụ về mã hóa chuỗi PHP với Libsodium

Nếu bạn đang sử dụng PHP <7.2 hoặc không cài đặt libsodium, bạn có thể sử dụng sodium_compat để đạt được kết quả tương tự (mặc dù chậm hơn).

Sau đó, để kiểm tra nó ra:

Halite - Libsodium được tạo ra dễ dàng hơn

Một trong những dự án mà tôi đang thực hiện là một thư viện mã hóa có tên là Halite , nhằm mục đích làm cho libsodium trở nên dễ dàng và trực quan hơn.

Tất cả các mật mã cơ bản được xử lý bởi libsodium.

Ví dụ với mã hóa defuse / php

Lưu ý : Crypto::encrypt()trả về đầu ra được mã hóa hex.

Quản lý khóa mã hóa

Nếu bạn muốn sử dụng "mật khẩu", hãy dừng lại ngay bây giờ. Bạn cần một khóa mã hóa 128 bit ngẫu nhiên, không phải một mật khẩu dễ nhớ của con người.

Bạn có thể lưu trữ một khóa mã hóa để sử dụng lâu dài như sau:

$storeMe = bin2hex($key);

Và, theo yêu cầu, bạn có thể truy xuất nó như sau:

$key = hex2bin($storeMe);

Tôi mạnh mẽ khuyên bạn nên chỉ lưu trữ một chìa khóa được tạo ra một cách ngẫu nhiên để sử dụng lâu dài thay vì bất kỳ loại mật khẩu là chìa khóa (hoặc để lấy được chìa khóa).

Nếu bạn đang sử dụng thư viện của Defuse:

  • $string = $keyObject->saveToAsciiSafeString()
  • $loaded = Key::loadFromAsciiSafeString($string);

"Nhưng tôi thực sự muốn sử dụng mật khẩu."

Đó là một ý tưởng tồi, nhưng không sao, đây là cách thực hiện điều đó một cách an toàn.

Đầu tiên, tạo một khóa ngẫu nhiên và lưu trữ nó trong một hằng số.

/**
 * Replace this with your own salt! 
 * Use bin2hex() then add \x before every 2 hex characters, like so:
 */
define('MY_PBKDF2_SALT', "\x2d\xb7\x68\x1a\x28\x15\xbe\x06\x33\xa0\x7e\x0e\x8f\x79\xd5\xdf");

Lưu ý rằng bạn đang làm thêm công việc và chỉ có thể sử dụng hằng số này làm chìa khóa và giúp bản thân đỡ đau lòng hơn!

Sau đó, sử dụng PBKDF2 (giống như vậy) để lấy khóa mã hóa phù hợp từ mật khẩu của bạn thay vì mã hóa trực tiếp bằng mật khẩu của bạn.

/**
 * Get an AES key from a static password and a secret salt
 * 
 * @param string $password Your weak password here
 * @param int $keysize Number of bytes in encryption key
 */
function getKeyFromPassword($password, $keysize = 16)
{
    return hash_pbkdf2(
        'sha256',
        $password,
        MY_PBKDF2_SALT,
        100000, // Number of iterations
        $keysize,
        true
    );
}

Đừng chỉ sử dụng mật khẩu 16 ký tự. Khóa mã hóa của bạn sẽ bị hỏng một cách hài hước.

445 hữu ích 5 bình luận chia sẻ