基于密码散列PBKDF2的用户密码加密

基于:密码散列PBKDF2的用户密码加密

为什么需要把应用程序中用户的密码进行散列化?

当设计一个需要接受用户密码的应用时, 对密码进行散列是最基本的,也是必需的安全考虑。 如果不对密码进行散列处理,那么一旦应用的数据库受到攻击, 那么用户的密码将被窃取。 同时,窃取者也可以使用用户账号和密码去尝试其他的应用, 如果用户没有为每个应用单独设置密码,那么将面临风险。
通过对密码进行散列处理,然后再保存到数据库中, 这样就使得攻击者无法直接获取原始密码, 同时还可以保证你的应用可以对原始密码进行相同的散列处理, 然后比对散列结果。
需要着重提醒的是,密码散列只能保护密码 不会被从数据库中直接窃取, 但是无法保证注入到应用中的 恶意代码拦截到原始密码。

为何诸如 md5()sha1() 这样的常见散列函数不适合用在密码保护场景?

MD5,SHA1 以及 SHA256 这样的散列算法是面向快速、高效 进行散列处理而设计的。随着技术进步和计算机硬件的提升, 破解者可以使用“暴力”方式来寻找散列码 所对应的原始数据。
因为现代化计算机可以快速的“反转”上述散列算法的散列值, 所以很多安全专家都强烈建议 不要在密码散列中使用这些散列算法。

如果不建议使用常用散列函数保护密码, 那么我应该如何对密码进行散列处理?

当进行密码散列处理的时候,有两个必须考虑的因素: 计算量以及“盐”。 散列算法的计算量越大, 暴力破解所需的时间就越长。
PHP 5.5 提供了 一个原生密码散列 API, 它提供一种安全的方式来完成密码 散列验证。 PHP 5.3.7 及后续版本中都提供了一个 » 纯 PHP 的兼容库
PHP 5.3 及后续版本中,还可以使用 crypt() 函数, 它支持多种散列算法。 针对每种受支持的散列算法,PHP 都提供了对应的原生实现, 所以在使用此函数的时候, 你需要保证所选的散列算法是你的系统所能够支持的。
当对密码进行散列处理的时候,建议采用 Blowfish 算法, 这是密码散列 API 的默认算法。 相比 MD5 或者 SHA1,这个算法提供了更高的计算量, 同时还有具有良好的伸缩性。
如果使用 crypt() 函数来进行密码验证, 那么你需要选择一种耗时恒定的字符串比较算法来避免时序攻击。 (译注:就是说,字符串比较所消耗的时间恒定, 不随输入数据的多少变化而变化) PHP 中的 == 和 === 操作符strcmp() 函数都不是耗时恒定的字符串比较, 但是 password_verify() 可以帮你完成这项工作。 我们鼓励你尽可能的使用 原生密码散列 API

“盐”是什么?

加解密领域中的“盐”是指在进行散列处理的过程中 加入的一些数据,用来避免从已计算的散列值表 (被称作“彩虹表”)中 对比输出数据从而获取明文密码的风险。
简单而言,“盐”就是为了提高散列值被破解的难度 而加入的少量数据。 现在有很多在线服务都能够提供 计算后的散列值以及其对应的原始输入的清单, 并且数据量极其庞大。 通过加“盐”就可以避免直接从清单中查找到对应明文的风险。
如果不提供“盐”,password_hash() 函数会随机生成“盐”。 非常简单,行之有效。

我应该如何保存“盐”?

当使用 password_hash() 或者 crypt() 函数时, “盐”会被作为生成的散列值的一部分返回。 你可以直接把完整的返回值存储到数据库中, 因为这个返回值中已经包含了足够的信息, 可以直接用在 password_verify()crypt() 函数来进行密码验证。
下图展示了
crypt()password_hash() 函数返回值的结构。 如你所见,算法的信息以及“盐”都已经包含在返回值中, 在后续的密码验证中将会用到这些信息。

Paste_Image.png

以下贴出一个用户注册时密码的加密代码 此代码中用了一个封装好的是静态加密类

  /**
     * 注册一个用户
     * @return \User
     */
    public function register()
    {
        //调用类获取盐
        $salt = YumEncrypt::generateSalt();
        //设置密码 将返回一个加密后的
        $this->setPassword($this->password, $salt);
        $this->createTime = time();
        $this->userName = empty($this->userName) ? $this->mobile : $this->userName;
        $this->updateTime = time();
        $this->lastVisit = time();
        $this->lastPasswordChange = time();
        $this->status = User::STATUS_ACTIVE;
        $this->regIp = Tools::getIpToInt();
        if ($this->validate()) {
            $this->save(false, array('trueName', 'mobile', 'password', 'userName', 'email','companyName',
                'salt',
                'createTime', 'lastPasswordChange', 'status', 'regIp',
            ));
            $this->afterRegister();
        }
        return $this;
    }

  /**
   * 设置密码
   * @param type $password
   * @param type $salt
   * @return \User
   */
  public function setPassword($password, $salt = null)
  {
      if ($password != '') {
          if (!$salt)
              $salt = YumEncrypt::generateSalt();
          $this->password = YumEncrypt::encrypt($password, $salt);
          $this->lastPasswordChange = time();
          $this->salt = $salt;
          if (!$this->isNewRecord)
              return $this->save(false, array('password', 'lastPasswordChange', 'salt'));
          else
              return $this;
      }
  }

