第七章 数字签名及其验证

在本章中,我们将学习数字签名digital signatures)以及如何在稍后对内容进行签名和验证签名。数字签名具有许多重要的实际应用,例如签署文档、证书、软件和网络请求和响应,包括金融交易。数字签名对于比特币等加密货币的安全性也很重要。数字签名算法是TLSSSHIPsec 等安全网络协议中客户端和服务器身份验证的必要构建块。数字签名既用于TLS协议握手,也用于签署X.509证书X.509 certificates),后者用于TLS中的身份表示和验证。有关X.509证书的更多信息将在【第8章X.509证书和PKI】中提供。TLS协议将在【第4部分“TLS连接和安全通信”】中介绍。数字签名也用于安全消息传递标准,如Pretty Good PrivacyPGP)和 Secure Multipurpose Mail ExtensionsS/MIME)。例如,OpenSSL版本由 PGP 数字签名签名。

我们还将概述OpenSSL支持的数字签名算法,并就使用和避免哪些数字签名方案提出建议。在本章的实践部分,我们将学习如何在命令行上对内容进行数字签名,并使用C代码以编程方式验证生成的签名。

第七章 数字签名及其验证技术要求理解数字签名数字签名和MACs之间的区别OpenSSL支持的数字签名算法概述RSA速览DSA速览ECDSA速览EdDSA速览SM2速览你应该选择哪一种数字签名算法?如何生成椭圆曲线密钥对如何在命令行上签名和验证签名如何以编程方式签名实施ec-sign程序运行ec-sign程序如何以编程方式验证签名实施ec-verify程序运行ec-verify程序总结

技术要求

本章将包含可以在命令行上运行的命令以及可以构建和运行的C源代码。对于命令行命令,您将需要openssl命令行工具以及一些 openssl 动态库。要构建C代码,您需要OpenSSL动态或静态库、库头、C编译器和链接器。

我们将在本章中实施一些示例程序,以练习我们正在学习的内容。这些程序的完整源代码可以在这里找到:

https://github.com/PacktPublishing/Demystifying-Cryptography-with-OpenSSL-3/tree/main/Chapter07

理解数字签名

数字签名是一组比特,为数字消息的真实性、完整性和不可否认性提供了强有力的加密保证。这些保证意味着什么?让我们来看看:

数字签名是使用私钥生成的,私钥可以使用相应的公钥进行验证。因此,数字签名和验证算法被认为是非对称密码算法asymmetric cryptography algorithms),即使它们不是非对称加密算法。

重要的是要知道,当消息被签名时,通常数字签名算法不会应用于消息本身。相反,签名算法应用于消息摘要message digest),该摘要由一些加密哈希函数(如SHA-256)生成。密码学家说,这种签名方案是基于哈希和签名hash-and-sign)范式的。

一个值得注意且有争议的例外是EdDSA签名算法。值得注意的是,它是唯一一种将整个消息而不是摘要作为输入的流行签名算法。这是有争议的,因为EdDSA无论如何都会对输入消息进行内部哈希,尽管其方式比其他签名算法更复杂。

那么,为什么对消息摘要而不是消息本身进行签名呢?原因与会话密钥用于非对称加密的原因相同。非对称加密算法相对较慢,比加密哈希函数慢得多。快速散列算法将可能非常长的输入消息转换为已知长度的短消息摘要,这样慢速数字签名算法就不需要处理大量输入。与签名算法需要处理整个长消息相比,这种工作分离提高了性能。另一个原因是,数字签名算法只能对一个块中有限数量的数据进行签名。将消息拆分为具有多个签名的多个块,并发明一种安全的方法来链接生成的签名并抵抗各种变序攻击,这很不方便。

请注意,有效和高性能effective and performant)的非对称密码学是如何建立在对称密码学之上的。非对称加密使用对称会话密钥,而数字签名使用消息摘要。因此,在学习非对称asymmetric)密码学之前,了解对称symmetric)密码学非常重要。

数字签名和MACs之间的区别

正如我们所看到的,数字签名与消息认证码Message Authentication CodesMAC)有一些共同之处。数字签名和MACs都可以提供身份验证和完整性。然而,有一些重要的区别:

现在我们已经了解了数字签名是什么,让我们来看看OpenSSL提供了什么样的数字签名算法。

OpenSSL支持的数字签名算法概述

