在ZeroMQ中使用SSL加密

来源:互联网 发布:算法导论书在线观看 编辑:程序博客网 时间:2024/05/16 11:38

It's pretty straightforward to use synchronous encryption over ZeroMQ - just a case of encrypting and decrypting at each end with some previously shared key. Asynchronous encryption is a bit more interesting, as it allows signing for message integrity and authenticity, as well as data hiding. There have been some good examples of crypto over Pub/Sub (notably  Salt), but not a lot of examples of direct messaging.

在ZeroMQ上使用同步加密非常直接:只要在每一端使用预先共享的密钥进行加密和解密就可以了。异步加密更有意义,因为它能对消息进行签名,保证其完整性和真实性,还可以隐藏数据。关于Pub/Sub上加密的例子很多,但是关于直接消息加密的例子并不多。 
The de-facto library for this sort of work is OpenSSL, but this has a couple of problems. The first is that usually openssl manages the TCP connection itself, which could be an option for some ZeroMQ cases, but doesn't fit if the user wanted to use a different transport, or an unusual topology. TLS or SSL also require a handshake at the start of the communication, which means we may have to send messages back and forth without there being any application data.

OpenSSL是进行这类工作的、事实上的标准库,但是它有两个问题。第一个问题是,OpenSSL通常会自己管理TCP连接。某些情况下ZeroMQ会使用TCP连接,但是如果用户需要使用不同的传输端点或者不常见的拓扑结构,则TCP连接就不适合了。TLS和SSL还要求在开始通信前进行握手,这意味着需要在传递应用数据之前来回传递(握手)消息。   
For the first part, OpenSSL includes support for usage as a filter thanks to it's BIO IO abstraction layer. Memory BIOs allow storing the data that would be written to or read from a network so that the sending and receving can be handled elsewhere.  Bert JW Regeer  has  previously blogged about using OpenSSL in an evented environment with the model, which I thought was a great base for use with ZeroMQ. Below, and in a Github  repo, I've built an example of pushing encrypted messages between two applications using ZeroMQ and OpenSSL with memory bios.

OpenSSL含有一个可用作过滤器的BIO抽象层。内存BIO可以存储将要写入网络或者从网络读取的数据,这样就可以在其他地方进行发送和收取。 Bert JW Regeer 写了一篇关于在事件驱动的网络环境中通过这种模型使用OpenSSL的 博客文章 ,这是在ZeroMQ中使用这种模型的重要基础。下面我将给出一个在两个应用之间使用ZeroMQ和OpenSSL内存BIO传递加密消息的例子,这个例子可以在GitHub上的 这个位置 找到。


As a quick note, for this example I generated a self-signed certificate to use for the communication:

我为这个例子生成了一个用于通信的自签名证书:

在ZeroMQ中使用SSL加密 

The code consists of a client, a server, and a class that handles generic TLS over ZeroMQ. The client code runs in a loop as we will need to send and receive as part of the handshake process. We push application data to our TLSZMQ object, and check whether it needs to write data to the network - in our case as ZeroMQ message - or whether there is an application data to process. When we receive replies via ZeroMQ, we push that into the object. In this case we're just sending a 'hello world' message and printing the result.

代码由一个客户端、一个服务器,以及一个在ZeroMQ上进行通用的TLS处理的类构成。客户端代码在一个循环中执行,因为我们需要在握手过程中进行发送和接收。我们把应用数据推送到TLSZMQ对象中,检查是否需要将数据写入网络,或者是否有应用数据需要处理。从ZeroMQ收到应答后,将应答送给TLSZMQ对象(进行解密)。这个例子仅仅发送“hello world”消息,打印结果。

在ZeroMQ中使用SSL加密

