คุณใช้ bcrypt สำหรับแฮ็ชรหัสผ่านใน PHP ได้อย่างไร


1255

ทุกครั้งที่ฉันได้ยินคำแนะนำ "ใช้ bcrypt สำหรับเก็บรหัสผ่านใน PHP, กฎ bcrypt"

แต่สิ่งที่เป็นbcrypt? PHP ไม่มีฟังก์ชั่นดังกล่าว Wikipedia พูดพล่ามเกี่ยวกับยูทิลิตี้การเข้ารหัสไฟล์และการค้นหาเว็บเพียงแค่แสดงการใช้งานบางอย่างของBlowfishในภาษาต่างๆ ตอนนี้ Blowfish ยังมีอยู่ใน PHP ผ่านmcryptแต่วิธีที่ช่วยในการจัดเก็บรหัสผ่าน? ปักเป้าเป็นเลขศูนย์วัตถุประสงค์ทั่วไปมันทำงานได้สองวิธี หากสามารถเข้ารหัสได้ก็สามารถถอดรหัสได้ รหัสผ่านต้องใช้ฟังก์ชันแฮชแบบทางเดียว

คำอธิบายคืออะไร


13
คำถามนี้ได้รับการแก้ไขก่อนหน้านี้และคำแนะนำในการใช้ห้องสมุดมาตรฐานนั้นยอดเยี่ยม ความปลอดภัยเป็นเรื่องที่ซับซ้อนและการใช้แพ็คเกจที่ออกแบบโดยคนที่รู้ว่าพวกเขากำลังทำอะไรอยู่คุณกำลังช่วยเหลือตัวเองเท่านั้น
eykanal

59
@eykanal - หน้าการที่ไม่ได้พูดถึง bcrypt มากน้อยอธิบายว่ามันคืออะไร
Vilx-

8
@eykanal - ฉันไม่ขอคำอธิบายว่ามันทำงานอย่างไร ฉันแค่อยากรู้ว่ามันคืออะไร เพราะสิ่งที่ฉันสามารถขุดในเน็ตภายใต้คำว่า "bcrypt" ไม่สามารถใช้ในการแฮชรหัสผ่านได้ ไม่ใช่โดยตรงและไม่ใช่ใน PHP ตกลงตอนนี้ฉันเข้าใจแล้วว่ามันเป็นแพคเกจ "phpass" ที่ใช้ปักเป้าเพื่อเข้ารหัสรหัสผ่านของคุณด้วยรหัสที่ได้มาจากรหัสผ่านของคุณ แต่การอ้างอิงมันว่า "bcrypt" นั้นทำให้เข้าใจผิดอย่างรุนแรงและนั่นคือสิ่งที่ฉันต้องการชี้แจงในคำถามนี้
Vilx-

3
@Vilx: ฉันได้เพิ่มข้อมูลเพิ่มเติมว่าทำไมbcryptเป็นขั้นตอนวิธีการแฮชทางเดียวเมื่อเทียบกับรูปแบบการเข้ารหัสในคำตอบของฉัน มีความเข้าใจผิดทั้งหมดนี้bcryptเป็นเพียงแค่ปักเป้าเมื่อจริง ๆ แล้วมันมีตารางคีย์ที่แตกต่างกันโดยสิ้นเชิงซึ่งทำให้มั่นใจได้ว่าข้อความธรรมดาไม่สามารถกู้คืนจากข้อความตัวเลขโดยไม่ทราบสถานะเริ่มต้นของตัวเลข (เกลือรอบคีย์)
Andrew Moore

1
ดูที่กรอบการแฮชรหัสผ่าน PHP แบบพกพา Openwall ของPHP (PHPass) มันแข็งต่อการโจมตีทั่วไปจำนวนมากในรหัสผ่านผู้ใช้
jww

คำตอบ:


1065

bcryptเป็นอัลกอริทึมคร่ำเครียดซึ่งปรับขนาดได้ด้วยฮาร์ดแวร์ (ผ่านจำนวนรอบที่กำหนด) ความเชื่องช้าและรอบที่หลากหลายทำให้มั่นใจได้ว่าผู้โจมตีจะต้องใช้เงินจำนวนมากและฮาร์ดแวร์เพื่อให้สามารถถอดรหัสรหัสผ่านของคุณได้ เพิ่มเกลือต่อรหัสผ่านนั้น( bcryptต้องมีเกลือ) และคุณสามารถมั่นใจได้ว่าการโจมตีนั้นไม่สามารถทำได้จริงโดยไม่ต้องมีเงินหรือฮาร์ดแวร์จำนวนมหาศาล

bcryptใช้อัลกอริทึมEksblowfishเพื่อแฮรหัสผ่าน ในขณะที่ขั้นตอนการเข้ารหัสของEksblowfishและBlowfishนั้นเหมือนกันขั้นตอนการกำหนดเวลาที่สำคัญของEksblowfishทำให้มั่นใจได้ว่าสถานะใด ๆ ที่ตามมาจะขึ้นอยู่กับทั้งเกลือและกุญแจ (รหัสผ่านผู้ใช้) และรัฐไม่สามารถคำนวณล่วงหน้าได้ เนื่องจากความแตกต่างที่สำคัญbcryptนี้เป็นอัลกอริทึมการแฮชแบบทางเดียว คุณไม่สามารถเรียกคืนรหัสผ่านแบบข้อความธรรมดาได้โดยที่ไม่รู้ว่ามีรอบและปุ่ม (รหัสผ่าน) อยู่แล้ว [ ที่มา ]

วิธีใช้ bcrypt:

ใช้ PHP> = 5.5-DEV

ฟังก์ชั่นรหัสผ่านคร่ำเครียดขณะนี้ได้สร้างขึ้นโดยตรงใน PHP> = 5.5 ตอนนี้คุณสามารถใช้password_hash()เพื่อสร้างbcryptรหัสผ่านแฮช:

<?php
// Usage 1:
echo password_hash('rasmuslerdorf', PASSWORD_DEFAULT)."\n";
// $2y$10$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// For example:
// $2y$10$.vGA1O9wmRjrwAVXD98HNOgsNpDczlqm3Jq7KnEd1rVAGv3Fykk1a

