【前端】深入剖析MD5加密技术

前言

  最近笔者在做个人的小项目,里面用到了各种前后端技术,在对自己技术进行实践和复习的同时,也加强了自己对于工作原理的理解。在进行用户登录、在线交易以及信息通讯等都需要用到数字签名信息加密技术,不同场景也会使用到不同的加密算法,当然你也可以用保密性很强的区块链技术。而在进行网站登录这块技术设计时,考虑到对用户密码等敏感信息进行加密保存,而选择合适的密码能够事半功倍。笔者与工作室中就职于阿里淘系的秋呈同学进行了深切交流,从中收获到了大佬对于加密解密技术的深刻理解,感受到了其技术深度、广度和厚度。在自己进行聆听和阅读诸多文献博客后,经过自己的总结消化得到此篇文章,同时感激我的前端领路人一直以来对于我的帮助和解惑。

1.加密和解密

  在正式深入探讨加密和解密技术之前,我们先来回顾和理解一下什么是加密和解密、为什么需要加密和解密技术?

  加密:加密就是对于不想被直接看到的明文数据或文件进行某种或者某些算法处理,使其成为他人不可直接进行阅读和获取信息的一段代码,而这段代码即为“密文”,从而实现保护数据的目的。

  解密解密加密逆过程,即把通过加密算法处理后的加密数据,采用对应的解密算法将密文的编码信息还原到之前的原始明文数据,从而达到自己可以获取和阅读的目的。

  通过阅读加密解密的定义,相信聪明的读者已经对于它们的作用有了大致的理解,笔者在这里通过形象的比喻进行概念的阐述。在下面的对比图片中,我们可以看到通过加密算法对原始明文数据进行加密处理得到密文,从而实现保密的作用;而我们要得到密文中的数据,就需要使用对应的解密算法进行获取。同样的,在我们生活中如果一间屋子没有门锁是不是很没有安全感,那么需要通过加装门锁来保障财物的安全(可能你把钱存在了银行吧),那么屋主需要通过钥匙进行开锁进屋、关锁出屋等操作。而钥匙对应的就是密钥,开锁关锁对应的分别是开锁和关锁操作,当然这是最简单和最普通的加密解密的解释,要知道门锁和密码都是可以通过暴力进行破解的,即用来防君子不防小人的。

2.对称加密和非对称加密

  前面介绍的加密和解密模型是最简单的技术,不法分子获取到你的密钥是可以进行解密的,而这里采用单一相同密钥进行加密和解密的技术就是对称加密。(PS:百度百科显示密钥、公钥和私钥的“钥”都读作“yuè”,钥匙的“钥”读作“yào” )

  对称加密:对称加密算法是在加密和解密过程中,发送和接收双方对数据信息采用同一个密钥进行加密和解密操作,双方都需要达成共识采用同一套加密解密方案──相同密钥

  客户端和服务端进行HTTP协议通信时,采用对称加密,如果只使用一个秘钥,很容易破解;如果每次用不同的秘钥,海量秘钥的管理和传输成本又会比较高。

对称加密机制

  非对称加密:非对称加密算法是采用公钥和私钥结合的方式进行加密解密的,公钥即公开共享的密钥,私钥即私有密钥。因为加密和解密使用的是两个不同的密钥,因此被称为非对称加密──不同密钥。非对称加密的加密解密模式是:采用公钥加密的数据,使用对应的私钥进行解密;在用私钥加密的数据,使用对应的公钥进行解密。

  客户端和服务端进行HTTPS协议通信时,采用对称加密和非对称加密兼有的混合加密机制。非对称加密方面,浏览器向服务器发起请求,服务器收到请求之后,将公钥传输给浏览器,然后在接下来的通信中,浏览器要向服务器发送数据时,先用公钥将数据加密,然后发送给服务器,服务器收到之后,再用对应的私钥进行解密,这样就保证了浏览器──服务器这条路的数据安全。因为浏览器发送给服务器的数据,只有服务器有私钥能够解密它。

非对称加密机制

混合加密机制