(可以从Github上的 这个位置 下载这段代码) 
The server code is slightly more complicated, as we have to initialise with our certificate details, and we want to be able to support multiple clients. As we are using a ROUTER socket, we can take the identity out of the message parts before the delimiter, and use the furthest back as the connection identifier. This means we're encrypting between client -> server, even if it's client (ssl) -> hop -> hop -> server (ssl). That said, I suspect a large number of uses of this kind of encryption will actually be going over an inner hop, with the rest unencrypted on a private network, e.g. client -> hop (ssl) -> hop (ssl) -> server.

服务器代码稍微复杂一些,因为需要使用证书进行初始化,还需要支持多个客户端。因为使用ROUTER套接字,所以可以从消息中取得套接字ID,用作连接ID。这意味着我们可以进行client->server加密,即使消息传递过程是client(SSL)->hop->hop->server(SSL)。我怀疑很多情况下是在中间过程中使用这种加密的,而其他部分则是消息未经加密的私有网络,传输过程是client->hop(SSL)->hop(SSL)->server。 
Each identity gets a new TLSZMQ object, which is stored in a std::map keyed agains the identity. Each message that comes in we push to the appropriate TLSZMQ object (creating one if we have a new connection), then checking whether we can recv application data or whether the object needs to write to the network, exactly as with the client.

每个连接ID对应一个TLSZMQ对象,这个对象被存储在一个std::map中,以连接ID作为键值。我们把每个进入的消息推送给合适的TLSZMQ对象(将为新连接创建TLSZMQ对象),然后为每个客户端检查能否收取应用数据,或者是否有数据需要写入到网络中。

在ZeroMQ中使用SSL加密

(可以从GitHub上的 这个位置 下载这段代码)

Finally, the meat of the work is in the TLSZMQ class. This class is a bit longer, so it's worth breaking it down a little. We start of with the constructors. We use two - one for clients, one for the servers. The differences are which connection methods we use - SSLv3_client_method or SSLv3_server_method (we could also use TLSv1), and then importantly we set the state. SSL_set_connect_state tells the library to reach out to a server to establish a connect, SSL_set_accept_state instructs it to expect an inbound connection. Of course, as we are using ZeroMQ we can connect or bind and start services in any order.

最后的工作是实现TLSZMQ类。这个类有点长,所以需要进行切分。从构造函数开始。有两个构造函数:一个用于客户端,一个用于服务器。不同之处在于使用哪种连接方法:SSLv3_client_method或者SSLv3_server_method(也可以使用TLSv1)。然后重要的是设置状态:SSL_set_connect_state让库建立到服务器的连接;SSL_set_accept_state则要求库等待进入的连接。当然,我们使用的是ZeroMQ,可以以任意次序进行connect和bind以启动服务器。

在ZeroMQ中使用SSL加密

(可以从GitHub上的 这个位置 下载这段代码)

The constructor calls the init functions, which setup the OpenSSL library. It's split into two parts as we need to attach the certificates to the context in the server version - note that we should be just creating a context once per program initialisation, but in this case I was a bit lazy! The first section just inits the general library and loads error strings, before creating a context with the passed in method. The second section creates the BIO i/o abstractions, using the mem BIO type that allows us to treat use it as a filter. We use the SSL_set_bio function to instruct the library to use them.

构造函数调用init函数来设置OpenSSL库。初始化分成两个步骤,因为服务器版本的函数需要为上下文设置证书:我们只应该在程序初始化的时候创建一个上下文,但是这里我偷懒了(为每个TLSZMQ对象建立上下文)。初始化的第一步只是进行通常的库初始化,加载错误字符串,然后使用传入的方法创建一个上下文。初始化的第二步是创建BIO抽象,使用可用作过滤器的内存BIO类型。调用SSL_set_bio函数要求库使用内存BIO抽象。

在ZeroMQ中使用SSL加密

(可以从GitHub上的 这个位置 下载这段代码) 
The main update loop is ticked at various points by the client and server code. This addresses the communication with the SSL functionality via the BIO. We have four variables we're using to push data in and out - from the app to the library, and from the the library to zeromq. In the update loop we check for network data (e.g. data from the other side of the SSL connection) and BIO_write it, which pushes it into memory for use. If there is data from the application to be encrypted and transmitted we push it in with SSL_write. Then we call the netread and netwrite functions which handle the other parts.

