Cá nhân, tôi sẽ sử dụng mcrypt
như những người khác đăng. Nhưng còn nhiều điều cần lưu ý ...
Làm cách nào để mã hóa và giải mã mật khẩu trong PHP?
Xem bên dưới để biết một lớp học mạnh mẽ chăm sóc mọi thứ cho bạn:
Thuật toán an toàn nhất để mã hóa mật khẩu là gì?
an toàn nhất ? bất kỳ trong số họ. Phương pháp an toàn nhất nếu bạn sẽ mã hóa là bảo vệ chống lại các lỗ hổng tiết lộ thông tin (XSS, bao gồm từ xa, v.v.). Nếu nó thoát ra, kẻ tấn công cuối cùng có thể bẻ khóa mã hóa (không mã hóa nào không thể đảo ngược 100% nếu không có khóa - Như @NullUserException chỉ ra điều này không hoàn toàn đúng. Có một số lược đồ mã hóa không thể bẻ khóa như OneTimePad ) .
Tôi lưu trữ khóa riêng ở đâu?
Những gì tôi sẽ làm là sử dụng 3 phím. Một là do người dùng cung cấp, một là dành riêng cho ứng dụng và một là dành riêng cho người dùng (như muối). Khóa cụ thể của ứng dụng có thể được lưu trữ ở bất cứ đâu (trong tệp cấu hình bên ngoài gốc web, trong biến môi trường, v.v.). Người dùng cụ thể sẽ được lưu trữ trong một cột trong db bên cạnh mật khẩu được mã hóa. Người dùng được cung cấp sẽ không được lưu trữ. Sau đó, bạn sẽ làm một cái gì đó như thế này:
$key = $userKey . $serverKey . $userSuppliedKey;
Lợi ích ở đây là bất kỳ 2 trong số các khóa đều có thể bị xâm phạm mà dữ liệu không bị xâm phạm. Nếu có một cuộc tấn công SQL Injection, họ có thể có được $userKey
, nhưng không phải là khác 2. Nếu có một máy chủ địa phương khai thác, họ có thể nhận được $userKey
và $serverKey
, nhưng không phải là thứ ba $userSuppliedKey
. Nếu họ đi đánh người dùng bằng cờ lê, họ có thể lấy $userSuppliedKey
, nhưng không phải 2 người kia (nhưng sau đó, nếu người dùng bị đánh bằng cờ lê, dù sao bạn cũng đã quá muộn).
Thay vì lưu trữ khóa riêng, có nên yêu cầu người dùng nhập khóa riêng bất cứ khi nào họ cần mật khẩu được giải mã không? (Người dùng của ứng dụng này có thể được tin cậy)
Chắc chắn rồi. Trên thực tế, đó là cách duy nhất tôi sẽ làm. Nếu không, bạn cần lưu trữ một phiên bản không được mã hóa ở định dạng lưu trữ lâu bền (bộ nhớ dùng chung như APC hoặc memcached hoặc trong tệp phiên). Đó là phơi bày bản thân với những thỏa hiệp bổ sung. Không bao giờ lưu trữ phiên bản mật khẩu không được mã hóa trong bất kỳ thứ gì ngoại trừ một biến cục bộ.
Mật khẩu có thể bị đánh cắp và giải mã theo những cách nào? Tôi cần phải nhận thức được gì?
Bất kỳ hình thức thỏa hiệp nào của hệ thống của bạn sẽ cho phép họ xem dữ liệu được mã hóa. Nếu họ có thể tiêm mã hoặc truy cập vào hệ thống tệp của bạn, họ có thể xem dữ liệu được giải mã (vì họ có thể chỉnh sửa các tệp giải mã dữ liệu). Bất kỳ hình thức tấn công Replay hoặc MITM nào cũng sẽ cung cấp cho họ quyền truy cập đầy đủ vào các khóa liên quan. Đánh hơi lưu lượng HTTP thô cũng sẽ cung cấp cho họ các khóa.
Sử dụng SSL cho tất cả lưu lượng. Và đảm bảo không có gì trên máy chủ có bất kỳ loại lỗ hổng nào (CSRF, XSS, SQL Injection, Đặc quyền nâng cao, Thi hành mã từ xa, v.v.).
Chỉnh sửa: Đây là một triển khai lớp PHP của một phương thức mã hóa mạnh:
/**
* A class to handle secure encryption and decryption of arbitrary data
*
* Note that this is not just straight encryption. It also has a few other
* features in it to make the encrypted data far more secure. Note that any
* other implementations used to decrypt data will have to do the same exact
* operations.
*
* Security Benefits:
*
* - Uses Key stretching
* - Hides the Initialization Vector
* - Does HMAC verification of source data
*
*/
class Encryption {
/**
* @var string $cipher The mcrypt cipher to use for this instance
*/
protected $cipher = '';
/**
* @var int $mode The mcrypt cipher mode to use
*/
protected $mode = '';
/**
* @var int $rounds The number of rounds to feed into PBKDF2 for key generation
*/
protected $rounds = 100;
/**
* Constructor!
*
* @param string $cipher The MCRYPT_* cypher to use for this instance
* @param int $mode The MCRYPT_MODE_* mode to use for this instance
* @param int $rounds The number of PBKDF2 rounds to do on the key
*/
public function __construct($cipher, $mode, $rounds = 100) {
$this->cipher = $cipher;
$this->mode = $mode;
$this->rounds = (int) $rounds;
}
/**
* Decrypt the data with the provided key
*
* @param string $data The encrypted datat to decrypt
* @param string $key The key to use for decryption
*
* @returns string|false The returned string if decryption is successful
* false if it is not
*/
public function decrypt($data, $key) {
$salt = substr($data, 0, 128);
$enc = substr($data, 128, -64);
$mac = substr($data, -64);
list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);
if (!hash_equals(hash_hmac('sha512', $enc, $macKey, true), $mac)) {
return false;
}
$dec = mcrypt_decrypt($this->cipher, $cipherKey, $enc, $this->mode, $iv);
$data = $this->unpad($dec);
return $data;
}
/**
* Encrypt the supplied data using the supplied key
*
* @param string $data The data to encrypt
* @param string $key The key to encrypt with
*
* @returns string The encrypted data
*/
public function encrypt($data, $key) {
$salt = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);
$data = $this->pad($data);
$enc = mcrypt_encrypt($this->cipher, $cipherKey, $data, $this->mode, $iv);
$mac = hash_hmac('sha512', $enc, $macKey, true);
return $salt . $enc . $mac;
}
/**
* Generates a set of keys given a random salt and a master key
*
* @param string $salt A random string to change the keys each encryption
* @param string $key The supplied key to encrypt with
*
* @returns array An array of keys (a cipher key, a mac key, and a IV)
*/
protected function getKeys($salt, $key) {
$ivSize = mcrypt_get_iv_size($this->cipher, $this->mode);
$keySize = mcrypt_get_key_size($this->cipher, $this->mode);
$length = 2 * $keySize + $ivSize;
$key = $this->pbkdf2('sha512', $key, $salt, $this->rounds, $length);
$cipherKey = substr($key, 0, $keySize);
$macKey = substr($key, $keySize, $keySize);
$iv = substr($key, 2 * $keySize);
return array($cipherKey, $macKey, $iv);
}
/**
* Stretch the key using the PBKDF2 algorithm
*
* @see http://en.wikipedia.org/wiki/PBKDF2
*
* @param string $algo The algorithm to use
* @param string $key The key to stretch
* @param string $salt A random salt
* @param int $rounds The number of rounds to derive
* @param int $length The length of the output key
*
* @returns string The derived key.
*/
protected function pbkdf2($algo, $key, $salt, $rounds, $length) {
$size = strlen(hash($algo, '', true));
$len = ceil($length / $size);
$result = '';
for ($i = 1; $i <= $len; $i++) {
$tmp = hash_hmac($algo, $salt . pack('N', $i), $key, true);
$res = $tmp;
for ($j = 1; $j < $rounds; $j++) {
$tmp = hash_hmac($algo, $tmp, $key, true);
$res ^= $tmp;
}
$result .= $res;
}
return substr($result, 0, $length);
}
protected function pad($data) {
$length = mcrypt_get_block_size($this->cipher, $this->mode);
$padAmount = $length - strlen($data) % $length;
if ($padAmount == 0) {
$padAmount = $length;
}
return $data . str_repeat(chr($padAmount), $padAmount);
}
protected function unpad($data) {
$length = mcrypt_get_block_size($this->cipher, $this->mode);
$last = ord($data[strlen($data) - 1]);
if ($last > $length) return false;
if (substr($data, -1 * $last) !== str_repeat(chr($last), $last)) {
return false;
}
return substr($data, 0, -1 * $last);
}
}
Lưu ý rằng tôi đang sử dụng một hàm được thêm vào trong PHP 5.6 : hash_equals
. Nếu bạn ở mức thấp hơn 5,6, bạn có thể sử dụng chức năng thay thế này để thực hiện chức năng so sánh an toàn theo thời gian bằng cách sử dụng xác minh hai lần HMAC :
function hash_equals($a, $b) {
$key = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
return hash_hmac('sha512', $a, $key) === hash_hmac('sha512', $b, $key);
}
Sử dụng:
$e = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$encryptedData = $e->encrypt($data, $key);
Sau đó, để giải mã:
$e2 = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$data = $e2->decrypt($encryptedData, $key);
Lưu ý rằng tôi đã sử dụng $e2
lần thứ hai để hiển thị cho bạn các trường hợp khác nhau vẫn sẽ giải mã dữ liệu đúng cách.
Bây giờ, làm thế nào nó hoạt động / tại sao sử dụng nó trên một giải pháp khác:
Chìa khóa
Các phím không được sử dụng trực tiếp. Thay vào đó, khóa được kéo dài bởi một dẫn xuất PBKDF2 tiêu chuẩn.
Khóa được sử dụng để mã hóa là duy nhất cho mọi khối văn bản được mã hóa. Do đó, khóa được cung cấp trở thành "khóa chính". Do đó, lớp này cung cấp xoay vòng khóa cho các khóa mật mã và xác thực.
LƯU Ý QUAN TRỌNG , $rounds
tham số được cấu hình cho các khóa ngẫu nhiên thực sự có đủ cường độ (tối thiểu 128 bit Bảo mật mã hóa ở mức tối thiểu). Nếu bạn định sử dụng mật khẩu hoặc khóa không ngẫu nhiên (hoặc ít ngẫu nhiên hơn 128 bit ngẫu nhiên CS), bạn phải tăng tham số này. Tôi sẽ đề xuất tối thiểu 10000 cho mật khẩu (bạn càng có đủ khả năng thì càng tốt, nhưng nó sẽ thêm vào thời gian chạy) ...
Toàn vẹn dữ liệu
- Phiên bản cập nhật sử dụng ENCRYPT-THEN-MAC, đây là một phương pháp tốt hơn nhiều để đảm bảo tính xác thực của dữ liệu được mã hóa.
Mã hóa:
- Nó sử dụng mcrypt để thực sự mã hóa. Tôi sẽ đề nghị sử dụng một trong hai
MCRYPT_BLOWFISH
hoặc MCRYPT_RIJNDAEL_128
cyphers và MCRYPT_MODE_CBC
cho chế độ. Nó đủ mạnh và vẫn khá nhanh (một chu kỳ mã hóa và giải mã mất khoảng 1/2 giây trên máy của tôi).
Bây giờ, như điểm 3 từ danh sách đầu tiên, những gì sẽ cung cấp cho bạn là một chức năng như thế này:
function makeKey($userKey, $serverKey, $userSuppliedKey) {
$key = hash_hmac('sha512', $userKey, $serverKey);
$key = hash_hmac('sha512', $key, $userSuppliedKey);
return $key;
}
Bạn có thể kéo dài nó trong makeKey()
hàm, nhưng vì nó sẽ được kéo dài sau đó, nên thực sự không phải là một điểm rất lớn để làm điều đó.
Theo kích thước lưu trữ, nó phụ thuộc vào văn bản đơn giản. Blowfish sử dụng kích thước khối 8 byte, vì vậy bạn sẽ có:
- 16 byte cho muối
- 64 byte cho hmac
- chiều dài dữ liệu
- Đệm sao cho độ dài dữ liệu% 8 == 0
Vì vậy, đối với nguồn dữ liệu 16 ký tự, sẽ có 16 ký tự dữ liệu được mã hóa. Vì vậy, điều đó có nghĩa là kích thước dữ liệu được mã hóa thực tế là 16 byte do phần đệm. Sau đó thêm 16 byte cho muối và 64 byte cho hmac và tổng kích thước được lưu trữ là 96 byte. Vì vậy, có tối đa 80 ký tự trên đầu, và tệ nhất là trên 87 ký tự ...
Tôi hy vọng điều đó sẽ giúp ...
Lưu ý: 12/11/12: Tôi vừa cập nhật lớp này với phương thức mã hóa tốt hơn NHIỀU, sử dụng các khóa có nguồn gốc tốt hơn và sửa lỗi tạo MAC ...