OpenSSL 入门：密码学基础知识
OpenSSL HTTPS

> 
> 想要入门密码学的基础知识，尤其是有关 OpenSSL 的入门知识吗？继续阅读。
> 
> 
> 


![](/data/attachment/album/202001/23/142249fpnhyqz9y2cz1exe.jpg)


本文是使用 [OpenSSL](https://www.openssl.org/) 的密码学基础知识的两篇文章中的第一篇，OpenSSL 是在 Linux 和其他系统上流行的生产级库和工具包。（要安装 OpenSSL 的最新版本，请参阅[这里](https://www.howtoforge.com/tutorial/how-to-install-openssl-from-source-on-linux/)。）OpenSSL 实用程序可在命令行使用，程序也可以调用 OpenSSL 库中的函数。本文的示例程序使用的是 C 语言，即 OpenSSL 库的源语言。


本系列的两篇文章涵盖了加密哈希、数字签名、加密和解密以及数字证书。你可以从[我的网站](http://condor.depaul.edu/mkalin)的 ZIP 文件中找到这些代码和命令行示例。


让我们首先回顾一下 OpenSSL 名称中的 SSL。


### OpenSSL 简史


<ruby> <a href="https://en.wikipedia.org/wiki/Transport_Layer_Security">  安全套接字层 </a> <rt>  Secure Socket Layer </rt></ruby>（SSL）是 Netscape 在 1995 年发布的一种加密协议。该协议层可以位于 HTTP 之上，从而为 HTTPS 提供了 S：<ruby> 安全 <rt>  secure </rt></ruby>。SSL 协议提供了各种安全服务，其中包括两项在 HTTPS 中至关重要的服务：


* <ruby> 对等身份验证 <rt>  Peer authentication </rt></ruby>（也称为相互质询）：连接的每一边都对另一边的身份进行身份验证。如果 Alice 和 Bob 要通过 SSL 交换消息，则每个人首先验证彼此的身份。
* <ruby> 机密性 <rt>  Confidentiality </rt></ruby>：发送者在通过通道发送消息之前先对其进行加密。然后，接收者解密每个接收到的消息。此过程可保护网络对话。即使窃听者 Eve 截获了从 Alice 到 Bob 的加密消息（即*中间人*攻击），Eve 会发现他无法在计算上解密此消息。


反过来，这两个关键 SSL 服务与其他不太受关注的服务相关联。例如，SSL 支持消息完整性，从而确保接收到的消息与发送的消息相同。此功能是通过哈希函数实现的，哈希函数也随 OpenSSL 工具箱一起提供。


SSL 有多个版本（例如 SSLv2 和 SSLv3），并且在 1999 年出现了一个基于 SSLv3 的类似协议<ruby> 传输层安全性 <rt>  Transport Layer Security </rt></ruby>（TLS）。TLSv1 和 SSLv3 相似，但不足以相互配合工作。不过，通常将 SSL/TLS 称为同一协议。例如，即使正在使用的是 TLS（而非 SSL），OpenSSL 函数也经常在名称中包含 SSL。此外，调用 OpenSSL 命令行实用程序以 `openssl` 开始。


除了 man 页面之外，OpenSSL 的文档是零零散散的，鉴于 OpenSSL 工具包很大，这些页面很难以查找使用。命令行和代码示例可以将主要主题集中起来。让我们从一个熟悉的示例开始（使用 HTTPS 访问网站），然后使用该示例来选出我们感兴趣的加密部分进行讲述。


### 一个 HTTPS 客户端


此处显示的 `client` 程序通过 HTTPS 连接到 Google：



```
/* compilation: gcc -o client client.c -lssl -lcrypto */
#include <stdio.h>
#include <stdlib.h>
#include <openssl/bio.h> /* BasicInput/Output streams */
#include <openssl/err.h> /* errors */
#include <openssl/ssl.h> /* core library */
#define BuffSize 1024

void report_and_exit(const char* msg) {
  perror(msg);
  ERR_print_errors_fp(stderr);
  exit(-1);
}

void init_ssl() {
  SSL_load_error_strings();
  SSL_library_init();
}

void cleanup(SSL_CTX* ctx, BIO* bio) {
  SSL_CTX_free(ctx);
  BIO_free_all(bio);
}

void secure_connect(const char* hostname) {
  char name[BuffSize];
  char request[BuffSize];
  char response[BuffSize];

  const SSL_METHOD* method = TLSv1_2_client_method();
  if (NULL == method) report_and_exit("TLSv1_2_client_method...");

  SSL_CTX* ctx = SSL_CTX_new(method);
  if (NULL == ctx) report_and_exit("SSL_CTX_new...");

  BIO* bio = BIO_new_ssl_connect(ctx);
  if (NULL == bio) report_and_exit("BIO_new_ssl_connect...");

  SSL* ssl = NULL;

  /* 链路 bio 通道，SSL 会话和服务器端点 */

  sprintf(name, "%s:%s", hostname, "https");
  BIO_get_ssl(bio, &ssl); /* 会话 */
  SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); /* 鲁棒性 */
  BIO_set_conn_hostname(bio, name); /* 准备连接 */

  /* 尝试连接 */
  if (BIO_do_connect(bio) <= 0) {
    cleanup(ctx, bio);
    report_and_exit("BIO_do_connect...");
  }

  /* 验证信任库，检查证书 */
  if (!SSL_CTX_load_verify_locations(ctx,
                                      "/etc/ssl/certs/ca-certificates.crt", /* 信任库 */
                                      "/etc/ssl/certs/")) /* 其它信任库 */
    report_and_exit("SSL_CTX_load_verify_locations...");

  long verify_flag = SSL_get_verify_result(ssl);
  if (verify_flag != X509_V_OK)
    fprintf(stderr,
            "##### Certificate verification error (%i) but continuing...\n",
            (int) verify_flag);

  /* 获取主页作为示例数据 */
  sprintf(request,
          "GET / HTTP/1.1\x0D\x0AHost: %s\x0D\x0A\x43onnection: Close\x0D\x0A\x0D\x0A",
          hostname);
  BIO_puts(bio, request);

  /* 从服务器读取 HTTP 响应并打印到输出 */
  while (1) {
    memset(response, '\0', sizeof(response));
    int n = BIO_read(bio, response, BuffSize);
    if (n <= 0) break; /* 0 代表流结束，< 0 代表有错误 */
  puts(response);
  }

  cleanup(ctx, bio);
}

int main() {
  init_ssl();

  const char* hostname = "www.google.com:443";
  fprintf(stderr, "Trying an HTTPS connection to %s...\n", hostname);
  secure_connect(hostname);

return 0;
}
```

可以从命令行编译和执行该程序（请注意 `-lssl` 和 `-lcrypto` 中的小写字母 `L`）：



```
gcc -o client client.c -lssl -lcrypto
```

该程序尝试打开与网站 [www.google.com](http://www.google.com) 的安全连接。在与 Google Web 服务器的 TLS 握手过程中，`client` 程序会收到一个或多个数字证书，该程序会尝试对其进行验证（但在我的系统上失败了）。尽管如此，`client` 程序仍继续通过安全通道获取 Google 主页。该程序取决于前面提到的安全工件，尽管在上述代码中只着重突出了数字证书。但其它工件仍在幕后发挥作用，稍后将对它们进行详细说明。


通常，打开 HTTP（非安全）通道的 C 或 C++ 的客户端程序将使用诸如*文件描述符*或*网络套接字*之类的结构，它们是两个进程（例如，这个 `client` 程序和 Google Web 服务器）之间连接的端点。另一方面，文件描述符是一个非负整数值，用于在程序中标识该程序打开的任何文件类的结构。这样的程序还将使用一种结构来指定有关 Web 服务器地址的详细信息。


这些相对较低级别的结构不会出现在客户端程序中，因为 OpenSSL 库会将套接字基础设施和地址规范等封装在更高层面的安全结构中。其结果是一个简单的 API。下面首先看一下 `client` 程序示例中的安全性详细信息。


* 该程序首先加载相关的 OpenSSL 库，我的函数 `init_ssl` 中对 OpenSSL 进行了两次调用：



```
SSL_load_error_strings();
SSL_library_init(); 
```
* 下一个初始化步骤尝试获取安全*上下文*，这是建立和维护通往 Web 服务器的安全通道所需的信息框架。如对 OpenSSL 库函数的调用所示，在示例中使用了 TLS 1.2：



```
const SSL_METHOD* method = TLSv1_2_client_method(); /* TLS 1.2 */
```

如果调用成功，则将 `method` 指针被传递给库函数，该函数创建类型为 `SSL_CTX` 的上下文：



```
SSL_CTX* ctx = SSL_CTX_new(method);
```

`client` 程序会检查每个关键的库调用的错误，如果其中一个调用失败，则程序终止。
* 现在还有另外两个 OpenSSL 工件也在发挥作用：SSL 类型的安全会话，从头到尾管理安全连接；以及类型为 BIO（<ruby> 基本输入/输出 <rt>  Basic Input/Output </rt></ruby>）的安全流，用于与 Web 服务器进行通信。BIO 流是通过以下调用生成的：



```
BIO* bio = BIO_new_ssl_connect(ctx);
```

请注意，这个最重要的上下文是其参数。`BIO` 类型是 C 语言中 `FILE` 类型的 OpenSSL 封装器。此封装器可保护 `client` 程序与 Google 的网络服务器之间的输入和输出流的安全。
* 有了 `SSL_CTX` 和 `BIO`，然后程序在 SSL 会话中将它们组合在一起。三个库调用可以完成工作：



```
BIO_get_ssl(bio, &ssl); /* 会话 */
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); /* 鲁棒性 */
BIO_set_conn_hostname(bio, name); /* 准备连接 */
```

安全连接本身是通过以下调用建立的：



```
BIO_do_connect(bio);
```

如果最后一个调用不成功，则 `client` 程序终止；否则，该连接已准备就绪，可以支持 `client` 程序与 Google Web 服务器之间的机密对话。


在与 Web 服务器握手期间，`client` 程序会接收一个或多个数字证书，以认证服务器的身份。但是，`client` 程序不会发送自己的证书，这意味着这个身份验证是单向的。（Web 服务器通常配置为**不**需要客户端证书）尽管对 Web 服务器证书的验证失败，但 `client` 程序仍通过了连接到 Web 服务器的安全通道继续获取 Google 主页。


为什么验证 Google 证书的尝试会失败？典型的 OpenSSL 安装目录为 `/etc/ssl/certs`，其中包含 `ca-certificates.crt` 文件。该目录和文件包含着 OpenSSL 自带的数字证书，以此构成<ruby> 信任库 <rt>  truststore </rt></ruby>。可以根据需要更新信任库，尤其是可以包括新信任的证书，并删除不再受信任的证书。


`client` 程序从 Google Web 服务器收到了三个证书，但是我的计算机上的 OpenSSL 信任库并不包含完全匹配的证书。如目前所写，`client` 程序不会通过例如验证 Google 证书上的数字签名（一个用来证明该证书的签名）来解决此问题。如果该签名是受信任的，则包含该签名的证书也应受信任。尽管如此，`client` 程序仍继续获取页面，然后打印出 Google 的主页。下一节将更详细地介绍这些。


### 客户端程序中隐藏的安全性


让我们从客户端示例中可见的安全工件（数字证书）开始，然后考虑其他安全工件如何与之相关。数字证书的主要格式标准是 X509，生产级的证书由诸如 [Verisign](https://www.verisign.com) 的<ruby> 证书颁发机构 <rt>  Certificate Authority </rt></ruby>（CA）颁发。


数字证书中包含各种信息（例如，激活日期和失效日期以及所有者的域名），也包括发行者的身份和*数字签名*（这是加密过的*加密哈希*值）。证书还具有未加密的哈希值，用作其标识*指纹*。


哈希值来自将任意数量的二进制位映射到固定长度的摘要。这些位代表什么（会计报告、小说或数字电影）无关紧要。例如，<ruby> 消息摘要版本 5 <rt>  Message Digest version 5 </rt></ruby>（MD5）哈希算法将任意长度的输入位映射到 128 位哈希值，而 SHA1（<ruby> 安全哈希算法版本 1 <rt>  Secure Hash Algorithm version 1 </rt></ruby>）算法将输入位映射到 160 位哈希值。不同的输入位会导致不同的（实际上在统计学上是唯一的）哈希值。下一篇文章将会进行更详细的介绍，并着重介绍什么使哈希函数具有加密功能。


数字证书的类型有所不同（例如根证书、中间证书和最终实体证书），并形成了反映这些证书类型的层次结构。顾名思义，*根*证书位于层次结构的顶部，其下的证书继承了根证书所具有的信任。OpenSSL 库和大多数现代编程语言都具有 X509 数据类型以及处理此类证书的函数。来自 Google 的证书具有 X509 格式，`client` 程序会检查该证书是否为 `X509_V_OK`。


X509 证书基于<ruby> 公共密钥基础结构 <rt>  public-key infrastructure </rt></ruby>（PKI），其中包括的算法（RSA 是占主导地位的算法）用于生成*密钥对*：公共密钥及其配对的私有密钥。公钥是一种身份：[Amazon](https://www.amazon.com) 的公钥对其进行标识，而我的公钥对我进行标识。私钥应由其所有者负责保密。


成对出现的密钥具有标准用途。可以使用公钥对消息进行加密，然后可以使用同一个密钥对中的私钥对消息进行解密。私钥也可以用于对文档或其他电子工件（例如程序或电子邮件）进行签名，然后可以使用该对密钥中的公钥来验证签名。以下两个示例补充了一些细节。


在第一个示例中，Alice 将她的公钥分发给全世界，包括 Bob。然后，Bob 用 Alice 的公钥加密邮件，然后将加密的邮件发送给 Alice。用 Alice 的公钥加密的邮件将可以用她的私钥解密（假设是她自己的私钥），如下所示：



```
             +------------------+ encrypted msg  +-------------------+
Bob's msg--->|Alice's public key|--------------->|Alice's private key|---> Bob's msg
             +------------------+                +-------------------+
```

理论上可以在没有 Alice 的私钥的情况下解密消息，但在实际情况中，如果使用像 RSA 这样的加密密钥对系统，则在计算上做不到。


现在，第二个示例，请对文档签名以证明其真实性。签名算法使用密钥对中的私钥来处理要签名的文档的加密哈希：



```
                    +-------------------+
Hash of document--->|Alice's private key|--->Alice's digital signature of the document
                    +-------------------+
```

假设 Alice 以数字方式签署了发送给 Bob 的合同。然后，Bob 可以使用 Alice 密钥对中的公钥来验证签名：



```
                                             +------------------+
Alice's digital signature of the document--->|Alice's public key|--->verified or not
                                             +------------------+
```

假若没有 Alice 的私钥，就无法轻松伪造 Alice 的签名：因此，Alice 有必要保密她的私钥。


在 `client` 程序中，除了数字证书以外，这些安全性都没有明确展示。下一篇文章使用使用 OpenSSL 实用程序和库函数的示例填充更多详细的信息。


### 命令行的 OpenSSL


同时，让我们看一下 OpenSSL 命令行实用程序：特别是在 TLS 握手期间检查来自 Web 服务器的证书的实用程序。调用 OpenSSL 实用程序可以使用 `openssl` 命令，然后添加参数和标志的组合以指定所需的操作。


看看以下命令：



```
openssl list-cipher-algorithms
```

该输出是组成<ruby> 加密算法套件 <rt>  cipher suite </rt> <rt> </rt></ruby>的相关算法的列表。下面是列表的开头，加了澄清首字母缩写词的注释：



```
AES-128-CBC ## Advanced Encryption Standard, Cipher Block Chaining
AES-128-CBC-HMAC-SHA1 ## Hash-based Message Authentication Code with SHA1 hashes
AES-128-CBC-HMAC-SHA256 ## ditto, but SHA256 rather than SHA1
...
```

下一条命令使用参数 `s_client` 将打开到 [www.google.com](http://www.google.com) 的安全连接，并在屏幕上显示有关此连接的所有信息：



```
openssl s_client -connect www.google.com:443 -showcerts
```

端口号 443 是 Web 服务器用于接收 HTTPS（而不是 HTTP 连接）的标准端口号。（对于 HTTP，标准端口为 80）Web 地址 [www.google.com:443](http://www.google.com:443) 也出现在 `client` 程序的代码中。如果尝试连接成功，则将显示来自 Google 的三个数字证书以及有关安全会话、正在使用的加密算法套件以及相关项目的信息。例如，这是开头的部分输出，它声明*证书链*即将到来。证书的编码为 base64：



```
Certificate chain
 0 s:/C=US/ST=California/L=Mountain View/O=Google LLC/CN=www.google.com
 i:/C=US/O=Google Trust Services/CN=Google Internet Authority G3
-----BEGIN CERTIFICATE-----
MIIEijCCA3KgAwIBAgIQdCea9tmy/T6rK/dDD1isujANBgkqhkiG9w0BAQsFADBU
MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZpY2VzMSUw
...
```

诸如 Google 之类的主要网站通常会发送多个证书进行身份验证。


输出以有关 TLS 会话的摘要信息结尾，包括加密算法套件的详细信息：



```
SSL-Session:
    Protocol : TLSv1.2
    Cipher : ECDHE-RSA-AES128-GCM-SHA256
    Session-ID: A2BBF0E4991E6BBBC318774EEE37CFCB23095CC7640FFC752448D07C7F438573
...
```

`client` 程序中使用了协议 TLS 1.2，`Session-ID` 唯一地标识了 `openssl` 实用程序和 Google Web 服务器之间的连接。`Cipher` 条目可以按以下方式进行解析：


* `ECDHE`（<ruby> 椭圆曲线 Diffie-Hellman（临时） <rt>  Elliptic Curve Diffie Hellman Ephemeral </rt></ruby>）是一种用于管理 TLS 握手的高效的有效算法。尤其是，ECDHE 通过确保连接双方（例如，`client` 程序和 Google Web 服务器）使用相同的加密/解密密钥（称为*会话密钥*）来解决“密钥分发问题”。后续文章会深入探讨该细节。
* `RSA`（Rivest Shamir Adleman）是主要的公共密钥密码系统，并以 1970 年代末首次描述了该系统的三位学者的名字命名。这个正在使用的密钥对是使用 RSA 算法生成的。
* `AES128`（<ruby> 高级加密标准 <rt>  Advanced Encryption Standard </rt></ruby>）是一种<ruby> 块式加密算法 <rt>  block cipher </rt></ruby>，用于加密和解密<ruby> 位块 <rt>  blocks of bits </rt></ruby>。（另一种算法是<ruby> 流式加密算法 <rt>  stream cipher </rt></ruby>，它一次加密和解密一个位。）这个加密算法是对称加密算法，因为使用同一个密钥进行加密和解密，这首先引起了密钥分发问题。AES 支持 128（此处使用）、192 和 256 位的密钥大小：密钥越大，安全性越好。


通常，像 AES 这样的对称加密系统的密钥大小要小于像 RSA 这样的非对称（基于密钥对）系统的密钥大小。例如，1024 位 RSA 密钥相对较小，而 256 位密钥则当前是 AES 最大的密钥。
* `GCM`（<ruby> 伽罗瓦计数器模式 <rt>  Galois Counter Mode </rt></ruby>）处理在安全对话期间重复应用的加密算法（在这种情况下为 AES128）。AES128 块的大小仅为 128 位，安全对话很可能包含从一侧到另一侧的多个 AES128 块。GCM 非常有效，通常与 AES128 搭配使用。
* `SHA256`（<ruby> 256 位安全哈希算法 <rt>  Secure Hash Algorithm 256 bits </rt></ruby>）是我们正在使用的加密哈希算法。生成的哈希值的大小为 256 位，尽管使用 SHA 甚至可以更大。


加密算法套件正在不断发展中。例如，不久前，Google 使用 RC4 流加密算法（RSA 的 Ron Rivest 后来开发的 Ron’s Cipher 版本 4）。 RC4 现在有已知的漏洞，这大概部分导致了 Google 转换为 AES128。


### 总结


我们通过安全的 C Web 客户端和各种命令行示例对 OpenSSL 做了首次了解，使一些需要进一步阐明的主题脱颖而出。[下一篇文章会详细介绍](https://opensource.com/article/19/6/cryptography-basics-openssl-part-2)，从加密散列开始，到对数字证书如何应对密钥分发挑战为结束的更全面讨论。




---


via: <https://opensource.com/article/19/6/cryptography-basics-openssl-part-1>


作者：[Marty Kalin](https://opensource.com/users/mkalindepauledu/users/akritiko/users/clhermansen) 选题：[lujun9972](https://github.com/lujun9972) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
This article is the first of two on cryptography basics using [OpenSSL](https://www.openssl.org/), a production-grade library and toolkit popular on Linux and other systems. (To install the most recent version of OpenSSL, see [here](https://www.howtoforge.com/tutorial/how-to-install-openssl-from-source-on-linux/).) OpenSSL utilities are available at the command line, and programs can call functions from the OpenSSL libraries. The sample program for this article is in C, the source language for the OpenSSL libraries.

The two articles in this series cover—collectively—cryptographic hashes, digital signatures, encryption and decryption, and digital certificates. You can find the code and command-line examples in a ZIP file from [my website](http://condor.depaul.edu/mkalin).

Let’s start with a review of the SSL in the OpenSSL name.

## A quick history

[Secure Socket Layer (SSL)](https://en.wikipedia.org/wiki/Transport_Layer_Security) is a cryptographic protocol that [Netscape](https://en.wikipedia.org/wiki/Netscape) released in 1995. This protocol layer can sit atop HTTP, thereby providing the *S* for *secure* in HTTPS. The SSL protocol provides various security services, including two that are central in HTTPS:

- Peer authentication (aka mutual challenge): Each side of a connection authenticates the identity of the other side. If Alice and Bob are to exchange messages over SSL, then each first authenticates the identity of the other.
- Confidentiality: A sender encrypts messages before sending these over a channel. The receiver then decrypts each received message. This process safeguards network conversations. Even if eavesdropper Eve intercepts an encrypted message from Alice to Bob (a
*man-in-the-middle*attack), Eve finds it computationally infeasible to decrypt this message.

These two key SSL services, in turn, are tied to others that get less attention. For example, SSL supports message integrity, which assures that a received message is the same as the one sent. This feature is implemented with hash functions, which likewise come with the OpenSSL toolkit.

SSL is versioned (e.g., SSLv2 and SSLv3), and in 1999 Transport Layer Security (TLS) emerged as a similar protocol based upon SSLv3. TLSv1 and SSLv3 are alike, but not enough so to work together. Nonetheless, it is common to refer to SSL/TLS as if they are one and the same protocol. For example, OpenSSL functions often have SSL in the name even when TLS rather than SSL is in play. Furthermore, calling OpenSSL command-line utilities begins with the term **openssl**.

The documentation for OpenSSL is spotty beyond the **man** pages, which become unwieldy given how big the OpenSSL toolkit is. Command-line and code examples are one way to bring the main topics into focus together. Let’s start with a familiar example—accessing a web site with HTTPS—and use this example to pick apart the cryptographic pieces of interest.

## An HTTPS client

The **client** program shown here connects over HTTPS to Google:

```
/* compilation: gcc -o client client.c -lssl -lcrypto */
#include <stdio.h>
#include <stdlib.h>
#include <openssl/bio.h> /* BasicInput/Output streams */
#include <openssl/err.h> /* errors */
#include <openssl/ssl.h> /* core library */
#define BuffSize 1024
void report_and_exit(const char* msg) {
perror(msg);
ERR_print_errors_fp(stderr);
exit(-1);
}
void init_ssl() {
SSL_load_error_strings();
SSL_library_init();
}
void cleanup(SSL_CTX* ctx, BIO* bio) {
SSL_CTX_free(ctx);
BIO_free_all(bio);
}
void secure_connect(const char* hostname) {
char name[BuffSize];
char request[BuffSize];
char response[BuffSize];
const SSL_METHOD* method = TLSv1_2_client_method();
if (NULL == method) report_and_exit("TLSv1_2_client_method...");
SSL_CTX* ctx = SSL_CTX_new(method);
if (NULL == ctx) report_and_exit("SSL_CTX_new...");
BIO* bio = BIO_new_ssl_connect(ctx);
if (NULL == bio) report_and_exit("BIO_new_ssl_connect...");
SSL* ssl = NULL;
/* link bio channel, SSL session, and server endpoint */
sprintf(name, "%s:%s", hostname, "https");
BIO_get_ssl(bio, &ssl); /* session */
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); /* robustness */
BIO_set_conn_hostname(bio, name); /* prepare to connect */
/* try to connect */
if (BIO_do_connect(bio) <= 0) {
cleanup(ctx, bio);
report_and_exit("BIO_do_connect...");
}
/* verify truststore, check cert */
if (!SSL_CTX_load_verify_locations(ctx,
"/etc/ssl/certs/ca-certificates.crt", /* truststore */
"/etc/ssl/certs/")) /* more truststore */
report_and_exit("SSL_CTX_load_verify_locations...");
long verify_flag = SSL_get_verify_result(ssl);
if (verify_flag != X509_V_OK)
fprintf(stderr,
"##### Certificate verification error (%i) but continuing...\n",
(int) verify_flag);
/* now fetch the homepage as sample data */
sprintf(request,
"GET / HTTP/1.1\x0D\x0AHost: %s\x0D\x0A\x43onnection: Close\x0D\x0A\x0D\x0A",
hostname);
BIO_puts(bio, request);
/* read HTTP response from server and print to stdout */
while (1) {
memset(response, '\0', sizeof(response));
int n = BIO_read(bio, response, BuffSize);
if (n <= 0) break; /* 0 is end-of-stream, < 0 is an error */
puts(response);
}
cleanup(ctx, bio);
}
int main() {
init_ssl();
const char* hostname = "www.google.com:443";
fprintf(stderr, "Trying an HTTPS connection to %s...\n", hostname);
secure_connect(hostname);
return 0;
}
```


This program can be compiled and executed from the command line (note the lowercase L in **-lssl** and **-lcrypto**):

**gcc**** -o ****client client****.c -****lssl**** -****lcrypto**

This program tries to open a secure connection to the web site [www.google.com](http://www.google.com). As part of the TLS handshake with the Google web server, the **client** program receives one or more digital certificates, which the program tries (but, on my system, fails) to verify. Nonetheless, the **client** program goes on to fetch the Google homepage through the secure channel. This program depends on the security artifacts mentioned earlier, although only a digital certificate stands out in the code. The other artifacts remain behind the scenes and are clarified later in detail.

Generally, a client program in C or C++ that opened an HTTP (non-secure) channel would use constructs such as a *file descriptor* for a *network socket*, which is an endpoint in a connection between two processes (e.g., the client program and the Google web server). A file descriptor, in turn, is a non-negative integer value that identifies, within a program, any file-like construct that the program opens. Such a program also would use a structure to specify details about the web server’s address.

None of these relatively low-level constructs occurs in the client program, as the OpenSSL library wraps the socket infrastructure and address specification in high-level security constructs. The result is a straightforward API. Here’s a first look at the security details in the example **client** program.

-
The program begins by loading the relevant OpenSSL libraries, with my function

**init_ssl**making two calls into OpenSSL:**SSL_library_init(); SSL_load_error_strings();** -
The next initialization step tries to get a security

*context*, a framework of information required to establish and maintain a secure channel to the web server.**TLS 1.2**is used in the example, as shown in this call to an OpenSSL library function:**const SSL_METHOD* method = TLSv1_2_client_method(); /* TLS 1.2 */**If the call succeeds, then the

**method**pointer is passed to the library function that creates the context of type**SSL_CTX**:**SSL_CTX*****ctx****= SSL_CTX_new(method);**The

**client**program checks for errors on each of these critical library calls, and then the program terminates if either call fails. -
Two other OpenSSL artifacts now come into play: a security session of type

**SSL**, which manages the secure connection from start to finish; and a secured stream of type**BIO**(Basic Input/Output), which is used to communicate with the web server. The**BIO**stream is generated with this call:**BIO* bio = BIO_new_ssl_connect(****ctx****);**Note that the all-important context is the argument. The

**BIO**type is the OpenSSL wrapper for the**FILE**type in C. This wrapper secures the input and output streams between the**client**program and Google's web server. -
With the

**SSL_CTX**and**BIO**in hand, the program then links these together in an**SSL**session. Three library calls do the work:**BIO_get_ssl(bio, &ssl); /* get a TLS session */****SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); /* for robustness */****BIO_set_conn_hostname(bio, name); /* prepare to connect to Google */**The secure connection itself is established through this call:

**BIO_do_connect(bio);**If this last call does not succeed, the

**client**program terminates; otherwise, the connection is ready to support a confidential conversation between the**client**program and the Google web server.

During the handshake with the web server, the **client** program receives one or more digital certificates that authenticate the server’s identity. However, the **client** program does not send a certificate of its own, which means that the authentication is one-way. (Web servers typically are configured *not* to expect a client certificate.) Despite the failed verification of the web server’s certificate, the **client** program continues by fetching the Google homepage through the secure channel to the web server.

Why does the attempt to verify a Google certificate fail? A typical OpenSSL installation has the directory **/etc/ssl/certs**, which includes the **ca-certificates.crt** file. The directory and the file together contain digital certificates that OpenSSL trusts out of the box and accordingly constitute a *truststore*. The truststore can be updated as needed, in particular, to include newly trusted certificates and to remove ones no longer trusted.

The client program receives three certificates from the Google web server, but the OpenSSL truststore on my machine does not contain exact matches. As presently written, the **client** program does not pursue the matter by, for example, verifying the digital signature on a Google certificate (a signature that vouches for the certificate). If that signature were trusted, then the certificate containing it should be trusted as well. Nonetheless, the client program goes on to fetch and then to print Google’s homepage. The next section gets into more detail.

## The hidden security pieces in the client program

Let’s start with the visible security artifact in the client example—the digital certificate—and consider how other security artifacts relate to it. The dominant layout standard for a digital certificate is X509, and a production-grade certificate is issued by a certificate authority (CA) such as [Verisign](https://www.verisign.com).

A digital certificate contains various pieces of information (e.g., activation and expiration dates, and a domain name for the owner), including the issuer’s identity and *digital signature*, which is an encrypted *cryptographic hash* value. A certificate also has an unencrypted hash value that serves as its identifying *fingerprint*.

A hash value results from mapping an arbitrary number of bits to a fixed-length digest. What the bits represent (an accounting report, a novel, or maybe a digital movie) is irrelevant. For example, the Message Digest version 5 (MD5) hash algorithm maps input bits of whatever length to a 128-bit hash value, whereas the SHA1 (Secure Hash Algorithm version 1) algorithm maps input bits to a 160-bit value. Different input bits result in different—indeed, statistically unique—hash values. The next article goes into further detail and focuses on what makes a hash function *cryptographic*.

Digital certificates differ in type (e.g., *root*, *intermediate*, and *end-entity* certificates) and form a hierarchy that reflects these types. As the name suggests, a *root* certificate sits atop the hierarchy, and the certificates under it inherit whatever trust the root certificate has. The OpenSSL libraries and most modern programming languages have an X509 type together with functions that deal with such certificates. The certificate from Google has an X509 format, and the **client** program checks whether this certificate is **X509_V_OK**.

X509 certificates are based upon public-key infrastructure (PKI), which includes algorithms—RSA is the dominant one—for generating *key pairs*: a public key and its paired private key. A public key is an identity: [Amazon’s](https://www.amazon.com) public key identifies it, and my public key identifies me. A private key is meant to be kept secret by its owner.

The keys in a pair have standard uses. A public key can be used to encrypt a message, and the private key from the same pair can then be used to decrypt the message. A private key also can be used to sign a document or other electronic artifact (e.g., a program or an email), and the public key from the pair can then be used to verify the signature. The following two examples fill in some details.

In the first example, Alice distributes her public key to the world, including Bob. Bob then encrypts a message with Alice’s public key, sending the encrypted message to Alice. The message encrypted with Alice’s public key is decrypted with her private key, which (by assumption) she alone has, like so:

```
+------------------+ encrypted msg +-------------------+
Bob's msg--->|Alice's public key|--------------->|Alice's private key|---> Bob's msg
+------------------+ +-------------------+
```


Decrypting the message without Alice’s private key is possible in principle, but infeasible in practice given a sound cryptographic key-pair system such as RSA.

Now, for the second example, consider signing a document to certify its authenticity. The signature algorithm uses a private key from a pair to process a cryptographic hash of the document to be signed:

```
+-------------------+
Hash of document--->|Alice's private key|--->Alice's digital signature of the document
+-------------------+
```


Assume that Alice digitally signs a contract sent to Bob. Bob then can use Alice’s public key from the key pair to verify the signature:

```
+------------------+
Alice's digital signature of the document--->|Alice's public key|--->verified or not
+------------------+
```


It is infeasible to forge Alice’s signature without Alice’s private key: hence, it is in Alice’s interest to keep her private key secret.

None of these security pieces, except for digital certificates, is explicit in the **client** program. The next article fills in the details with examples that use the OpenSSL utilities and library functions.

## OpenSSL from the command line

In the meantime, let’s take a look at OpenSSL command-line utilities: in particular, a utility to inspect the certificates from a web server during the TLS handshake. Invoking the OpenSSL utilities begins with the **openssl** command and then adds a combination of arguments and flags to specify the desired operation.

Consider this command:

**openssl list-cipher-algorithms**

The output is a list of associated algorithms that make up a *cipher suite*. Here’s the start of the list, with comments to clarify the acronyms:

```
AES-128-CBC ## Advanced Encryption Standard, Cipher Block Chaining
AES-128-CBC-HMAC-SHA1 ## Hash-based Message Authentication Code with SHA1 hashes
AES-128-CBC-HMAC-SHA256 ## ditto, but SHA256 rather than SHA1
...
```


The next command, using the argument **s_client**, opens a secure connection to ** www.google.com** and prints screens full of information about this connection:

**openssl s_client -connect www.google.com:443 -**

**showcerts**

The port number 443 is the standard one used by web servers for receiving HTTPS rather than HTTP connections. (For HTTP, the standard port is 80.) The network address ** www.google.com:443** also occurs in the

**client**program's code. If the attempted connection succeeds, the three digital certificates from Google are displayed together with information about the secure session, the cipher suite in play, and related items. For example, here is a slice of output from near the start, which announces that a

*certificate chain*is forthcoming. The encoding for the certificates is base64:

```
Certificate chain
0 s:/C=US/ST=California/L=Mountain View/O=Google LLC/CN=www.google.com
i:/C=US/O=Google Trust Services/CN=Google Internet Authority G3
-----BEGIN CERTIFICATE-----
MIIEijCCA3KgAwIBAgIQdCea9tmy/T6rK/dDD1isujANBgkqhkiG9w0BAQsFADBU
MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZpY2VzMSUw
...
```


A major web site such as Google usually sends multiple certificates for authentication.

The output ends with summary information about the TLS session, including specifics on the cipher suite:

```
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES128-GCM-SHA256
Session-ID: A2BBF0E4991E6BBBC318774EEE37CFCB23095CC7640FFC752448D07C7F438573
...
```


The protocol **TLS 1.2** is used in the **client** program, and the **Session-ID** uniquely identifies the connection between the **openssl** utility and the Google web server. The **Cipher** entry can be parsed as follows:

-
**ECDHE**(Elliptic Curve Diffie Hellman Ephemeral) is an effective and efficient algorithm for managing the TLS handshake. In particular, ECDHE solves the*key-distribution problem*by ensuring that both parties in a connection (e.g., the client program and the Google web server) use the same encryption/decryption key, which is known as the*session key*. The follow-up article digs into the details. -
**RSA**(Rivest Shamir Adleman) is the dominant public-key cryptosystem and named after the three academics who first described the system in the late 1970s. The key-pairs in play are generated with the RSA algorithm. -
**AES128**(Advanced Encryption Standard) is a*block cipher*that encrypts and decrypts blocks of bits. (The alternative is a*stream cipher*, which encrypts and decrypts bits one at a time.) The cipher is*symmetric*in that the same key is used to encrypt and to decrypt, which raises the key-distribution problem in the first place. AES supports key sizes of 128 (used here), 192, and 256 bits: the larger the key, the better the protection.Key sizes for symmetric cryptosystems such as AES are, in general, smaller than those for asymmetric (key-pair based) systems such as RSA. For example, a 1024-bit RSA key is relatively small, whereas a 256-bit key is currently the largest for AES.

-
**GCM**(Galois Counter Mode) handles the repeated application of a cipher (in this case, AES128) during a secured conversation. AES128 blocks are only 128-bits in size, and a secure conversation is likely to consist of multiple AES128 blocks from one side to the other. GCM is efficient and commonly paired with AES128. -
**SHA256**(Secure Hash Algorithm 256 bits) is the cryptographic hash algorithm in play. The hash values produced are 256 bits in size, although even larger values are possible with SHA.

Cipher suites are in continual development. Not so long ago, for example, Google used the RC4 stream cipher (Ron’s Cipher version 4 after Ron Rivest from RSA). RC4 now has known vulnerabilities, which presumably accounts, at least in part, for Google’s switch to AES128.

## Wrapping up

This first look at OpenSSL, through a secure C web client and various command-line examples, has brought to the fore a handful of topics in need of more clarification. [The next article gets into the details](https://opensource.com/article/19/6/cryptography-basics-openssl-part-2), starting with cryptographic hashes and ending with a fuller discussion of how digital certificates address the key distribution challenge.

## 2 Comments