// Usage 2:
$options = [
  'cost' => 11
];
echo password_hash('rasmuslerdorf', PASSWORD_BCRYPT, $options)."\n";
// $2y$11$6DP.V0nO7YI3iSki4qog6OQI5eiO6Jnjsqg7vdnb.JgGIsxniOn4C

ในการตรวจสอบรหัสผ่านที่ผู้ใช้แจ้งกับแฮชที่มีอยู่คุณสามารถใช้รหัสpassword_verify()ดังกล่าว:

<?php
// See the password_hash() example to see where this came from.
$hash = '$2y$07$BCryptRequires22Chrcte/VlQH0piJtjXl.0t1XkA8pw9dMXTpOq';

if (password_verify('rasmuslerdorf', $hash)) {
    echo 'Password is valid!';
} else {
    echo 'Invalid password.';
}

ใช้ PHP> = 5.3.7, <5.5-DEV (เช่น RedHat PHP> = 5.3.3)

มีไลบรารีความเข้ากันได้ในGitHub ที่สร้างขึ้นตามรหัสแหล่งที่มาของฟังก์ชั่นด้านบนที่เขียนด้วยภาษา C ซึ่งมีฟังก์ชั่นเดียวกัน เมื่อติดตั้งไลบรารีความเข้ากันได้แล้วการใช้งานจะเหมือนกับด้านบน (ลบเครื่องหมายคำสั่งแบบย่อของชวเลขถ้าคุณยังคงอยู่ในสาขา 5.3.x)

ใช้ PHP <5.3.7 (เลิกใช้)

คุณสามารถใช้crypt()ฟังก์ชั่นเพื่อสร้าง bcrypt hash ของสายอักขระอินพุต คลาสนี้สามารถสร้างเกลือโดยอัตโนมัติและตรวจสอบแฮชที่มีอยู่กับอินพุต หากคุณใช้ PHP เวอร์ชันที่สูงกว่าหรือเท่ากับ 5.3.7 ขอแนะนำอย่างยิ่งให้คุณใช้ฟังก์ชันในตัวหรือไลบรารีที่เข้ากันได้ ทางเลือกนี้จัดทำขึ้นเพื่อวัตถุประสงค์ทางประวัติศาสตร์เท่านั้น

class Bcrypt{
  private $rounds;

  public function __construct($rounds = 12) {
    if (CRYPT_BLOWFISH != 1) {
      throw new Exception("bcrypt not supported in this installation. See http://php.net/crypt");
    }

    $this->rounds = $rounds;
  }

  public function hash($input){
    $hash = crypt($input, $this->getSalt());

    if (strlen($hash) > 13)
      return $hash;

    return false;
  }

  public function verify($input, $existingHash){
    $hash = crypt($input, $existingHash);

    return $hash === $existingHash;
  }

  private function getSalt(){
    $salt = sprintf('$2a$%02d$', $this->rounds);

    $bytes = $this->getRandomBytes(16);

    $salt .= $this->encodeBytes($bytes);

    return $salt;
  }

