关注我们 文末有福利
作者简介
陈龙
转转平台运营部前端负责人,9年前端经验,含3年技术管理,平时喜欢研究一些技术方案,写写文章,做做分享。
背景
前端做流量AB测
AB测应该注意什么?
保证流量分配尽量准确
命中某项规则的用户,下次访问依旧是命中相同规则
每个AB测相互独立
有同学说了:让server做不就行了
那我想问:前端不能做么?
于是我就打算在前端实现。
怎么做分流
通常第一时间会想到:哈希算法
然后再将生成的hash值转换成0-99间的数字,再按比例分配就可以了
另外,误差要尽可能的小
比如:10W个hash值,转换后落在每个区间[0-9] [10-19] ..., [90-99] 各10%
当hash算法是现成的,直接拿过来用就行了
那问题来了,为什么hash能做到?
hash在数据结构中的含义和密码学中的含义并不相同,所以在这两种不同的领域里,算法的设计侧重点也不同
什么是hash算法
Hash算法也被称为散列算法,就是把任意长度的字符串,通过散列算法,变换成固定长度的输出,而输出值没有任何规律,这就是散列值
虽然被称为算法,但实际上它更像是一种思想。
Hash算法没有一个固定的公式,只要符合散列思想的算法都可以被称为是Hash算法
什么是散列思想?
对于每一个值,都可以唯一地映射到散列表中的一个位置,而且让位置分配尽可能的均匀
hash算法的特点
它是一种压缩映射,散列值的空间远远小于输入空间,不同的输入可能会有相同的输出。
抗碰撞能力:对于任意两个不同的数据块,其hash值相同的可能性极小;对于一个给定的数据块,找到和它hash值相同的数据块极为困难。
抗篡改能力:对于一个数据块,哪怕只改动其一个比特位,其hash值的改动也会非常大。
固定输出长度:无论多长的输入,输出一定是固定长度
单向唯一:它是一种单向密码体制,即它是一个从明文到密文的不可逆的映射,只有加密过程,没有解密过程
相同输入相同输出:同样的输入,每次都有同样的输出
以MD算法为例:
MD5("version1") = "966634ebf2fc135707d6753692bf4b1e";
MD5("version2") = "2e0e95285f08a07dea17e7ee111b21c8";
使用类型
在数据结构中
hash出来的key,只要保证value大致均匀的放在不同的桶里就可以了
比如hashmap,hash值(key)存在的目的是加速键值对的查找,key的作用是为了将元素适当地放在各个桶里,对于抗碰撞的要求没有那么高。
在密码学中
hash算法的作用主要是用于消息摘要和签名,换句话说,它主要用于对整个消息的完整性进行校验。
比如存储用户的账户密码信息,数据库里是不会存储明文的,通常是存储加密后的md5值,后台会把用户输入的账户和密码做md5,然后和库里存储的md5值做比对。
常见的hash算法
MD4
MD4(RFC 1320)是 MIT 的Ronald L. Rivest在 1990 年设计的,MD 是 Message Digest(消息摘要) 的缩写。它适用在32位字长的处理器上用高速软件实现——它是基于 32位操作数的位操作来实现的。
MD5
MD5(RFC 1321)是 Rivest 于1991年对MD4的改进版本。它对输入仍以512位分组,其输出是4个32位字的级联,与 MD4 相同。MD5比MD4来得复杂,并且速度较之要慢一点,但更安全,在抗分析和抗差分方面表现更好。
SHA-1
SHA1是由NIST NSA设计的,SHA1对任意长度明文的预处理和MD5的过程是一样的,即预处理完后的明文长度是512位的整数倍,但是有一点不同,那就是SHA1的原始报文长度不能超过2的64次方,然后SHA1生成160位的报文摘要。SHA1算法简单而且紧凑,容易在计算机上实现。
因为我用到的是sha-1算法,所以本次分享是基于sha-1算法的分析
SHA-1算法
算法介绍
SHA-1(英语:Secure Hash Algorithm 1,中文名:安全散列算法1)是一种密码散列函数,美国国家安全局设计,并由美国国家标准技术研究所(NIST)发布为联邦数据处理标准(FIPS)。SHA-1可以生成一个被称为消息摘要的160位(20字节)散列值,散列值通常的呈现形式为40个十六进制数。(来自百度)
SHA1的算法已被破解,出于安全考虑官方推荐用SHA2代替,
但作为不涉及安全性的功能(如AB测分流)依旧不失为一种选择
本次算法分析主要是跟大家一起了解Hash算法世界,并非要破解
里面会用到大量的位运算,如果不熟悉的同学,可以参看前面的文章《JavaScript位运算最强指南》
算法思路
对任意长度的明文字符串,转换成数字(32位二进制),并进行初始化分组,
将初始分组后的数组每16个元素分为一组,不足16个元素部分会补齐
申请5个32位的计算因子,记为A、B、C、D、E。
每个分组进行80次复杂混合运算,每个分组计算完成后,得出新的A、B、C、D、E
将新的A、B、C、D、E和老的A、B、C、D、E分别进行求和运算,得出新的计算因子,并重复(4 到 5)的操作,得出最终的A、B、C、D、E
将最终得出的A、B、C、D、E每个元素进行8次混合运算,得出40位16进制hash码
js-sha1算法
var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
function hex_sha1(s) {
return binb2hex(core_sha1(str2binb(s), s.length * chrsz));
}
/*
* Convert an 8-bit or 16-bit string to an array of big-endian words
* In 8-bit function, characters >255 have their hi-byte silently ignored.
*/
function str2binb(str) {
var bin = Array();
var mask = (1 << chrsz) - 1;
for (var i = 0; i < str.length * chrsz; i += chrsz)
bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i % 32);
return bin;
}
/*
* Bitwise rotate a 32-bit number to the left.
*/
function rol(num, cnt) {
return (num << cnt) | (num >>> (32 - cnt));
}
/*
* Calculate the SHA-1 of an array of big-endian words, and a bit length
*/
function core_sha1(x, len) {
/* append padding */
x[len >> 5] |= 0x80 << (24 - len % 32);
x[((len + 64 >> 9) << 4) + 15] = len;
var w = Array(80);
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
var e = -1009589776;
for (var i = 0; i < x.length; i += 16) {
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;
var olde = e;
for (var j = 0; j < 80; j++) {
if (j < 16) w[j] = x[i + j];
else w[j] = rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);
var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)), safe_add(safe_add(e, w[j]), sha1_kt(j)));
e = d;
d = c;
c = rol(b, 30);
b = a;
a = t;
}
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
e = safe_add(e, olde);
}
return Array(a, b, c, d, e);
}
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
functi