คำนำ
เริ่มต้นด้วยนิยามตารางของคุณ:
- UserID
- Fname
- Lname
- Email
- Password
- IV
การเปลี่ยนแปลงมีดังนี้
- เขตข้อมูล
Fname
, Lname
และEmail
จะได้รับการเข้ารหัสโดยใช้การเข้ารหัสแบบสมมาตรให้โดยOpenSSL ,
IV
ข้อมูลจะเก็บเวกเตอร์ initialisationใช้สำหรับการเข้ารหัส ข้อกำหนดในการจัดเก็บขึ้นอยู่กับการเข้ารหัสและโหมดที่ใช้ เพิ่มเติมเกี่ยวกับเรื่องนี้ในภายหลัง
Password
ฟิลด์จะถกใช้ทางเดียวกัญชารหัสผ่าน
การเข้ารหัส
การเข้ารหัสและโหมด
การเลือกรหัสและโหมดการเข้ารหัสที่ดีที่สุดอยู่นอกเหนือขอบเขตของคำตอบนี้ แต่ตัวเลือกสุดท้ายมีผลต่อขนาดของทั้งคีย์การเข้ารหัสและเวกเตอร์การเริ่มต้น สำหรับโพสต์นี้เราจะใช้ AES-256-CBC ซึ่งมีขนาดบล็อกคงที่ 16 ไบต์และขนาดคีย์ 16, 24 หรือ 32 ไบต์
คีย์การเข้ารหัส
คีย์เข้ารหัสที่ดีคือ binary blob ที่สร้างจากตัวสร้างตัวเลขสุ่มที่เชื่อถือได้ ขอแนะนำตัวอย่างต่อไปนี้ (> = 5.3):
$key_size = 32; // 256 bits
$encryption_key = openssl_random_pseudo_bytes($key_size, $strong);
// $strong will be true if the key is crypto safe
ซึ่งสามารถทำได้ครั้งเดียวหรือหลายครั้ง (หากคุณต้องการสร้างชุดคีย์การเข้ารหัสลับ) ทำให้สิ่งเหล่านี้เป็นส่วนตัวมากที่สุด
IV
เวกเตอร์เริ่มต้นจะเพิ่มการสุ่มให้กับการเข้ารหัสและจำเป็นสำหรับโหมด CBC ควรใช้ค่าเหล่านี้เพียงครั้งเดียว (ในทางเทคนิคหนึ่งครั้งต่อคีย์การเข้ารหัส) ดังนั้นการอัปเดตส่วนใดส่วนหนึ่งของแถวควรสร้างใหม่
มีฟังก์ชันเพื่อช่วยคุณสร้าง IV:
$iv_size = 16; // 128 bits
$iv = openssl_random_pseudo_bytes($iv_size, $strong);
ตัวอย่าง
มาเข้ารหัสฟิลด์ชื่อโดยใช้ก่อนหน้านี้$encryption_key
และ$iv
; ในการทำเช่นนี้เราต้องใส่ข้อมูลของเราให้มีขนาดบล็อก:
function pkcs7_pad($data, $size)
{
$length = $size - strlen($data) % $size;
return $data . str_repeat(chr($length), $length);
}
$name = 'Jack';
$enc_name = openssl_encrypt(
pkcs7_pad($name, 16), // padded data
'AES-256-CBC', // cipher and mode
$encryption_key, // secret key
0, // options (not used)
$iv // initialisation vector
);
ข้อกำหนดในการจัดเก็บ
เอาต์พุตที่เข้ารหัสเช่น IV เป็นไบนารี การจัดเก็บค่าเหล่านี้ในฐานข้อมูลสามารถทำได้โดยใช้ประเภทคอลัมน์ที่กำหนดเช่นหรือBINARY
VARBINARY
ค่าผลลัพธ์เช่น IV เป็นไบนารี ในการจัดเก็บค่าเหล่านั้นใน MySQL ให้พิจารณาใช้BINARY
หรือVARBINARY
คอลัมน์ หากนี่ไม่ใช่ตัวเลือกคุณยังสามารถแปลงข้อมูลไบนารีเป็นการแสดงข้อความโดยใช้base64_encode()
หรือbin2hex()
การทำเช่นนี้ต้องใช้พื้นที่เก็บข้อมูลเพิ่มขึ้นระหว่าง 33% ถึง 100%
การถอดรหัส
การถอดรหัสค่าที่เก็บไว้จะคล้ายกัน:
function pkcs7_unpad($data)
{
return substr($data, 0, -ord($data[strlen($data) - 1]));
}
$row = $result->fetch(PDO::FETCH_ASSOC); // read from database result
// $enc_name = base64_decode($row['Name']);
// $enc_name = hex2bin($row['Name']);
$enc_name = $row['Name'];
// $iv = base64_decode($row['IV']);
// $iv = hex2bin($row['IV']);
$iv = $row['IV'];
$name = pkcs7_unpad(openssl_decrypt(
$enc_name,
'AES-256-CBC',
$encryption_key,
0,
$iv
));
การเข้ารหัสที่พิสูจน์ตัวตน
คุณสามารถปรับปรุงความสมบูรณ์ของข้อความรหัสที่สร้างขึ้นเพิ่มเติมได้โดยการต่อท้ายลายเซ็นที่สร้างจากคีย์ลับ (ต่างจากคีย์เข้ารหัส) และข้อความเข้ารหัส ก่อนที่ข้อความรหัสจะถูกถอดรหัสลายเซ็นจะได้รับการตรวจสอบก่อน (ควรใช้วิธีการเปรียบเทียบเวลาคงที่)
ตัวอย่าง
// generate once, keep safe
$auth_key = openssl_random_pseudo_bytes(32, $strong);
// authentication
$auth = hash_hmac('sha256', $enc_name, $auth_key, true);
$auth_enc_name = $auth . $enc_name;
// verification
$auth = substr($auth_enc_name, 0, 32);
$enc_name = substr($auth_enc_name, 32);
$actual_auth = hash_hmac('sha256', $enc_name, $auth_key, true);
if (hash_equals($auth, $actual_auth)) {
// perform decryption
}
ดูสิ่งนี้ด้วย: hash_equals()
แฮช
การจัดเก็บรหัสผ่านที่ย้อนกลับได้ในฐานข้อมูลของคุณจะต้องหลีกเลี่ยงให้มากที่สุด คุณต้องการตรวจสอบรหัสผ่านเท่านั้นแทนที่จะรู้เนื้อหา หากผู้ใช้ทำรหัสผ่านหายจะเป็นการดีกว่าที่จะอนุญาตให้รีเซ็ตได้แทนที่จะส่งรหัสเดิมมาให้ (ตรวจสอบให้แน่ใจว่าการรีเซ็ตรหัสผ่านทำได้ในระยะเวลา จำกัด เท่านั้น)
การใช้ฟังก์ชันแฮชเป็นการดำเนินการทางเดียว หลังจากนั้นสามารถใช้เพื่อการตรวจสอบได้อย่างปลอดภัยโดยไม่ต้องเปิดเผยข้อมูลต้นฉบับ สำหรับรหัสผ่านวิธี brute force เป็นวิธีการที่เป็นไปได้ในการเปิดเผยเนื่องจากความยาวค่อนข้างสั้นและตัวเลือกรหัสผ่านที่ไม่ดีสำหรับหลาย ๆ คน
อัลกอริทึมการแฮชเช่น MD5 หรือ SHA1 ถูกสร้างขึ้นเพื่อตรวจสอบเนื้อหาไฟล์กับค่าแฮชที่ทราบ พวกเขาได้รับการปรับให้เหมาะสมอย่างมากเพื่อให้การยืนยันนี้เร็วที่สุดในขณะที่ยังคงมีความถูกต้อง เนื่องจากพื้นที่เอาต์พุตที่ค่อนข้าง จำกัด จึงง่ายต่อการสร้างฐานข้อมูลด้วยรหัสผ่านที่รู้จักและเอาต์พุตแฮชตามลำดับตารางสายรุ้ง
การเพิ่มเกลือลงในรหัสผ่านก่อนที่จะแฮชจะทำให้ตารางสายรุ้งไร้ประโยชน์ แต่ความก้าวหน้าของฮาร์ดแวร์ล่าสุดทำให้การค้นหากำลังดุร้ายเป็นแนวทางที่ใช้ได้ผล นั่นเป็นเหตุผลที่คุณต้องมีอัลกอริทึมการแฮชที่ช้าโดยเจตนาและไม่สามารถปรับให้เหมาะสมได้ นอกจากนี้ยังควรจะสามารถเพิ่มภาระสำหรับฮาร์ดแวร์ที่เร็วขึ้นโดยไม่ส่งผลต่อความสามารถในการตรวจสอบแฮชรหัสผ่านที่มีอยู่เพื่อพิสูจน์ในอนาคต
ปัจจุบันมีสองตัวเลือกยอดนิยมให้เลือก:
- PBKDF2 (ฟังก์ชันการหาคีย์จากรหัสผ่าน v2)
- bcrypt (aka Blowfish)
คำตอบนี้จะใช้ตัวอย่างกับ bcrypt
เจนเนอเรชั่น
แฮชรหัสผ่านสามารถสร้างได้ดังนี้:
$password = 'my password';
$random = openssl_random_pseudo_bytes(18);
$salt = sprintf('$2y$%02d$%s',
13, // 2^n cost factor
substr(strtr(base64_encode($random), '+', '.'), 0, 22)
);
$hash = crypt($password, $salt);
เกลือจะถูกสร้างขึ้นด้วยopenssl_random_pseudo_bytes()
ในรูปแบบหยดสุ่มของข้อมูลซึ่งดำเนินการแล้วผ่านbase64_encode()
และเพื่อให้ตรงกับตัวอักษรที่จำเป็นของstrtr()
[A-Za-z0-9/.]
crypt()
ประสิทธิภาพการทำงานคร่ำเครียดอยู่บนพื้นฐานของอัลกอริทึม ( $2y$
สำหรับปักเป้า) ปัจจัยต้นทุน (ปัจจัย 13 ใช้เวลาประมาณ 0.40s บนเครื่อง 3GHz) และเกลือของ 22 ตัวอักษร
การตรวจสอบ
เมื่อคุณดึงข้อมูลแถวที่มีข้อมูลผู้ใช้แล้วให้ตรวจสอบรหัสผ่านในลักษณะนี้:
$given_password = $_POST['password']; // the submitted password
$db_hash = $row['Password']; // field with the password hash
$given_hash = crypt($given_password, $db_hash);
if (isEqual($given_hash, $db_hash)) {
// user password verified
}
// constant time string compare
function isEqual($str1, $str2)
{
$n1 = strlen($str1);
if (strlen($str2) != $n1) {
return false;
}
for ($i = 0, $diff = 0; $i != $n1; ++$i) {
$diff |= ord($str1[$i]) ^ ord($str2[$i]);
}
return !$diff;
}
ในการตรวจสอบรหัสผ่านคุณโทรcrypt()
อีกครั้ง แต่คุณส่งแฮชที่คำนวณไว้ก่อนหน้านี้เป็นค่าเกลือ ค่าที่ส่งคืนจะให้แฮชเดียวกันหากรหัสผ่านที่กำหนดตรงกับแฮช ในการตรวจสอบแฮชมักแนะนำให้ใช้ฟังก์ชันเปรียบเทียบเวลาคงที่เพื่อหลีกเลี่ยงการโจมตีตามเวลา
การแฮชรหัสผ่านด้วย PHP 5.5
PHP 5.5 แนะนำฟังก์ชันการแฮชรหัสผ่านที่คุณสามารถใช้เพื่อลดความซับซ้อนของวิธีการแฮชข้างต้น:
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 13]);
และตรวจสอบ:
if (password_verify($given_password, $db_hash)) {
// password valid
}
ดูเพิ่มเติม: password_hash()
,password_verify()