现在的位置: 首页 > 综合 > 正文

openssl的互斥回调机制

2013年10月26日 ⁄ 综合 ⁄ 共 1689字 ⁄ 字号 评论关闭

很不幸,openssl的底层实现不是线程安全的,虽然我可以通过我自己实现的锁来安全的在不同的线程中使用同一个 ssl连接,也就是一个ssl指针可以在多个线程中共用,但是我们能看到的最底层也就是这个SSL类型的指针了,这也是我们可以保护的最底层了,再往下的 层次中该如何实现线程互斥呢?大致看了一下openssl的代码,发现在底层加密的时候用到了很多的共享的数据结构或者共享变量,如此一来,虽然比较上层 的SSL指针可以很好的被保护,但是一旦两个线程同时读写而同时调用加密解密函数的话会很显然的用到那些共享变量和共享数据结构,如此一来就很容易出错, 比如我遇到的就是本来要SSL_read出100个字节的数据,可是可能由于SSL_write此时修改了数据结构,我的程序异常崩溃,分析dmp文件发 现是由于s3_read_bytes中将读取数据的长度填充成了0xedffffff,危险啊!
可是怎么解决这个问题呢?总要有解决的办法啊,是的,有办法,openssl提供了几个接口函数用于设置一些回调函数,在openssl本身它也自带了一 个,在th-lock.c中实现。这两个接口函数是:void CRYPTO_set_locking_callback(void (*func)(int mode,int type, const char *file,int line))设置加锁解锁回调函数;void CRYPTO_set_id_callback(unsigned long (*func)(void))设置得到线程id回调函数。其中我觉得第一个非常有创意,将如何加锁和解锁留给用户自己去实现,这就在库的底层将机制和用户 的策略分离中出来,并且底层可以保持一份平台无关的加密和解密代码,各个不同的操作系统平台可以提供自己实现的加锁和解锁回调函数,并且如果用户只是跑单 线程,那么加锁和解锁会带来很大不必要的开销,于是对共享数据的保护就成了可选的选择了,总之,策略不会污染机制,一切分得很清!对于上面的接口设置的回 调函数,在windows平台就是:
void win32_locking_callback(int mode, int type, char *file, int line)
{
    if (mode & CRYPTO_LOCK)
    {
        WaitForSingleObject(lock_cs[type],INFINITE);  //lock_cs是一个数组,数量为潜在的共享数据的数量,由CRYPTO_num_locks得到
    }
    else
    {
        ReleaseMutex(lock_cs[type]);
    }
}
在linux平台上普通话pthread相应的回调函数就是:
void pthreads_locking_callback(int mode, int type, char *file, int line)
{
    if (mode & CRYPTO_LOCK)
    {
        pthread_mutex_lock(&(lock_cs[type]));
        lock_count[type]++;
    }
    else
    {
        pthread_mutex_unlock(&(lock_cs[type]));
    }
}
不 能不说这一切很简单又很美,叹为观止!openssl默认不实现线程安全,就是说它默认不会付出可能用户根本就无意付出的代价,你的程序,由你自己掌控。 最终openssl的加密解密底层非常美观,非常平台无关化,因此非常简洁,如果不这么实现,那么本来就很复杂的加密解密算法加上平台相关的if- then-else(或者switch-case)的判断,然后再加锁解锁,让人一声叹息后发誓再也不读openssl了,无疑是雪上加霜。我们需要学习 的不是这里的代码本身,而是这种架构的思想,机制和策略分离,灵活性就可以由用户自己掌控。机制就是回调函数的调用路径或者说是调用堆栈或者说在一些情况 下激活回调函数,具体回调函数如何实现就是用户的策略,用户负责实现回调函数。一切看起来很美,实际上也是。

抱歉!评论已关闭.