ส่วนตัวฉันจะใช้mcrypt
เหมือนคนอื่นโพสต์ แต่มีอีกมากมายที่ต้องทราบ ...
ฉันจะเข้ารหัสและถอดรหัสรหัสผ่านใน PHP ได้อย่างไร
ดูด้านล่างสำหรับคลาสที่แข็งแกร่งที่ดูแลทุกอย่างสำหรับคุณ:
อัลกอริทึมที่ปลอดภัยที่สุดในการเข้ารหัสรหัสผ่านคืออะไร?
ปลอดภัยที่สุด ? อะไรก็ได้. วิธีที่ปลอดภัยที่สุดหากคุณกำลังจะเข้ารหัสคือการป้องกันช่องโหว่การเปิดเผยข้อมูล (XSS, การรวมระยะไกล ฯลฯ ) หากออกมาแล้วผู้โจมตีสามารถถอดรหัสได้ในที่สุด (ไม่มีการเข้ารหัสใด ๆ ที่ไม่สามารถย้อนกลับได้ 100% หากไม่มีคีย์ - เนื่องจาก @NullUserException ชี้ให้เห็นว่าสิ่งนี้ไม่เป็นความจริงโดยสิ้นเชิงมีรูปแบบการเข้ารหัสบางอย่างที่ไม่สามารถถอดรหัสได้เช่นOneTimePad ) .
ฉันจะเก็บกุญแจส่วนตัวได้ที่ไหน
สิ่งที่ฉันจะทำคือใช้ 3 ปุ่ม หนึ่งคือผู้ใช้ที่ให้มาหนึ่งคือโปรแกรมเฉพาะและอื่น ๆ เป็นของผู้ใช้เฉพาะ (เช่นเกลือ) คีย์เฉพาะแอปพลิเคชันสามารถจัดเก็บได้ทุกที่ (ในไฟล์ปรับแต่งภายนอกของเว็บรูทในตัวแปรสภาพแวดล้อม ฯลฯ ) ผู้ใช้เฉพาะจะถูกเก็บไว้ในคอลัมน์ในฐานข้อมูลถัดจากรหัสผ่านที่เข้ารหัส ผู้ใช้ที่ให้มาจะไม่ถูกจัดเก็บ จากนั้นคุณจะทำสิ่งนี้:
$key = $userKey . $serverKey . $userSuppliedKey;
ประโยชน์ที่ได้รับคือกุญแจใด ๆ ที่สามารถประนีประนอมได้โดยไม่ทำให้ข้อมูลถูกบุกรุก หากมีการโจมตี SQL Injection พวกเขาจะได้รับ$userKey
แต่ไม่ได้อื่น ๆ 2. หากมีเซิร์ฟเวอร์ท้องถิ่นใช้ประโยชน์จากพวกเขาจะได้รับ$userKey
และแต่ไม่สาม$serverKey
$userSuppliedKey
หากพวกเขาเอาชนะผู้ใช้ด้วยประแจพวกเขาสามารถได้รับ$userSuppliedKey
แต่ไม่ใช่อีก 2 คน (แต่หลังจากนั้นอีกครั้งหากผู้ใช้ทุบด้วยประแจคุณจะสายเกินไป)
แทนที่จะเก็บรหัสส่วนตัวเป็นความคิดที่ดีที่จะกำหนดให้ผู้ใช้ป้อนรหัสส่วนตัวทุกครั้งที่ต้องการรหัสผ่านหรือไม่ (ผู้ใช้แอปพลิเคชันนี้สามารถเชื่อถือได้)
อย่างแน่นอน ในความเป็นจริงนั่นเป็นวิธีเดียวที่ฉันจะทำได้ มิฉะนั้นคุณจะต้องจัดเก็บรุ่นที่ไม่ได้เข้ารหัสในรูปแบบที่เก็บข้อมูลถาวร (หน่วยความจำที่แชร์เช่น APC หรือ memcached หรือในไฟล์เซสชัน) นั่นคือการเปิดเผยตัวเองเพื่อประนีประนอมเพิ่มเติม อย่าเก็บรหัสผ่านเวอร์ชันที่ไม่ได้เข้ารหัสในสิ่งใด ๆ ยกเว้นตัวแปรโลคัล
รหัสผ่านสามารถขโมยและถอดรหัสได้ในรูปแบบใด? ฉันต้องระวังอะไรบ้าง
รูปแบบของการประนีประนอมระบบของคุณจะช่วยให้พวกเขาดูข้อมูลที่เข้ารหัส หากพวกเขาสามารถฉีดโค้ดหรือไปยังระบบไฟล์ของคุณพวกเขาสามารถดูข้อมูลที่ถอดรหัสได้ (เนื่องจากพวกเขาสามารถแก้ไขไฟล์ที่ถอดรหัสข้อมูล) การโจมตี Replay หรือ MITM ทุกรูปแบบจะทำให้พวกเขาสามารถเข้าถึงกุญแจที่เกี่ยวข้องได้อย่างเต็มที่ การสูดดมทราฟฟิก HTTP แบบ raw จะให้กุญแจแก่พวกเขา
ใช้ SSL สำหรับการรับส่งข้อมูลทั้งหมด และตรวจสอบให้แน่ใจว่าไม่มีสิ่งใดบนเซิร์ฟเวอร์ที่มีช่องโหว่ใด ๆ (CSRF, XSS, การฉีด SQL, การเพิ่มสิทธิ์, การเรียกใช้โค้ดจากระยะไกล ฯลฯ )
แก้ไข:นี่คือการใช้คลาส PHP ของวิธีการเข้ารหัสที่แข็งแกร่ง:
/**
* 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);
}
}
โปรดทราบว่าฉันใช้ฟังก์ชั่นเพิ่มใน PHP hash_equals
5.6: หากคุณต่ำกว่า 5.6 คุณสามารถใช้ฟังก์ชั่นทดแทนนี้ซึ่งใช้ฟังก์ชั่นการเปรียบเทียบที่ปลอดภัยเวลาโดยใช้การตรวจสอบ 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);
}
การใช้งาน:
$e = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$encryptedData = $e->encrypt($data, $key);
จากนั้นเพื่อถอดรหัส:
$e2 = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$data = $e2->decrypt($encryptedData, $key);
โปรดทราบว่าฉันใช้$e2
ครั้งที่สองเพื่อแสดงอินสแตนซ์ต่าง ๆ ให้คุณจะยังคงถอดรหัสข้อมูลได้อย่างถูกต้อง
ตอนนี้มันใช้งานอย่างไร / ทำไมจึงใช้กับโซลูชันอื่น:
คีย์
กุญแจไม่ได้ใช้โดยตรง กุญแจจะถูกยืดออกโดยการสืบทอด PBKDF2 มาตรฐาน
กุญแจที่ใช้สำหรับการเข้ารหัสนั้นมีความเป็นเอกลักษณ์สำหรับบล็อคข้อความที่เข้ารหัสทุกครั้ง คีย์ที่ให้มาจึงกลายเป็น "มาสเตอร์คีย์" คลาสนี้จัดเตรียมการหมุนคีย์สำหรับการเข้ารหัสและคีย์การรับรองความถูกต้อง
หมายเหตุสำคัญที่$rounds
พารามิเตอร์มีการกำหนดค่าสำหรับคีย์สุ่มที่แท้จริงของความแข็งแรงเพียงพอ (128 บิต Cryptographically การรักษาความปลอดภัยแบบสุ่มอย่างน้อย) หากคุณจะใช้รหัสผ่านหรือคีย์ที่ไม่ใช่แบบสุ่ม (หรือสุ่มน้อยกว่า 128 บิตของ CS Random) คุณต้องเพิ่มพารามิเตอร์นี้ ฉันขอแนะนำอย่างน้อย 10,000 รหัสผ่าน (ยิ่งคุณสามารถจ่ายได้ดีเท่าไรก็ยิ่งดี แต่มันจะเพิ่มใน runtime) ...
ความสมบูรณ์ของข้อมูล
- รุ่นที่ได้รับการปรับปรุงใช้ ENCRYPT-THEN-MAC ซึ่งเป็นวิธีที่ดีกว่ามากในการรับรองความถูกต้องของข้อมูลที่เข้ารหัส
การเข้ารหัสลับ:
- มันใช้ mcrypt เพื่อทำการเข้ารหัส ฉันอยากจะแนะนำให้ใช้อย่างใดอย่างหนึ่ง
MCRYPT_BLOWFISH
หรือMCRYPT_RIJNDAEL_128
cyphers และMCRYPT_MODE_CBC
สำหรับโหมด มันแข็งแรงเพียงพอและยังค่อนข้างเร็ว (วงจรการเข้ารหัสและถอดรหัสใช้เวลาประมาณ 1/2 วินาทีในเครื่องของฉัน)
ทีนี้เมื่อถึงจุดที่ 3 จากรายการแรกสิ่งที่จะให้คุณคือฟังก์ชั่นเช่นนี้:
function makeKey($userKey, $serverKey, $userSuppliedKey) {
$key = hash_hmac('sha512', $userKey, $serverKey);
$key = hash_hmac('sha512', $key, $userSuppliedKey);
return $key;
}
คุณสามารถยืดมันในmakeKey()
ฟังก์ชั่นได้ แต่เนื่องจากมันจะยืดตัวในภายหลัง
สำหรับขนาดหน่วยเก็บข้อมูลนั้นขึ้นอยู่กับข้อความธรรมดา ปักเป้าใช้ขนาดบล็อก 8 ไบต์ดังนั้นคุณจะมี:
- 16 ไบต์สำหรับเกลือ
- 64 ไบต์สำหรับ hmac
- ความยาวข้อมูล
- การเสริมความยาวของข้อมูล% 8 == 0
ดังนั้นสำหรับแหล่งข้อมูล 16 อักขระจะมีการเข้ารหัสข้อมูล 16 อักขระ ดังนั้นหมายความว่าขนาดข้อมูลที่เข้ารหัสจริงคือ 16 ไบต์เนื่องจากการขยาย จากนั้นเพิ่ม 16 ไบต์สำหรับเกลือและ 64 ไบต์สำหรับ hmac และขนาดที่เก็บไว้ทั้งหมดคือ 96 ไบต์ ดังนั้นจึงมีค่าใช้จ่าย 80 ตัวอักษรที่ดีที่สุดและที่แย่ที่สุดค่าตัวอักษร 87 ตัว ...
ฉันหวังว่าจะช่วย ...
หมายเหตุ: 12/11/12: ฉันเพิ่งอัปเดตคลาสนี้ด้วยวิธีการเข้ารหัสที่ดีกว่ามากใช้คีย์ที่ได้รับดีกว่าและแก้ไขการสร้าง MAC ...