客户端和服务器代码在各个地方调用update函数。这个函数通过BIO进行SSL通信。有四个用于推入和推出数据的变量:将数据从应用程序推入库,以及将数据从库推送给zeromq。update循环会检查网络数据(比如说,来自SSL连接另一端的数据),调用BIO_write将数据推入内存。如果有来自应用程序、需要加密和传输的数据,则对其调用SSL_write。最后调用net_read()和net_write()来进行其他处理。

在ZeroMQ中使用SSL加密

(可以从GitHub上的 这个位置 下载这段代码) 
Net_write_ and net_read_ work pretty much the same we - we use a buffer and read information from either the memory BIO (destined to be sent over ZeroMQ) or from the SSL (destined for the application). We loop over all the sections of the data, 1k at a time, and push it into a ZeroMQ message after ready for sending.

net_write()和net_read()的工作非常相似:使用一个缓冲区从内存BIO(带有目标地址,将通过ZeroMQ发送)或者SSL(送给应用程序)读取数据。程序循环读取数据,每次1KB,最后将数据输入到一个ZeroMQ消息中,准备发送。

在ZeroMQ中使用SSL加密

(可以从GitHub上的 这个位置 下载这段代码) 
As part of that, we check any error messages. If we get a WANT_READ, or a NONE error we just continue. We'll hit these, for example, when we first try and write application data when we haven't completed the handshake.

处理过程会检查错误消息。如果错误是WANT_READ或者NONE,则继续处理。在没有完成握手之前就尝试写入应用数据的时候会遇到这两种错误。  

在ZeroMQ中使用SSL加密

(可以从GitHub上的 这个位置 下载这段代码) 
Finally, we have a few functions we allow pushing data into and pulling it out of the object.

最后,我们需要一些函数来将数据推入对象,或者从对象取出数据。

在ZeroMQ中使用SSL加密

(可以从GitHub上的 这个位置 下载这段代码) 
When we run these, there's enough debug output in to show the handshake. If we look at the output, we can see the -1s from the application data failing to write, and the read and writes from the BIO as the handshake messages go between client and server. The "12" written below is the application message, and the 90 is the encrypted "Got it!"

运行的时候有很多输出显示握手过程。查看输出,可以看到多次应用数据写入失败,返回-1;还可以看到BIO层的、用于在客户端和服务器之间交换握手消息的读写操作。随后写入的12字节是应用消息,而90字节是加密后的“Got it!”。

DEBUG: -1 written to SSL 
DEBUG: 95 read from BIO 
DEBUG: 627 written to BIO 
DEBUG: -1 written to SSL 
DEBUG: 228 read from BIO 
DEBUG: -1 written to SSL 
DEBUG: 91 written to BIO 
DEBUG: 12 written to SSL 
DEBUG: 90 read from BIO 
DEBUG: 90 written to BIO 
Received: Got it!

If we run the server, we see the other side.

运行服务器则可以看到以下调试信息:

DEBUG: 95 written to BIO 
DEBUG: 627 read from BIO 
DEBUG: 228 written to BIO 
DEBUG: 91 read from BIO 
DEBUG: 90 written to BIO 
Received: hello world! 
DEBUG: 8 written to SSL 
DEBUG: 90 read from BIO

The code is a bit of a quick fix, and it doesn't handle multi-part messages particularly well. How that should work is likely to be an app-specific decision, but as a starting point just returning some sort of array of decoded parts would be a good start! Hopefully this will give anyone looking to implement something more robust a few pointers! The code is up on  github .

上述代码仅仅是简单的示例,还不能很好地处理多段消息。如何处理多段消息应该是应用特定的,但是返回多段解码后的消息部分是一个良好的开端。希望上述代码能给大家实现更健壮应用以指引。

0 0
原创粉丝点击