在本节中,我们将回顾OpenSSL支持的数字签名算法,并就使用哪些算法给出一些建议。

RSA速览

Rivest-Shamir-AdlemanRSA)算法是由Ronald Rivest、Adi Shamir和Leonard Adleman发明的。Ronald Rivest是发明RC家族对称加密算法和MD家族消息摘要算法的同一个人。RSA算法于1977年首次发表。这是第一个流行的非对称加密算法。

RSA是一种非常通用的算法,因为它既可以加密消息,也可以对消息进行签名。在OpenSSL提供的数字签名算法中,RSA是签名最慢但验证相同安全级别最快的算法。

签名和验证的速度取决于密钥大小。签名速度随着密钥大小的增长而迅速下降。验证速度也会下降,但不会那么快。验证比签名快30-70倍,具体取决于密钥大小。

RSA产生最大的签名。RSA签名的大小与用于生成签名的密钥大小相同。例如,由4096位密钥生成的签名将具有4096位或512字节的大小。其他算法产生的签名要小得多。

RSA的另一个缺点是RSA密钥比更现代的基于椭圆曲线Elliptic CurveEC)的算法的密钥长得多。此外,为了获得少量的安全性,必须大幅增加RSA密钥的大小。

RSA是一种相当古老的算法。OpenSSL支持另一种旧算法DSA。

DSA速览

数字签名算法Digital Signature AlgorithmDSA)是由David W.Kravitz在美国国家安全局National Security AgencyNSA)工作时发明的。DSA于1991年首次发布,当时NIST提出将DSA用于其数字签名标准Digital Signature StandardDSS)。

DSA的安全性依赖于离散对数问题的计算难度。与RSA一样,DSA使用长密钥,如2048位密钥。与RSA一样,DSA密钥的安全级别远低于其长度。DSA密钥恰好与长度相同的RSA密钥具有相同的安全级别。因此,如【第6章“非对称加密和解密”】所示,将RSA密钥长度映射到其安全级别的表也可用于找出DSA密钥的安全性。例如,2048位DSA密钥的安全级别为112位。

1994年,第一个DSS被采纳为FIPS-186,它只允许DSA与SHA-1哈希函数一起使用,密钥长度可达1024位。最新的DSS,2013年的FIPS 186-4,也允许我们将DSA与SHA-224和SHA-256哈希函数一起使用,密钥长度可达3072位。OpenSSL通过上述三个哈希函数和长度非常长的密钥支持DSA。然而,在我看来,你不应该需要长度超过4096位的DSA密钥。

尽管DSA密钥很长,就像RSA密钥一样,但它们产生的签名比密钥长度短得多。DSA的签名长度(以位为单位)约为 4*K_bits,其中 K_bits 是所用DSA密钥的安全强度。例如,4096位RSA密钥产生512字节的签名,但相同长度的DSA密钥产生70或71字节的签名。

目前,DSA不是一种非常流行的算法,正在被ECDSA取代。

ECDSA速览

椭圆曲线数字签名算法Elliptic Curve Digital Signature AlgorithmECDSA)是使用椭圆曲线密码学Elliptic Curve CryptographyECC)的DSA算法的变体。ECC是一种基于有限域上椭圆曲线代数结构的公钥密码学。ECC是由Neal Koblitz和Victor Miller于1985年发明的。ECDSA由Scott Vanstone于1992年提出,作为对第一个DSS标准的评论。

ECDSA相对于DSA的优势在于,在相同的安全级别下,它的密钥大小更短。椭圆曲线密钥的安全级别大约是密钥长度的一半。例如,224位密钥具有112位安全级别。为了进行比较,具有相同安全级别的DSA密钥必须长达2048位。与DSA一样,ECDSA签名长度(以比特为单位)约为 4*K_bits ,其中 K_bits 是所用ECDSA密钥的安全强度。

