如何使用 OpenSSL：哈希值、数字签名等
OpenSSL

> 
> 通过 OpenSSL 深入了解密码学的细节：哈希值、数字签名、数字证书等。
> 
> 
> 


![](/data/attachment/album/202008/13/150012t7zwwk6x7wr69wih.jpg)


[本系列的第一篇文章](/article-11810-1.html)通过 OpenSSL 库和命令行实用程序介绍了哈希、加密/解密、数字签名和数字证书。这第二篇文章将对细节进行深入探讨。让我们从计算中无处不在的哈希开始，并考虑是什么使哈希函数*具备密码学意义*。


### 密码学哈希


OpenSSL 源代码的[下载页面](https://www.openssl.org/source/)包含了一个带有最新版本的表格。每个版本都有两个<ruby> 哈希值 <rt>  hash </rt></ruby>：160 位 SHA1 和 256 位 SHA256。这些值可以用来验证下载的文件是否与存储库中的原始文件相匹配：下载者在本地重新计算下载文件的哈希值，然后将结果与原始文件进行比较。现代系统有计算这种哈希值的实用程序。例如，Linux 有 `md5sum` 和 `sha256sum`。OpenSSL 本身也提供了类似的命令行实用程序。


哈希值被用于计算的许多领域。例如，比特币区块链使用 SHA256 哈希值作为区块标识符。挖比特币就是生成一个低于指定阈值的 SHA256 哈希值，也就是至少有 N 个前导零的哈希值。（N 的值可以上升或下降，这取决于特定时间的挖矿生产力）。作为一个兴趣点，如今的矿机是为并行生成 SHA256 哈希值而设计的硬件集群。在 2018 年的一个高峰期，全球的比特币矿工每秒产生约 7500 万个<ruby> 太哈希值 <rt>  terahash </rt></ruby> —— 这真是一个不可思议的数字。


网络协议也使用哈希值（在这里通常叫做“<ruby> 校验和 <rt>  checksum </rt></ruby>”）来支持消息的完整性；也就是说，保证收到的消息与发送的消息是一样的。消息发送者计算消息的校验和，并将结果与消息一起发送。当消息到达时，接收方重新计算校验和。如果发送的校验和与重新计算的校验和不一致，那么消息在传输过程中可能出现了一些问题，或者发送的校验和出现了问题，或者两者都出现了问题。在这种情况下，应该重新发送消息和它的校验和，或者至少应该触发一个错误情况。（如 UDP 这样的低级网络协议不会理会校验和。）


哈希的其他例子大家都很熟悉。比如一个网站，要求用户用密码进行验证，用户在浏览器中输入密码，然后，他们通过 HTTPS 连接到服务器，密码从浏览器加密发送到服务器。一旦密码到达服务器，就会被解密，然后进行数据库表的查询。


在这个查询表中应该存储什么？存储密码本身是有风险的。风险要小得多的方式是存储一个由密码生成的哈希值，也许在计算哈希值之前“加一些<ruby> 盐 <rt>  salt </rt></ruby>（额外的位）改善口味”。你的密码可能会被发送到 Web 服务器上，但网站可以向你保证，密码不会存储在那里。


哈希值还出现在安全的各个领域。例如，<ruby> 基于哈希值的消息认证码 <rt>  hash-based message authentication code </rt></ruby>（[HMAC](https://en.wikipedia.org/wiki/HMAC)）使用一个哈希值和一个秘密的<ruby> 加密密钥 <rt>  cryptographic key </rt></ruby>来认证通过网络发送的消息。HMAC 码轻量级且易于在程序中使用，在 Web 服务中很受欢迎。一个 X509 数字证书包括一个称为<ruby> 指纹 <rt>  fingerprint </rt></ruby>的哈希值，它可以方便证书验证。一个存放于内存中的<ruby> 可信存储 <rt>  truststore </rt></ruby>可以实现为一个以这种指纹为键的查找表 —— 作为一个支持恒定查找时间的<ruby> 哈希映射 <rt>  hash map </rt></ruby>。来自传入的证书的指纹可以与可信存储中的密钥进行比较，以确定是否匹配。


<ruby> 密码学哈希函数 <rt>  cryptographic hash function </rt></ruby>应该具有什么特殊属性？它应该是<ruby> 单向 <rt>  one-way </rt></ruby>的，这意味着很难被逆转。一个加密哈希函数应该是比较容易计算的，但是计算它的反函数（将哈希值映射回输入位串的函数）在计算上应该是困难的。下面是一个描述，用 `chf` 作为加密哈希函数，我的密码 `foobar` 作为样本输入。



```
        +---+
foobar—>|chf|—>hash value ## 简单直接
        +--–+

```

相比之下，逆向操作是不可行的：



```
            +-----------+
hash value—>|chf inverse|—>foobar ## 棘手困难
            +-----------+

```

例如，回忆一下 SHA256 哈希函数。对于一个任意长度为 N > 0 的输入位串，这个函数会生成一个 256 位的固定长度的哈希值；因此，这个哈希值甚至不会反映出输入位串的长度 N，更不用说字符串中每个位的值了。顺便说一下，SHA256 不容易受到<ruby> <a href="https://en.wikipedia.org/wiki/Length_extension_attack">  长度扩展攻击 </a> <rt>  length extension attack </rt></ruby>。唯一有效的逆向工程方法是通过蛮力搜索将计算出的 SHA256 哈希值逆向返回到输入位串，这意味着需要尝试所有可能的输入位串，直到找到与目标哈希值匹配的位串。这样的搜索在 SHA256 这样一个完善的加密哈希函数上是不可行的。


现在，最后一个回顾的知识点是<ruby> 有序 <rt>  in order </rt></ruby>。加密哈希值是统计学上的唯一，而不是无条件的唯一，这意味着两个不同的输入位串产生相同的哈希值是不太可能的，但也不是不可能的 —— 这称之为<ruby> 碰撞 <rt>  collision </rt></ruby>。[生日问题](https://en.wikipedia.org/wiki/Birthday_problem)提供了一个很好的反直觉的碰撞例子。对各种哈希算法的<ruby> 抗碰撞性 <rt>  collision resistance </rt></ruby>有着广泛的研究。例如，MD5（128 位哈希值）在大约 2<sup> 21</sup> 次哈希之后，抗碰撞能力就会崩溃。对于 SHA1（160 位哈希值），大约在 2<sup> 61</sup> 次哈希后开始崩溃。


对于 SHA256 的抗碰撞能力的剖析，目前还没有一个很好的估计。这个事实并不奇怪。SHA256 有 2<sup> 256</sup> 个不同的哈希值范围，这个数字的十进制表示法有 78 位之多！那么，SHA256 哈希会不会发生碰撞呢？当然可能，但可能性极小。


在下面的命令行示例中，有两个输入文件被用作位串源：`hashIn1.txt` 和 `hashIn2.txt`。第一个文件包含 `abc`，第二个文件包含 `1a2b3c`。


为了便于阅读，这些文件包含的是文本，但也可以使用二进制文件代替。


在命令行（百分号 `%` 是提示符）使用 Linux `sha256sum` 实用程序对这两个文件进行处理产生以下哈希值（十六进制）:



```
% sha256sum hashIn1.txt
9e83e05bbf9b5db17ac0deec3b7ce6cba983f6dc50531c7a919f28d5fb3696c3 hashIn1.txt

% sha256sum hashIn2.txt
3eaac518777682bf4e8840dd012c0b104c2e16009083877675f00e995906ed13 hashIn2.txt

```

OpenSSL 哈希对应的结果与预期相同：



```
% openssl dgst -sha256 hashIn1.txt
SHA256(hashIn1.txt)= 9e83e05bbf9b5db17ac0deec3b7ce6cba983f6dc50531c7a919f28d5fb3696c3

% openssl dgst -sha256 hashIn2.txt
SHA256(hashIn2.txt)= 3eaac518777682bf4e8840dd012c0b104c2e16009083877675f00e995906ed13

```

这种对密码学哈希函数的研究，为我们仔细研究数字签名及其与密钥对的关系奠定了基础。


### 数字签名


顾名思义，<ruby> 数字签字 <rt>  digital signature </rt></ruby>可以附在文件或其他一些电子<ruby> 工件 <rt>  artifact </rt></ruby>（如程序）上，以证明其真实性。因此，这种签名类似于纸质文件上的手写签名。验证数字签名就是要确认两件事：第一，被担保的工件在签名被附上后没有改变，因为它部分是基于文件的加密学哈希值。第二，签名属于一个人（例如 Alice），只有她才能获得一对密钥中的私钥。顺便说一下，对代码（源码或编译后的代码）进行数字签名已经成为程序员的普遍做法。


让我们来了解一下数字签名是如何创建的。如前所述，没有公钥和私钥对就没有数字签名。当使用 OpenSSL 创建这些密钥时，有两个独立的命令：一个是创建私钥，另一个是从私钥中提取匹配的公钥。这些密钥对用 base64 编码，在这个过程中可以指定它们的大小。


<ruby> 私钥 <rt>  private key </rt></ruby>由数值组成，其中两个数值（一个<ruby> 模数 <rt>  modulus </rt></ruby>和一个<ruby> 指数 <rt>  exponent </rt></ruby>）组成了公钥。虽然私钥文件包含了<ruby> 公钥 <rt>  public key </rt></ruby>，但提取出来的公钥并**不会**透露相应私钥的值。


因此，生成的带有私钥的文件包含了完整的密钥对。将公钥提取到自己的文件中是很实用的，因为这两把钥匙有不同的用途，而这种提取方式也将私钥可能被意外公开的危险降到最低。


接下来，这对密钥的私钥被用来生成目标工件（如电子邮件）的哈希值，从而创建签名。在另一端，接收者的系统使用这对密钥的公钥来验证附在工件上的签名。


现在举个例子。首先，用 OpenSSL 生成一个 2048 位的 RSA 密钥对：



```
openssl genpkey -out privkey.pem -algorithm rsa 2048

```

在这个例子中，我们可以舍去 `-algorithm rsa` 标志，因为 `genpkey` 默认为 RSA 类型。文件的名称（`privkey.pem`）是任意的，但是<ruby> 隐私增强邮件 <rt>  Privacy Enhanced Mail </rt></ruby>（PEM）扩展名 `.pem` 是默认 PEM 格式的惯用扩展名。（如果需要的话，OpenSSL 有命令可以在各种格式之间进行转换。）如果需要更大的密钥大小（例如 4096），那么最后一个参数 `2048` 可以改成 `4096`。这些大小总是二的幂。


下面是产生的 `privkey.pem` 文件的一个片断，它是 base64 编码的：



```
-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBANnlAh4jSKgcNj/Z
JF4J4WdhkljP2R+TXVGuKVRtPkGAiLWE4BDbgsyKVLfs2EdjKL1U+/qtfhYsqhkK
...
-----END PRIVATE KEY-----

```

接下来的命令就会从私钥中提取出这对密钥的公钥：



```
openssl rsa -in privkey.pem -outform PEM -pubout -out pubkey.pem

```

由此产生的 `pubkey.pem` 文件很小，可以在这里完整地显示出来：



```
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZ5QIeI0ioHDY/2SReCeFnYZJY
z9kfk11RrilUbT5BgIi1hOAQ24LMilS37NhHYyi9VPv6rX4WLKoZCmkeYaWk/TR5
4nbH1E/AkniwRoXpeh5VncwWMuMsL5qPWGY8fuuTE27GhwqBiKQGBOmU+MYlZonO
O0xnAKpAvysMy7G7qQIDAQAB
-----END PUBLIC KEY-----

```

现在，有了密钥对，数字签名就很容易了 —— 在本例中，源文件 `client.c` 是要签名的工件：



```
openssl dgst -sha256 -sign privkey.pem -out sign.sha256 client.c

```

`client.c` 源文件的摘要是 SHA256，私钥在前面创建的 `privkey.pem` 文件中。由此产生的二进制签名文件是 `sign.sha256`，这是一个任意的名字。要得到这个文件的可读版本（比如 base64），后续命令是：



```
openssl enc -base64 -in sign.sha256 -out sign.sha256.base64

```

文件 `sign.sha256.base64` 现在包含如下内容：



```
h+e+3UPx++KKSlWKIk34fQ1g91XKHOGFRmjc0ZHPEyyjP6/lJ05SfjpAJxAPm075
VNfFwysvqRGmL0jkp/TTdwnDTwt756Ej4X3OwAVeYM7i5DCcjVsQf5+h7JycHKlM
o/Jd3kUIWUkZ8+Lk0ZwzNzhKJu6LM5KWtL+MhJ2DpVc=

```

或者，可执行文件 `client` 也可以被签名，由此产生的 base64 编码签名将如预期的不同：



```
VMVImPgVLKHxVBapJ8DgLNJUKb98GbXgehRPD8o0ImADhLqlEKVy0HKRm/51m9IX
xRAN7DoL4Q3uuVmWWi749Vampong/uT5qjgVNTnRt9jON112fzchgEoMb8CHNsCT
XIMdyaPtnJZdLALw6rwMM55MoLamSc6M/MV1OrJnk/g=

```

这一过程的最后一步是用公钥验证数字签名。作为验证的一个重要步骤，应重新计算用于签署工件（在本例中，是可执行的 `client` 程序）的哈希值，因为验证过程应表明工件在签署后是否发生了变化。


有两个 OpenSSL 命令用于这个目的。第一条命令是对 base64 签名进行解码。



```
openssl enc -base64 -d -in sign.sha256.base64 -out sign.sha256

```

第二条是核实签名：



```
openssl dgst -sha256 -verify pubkey.pem -signature sign.sha256 client

```

第二条命令的输出，应该是这样的：



```
Verified OK

```

为了了解验证失败时的情况，一个简短但有用的练习是将最后一个 OpenSSL 命令中的可执行的 `client` 文件替换为源文件 `client.c`，然后尝试验证。另一个练习是改变 `client` 程序，无论多么轻微，然后再试一次。


### 数字证书


<ruby> 数字证书 <rt>  digital certificate </rt></ruby>汇集了到目前为止所分析的各个部分：哈希值、密钥对、数字签名和加密/解密。生产级证书的第一步是创建一个<ruby> 证书签名请求 <rt>  certificate signing request </rt></ruby>（CSR），然后将其发送给<ruby> 证书颁发机构 <rt>  certificate authority </rt></ruby>（CA）。在 OpenSSL 的例子中，要做到这一点，请运行：



```
openssl req -out myserver.csr -new -newkey rsa:4096 -nodes -keyout myserverkey.pem

```

这个例子生成了一个 CSR 文档，并将该文档存储在文件 `myserver.csr`（base64 文本）中。这里的目的是：CSR 文档要求 CA 保证与指定域名相关联的身份，域名也就是 CA 所说的<ruby> 通用名 <rt>  common name </rt></ruby>（CN）。


尽管可以使用现有的密钥对，但这个命令也会生成一个新的密钥对。请注意，在诸如 `myserver.csr` 和 `myserverkey.pem` 等名称中使用 `server` 暗示了数字证书的典型用途：作为与 www.google.com 等域名相关的 Web 服务器的身份担保。


然而，无论数字证书如何使用，同样使用这个命令都会创建一个 CSR。它还会启动一个问题/回答的交互式会话，提示有关域名的相关信息，以便与请求者的数字证书相连接。这个交互式会话可以通过在命令中提供基本的信息，用反斜杠来续行一步完成。`-subj` 标志提供了所需的信息。



```
% openssl req -new \
-newkey rsa:2048 -nodes -keyout privkeyDC.pem \
-out myserver.csr \
-subj "/C=US/ST=Illinois/L=Chicago/O=Faulty Consulting/OU=IT/CN=myserver.com"

```

产生的 CSR 文件在发送给 CA 之前可以进行检查和验证。这个过程可以创建具有所需格式（如 X509）、签名、有效期等的数字证书。



```
openssl req -text -in myserver.csr -noout -verify

```

这是输出的一个片断：



```
verify OK
Certificate Request:
Data:
Version: 0 (0x0)
Subject: C=US, ST=Illinois, L=Chicago, O=Faulty Consulting, OU=IT, CN=myserver.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:ba:36:fb:57:17:65:bc:40:30:96:1b:6e:de:73:
…
Exponent: 65537 (0x10001)
Attributes:
a0:00
Signature Algorithm: sha256WithRSAEncryption
…

```

### 自签证书


在开发 HTTPS 网站的过程中，手头有一个不用经过 CA 流程的数字证书是很方便的。在 HTTPS 握手的认证阶段，<ruby> 自签证书 <rt>  self-signed certificate </rt></ruby>就能满足要求，尽管任何现代浏览器都会警告说这样的证书毫无价值。继续这个例子，自签证书的 OpenSSL 命令（有效期为一年，使用 RSA 公钥）如下：



```
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:4096 -keyout myserver.pem -out myserver.crt

```

下面的 OpenSSL 命令呈现了生成的证书的可读版本：



```
openssl x509 -in myserver.crt -text -noout

```

这是自签证书的部分输出：



```
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 13951598013130016090 (0xc19e087965a9055a)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=US, ST=Illinois, L=Chicago, O=Faulty Consulting, OU=IT, CN=myserver.com
Validity
Not Before: Apr 11 17:22:18 2019 GMT
Not After : Apr 10 17:22:18 2020 GMT
Subject: C=US, ST=Illinois, L=Chicago, O=Faulty Consulting, OU=IT, CN=myserver.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (4096 bit)
Modulus:
00:ba:36:fb:57:17:65:bc:40:30:96:1b:6e:de:73:
...
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
3A:32:EF:3D:EB:DF:65:E5:A8:96:D7:D7:16:2C:1B:29:AF:46:C4:91
X509v3 Authority Key Identifier:
keyid:3A:32:EF:3D:EB:DF:65:E5:A8:96:D7:D7:16:2C:1B:29:AF:46:C4:91

        X509v3 Basic Constraints:
            CA:TRUE
Signature Algorithm: sha256WithRSAEncryption
     3a:eb:8d:09:53:3b:5c:2e:48:ed:14:ce:f9:20:01:4e:90:c9:
     ...

```

如前所述，RSA 私钥包含的值是用来生成公钥的。但是，给定的公钥**不会**泄露匹配的私钥。关于底层数学理论的介绍，见 <https://simple.wikipedia.org/wiki/RSA_algorithm>。


数字证书与用于生成该证书的密钥对之间存在着重要的对应关系，即使证书只是自签的：


* 数字证书包含构成公钥的指数和模数值。这些值是最初生成的 PEM 文件中密钥对的一部分，在本例中，是文件 `myserver.pem`。
* <ruby> 指数 <rt>  exponent </rt></ruby>几乎总是 65,537（如本例中），所以可以忽略。
* 密钥对的<ruby> 模数 <rt>  modulus </rt></ruby>应该与数字证书的模数相匹配。


模数是一个很大的值，为了便于阅读，可以进行哈希处理。下面是两个 OpenSSL 命令，它们检查相同的模数，从而确认数字证书是基于 PEM 文件中的密钥对。



```
% openssl x509 -noout -modulus -in myserver.crt | openssl sha1 ## 证书中的模数
(stdin)= 364d21d5e53a59d482395b1885aa2c3a5d2e3769

% openssl rsa -noout -modulus -in myserver.pem | openssl sha1 ## 密钥中的模数
(stdin)= 364d21d5e53a59d482395b1885aa2c3a5d2e3769

```

所产生的哈希值匹配，从而确认数字证书是基于指定的密钥对。


### 回到密钥分发问题上


让我们回到第一部分末尾提出的一个问题：`client` 程序和 Google Web 服务器之间的 TLS 握手。握手协议有很多种，即使是用在 `client` 例子中的 Diffie-Hellman 版本也有不同的方式。尽管如此，`client` 例子遵循了一个共同的模式。


首先，在 TLS 握手过程中，`client` 程序和 Web 服务器就<ruby> 加密套件 <rt>  cipher suite </rt></ruby>达成一致，其中包括要使用的算法。在本例中，该套件是 `ECDHE-RSA-AES128-GCM-SHA256`。


现在值得关注的两个要素是 RSA 密钥对算法和 AES128 块密码，用于在握手成功的情况下对消息进行加密和解密。关于加密/解密，这个过程有两种流派：<ruby> 对称 <rt>  symmetric </rt></ruby>和<ruby> 非对称 <rt>  asymmetric </rt></ruby>。在对称流派中，加密和解密使用的是*相同的*密钥，这首先就引出了<ruby> 密钥分发问题 <rt>  key distribution problem </rt></ruby>。如何将密钥安全地分发给双方？在非对称流派中，一个密钥用于加密（在这种情况下，是 RSA 公钥），但另一个密钥用于解密（在这种情况下，是来自同一对密钥的 RSA 私钥）。


`client` 程序拥有来认证证书的 Google Web 服务器的公钥，而 Web 服务器拥有来自同一对密钥的私钥。因此，`client` 程序可以向 Web 服务器发送加密信息，而 Web 服务器可以单独对该通信进行解密。


在 TLS 的情况下，对称方式有两个显著的优势：


* 在 `client` 程序与 Google Web 服务器之间的互动中，认证是单向的。Google Web 服务器向 `client` 程序发送三张证书，但 `client` 程序并没有向 Web 服务器发送证书，因此，Web 服务器没有来自客户端的公钥，无法加密发给客户端的消息。
* 使用 AES128 的对称加密/解密比使用 RSA 密钥的非对称加密/解密快了**近千倍**。


TLS 握手将两种加密/解密方式巧妙地结合在一起。在握手过程中，`client` 程序会生成随机位，即所谓的<ruby> 预主密 <rt>  pre-master secret </rt></ruby>（PMS）。然后，`client` 程序用服务器的公钥对 PMS 进行加密，并将加密后的 PMS 发送给服务器，服务器再用 RSA 密钥对的私钥对 PMS 信息进行解密：



```
              +-------------------+ encrypted PMS  +--------------------+
client PMS--->|server’s public key|--------------->|server’s private key|--->server PMS
              +-------------------+                +--------------------+

```

在这个过程结束时，`client` 程序和 Google Web 服务器现在拥有相同的 PMS 位。每一方都使用这些位生成一个<ruby> 主密码 <rt>  master secret </rt></ruby>，并立即生成一个称为<ruby> 会话密钥 <rt>  session key </rt></ruby>的对称加密/解密密钥。现在有两个不同但等价的会话密钥，连接的每一方都有一个。在 `client` 的例子中，会话密钥是 AES128 类的。一旦在 `client` 程序和 Google Web 服务器两边生成了会话密钥，每一边的会话密钥就会对双方的对话进行保密。如果任何一方（例如，`client` 程序）或另一方（在这种情况下，Google Web 服务器）要求重新开始握手，握手协议（如 Diffie-Hellman）允许整个 PMS 过程重复进行。


### 总结


在命令行上说明的 OpenSSL 操作也可以通过底层库的 API 完成。这两篇文章重点使用了这个实用程序，以保持例子的简短，并专注于加密主题。如果你对安全问题感兴趣，OpenSSL 是一个很好的开始地方，并值得深入研究。




---


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


作者：[Marty Kalin](https://opensource.com/users/mkalindepauledu) 选题：[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
The [first article in this series](https://opensource.com/article/19/6/cryptography-basics-openssl-part-1) introduced hashes, encryption/decryption, digital signatures, and digital certificates through the OpenSSL libraries and command-line utilities. This second article drills down into the details. Let’s begin with hashes, which are ubiquitous in computing, and consider what makes a hash function *cryptographic*.

## Cryptographic hashes

The download page for the OpenSSL source code ([https://www.openssl.org/source/](https://www.openssl.org/source/)) contains a table with recent versions. Each version comes with two hash values: 160-bit SHA1 and 256-bit SHA256. These values can be used to verify that the downloaded file matches the original in the repository: The downloader recomputes the hash values locally on the downloaded file and then compares the results against the originals. Modern systems have utilities for computing such hashes. Linux, for instance, has **md5sum** and **sha256sum**. OpenSSL itself provides similar command-line utilities.

Hashes are used in many areas of computing. For example, the Bitcoin blockchain uses SHA256 hash values as block identifiers. To mine a Bitcoin is to generate a SHA256 hash value that falls below a specified threshold, which means a hash value with at least N leading zeroes. (The value of N can go up or down depending on how productive the mining is at a particular time.) As a point of interest, today’s miners are hardware clusters designed for generating SHA256 hashes in parallel. During a peak time in 2018, Bitcoin miners worldwide generated about 75 million terahashes per second—yet another incomprehensible number.

Network protocols use hash values as well—often under the name **checksum**—to support message integrity; that is, to assure that a received message is the same as the one sent. The message sender computes the message’s checksum and sends the results along with the message. The receiver recomputes the checksum when the message arrives. If the sent and the recomputed checksum do not match, then something happened to the message in transit, or to the sent checksum, or to both. In this case, the message and its checksum should be sent again, or at least an error condition should be raised. (Low-level network protocols such as UDP do not bother with checksums.)

Other examples of hashes are familiar. Consider a website that requires users to authenticate with a password, which the user enters in their browser. Their password is then sent, encrypted, from the browser to the server via an HTTPS connection to the server. Once the password arrives at the server, it's decrypted for a database table lookup.

What should be stored in this lookup table? Storing the passwords themselves is risky. It’s far less risky is to store a hash generated from a password, perhaps with some *salt* (extra bits) added to taste before the hash value is computed. Your password may be sent to the web server, but the site can assure you that the password is not stored there.

Hash values also occur in various areas of security. For example, hash-based message authentication code ([HMAC](https://en.wikipedia.org/wiki/HMAC)) uses a hash value and a secret cryptographic key to authenticate a message sent over a network. HMAC codes, which are lightweight and easy to use in programs, are popular in web services. An X509 digital certificate includes a hash value known as the *fingerprint*, which can facilitate certificate verification. An in-memory truststore could be implemented as a lookup table keyed on such fingerprints—as a *hash map*, which supports constant-time lookups. The fingerprint from an incoming certificate can be compared against the truststore keys for a match.

What special property should a *cryptographic hash function* have? It should be *one-way*, which means very difficult to invert. A cryptographic hash function should be relatively straightforward to compute, but computing its inverse—the function that maps the hash value back to the input bitstring—should be computationally intractable. Here is a depiction, with **chf** as a cryptographic hash function and my password **foobar** as the sample input:

```
+---+
foobar—>|chf|—>hash value ## straightforward
+--–+
```


By contrast, the inverse operation is infeasible:

```
+-----------+
hash value—>|chf inverse|—>foobar ## intractable
+-----------+
```


Recall, for example, the SHA256 hash function. For an input bitstring of any length N > 0, this function generates a fixed-length hash value of 256 bits; hence, this hash value does not reveal even the input bitstring’s length N, let alone the value of each bit in the string. By the way, SHA256 is not susceptible to a [ length extension attack](https://en.wikipedia.org/wiki/Length_extension_attack). The only effective way to reverse engineer a computed SHA256 hash value back to the input bitstring is through a brute-force search, which means trying every possible input bitstring until a match with the target hash value is found. Such a search is infeasible on a sound cryptographic hash function such as SHA256.

Now, a final review point is in order. Cryptographic hash values are statistically rather than unconditionally unique, which means that it is unlikely but not impossible for two different input bitstrings to yield the same hash value—a *collision*. The [ birthday problem](https://en.wikipedia.org/wiki/Birthday_problem) offers a nicely counter-intuitive example of collisions. There is extensive research on various hash algorithms’

*collision resistance*. For example, MD5 (128-bit hash values) has a breakdown in collision resistance after roughly 2

21hashes. For SHA1 (160-bit hash values), the breakdown starts at about 2

61hashes.

A good estimate of the breakdown in collision resistance for SHA256 is not yet in hand. This fact is not surprising. SHA256 has a range of 2256 distinct hash values, a number whose decimal representation has a whopping 78 digits! So, can collisions occur with SHA256 hashing? Of course, but they are extremely unlikely.

In the command-line examples that follow, two input files are used as bitstring sources: **hashIn1.txt** and **hashIn2.txt**. The first file contains **abc** and the second contains **1a2b3c**.

These files contain text for readability, but binary files could be used instead.

Using the Linux **sha256sum** utility on these two files at the command line—with the percent sign (**%**) as the prompt—produces the following hash values (in hex):

```
% sha256sum hashIn1.txt
9e83e05bbf9b5db17ac0deec3b7ce6cba983f6dc50531c7a919f28d5fb3696c3 hashIn1.txt
% sha256sum hashIn2.txt
3eaac518777682bf4e8840dd012c0b104c2e16009083877675f00e995906ed13 hashIn2.txt
```


The OpenSSL hashing counterparts yield the same results, as expected:

```
% openssl dgst -sha256 hashIn1.txt
SHA256(hashIn1.txt)= 9e83e05bbf9b5db17ac0deec3b7ce6cba983f6dc50531c7a919f28d5fb3696c3
% openssl dgst -sha256 hashIn2.txt
SHA256(hashIn2.txt)= 3eaac518777682bf4e8840dd012c0b104c2e16009083877675f00e995906ed13
```


This examination of cryptographic hash functions sets up a closer look at digital signatures and their relationship to key pairs.

## Digital signatures

As the name suggests, a digital signature can be attached to a document or some other electronic artifact (e.g., a program) to vouch for its authenticity. Such a signature is thus analogous to a hand-written signature on a paper document. To verify the digital signature is to confirm two things. First, that the vouched-for artifact has not changed since the signature was attached because it is based, in part, on a cryptographic *hash* of the document. Second, that the signature belongs to the person (e.g., Alice) who alone has access to the private key in a pair. By the way, digitally signing code (source or compiled) has become a common practice among programmers.

Let’s walk through how a digital signature is created. As mentioned before, there is no digital signature without a public and private key pair. When using OpenSSL to create these keys, there are two separate commands: one to create a private key, and another to extract the matching public key from the private one. These key pairs are encoded in base64, and their sizes can be specified during this process.

The private key consists of numeric values, two of which (a *modulus* and an *exponent*) make up the public key. Although the private key file contains the public key, the extracted public key does *not* reveal the value of the corresponding private key.

The resulting file with the private key thus contains the full key pair. Extracting the public key into its own file is practical because the two keys have distinct uses, but this extraction also minimizes the danger that the private key might be publicized by accident.

Next, the pair’s private key is used to process a hash value for the target artifact (e.g., an email), thereby creating the signature. On the other end, the receiver’s system uses the pair’s public key to verify the signature attached to the artifact.

Now for an example. To begin, generate a 2048-bit RSA key pair with OpenSSL:

**openssl genpkey -out privkey.pem -algorithm rsa 2048**

We can drop the **-algorithm rsa** flag in this example because **genpkey** defaults to the type RSA. The file’s name (**privkey.pem**) is arbitrary, but the Privacy Enhanced Mail (PEM) extension **pem** is customary for the default PEM format. (OpenSSL has commands to convert among formats if needed.) If a larger key size (e.g., 4096) is in order, then the last argument of **2048** could be changed to **4096**. These sizes are always powers of two.

Here’s a slice of the resulting **privkey.pem** file, which is in base64:

```
-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBANnlAh4jSKgcNj/Z
JF4J4WdhkljP2R+TXVGuKVRtPkGAiLWE4BDbgsyKVLfs2EdjKL1U+/qtfhYsqhkK
…
-----END PRIVATE KEY-----
```


The next command then extracts the pair’s public key from the private one:

**openssl rsa -in privkey.pem -outform PEM -pubout -out pubkey.pem**

The resulting **pubkey.pem** file is small enough to show here in full:

```
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZ5QIeI0ioHDY/2SReCeFnYZJY
z9kfk11RrilUbT5BgIi1hOAQ24LMilS37NhHYyi9VPv6rX4WLKoZCmkeYaWk/TR5
4nbH1E/AkniwRoXpeh5VncwWMuMsL5qPWGY8fuuTE27GhwqBiKQGBOmU+MYlZonO
O0xnAKpAvysMy7G7qQIDAQAB
-----END PUBLIC KEY-----
```


Now, with the key pair at hand, the digital signing is easy—in this case with the source file **client.c** as the artifact to be signed:

**openssl dgst -sha256 -sign privkey.pem -out sign.sha256 client.c**

The digest for the **client.c** source file is SHA256, and the private key resides in the **privkey.pem** file created earlier. The resulting binary signature file is **sign.sha256**, an arbitrary name. To get a readable (if base64) version of this file, the follow-up command is:

**openssl enc -base64 -in sign.sha256 -out sign.sha256.base64**

The file **sign.sha256.base64** now contains:

```
h+e+3UPx++KKSlWKIk34fQ1g91XKHOGFRmjc0ZHPEyyjP6/lJ05SfjpAJxAPm075
VNfFwysvqRGmL0jkp/TTdwnDTwt756Ej4X3OwAVeYM7i5DCcjVsQf5+h7JycHKlM
o/Jd3kUIWUkZ8+Lk0ZwzNzhKJu6LM5KWtL+MhJ2DpVc=
```


Or, the executable file **client** could be signed instead, and the resulting base64-encoded signature would differ as expected:

```
VMVImPgVLKHxVBapJ8DgLNJUKb98GbXgehRPD8o0ImADhLqlEKVy0HKRm/51m9IX
xRAN7DoL4Q3uuVmWWi749Vampong/uT5qjgVNTnRt9jON112fzchgEoMb8CHNsCT
XIMdyaPtnJZdLALw6rwMM55MoLamSc6M/MV1OrJnk/g=
```


The final step in this process is to verify the digital signature with the public key. The hash used to sign the artifact (in this case, the executable **client** program) should be recomputed as an essential step in the verification since the verification process should indicate whether the artifact has changed since being signed.

There are two OpenSSL commands used for this purpose. The first decodes the base64 signature:

**openssl enc -base64 -d -in sign.sha256.base64 -out sign.sha256**

The second verifies the signature:

**openssl dgst -sha256 -verify pubkey.pem -signature sign.sha256 client**

The output from this second command is, as it should be:

`Verified OK`


To understand what happens when verification fails, a short but useful exercise is to replace the executable **client** file in the last OpenSSL command with the source file **client.c** and then try to verify. Another exercise is to change the **client** program, however slightly, and try again.

## Digital certificates

A digital certificate brings together the pieces analyzed so far: hash values, key pairs, digital signatures, and encryption/decryption. The first step toward a production-grade certificate is to create a certificate signing request (CSR), which is then sent to a certificate authority (CA). To do this for the example with OpenSSL, run:

**openssl req -out myserver.csr -new -newkey rsa:4096 -nodes -keyout myserverkey.pem**

This example generates a CSR document and stores the document in the file **myserver.csr** (base64 text). The purpose here is this: the CSR document requests that the CA vouch for the identity associated with the specified domain name—the common name (CN) in CA-speak.

A new key pair also is generated by this command, although an existing pair could be used. Note that the use of **server** in names such as **myserver.csr** and **myserverkey.pem** hints at the typical use of digital certificates: as vouchers for the identity of a web server associated with a domain such as [www.google.com](http://www.google.com).

The same command, however, creates a CSR regardless of how the digital certificate might be used. It also starts an interactive question/answer session that prompts for relevant information about the domain name to link with the requester’s digital certificate. This interactive session can be short-circuited by providing the essentials as part of the command, with backslashes as continuations across line breaks. The **-subj** flag introduces the required information:

```
% openssl req -new
-newkey rsa:2048 -nodes -keyout privkeyDC.pem
-out myserver.csr
-subj "/C=US/ST=Illinois/L=Chicago/O=Faulty Consulting/OU=IT/CN=myserver.com"
```


The resulting CSR document can be inspected and verified before being sent to a CA. This process creates the digital certificate with the desired format (e.g., X509), signature, validity dates, and so on:

**openssl req -text -in myserver.csr -noout -verify**

Here’s a slice of the output:

```
verify OK
Certificate Request:
Data:
Version: 0 (0x0)
Subject: C=US, ST=Illinois, L=Chicago, O=Faulty Consulting, OU=IT, CN=myserver.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:ba:36:fb:57:17:65:bc:40:30:96:1b:6e:de:73:
…
Exponent: 65537 (0x10001)
Attributes:
a0:00
Signature Algorithm: sha256WithRSAEncryption
…
```


## A self-signed certificate

During the development of an HTTPS web site, it is convenient to have a digital certificate on hand without going through the CA process. A self-signed certificate fills the bill during the HTTPS handshake’s authentication phase, although any modern browser warns that such a certificate is worthless. Continuing the example, the OpenSSL command for a self-signed certificate—valid for a year and with an RSA public key—is:

**openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:4096 -keyout myserver.pem -out myserver.crt**

The OpenSSL command below presents a readable version of the generated certificate:

**openssl x509 -in myserver.crt -text -noout**

Here’s part of the output for the self-signed certificate:

```
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 13951598013130016090 (0xc19e087965a9055a)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=US, ST=Illinois, L=Chicago, O=Faulty Consulting, OU=IT, CN=myserver.com
Validity
Not Before: Apr 11 17:22:18 2019 GMT
Not After : Apr 10 17:22:18 2020 GMT
Subject: C=US, ST=Illinois, L=Chicago, O=Faulty Consulting, OU=IT, CN=myserver.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (4096 bit)
Modulus:
00:ba:36:fb:57:17:65:bc:40:30:96:1b:6e:de:73:
…
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
3A:32:EF:3D:EB:DF:65:E5:A8:96:D7:D7:16:2C:1B:29:AF:46:C4:91
X509v3 Authority Key Identifier:
keyid:3A:32:EF:3D:EB:DF:65:E5:A8:96:D7:D7:16:2C:1B:29:AF:46:C4:91
X509v3 Basic Constraints:
CA:TRUE
Signature Algorithm: sha256WithRSAEncryption
3a:eb:8d:09:53:3b:5c:2e:48:ed:14:ce:f9:20:01:4e:90:c9:
...
```


As mentioned earlier, an RSA private key contains values from which the public key is generated. However, a given public key does *not* give away the matching private key. For an introduction to the underlying mathematics, see [https://simple.wikipedia.org/wiki/RSA_algorithm](https://simple.wikipedia.org/wiki/RSA_algorithm).

There is an important correspondence between a digital certificate and the key pair used to generate the certificate, even if the certificate is only self-signed:

- The digital certificate contains the
*exponent*and*modulus*values that make up the public key. These values are part of the key pair in the originally-generated PEM file, in this case, the file**myserver.pem**. - The exponent is almost always 65,537 (as in this case) and so can be ignored.
- The modulus from the key pair should match the modulus from the digital certificate.

The modulus is a large value and, for readability, can be hashed. Here are two OpenSSL commands that check for the same modulus, thereby confirming that the digital certificate is based upon the key pair in the PEM file:

```
% openssl x509 -noout -modulus -in myserver.crt | openssl sha1 ## modulus from CRT
(stdin)= 364d21d5e53a59d482395b1885aa2c3a5d2e3769
% openssl rsa -noout -modulus -in myserver.pem | openssl sha1 ## modulus from PEM
(stdin)= 364d21d5e53a59d482395b1885aa2c3a5d2e3769
```


The resulting hash values match, thereby confirming that the digital certificate is based upon the specified key pair.

## Back to the key distribution problem

Let’s return to an issue raised at the end of Part 1: the TLS handshake between the **client** program and the Google web server. There are various handshake protocols, and even the Diffie-Hellman version at work in the **client** example offers wiggle room. Nonetheless, the **client** example follows a common pattern.

To start, during the TLS handshake, the **client** program and the web server agree on a cipher suite, which consists of the algorithms to use. In this case, the suite is **ECDHE-RSA-AES128-GCM-SHA256**.

The two elements of interest now are the RSA key-pair algorithm and the AES128 block cipher used for encrypting and decrypting messages if the handshake succeeds. Regarding encryption/decryption, this process comes in two flavors: symmetric and asymmetric. In the symmetric flavor, the *same* key is used to encrypt and decrypt, which raises the *key distribution problem* in the first place: How is the key to be distributed securely to both parties? In the asymmetric flavor, one key is used to encrypt (in this case, the RSA public key) but a different key is used to decrypt (in this case, the RSA private key from the same pair).

The **client** program has the Google web server’s public key from an authenticating certificate, and the web server has the private key from the same pair. Accordingly, the **client** program can send an encrypted message to the web server, which alone can readily decrypt this message.

In the TLS situation, the symmetric approach has two significant advantages:

- In the interaction between the
**client**program and the Google web server, the authentication is one-way. The Google web server sends three certificates to the**client**program, but the**client**program does not send a certificate to the web server; hence, the web server has no public key from the client and can’t encrypt messages to the client. - Symmetric encryption/decryption with AES128 is nearly a
*thousand times faster*than the asymmetric alternative using RSA keys.

The TLS handshake combines the two flavors of encryption/decryption in a clever way. During the handshake, the **client** program generates random bits known as the pre-master secret (PMS). Then the **client** program encrypts the PMS with the server’s public key and sends the encrypted PMS to the server, which in turn decrypts the PMS message with its private key from the RSA pair:

```
+-------------------+ encrypted PMS +--------------------+
client PMS--->|server’s public key|--------------->|server’s private key|--->server PMS
+-------------------+ +--------------------+
```


At the end of this process, the **client** program and the Google web server now have the same PMS bits. Each side uses these bits to generate a *master secret* and, in short order, a symmetric encryption/decryption key known as the *session key*. There are now two distinct but identical session keys, one on each side of the connection. In the **client** example, the session key is of the AES128 variety. Once generated on both the **client** program’s and Google web server’s sides, the session key on each side keeps the conversation between the two sides confidential. A handshake protocol such as Diffie-Hellman allows the entire PMS process to be repeated if either side (e.g., the **client** program) or the other (in this case, the Google web server) calls for a restart of the handshake.

## Wrapping up

The OpenSSL operations illustrated at the command line are available, too, through the API for the underlying libraries. These two articles have emphasized the utilities to keep the examples short and to focus on the cryptographic topics. If you have an interest in security issues, OpenSSL is a fine place to start—and to stay.

## Comments are closed.