Kata pengantar
Dimulai dengan definisi tabel Anda:
- UserID
- Fname
- Lname
- Email
- Password
- IV
Berikut perubahannya:
- Bidang
Fname
, Lname
dan Email
akan dienkripsi menggunakan cipher simetris, disediakan oleh OpenSSL ,
- The
IV
lapangan akan menyimpan vektor inisialisasi yang digunakan untuk enkripsi. Persyaratan penyimpanan tergantung pada sandi dan mode yang digunakan; lebih lanjut tentang ini nanti.
- The
Password
lapangan akan diacak dengan menggunakan satu arah hash password,
Enkripsi
Sandi dan mode
Memilih cipher dan mode enkripsi terbaik berada di luar cakupan jawaban ini, tetapi pilihan terakhir memengaruhi ukuran kunci enkripsi dan vektor inisialisasi; untuk posting ini kita akan menggunakan AES-256-CBC yang memiliki ukuran blok tetap 16 byte dan ukuran kunci 16, 24 atau 32 byte.
Kunci enkripsi
Kunci enkripsi yang baik adalah gumpalan biner yang dihasilkan dari generator nomor acak yang andal. Contoh berikut akan direkomendasikan (> = 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
Ini dapat dilakukan sekali atau beberapa kali (jika Anda ingin membuat rantai kunci enkripsi). Jaga kerahasiaannya sebisa mungkin.
IV
Vektor inisialisasi menambahkan keacakan pada enkripsi dan diperlukan untuk mode CBC. Nilai-nilai ini idealnya harus digunakan hanya sekali (secara teknis sekali per kunci enkripsi), jadi pembaruan ke bagian mana pun dari baris harus memperbaruinya.
Sebuah fungsi disediakan untuk membantu Anda menghasilkan IV:
$iv_size = 16; // 128 bits
$iv = openssl_random_pseudo_bytes($iv_size, $strong);
Contoh
Mari mengenkripsi bidang nama, menggunakan yang sebelumnya $encryption_key
dan $iv
; untuk melakukan ini, kita harus memasukkan data kita ke ukuran blok:
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
);
Persyaratan penyimpanan
Output terenkripsi, seperti IV, adalah biner; Menyimpan nilai-nilai ini dalam database bisa dilakukan dengan menggunakan tipe kolom yang ditentukan seperti BINARY
atau VARBINARY
.
Nilai keluaran, seperti IV, adalah biner; untuk menyimpan nilai-nilai tersebut di MySQL, pertimbangkan untuk menggunakan kolom BINARY
atauVARBINARY
. Jika ini bukan pilihan, Anda juga dapat mengonversi data biner menjadi representasi tekstual menggunakan base64_encode()
atau bin2hex()
, melakukannya membutuhkan ruang penyimpanan antara 33% hingga 100% lebih banyak.
Dekripsi
Dekripsi dari nilai yang disimpan serupa:
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
));
Enkripsi terautentikasi
Anda selanjutnya dapat meningkatkan integritas teks sandi yang dihasilkan dengan menambahkan tanda tangan yang dihasilkan dari kunci rahasia (berbeda dari kunci enkripsi) dan teks sandi. Sebelum teks sandi didekripsi, tanda tangan diverifikasi terlebih dahulu (sebaiknya dengan metode perbandingan waktu konstan).
Contoh
// 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
}
Lihat juga: hash_equals()
Hashing
Menyimpan kata sandi yang dapat dibalik dalam database Anda harus sebisa mungkin dihindari; Anda hanya ingin memverifikasi kata sandi daripada mengetahui isinya. Jika pengguna kehilangan kata sandinya, lebih baik izinkan mereka menyetel ulang daripada mengirimkan kata sandi asli kepada mereka (pastikan penyetelan ulang kata sandi hanya dapat dilakukan untuk waktu yang terbatas).
Menerapkan fungsi hash adalah operasi satu arah; setelah itu dapat digunakan dengan aman untuk verifikasi tanpa mengungkap data asli; untuk kata sandi, metode brute force adalah pendekatan yang layak untuk mengungkapnya karena panjangnya yang relatif pendek dan pilihan kata sandi yang buruk dari banyak orang.
Algoritme hash seperti MD5 atau SHA1 dibuat untuk memverifikasi konten file terhadap nilai hash yang diketahui. Mereka sangat dioptimalkan untuk membuat verifikasi ini secepat mungkin dengan tetap akurat. Mengingat ruang keluaran yang relatif terbatas, mudah untuk membangun database dengan kata sandi yang diketahui dan keluaran hash masing-masing, tabel pelangi.
Menambahkan salt ke kata sandi sebelum melakukan hashing akan membuat tabel pelangi tidak berguna, tetapi kemajuan perangkat keras baru-baru ini membuat pencarian brute force menjadi pendekatan yang layak. Itulah mengapa Anda memerlukan algoritme hashing yang sengaja lambat dan tidak mungkin dioptimalkan. Ini juga harus dapat meningkatkan beban untuk perangkat keras yang lebih cepat tanpa mempengaruhi kemampuan untuk memverifikasi hash kata sandi yang ada untuk membuatnya menjadi bukti di masa mendatang.
Saat ini ada dua pilihan populer yang tersedia:
- PBKDF2 (Fungsi Penurunan Kunci Berbasis Kata Sandi v2)
- bcrypt (alias Blowfish)
Jawaban ini akan menggunakan contoh dengan bcrypt.
Generasi
Hash kata sandi dapat dibuat seperti ini:
$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);
Salt dibuat openssl_random_pseudo_bytes()
untuk membentuk gumpalan data acak yang kemudian dijalankan base64_encode()
dan strtr()
dicocokkan dengan alfabet yang diperlukan [A-Za-z0-9/.]
.
The crypt()
melakukan fungsi hashing berdasarkan algoritma ( $2y$
untuk Blowfish), faktor biaya (faktor dari 13 Dibutuhkan sekitar 0.40s pada mesin 3GHz) dan garam 22 karakter.
Validasi
Setelah Anda mengambil baris yang berisi informasi pengguna, Anda memvalidasi kata sandi dengan cara ini:
$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;
}
Untuk memverifikasi kata sandi, Anda memanggil crypt()
lagi tetapi Anda meneruskan hash yang dihitung sebelumnya sebagai nilai garam. Nilai yang dikembalikan menghasilkan hash yang sama jika kata sandi yang diberikan cocok dengan hash. Untuk memverifikasi hash, sering kali disarankan untuk menggunakan fungsi perbandingan waktu konstan untuk menghindari serangan waktu.
Hash sandi dengan PHP 5.5
PHP 5.5 memperkenalkan fungsi hashing kata sandi yang dapat Anda gunakan untuk menyederhanakan metode hashing di atas:
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 13]);
Dan memverifikasi:
if (password_verify($given_password, $db_hash)) {
// password valid
}
Lihat juga: password_hash()
,password_verify()