生成ECDSA密钥时,必须选择一条曲线。曲线有一个名称,并定义了当基于该曲线生成密钥时,EC密钥的长度。例如,基于NIST P-256曲线生成的密钥长度为256位。OpenSSL支持NIST曲线和Brainpool曲线。NIST曲线由NSA开发,并由NIST标准化。Brainpool曲线是由Brainpool工作组提出的,该工作组是一组密码学家,他们对NIST曲线不满意,因为NIST曲线不是可验证的随机生成的,因此它们可能有意或无意地具有弱安全性。Brainpool 线由德国联邦信息安全办公室(BSI)标准化和使用。Brainpool工作组定义了一种可验证的随机生成椭圆曲线的方法,并声称该工作组使用该方法生成了Brainpool曲线。然而,Daniel J.Bernstein领导的一个研究小组试图验证Brainpool工作组方法,但无法使用所描述的方法生成相同的Brainpool曲线。Bernstein的团队后来提出了他们自己的椭圆曲线和自己的EC签名算法EdDSA。下一节将提供EdDSA曲线的更多详细信息。

尽管存在这些疑问,NIST曲线目前是ECDSA中最受欢迎的曲线。NIST曲线套件包括两条非常快的曲线:P-256曲线和P-224曲线。NIST P-256曲线在签名时比Brainpool P256r1曲线快16倍,在验证签名时快5倍。NIST P-224曲线在安全性上与2048位DSA相当,在签名时大约比DSA-2048快五倍,在验证时快两倍。其他NIST曲线的速度与具有类似安全性的Brainpool曲线相当,这意味着有时NIST曲线更快,而其他时候Brainpool曲线更快。

值得一提的是,ECDSA的标准实现需要一个好的随机数生成器Random Number GeneratorRNG)来生成密钥和签名过程。还有一种ECDSA的替代实现,在签名时不需要随机数据生成,它使用私钥的哈希值而不是随机数据。签名过程中出现错误的RNG可能会导致私钥通过签名泄露。正确实施ECDSA时应小心;否则,实现可能容易受到定时攻击,也可能泄露私钥。OpenSSL曾经有一个易受定时攻击的实现,但该漏洞在2011年得到了修复。

ECDSA是一种非常流行的算法,但它有一个竞争对手:另一种基于EC的签名算法,称为EdDSA。

EdDSA速览

Edwards曲线数字签名算法Edwards-curve Digital Signature AlgorithmEdDSA)是另一种使用椭圆曲线的签名算法。它是由Daniel J.Bernstein领导的一组密码学家于2011年发表的,Daniel J.Bernstein也是开发ChaCha对称流密码家族和Poly1305 MAC算法的人。

EdDSA基于扭曲的Edwards曲线twisted Edwards curves),旨在比ECDSA更快、更容易安全地实施。与ECDSA不同,EdDSA在签名过程中不需要RNG,因此不会因为RNG不好而泄露私钥。

EdDSA支持两条曲线:253位Curve25519和456位Curve448。这些曲线是为良好的安全性而设计的,试图使其难以犯可能削弱安全性的实现错误。曲线也不受时间攻击。

与ECDSA一样,EdDSA签名的安全级别是密钥长度的一半,这意味着Curve25519为126.5位,Curve448为228位。Curve25519的签名长度为64字节,而Cruve448的签名长度则为114字节。

尽管Curve25519和Ed448是为速度而设计的,但它们并不比NIST P-256曲线快。NIST P-256的签名和签名验证速度大约是Curve25519的两倍。NIST P-224曲线的速度与Curve25519大致相同。然而,Curve25519在签名方面比其他具有类似安全性的NIST和Brainpool曲线快8-13倍,在验证方面比它们快2-9倍。Curve448在签名方面比NIST和Brainpool曲线快大约两倍,在验证方面快1.5-3倍。

EdDSA是一种不同寻常的数字签名算法。通常的签名算法期望以输入数据的加密哈希的形式输入。为清楚起见,输入数据是要签名的数据。相反,EdDSA期望将整个输入数据作为输入,但允许算法的用户提供所谓的预处理功能,该功能将在进一步处理之前应用于输入数据。

预散列函数可以是实际的加密散列函数,如SHA-256。EdDSA的这种变体称为HashEdDSA。HashEdDSA变体与传统签名算法最为相似,因为它将输入数据的哈希值提供给进一步的处理。预散列函数也可以是恒等函数,即只返回其输入的函数。EdDSA的这种变体被称为PureEdDSA

值得注意的是,EdDSA算法在使用Curve25519时内部使用SHA-512哈希函数,在使用Curve448时内部使用SHAKE256函数。因此,在HashEdDSA的情况下,输入数据将被散列两次。那么,是否应该始终使用PureEdDSA?不,没那么容易——请继续阅读。

