openssl哈希
本系列的第一篇文章通过OpenSSL库和命令行实用程序介绍了哈希,加密/解密,数字签名和数字证书。 第二篇文章深入探讨了细节。 让我们从在计算中无处不在的散列开始,并考虑使散列函数成为加密的原因 。
OpenSSL源代码( https://www.openssl.org/source/ )的下载页面包含一个带有最新版本的表。 每个版本都带有两个哈希值:160位SHA1和256位SHA256。 这些值可用于验证下载的文件是否与存储库中的原始文件相匹配:下载器会在本地重新计算下载文件上的哈希值,然后将结果与原始文件进行比较。 现代系统具有用于计算此类哈希的实用程序。 例如,Linux具有md5sum和sha256sum 。 OpenSSL本身提供了类似的命令行实用程序。
哈希用于计算的许多领域。 例如,比特币区块链使用SHA256哈希值作为块标识符。 要挖掘比特币,就是要生成一个低于指定阈值的SHA256哈希值,这意味着哈希值至少具有N个前导零。 (N的值可以上升或下降,具体取决于在特定时间的开采效率。)有趣的是,当今的矿工是旨在并行生成SHA256哈希的硬件集群。 在2018年的高峰时段,全球比特币矿工每秒产生约7500万太赫兹,但这个数字还令人难以理解。
网络协议也使用哈希值(通常以校验和命名)来支持消息完整性。 即,确保接收到的消息与发送的消息相同。 邮件发送者计算邮件的校验和,并将结果与邮件一起发送。 消息到达时,接收方将重新计算校验和。 如果发送的校验和与重新计算的校验和不匹配,则说明正在传输的邮件或发送的校验和或两者均发生了问题。 在这种情况下,应再次发送消息及其校验和,或者至少应提出错误条件。 (诸如UDP之类的低级网络协议不会影响校验和。)
哈希的其他示例是熟悉的。 考虑一个要求用户使用密码进行身份验证的网站,该密码是用户在浏览器中输入的。 然后,通过与服务器的HTTPS连接,将其密码从浏览器发送到服务器并进行加密。 密码到达服务器后,将对其解密以进行数据库表查找。
该查询表应存储什么? 自己存储密码是有风险的。 存储从密码生成的散列的风险要小得多,在计算散列值之前,可能还添加了一些盐 (多余的位)来品尝。 您的密码可能会发送到Web服务器,但是该站点可以向您保证密码不会存储在该服务器上。
散列值也出现在各个安全领域。 例如,基于散列的消息认证代码( HMAC )使用散列值和秘密密钥来认证通过网络发送的消息。 HMAC代码轻巧且易于在程序中使用,在Web服务中很受欢迎。 X509数字证书包含称为指纹的哈希值,可以帮助进行证书验证。 内存中的信任库可以实现为以这种指纹为键的查找表,也可以作为哈希图实现 ,该哈希表支持恒定时间的查找。 可以将传入证书中的指纹与信任库密钥进行比较以进行匹配。
密码哈希函数应具有什么特殊属性? 它应该是单向的 ,这意味着很难反转。 加密散列函数应该相对简单地进行计算,但是计算其反函数(将散列值映射回输入位串的函数)应具有计算上的难点。 这是一个描述,其中chf为加密哈希函数,而我的密码foobar为示例输入:
+---+
foobar—>|chf|—>hash value ## straightforward
+--–+
相比之下,逆运算是不可行的:
+-----------+
hash value—>|chf inverse|—>foobar ## intractable
+-----------+
例如,调用SHA256哈希函数。 对于任何长度为N> 0的输入位串,此函数将生成256位固定长度的哈希值;否则,该值将变为256位。 因此,该哈希值甚至不显示输入位串的长度N,更不用说字符串中每个位的值了。 顺便说一下,SHA256不受长度扩展攻击的影响 。 将计算得出的SHA256哈希值反向工程回输入位串的唯一有效方法是通过蛮力搜索,这意味着尝试所有可能的输入位串,直到找到与目标哈希值匹配的地方。 在诸如SHA256之类的加密密码哈希函数上,这种搜索是不可行的。
现在,最后的审查点已经准备就绪。 密码哈希值在统计上是唯一的,而不是无条件地唯一的,这意味着两个不同的输入位字符串产生相同的哈希值(即冲突 )的可能性很小,但并非不可能。 生日问题提供了一个很好的违反直觉的碰撞示例。 对各种哈希算法的抗冲突性进行了广泛的研究。 例如,在大约2 21个哈希之后,MD5(128位哈希值)的耐冲击性下降。 对于SHA1(160位哈希值),细分从大约2 61个哈希开始。
目前尚无法对SHA256的耐碰撞性进行很好的估算。 这个事实不足为奇。 SHA256的范围为2 256个不同的哈希值,该数字的十进制表示形式高达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
对密码哈希函数的检查使您可以更仔细地查看数字签名及其与密钥对的关系。
顾名思义,数字签名可以附加到文档或其他一些电子产品(例如程序)上以保证其真实性。 因此,这种签名类似于纸质文档上的手写签名。 验证数字签名是要确认两件事。 首先,自附加签名以来,凭证伪像没有改变,因为它部分基于文档的加密哈希 。 其次,签名属于一个人(例如,爱丽丝),该人可以成对访问私钥。 顺便说一下,数字签名代码(源代码或编译后的代码)已成为程序员的一种常见做法。
让我们逐步介绍如何创建数字签名。 如前所述,没有公钥和私钥对就没有数字签名。 使用OpenSSL创建这些密钥时,有两个单独的命令:一个用于创建私钥,另一个用于从私钥中提取匹配的公钥。 这些密钥对以base64编码,可以在此过程中指定其大小。
私钥由数字值组成,其中两个值( 模数和指数 )组成公钥。 虽然私钥文件包含的公共密钥,将提取的公共密钥不泄露对应的私钥的价值。
因此,带有私钥的结果文件包含完整的密钥对。 将公用密钥提取到其自己的文件中是很实际的,因为这两个密钥具有不同的用途,但是这种提取方式还最大程度地减少了可能会意外公开私有密钥的危险。
接下来,使用该对的私钥来处理目标工件(例如电子邮件)的哈希值,从而创建签名。 另一方面,接收者的系统使用该对的公钥来验证附加到工件的签名。
现在举个例子。 首先,使用OpenSSL生成2048位RSA密钥对:
openssl genpkey -out privkey.pem -algorithm rsa 2048
在本示例中,我们可以删除-algorithm rsa标志,因为genpkey缺省为RSA类型。 该文件的名称( privkey.pem )是任意的,但默认情况下,PEM扩展名pem是习惯于默认PEM格式的。 (如果需要,OpenSSL会提供在格式之间进行转换的命令。)如果要使用更大的密钥大小(例如4096),则最后一个参数2048可以更改为4096 。 这些大小始终是2的幂。
这是生成的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=
或者,可以对可执行文件客户端进行签名,并且生成的base64编码签名将与预期的不同:
VMVImPgVLKHxVBapJ8DgLNJUKb98GbXgehRPD8o0ImADhLqlEKVy0HKRm/51m9IX
xRAN7DoL4Q3uuVmWWi749Vampong/uT5qjgVNTnRt9jON112fzchgEoMb8CHNsCT
XIMdyaPtnJZdLALw6rwMM55MoLamSc6M/MV1OrJnk/g=
此过程的最后一步是使用公钥验证数字签名。 用于签名工件的哈希(在这种情况下为可执行的客户端程序)应重新计算为验证中的必要步骤,因为验证过程应指示自签名以来工件是否已更改。
有两个用于此目的的OpenSSL命令。 第一个解码base64签名:
openssl enc -base64 -d -in sign.sha256.base64 -out sign.sha256
第二个验证签名:
openssl dgst -sha256-验证pubkey.pem-签名sign.sha256客户端
第二个命令的输出应为:
Verified OK
要了解验证失败时会发生什么,一个简短但有用的练习是用源文件client.c替换最后一个OpenSSL命令中的可执行客户端文件,然后尝试进行验证。 另一个练习是更改客户端程序,但是要稍做更改,然后重试。
数字证书汇集了到目前为止已分析的部分:哈希值,密钥对,数字签名和加密/解密。 生产级证书的第一步是创建证书签名请求(CSR),然后将其发送到证书颁发机构(CA)。 要在使用OpenSSL的示例中执行此操作,请运行:
openssl req -out myserver.csr -new -newkey rsa:4096 -nodes -keyout myserverkey.pem
本示例生成一个CSR文档,并将该文档存储在文件myserver.csr (base64文本)中。 这里的目的是这样的:CSR文档请求CA担保与指定域名(CA的通用名称(CN))关联的身份。
尽管可以使用现有的密钥对,但此命令也会生成新的密钥对。 请注意,以诸如myserver.csr和myserverkey.pem之类的名称使用服务器暗示了数字证书的典型用法:作为与域(如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握手的身份验证阶段,使用自签名证书即可解决问题,尽管任何现代浏览器都会警告称此类证书毫无用处。 继续该示例,用于自签名证书的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 。
数字证书和用于生成证书的密钥对之间存在重要的对应关系,即使证书只是自签名的也是如此:
模数是一个很大的值,并且出于可读性考虑,可以进行哈希处理。 以下是两个检查相同模数的OpenSSL命令,从而确认数字证书基于PEM文件中的密钥对:
% 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
产生的哈希值匹配,从而确认数字证书基于指定的密钥对。
让我们回到第1部分结尾处提出的问题: 客户端程序和Google Web服务器之间的TLS握手。 握手协议多种多样,甚至在客户端示例中运行的Diffie-Hellman版本都提供了摆动空间。 但是, 客户示例遵循一种通用模式。
首先,在TLS握手期间, 客户端程序和Web服务器会同意一个密码套件,该套件包含要使用的算法。 在这种情况下,套件为ECDHE-RSA-AES128-GCM-SHA256 。
现在,感兴趣的两个元素是RSA密钥对算法和AES128分组密码,如果握手成功,该密码用于加密和解密消息。 关于加密/解密,此过程有两种形式:对称和非对称。 在对称形式中,使用相同的密钥进行加密和解密,这首先引起了密钥分发问题 :如何将密钥安全地分发给双方? 在非对称形式中,一个密钥用于加密(在这种情况下为RSA公钥),而另一密钥用于解密(在这种情况下为同一对中的RSA私钥)。
客户端程序具有来自身份验证证书的Google Web服务器的公钥,而Web服务器具有来自同一对的私钥。 相应地, 客户端程序可以将加密的消息发送到Web服务器,Web服务器单独可以轻松地解密此消息。
在TLS情况下,对称方法具有两个重要优点:
TLS握手以巧妙的方式结合了两种加密/解密方式。 在握手过程中, 客户端程序会生成随机位,称为主主机密(PMS)。 然后, 客户端程序使用服务器的公共密钥对PMS进行加密,然后将加密的PMS发送到服务器,服务器再使用其私钥从RSA对中解密PMS消息:
+-------------------+ encrypted PMS +--------------------+
client PMS--->|server’s public key|--------------->|server’s private key|--->server PMS
+-------------------+ +--------------------+
在此过程结束时, 客户端程序和Google Web服务器现在具有相同的PMS位。 每一端都使用这些位生成主密钥,并以短顺序生成称为会话密钥的对称加密/解密密钥 。 现在有两个不同但完全相同的会话密钥,连接的每一侧都有一个。 在客户端示例中,会话密钥是AES128类型的。 一旦在客户端程序端和Google Web服务器端都生成了会话,则每方的会话密钥都会使双方之间的对话保持机密。 如果一方(例如, 客户端程序)或另一方(在这种情况下,是Google Web服务器)要求重新启动握手,则诸如Diffie-Hellman之类的握手协议允许重复整个PMS过程。
通过底层库的API,也可以使用命令行中所示的OpenSSL操作。 这两篇文章都强调了实用程序,以使示例简短,并专注于加密主题。 如果您对安全性问题感兴趣,那么OpenSSL是一个很好的起点,也是一个值得保留的地方。
翻译自: https://opensource.com/article/19/6/cryptography-basics-openssl-part-2
openssl哈希