3.MD5算法

  在前后端开发过程中常见的加密算法众多,其中对称加密算法主要有:DES3DESAES 等,常见的非对称算法 主要有 RSADSA 等,散列算法主要有 SHA-1MD5 等。

3.1 加密算法的安全级别

安全级别 破解算法复杂度 算法
薄弱 O(2 40) DES, MD5
传统 O(2 64) RC4, SHA-1
基准 O(2 80) 3DES
标准 O(2 128) AES-128, SHA-256
较高 O(2 192) AES-192, SHA-384
超高 O(2 256) AES-256, SHA-512

3.2 MD5算法

  MD5算法MD5全称Message Digest Algorithm 5,即消息摘要算法第5版。MD5是计算机安全领域广泛使用的一种散列函数,用于确保信息传输的完整性,是一种单向加密算法,一种不可逆的加密方式。MD5是摘要算法,所以严格意义上不属于加密算法。MD5以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。简而言之,就是进行哈希处理。区块链里面,能够做到防篡改就是因为累积hash,用的就是这类算法的不可逆特性。

特点

  MD5的主要特点有:长度固定、计算简单、抗修改性以及强抗碰撞性。

  • 长度固定MD5加密后值固定长度是128位,使用32个16进制数字进行表示。
  • 计算简单MD5加密采用的是散列算法(哈希算法),进行MD5计算时简单易得,加密速度快。
  • 抗修改性MD5值是16进制数,当修改原数据的1个字节,所代表MD5值也是截然不同,只要有任何改动都能够察觉。
  • 强抗碰撞性碰撞性就是通过MD5加密算法得到MD5值中与其它明文加密得到的MD5值相同的概率。强抗碰撞性指要找到散列值相同的两条不同的消息是非常困难的,得到相同MD5的概率低。

原理

  • 填充数据:首先将输入的数据长度(bit)进行512求余,如果取余得到结果不等于448,则对其进行填充bit使其取余结果448。具体填充方式是在其第一位bit填充1,其余N位bit进行补0,这样得到的数据长度为512*N+448。
  • 记录数据长度:其次将填充前的数据长度用64位进行存储,而后将其追加在填充后的数据后面,使其数据长度为N×512+448+64=(N+1)×512bit。
  • 装入标准幻数:其实就是将128bit的MD5值按照每32bit就是一个标准幻数,可以得到四个标准幻数A=01234567B=89ABCDEFC=FEDCBA98以及D=76543210
  • 四轮循环运算:上面数据处理后的数据长度是(N+1)/512,按照每512位(64字节)作为一块,总共要循环N+1次,并将块细分为16个小组,每组的长度为32位(4字节),这16个小组即为一轮,总共得循环4轮,即64次循环。总的来说我们需要(N+1)个主循环,每个主循环包含了64次子循环,来不断的改变幻数A,B,C,D才能最终得到数据的MD5值。

源码

  我们在安装md5包后可以看到源码信息,源码中依赖的crypt模块。

