Làm cách nào để lưu trữ mật khẩu của người dùng một cách an toàn?


169

Điều này an toàn hơn bao nhiêu so với MD5 đơn giản ? Tôi mới bắt đầu xem xét bảo mật mật khẩu. Tôi khá mới với PHP.

$salt = 'csdnfgksdgojnmfnb';

$password = md5($salt.$_POST['password']);
$result = mysql_query("SELECT id FROM users
                       WHERE username = '".mysql_real_escape_string($_POST['username'])."'
                       AND password = '$password'");

if (mysql_num_rows($result) < 1) {
    /* Access denied */
    echo "The username or password you entered is incorrect.";
} 
else {
    $_SESSION['id'] = mysql_result($result, 0, 'id');
    #header("Location: ./");
    echo "Hello $_SESSION[id]!";
}

Lưu ý php 5.4+ được tích hợp sẵn
Benjamin Gruenbaum

Cũng xem khung băm mật khẩu PHP của Openwall (PHPass). Nó di động và cứng đối với một số cuộc tấn công phổ biến vào mật khẩu người dùng.
jww

1
Bắt buộc "sử dụng PDO thay vì nội suy chuỗi", cho những người vấp phải câu hỏi này ngày hôm nay.
Vụ kiện của Quỹ Monica

Câu trả lời:


270

Cách dễ nhất để bảo mật lược đồ lưu trữ mật khẩu của bạn là sử dụng thư viện chuẩn .

Bởi vì bảo mật có xu hướng phức tạp hơn rất nhiều và với nhiều khả năng vô hình hơn so với hầu hết các lập trình viên có thể giải quyết một mình, sử dụng một thư viện tiêu chuẩn hầu như luôn luôn dễ dàng và an toàn nhất (nếu không phải là duy nhất).


API mật khẩu PHP mới (5.5.0+)

Nếu bạn đang sử dụng phiên bản PHP 5.5.0 trở lên, bạn có thể sử dụng API băm mật khẩu đơn giản hóa mới

Ví dụ về mã sử dụng API mật khẩu của PHP:

<?php
// $hash is what you would store in your database
$hash = password_hash($_POST['password'], PASSWORD_DEFAULT, ['cost' => 12]);

// $hash would be the $hash (above) stored in your database for this user
$checked = password_verify($_POST['password'], $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}

(Trong trường hợp bạn vẫn đang sử dụng kế thừa 5.3.7 hoặc mới hơn, bạn có thể cài đặt ircmaxell / password_compat để có quyền truy cập vào các chức năng tích hợp)


Cải thiện khi băm muối: thêm hạt tiêu

Nếu bạn muốn bảo mật thêm, những người bảo mật hiện nay (2017) khuyên bạn nên thêm một ' tiêu ' vào băm mật khẩu muối (tự động).

Có một cách đơn giản, thả trong lớp thực hiện mô hình này một cách an toàn, tôi khuyên bạn nên: Netsilik / PepperedPasswords ( github ).
Nó đi kèm với Giấy phép MIT, vì vậy bạn có thể sử dụng nó theo cách bạn muốn, ngay cả trong các dự án độc quyền.

Ví dụ về mã sử dụng Netsilik/PepperedPasswords:

<?php
use Netsilik/Lib/PepperedPasswords;

// Some long, random, binary string, encoded as hexadecimal; stored in your configuration (NOT in your Database, as that would defeat the entire purpose of the pepper).
$config['pepper'] = hex2bin('012345679ABCDEF012345679ABCDEF012345679ABCDEF012345679ABCDEF');

$hasher = new PepperedPasswords($config['pepper']);

// $hash is what you would store in your database
$hash = $hasher->hash($_POST['password']);

// $hash would be the $hash (above) stored in your database for this user
$checked = $hasher->verify($_POST['password'], $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}


Thư viện chuẩn OLD

Xin lưu ý: bạn không cần phải điều này nữa! Điều này chỉ ở đây cho mục đích lịch sử.

Hãy xem: Khung băm mật khẩu PHP di động : phpass và đảm bảo bạn sử dụng CRYPT_BLOWFISHthuật toán nếu có thể.

Ví dụ về mã sử dụng phpass (v0.2):

<?php
require('PasswordHash.php');

$pwdHasher = new PasswordHash(8, FALSE);

// $hash is what you would store in your database
$hash = $pwdHasher->HashPassword( $password );

// $hash would be the $hash (above) stored in your database for this user
$checked = $pwdHasher->CheckPassword($password, $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}

PHPass đã được triển khai trong một số dự án khá nổi tiếng:

  • phpBB3
  • WordPress 2.5+ cũng như bbPress
  • bản phát hành Drupal 7, (mô-đun có sẵn cho Drupal 5 & 6)
  • khác

Điều tốt là bạn không cần phải lo lắng về các chi tiết, những chi tiết đó đã được lập trình bởi những người có kinh nghiệm và được xem xét bởi nhiều người trên internet.

Để biết thêm thông tin về các lược đồ lưu trữ mật khẩu, hãy đọc bài đăng trên blog của Jeff : Có thể bạn đang lưu trữ mật khẩu không chính xác

Bất cứ điều gì bạn làm nếu bạn đi theo cách tiếp cận ' Tôi sẽ tự làm, cảm ơn bạn , không sử dụng MD5hoặc không sử dụng SHA1nữa . Chúng là thuật toán băm đẹp, nhưng được coi là bị hỏng vì mục đích bảo mật .

Hiện nay, sử dụng hầm mộ , với CRYPT_BLOWFISH là thực hành tốt nhất.
CRYPT_BLOWFISH trong PHP là một triển khai băm Bcrypt. Bcrypt dựa trên mật mã khối Blowfish, sử dụng thiết lập khóa đắt tiền của nó để làm chậm thuật toán.


29

Người dùng của bạn sẽ an toàn hơn nhiều nếu bạn sử dụng các truy vấn được tham số hóa thay vì nối các câu lệnh SQL. Và muối phải là duy nhất cho mỗi người dùng và nên được lưu trữ cùng với hàm băm mật khẩu.


1
Có một bài viết hay về bảo mật trong PHP tại Nettuts +, mật khẩu muối cũng được đề cập. Có lẽ bạn nên xem qua: net.tutsplus.com/tutorials/php/ Kẻ
Fábio Antunes

3
Nettuts + là một bài viết rất tệ để sử dụng làm mô hình - nó bao gồm việc sử dụng MD5 có thể bị ép buộc rất dễ dàng ngay cả với muối. Thay vào đó, chỉ cần sử dụng thư viện PHPass rất xa, tốt hơn nhiều so với bất kỳ mã nào bạn có thể tìm thấy trên trang web hướng dẫn, tức là câu trả lời này: stackoverflow.com/questions/1581610/
Lỗi

11

Một cách tốt hơn sẽ là cho mỗi người dùng có một loại muối duy nhất.

Lợi ích của việc có muối là giúp kẻ tấn công tạo ra chữ ký MD5 của mỗi từ điển khó hơn. Nhưng nếu kẻ tấn công biết rằng bạn có một loại muối cố định, thì chúng có thể tạo trước chữ ký MD5 của mỗi từ trong từ điển được tiền tố bởi muối cố định của bạn.

Cách tốt hơn là mỗi khi người dùng thay đổi mật khẩu, hệ thống của bạn sẽ tạo ra một loại muối ngẫu nhiên và lưu trữ lượng muối đó cùng với hồ sơ người dùng. Việc kiểm tra mật khẩu sẽ tốn kém hơn một chút (vì bạn cần tra cứu muối trước khi có thể tạo chữ ký MD5) nhưng việc kẻ tấn công tạo trước MD5 khó khăn hơn nhiều.


3
Các muối thường được lưu trữ cùng với hàm băm mật khẩu (ví dụ: đầu ra của crypt()hàm). Và vì dù sao bạn cũng phải truy xuất mật khẩu băm, sử dụng muối cụ thể của người dùng sẽ không khiến thủ tục trở nên đắt hơn. (Hoặc bạn có nghĩa là tạo ra một loại muối ngẫu nhiên mới là tốn kém? Tôi thực sự không nghĩ vậy.) Nếu không +1.
Inshallah

Vì mục đích bảo mật, bạn có thể chỉ muốn cung cấp quyền truy cập vào bảng thông qua các thủ tục được lưu trữ và ngăn không cho hàm băm được trả về. Thay vào đó, khách hàng vượt qua những gì nó nghĩ là hàm băm và nhận được cờ thành công hay thất bại. Điều này cho phép các Proc được lưu trữ để ghi lại nỗ lực, tạo một phiên, v.v.
Steven Sudit

@Inshallah - nếu tất cả người dùng có cùng một loại muối, thì bạn có thể sử dụng lại cuộc tấn công từ điển bạn sử dụng trên user1 chống lại user2. Nhưng nếu mỗi người dùng có một loại muối duy nhất, bạn sẽ cần tạo một từ điển mới cho mỗi người dùng mà bạn muốn tấn công.
R Samuel Klatchko

@R Samuel - đó chính xác là lý do tại sao tôi bỏ phiếu trả lời của bạn, bởi vì nó khuyến nghị chiến lược thực hành tốt nhất để tránh các cuộc tấn công như vậy. Nhận xét của tôi là nhằm thể hiện sự lúng túng của tôi về những gì bạn nói về chi phí bổ sung của một loại muối cho mỗi người dùng, điều mà tôi hoàn toàn không hiểu. (vì "muối thường được lưu trữ cùng với mật khẩu băm", mọi yêu cầu lưu trữ và CPU bổ sung cho muối của mỗi người dùng đều rất nhỏ, đến mức họ không cần phải đề cập đến ...)
Inshallah

@Inshallah - Tôi đã suy nghĩ về trường hợp bạn đã kiểm tra cơ sở dữ liệu nếu mật khẩu băm có ổn không (sau đó bạn có một truy xuất db để lấy muối và truy cập db thứ hai để kiểm tra mật khẩu băm). Bạn đã đúng về trường hợp bạn tải xuống mật khẩu muối / băm trong một lần truy xuất và sau đó thực hiện so sánh trên máy khách. Xin lỗi vì sự nhầm lẫn.
R Samuel Klatchko

11

Với PHP 5.5 (những gì tôi mô tả có sẵn cho các phiên bản trước đó, xem bên dưới) xung quanh góc tôi muốn đề xuất sử dụng giải pháp tích hợp mới, tích hợp của nó: password_hash()password_verify(). Nó cung cấp một số tùy chọn để đạt được mức độ bảo mật mật khẩu bạn cần (ví dụ: bằng cách chỉ định tham số "chi phí" thông qua $optionsmảng)

<?php
var_dump(password_hash("my-secret-password", PASSWORD_DEFAULT));

$options = array(
    'cost' => 7, // this is the number of rounds for bcrypt
    // 'salt' => 'TphfsM82o1uEKlfP9vf1f', // you could specify a salt but it is not recommended
);
var_dump(password_hash("my-secret-password", PASSWORD_BCRYPT, $options));
?>

sẽ trở lại

string(60) "$2y$10$w2LxXdIcqJpD6idFTNn.eeZbKesdu5y41ksL22iI8C4/6EweI7OK."
string(60) "$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d."

Như bạn có thể thấy, chuỗi chứa muối cũng như chi phí đã được chỉ định trong các tùy chọn. Nó cũng chứa các thuật toán được sử dụng.

Do đó, khi kiểm tra mật khẩu (ví dụ khi người dùng đăng nhập), khi sử dụng password_verify()chức năng miễn phí, nó sẽ trích xuất các tham số mật mã cần thiết từ chính mật khẩu băm.

Khi không chỉ định muối, hàm băm mật khẩu được tạo sẽ khác nhau theo mỗi cuộc gọi password_hash()vì muối được tạo ngẫu nhiên. Do đó, việc so sánh hàm băm trước đó với hàm mới được tạo sẽ thất bại, ngay cả đối với mật khẩu chính xác.

Xác minh công việc như thế này:

var_dump(password_verify("my-secret-password", '$2y$10$BjHJbMCNWIJq7xiAeyFaHOGaO0jjNoE11e0YAer6Zu01OZHN/gk6K'));
var_dump(password_verify("wrong-password", '$2y$10$BjHJbMCNWIJq7xiAeyFaHOGaO0jjNoE11e0YAer6Zu01OZHN/gk6K'));

var_dump(password_verify("my-secret-password", '$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d.'));
var_dump(password_verify("wrong-password", '$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d.'));

Tôi hy vọng rằng việc cung cấp các chức năng tích hợp này sẽ sớm cung cấp bảo mật mật khẩu tốt hơn trong trường hợp bị đánh cắp dữ liệu, vì nó làm giảm lượng suy nghĩ mà lập trình viên phải đưa vào để thực hiện đúng.

Có một thư viện nhỏ (một tệp PHP) sẽ cung cấp cho bạn các phiên bản PHP 5.5 password_hashtrong PHP 5.3.7+: https://github.com/ircmaxell/password_compat


2
Trong hầu hết các trường hợp, tốt hơn là bỏ qua tham số muối. Hàm tạo ra một loại muối từ nguồn ngẫu nhiên của hệ điều hành, có rất ít khả năng bạn có thể tự cung cấp một loại muối tốt hơn.
martinstoeckli

1
Đó là những gì tôi đã viết, phải không? "nếu không có muối được chỉ định, nó sẽ được tạo ngẫu nhiên, vì lý do đó, tốt hơn là không chỉ định muối"
akirk

Hầu hết các ví dụ cho thấy làm thế nào để thêm cả hai tham số, ngay cả khi không nên thêm muối, vì vậy tôi tự hỏi tại sao? Và thành thật mà nói, tôi chỉ đọc bình luận đằng sau mã, không phải trên dòng tiếp theo. Dù sao, sẽ tốt hơn khi ví dụ cho thấy cách sử dụng chức năng tốt nhất?
martinstoeckli

Bạn nói đúng, tôi đồng ý. Tôi đã thay đổi câu trả lời của tôi cho phù hợp và nhận xét ra dòng. Cảm ơn
akirk

Làm thế nào để kiểm tra xem mật khẩu đã lưu và mật khẩu đã nhập có giống nhau không? Tôi đang sử dụng password_hash()password_verifybất kể mật khẩu nào (đúng hay không) tôi đã sử dụng tôi kết thúc bằng mật khẩu chính xác
Brownman Revival ngày 25/8/2015

0

Điều đó tốt với tôi. Ông Atwood đã viết về sức mạnh của MD5 so với các bảng cầu vồng và về cơ bản với một loại muối dài như thế bạn đang ngồi rất đẹp (mặc dù một số dấu câu / số ngẫu nhiên, nó có thể cải thiện nó).

Bạn cũng có thể nhìn vào SHA-1, dường như đang trở nên phổ biến hơn những ngày này.


6
Ghi chú ở cuối bài đăng của ông Atwood (màu đỏ) liên kết với một bài đăng khác từ một nhà thực hành bảo mật nói rằng sử dụng MD5, SHA1 và các băm nhanh khác để lưu trữ mật khẩu là rất sai.
sipwiz

2
@Matthew Scharley: Tôi không đồng ý rằng nỗ lực bổ sung được áp dụng bởi các thuật toán băm mật khẩu đắt tiền là bảo mật sai. Đó là để bảo vệ chống lại vũ phu buộc các mật khẩu dễ đoán. Nếu bạn đang hạn chế các nỗ lực đăng nhập, thì bạn đang bảo vệ chống lại điều tương tự (mặc dù hiệu quả hơn một chút). Nhưng nếu một kẻ thù có quyền truy cập vào các băm được lưu trữ DB, anh ta sẽ có thể tạo ra các mật khẩu (dễ đoán) như vậy một cách nhanh chóng (tùy thuộc vào mức độ dễ đoán). Mặc định cho thuật toán mã hóa SHA-256 là 10000 vòng, do đó sẽ khó khăn hơn 10000 lần.
Inshallah

3
Băm chậm thực sự được thực hiện bằng cách lặp lại nhanh một số lần rất lớn và xáo trộn dữ liệu xung quanh giữa mỗi lần lặp. Mục đích là để đảm bảo rằng ngay cả khi kẻ xấu nhận được một bản sao băm mật khẩu của bạn, anh ta phải đốt một lượng thời gian CPU đáng kể để kiểm tra từ điển của anh ta chống lại băm của bạn.
phê

4
@caf: Tôi tin rằng thuật toán bcrypt sử dụng mức độ mở rộng có thể tham số hóa của lập lịch khóa Eksblowfish; không hoàn toàn chắc chắn làm thế nào điều này hoạt động, nhưng lập lịch chính thường là một hoạt động rất tốn kém được thực hiện trong quá trình khởi tạo một đối tượng bối cảnh mật mã, trước khi bất kỳ mã hóa nào được thực hiện.
Inshallah

3
Inshallah: Điều này là đúng - thuật toán bcrypt là một thiết kế khác, trong đó nguyên thủy mã hóa cơ bản là một mật mã khối chứ không phải là hàm băm. Tôi đã đề cập đến các lược đồ dựa trên các hàm băm, như mã hóa MD5 của PHK ().
phê

0

Tôi muốn thêm vào:

  • Không giới hạn mật khẩu người dùng theo độ dài

Để tương thích với các hệ thống cũ thường đặt giới hạn cho độ dài tối đa của mật khẩu. Đây là một chính sách bảo mật tồi: nếu bạn đặt hạn chế, chỉ đặt nó cho độ dài tối thiểu của mật khẩu.

  • Không gửi mật khẩu người dùng qua email

Để khôi phục mật khẩu đã quên, bạn nên gửi địa chỉ mà người dùng có thể thay đổi mật khẩu.

  • Cập nhật băm mật khẩu người dùng

Băm mật khẩu có thể đã hết hạn (các tham số của thuật toán có thể được cập nhật). Bằng cách sử dụng chức năng password_needs_rehash()bạn có thể kiểm tra nó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.