密码学科普

密码学含义

作为一个个体,你总是有很多不想让别人知道的秘密。本来吧,不说就不会有人知道,但是你又特别特别想和特定人分享……

作为一个群体的成员,你希望在这个群体内的交流不泄漏给不属于这个群体的人……

可是存在于这个世界上的一种生物——熊孩子粉碎了你的梦想……他们千方百计窃听你们的交流……

当你使用纸笔交流时,熊孩子们不仅可以冒充你写信给别人,还可以截获你的信进行修改……

于是你不能忍了!你要在熊孩子成为你的记忆副本之前阻止他们!

密码学就是研究如何在不安全环境中通信的科学。通过密码学,你可以仰天狂啸一堆乱码,而只有你的那个TA,才能读懂你想表达的含义~

你的原文称作明文,说出去的乱码称作密文,从明文到密文的过程称为加密,反之称为解密。显然,加密过程必须是可逆的。

TIPS: 密码学能做的是将明文尽可能的隐藏在密文之中,只是使你和某人通信的内容对第三方来说不可读,但不能隐藏你和某人通信的事实。

举例:你打算将自己某网站的登录密码存在电脑里。两种方案:
1) 你将自己的登录密码存放在文件 password.txt 中,对其进行加密。这样没有解密能力的人就无法读取你的登录密码,但是所有人都知道你把自己登录密码放在文件 password.txt 中!(密码学)
2) 你将自己的登录密码通过一定的变换嵌入到你的桌面壁纸当中而不影响壁纸的显示。没有人知道你把自己登录密码存在电脑里!(信息隐藏)

加密算法的实质

从数学上说,加密是一个函数。定义域即明文集合,值域即密文集合。由于所有的明文加密后都必须能被解密还原,所以这个函数一定有反函数(用来解密),因此该函数可逆(亦即一个明文唯一对应一个密文)。因此明文集合和密文集合必须是等大的。

e.g. 明文集合 = 密文集合 = {00, 01, 10, 11},定义加密函数 f: f(00) = 01, f(01) = 11, f(10) = 10, f(11) = 00,解密函数是其逆。

这就是一个 2 bits 的加密算法。

那么,没错,密码学的重点就是研究这个函数变换 f ,使其具有相当强度的复杂性。

试想:函数 y = x + 1 与 分段函数 y = x^2 (0 ≤ x < 1), y=x ^3 (1 ≤ x < 2), y=x ^4 (2 ≤ x < 3), y=x ^5 (x ≥ 3) (定义域都是 [0, +∞) ),告诉你 10 个数和这 10 个数分别经过上述俩函数变换后的结果,哪个函数更容易被猜出解析式?

密钥的出现

上段说到设计一个加密算法并不是件容易事。于是新的问题又出现了:我需要和两个人通信,但我不想他们任意一个看到我和另一个人通信的内容,因此不能使用相同的算法,那么我就得设计两个算法。可是智商只够设计一个怎么办?

试想世界上任意两个人之间的通信都使用不同的加密算法,需要的加密算法个数是一个二次幂增长函数。于是人类从出生到死亡就为了设计一个加密算法……么?

实际使用中发现,尽管是不同的算法,有些却有很大的相似性。例如一个加密算法是 y = x ,另外一个加密算法是 y = 2x ,还有一个是 y = 3x 。那么可不可以设计这样一个算法 f(k, x), x 是明文, k 是一个可配置的参数 。通过修改 k ,“生成”一个新的算法 g(x) = f(k, x) 呢?(对上例来说就是 y = kx)

显然,这样做明显减少了需要设计的算法个数,而且可以把类似算法的优点集中,数量少而质量上乘的加密算法还解决了大量算法安全性参差不齐的问题。这个 k ,即被称作密钥。

密码系统安全性应只依赖密钥

再做一个假设,如果一个密码系统只依赖算法的秘密性是否安全呢?一旦算法泄漏,就必须重新设计新算法。如果依赖密钥的秘密性,一旦泄漏,就得更新一个密钥。相比之下,在泄漏事件发生后,依赖密钥秘密性的系统付出的代价低很多。

那么,既然如此,一个密码系统只依赖密钥的秘密性是否安全呢?(Kerckhoffs’ Principle)

当破译密钥的代价超出密文信息的价值或有效生命期时,该密码系统是安全的。

由于更换密钥的代价实在太低,使用者甚至可以主动废弃当前密钥转而使用另一个。这大大增加了攻击难度。至少,攻击者不知道密钥什么时候更换的也就不知道某条密文究竟是用哪个密钥加密的。

因此,只依赖密钥的秘密性的密码系统理应是安全的。