(function(){
  var crypt = require('crypt'),
      utf8 = require('charenc').utf8,
      isBuffer = require('is-buffer'),
      bin = require('charenc').bin,

  // The core
  md5 = function (message, options) {
    // Convert to byte array
    if (message.constructor == String)
      if (options && options.encoding === 'binary')
        message = bin.stringToBytes(message);
      else
        message = utf8.stringToBytes(message);
    else if (isBuffer(message))
      message = Array.prototype.slice.call(message, 0);
    else if (!Array.isArray(message) && message.constructor !== Uint8Array)
      message = message.toString();
    // else, assume byte array already
    
    // m是将btyes转为数据信息
    var m = crypt.bytesToWords(message),
        l = message.length * 8,//数据信息的长度
        //标准幻数
        a =  1732584193,
        b = -271733879,
        c = -1732584194,
        d =  271733878;

    // 交换字节
    for (var i = 0; i < m.length; i++) {
      m[i] = ((m[i] <<  8) | (m[i] >>> 24)) & 0x00FF00FF |
             ((m[i] << 24) | (m[i] >>>  8)) & 0xFF00FF00;
    }

    // 补零操作
    m[l >>> 5] |= 0x80 << (l % 32);
    m[(((l + 64) >>> 9) << 4) + 14] = l;

    // Method shortcuts
    var FF = md5._ff,
        GG = md5._gg,
        HH = md5._hh,
        II = md5._ii;
    /*
    四轮循环运算:
    每一个分组512位(64字节)
    */
    for (var i = 0; i < m.length; i += 16) {

      var aa = a,
          bb = b,
          cc = c,
          dd = d;
      
      //第一轮循环
      a = FF(a, b, c, d, m[i+ 0],  7, -680876936);//1
      d = FF(d, a, b, c, m[i+ 1], 12, -389564586);//2
      c = FF(c, d, a, b, m[i+ 2], 17,  606105819);//3
      b = FF(b, c, d, a, m[i+ 3], 22, -1044525330);//4
      a = FF(a, b, c, d, m[i+ 4],  7, -176418897);//5
      d = FF(d, a, b, c, m[i+ 5], 12,  1200080426);//6
      c = FF(c, d, a, b, m[i+ 6], 17, -1473231341);//7
      b = FF(b, c, d, a, m[i+ 7], 22, -45705983);//8
      a = FF(a, b, c, d, m[i+ 8],  7,  1770035416);//9
      d = FF(d, a, b, c, m[i+ 9], 12, -1958414417);//10
      c = FF(c, d, a, b, m[i+10], 17, -42063);//11
      b = FF(b, c, d, a, m[i+11], 22, -1990404162);//12
      a = FF(a, b, c, d, m[i+12],  7,  1804603682);//13
      d = FF(d, a, b, c, m[i+13], 12, -40341101);//14
      c = FF(c, d, a, b, m[i+14], 17, -1502002290);//15
      b = FF(b, c, d, a, m[i+15], 22,  1236535329);//16
      
      //第二轮循环
      a = GG(a, b, c, d, m[i+ 1],  5, -165796510);
      d = GG(d, a, b, c, m[i+ 6],  9, -1069501632);
      c = GG(c, d, a, b, m[i+11], 14,  643717713);
      b = GG(b, c, d, a, m[i+ 0], 20, -373897302);
      a = GG(a, b, c, d, m[i+ 5],  5, -701558691);
      d = GG(d, a, b, c, m[i+10],  9,  38016083);
      c = GG(c, d, a, b, m[i+15], 14, -660478335);
      b = GG(b, c, d, a, m[i+ 4], 20, -405537848);
      a = GG(a, b, c, d, m[i+ 9],  5,  568446438);
      d = GG(d, a, b, c, m[i+14],  9, -1019803690);
      c = GG(c, d, a, b, m[i+ 3], 14, -187363961);
      b = GG(b, c, d, a, m[i+ 8], 20,  1163531501);
      a = GG(a, b, c, d, m[i+13],  5, -1444681467);
      d = GG(d, a, b, c, m[i+ 2],  9, -51403784);
      c = GG(c, d, a, b, m[i+ 7], 14,  1735328473);
      b = GG(b, c, d, a, m[i+12], 20, -1926607734);
      
      //第三轮循环
      a = HH(a, b, c, d, m[i+ 5],  4, -378558);
      d = HH(d, a, b, c, m[i+ 8], 11, -2022574463);
      c = HH(c, d, a, b, m[i+11], 16,  1839030562);
      b = HH(b, c, d, a, m[i+14], 23, -35309556);
      a = HH(a, b, c, d, m[i+ 1],  4, -1530992060);
      d = HH(d, a, b, c, m[i+ 4], 11,  1272893353);
      c = HH(c, d, a, b, m[i+ 7], 16, -155497632);
      b = HH(b, c, d, a, m[i+10], 23, -1094730640);
      a = HH(a, b, c, d, m[i+13],  4,  681279174);
      d = HH(d, a, b, c, m[i+ 0], 11, -358537222);
      c = HH(c, d, a, b, m[i+ 3], 16, -722521979);
      b = HH(b, c, d, a, m[i+ 6], 23,  76029189);
      a = HH(a, b, c, d, m[i+ 9],  4, -640364487);
      d = HH(d, a, b, c, m[i+12], 11, -421815835);
      c = HH(c, d, a, b, m[i+15], 16,  530742520);
      b = HH(b, c, d, a, m[i+ 2], 23, -995338651);
      
      //第四轮循环
      a = II(a, b, c, d, m[i+ 0],  6, -198630844);
      d = II(d, a, b, c, m[i+ 7], 10,  1126891415);
      c = II(c, d, a, b, m[i+14], 15, -1416354905);
      b = II(b, c, d, a, m[i+ 5], 21, -57434055);
      a = II(a, b, c, d, m[i+12],  6,  1700485571);
      d = II(d, a, b, c, m[i+ 3], 10, -1894986606);
      c = II(c, d, a, b, m[i+10], 15, -1051523);
      b = II(b, c, d, a, m[i+ 1], 21, -2054922799);
      a = II(a, b, c, d, m[i+ 8],  6,  1873313359);
      d = II(d, a, b, c, m[i+15], 10, -30611744);
      c = II(c, d, a, b, m[i+ 6], 15, -1560198380);
      b = II(b, c, d, a, m[i+13], 21,  1309151649);
      a = II(a, b, c, d, m[i+ 4],  6, -145523070);
      d = II(d, a, b, c, m[i+11], 10, -1120210379);
      c = II(c, d, a, b, m[i+ 2], 15,  718787259);
      b = II(b, c, d, a, m[i+ 9], 21, -343485551);
      
      /*加入到之前计算的结果当中*/
      a = (a + aa) >>> 0;
      b = (b + bb) >>> 0;
      c = (c + cc) >>> 0;
      d = (d + dd) >>> 0;
    }

    return crypt.endian([a, b, c, d]);
  };

  // 辅助函数--线性函数
  md5._ff  = function (a, b, c, d, x, s, t) {
    var n = a + (b & c | ~b & d) + (x >>> 0) + t;
    return ((n << s) | (n >>> (32 - s))) + b;
  };
  md5._gg  = function (a, b, c, d, x, s, t) {
    var n = a + (b & d | c & ~d) + (x >>> 0) + t;
    return ((n << s) | (n >>> (32 - s))) + b;
  };
  md5._hh  = function (a, b, c, d, x, s, t) {
    var n = a + (b ^ c ^ d) + (x >>> 0) + t;
    return ((n << s) | (n >>> (32 - s))) + b;
  };
  md5._ii  = function (a, b, c, d, x, s, t) {
    var n = a + (c ^ (b | ~d)) + (x >>> 0) + t;
    return ((n << s) | (n >>> (32 - s))) + b;
  };

  // 打包私有程序块大小
  md5._blocksize = 16;
  md5._digestsize = 16;
  
  // 导出包文件
  module.exports = function (message, options) {
    if (message === undefined || message === null)
      throw new Error('Illegal argument ' + message);

    var digestbytes = crypt.wordsToBytes(md5(message, options));
    return options && options.asBytes ? digestbytes :
        options && options.asString ? bin.bytesToString(digestbytes) :
        crypt.bytesToHex(digestbytes);
  };

})();