  private $randomState;
  private function getRandomBytes($count){
    $bytes = '';

    if (function_exists('openssl_random_pseudo_bytes') &&
        (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) { // OpenSSL is slow on Windows
      $bytes = openssl_random_pseudo_bytes($count);
    }

    if ($bytes === '' && is_readable('/dev/urandom') &&
       ($hRand = @fopen('/dev/urandom', 'rb')) !== FALSE) {
      $bytes = fread($hRand, $count);
      fclose($hRand);
    }

    if (strlen($bytes) < $count) {
      $bytes = '';

      if ($this->randomState === null) {
        $this->randomState = microtime();
        if (function_exists('getmypid')) {
          $this->randomState .= getmypid();
        }
      }

      for ($i = 0; $i < $count; $i += 16) {
        $this->randomState = md5(microtime() . $this->randomState);

        if (PHP_VERSION >= '5') {
          $bytes .= md5($this->randomState, true);
        } else {
          $bytes .= pack('H*', md5($this->randomState));
        }
      }

      $bytes = substr($bytes, 0, $count);
    }

    return $bytes;
  }

  private function encodeBytes($input){
    // The following is code from the PHP Password Hashing Framework
    $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

    $output = '';
    $i = 0;
    do {
      $c1 = ord($input[$i++]);
      $output .= $itoa64[$c1 >> 2];
      $c1 = ($c1 & 0x03) << 4;
      if ($i >= 16) {
        $output .= $itoa64[$c1];
        break;
      }

      $c2 = ord($input[$i++]);
      $c1 |= $c2 >> 4;
      $output .= $itoa64[$c1];
      $c1 = ($c2 & 0x0f) << 2;

      $c2 = ord($input[$i++]);
      $c1 |= $c2 >> 6;
      $output .= $itoa64[$c1];
      $output .= $itoa64[$c2 & 0x3f];
    } while (true);

    return $output;
  }
}

คุณสามารถใช้รหัสนี้เช่นนี้:

$bcrypt = new Bcrypt(15);

$hash = $bcrypt->hash('password');
$isGood = $bcrypt->verify('password', $hash);

หรือคุณอาจจะใช้แบบพกพา PHP Hashing กรอบ


7
@ The Wicked Flea: ขออภัยที่ทำให้คุณผิดหวัง แต่mt_rand()ก็มีการใช้เวลาปัจจุบันและรหัสกระบวนการปัจจุบัน โปรดดูในGENERATE_SEED() /ext/standard/php_rand.h
Andrew Moore

53
@ ไมค์: ไปข้างหน้ามันมีเหตุผลอย่างนั้น!
Andrew Moore

14
สำหรับทุกคนที่คิดว่าพวกเขาจำเป็นต้องปรับเปลี่ยนจุดเริ่มต้นของ $ salt string ในฟังก์ชั่น getSalt ซึ่งไม่จำเป็น $ 2a $ __ เป็นส่วนหนึ่งของเกลือ CRYPT_BLOWFISH จากเอกสาร: "ปักเป้าปักหลักด้วยเกลือดังนี้:" $ 2a $ ", พารามิเตอร์ต้นทุนสองหลัก," $ "และ 22 หลักจากตัวอักษร"
jwinn

18
@MichaelLang: สิ่งที่ดีcrypt()คือการตรวจสอบโดยเพื่อนและตรวจสอบแล้ว รหัสด้านบนเรียก PHP ของcrypt()ซึ่งเรียกcrypt()ฟังก์ชั่นPOSIX ทั้งหมดโค้ดข้างต้นไม่มากขึ้นคือการสร้างเกลือสุ่ม (ซึ่งไม่จำเป็นต้องเป็นแบบเข้ารหัสรักษาความปลอดภัย, เกลือจะไม่ถือว่าเป็นความลับ) crypt()ก่อนที่จะเรียก บางทีคุณควรทำวิจัยเล็กน้อยด้วยตัวเองก่อนโทรหาหมาป่า
Andrew Moore

31
โปรดทราบว่าคำตอบนี้ในขณะที่ดีกำลังเริ่มแสดงอายุของมัน รหัสนี้ (เช่นใดการดำเนินงาน PHP อาศัยcrypt()) อยู่ภายใต้ช่องโหว่ความปลอดภัยก่อน 5.3.7 และเป็น (เล็กน้อย) ไม่มีประสิทธิภาพ 5.3.7 การโพสต์ - รายละเอียดของปัญหาที่เกี่ยวข้องสามารถพบได้ที่นี่ โปรดทราบว่าตอนนี้API การแฮชรหัสผ่านใหม่( lib แบบย้อนหลัง compat ) ตอนนี้เป็นวิธีที่ต้องการในการใช้การแฮชรหัสผ่าน bcrypt ในแอปพลิเคชันของคุณ
DaveRandom

295

ดังนั้นคุณต้องการใช้ bcrypt หรือไม่ ! น่ากลัว อย่างไรก็ตามเช่นเดียวกับด้านอื่น ๆ ของการเข้ารหัสคุณไม่ควรทำเอง หากคุณจำเป็นต้องกังวลเกี่ยวกับสิ่งต่าง ๆ เช่นการจัดการคีย์หรือการจัดเก็บเกลือหรือสร้างตัวเลขสุ่มแสดงว่าคุณทำผิด

เหตุผลง่าย: มันจึงง่ายนิดที่จะกรูขึ้น bcrypt ในความเป็นจริงหากคุณดูโค้ดเกือบทุกชิ้นในหน้านี้คุณจะสังเกตเห็นว่ามันละเมิดปัญหาทั่วไปอย่างน้อยหนึ่งปัญหา

เผชิญหน้ากับมันการเข้ารหัสยาก

ทิ้งไว้ให้ผู้เชี่ยวชาญ ปล่อยไว้สำหรับคนที่มีหน้าที่ดูแลห้องสมุดเหล่านี้ หากคุณต้องการตัดสินใจคุณกำลังทำผิด

ให้ใช้ไลบรารีแทน มีอยู่หลายอย่างขึ้นอยู่กับความต้องการของคุณ

ห้องสมุด

นี่คือรายละเอียดของ API ทั่วไปบางส่วน

PHP 5.5 API - (ใช้ได้สำหรับ 5.3.7+)

เริ่มต้นใน PHP 5.5 จะมีการแนะนำ API ใหม่สำหรับรหัสผ่านการแฮช นอกจากนี้ยังมีไลบรารีความเข้ากันได้แบบชิม (โดยฉัน) สำหรับ 5.3.7+ นี้มีประโยชน์ในการเป็น peer-reviewed และง่ายในการดำเนินการใช้งาน

function register($username, $password) {
    $hash = password_hash($password, PASSWORD_BCRYPT);
    save($username, $hash);
}

function login($username, $password) {
    $hash = loadHashByUsername($username);
    if (password_verify($password, $hash)) {
        //login
    } else {
        // failure
    }
}

จริงๆแล้วมันมีจุดประสงค์เพื่อให้ง่ายมาก

แหล่งข้อมูล:

Zend \ Crypt \ รหัสผ่าน \ Bcrypt (5.3.2+)

นี่เป็นอีก API ที่คล้ายกับ PHP 5.5 และทำหน้าที่คล้ายกัน

function register($username, $password) {
    $bcrypt = new Zend\Crypt\Password\Bcrypt();
    $hash = $bcrypt->create($password);
    save($user, $hash);
}

function login($username, $password) {
    $hash = loadHashByUsername($username);
    $bcrypt = new Zend\Crypt\Password\Bcrypt();
    if ($bcrypt->verify($password, $hash)) {
        //login
    } else {
        // failure
    }
}

แหล่งข้อมูล:

PasswordLib

นี่เป็นวิธีที่แตกต่างกันเล็กน้อยในการแฮชรหัสผ่าน PasswordLib รองรับอัลกอริธึมการแฮชจำนวนมากแทนที่จะสนับสนุน bcrypt เพียงอย่างเดียว ส่วนใหญ่จะเป็นประโยชน์ในบริบทที่คุณต้องการสนับสนุนความเข้ากันได้กับระบบเดิมและระบบที่แตกต่างกันซึ่งอาจอยู่นอกเหนือการควบคุมของคุณ สนับสนุนอัลกอริทึมการแปลงแป้นพิมพ์จำนวนมาก และได้รับการสนับสนุน 5.3.2+

function register($username, $password) {
    $lib = new PasswordLib\PasswordLib();
    $hash = $lib->createPasswordHash($password, '$2y$', array('cost' => 12));
    save($user, $hash);
}

function login($username, $password) {
    $hash = loadHashByUsername($username);
    $lib = new PasswordLib\PasswordLib();
    if ($lib->verifyPasswordHash($password, $hash)) {
        //login
    } else {
        // failure
    }
}

อ้างอิง:

  • รหัสที่มา / เอกสารประกอบ: GitHub

phpass

นี่คือเลเยอร์ที่รองรับ bcrypt แต่ก็รองรับอัลกอริธึมที่ค่อนข้างแรงซึ่งมีประโยชน์ถ้าคุณไม่สามารถเข้าถึง PHP> = 5.3.2 ... จริง ๆ แล้วรองรับ PHP 3.0+ (แม้ว่าจะไม่ใช่ bcrypt)

function register($username, $password) {
    $phpass = new PasswordHash(12, false);
    $hash = $phpass->HashPassword($password);
    save($user, $hash);
}

function login($username, $password) {
    $hash = loadHashByUsername($username);
    $phpass = new PasswordHash(12, false);
    if ($phpass->CheckPassword($password, $hash)) {
        //login
    } else {
        // failure
    }
}

ทรัพยากร

หมายเหตุ:อย่าใช้ทางเลือก PHPASS ที่ไม่ได้โฮสต์บน openwall พวกเขาเป็นโครงการที่แตกต่าง !!!

เกี่ยวกับ BCrypt

หากคุณสังเกตเห็นไลบรารีเหล่านี้ทุกตัวจะคืนค่าสตริง นั่นเป็นเพราะวิธีการทำงานของ BCrypt ภายใน และมีคำตอบมากมายเกี่ยวกับเรื่องนั้น นี่คือตัวเลือกที่ฉันเขียนว่าฉันจะไม่คัดลอก / วางที่นี่ แต่ลิงก์ไปที่:

สรุป

มีตัวเลือกที่แตกต่างกันมากมาย สิ่งที่คุณเลือกนั้นขึ้นอยู่กับคุณ อย่างไรก็ตามฉันขอแนะนำให้คุณใช้หนึ่งในห้องสมุดด้านบนเพื่อจัดการสิ่งนี้ให้คุณ

ถ้าคุณใช้crypt()โดยตรงคุณอาจทำผิดพลาด หากรหัสของคุณใช้hash()(หรือmd5()หรือsha1()) โดยตรงคุณเกือบทำอะไรผิดพลาด

แค่ใช้ห้องสมุด ...


7
เกลือจะต้องถูกสร้างแบบสุ่ม แต่ไม่จำเป็นต้องมาจากแหล่งสุ่มที่ปลอดภัย เกลือไม่ได้เป็นความลับ ความสามารถในการคาดเดาเกลือต่อไปไม่มีผลกระทบต่อความปลอดภัยที่แท้จริง ตราบใดที่พวกเขามาจากแหล่งข้อมูลขนาดใหญ่เพียงพอที่จะสร้างเกลือที่แตกต่างกันสำหรับการเข้ารหัสรหัสผ่านแต่ละครั้งคุณก็โอเค โปรดจำไว้ว่าเกลือนั้นมีไว้เพื่อป้องกันการใช้โต๊ะสายรุ้งหากแฮชของคุณอยู่ในมือที่ไม่ดี พวกเขาไม่ได้เป็นความลับ
Andrew Moore

7
@AndrewMoore ถูกต้องอย่างแน่นอน! อย่างไรก็ตามเกลือจะต้องมีเอนโทรปีเพียงพอที่จะมีความเป็นเอกลักษณ์ทางสถิติ ไม่เพียง แต่ในแอปพลิเคชันของคุณ แต่ในแอปพลิเคชันทั้งหมด ดังนั้นmt_rand()มีระยะเวลาสูงพอ แต่ค่าเมล็ดพันธุ์เพียง 32 บิต ดังนั้นการใช้mt_rand()อย่างมีประสิทธิภาพ จำกัด คุณเพียง 32 บิตของเอนโทรปี ซึ่งต้องขอบคุณปัญหาวันเกิดซึ่งหมายความว่าคุณมีโอกาส 50% ของการชนที่เกลือ 7k เท่านั้นที่สร้างขึ้น (ทั่วโลก) เนื่องจากbcryptยอมรับเกลือขนาด 128 บิตจะเป็นการดีกว่าถ้าใช้แหล่งที่สามารถจัดหาได้ทั้ง 128 บิต ;-) (ที่ 128 บิตโอกาส 50% ของการชนเกิดขึ้นที่ 2e19 แฮช) ...
ircmaxell

1
@ircmaxell: Hense "พูลข้อมูลขนาดใหญ่เพียงพอ" อย่างไรก็ตามแหล่งที่มาของคุณไม่จำเป็นต้องเป็นแหล่งข้อมูลเอนโทรปีมากเพียงสูงพอสำหรับบิตที่ 128 อย่างไรก็ตามหากคุณใช้แหล่งข้อมูลที่มีอยู่หมด (ไม่มี OpenSSL และอื่น ๆ ... ) และทางเลือกเดียวของคุณคือ mt_rand () มันยังดีกว่าทางเลือกอื่น (ซึ่งคือ rand ())
Andrew Moore

4
@AndrewMoore: แน่นอน ไม่เถียงว่า เพียงแค่นั้นmt_randและuniqid(และด้วยเหตุนี้lcg_valueและrand) ไม่ใช่ตัวเลือกแรก ...
ircmaxell

1
ircmaxell ขอบคุณมากสำหรับห้องสมุด password_compat สำหรับ 5.3.xx เราไม่เคยต้องการสิ่งนี้มาก่อน แต่ตอนนี้เราทำได้บนเซิร์ฟเวอร์ 5.3.xx php และขอขอบคุณสำหรับคำแนะนำที่ชัดเจนของคุณที่จะไม่พยายามทำตรรกะนี้ ตัวเอง
Lizardx

47

คุณจะได้รับข้อมูลจำนวนมากในเพียงพอกับตารางสายรุ้ง: สิ่งที่คุณต้องรู้เกี่ยวกับแบบแผนรหัสผ่านที่ปลอดภัยหรือPHP กรอบรหัสผ่านคร่ำเครียดแบบพกพา

