Trước khi bạn làm bất cứ điều gì hơn nữa, hãy tìm cách hiểu sự khác biệt giữa mã hóa và xác thực và lý do tại sao bạn có thể muốn mã hóa được xác thực thay vì chỉ mã hóa .
Để thực hiện mã hóa xác thực, bạn muốn Mã hóa sau đó 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 thực hiện mật mã của riêng mình , và thay vào đó hãy sử dụng một thư viện an toàn được viết và xem xét bởi các chuyên gia về 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 các 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 không có PECL); mặt khác ...
Sử dụng mã hóa defuse / php ; đừ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 việc triển khai mã hóa được xác thực vào thư viện của bạn trở nên dễ dàng và không đau đớn.
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à những bước bạn sẽ phải thực hiện.
Mã hóa:
- Mã hóa bằng AES trong chế độ CTR. Bạn cũng có thể sử dụng GCM (loại bỏ nhu cầu về MAC riêng). Ngoài ra, ChaCha20 và Salsa20 (được cung cấp bởi libsodium ) là mật mã luồng và không cần chế độ đặc biệt.
- 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 làm điều này cho bạn). MAC nên bao gồm IV cũng như bản mã!
Giải mã:
- Trừ khi Poly1305 hoặc GCM được sử dụng, hãy tính 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 thất bại, hãy hủy bỏ.
- Giải mã tin nhắn.
Các cân nhắc thiết kế khác:
- Đừng nén bất cứ điều gì bao giờ. Bản mã 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).
- Đảm bảo bạn sử dụng
mb_strlen()
và mb_substr()
, sử dụng '8bit'
chế độ đặt ký tự để ngăn sự mbstring.func_overload
cố.
- IV nên được tạo bằng CSPRNG ; Nếu bạn đang sử dụng
mcrypt_create_iv()
, KHÔNG SỬ DỤNGMCRYPT_RAND
!
- Trừ khi bạn đang sử dụng cấu trúc AEAD, LUÔN LUÔN mã hóa rồi MAC!
bin2hex()
,, base64_encode()
vv có thể rò rỉ thông tin về các khóa mã hóa của bạn thông qua thời gian 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ể sai với mật mã. Luôn luôn có một chuyên gia mật mã xem xét thực hiện của bạn. Nếu bạn không đủ may mắn để trở thành bạn bè cá nhân với một sinh viên mật mã tại trường đại học địa phương, bạn luôn có thể thử diễn đàn Trao đổi mật mã để đượ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 các chuyên gia 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ộ: chủ nhân 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, sử dụng một trong các thuật toán băm mật khẩu này:
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ụ 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 để thực hiện cùng một kết quả (mặc dù chậm hơn).
<?php
declare(strict_types=1);
/**
* Encrypt a message
*
* @param string $message - message to encrypt
* @param string $key - encryption key
* @return string
* @throws RangeException
*/
function safeEncrypt(string $message, string $key): string
{
if (mb_strlen($key, '8bit') !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
throw new RangeException('Key is not the correct size (must be 32 bytes).');
}
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$cipher = base64_encode(
$nonce.
sodium_crypto_secretbox(
$message,
$nonce,
$key
)
);
sodium_memzero($message);
sodium_memzero($key);
return $cipher;
}
/**
* Decrypt a message
*
* @param string $encrypted - message encrypted with safeEncrypt()
* @param string $key - encryption key
* @return string
* @throws Exception
*/
function safeDecrypt(string $encrypted, string $key): string
{
$decoded = base64_decode($encrypted);
$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
$ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
$plain = sodium_crypto_secretbox_open(
$ciphertext,
$nonce,
$key
);
if (!is_string($plain)) {
throw new Exception('Invalid MAC');
}
sodium_memzero($ciphertext);
sodium_memzero($key);
return $plain;
}
Sau đó, để kiểm tra nó:
<?php
// This refers to the previous code block.
require "safeCrypto.php";
// Do this once then store it somehow:
$key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
$message = 'We are all living in a yellow submarine';
$ciphertext = safeEncrypt($message, $key);
$plaintext = safeDecrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
Halite - Libsodium được thực hiện dễ dàng hơn
Một trong những dự án tôi đang thực hiện là một thư viện mã hóa có tên Halite , nhằm mục đích làm cho libsodium dễ dàng và trực quan hơn.
<?php
use \ParagonIE\Halite\KeyFactory;
use \ParagonIE\Halite\Symmetric\Crypto as SymmetricCrypto;
// Generate a new random symmetric-key encryption key. You're going to want to store this:
$key = new KeyFactory::generateEncryptionKey();
// To save your encryption key:
KeyFactory::save($key, '/path/to/secret.key');
// To load it again:
$loadedkey = KeyFactory::loadEncryptionKey('/path/to/secret.key');
$message = 'We are all living in a yellow submarine';
$ciphertext = SymmetricCrypto::encrypt($message, $key);
$plaintext = SymmetricCrypto::decrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
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
<?php
/**
* This requires https://github.com/defuse/php-encryption
* php composer.phar require defuse/php-encryption
*/
use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;
require "vendor/autoload.php";
// Do this once then store it somehow:
$key = Key::createNewRandomKey();
$message = 'We are all living in a yellow submarine';
$ciphertext = Crypto::encrypt($message, $key);
$plaintext = Crypto::decrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
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 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 khẩu dễ nhớ của con người.
Bạn có thể lưu trữ khóa mã hóa để sử dụng lâu dài như vậy:
$storeMe = bin2hex($key);
Và, theo yêu cầu, bạn có thể truy xuất nó như vậy:
$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:
"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 nó 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 thêm công việc làm thêm và chỉ có thể sử dụng hằng số này làm chìa khóa và tiết kiệm cho mình rất nhiều nỗi đau!
Sau đó, sử dụng PBKDF2 (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.