多线程环境下使用openssl

原创 2014年02月12日 17:25:08

openssl 官网说了:


OpenSSL can safely be used in multi-threaded applications provided that at least two callback functions are set, locking_function and threadid_func.

还有这里说了,Openssl 是安全的,前提是必须注册两个回调函数。其中根据 Openssl 的版本不同,会有不同 版本的 threadid 回调函数。

1. Is OpenSSL thread-safe?

Yes (with limitations: an SSL connection may not concurrently be used by multiple threads). On Windows and many Unix systems, OpenSSL automatically uses the multi-threaded versions of the standard libraries. If your platform is not one of these, consult the INSTALL file.

Multi-threaded applications must provide two callback functions to OpenSSL by calling CRYPTO_set_locking_callback() and CRYPTO_set_id_callback(), for all versions of OpenSSL up to and including 0.9.8[abc...]. As of version 1.0.0, CRYPTO_set_id_callback() and associated APIs are deprecated by CRYPTO_THREADID_set_callback() and friends. This is described in the threads(3) manpage.

这里有几个例子程序,主要是在使用openssl之前,做一些初始化和设置callback函数,以及退出时的范初始化动作:

这里有老外推荐的3本书的链接:

Chapter 10 of the book The Definitive Guide to Linux Network Programming includes a section Thread-safe Programming with OpenSSL (on pages 255-259). This section details how OpenSSL and the pthreads library work. Specially, it tells how to setup the callback functions both in static allocation (where the number of threads are known a priori) and dynamic allocation (where threads are created and destroyed on the fly).

Another good source is Section 4.1 of the book Network Security with OpenSSL, titled Multithread Support. It provides static/dynamic allocation mechanisms in subsections 4.1.1 and 4.1.2, respectively.

Finally, there's the book Unix-Netzwerkprogrammierung mit Threads, Sockets und SSL, which is by far the most comprehensive one on the subject. Unfortunately, the English translation of this German book is not available.

这里的例子程序中,有对locking callback function 参数的注释:

// Locking callback. The type, file and line arguments are
// ignored. The file and line may be used to identify the site of the
// call in the OpenSSL library for diagnostic purposes if required.
void ecos_locking_callback(int mode, int type, char *file, int line)
{
    if (mode & CRYPTO_LOCK)
    {
        cyg_mutex_lock(&(lock_cs[type]));
    }
    else
    {
        cyg_mutex_unlock(&(lock_cs[type]));
    }
}

locking callback function 更详细的例子如:

void ssl_locking_function(int mode, int type, const char * file, int line) {
     if(CRYPTO_LOCK_FIPS == type
          || CRYPTO_LOCK_FIPS2 == type 
          || CRYPTO_LOCK_ERR == type 
          || CRYPTO_LOCK_EVP_PKEY == type)
     {
          printf("ssl_locking_function return directly: %d %d %s %d\n", mode, type, file, line);
          return;
     }


     if(mode & CRYPTO_LOCK) {
          if(mode & CRYPTO_WRITE) {
               printf("ssl_locking_function write lock: %d %d %s %d\n", mode, type, file, line);
               pthread_rwlock_wrlock(&(ssl_lock[type]));
          }
          else {
               printf("ssl_locking_function read lock: %d %d %s %d\n", mode, type, file, line);
               pthread_rwlock_rdlock(&(ssl_lock[type]));
          }
     }
     else
     {
          printf("ssl_locking_function unlock: %d %d %s %d\n", mode, type, file, line);
          pthread_rwlock_unlock(&(ssl_lock[type]));
     }
}

上面的代码中,有些被划掉了的原因是这样的:
在有些性能至关重要的项目中,相比其他值,openssl 会更加频繁地用 CRYPTO_LOCK_FIPS、CRYPTO_LOCK_FIPS2、CRYPTO_LOCK_ERR、CRYPTO_LOCK_EVP_PKEY 中的某些值回调,当像上面的代码中那样直接 return 从而忽视这些值的回调后,性能陡然上升许多,但貌似存在 crash 的风险。如果针对这些值也枷锁的话,程序的性能会下降的很厉害。其中,CRYPTO_LOCK_ERR 是最频繁回调的,在不支持FIPS 的情况下,CRYPTO_LOCK_FIPS 和 CRYPTO_LOCK_FIPS2 是不会被回调的。总的来说,一般情况下,被划掉的代码是不需要的。

基本上,locking回调函数中的几个参数的含义如下:

参数 含义
mode 指明是lock/unlock,或者更详细些,read lock 或 write lock
type 是初始化时动态分配的lock_cs数组的下标,即指明当前用的是第几个lock
file 用于程序诊断,指明当前的lock调用出自openssl的哪个文件
line 用于程序诊断,指明当前的lock调用出自openssl的哪个文件的哪一行

下面是一个比较完整的例子(出处):

==================

