Sebelum Anda melakukan sesuatu lebih jauh, cobalah untuk memahami perbedaan antara enkripsi dan otentikasi , dan mengapa Anda mungkin menginginkan enkripsi yang diautentikasi daripada hanya enkripsi .
Untuk menerapkan enkripsi yang diautentikasi, Anda ingin Mengenkripsi kemudian MAC. Urutan enkripsi dan otentikasi sangat penting!Salah satu jawaban yang ada untuk pertanyaan ini membuat kesalahan ini; seperti halnya banyak pustaka kriptografi yang ditulis dalam PHP.
Anda harus menghindari penerapan kriptografi Anda sendiri , dan sebagai gantinya menggunakan perpustakaan aman yang ditulis oleh dan ditinjau oleh para ahli kriptografi.
Pembaruan: PHP 7.2 sekarang menyediakan libsodium ! Untuk keamanan terbaik, perbarui sistem Anda untuk menggunakan PHP 7.2 atau lebih tinggi dan hanya mengikuti saran libsodium dalam jawaban ini.
Gunakan libsodium jika Anda memiliki akses PECL (atau sodium_compat jika Anda ingin libsodium tanpa PECL); jika tidak ...
Gunakan defuse / php-enkripsi ; jangan roll kriptografi Anda sendiri!
Kedua perpustakaan yang ditautkan di atas membuatnya mudah dan tidak menyakitkan untuk menerapkan enkripsi terotentikasi ke perpustakaan Anda sendiri.
Jika Anda masih ingin menulis dan menggunakan pustaka kriptografi Anda sendiri, bertentangan dengan kebijaksanaan konvensional setiap ahli kriptografi di Internet, ini adalah langkah-langkah yang harus Anda ambil.
Enkripsi:
- Enkripsi menggunakan AES dalam mode CTR. Anda juga dapat menggunakan GCM (yang menghilangkan kebutuhan untuk MAC yang terpisah). Selain itu, ChaCha20 dan Salsa20 (disediakan oleh libsodium ) adalah stream cipher dan tidak memerlukan mode khusus.
- Kecuali Anda memilih GCM di atas, Anda harus mengotentikasi ciphertext dengan HMAC-SHA-256 (atau, untuk stream cipher, Poly1305 - kebanyakan libsodium API melakukan ini untuk Anda). MAC harus mencakup IV dan juga ciphertext!
Dekripsi:
- Kecuali jika Poly1305 atau GCM digunakan, hitung ulang MAC dari ciphertext dan bandingkan dengan MAC yang dikirim menggunakan
hash_equals()
. Jika gagal, batalkan.
- Dekripsi pesan tersebut.
Pertimbangan Desain Lainnya:
- Jangan kompres apa pun. Ciphertext tidak bisa dimampatkan; mengompresi plaintext sebelum enkripsi dapat menyebabkan kebocoran informasi (misalnya, CRIME dan BREACH pada TLS).
- Pastikan Anda menggunakan
mb_strlen()
dan mb_substr()
, menggunakan '8bit'
mode set karakter untuk mencegah mbstring.func_overload
masalah.
- IVs harus menghasilkan menggunakan CSPRNG ; Jika Anda menggunakan
mcrypt_create_iv()
, JANGAN GUNAKANMCRYPT_RAND
!
- Kecuali Anda menggunakan konstruk AEAD, SELALU mengenkripsi kemudian MAC!
bin2hex()
,, base64_encode()
dll. dapat membocorkan informasi tentang kunci enkripsi Anda melalui waktu cache. Hindari mereka jika memungkinkan.
Bahkan jika Anda mengikuti saran yang diberikan di sini, banyak yang bisa salah dengan kriptografi. Selalu minta pakar kriptografi meninjau penerapan Anda. Jika Anda tidak cukup beruntung untuk menjadi teman pribadi dengan seorang siswa kriptografi di universitas lokal Anda, Anda selalu dapat mencoba Cryptography Stack Exchange untuk meminta saran.
Jika Anda memerlukan analisis profesional tentang implementasi Anda, Anda selalu dapat menyewa tim konsultan keamanan yang memiliki reputasi baik untuk meninjau kode kriptografi PHP Anda (pengungkapan: perusahaan saya).
Penting: Kapan Tidak Menggunakan Enkripsi
Jangan mengenkripsi kata sandi . Anda ingin menggunakan hash sebagai gantinya, menggunakan salah satu dari algoritma hashing kata sandi ini:
Jangan pernah menggunakan fungsi hash tujuan umum (MD5, SHA256) untuk penyimpanan kata sandi.
Jangan mengenkripsi Parameter URL . Ini alat yang salah untuk pekerjaan itu.
Contoh Enkripsi String PHP dengan Libsodium
Jika Anda menggunakan PHP <7.2 atau tidak menginstal libsodium, Anda dapat menggunakan sodium_compat untuk mencapai hasil yang sama (walaupun lebih lambat).
<?php
declare(strict_types=1);
/**
* Encrypt a message
*
* @param string $message - message to encrypt
* @param string $key - encryption key
* @return string
* @throws RangeException
*/
function safeEncrypt(string $message, string $key): string
{
if (mb_strlen($key, '8bit') !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
throw new RangeException('Key is not the correct size (must be 32 bytes).');
}
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$cipher = base64_encode(
$nonce.
sodium_crypto_secretbox(
$message,
$nonce,
$key
)
);
sodium_memzero($message);
sodium_memzero($key);
return $cipher;
}
/**
* Decrypt a message
*
* @param string $encrypted - message encrypted with safeEncrypt()
* @param string $key - encryption key
* @return string
* @throws Exception
*/
function safeDecrypt(string $encrypted, string $key): string
{
$decoded = base64_decode($encrypted);
$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
$ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
$plain = sodium_crypto_secretbox_open(
$ciphertext,
$nonce,
$key
);
if (!is_string($plain)) {
throw new Exception('Invalid MAC');
}
sodium_memzero($ciphertext);
sodium_memzero($key);
return $plain;
}
Kemudian untuk mengujinya:
<?php
// This refers to the previous code block.
require "safeCrypto.php";
// Do this once then store it somehow:
$key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
$message = 'We are all living in a yellow submarine';
$ciphertext = safeEncrypt($message, $key);
$plaintext = safeDecrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
Halite - Libsodium Membuat Lebih Mudah
Salah satu proyek yang saya kerjakan adalah perpustakaan enkripsi bernama Halite , yang bertujuan untuk membuat libsodium lebih mudah dan lebih intuitif.
<?php
use \ParagonIE\Halite\KeyFactory;
use \ParagonIE\Halite\Symmetric\Crypto as SymmetricCrypto;
// Generate a new random symmetric-key encryption key. You're going to want to store this:
$key = new KeyFactory::generateEncryptionKey();
// To save your encryption key:
KeyFactory::save($key, '/path/to/secret.key');
// To load it again:
$loadedkey = KeyFactory::loadEncryptionKey('/path/to/secret.key');
$message = 'We are all living in a yellow submarine';
$ciphertext = SymmetricCrypto::encrypt($message, $key);
$plaintext = SymmetricCrypto::decrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
Semua kriptografi yang mendasarinya ditangani oleh libsodium.
Contoh dengan defuse / php-enkripsi
<?php
/**
* This requires https://github.com/defuse/php-encryption
* php composer.phar require defuse/php-encryption
*/
use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;
require "vendor/autoload.php";
// Do this once then store it somehow:
$key = Key::createNewRandomKey();
$message = 'We are all living in a yellow submarine';
$ciphertext = Crypto::encrypt($message, $key);
$plaintext = Crypto::decrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
Catatan : Crypto::encrypt()
mengembalikan output hex-encoded.
Manajemen Kunci Enkripsi
Jika Anda tergoda untuk menggunakan "kata sandi", berhentilah sekarang. Anda memerlukan kunci enkripsi 128-bit acak, bukan kata sandi yang mudah diingat manusia.
Anda dapat menyimpan kunci enkripsi untuk penggunaan jangka panjang seperti:
$storeMe = bin2hex($key);
Dan, sesuai permintaan, Anda dapat mengambilnya seperti ini:
$key = hex2bin($storeMe);
Saya sangat menyarankan hanya menyimpan kunci yang dibuat secara acak untuk penggunaan jangka panjang dan bukan jenis kata sandi sebagai kunci (atau untuk menurunkan kunci).
Jika Anda menggunakan perpustakaan Defuse:
"Tapi aku benar - benar ingin menggunakan kata sandi."
Itu ide yang buruk, tapi oke, inilah cara melakukannya dengan aman.
Pertama, buat kunci acak dan simpan dalam konstanta.
/**
* Replace this with your own salt!
* Use bin2hex() then add \x before every 2 hex characters, like so:
*/
define('MY_PBKDF2_SALT', "\x2d\xb7\x68\x1a\x28\x15\xbe\x06\x33\xa0\x7e\x0e\x8f\x79\xd5\xdf");
Perhatikan bahwa Anda menambahkan pekerjaan ekstra dan bisa menggunakan konstanta ini sebagai kuncinya dan menyelamatkan diri Anda dari sakit hati!
Kemudian gunakan PBKDF2 (seperti itu) untuk mendapatkan kunci enkripsi yang sesuai dari kata sandi Anda daripada langsung mengenkripsi dengan kata sandi Anda.
/**
* Get an AES key from a static password and a secret salt
*
* @param string $password Your weak password here
* @param int $keysize Number of bytes in encryption key
*/
function getKeyFromPassword($password, $keysize = 16)
{
return hash_pbkdf2(
'sha256',
$password,
MY_PBKDF2_SALT,
100000, // Number of iterations
$keysize,
true
);
}
Jangan hanya menggunakan kata sandi 16 karakter. Kunci enkripsi Anda akan rusak secara komikal.