เป้าหมายคือการแฮชรหัสผ่านด้วยสิ่งที่ช้าดังนั้นใครบางคนที่ได้รับฐานข้อมูลรหัสผ่านของคุณจะตายพยายามบังคับให้เดรัจฉาน (ความล่าช้า 10 ms ในการตรวจสอบรหัสผ่านนั้นไม่ได้มีประโยชน์อะไรสำหรับคุณ Bcryptช้าและสามารถใช้กับพารามิเตอร์เพื่อเลือกว่ามันช้าแค่ไหน


7
บังคับใช้ทุกสิ่งที่คุณต้องการผู้ใช้จะจัดการให้พลาดและใช้รหัสผ่านเดียวกันกับหลายสิ่ง ดังนั้นคุณต้องปกป้องมันให้มากที่สุดหรือใช้สิ่งที่ให้คุณไม่ต้องเก็บรหัสผ่านใด ๆ (SSO, openID เป็นต้น)
Arkh

41
ไม่รหัสผ่านการแฮ็ชใช้เพื่อป้องกันการโจมตีครั้งเดียว: มีคนขโมยฐานข้อมูลของคุณและต้องการได้รับการเข้าสู่ระบบด้วยรหัสผ่านที่ชัดเจน
Arkh

4
@Josh K. ฉันขอแนะนำให้คุณพยายามถอดรหัสรหัสผ่านง่ายๆหลังจากผ่าน phpass tuned ดังนั้นจึงใช้เวลาระหว่าง 1ms ถึง 10ms ในการคำนวณบนเว็บเซิร์ฟเวอร์ของคุณ
Arkh

3
ตกลง แต่ชนิดของผู้ใช้ที่จะใช้ qwerty เป็นรหัสผ่านก็เป็นประเภทของผู้ใช้ที่จะทำเครื่องหมายใด ๆ ที่ซับซ้อนที่ไหนสักแห่งที่เขา (และโจมตี) สามารถอ่านได้อย่างง่ายดาย สิ่งที่ใช้ bcrypt สำเร็จคือเมื่อ db ของคุณเป็นแบบสาธารณะต่อความต้องการของคุณจะเป็นการยากที่จะเข้าถึงผู้ใช้ที่มีรหัสผ่านบางอย่างเช่น ^ | $$ & ZL6- £มากกว่าถ้าคุณใช้ sha512 ในครั้งเดียว
Arkh

4
@coreyward น่าสังเกตว่าการทำเช่นนั้นเป็นอันตรายมากกว่าการไม่ปิดกั้นเลย ที่ถูกพิจารณาอย่างง่ายดายว่าเป็น "การปฏิเสธการบริการ" เวกเตอร์ เพียงเริ่มสแปมการเข้าสู่ระบบที่ไม่ดีในบัญชีที่รู้จักและคุณสามารถทำลายผู้ใช้จำนวนมากได้อย่างง่ายดาย จะเป็นการดีกว่าถ้าผู้โจมตี tarpit (ล่าช้า) ดีกว่าคนที่ปฏิเสธการเข้าถึงโดยเฉพาะถ้าเป็นลูกค้าที่จ่ายเงิน
damianb

36

คุณสามารถสร้างแฮชแบบทางเดียวด้วย bcrypt โดยใช้crypt()ฟังก์ชั่นของ PHP และส่งผ่านเกลือเป่าปลาที่เหมาะสม ที่สำคัญที่สุดของสมการทั้งที่ A) ขั้นตอนวิธีการที่ไม่ได้รับการโจมตีและ B) คุณอย่างถูกต้องเกลือแต่ละรหัสผ่าน อย่าใช้เกลือทั่วทั้งแอปพลิเคชัน ที่เปิดแอปพลิเคชันทั้งหมดของคุณเพื่อโจมตีจากชุดตารางสายรุ้งชุดเดียว

PHP - ฟังก์ชั่น Crypt


4
นี่คือแนวทางที่ถูกต้อง - ใช้crypt()ฟังก์ชั่นของ PHP ซึ่งรองรับฟังก์ชั่นการแฮ็ชรหัสผ่านที่แตกต่างกัน ตรวจสอบให้แน่ใจว่าคุณไม่ได้ใช้CRYPT_STD_DESหรือCRYPT_EXT_DES- ประเภทอื่น ๆ ที่รองรับนั้นใช้ได้ (และรวมถึง bcrypt ภายใต้ชื่อCRYPT_BLOWFISH)
caf

4
SHA มีพารามิเตอร์ราคาเช่นกันผ่านตัวเลือก 'รอบ' เมื่อใช้สิ่งนั้นฉันก็ไม่เห็นเหตุผลใด ๆ ที่จะชอบ bcrypt
Pieter Ennes

