美文网首页
Web 开发的身份数据和安全(二)——密码加密、哈希和加盐

Web 开发的身份数据和安全(二)——密码加密、哈希和加盐

作者: 独木舟的木 | 来源:发表于2018-12-17 13:47 被阅读17次

    探讨密码加密和安全性。

    静态数据&动态数据

    静态数据:不活动的(或静止的)数字数据,存储在服务器中,例如用于存储密码、个人资料、或其他应用所需数据的数据库。

    动态数据:传输中的数据,在应用和数据库之间来回发送,或者在网站和 API 或外部数据源之间来回传送。

    1. 静态数据

    • 数据库加密是绝对有必要的,尽管 99% 的团体没有这么做。
    • 加密数据库应该使用强加密、受 NIST 认可的算法,如:SHA-256、AES、RSA。
    • 遵循标准做法:1. 分开访问控制(用户登录)和数据加密;2. 定期更新加密数据库的密钥;3. 分开存储加密密钥和数据。
    • 对全球覆盖的应用来说,可以使用数据联合避免恶意访问数据存储器。
    • 根据运行应用、网站或服务的需要,应该尽量少存储敏感的用户数据
    • 敏感的财务信息(例如信用卡数据)可以交由他方(支付提供商)处理。

    2. 动态数据

    动态数据的使用场景:

    • 用户填写的注册信息,用于访问账户和认证身份。
    • 把个人档案信息传输给API服务,或从中获取。
    • 应用或网站收集的其他数据,传给数据库存储起来。

    密码攻击媒介

    • 钓鱼;
    • 社会工程;
    • 暴力攻击;(抵抗方式:密钥延伸技术)
    • 字典攻击;(抵抗方式:加盐)
    • 彩虹表;(抵抗方式:加盐)
    • 恶意软件;(抵抗方式:短信验证、n因素认证)
    • 离线破解;

    加盐

    盐值是一种随机数据,计算密码的哈希值时用于加强数据,抵御多种攻击媒介尤其是字典攻击和彩虹表。这个随机数据(特别长)能确保生成的哈希值唯一的,即使多个用户使用相同的密码(确实有这种情况),添加唯一的盐值后能确保得到的哈希值仍是唯一的。就因为得到的哈希值是唯一的,我们才得以免受彩虹表和字典攻击的危害。

    生成随机盐值

    使用 Node 原生支持的的 crypto 库生成随机盐值。

    const crypto = require('crypto');
    
    // crypto.randomBytes() 生成 16 位强加密的伪随机数
    crypto.randomBytes(16, (err, buf) => {
      if (err) throw err;
      console.log(`${buf.length} bytes of random data: ${buf.toString('hex')}`);
      console.log(`${buf.length} bytes of random data: ${buf.toString('base64')}`);
    });
    
    // 同步方法生成盐值
    const buf = crypto.randomBytes(256);
    

    重用盐值

    用户注册账户或修改密码时应该生成并存储新的盐值和哈希值。

    盐值的长度

    • 根据经验,盐值的长度应该与哈希函数的输出长度一致。
    • PBKDF2 标准建议至少应该使用 64 位(8字节)长度的盐值。通常,多数情况下使用的是2的7次方,即128位(16字节)。

    把盐值存储在哪?

    盐值可以和哈希值一起以明文形式存储在数据库中。

    撒胡椒

    • 胡椒是在计算哈希值时随盐值和密码一起传入的值。
    • 使用胡椒的原因:利用额外的字符和符号加强密码的强度。

    选择正确的密码哈希函数

    bcrypt ⭐️⭐️⭐️

    特性:专为加密密码设计,有异步方法和同步方法、密码哈希函数、校验密码函数、带 promise 特性...

    const bcrypt = require('bcrypt');
    
    // 封装 hash 函数
    function bcrypt_encrypt(username, password) {
      // 1. bcrypt 内置了生成盐值的方法:getSalt()
      bcrypt.genSalt(10, (err, salt) => {
        if (err) throw err;
          
        // 2. 生成哈希值:hash()
        bcrypt.hash(password, salt, (err, key) => {
          if (err) throw err;
          
          // 3. 把用户名、密码哈希值和盐值存入数据库
        });
      });
    }
    
    // 调用示例
    bcrypt_encrypt('zhangsan', '123456');
    
    // 对比哈希值,验证密码:
    bcrypt.compare(password, hash, (err, same) => {
      // 返回 true 或 false
    });
    

    PBKDF2

    • 1Password、LastPass 等密码管理系统采用的算法;
    • Node crypt 库原生支持的标准算法;

    加密示例:

    // PBKDF2 算法
    function pbkdf2_encrypt(username, password) {
      // 1. crypto.randomBytes()方法生成 32 字节的随机盐值
      crypto.randomBytes(32, (err, salt) => {
        if (err) throw err;
    
        // 2. 参数列表:(密码,盐值,迭代次数,密钥长度,摘要函数)
        crypto.pbkdf2(password, salt, 4096, 512, 'sha256', (err, key) => {
          if (err) throw err;
          
          // 3. 将用户名、密码哈希值和盐值存入数据库
          console.log(username, key.toString('hex'), salt.toString('hex'));
        });
      });
    }
    
    // 调用示例
    pbkdf2_encrypt('zhangSan', '123456');
    

    对比哈希值,验证密码:

    const dbsalt = 'USER RECORD SALT FROM YOUR DATABASE';
    const dbhash = 'USER RECORD KEY FROM YOUR DATABASE';
    
    crypto.pbkdf2(password, dbsalt, 4096, 512, 'sha256', (err, comparsehash) => {
      if (err) throw err;
    
      // 比较
      if (dbhash.toString('hex') === comparsehash.toString('hex')) {
        // 密码匹配
      } else {
        // 密码不匹配
      }
    });
    

    scrypt

    scrypt 的优势和实现如下:

    • 做了特殊设计,是硬件和内存密集型算法,攻击者要实施大型攻击,想要破解需要耗费异常多的硬件和内存。
    • 是加密数字货币莱特币和狗狗币背后采用的算法。
    'use strict';
    
    const scrypt = require('scrypt');
    const crypto = require('crypto');
    
    function scrypt_encrypt(username, password) {
      // 1. 使用 crypto 的 crypto.randomBytes(...) 方法生成盐值。
      crypto.randomBytes(32, (err, salt) => {
        if (err) throw err;
    
        // 2. scrypt.hash(...) 生成 64 位的哈希值
        // - N: crypt最多使用多长时间(秒数)计算密钥(偶数)。
        // - r:计算密钥时最多使用多少字节RAM(整数)。默认为0。
        // - p:计算密钥时所用RAM占可用值的比例(0-1,换算成百分比)默认为0.5。
        scrypt.hash(password, {"N":16384,"r":8,"p":1}, 64, salt, (err, key) => {
          if (err) throw err;
    
          // 3. 把用户名、密码哈希值和盐值存入数据库
          console.log(`key is ${key}`);
        });
      });
    }
    

    密钥延伸

    bcrypt、scrypt 和 PBKDF2 行之有效涉及到的底层概念——密钥延伸。
    密钥延伸:把弱密码变成特别复杂的长密码,致使暴力攻击等攻击媒介不再可行。

    对加密哈希函数来说,密钥延伸体现在不断循环应用哈希函数(哈希函数迭代),直到得到所需长度和复杂度的哈希值为止。

    重新计算哈希值

    有时可能需要为用户生成新的密码哈希值。比如说:

    • 根据摩尔定律,硬件更新了,要修改加密算法使用的权重/工作因子。
    • 算法变了,或者有更好的算法出现,目前使用的算法不安全。
    • 觉得现有哈希值不再安全。

    遇到这些情况,符合常规的做法是为用户生成新哈希值,存储在系统中。用户提供用户名和密码请求登录时,正常比较根据输入值计算的哈希值和存储的哈希值,而不是拒绝登录。随后,为用户生成新的哈希值,替换用户记录中的旧值。

    相关文章

      网友评论

          本文标题:Web 开发的身份数据和安全(二)——密码加密、哈希和加盐

          本文链接:https://www.haomeiwen.com/subject/emgqkqtx.html