EdDSA与传统签名算法的另一个区别是EdDSA是一种两遍算法,这意味着它对输入数据进行两次处理。这意味着,与传统的签名算法不同,PureEdDSA不支持流式输入数据。如果输入数据很长,并且来自慢速存储或通过昂贵的网络连接请求,PureEdDSA可能不是最佳选择。

OpenSSL 3.0仅支持EdDSA的PureEdDSA变体,并要求将所有数据放入内存进行哈希运算。如果输入数据很长,内存不足,这可能会有问题。另一方面,即使OpenSSL还不直接支持HashEdDSA,程序员也可以先自己对输入数据进行哈希运算,然后使用PureEdDSA进行进一步处理。

现在,让我们来看看我们将要回顾的最后一个签名算法——SM2。

SM2速览

Shang Mi 2SM2)是另一种基于椭圆曲线的数字签名算法。它是中华人民共和国的国家标准。SM2由王晓云等人发明,并于2012年由中国商用密码管理局(Chinese Commercial Cryptography Administration Office)标准化。

SM2只支持一条曲线:256位CurveSM2。CurveSM2的签名和验证速度与256位Brainpool曲线大致相同。CurveSM2的签名长度为71字节。

SM2签名算法通常与256位SM3哈希函数一起使用。OpenSSL还在其高级API中支持仅将SM2与SM3组合。

正如我们所看到的,OpenSSL支持多种数字签名算法。但你应该选择哪一个?让我们在下一小节中讨论它。

你应该选择哪一种数字签名算法?

最近,EdDSA算法和Curve25519已经变得相当流行。Curve25519似乎受到许多安全专家的信任。如果要签名的数据始终适合内存,并且不需要与旧软件兼容,请选择EdDSA。值得一提的是,在撰写本文时,主要的证书颁发机构Certificate Authorities)尚未颁发基于EdDSA的X.509证书。因此,如果你需要为互联网上的服务器提供TLS证书,你需要暂时选择一个用另一种算法签名的证书。

如果你想要一个更传统的签名算法或想要支持流媒体,那么选择ECDSA,它也很受欢迎。

如果您需要与旧软件的互操作性,或者需要非常快速的签名验证,请选择RSA。

至此,我们了解了OpenSSL支持的不同数字签名算法,以及在哪种情况下使用哪种算法的建议。本章的理论部分到此结束。下一节将是实用的。在那里,我们将学习如何生成EC密钥对,以及如何在命令行上对一些数据进行签名。

如何生成椭圆曲线密钥对

我想演示在对输入数据的消息摘要进行签名时,数字签名的传统方法。因此,在我们的示例中,我们将使用ECDSA,而不是EdDSA。

正如我们所知,可以使用 openssl genpkey 子命令生成新的密钥对。我们将根据NIST B-571曲线生成OpenSSL中可用的最长长度的EC密钥对,570位:

在这里,我们使用了 -pkeyopt ec_paramgen_curve:secp521r1 开关来指定我们要使用NIST B-571曲线。可以使用哪些其他曲线名称来代替 secp521r1 ?使用以下命令可以获得支持曲线的完整列表:

【debian比FreeBSD多了SM2】

生成密钥对并将其写入 ec_keypair.pem 文件后,我们可以检查其结构:

【实操secp521r1得到的私钥是521位,书上的例子用的应该是 sect571r1,大概率是笔误】

正如我们所看到的,EC密钥对的结构比RSA密钥对的架构更简单。这只是两个长值——私钥部分和公钥部分。

从密钥对中提取公钥的方法与我们在【第6章“非对称加密和解密”】中为RSA提取公钥的方式类似:

让我们检查一下提取的公钥的结构:

在这里,我们可以看到公钥包含与密钥对中相同的 pub 值,并且自然不包含 priv 部分。

要进行数字签名sign),您需要一个私钥private key),而要验证verify)签名,您则需要一个公钥public key)。作为提醒,您永远不应该共享您的私钥或密钥对。你应该只分发你的公钥。

正如我们在【第6章“非对称加密和解密”】中所解释的那样,当OpenSSL文档提到私钥时,通常意味着密钥对。此外,没有命令行命令可以从密钥对中提取私有部分。这只能通过了解EC密钥在C代码中的构造方式并获取正确的数据来以编程方式完成。但是从密钥对中提取私钥在实践中没有多大意义,因为当OpenSSL需要私钥时,它可以使用整个密钥对。如果您需要使用OpenSSL在命令行上或以编程方式对某些内容进行签名,只需将整个密钥对提供给OpenSSL即可,无需费心提取私钥。