复制代码

使用

  在终端框中输入npm install md5即可安装成功。具体文档可以查看www.npmjs.com/package/md5

$ npm install md5
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN node@1.0.0 No description
npm WARN node@1.0.0 No repository field.

+ md5@2.3.0
added 4 packages from 4 contributors and audited 4 packages in 2.457s
found 0 vulnerabilities
复制代码

  在Node中使用MD5进行加密。

const md5 = require("md5");
console.log(md5("一川"));//3db9c89fbb99eca13513b823ac09ec5b
复制代码

  其实,在Node中的crypto模块封装提供了诸多加密功能,实现了包括对 OpenSSL 的哈希、HMAC、加密、解密、签名、以及验证功能的一整套封装。使用 crypto 模块查看支持的 hash 函数:crypto.getHashes()

[ 'RSA-MD4',
  'RSA-MD5',
  'RSA-MDC2',
  'RSA-RIPEMD160',
  'RSA-SHA1',
  'RSA-SHA1-2',
  'RSA-SHA224',
  'RSA-SHA256',
  'RSA-SHA384',
  'RSA-SHA512',
  'blake2b512',
  'blake2s256',
  'md4',
  'md4WithRSAEncryption',
  'md5',
  'md5-sha1',
  'md5WithRSAEncryption',
  'mdc2',
  'mdc2WithRSA',
  'ripemd',
  'ripemd160',
  'ripemd160WithRSA',
  'rmd160',
  'sha1',
  'sha1WithRSAEncryption',
  'sha224',
  'sha224WithRSAEncryption',
  'sha256',
  'sha256WithRSAEncryption',
  'sha384',
  'sha384WithRSAEncryption',
  'sha512',
  'sha512WithRSAEncryption',
  'ssl3-md5',
  'ssl3-sha1',
  'whirlpool' ]
