Quan trọng : Trừ khi bạn có trường hợp sử dụng rất đặc biệt, không mã hóa mật khẩu , thay vào đó hãy sử dụng thuật toán băm mật khẩu. Khi ai đó nói rằng họ mã hóa mật khẩu của họ trong một ứng dụng phía máy chủ, họ sẽ không hiểu rõ hoặc họ đang mô tả một thiết kế hệ thống nguy hiểm. Lưu trữ an toàn mật khẩu là một vấn đề hoàn toàn tách biệt với mã hóa.
Được thông báo. Thiết kế hệ thống an toàn.
Mã hóa dữ liệu di động trong PHP
Nếu bạn đang sử dụng PHP 5.4 trở lên và không muốn tự mình viết mô-đun mật mã, tôi khuyên bạn nên sử dụng thư viện hiện có cung cấp mã hóa được xác thực . Thư viện mà tôi liên kết chỉ dựa trên những gì PHP cung cấp và đang được một số nhà nghiên cứu bảo mật xem xét định kỳ. (Bao gồm cả bản thân tôi.)
Nếu các mục tiêu về tính di động của bạn không ngăn được yêu cầu các phần mở rộng PECL, libsodium được khuyến nghị cao hơn bất cứ thứ gì bạn hoặc tôi có thể viết trong PHP.
Cập nhật (2016-06-12): Bây giờ bạn có thể sử dụng sodium_compat và sử dụng cùng các ưu đãi libsodium của tiền điện tử mà không cần cài đặt các tiện ích mở rộng PECL.
Nếu bạn muốn thử sức mình với kỹ thuật mật mã, hãy đọc tiếp.
Đầu tiên, bạn nên dành thời gian để tìm hiểu sự nguy hiểm của mã hóa không được xác thực và Nguyên tắc Doom Mật mã .
- Dữ liệu được mã hóa vẫn có thể bị giả mạo bởi người dùng độc hại.
- Xác thực dữ liệu được mã hóa ngăn chặn giả mạo.
- Xác thực dữ liệu không được mã hóa không ngăn chặn giả mạo.
Mã hóa và giải mã
Mã hóa trong PHP thực sự đơn giản (chúng tôi sẽ sử dụng openssl_encrypt()
và openssl_decrypt()
một khi bạn đã đưa ra một số quyết định về cách mã hóa thông tin của mình. Tham khảo openssl_get_cipher_methods()
danh sách các phương thức được hỗ trợ trên hệ thống của bạn. Lựa chọn tốt nhất là AES trong chế độ CTR :
aes-128-ctr
aes-192-ctr
aes-256-ctr
Hiện tại không có lý do nào để tin rằng kích thước khóa AES là vấn đề đáng lo ngại (lớn hơn có lẽ không tốt hơn, do lập lịch khóa không tốt trong chế độ 256 bit).
Lưu ý: Chúng tôi không sử dụng mcrypt
vì đây là phần mềm bị bỏ rơi và có các lỗi chưa được vá có thể ảnh hưởng đến bảo mật. Vì những lý do này, tôi khuyến khích các nhà phát triển PHP khác cũng tránh nó.
Gói mã hóa / giải mã đơn giản sử dụng OpenSSL
class UnsafeCrypto
{
const METHOD = 'aes-256-ctr';
/**
* Encrypts (but does not authenticate) a message
*
* @param string $message - plaintext message
* @param string $key - encryption key (raw binary expected)
* @param boolean $encode - set to TRUE to return a base64-encoded
* @return string (raw binary)
*/
public static function encrypt($message, $key, $encode = false)
{
$nonceSize = openssl_cipher_iv_length(self::METHOD);
$nonce = openssl_random_pseudo_bytes($nonceSize);
$ciphertext = openssl_encrypt(
$message,
self::METHOD,
$key,
OPENSSL_RAW_DATA,
$nonce
);
// Now let's pack the IV and the ciphertext together
// Naively, we can just concatenate
if ($encode) {
return base64_encode($nonce.$ciphertext);
}
return $nonce.$ciphertext;
}
/**
* Decrypts (but does not verify) a message
*
* @param string $message - ciphertext message
* @param string $key - encryption key (raw binary expected)
* @param boolean $encoded - are we expecting an encoded string?
* @return string
*/
public static function decrypt($message, $key, $encoded = false)
{
if ($encoded) {
$message = base64_decode($message, true);
if ($message === false) {
throw new Exception('Encryption failure');
}
}
$nonceSize = openssl_cipher_iv_length(self::METHOD);
$nonce = mb_substr($message, 0, $nonceSize, '8bit');
$ciphertext = mb_substr($message, $nonceSize, null, '8bit');
$plaintext = openssl_decrypt(
$ciphertext,
self::METHOD,
$key,
OPENSSL_RAW_DATA,
$nonce
);
return $plaintext;
}
}
Ví dụ sử dụng
$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');
$encrypted = UnsafeCrypto::encrypt($message, $key);
$decrypted = UnsafeCrypto::decrypt($encrypted, $key);
var_dump($encrypted, $decrypted);
Bản trình diễn : https://3v4l.org/jl7qR
Thư viện tiền điện tử đơn giản ở trên vẫn không an toàn để sử dụng. Chúng ta cần xác thực các bản mã và xác minh chúng trước khi giải mã .
Lưu ý : Theo mặc định, UnsafeCrypto::encrypt()
sẽ trả về một chuỗi nhị phân thô. Gọi nó như thế này nếu bạn cần lưu trữ nó ở định dạng an toàn nhị phân (mã hóa base64):
$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');
$encrypted = UnsafeCrypto::encrypt($message, $key, true);
$decrypted = UnsafeCrypto::decrypt($encrypted, $key, true);
var_dump($encrypted, $decrypted);
Bản trình diễn : http://3v4l.org/f5K93
Gói xác thực đơn giản
class SaferCrypto extends UnsafeCrypto
{
const HASH_ALGO = 'sha256';
/**
* Encrypts then MACs a message
*
* @param string $message - plaintext message
* @param string $key - encryption key (raw binary expected)
* @param boolean $encode - set to TRUE to return a base64-encoded string
* @return string (raw binary)
*/
public static function encrypt($message, $key, $encode = false)
{
list($encKey, $authKey) = self::splitKeys($key);
// Pass to UnsafeCrypto::encrypt
$ciphertext = parent::encrypt($message, $encKey);
// Calculate a MAC of the IV and ciphertext
$mac = hash_hmac(self::HASH_ALGO, $ciphertext, $authKey, true);
if ($encode) {
return base64_encode($mac.$ciphertext);
}
// Prepend MAC to the ciphertext and return to caller
return $mac.$ciphertext;
}
/**
* Decrypts a message (after verifying integrity)
*
* @param string $message - ciphertext message
* @param string $key - encryption key (raw binary expected)
* @param boolean $encoded - are we expecting an encoded string?
* @return string (raw binary)
*/
public static function decrypt($message, $key, $encoded = false)
{
list($encKey, $authKey) = self::splitKeys($key);
if ($encoded) {
$message = base64_decode($message, true);
if ($message === false) {
throw new Exception('Encryption failure');
}
}
// Hash Size -- in case HASH_ALGO is changed
$hs = mb_strlen(hash(self::HASH_ALGO, '', true), '8bit');
$mac = mb_substr($message, 0, $hs, '8bit');
$ciphertext = mb_substr($message, $hs, null, '8bit');
$calculated = hash_hmac(
self::HASH_ALGO,
$ciphertext,
$authKey,
true
);
if (!self::hashEquals($mac, $calculated)) {
throw new Exception('Encryption failure');
}
// Pass to UnsafeCrypto::decrypt
$plaintext = parent::decrypt($ciphertext, $encKey);
return $plaintext;
}
/**
* Splits a key into two separate keys; one for encryption
* and the other for authenticaiton
*
* @param string $masterKey (raw binary)
* @return array (two raw binary strings)
*/
protected static function splitKeys($masterKey)
{
// You really want to implement HKDF here instead!
return [
hash_hmac(self::HASH_ALGO, 'ENCRYPTION', $masterKey, true),
hash_hmac(self::HASH_ALGO, 'AUTHENTICATION', $masterKey, true)
];
}
/**
* Compare two strings without leaking timing information
*
* @param string $a
* @param string $b
* @ref https://paragonie.com/b/WS1DLx6BnpsdaVQW
* @return boolean
*/
protected static function hashEquals($a, $b)
{
if (function_exists('hash_equals')) {
return hash_equals($a, $b);
}
$nonce = openssl_random_pseudo_bytes(32);
return hash_hmac(self::HASH_ALGO, $a, $nonce) === hash_hmac(self::HASH_ALGO, $b, $nonce);
}
}
Ví dụ sử dụng
$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');
$encrypted = SaferCrypto::encrypt($message, $key);
$decrypted = SaferCrypto::decrypt($encrypted, $key);
var_dump($encrypted, $decrypted);
Trình diễn : nhị phân thô , mã hóa base64
Nếu bất cứ ai muốn sử dụng SaferCrypto
thư viện này trong môi trường sản xuất hoặc thực hiện các khái niệm tương tự của riêng bạn, tôi khuyên bạn nên liên hệ với các nhà mật mã thường trú để có ý kiến thứ hai trước khi bạn thực hiện. Họ sẽ có thể nói với bạn về những sai lầm mà tôi thậm chí không nhận thức được.
Bạn sẽ tốt hơn nhiều khi sử dụng một thư viện mật mã có uy tín .