这样,我们就生成了椭圆曲线密钥对。现在,让我们签署一些东西。

如何在命令行上签名和验证签名

OpenSSL提供了几个用于签名和验证签名的子命令。让我们来看看:

我们将在示例中使用 openssl pkeyutl 子命令。其文档可以在 openssl-pkeyutl 手册页上找到:

按照以下步骤在命令行上签名和验证签名:

  1. 首先,让我们生成一个包含要签名的示例数据的文件:

  2. 现在,让我们使用SHA3-512哈希函数和我们已经生成的EC密钥对来对示例文件进行签名:

  3. 让我们检查一下文件系统:

    此操作的输出是写入 somefile.txt.signature 文件的151字节长的签名。

  4. 现在,如果我们共享原始的 somefile.txt 文件、somefile.txt.signature 中的签名和 ec_public_key.pem 中的公钥,任何人都可以验证签名。这是在命令行上完成的:

    请注意,我们使用 -sign 开关进行签名,使用 -verify 开关进行签名验证。

  5. 如果签名数据在传输过程中被修改了怎么办?让我们通过在签名数据后附加一些数据并再次尝试验证来模拟这种情况:

    正如我们所看到的,在这种情况下,签名验证失败了。我们观察到,如果签名数据保持不变——即签名验证成功,但签名数据已被更改——签名验证过程会检测到它并报告失败。

现在我们已经学习了如何在命令行上对生成的签名进行数字签名和验证,让我们学习如何以编程方式进行。

如何以编程方式签名

OpenSSL 3.0为数字签名提供了以下API:

我们将开发一个小型的 ec-sign 程序,使用ECDSA算法对文件进行签名,类似于我们在上一节中使用 openssl pkeyutl 签名的方式。我们的程序将与 pkeyutl 互操作,这意味着 pkeyutl 将能够验证我们程序生成的签名。

以下是我们将要使用的API的一些相关手册页面:

我们的程序将接受三个命令行参数:

  1. 包含要签名的数据的输入文件的名称
  2. 将写入签名的输出文件的名称
  3. 包含签名密钥对的文件的名称

我们的高层实施计划如下:

  1. 从密钥对文件加载密钥对。
  2. 创建一个用作签名上下文的 EVP_MD_CTX 对象。
  3. 使用哈希函数名和加载的密钥对初始化签名上下文。
  4. 逐块读取要签名的数据,并将其提供给签名上下文。
  5. 发现签名长度并为签名分配内存。
  6. 完成签名并获得签名。
  7. 将签名写入输出签名文件。

现在是时候通过编写必要的代码来实现我们的计划了。

实施ec-sign程序

让我们从实现开始:

  1. 首先,加载密钥对:

  2. 然后,创建 EVP_MD_CTX 签名上下文,并用SHA3-512哈希函数和加载的密钥对初始化它:

  3. 然后,创建签名上下文初始化。让我们将输入数据提供给上下文:

    一旦所有数据都被馈送到上下文中,我们就必须完成签名过程并获得签名。但我们不知道要为签名缓冲区分配多少内存!

  4. 幸运的是,如果我们提供NULL作为签名缓冲区指针,EVP_DigestSignFinal() 函数将为我们提供签名长度:

    请注意,EVP_DigestSignFinal() 不是唯一可以以这种方式指示所需输出缓冲区长度的OpenSSL函数。如果提供的缓冲区指针为 NULL ,许多其他OpenSSL函数也会在提供的长度指针处写入所需的缓冲区长度。

  5. 现在,我们知道签名长度,可以为签名分配内存:

  6. 这样,我们就有了一个存储签名的地方,最终可以得到它:

    请注意,这一次,我们将 sig_buf 而不是 NULL 作为目标签名缓冲区提供给 EVP_DigestSignFinal() 函数。

    如果我们所有要签名的数据都可以加载到一个连续的缓冲区中,我们可以使用一个名为 EVP_DigestSign() 的一次性函数,而不是使用 EVP_DigestSignInit_ex()EVP_DigestonUpdate()EVP_DigesteSignFinal() 函数。我们不必使用ECDSA,但这将是在OpenSSL 3.0中制作PureEdDSA签名的唯一方法。

  7. 现在我们已经得到了签名,让我们将其写入输出文件:

  8. 最后,让我们释放缓冲区和对象以避免内存泄漏:

我们的ec-sign程序的完整源代码可以在GitHub的ec-sign.c文件中找到:https://github.com/PacktPublishing/Demystifying-Cryptography-with-OpenSSL-3/blob/main/Chapter07/ec-sign.c

运行ec-sign程序

让我们运行我们的ec-sign程序,看看它是如何工作的:

据报道,该计划取得了成功。让我们使用 openssl pkeyutl 来检查我们的程序是否生成了良好的签名:

我们的程序能够生成正确的签名,这很好。但更好的是,我们已经学会了如何编写这样的程序。我们的下一个冒险是学习如何以编程方式验证签名。

如何以编程方式验证签名

为了学习如何以编程方式验证签名,让我们开发一个小型的 ec-verify 程序,可以验证 ec-signopenssl pkeyutl 生成的签名。

我们的程序将接受三个命令行参数:

  1. 包含已签名数据的输入文件的名称
  2. 包含签名的文件的名称
  3. 包含验证公钥的文件的名称

以下是我们对该计划的高级实施计划:

  1. 加载签名。
  2. 加载验证公钥。
  3. 创建一个用作验证上下文的 EVP_MD_CTX 对象。
  4. 使用哈希函数的名称和加载的公钥初始化验证上下文。
  5. 逐个读取已签名的数据块,并将其提供给验证上下文。
  6. 完成验证,并找出验证是成功还是失败。

现在是时候实施我们的计划了。

实施ec-verify程序

根据我们的计划,按照以下步骤实施 ec-verify 程序:

  1. 首先,我们必须加载签名。但是,我们不知道要为它分配多少内存。所以,让我们打开签名文件并找出它的长度:

    这种基于查找的文件长度方法可能不是最优的,但它只使用标准的C库,因此非常跨平台。

  2. 现在,当我们知道签名长度时,我们可以为签名分配内存并从签名文件中加载它:

  3. 接下来,加载验证公钥:

  4. 然后,创建验证上下文,并使用SHA3-512哈希函数和加载的公钥对其进行初始化:

  5. 现在验证上下文已经初始化,让我们给它提供签名数据:

  6. 一旦上下文处理了所有签名的数据,我们就可以知道验证状态:

    如果签名验证成功,EVP_DigestVerifyFinal() 函数将返回1。任何其他返回值都表示失败。

    与签名API一样,验证API也有一个名为 EVP_DigestVerify() 的一次性验证函数,该函数不逐块获取数据,但要求将整个签名数据放置在连续的内存块中。从OpenSSL 3.0开始,使用 EVP_DigestVerify() 是验证不支持流式传输的算法(如PureEdDSA)的签名的唯一方法。

  7. 一旦我们从 EVP_DigestVerifyFinal() 函数获得了验证状态,我们就不再需要使用过的缓冲区和对象,可以释放它们:

我们的ec验证程序的完整源代码可以在GitHub的ec verify.c文件中找到:https://github.com/PacktPublishing/Demystifying-Cryptography-with-OpenSSL-3/blob/main/Chapter07/ec-verify.c

运行ec-verify程序

让我们运行 ec-verify 程序,并尝试验证 ec-sigh 程序生成的签名:

很好——我们的 ec-verify 程序已成功验证了有效签名。

让我们尝试更改已签名的数据,并检查验证是否会失败:

正如我们所看到的,我们的 ec-verify 程序可以区分有效签名和无效签名。

本节总结了本章的实践部分。让我们继续进行总结。

总结

在本章中,我们了解了数字签名的概念、它们如何使用消息摘要以及数字签名与消息身份验证码的区别。我们还了解了数字签名提供的加密保证。然后,我们回顾了OpenSSL支持的数字签名算法,讨论了它们的技术优点,并简要介绍了它们的历史。我们完成了理论部分,并就在何种情况下选择哪种数字签名算法提出了建议。

在实践部分,我们学习了如何使用ECDSA签名并在命令行上验证签名。然后,我们学习了如何使用C代码以编程方式对签名进行签名和验证。

在下一章中,我们将学习X.509证书和基于X.509证书的公钥基础设施Public Key InfrastructurePKI)。