// 要注册的两个回调函数就长这样的.
// Pointer to array of locks.
void ecos_locking_callback(int mode, int type, char *file, int line);
unsigned long ecos_thread_id_callback(void);

// ecos_locking_callback 函数中要用到的 lock 数组申明
// 这里没有的 cyg_mutex_t 的类型定义,应该是原作者自定义的东西,Linux 下一般就是 pthread_mutex_t 之类的
// Pointer to array of locks.
static cyg_mutex_t *lock_cs;

// 多线程保护的初始化
// This function allocates and initializes the lock array
// and registers the callbacks. This should be called
// after the OpenSSL library has been initialized and
// before any new threads are created.  
void thread_setup(void)
{
    int i;

    // 动态创建 lock 数组
    // Allocate lock array according to OpenSSL's requirements
    lock_cs=OPENSSL_malloc(CRYPTO_num_locks() * sizeof(cyg_mutex_t));

    // lock 数组初始化
    // Initialize the locks
    for (i=0; i<CRYPTO_num_locks(); i++)
    {
        cyg_mutex_init(&(lock_cs[i]));
    }

    //  注册两个回调函数
    // Register callbacks
    CRYPTO_set_id_callback((unsigned long (*)())ecos_thread_id_callback);
    CRYPTO_set_locking_callback((void (*)())ecos_locking_callback);
}

// 多线程保护的反初始化
// This function deallocates the lock array and deregisters the
// callbacks. It should be called after all threads have
// terminated.  
void thread_cleanup(void)
{
    int i;

    // 清空 locking 回调函数
    // Deregister locking callback. No real need to
    // deregister id callback.
    CRYPTO_set_locking_callback(NULL);

    // 销毁初始化时分配的 lock 数组
    // Destroy the locks
    for (i=0; i<CRYPTO_num_locks(); i++)
    {
        cyg_mutex_destroy(&(lock_cs[i]));
    }

   // 销毁初始化时分配的 lock 数组
    // Release the lock array.
    OPENSSL_free(lock_cs);
    lock_cs = NULL;
}

// locking 回调函数,由openssl库回调,向 openssl 库 提供 lock/unlock,或更详细些 read lock 或 write lock 的功能
// Locking callback. The type, file and line arguments are
// ignored. The file and line may be used to identify the site of the
// call in the OpenSSL library for diagnostic purposes if required.
void ecos_locking_callback(int mode, int type, char *file, int line)
{
    if (mode & CRYPTO_LOCK)
    {
        cyg_mutex_lock(&(lock_cs[type]));
    }
    else
    {
        cyg_mutex_unlock(&(lock_cs[type]));
    }
}

// thraed id 回调函数,由openssl回调,向 openssl 库提供当前线程号
// Thread id callback.
unsigned long ecos_thread_id_callback(void)
{
    return (unsigned long)cyg_thread_get_id(cyg_thread_self());
}

【注意】
openssl 官网提到下面的有关 openssl 版本的注意事项

CRYPTO_set_locking_callback() is available in all versions of SSLeay and OpenSSL. CRYPTO_num_locks() was added in OpenSSL 0.9.4. All functions dealing with dynamic locks were added in OpenSSL 0.9.5b-dev. CRYPTO_THREADID and associated functions were introduced in OpenSSL 1.0.0 to replace (actually, deprecate) the previousCRYPTO_set_id_callback(), CRYPTO_get_id_callback(), and CRYPTO_thread_id() functions which assumed thread IDs to always be represented by 'unsigned long'.

即,从 openssl 1.0.0 开始,CRYPTO_set_id_callback() 就被废除了,CRYPTO_THREADID_set_callback() 取而代之。因此,注册 threadid 的 callback 函数的时候,要注意判断当前使用的 openssl 的版本。鉴于 openssl 下载页面中出现的最早的 1.0.0 版本,即
3954601 Apr  1 11:11:42 2009openssl-1.0.0-beta1.tar.gz
中的 openssl/crypto.h 中的 OPENSSL_VERSION_NUMBER 定义为 0x10000001L
#define   OPENSSL_VERSION_NUMBER   0x10000001L

于是,判断 openssl 版本的做法如下:
(注意,这里是不建议用 SSLeay() >= OPENSSL_VERSION_NUMBER_1_0_0 的方式的,因为定义回调函数的地方是函数申明,肯定不行,并且下面3处也不能假设任何版本的 openssl 中都定义了 CRYPTO_THREADI 类型。呵呵,幸好 C 语言的宏还支持数值大小比较)
1)定义两个版本的 threadid 回调函数
#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_1_0_0
	void ssl_threadid_function(CRYPTO_THREADID * id);
#else
	unsigned long ssl_threadid_function_deprecated();
#endif

2)注册回调
#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_1_0_0
	CRYPTO_THREADID_set_callback(ssl_threadid_function);