现今的密码系统多遵循 Kerckhoffs’ Principle 即密钥安全则密码系统安全。

公开密码算法

得出以上结论后,顺水推舟就可以想到,既然密码算法的秘密性已经不重要了,那么干脆公开如何?

闭源密码算法不知道内部除了加解密还干了什么,使用者不放心。

公开密码算法可以使全世界的学者对其进行研究,经受很大的考验。如此还能存活于世的密码算法,会更加被信任。

因此,民用密码算法都是公开的。(军用的密码算法还是保密的)

分组密码

计算机上全是二进制数据,无论是明文还是密文都是一大波二进制。因为计算机一次能处理的数据总是有限的,所以无论是处理 1 B,还是 1 GB,计算机每次都只处理其中一部分。

对一个密码算法而言,接受固定长度的明文与接受不定长的明文进行加密,前者设计难度更小。再结合计算机还没有强悍到能一次性处理任意长度的数据,因此有了分组密码的概念。

分组密码的思想是将明文按指定长度分割成多个块,对每一个块进行加密,然后把密文连起来。

显然分割过程会涉及到最后一块达不到指定长度(加密算法只接受指定长度的输入),此时就涉及到分组密码填充算法(例如 PKCS#5 填充 ,PKCS#7 填充)。

回到最开始介绍加密算法的实质,那里讲了加密算法是一个可逆函数对吧?长度为 n bits 的明文集合有 2^n 个元素,对应的密文集合也有这么多,需要至少 n bits 来存放密文。因此分组加密的明文和密文长度是一致的。

再者,假设长度为 1 bit ,无论多么厉害的加密算法,在相同密钥的情况下,最终变换要么是 f(0) = 0, f(1) = 1 ,要么是 f(0) = 1, f(1) = 0 。因此可以想到分组的长度越小分组密码就越不安全。但是分组很大的时候频繁加密很小的数据块会很浪费。怎么办呢?

流密码

流密码将分组缩小到 1 bit ,即每次加密的数据单元是 1 bit ,又称按位加密。

流密码的加密算法很简单,把明文和“密钥”进行一次异或就得到密文了。再异或一次恢复明文。

重点是:流密码会将原用户密钥作为一个伪随机数产生器的输入,产生出的随机数依次作为每次加密(异或)的“密钥”。

因此流密码的算法安全性依赖于这个伪随机数产生器。

散列函数的零知识证明

散列函数不仅保证了消息完整性,还做到了一个新功能——不可逆“加密”。

不可逆的“加密”显然不是用来加密的,而是用来进行零知识证明的。

什么是零知识证明?简单来说就是 我向你证明我拥有密码,但你至始至终不知道该密码。

零知识的好处嘛~面对黑阔大大的威逼利诱~某采用零知识证明的授权服务器就算把持不住意志屈服也交不出用户密码……它压根就不知道啊……

没错,服务器能认证用户但又不存储用户的密码,实现这一点的方案是——存储密码的散列值。

每次用户登录时,其密码会通过一个散列函数,输出的散列值和授权服务器存储的密码散列值进行比较,从而判断用户是真是假。

TIPS: 综上,服务器不推荐使用明文存储用户密码!

在全过程明文传输的 HTTP 协议中,仍然有很多漏洞。这和使用零知识证明无关。解决方案是使用 HTTPS 协议。

消息认证码

黑客们面对从各大服务器上脱下来的数据库中那层层叠叠的散列值密码,使用了极度猥琐的方法——彩虹表!

彩虹表是存放 数据 和 其散列值 对应关系的表。例如 MD5 的彩虹表就记录着 “a” 和 MD5(“a”), “b” 和 MD5(“b”) 等等。既然我从散列值推不回去,又找不到碰撞,那我就把全部可能的密码对应的散列值都记下来!从里面反向查找好了!(网上什么 MD5 破解/解密 的其后台都是维护了这样一张表)

(这和暴力破解没什么区别…………)

如括号里所说,这算不上什么破解方法。然而这是一个潜在隐患。假设某段通信的文字是“Good Morning!”,使用散列函数来保证其完整性,如果彩虹表里有一条和这条消息散列值一样的记录呢?超级无敌至尊加密算法都拦不住别人大胆猜想这段通信文字就是“Good Morning!”……

从某密码学教材的顺序来看,消息认证码这个概念应该是先于散列函数出现的(散列函数是消息认证码的变种)。消息认证码的目的就是保证数据完整性。

没有散列函数,尝试通过分组密码的 CBC 工作模式对明文进行加密,CBC 具有加密错误传播无界的特性,即明文任意一个点发生转变,自此以后的密文全部都会变化。

将最后一块明文对应的密文作为消息认证码。如果明文任意一位发生错误,那么在用此法重新计算消息认证码的时候根据如上所述特性,计算结果与发送方附带的消息认证码必然大相径庭。

这种方式即为消息认证码中的 CBC-MAC(密文分组链接消息认证码)。消息认证码国际标准 ISO/IEC 9797-1 MAC Algorithm 1 的算法。当其分组密码算法为 DES 时,还是美国联邦资料处理标准 FIPS PUB 113 的算法。

TIPS: 此方法在消息长度不固定时不安全,已被各种变种算法如 CMAC 取代。

掺合了密钥后,同一明文也能产生各种不一样的消息认证码了,有没有安全性一下子直冲云霄的感觉?

在消息认证码标准 ISO/IEC 9797 中,9797-1 定义了如何使用分组密码来构造消息认证码,而 9797-2 则定义了如何使用带密钥的散列函数来构造消息认证码。

有了散列函数,结合密钥和散列算法的消息认证码称作 HMAC (Hash-based Message Authentication Code) 。

HMAC 在 RFC 2104 中定义。具体流程见Hash-based message authentication code – Wikipedia。这里只说明一点,HMAC 中没有使用任何加密算法。密钥只是以某种形式和明文共同作为散列函数的输入。

公钥密码与数字签名

公钥密码体系是密码学历史上的一次革命,也许可以说是迄今为止唯一的一次。在此之前的传统密码,也即之前讨论的分组密码、流密码以及古典密码,无不是通过代换和置换的交替迭代来实现加密。公钥密码是为解决两个传统密码中最困难的问题而提出的。

密钥分配问题:通信双方必须事先持有一个共享的密钥用于加解密,这怎么想都不现实。而且传统密码没法解决。

签名问题:密码学除了用于军事,还可用于商业和个人目的。那么像现实中的签名一样,电子文件也需要一种签名的手段,以此证明某消息、文件是出自某个特定的人,并且各方对此无异议,该人也不可抵赖。传统密码无法做到这点,因为密钥是共享的,任何一条用共享密钥加密的消息都可能出自双方任意一方之手,而双方都可以尽情抵赖消息出自自己。

1976 年,Diffie 和 Hellman(DH 密钥交换算法的作者)针对上述两个问题公开提出了一种异于此前四千多年的方法——分离加密密钥与解密密钥。

此前的加密方法,加密和解密统统使用同一密钥。如果能够分离加密和解密的密钥,那么可以想象……

每个人都可以持有一对加密密钥及其对应的解密密钥,把解密密钥私藏,加密密钥公开。

任何人可以用某人的加密密钥加密消息,但只有持有解密密钥的该人能够解读。

根本上解决了密钥分配难题,通信双方只需简单交换双方的加密密钥即可开始通信。

由于解密密钥私有,可以利用其制作签名(加密密钥无法制作或制作的无效),加密密钥进行验证,可以初步实现签名的效果。

将加密密钥(公开的)称作公钥,解密密钥(私有的)称作私钥。

显然,公钥和私钥是数学相关的,并且应该很难通过公钥推算出私钥。(由于数学相关,公钥和私钥一定可以根据这个相关性相互推出。)为了寻求这一点,需要使用一种叫做单向函数的数学物质。单向函数是一个单射函数(任意函数值都仅对应一个自变量),给出一个自变量,计算函数值很容易(P 问题);然而给出一个函数值,计算自变量很困难(NP 问题)。

(这个小框里的内容仅供学习过算法设计的读者浏览,如果没有学习过算法相关知识可以无视上文中的“P 问题”与“NP 问题”)

单向函数是否存在 和 计算复杂度理论中 P ≠ NP 是否成立 息息相关。如果前者成立,那么可以证明后者成立。

换言之,如果能够证明 P = NP ,那么单向函数就不存在,依赖于单向函数的公钥密码体系也即告破解。

RSA 算法使用的单向函数是 素数的乘积 。给出两个素数,求其乘积很容易;相对的,给出乘积,分解出两个素数,极其困难。

DH, DSA 算法基于的数学难题都是 有限域上的离散对数问题。(f(x) = a^x mod b ,在有限域上计算,常数 a, b 和选取的有限域有关)

ECDH, ECDSA, ElGamal 算法都是基于 椭圆曲线上的离散对数问题(比有限域上的更难)(函数同上,在椭圆曲线上计算,常数 a, b 和选取的椭圆曲线有关)。

以上算法的具体原理都可以在维基百科上查阅到。

想了解有限域与椭圆曲线,您需要熟悉抽象代数。

为区分公钥密码与分组密码、流密码,将分组密码、流密码归纳为对称密码,即加解密密钥相同;公钥密码又称为非对称密码。

公钥密码有三个用途:密钥交换,加/解密,数字签名。

由于公钥密码的数学特性,其加/解密速度比传统密码低很多,因此,通常采取使用公钥密码的密钥交换,使通信双方获得一个共享密钥,然后使用分组加密、流密码等。

并非每一种公钥密码算法都能用于上述 3 个目的,例如 DH, ECDH 仅能用于密钥交换,DSA, ECDSA 仅能用于数字签名,RSA 全能,ElGamal 能用于加/解密。(ElGamal 似乎也是全能,作者没找到相关资料)

限制其使用目的的是算法本身的实现,亦即对单向函数的利用,在清晰的了解算法流程后就会理解啦。

RSA 全能是因为其数学原理决定了它的一个性质:既可以用公钥加密、私钥解密,又可以用私钥加密、公钥解密。在加/解密中,用公钥加密私钥解密;在数字签名中,对要签名的消息用私钥加密,如果公众能用指定公钥解开,那么就能认定这是来源于该公/私钥持有者的消息。而一旦可以实现加/解密,就能完成密钥交换:把双方的公钥扔给对方,然后就可以加密通讯了,协商个密钥不是难事吧。只是使用密钥交换就是因为加/解密速度慢,通过加/解密来进行密钥交换怪怪的~

PKI(公钥基础设施)

擅于思考的读者一定会发现上述全部内容都有一个巨大的漏洞——即无法确信通信对方的身份。

例如:若使用对称密码,任何一个持有密钥的人(无论是合法持有还是偷或抢)都可以宣称他是你的原通信目标;若使用非对称密码,任何有自己公钥私钥对的人都可以宣称这个公钥就是你的原通信目标。

PKI 借助数字证书认证机构(Certificate Authority,CA)将使用者的真实身份和一对公/私钥绑定在一起。

重点:权威 CA 是每个人默认信任的!所有人都信任 CA 分发的公/私钥以及 CA 对拥有这对密码的人的真实身份认定。

我相信你在用 Windows ,来来,按下 Win + R ,打开“运行”窗口,输入 certmgr.msc ,回车。点开 受信任的根证书颁发机构->证书 ,里面全部是你默认信任的 CA 机构!这些默认信任的 CA 机构都是随 Windows 安装而附加上的。所有由他们分配的公/私钥都能得到明确的身份鉴定,因此你也可以信任他们。

双击列表中任何一个证书,就可以看到简略的身份说明,点击 详细信息 ,在 字段 里找 公钥 。剩下的不用废话了……(别问我私钥在哪!)

证书只是一种凭证格式。你看到的数字证书都是遵循 X.509 标准的身份凭证。与此同类的还有 PGP 协议,也可以用来表示个人身份,只是不够权威,使用范围不及 X.509 了。

如果你想玩 X.509 ,正式向权威 CA 申请一个证书价格不菲的!!不过自己搭个 CA (没有权威性),颁发几个证书还是可以做到的。请下载 OpenSSL ,使用命令 openssl ca / openssl x509 。

如果你想玩 PGP ,请下载 GnuPG (默认你是 Linux/Unix 环境),使用命令 gpg 。

如果你只是想尝鲜亲自玩玩加密解密签名验证什么的,请下载 OpenSSL ,想玩 AES 就用 openssl aes ,想玩 RSA 就用 openssl rsa ……

常见算法清单

分组密码:Rijndael(128, 128/192/256, AES), DES(64, 56), 3DES(64, 56/112/168), Serpent(128, 128/192/256), Blowfish(64, 32-448), Twofish(128, 128/192/256)…

流密码:RC4, 分组密码的 OFB 模式等, …

散列函数:MD5(128), SHA-1(160), SHA-2(SHA-224, SHA-256, SHA-384, SHA-512), SHA-3(SHA-3-224, SHA-3-256, SHA-3-384, SHA-3-512), …

公钥密码:RSA(1024-4096 typical), DSA(1024), EC(ECDH, ECDSA, …), …

说明:(单位未注明均为 位(bit) )

分组密码括号里标注的是块大小,密钥的长度;(例 128, 128/192/256 表示 块大小 128 位,密钥长度可以是 128, 192, 256 位)

散列函数括号里标注的是散列输出的长度,SHA-2 和 SHA-3 都是一个算法家族,括号里的算法都是对应的家族成员,算法名已经标注出了散列输出的长度

END