封装过的密码加密类

  <?php
  /**
   * This class file holds static encryption functions used by Yum. 
   *
   * The password encryption system is based on:
   *
   * Password hashing with PBKDF2.
   * Author: havoc AT defuse.ca
   * www: https://defuse.ca/php-pbkdf2.htm
   **/
  class YumEncrypt {
     /**
      * This function is used for password encryption.
      * @return hex encoded hash string.
      */
     public static function encrypt($string, $salt = null)
     {
        if(!$salt)
           $salt = YumEncrypt::generateSalt();
          return YumEncrypt::pbkdf2($string, $salt);
     }
     /**
      * This function is used for generating the salt.
      * @return base64_encoded hash string.
      */
      public static function generateSalt()
      {
          if (function_exists('mcrypt_create_iv'))
          {            
              $sHash =  base64_encode(mcrypt_create_iv(64, MCRYPT_DEV_URANDOM));
          }
          else
          {
              $sHash = hash('sha256', mt_rand() . uniqid());
          }
          return $sHash;
      }
     /**
      * This function is used for generating the salt.
      * @return hash string.
      */
      public static function validate_password($password, $good_hash, $salt)
      {
        $enc_pwd = YumEncrypt::encrypt($password, $salt);
        return YumEncrypt::slow_equals($enc_pwd, $good_hash);
      }
      // Compares two strings $a and $b in length-constant time.
     private static function slow_equals($a, $b)
     {
         $diff = strlen($a) ^ strlen($b);
         for($i = 0; $i < strlen($a) && $i < strlen($b); $i++)       {
             $diff |= ord($a[$i]) ^ ord($b[$i]);
         }
         return $diff === 0;
    }
     /*
      * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
      * $algorithm - The hash algorithm to use. Recommended: SHA256
      * $password - The password.
      * $salt - A salt that is unique to the password.
      * $count - Iteration count. Higher is better, but slower. Recommended: At least 1000.
      * $key_length - The length of the derived key in bytes.
      * $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise.
      * Returns: A $key_length-byte key derived from the password and salt.
      *
      * Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
      *
      * This implementation of PBKDF2 was originally created by https://defuse.ca
      * With improvements by http://www.variations-of-shadow.com
      */
     private static function pbkdf2($password, $salt, $algorithm = 'sha256', $count = 1000, $key_length = 64, $raw_output = false)
     {
         $algorithm = strtolower($algorithm);
         if(!in_array($algorithm, hash_algos(), true))
             die('PBKDF2 ERROR: Invalid hash algorithm.');
         if($count <= 0 || $key_length <= 0)
             die('PBKDF2 ERROR: Invalid parameters.');
         $hash_length = strlen(hash($algorithm, "", true));
         $block_count = ceil($key_length / $hash_length);
         $output = "";
         for($i = 1; $i <= $block_count; $i++) {
             // $i encoded as 4 bytes, big endian.
             $last = $salt . pack("N", $i);
             // first iteration
             $last = $xorsum = hash_hmac($algorithm, $last, $password, true);
             // perform the other $count - 1 iterations
             for ($j = 1; $j < $count; $j++) {
                 $xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
             }
             $output .= $xorsum;
         }
           if($raw_output)
             return substr($output, 0, $key_length);
           else
             return bin2hex(substr($output, 0, $key_length));
     }
  }
  ?>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,667评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,361评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,700评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,027评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,988评论 5 361
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,230评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,705评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,366评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,496评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,405评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,453评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,725评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,803评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,015评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,514评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,111评论 2 341

推荐阅读更多精彩内容

  • 这篇文章主要讲述在Mobile BI(移动商务智能)开发过程中,在网络通信、数据存储、登录验证这几个方面涉及的加密...
    雨_树阅读 2,323评论 0 6
  • 单向散列加密 单向散列加密就是把任意长的输入消息串变化成固定长的输出串且由输出串难以得到输入串的一种加密算法。 常...
    新亮笔记阅读 823评论 0 5
  • php usleep() 函数延迟代码执行若干微秒。 unpack() 函数从二进制字符串对数据进行解包。 uni...
    思梦PHP阅读 1,979评论 1 24
  • 前言 《图解密码技术》一书介绍了很多关于密码的知识,通读一遍需要不少时间。为了方便学习,我对书中关键的部分进行了总...
    咖枯阅读 7,107评论 1 25
  • 小家伙有一部分作业已经完成了,玩的心开始一点一点增加了。让他背点东西,他玩玩这个吃点那个,能磨蹭一会是一会。...
    邓启旭邓君浩妈妈阅读 101评论 0 1