#else
	CRYPTO_set_id_callback(ssl_threadid_function_deprecated);
#endif

3)反注册回调
#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_1_0_0
	CRYPTO_THREADID_set_callback(NULL);
#else
	CRYPTO_set_id_callback(NULL);
#endif

Stackoverflow 的这篇帖子 的回复中,有提到一直build openssl 时就配置“支持多线程”的方式。
实际上说的就是,openssl 的代码中判断了 OPENSSL_THREADS 宏,当该宏定义了,也就是在build Openssl 之前,做了 ./config threads 操作,就支持多线程,否则不支持多线程。
根据这里说的
1. Is OpenSSL thread-safe?

Yes (with limitations: an SSL connection may not concurrently be used by multiple threads). On Windows and many Unix systems, OpenSSL automatically uses the multi-threaded versions of the standard libraries. If your platform is not one of these, consult the INSTALL file.

Multi-threaded applications must provide two callback functions to OpenSSL by calling CRYPTO_set_locking_callback() and CRYPTO_set_id_callback(), for all versions of OpenSSL up to and including 0.9.8[abc...]. As of version 1.0.0, CRYPTO_set_id_callback() and associated APIs are deprecated by CRYPTO_THREADID_set_callback() and friends. This is described in the threads(3) manpage.
个人理解是,依然是“Openssl 的所谓的多线程安全”最多只是“在调用者提供了两个回调函数的前提下的”多线程安全。如果在build Openssl 的时候,强制加上了"no-threads"选项,即先 ./config no-threads,那么这样build 出来的 Openssl,连“在调用者提供了两个回调函数的前提下的”多线程安全都保证不了!

总而言之,至少要注册那两个回调函数,并注意区分不同的 Openssl 版本。





参考:

http://www.openssl.org/docs/crypto/threads.html

http://stackoverflow.com/questions/3919420/tutorial-on-using-openssl-with-pthreads

http://ardoino.com/2008/02/openssl-thread-safe-secure-connections/


按照官网手册中说的,使用openssl,要先通过 CRYPTO_set_locking_callback() 和 CRYPTO_set_id_callback() 注册两个 callback 函数,只有这样,才能确保 openssl 的 API 是 thread-safe 的。

对此,ardoino 的博客中有详细的说明和例子。


相关文章推荐

openssl多线程实例

openssl多线程实例      本示例用多线程实现了一个ssl服务端和一个客户端。 服务端代码如下: #include #include #include #in...

OpenSSL多线程互斥的解决方案--一种新的锁

自己曾经将基于传统套接字的通信程序修改为了SSL的套接字程序,可是却在运行中出了问题,具体就是在SSL_write的地方遇到了NULL指针,不是SSL为NULL了,而是其中的一个字段为NULL但是在S...
  • dog250
  • dog250
  • 2010年02月09日 21:00
  • 6059

HTTPS是大势所趋?看腾讯专家通过Epoll+OpenSSL在高并发压测机器人中支持https

WeTest 导读用epoll编写一个高并发网络程序是很常见的任务,但在epoll中加入ssl层的支持则是一个不常见的场景。腾讯WeTest服务器压力测产品,在用户反馈中收到了不少支持https协议的...

linux环境下ssl多线程编程实例(整理)

服务端: #include #include #include #include #ifndef    _WIN32 #include #include #include #includ...

libcurl 多线程使用注意事项(补充)——HTTPS,openssl多线程使用加锁

问题 多线程libcurl运行一段时间后出现崩掉,没有确定的点,没有确定的URL。一直查看源代码没有问题,最后通过debug跟踪发现是在访问SSL的时候出现的crash。 才想起来openssl是...

openssl多线程实例

本示例用多线程实现了一个ssl服务端和一个客户端。 服务端代码如下: #include #include #include #include #ifndef    _WIN32 ...

openssl 线程安全 Windows

openssl 线程安全 在初始化的时候 调用 thread_setup,在结束的时候调用thread_cleanup。代码摘自crypto/threads/mttest.c static uns...
  • joimson
  • joimson
  • 2016年03月20日 15:58
  • 876

OpenSSL线程安全

OpenSSL can safely be used in multi-threaded applications provided that at least two callback fun...

openssl的互斥回调机制

很不幸,openssl的底层实现不是线程安全的,虽然我可以通过我自己实现的锁来安全的在不同的线程中使用同一个 ssl连接,也就是一个ssl指针可以在多个线程中共用,但是我们能看到的最底层也就是这个SS...
  • dog250
  • dog250
  • 2010年02月09日 21:01
  • 3498

关于openssl库的链接问题

../../../lib/libIceSSL.so: undefined reference to `RAND_load_file' ../../../lib/libIceSSL.so: undef...
  • bytxl
  • bytxl
  • 2015年07月08日 11:20
  • 2088
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:多线程环境下使用openssl
举报原因:
原因补充:

(最多只允许输入30个字)