复制代码

  使用crypto模块进行md5加密。

const crypto = require("crypto");
const md5 = crypto.createHash("md5");

// 对密码明文进行hash加密
const passToHash = password=>md5.update(password).digest("hex");

const password = "yichuan";
console.log(passToHash(password));
//9c1719293e8a445279f4bff5c954fda0
// 
复制代码

  可以通过 www.cmd5.com/ 反向查询得到密码,不够安全。

加盐加密

  前面我们谈到MD5的防碰撞性好,但是强中自有强中手呀,我们只要知道你的加密方式是原生的MD5,就能通过规律进行查询破解,这不安全呀gie gie。为了提高其安全性和唯一性,我们可以在进行加密的时候加盐,其实就是在计算密码的哈希值时添加随机数值加强数据的安全性。加盐加密可以有效抵御诸如字典攻击、彩虹表攻击等密码攻击媒介。

自定义盐值

const crypto = require("crypto");
const md5 = crypto.createHash("md5");
// 普通的加盐(自定义的盐)
const salt = "YiChuanIsAHandsome";
const passToSaltHash = password=>md5.update(password+salt).digest("hex");
const password = "yichuan";

console.log(passToSaltHash(password));
// e56fb85a6fb79120b2db837a46a6aba3
复制代码

随机盐值

const crypto = require("crypto");
const md5 = crypto.createHash("md5");
// 自定义盐值
const passToRandomSaltHash = (password)=>{
  // 产生32位长度的随机盐值
  const salt = crypto.randomBytes(32);
  // 记录盐值可读的字符串版本
  console.log("salt:  " + salt.toString("hex"));//a5bb712bf9de2b902be996c3317344185aed4bfbe78f68382429ef4c6aadac8d
  return md5.update(password+salt).digest("hex")
}
const password = "yichuan";
console.log(passToRandomSaltHash(password));//ff439334dec47045b9be413c84abfe82
复制代码

4.总结

  简而言之,md5这类hash官方说法叫做字符串定长算法,给出一段随便多长的字符串,通过hash后都能得到唯一定长的字符串,且不可逆。但是这类hash还是有一定的碰撞机率,也就是极小可能不同的字符串生成同样的定长字符串,但是几率很小。在实际应用开发时,前端页面拿到获取的密码,为了防止他输入的是各种符号,可以全部转成ascii码,然后,在这个基础上全部加指定数字,得到一串新的字符串。最后,在服务端对应解码就好。或者,全是字母的话,那就按照字母表下标对任何一个数字求余数,换成对应下边的字母。其他的加密方法之后再介绍吧。

参考资料

[1] 《浅谈常见的七种加密算法及实现》

[2] 《如何加密传输和存储用户密码》

[3]《图解HTTP》

[4]《MD5算法全解析》

[5]《NPM文档》

写在最后

我愿意用最通俗易懂的语言去表达我对Vuex的理解,如果在理解有偏差还请大家指出,谢谢。祝大家五一快乐,拥有江湖山海。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享