一. 简介
MD5 是一种信息摘要算法(Message-Digest Algorithm),可以产生出一个 128 位(8 位(bit)= 1 字节(byte),16 字节)的散列值(hash).
12345 MD5===> e10adc3949ba59abbe56e057f20f883e
// 说好的 128 位, 为什么有 32 个字符.
// 这串码是 16 进制表示,1 位 == 4 位二进制.
1
12345 MD5===> e10adc3949ba59abbe56e057f20f883e
2
// 说好的 128 位, 为什么有 32 个字符.
3
// 这串码是 16 进制表示,1 位 == 4 位二进制.
源于其算法, 不管是对于多大的文件数据进行 MD5 加密, 所得到结果的长度都是 32 个字符(1 个中文字符等于 2 个字节, 一个字母字符等于一个字节). 对于内容相同的数据进行加密, 得到的结果必然是相同的, 如果内容有改动, 哪怕是一个字节, 其 MD5 值也会改动.
所以也可以说 MD5 是有损算法, 在一定程度有丢失. 举一个假设就是压缩文件, 前后可以进行压缩和解压, 并且内容是不可以丢失的, 如果采用 MD5 来做, 那么解压后肯定有一部分的数据丢失了, 肯定不可取.
所以 MD5 的结果是不可逆运算的, 也就是几乎不可能通过 MD5 值去逆推出原始数据. 因为不管是多大的文件最终都是转化成 128 位的值, 所以现实是可能存在有不同的原始数据被转化成同样的 MD5 值的. 但是想主动搜寻两个不同的数据, 让他们具有相同的 MD5 值, 是非常困难. 或者有一个原始数据和它的 MD5 数据, 想再找一个和其 MD5 数据一样的原始数据也几乎是不可能的.
但是并不是说 MD5 是完全不可破解的. 有专业的黑客甚至普通黑客也可以利用 MD5 值实际是有损压缩技术这一原理,将 MD5 的逆运算的值作为一张表俗称彩虹表的散列表来破解密码。市面上也有不少的 MD5 破解网站, 大体的原理类似, 通过查字典的方式去查找.
二. 应用
01. 一致性检验
比如说在进行文件下载的时候, 会生成文件内容的 MD5 值. 等到文件完整下载的时候, 再用该文件得出 MD5 值和之前的文件的 MD5 值进行对比, 用以确认文件的内容的一致性.
有很多时候, 你会发现网络上的很多文件的命名都是一段 hash 值加后缀名的方式. 这样处理的好处是能在一定的程度上避免同名, 更大的用处是能够根据其文件内容来获取专属的 MD5 值来命名. 这样做, 可以算是以内容来命名, 可以一定程度上避免资源的浪费, 重复的文件.
所以说,MD5 有点类似 ” 指纹 ”, 每一个都独一无二. 只要内容不一致, 就产生不一样的 MD5 值.
利用 MD5 算法来进行文件校验的方案被大量应用到软件下载站、论坛数据库、系统文件安全等方面.
02. 数字签名
MD5 的典型应用是对一段字符串 (重要隐私信息) 产生,以防止被“篡改”。
你将一段话写在一个文件中,并产生一个 MD5 的值并记录在案,然后你可以传播这个文件给别人,别人如果修改了文件中的任何内容,你对这个文件重新计算 MD5 时就会发现(两个 MD5 值不相同)。如果再有一个第三方的认证机构,用 MD5 还可以防止文件作者的“抵赖”,这就是所谓的数字签名应用。
03. 安全访问认证
在现实的大部分场景都有登录的需要. 当用户登录的时候,系统把用户输入的密码进行 MD5 Hash 运算,然后再去和保存在文件系统中的 MD5 值进行比较,进而确定输入的密码是否正确。通过这样的步骤,系统在并不知道用户密码的明码的情况下就可以确定用户登录系统的合法性。这可以避免用户的密码被具有系统管理员权限的用户知道。
三. 原理
MD5 以 512 位分组来处理输入的信息,且每一分组又被划分为 16 个 32 位子分组,经过了一系列的处理后,算法的输出由四个 32 位分组组成,将这四个 32 位分组级联后将生成一个 128 位散列值。引用自 - 刘俊辉. MD5 消息摘要算法实现及改进[J]. 福建电脑, 2007 (4): 92-93.
function md5(string) {
function md5_RotateLeft(lValue, iShiftBits) {
return (lValue << iShiftBits) | (lValue >>> (32 – iShiftBits));
}
function md5_AddUnsigned(lX, lY) {
var lX4, lY4, lX8, lY8, lResult;
lX8 = (lX & 0x80000000);
lY8 = (lY & 0x80000000);
lX4 = (lX & 0x40000000);
lY4 = (lY & 0x40000000);
lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
if (lX4 & lY4) {
return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
}
if (lX4 | lY4) {
if (lResult & 0x40000000) {
return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
} else {
return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
}
} else {
return (lResult ^ lX8 ^ lY8);
}
}
function md5_F(x, y, z) {
return (x & y) | ((~x) & z);
}
function md5_G(x, y, z) {
return (x & z) | (y & (~z));
}
function md5_H(x, y, z) {
return (x ^ y ^ z);
}
function md5_I(x, y, z) {
return (y ^ (x | (~z)));
}
function md5_FF(a, b, c, d, x, s, ac) {
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_F(b, c, d), x), ac));
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
};
function md5_GG(a, b, c, d, x, s, ac) {
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_G(b, c, d), x), ac));
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
};
function md5_HH(a, b, c, d, x, s, ac) {
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_H(b, c, d), x), ac));
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
};
function md5_II(a, b, c, d, x, s, ac) {
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_I(b, c, d), x), ac));
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
};
function md5_ConvertToWordArray(string) {
var lWordCount;
var lMessageLength = string.length;
var lNumberOfWords_temp1 = lMessageLength + 8;
var lNumberOfWords_temp2 = (lNumberOfWords_temp1 – (lNumberOfWords_temp1 % 64)) / 64;
var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
var lWordArray = Array(lNumberOfWords – 1);
var lBytePosition = 0;
var lByteCount = 0;
while (lByteCount < lMessageLength) {
lWordCount = (lByteCount – (lByteCount % 4)) / 4;
lBytePosition = (lByteCount % 4) * 8;
lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount) << lBytePosition));
lByteCount++;
}
lWordCount = (lByteCount – (lByteCount % 4)) / 4;
lBytePosition = (lByteCount % 4) * 8;
lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
lWordArray[lNumberOfWords – 2] = lMessageLength << 3;
lWordArray[lNumberOfWords – 1] = lMessageLength >>> 29;
return lWordArray;
};
function md5_WordToHex(lValue) {
var WordToHexValue = “”,
WordToHexValue_temp = “”,
lByte, lCount;
for (lCount = 0; lCount <= 3; lCount++) {
lByte = (lValue >>> (lCount * 8)) & 255;
WordToHexValue_temp = “0” + lByte.toString(16);
WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length – 2, 2);
}
return WordToHexValue;
};
function md5_Utf8Encode(string) {
string = string.replace(/\r\n/g, “\n”);
var utftext = “”;
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
} else if ((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
} else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
};
var x = Array();
var k, AA, BB, CC, DD, a, b, c, d;
var S11 = 7,
S12 = 12,
S13 = 17,
S14 = 22;
var S21 = 5,
S22 = 9,
S23 = 14,
S24 = 20;
var S31 = 4,
S32 = 11,
S33 = 16,
S34 = 23;
var S41 = 6,
S42 = 10,
S43 = 15,
S44 = 21;
string = md5_Utf8Encode(string);
x = md5_ConvertToWordArray(string);
a = 0x67452301;
b = 0xEFCDAB89;
c = 0x98BADCFE;
d = 0x10325476;
for (k = 0; k < x.length; k += 16) {
AA = a;
BB = b;
CC = c;
DD = d;
a = md5_FF(a, b, c, d, x[k + 0], S11, 0xD76AA478);
d = md5_FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756);
c = md5_FF(c, d, a, b, x[k + 2], S13, 0x242070DB);
b = md5_FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE);
a = md5_FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF);
d = md5_FF(d, a, b, c, x[k + 5], S12, 0x4787C62A);
c = md5_FF(c, d, a, b, x[k + 6], S13, 0xA8304613);
b = md5_FF(b, c, d, a, x[k + 7], S14, 0xFD469501);
a = md5_FF(a, b, c, d, x[k + 8], S11, 0x698098D8);
d = md5_FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF);
c = md5_FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1);
b = md5_FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE);
a = md5_FF(a, b, c, d, x[k + 12], S11, 0x6B901122);
d = md5_FF(d, a, b, c, x[k + 13], S12, 0xFD987193);
c = md5_FF(c, d, a, b, x[k + 14], S13, 0xA679438E);
b = md5_FF(b, c, d, a, x[k + 15], S14, 0x49B40821);
a = md5_GG(a, b, c, d, x[k + 1], S21, 0xF61E2562);
d = md5_GG(d, a, b, c, x[k + 6], S22, 0xC040B340);
c = md5_GG(c, d, a, b, x[k + 11], S23, 0x265E5A51);
b = md5_GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA);
a = md5_GG(a, b, c, d, x[k + 5], S21, 0xD62F105D);
d = md5_GG(d, a, b, c, x[k + 10], S22, 0x2441453);
c = md5_GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681);
b = md5_GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8);
a = md5_GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6);
d = md5_GG(d, a, b, c, x[k + 14], S22, 0xC33707D6);
c = md5_GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87);
b = md5_GG(b, c, d, a, x[k + 8], S24, 0x455A14ED);
a = md5_GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905);
d = md5_GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8);
c = md5_GG(c, d, a, b, x[k + 7], S23, 0x676F02D9);
b = md5_GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A);
a = md5_HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942);
d = md5_HH(d, a, b, c, x[k + 8], S32, 0x8771F681);
c = md5_HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122);
b = md5_HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C);
a = md5_HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44);
d = md5_HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9);
c = md5_HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60);
b = md5_HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70);
a = md5_HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6);
d = md5_HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA);
c = md5_HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085);
b = md5_HH(b, c, d, a, x[k + 6], S34, 0x4881D05);
a = md5_HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039);
d = md5_HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5);
c = md5_HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8);
b = md5_HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665);
a = md5_II(a, b, c, d, x[k + 0], S41, 0xF4292244);
d = md5_II(d, a, b, c, x[k + 7], S42, 0x432AFF97);
c = md5_II(c, d, a, b, x[k + 14], S43, 0xAB9423A7);
b = md5_II(b, c, d, a, x[k + 5], S44, 0xFC93A039);
a = md5_II(a, b, c, d, x[k + 12], S41, 0x655B59C3);
d = md5_II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92);
c = md5_II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D);
b = md5_II(b, c, d, a, x[k + 1], S44, 0x85845DD1);
a = md5_II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F);
d = md5_II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0);
c = md5_II(c, d, a, b, x[k + 6], S43, 0xA3014314);
b = md5_II(b, c, d, a, x[k + 13], S44, 0x4E0811A1);
a = md5_II(a, b, c, d, x[k + 4], S41, 0xF7537E82);
d = md5_II(d, a, b, c, x[k + 11], S42, 0xBD3AF235);
c = md5_II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB);
b = md5_II(b, c, d, a, x[k + 9], S44, 0xEB86D391);
a = md5_AddUnsigned(a, AA);
b = md5_AddUnsigned(b, BB);
c = md5_AddUnsigned(c, CC);
d = md5_AddUnsigned(d, DD);
}
return (md5_WordToHex(a) + md5_WordToHex(b) + md5_WordToHex(c) + md5_WordToHex(d)).toLowerCase();
}
四. 前端的应用
目前在 GitHub 上找到两个有 MD5 功能还不错的库(基于 JavaScript)
1.JavaScript-MD5
功能单一, 专门处理 MD5.star 数目前 3k+, 使用非常简单, 并且前后端都可用.
// 前端
<script src=”js/md5.min.js”></script>
var hash = md5(“value”); // “2063c1608d6e0baf80249c42e2be5804”
// 后端
npm install blueimp-md5
require(“http”).createServer(function (req, res) {
// The md5 module exports the md5() function:
var md5 = require(“./md5”),
// Use the following version if you installed the package with npm:
// var md5 = require(“blueimp-md5”),
url = require(“url”),
query = url.parse(req.url).query;
res.writeHead(200, {“Content-Type”: “text/plain”});
// Calculate and print the MD5 hash of the url query:
res.end(md5(query));
}).listen(8080, “localhost”);
console.log(“Server running at http://localhost:8080/”);
2.crypto-js
功能强大, 不止处理 MD5, 支持的模块非常多,star 数目前 6k+, 前后端可用.
// 后端 npm 包管理
npm install crypto-js
import sha256 from ‘crypto-js/sha256’;
import hmacSHA512 from ‘crypto-js/hmac-sha512’;
import Base64 from ‘crypto-js/enc-base64’;
const message, nonce, path, privateKey; // …
const hashDigest = sha256(nonce + message);
const hmacDigest = Base64.stringify(hmacSHA512(path + hashDigest, privateKey));
// 前端 Brower 管理
bower install crypto-js
五. “ 加盐 ”
如果想提高你的安全性, 那么可以采取再进一步对原始数据进行“加盐”,“盐”就是一串比较复杂的字符串。你所添加的 ” 盐 ” 越长越复杂,加密后破解起来就越麻烦. 你可以尝试下在原始数据上加一串复杂的字符串, 然后再去进行 MD5 加密. 这样即使解开 MD5, 也不能知道你的原始数据到底是那一段.
但是即使这样也不可能绝对是绝对安全. 这个“盐”还是有泄漏的风险。比如苹果端、安卓端、前端、后台等等那些个技术人员都是知道的, 都有可能泄漏出去。放在服务器也不绝对安全,也有手段去抓取.
但是相对的还是进一步的提高了加密的安全性.