2021SC@SDUSC
2021-11-21
第八周完成事项
工作内容
这周的团队工作工作是将上周的剩下的内容分析完全。
BLAKE2算法
上周主要提及了哈希算法在全同态加密过程中的重要贡献以及blake2算法的由来,本周将对上周的内容进行补充完善。
BLAKE2算法起源——哈希碰撞
在介绍BLAKE2算法之前,先说一下之前所使用的哈希算法SHA-1以及为什么不再使用SHA-1。SHA-1会生成160位的摘要,这意味着我们需要进行大约 2 80 2^{80} 280次操作才能出现一次哈希碰撞(多亏了Birthday攻击)。但是在2005年密码学专家通过研究发现,发生一次哈希碰撞其实并不需要这么多次的操作,大概只需要 2 65 2^{65} 265次操作就可以实现了。
这就非常糟糕了,如果现实生活中可以实现哈希碰撞的话,那么这就会让很多使用SHA-1的应用程序变得更加的不安全。而这个操作次数,即 2 65 2^{65} 265次,是一个比较接近实际情况的数字。如果你想用自己的笔记本电脑来进行计算的话,可能需要好几年的时间,但是对于那些手头上有资金有技术的大型组织而言,计算出一次哈希碰撞可能只需要一两周的时间。
但是,这次哈希碰撞的实现让很多安全威胁变得离我们更加近了,很多目前正在使用SHA-1的服务也正在寻找替代方案。如果要代替SHA-1的话,BLAKE2即为一个更优解,下面我将列出使用BLAKE2代替SHA-1的五大原因。
1.SHA-1存在严重的安全问题
没人会愿意使用存在安全问题的哈希算法。不过这也得看情况,这一次破解的只是SHA-1的抗碰撞性,而某些网络应用其实对抗碰撞性并没有太高的要求。比如说,类似HMAC-SHA-1(使用HMAC伪随机函数和SHA-1共同作为哈希函数)或PBKDF2-HMAC-SHA1(使用HMAC-SHA-1作为伪随机函数的PBKDF2密码哈希)这样的哈希函数仍然是安全的,但是仍然建议大家放弃使用SHA-1。
2.BLAKE2是安全的
给BLAKE2的定位是目前安全系数最高的哈希函数,从2012年BLAKE2的发布之日至今,还没有发现针对BLAKE2的攻击。实际上,BLAKE2是基于BLAKE实现的,BLAKE是2008年被提交至SHA-3竞赛的一种哈希函数,而且全世界最优秀的密码学分析专家对这个哈希函数进行了长达四年的审查,然而并没有发现任何的不安全因素。
除此之外,BLAKE2的内部基于的是ChaCha,而ChaCha是高度可信任的,它不太可能被破解。(注:ChaCha是一种由Daniel J.Bernstein设计的流密码,它是TLS1.3所使用的三大密码之一,并且符合新的TLS标准。)
3.BLAKE2的速度比SHA-1更快
由于在设计之初就考虑到了安全和性能方面的优化,BLAKE2在速度方面是要优于SHA-1的。
4.BLAKE2非常简单
可以方便地用各种不同的编程语言来实现BLAKE2,例如C、Go、Java、JavaScript、Python或Rust。比如说,BLAKE2默认就是Go语言“/x/crypto”包中的一部分。除此之外,目前很多流行的加密代码库都包含有BLAKE2,例如OpenSSL和Sodium等等。
5.BLAKE2就是你所需要的东西
BLAKE2不仅仅只是一个简单的哈希函数而已!首先,BLAKE2有两大主要版本:BLAKE2b和BLAKE2s。BLAKE2b是BLAKE的64位版本,它可以生成最高512位的任意长度哈希。BLAKE2s是BLAKE的32位版本,它可以生成最高256位的任意长度哈希。
为了进一步提升哈希计算的速度,BLAKE的并行版本BLAKE2bp和BLAKE2sp可以在多个计算核心上并行运行,而此时的运行效率将是串行运行的八倍。如果你想在BLAKE2的哈希计算中使用密钥的话,例如伪随机函数(PRF)或消息认证码(MAC),你不需要像使用HMAC一样还要专门去实现这些计算,因为BLAKE2可以接受一个密钥作为可选输入参数,计算完成之后你就可以得到一个跟HMAC一样安全的哈希了。
继续上次BLAKE2b.c的算法分析
上次主要分析到了Blake2b执行之前的初始化,这次主要分析blake2b算法的迭代更新。
int blake2b_update( blake2b_state *S, const void *pin, size_t inlen )
{
const unsigned char * in = (const unsigned char *)pin;
if( inlen > 0 )
{
size_t left = S->buflen;
size_t fill = BLAKE2B_BLOCKBYTES - left;
if( inlen > fill )
{
S->buflen = 0;
memcpy( S->buf + left, in, fill );
blake2b_increment_counter( S, BLAKE2B_BLOCKBYTES );
blake2b_compress( S, S->buf );
in += fill; inlen -= fill;
while(inlen > BLAKE2B_BLOCKBYTES) {
blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES);
blake2b_compress( S, in );
in += BLAKE2B_BLOCKBYTES;
inlen -= BLAKE2B_BLOCKBYTES;
}
}
memcpy( S->buf + S->buflen, in, inlen );
S->buflen += inlen;
}
return 0;
}
pin
是一个指向需要加密部分的原文的指针,第一步需要先获取到指针,判断原文长度是否大于零。
const unsigned char * in = (const unsigned char *)pin;
如果大于零,计算一下blake2算法预留出来的缓存空间是否足够。
if( inlen > 0 )
{
size_t left = S->buflen;
size_t fill = BLAKE2B_BLOCKBYTES - left;
if( inlen > fill )
缓存空间充足的话使用需要加密的原文将内容填充。对blake2的状态变量S进行调整同时压缩S的缓冲区。
S->buflen = 0;
memcpy( S->buf + left, in, fill );
blake2b_increment_counter( S, BLAKE2B_BLOCKBYTES );
blake2b_compress( S, S->buf );
然后接下来就是不断压缩原文的时候,通过while循环不断对原文进行压缩,直到长度小于blake2算法的块字节大小。
while(inlen > BLAKE2B_BLOCKBYTES) {
blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES);
blake2b_compress( S, in );
in += BLAKE2B_BLOCKBYTES;
inlen -= BLAKE2B_BLOCKBYTES;
}
最后将不足blake2算法的块字节大小的剩余内容填充到S的缓冲区中,结束更新过程。
memcpy( S->buf + S->buflen, in, inlen );
S->buflen += inlen;
}
return 0;
上述是update方法的大致思路这里补充一下之前没有提到的blake2b_increment_counter
方法和blake2b_compress
方法,首先介绍一下blake2b_increment_counter
方法。
static void blake2b_increment_counter( blake2b_state *S, const uint64_t inc )
{
S->t[0] += inc;
S->t[1] += ( S->t[0] < inc );
}
blake2b_state是blake2b算法执行需要的状态变量,之前有提到过。
typedef struct blake2b_state__
{
uint64_t h[8];
uint64_t t[2];
uint64_t f[2];
uint8_t buf[BLAKE2B_BLOCKBYTES];
size_t buflen;
size_t outlen;
uint8_t last_node;
} blake2b_state;
其中buf就是我们进行压缩的缓冲区空间。
blake2b_increment_counter
方法的主要作用就是做一个计数器,每次将需要加密的部分的长度都加起来保存下来。从S->t[0] += inc;
可以看的比较清楚。
接下来就是内部主要的blake2b_compress
方法。
#define G(r,i,a,b,c,d) \
do { \
a = a + b + m[blake2b_sigma[r][2*i+0]]; \
d = rotr64(d ^ a, 32); \
c = c + d; \
b = rotr64(b ^ c, 24); \
a = a + b + m[blake2b_sigma[r][2*i+1]]; \
d = rotr64(d ^ a, 16); \
c = c + d; \
b = rotr64(b ^ c, 63); \
} while(0)
#define ROUND(r) \
do { \
G(r,0,v[ 0],v[ 4],v[ 8],v[12]); \
G(r,1,v[ 1],v[ 5],v[ 9],v[13]); \
G(r,2,v[ 2],v[ 6],v[10],v[14]); \
G(r,3,v[ 3],v[ 7],v[11],v[15]); \
G(r,4,v[ 0],v[ 5],v[10],v[15]); \
G(r,5,v[ 1],v[ 6],v[11],v[12]); \
G(r,6,v[ 2],v[ 7],v[ 8],v[13]); \
G(r,7,v[ 3],v[ 4],v[ 9],v[14]); \
} while(0)
static void blake2b_compress( blake2b_state *S, const uint8_t block[BLAKE2B_BLOCKBYTES] )
{
uint64_t m[16];
uint64_t v[16];
size_t i;
for( i = 0; i < 16; ++i ) {
m[i] = load64( block + i * sizeof( m[i] ) );
}
for( i = 0; i < 8; ++i ) {
v[i] = S->h[i];
}
v[ 8] = blake2b_IV[0];
v[ 9] = blake2b_IV[1];
v[10] = blake2b_IV[2];
v[11] = blake2b_IV[3];
v[12] = blake2b_IV[4] ^ S->t[0];
v[13] = blake2b_IV[5] ^ S->t[1];
v[14] = blake2b_IV[6] ^ S->f[0];
v[15] = blake2b_IV[7] ^ S->f[1];
ROUND( 0 );
ROUND( 1 );
ROUND( 2 );
ROUND( 3 );
ROUND( 4 );
ROUND( 5 );
ROUND( 6 );
ROUND( 7 );
ROUND( 8 );
ROUND( 9 );
ROUND( 10 );
ROUND( 11 );
for( i = 0; i < 8; ++i ) {
S->h[i] = S->h[i] ^ v[i] ^ v[i + 8];
}
}
首先进行变量初始化,这里有一些问题就是在于变量m的定义,m只是在初始化的时候进行了定义,但是后续的压缩中并没有用到,这点还是有点疑惑。
现在在实验室,马上宿舍就要锁门了,所以下周一再进行补充。
总结
这次先是了解了一下为什么blake2算法代替了SHA-1算法,同时分析了blake2算法的更新过程。更深一步的理解了如何进行哈希算法,一个随机数据怎样变成了碰撞性特别低的一段相同长度的哈希码。
最后,感谢孔老师的指导,感谢戴老师和其他审核老师的阅读!