3
ในความเป็นจริงรหัสผ่าน SHA-1 (หรือ MD5) เดียวยังสามารถบังคับเดรัจฉานได้อย่างง่ายดายโดยมีหรือไม่มีเกลือ (เกลือช่วยต่อต้านตารางรุ้ง ใช้ bcrypt
Paŭlo Ebermann

ฉันพบว่ามันน่ารำคาญที่ทุกคนดูเหมือนจะพูดว่า "bcrypt" เมื่อพวกเขาหมายถึง php's crypt ()
Sliq

3
@Panique ทำไม ขั้นตอนวิธีการที่เรียกว่าbcrypt cryptเปิดเผยแฮชรหัสผ่านหลายครั้งโดย bcrypt สอดคล้องกับCRYPT_BLOWFISHค่าคงที่ ปัจจุบัน Bcrypt เป็นอัลกอริธึมที่แข็งแกร่งที่สุดที่สนับสนุนcryptและอื่น ๆ อีกมากมายที่สนับสนุนค่อนข้างอ่อนแอ
CodesInChaos

34

แก้ไข: 2013.01.15 - หากเซิร์ฟเวอร์ของคุณรองรับให้ใช้โซลูชันของ martinstoeckliแทน


ทุกคนต้องการทำให้สิ่งนี้ซับซ้อนกว่าที่เป็นอยู่ ฟังก์ชัน crypt () ทำหน้าที่ส่วนใหญ่ได้

function blowfishCrypt($password,$cost)
{
    $chars='./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    $salt=sprintf('$2y$%02d$',$cost);
//For PHP < PHP 5.3.7 use this instead
//    $salt=sprintf('$2a$%02d$',$cost);
    //Create a 22 character salt -edit- 2013.01.15 - replaced rand with mt_rand
    mt_srand();
    for($i=0;$i<22;$i++) $salt.=$chars[mt_rand(0,63)];
    return crypt($password,$salt);
}

ตัวอย่าง:

$hash=blowfishCrypt('password',10); //This creates the hash
$hash=blowfishCrypt('password',12); //This creates a more secure hash
if(crypt('password',$hash)==$hash){ /*ok*/ } //This checks a password

ฉันรู้ว่ามันควรจะชัดเจน แต่โปรดอย่าใช้ 'รหัสผ่าน' เป็นรหัสผ่านของคุณ


3
การสร้างเกลือสามารถปรับปรุงได้ (ใช้แหล่งสุ่มของระบบปฏิบัติการ) มิฉะนั้นมันก็ดูดีสำหรับฉัน สำหรับรุ่นใหม่ PHP มันจะดีกว่าที่จะใช้แทน2y 2a
martinstoeckli

ใช้mcrypt_create_iv($size, MCRYPT_DEV_URANDOM)เป็นแหล่งเกลือ
CodesInChaos

ฉันจะมองอย่างใกล้ชิดที่ mcrypt_create_iv () เมื่อฉันได้รับสักครู่ถ้าไม่มีอะไรที่มันควรปรับปรุงประสิทธิภาพเล็กน้อย
Jon Hulka

2
เพิ่มการเข้ารหัส Base64 และแปลเป็นการbcryptใช้ตัวอักษรที่กำหนดเอง mcrypt_create_iv(17, MCRYPT_DEV_URANDOM), str_replace('+', '.', base64_encode($rawSalt)),$salt = substr($salt, 0, 22);
CodesInChaos

1
@JonHulka - ดูชุดความเข้ากันได้ของ PHP [Line 127] นี่เป็นการใช้งานที่ง่าย
martinstoeckli

29

เวอร์ชัน 5.5 ของ PHP จะได้สร้างขึ้นในการสนับสนุน bcrypt, ฟังก์ชั่นและpassword_hash() password_verify()จริงๆแล้วสิ่งเหล่านี้เป็นเพียงการล้อมรอบฟังก์ชันcrypt()และจะทำให้ใช้งานได้ง่ายขึ้น ดูแลการสร้างเกลือสุ่มที่ปลอดภัยและให้ค่าเริ่มต้นที่ดี

วิธีที่ง่ายที่สุดในการใช้ฟังก์ชั่นนี้คือ:

$hashToStoreInDb = password_hash($password, PASSWORD_BCRYPT);
$isPasswordCorrect = password_verify($password, $existingHashFromDb);

รหัสนี้จะแฮรหัสผ่านด้วย BCrypt (อัลกอริทึม2y) สร้างเกลือแบบสุ่มจากแหล่งสุ่ม OS และใช้พารามิเตอร์ค่าเริ่มต้น (ในขณะนี้คือ 10) การตรวจสอบบรรทัดที่สองหากผู้ใช้ป้อนรหัสผ่านตรงกับค่าแฮชที่เก็บไว้แล้ว

หากคุณต้องการเปลี่ยนพารามิเตอร์ต้นทุนคุณสามารถทำเช่นนี้ได้โดยเพิ่มพารามิเตอร์ต้นทุน 1 และเพิ่มเวลาที่ต้องการสองเท่าเพื่อคำนวณค่าแฮช:

$hash = password_hash($password, PASSWORD_BCRYPT, array("cost" => 11));

ตรงกันข้ามกับ"cost"พารามิเตอร์จะเป็นการดีที่สุดถ้าละเว้น"salt"พารามิเตอร์เนื่องจากฟังก์ชันทำอย่างดีที่สุดแล้วในการสร้างเกลือที่ปลอดภัยแบบเข้ารหัส

สำหรับ PHP เวอร์ชั่น 5.3.7 และใหม่กว่ามีชุดความเข้ากันได้จากผู้เขียนรายเดียวกันที่ทำpassword_hash()หน้าที่นี้ สำหรับ PHP เวอร์ชันก่อน 5.3.7 ไม่มีการสนับสนุนcrypt()ด้วย2yอัลกอริทึม BCrypt ที่ปลอดภัยของ unicode หนึ่งสามารถแทนที่ด้วย2aซึ่งเป็นทางเลือกที่ดีที่สุดสำหรับรุ่น PHP ก่อนหน้านี้


3
หลังจากที่ฉันอ่านสิ่งนี้ความคิดแรกของฉันคือ "คุณจะเก็บเกลือที่สร้างขึ้นได้อย่างไร" หลังจาก poking ผ่าน docs ฟังก์ชัน password_hash () จะสร้างสตริงที่เก็บวิธีการเข้ารหัสเกลือและแฮชที่สร้างขึ้น ดังนั้นมันจึงเก็บทุกอย่างที่มันต้องการในสตริงเดียวเพื่อให้ password_verify () ฟังก์ชั่นใช้งานได้ แค่อยากพูดถึงสิ่งนี้เพราะมันอาจช่วยคนอื่น ๆ เมื่อพวกเขาดูสิ่งนี้
jzimmerman2011

@ jzimmerman2011 - ในคำตอบอื่น ๆฉันพยายามอธิบายรูปแบบการจัดเก็บนี้ด้วยตัวอย่าง
martinstoeckli

7

การคิดในปัจจุบัน: แฮชควรช้าที่สุดไม่ใช่เป็นไปได้ที่เร็วที่สุด วิธีนี้จะยับยั้งการโจมตีของตารางสายรุ้ง

มีความเกี่ยวข้องกันด้วย แต่ข้อควรระวัง: ผู้โจมตีไม่ควรเข้าใช้งานหน้าจอเข้าระบบของคุณอย่างไม่ จำกัด เพื่อป้องกันไม่ให้ตั้งค่าตารางการติดตามที่อยู่ IP ที่บันทึกทุกการเข้าชมพร้อมกับ URI หากมีการพยายามลงชื่อเข้าใช้มากกว่า 5 ครั้งจากที่อยู่ IP เดียวกันในช่วงเวลาห้านาทีใด ๆ ให้บล็อกด้วยคำอธิบาย แนวทางที่สองคือการใช้รหัสผ่านแบบสองชั้นเช่นเดียวกับที่ธนาคารทำ การปิดระบบล็อคเพื่อความล้มเหลวในรอบที่สองช่วยเพิ่มความปลอดภัย

สรุป: ทำให้ผู้โจมตีช้าลงโดยใช้ฟังก์ชั่นแฮชที่ใช้เวลานาน นอกจากนี้บล็อกการเข้าถึงข้อมูลเข้าสู่ระบบของคุณมากเกินไปและเพิ่มระดับรหัสผ่านที่สอง


ฉันคิดว่าพวกเขาคิดว่าผู้โจมตีได้ทำการขโมยฐานข้อมูลของฉันไปแล้วด้วยวิธีการอื่นและตอนนี้กำลังพยายามเอารหัสผ่านออกมาเพื่อลองใช้กับ paypal หรืออะไรบางอย่าง
Vilx-

4
ครึ่งทางผ่านปี 2012 และคำตอบนี้ยังคงไร้ประโยชน์อัลกอริทึมการแปลงแป้นพิมพ์แบบช้าจะป้องกันการโจมตีด้วยตารางรุ้งได้อย่างไร ฉันคิดว่าเกลือพิสัยแบบสุ่มทำได้หรือไม่ ฉันคิดเสมอว่าความเร็วของอัลกอริทึมการแปลงแป้นพิมพ์จะกำหนดจำนวนการวนซ้ำที่พวกเขาสามารถส่งต่อแฮชที่พวกเขาสร้างคุณในเวลาที่กำหนด นอกจากนี้ไม่เคยบล็อกผู้ใช้ที่เข้าสู่ระบบล้มเหลวในการเข้าสู่ระบบเชื่อฉันผู้ใช้ของคุณจะได้รับเบื่อบ่อยในบางเว็บไซต์ที่ฉันต้องเข้าสู่ระบบใกล้ 5 ครั้งบางครั้งมากขึ้นก่อนที่ฉันจำรหัสผ่านของฉัน นอกจากนี้ระดับพาสพาสที่สองก็ใช้งานไม่ได้ แต่มีสองขั้นตอนในการตรวจสอบรหัสโทรศัพท์มือถือ
Sammaye

1
@ Samaye ฉันจะเห็นด้วยกับจุดนี้ การติดตั้งผมบล็อกบน 5 พยายามเข้าสู่ระบบล้มเหลวก่อนที่จะเพิ่มมันได้อย่างรวดเร็วถึง 7 แล้ว 10 ตอนนี้นั่งอยู่บน 20. ไม่มีผู้ใช้ปกติควรจะมี 20 ความพยายามล้มเหลวเข้าสู่ระบบ แต่พอต่ำที่จะหยุดการโจมตีได้อย่างง่ายดายแรงเดรัจฉาน
บรูซ Aldridge

@ BruceAldridge ฉันคิดว่าเป็นการดีกว่าที่จะหยุดสคริปต์ของคุณชั่วคราวหลังจากพูดว่าการเข้าสู่ระบบ 7 ครั้งล้มเหลวและแสดง captcha มากกว่าบล็อก การปิดกั้นเป็นการเคลื่อนไหวที่รุนแรงมาก
Sammaye

1
@ Samaye ฉันเห็นด้วยว่าบล็อคถาวรไม่ดี ฉันหมายถึงบล็อกชั่วคราวที่เพิ่มขึ้นตามจำนวนความพยายามที่ล้มเหลว
Bruce Aldridge

7

นี่คือคำตอบที่อัปเดตสำหรับคำถามเก่านี้!

วิธีที่ถูกต้องในการแฮชรหัสผ่านใน PHP ตั้งแต่ 5.5 นั้นมาพร้อมกับpassword_hash()และวิธีที่ถูกต้องในการตรวจสอบพวกเขาอยู่ด้วยpassword_verify()และนี่ก็ยังเป็นจริงใน PHP 8.0 ฟังก์ชั่นเหล่านี้ใช้ bcrypt hashes โดยค่าเริ่มต้น แต่มีการเพิ่มอัลกอริทึมที่แข็งแกร่งอื่น ๆ คุณสามารถเปลี่ยนปัจจัยการทำงานได้อย่างมีประสิทธิภาพว่า "แรง" การเข้ารหัสคืออะไรผ่านpassword_hashพารามิเตอร์

อย่างไรก็ตามในขณะที่มันยังคงแข็งแกร่งเพียงพอbcrypt จะไม่ถือว่าเป็นของรัฐอีกต่อไป ; อัลกอริธึมการแฮ็กรหัสผ่านที่ดีกว่ามาถึงเรียกว่าArgon2พร้อมกับ Argon2i, Argon2d และ Argon2id ความแตกต่างระหว่างพวกเขา (ดังอธิบายที่นี่ ):

Argon2 มีตัวแปรหลักหนึ่งตัว: Argon2id และสองตัวแปรเพิ่มเติม: Argon2d และ Argon2i Argon2d ใช้การเข้าถึงหน่วยความจำขึ้นอยู่กับข้อมูลซึ่งทำให้เหมาะสำหรับ cryptocurrencies และแอพพลิเคชั่นที่พิสูจน์การทำงานโดยไม่มีภัยคุกคามจากการโจมตีจังหวะเวลาด้านข้าง Argon2i ใช้การเข้าถึงหน่วยความจำที่ไม่ขึ้นอยู่กับข้อมูลซึ่งเป็นที่ต้องการสำหรับการแฮ็กรหัสผ่านและการสืบทอดคีย์ตามรหัสผ่าน Argon2id ทำหน้าที่เป็น Argon2i ในช่วงครึ่งแรกของการวนซ้ำครั้งแรกของหน่วยความจำและเป็น Argon2d สำหรับส่วนที่เหลือจึงให้การป้องกันการโจมตีทั้งสองแชนเนล

การสนับสนุน Argon2i ได้รับการเพิ่มใน PHP 7.2 และคุณร้องขอเช่นนี้:

$hash = password_hash('mypassword', PASSWORD_ARGON2I);

และการสนับสนุน Argon2id ถูกเพิ่มใน PHP 7.3:

$hash = password_hash('mypassword', PASSWORD_ARGON2ID);

ไม่ต้องทำการเปลี่ยนแปลงใด ๆ สำหรับการยืนยันรหัสผ่านเนื่องจากสตริงแฮชผลลัพธ์จะมีข้อมูลเกี่ยวกับอัลกอริทึมเกลือและปัจจัยการทำงานที่ใช้เมื่อสร้างขึ้น

ค่อนข้างแยกต่างหาก (และค่อนข้างซ้ำซ้อน), libsodium (เพิ่มใน PHP 7.2) ยังมีการแฮชของ Argon2 ผ่านทางsodium_crypto_pwhash_str ()และsodium_crypto_pwhash_str_verify()ฟังก์ชั่นซึ่งใช้งานได้เช่นเดียวกับ PHP ในตัว เหตุผลหนึ่งที่เป็นไปได้สำหรับการใช้สิ่งเหล่านี้คือบางครั้ง PHP อาจถูกคอมไพล์โดยไม่มี libargon2 ซึ่งทำให้อัลกอริทึม Argon2 ไม่สามารถใช้ได้กับฟังก์ชัน password_hash; PHP 7.2 และสูงกว่าควรเปิดใช้งาน libsodium เสมอ แต่อาจไม่ - แต่อย่างน้อยมีสองวิธีที่คุณสามารถรับอัลกอริทึมนั้นได้ นี่คือวิธีที่คุณสามารถสร้างแฮ็ก Argon2id ด้วย libsodium (แม้ใน PHP 7.2 ซึ่งไม่สนับสนุน Argon2id):

$hash = sodium_crypto_pwhash_str(
    'mypassword',
    SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
    SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
);

โปรดทราบว่ามันไม่อนุญาตให้คุณระบุเกลือด้วยตนเอง นี่เป็นส่วนหนึ่งของ ethos ของ libsodium - ไม่อนุญาตให้ผู้ใช้ตั้งค่าพารามิเตอร์เป็นค่าที่อาจทำให้เกิดความปลอดภัย - ตัวอย่างเช่นไม่มีสิ่งใดที่ขัดขวางไม่ให้คุณส่งสตริงเกลือที่ว่างเปล่าไปยังpassword_hashฟังก์ชันของ PHP libsodium ไม่อนุญาตให้คุณทำอะไรโง่ ๆ !



1

ในขณะที่เราทุกคนรู้ว่าการจัดเก็บรหัสผ่านเป็นข้อความที่ชัดเจนในฐานข้อมูลไม่ปลอดภัย bcrypt เป็นเทคนิคการแฮ็กรหัสผ่านมันถูกใช้เพื่อสร้างความปลอดภัยของรหัสผ่าน หนึ่งในฟังก์ชั่นที่น่าทึ่งของ bcrypt คือมันช่วยให้เราปลอดภัยจากแฮกเกอร์มันถูกใช้เพื่อปกป้องรหัสผ่านจากการแฮ็คการโจมตีเพราะรหัสผ่านจะถูกเก็บไว้ในรูปแบบ bcrypted

ฟังก์ชั่น password_hash () ใช้เพื่อสร้างรหัสผ่านแฮชใหม่ มันใช้อัลกอริทึมการแฮชที่แข็งแกร่งและมีประสิทธิภาพ ดังนั้นแฮ็ชรหัสผ่านที่สร้างโดย crypt () อาจใช้กับ password_hash () และในทางกลับกัน ฟังก์ชั่น password_verify () และ password_hash () เพียงแค่ล้อมรอบฟังก์ชัน crypt () และทำให้ง่ายต่อการใช้งานได้อย่างแม่นยำมากขึ้น

SYNTAX

string password_hash($password , $algo , $options)

ปัจจุบันอัลกอริทึมดังต่อไปนี้รองรับโดยฟังก์ชัน password_hash ():

PASSWORD_DEFAULT PASSWORD_BCRYPT PASSWORD_ARGON2I PASSWORD_ARGON2ID

พารามิเตอร์: ฟังก์ชั่นนี้ยอมรับสามพารามิเตอร์ดังกล่าวข้างต้นและอธิบายไว้ด้านล่าง:

รหัสผ่าน : มันเก็บรหัสผ่านของผู้ใช้ อัลโก : มันเป็นค่าคงที่อัลกอริทึมรหัสผ่านที่ใช้อย่างต่อเนื่องในขณะที่แสดงอัลกอริทึมซึ่งจะใช้เมื่อมีการแฮ็ชของรหัสผ่านเกิดขึ้น ตัวเลือก : มันเป็นอาเรย์เชื่อมโยงซึ่งมีตัวเลือก หากสิ่งนี้ถูกลบและไม่รวมจะมีการใช้เกลือแบบสุ่มและการใช้ต้นทุนเริ่มต้นจะเกิดขึ้น Return Value : มันจะส่งคืนรหัสผ่านที่แฮชเกี่ยวกับความสำเร็จหรือเท็จเมื่อล้มเหลว

ตัวอย่าง :

Input : echo password_hash("GFG@123", PASSWORD_DEFAULT); Output : $2y$10$.vGA19Jh8YrwSJFDodbfoHJIOFH)DfhuofGv3Fykk1a

ด้านล่างโปรแกรมแสดงให้เห็นถึงฟังก์ชั่น password_hash () ใน PHP:

<?php echo password_hash("GFG@123", PASSWORD_DEFAULT); ?>

เอาท์พุท

$2y$10$Z166W1fBdsLcXPVQVfPw/uRq1ueWMA6sLt9bmdUFz9AmOGLdM393G

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.