[关闭]
@romangol 2016-12-21T09:56:58.000000Z 字数 2913 阅读 1528

密码学诊所

research crypto


0 引言

本文将不断更新我们搜集到的现实中密码学误用问题

参考阅读

1 个推SDK协议密钥交换

2 Imagine Viewer 注册算法的DSA误用

3 CryptCat的协议加密实现安全问题

Cryptcat是经典网络工具netcat的加密版,使用twofish算法加密,其密钥协商过程基于发送、接收双发共享的一个password。
Cryptcat密码学模块的源文件主要为farm9crypt.cpptwofish.cppfarm9crypt.cpp作为netcat和twofish之间的接口,twofish.cpp`实现了加解密算法。本次分析的源码来自其sourceforge项目主页 ,unix版的cryptcat密码学模块基本相同

3.1 加密解密流程

Cryptcat使用了CBC(密文分组链接)模式,大致流程如下:

  1. 发送端将待发送数据的size与一些“随机数”用password加密后发送,接收端解开后可以得知size
  2. 将第一步的密文与第一步的明文异或后,用password加密发送,密文作为后续CBC模式的IV
  3. 在CBC模式中,采用了ciphertext stealing技术,可以发送不是分组长度倍数的数据

ciphertext stealing

如上图,b为分组长度,b-d是最后一个分组的填充长度,默认用0填充。在传输密文时,倒数第二个分组()后半部分b-d长度的密文无需传输,因为解密后可以得到这部分的值。所以利用Ciphertext Stealing技术,在无需扩展密文长度的情况下进行分组加密,减少了不必要的网络传输。

3.2 密码学安全问题

1) 初始化

keystr为用户输入的password,此处使用了固定值的随机数种子。

extern "C" void farm9crypt_init( char* keystr ) {
   printf( "farm9crypt_init: %s\n", keystr );
   encryptor = new TwoFish( generateKey( keystr ), false, NULL, NULL );
   decryptor = new TwoFish( generateKey( keystr ), true, NULL, NULL );
   initialized = true;
   srand( 1000 );
}

2) 密钥初始化

twofish2.cpp中的generateKey函数,功能是将用户提供的字符串password转换成twofish的128bit密钥。

static char key[32];
char* generateKey( char* s ) {
int sIdx = 0;
for ( int i = 0; i < 32; i++ ) {
    char sval = *( s + sIdx );
    if (( sval >= '0' ) && ( sval <= '9' )) {
        key[i] = sval;
    } else if (( sval >= 'a' ) && ( sval <= 'f' )) {
        key[i] = sval;
    } else {
        int q = sval%16;
        if ( q < 10 ) {
            key[i] = ('0' + q);
        } else {
            key[i] = ('a' + q - 10);
        }
    }
    sIdx++;
    if ( *( s + sIdx ) == 0 ) {
        sIdx = 0;
    }
}
return( &key[0] );

对password的每个字符,函数用取模的方式只取了4个bit,如果password过短的话函数就对passwrod进行循环,超过32个字符的话则会丢弃剩下的部分。造成多个字符串可以对应相同的twofish密钥,并且还存在默认密码metallica的情况。

p.s. 这个bug已经有人报告了,但开发者没有回复

3) CBC模式

iv的产生与farm9crypt.cpp中的farm9crypt_write函数有关。

char tempbuf[16];
char outbuf[16];
sprintf( tempbuf, "%d %d", size, rand() );
tempbuf[strlen(tempbuf)] = 'x';
encryptor->setSocket( sockfd );
encryptor->setOutputBuffer( (unsigned char*)&outBuffer[0] );
encryptor->resetCBC();
encryptor->blockCrypt( tempbuf, outbuf, 16 );
encryptor->blockCrypt( tempbuf, outbuf, 16 );

tempbuf的取值来自传输数据的size和rand(),而rand的种子在farm9crypt_init中被设定为了1000,因此这里rand并没有产生随机意义。随机的来源还是开辟数组空间后栈上原来的值。

encryptor->blockCrypt( tempbuf, outbuf, 16 );

这一语句出现了两次,blockCrypt函数的作用是对输入进行加密或解密。 第一次将tempbuf加密后发送给接受端,接收端解密后可以获得待发送内容的size 第二次将tempbuf与第一次的密文异或后加密后发送,作为CBC模式的IV.

从接受端的函数farm9crypt_read也能看出这一过程,decryptor也调用了blockCrypt函数两次,第一次得到变量limit,就是所要接受数据的大小,atoi的调用对应了发送端的

sprintf( tempbuf, "%d %d", size, rand() );

第二次则是取得了IV的值。

decryptor->resetCBC();
decryptor->setOutputBuffer( (unsigned char*)&outBuffer[0] );
decryptor->blockCrypt( buf, outbuf, 16 );
decryptor->flush();
decryptor->setOutputBuffer( (unsigned char*)&outBuffer[0] );
decryptor->blockCrypt( buf + 16, outbuf2, 16 );
int limit = atoi( outbuf );
total = 0;
char* inbuf = &inBuffer[0];
while ( total < limit ) {
    int result = recv( sockfd, inbuf + total, limit - total, 0 );
    if ( result > 0 ) {
        total += result;
    } else {
        break;
    }
}

4) 传输完整性问题

缺少mac验证。尤其是第一个包,包含了待收数据的大小,被攻击者修改后会导致后续的内容都无法解密。

3.3 总结

从cryptcat源码分析中看到了一些密码学的使用问题包括默认密码、固定随机数种子、IV随机性不足、缺少mac验证等。

4 腾讯MTA SDK iOS版加密通讯密钥问题

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注