网络安全 与 加密算法

计算机中的网络安全

在本篇中介绍了以下几个方面:

  1. 机密性

    • 密码学

    • 对称加密算法(DES, 3DES, AES)

    • 公开秘钥算法

      • RSA大素数的获取
  2. 完整性

    • 散列函数(MD5, SHA-1, 并没有提及算法实现)

    • 报文鉴别(MAC)

    • 数字签名

  3. 端点鉴别

  4. 应用

    • SSL(TCP网络安全)
  5. 运行时安全

    • 防火墙的基本知识

主要体现在以下几个方面:

  1. 机密性, 即发送的信息只有双方彼此能够解读,其他人以任何方式皆无法解读。

  2. 报文完整性, 即接收方需要能够验证,当前接收到的数据是完整的,没有被经过篡改的。机密性与完整性是相互独立的两个属性。

  3. 端点鉴别, 需要知道我收到的消息, 确确实实是来自于对方, 而不是恶意方伪装的。

  4. 运行性安全, 需要能够识别并阻拦恶意攻击。如 Dos攻击等, 其目的并非为了窃取信息,而是使得系统瘫痪, 无法运行。

机密性

首先来看机密性问题, 小明和小红之间发送的有效信息 不希望被第三方解读。 而实现这点的方式自然是, 加密, 加密,就需要相关的密码。

密码学

加密所要实现的根本目的是,将数据加密,除非拥有相关的秘钥,算法,才能够 也 必须能够 将数据恢复到 原始数据。

这里的原始数据被称作明文, 小明使用了加密算法加密其明文, 生成的文本为密文, 加密算法是公知的,而秘钥是私有的.

对称秘钥体系
  • 凯撒密码 是一种简单的加密算法。

    对于字母而言, 约定 用 当前字母的后 第K个字母加以替换, 即可生成对应的密文。

    如: a b c k=2-> c d e

    只需要双方约定好共同的K, 即可进行加密解密。

  • 更进一步 则是将 k=2 换成只有双方知道的算法, 如 k = index + 1, 即 k=3

    但缺点同样明显, 文本自身并不是没有特点的, 如小明和小红在沟通的时候常常会在开头加入对方的姓名, xiao hong,

    那么经过加密之后,变成了 zkcq jqpi, 则根据习惯来推算, 很容易就得出来 K = 2.

  • 即使是它的改进算法:

    单码代替密码, 也难以规避掉这个问题。

    单码代替密码不再使用固定的K, 而是有 密码本 将 明文一一映射替换为 密文, 如 a->h b->k c->a, 双方都持有相应的密码本,即可完成加解密工作。

    但是依然是之前的 xiao hong 作为惯用语, 随着接收到的密文不断变大,变多,则能够逐步推算, 还原出对应的密码本。 完成解密工作。

  • 多码代替密码

    这是对单码代替密码的一种改进, 对于明文中 文本 位置的不同 采用不同的 密码本进行匹配。

    而秘钥呢? 则是 多个密码本, 以及 位置 和 密码本之间的匹配关系。

对称加密在现代社会有两种宽泛的类型: 流密码, 块密码。

我们会观察到这样两个问题:

  1. 在上面提到的几种简单的文本加密算法中, 仅仅能够对文本进行加密, 而我们在网络中传输 可不一定是文本,更大的可能性是流。 包括协议的首部, 数据报本身。我们所需要加密的对象不仅仅是文本, 而在网络中 自然更为合适的是对 数据流进行加密。 这并不意味着是 流密码。

  2. 可匹配的可能性依然太少, 适用性不够强。

而块密码, 正是前面几种简单密码的延伸。

我们将数据流进行拆分 3 6 64 128 等等 bit 为一个块, 然后对每一个块都进行加密。

注意到 对 3 bit块进行加密。 可能会映射到的结果有: 000 001 010 011 100 101 110 111 即 2^3 8中可能性。

而我们的输入同样有8种可能性, 因此会映射为 8! = 40320 种可能的结果。 即对每一种输入都找到其映射结果, 且与其他输入的结果不重复。

40320 种可能的结果, 对现代计算机来说破解起来实在是一件很轻易的事情。而为了防止被破解, 因此往往会采用更大的块 做为映射, 比如说 64位, 这样我们的可能结果就有 (2^64)! 想要破解无疑是一件很困难的事情。

不仅仅是破解起来很是困难,实现起来也是同样的困难,需要双方维护一个 2^64 的输入输出映射表, 如果需要改变映射表, 也需要双方都进行调整才可以。

而真实采用的方式,则是 使用函数来模拟 随机排列表, 一个简单的例子是:将 64bit拆分成 8 * 8 bit, 每次用一个 8bit的映射表,去置换第一个块, 将置换后的结果 与 剩余部分混合, 再循环置换 剩下的 bit块,经过n次循环以后, 就提供了一个64bit的块, 这种算法的秘钥是 8张排列表。

DES算法

DES是一个分组加密算法,典型的DES以64位为分组对数据加密,加密和解密用的是同一个算法。

密钥长64位,密钥事实上是56位参与DES运算(第8、16、24、32、40、48、56、64位是校验位,使得每个密钥都有奇数个1),分组后的明文组和56位的密钥按位替代或交换的方法形成密文组。

需要注意的地方是, 如果初始秘钥转换成 byte之后, 如果其前7位是相同的, 那么这两个秘钥所计算出来的数据就是相同的。 因为 8 16 等等 是不参与DES运算的。

将初始秘钥 通过16轮的计算,转换, 生成16个子秘钥(子秘钥长度为48位)。

而后将明文数据:

首先按照固定的置换规则, 将原数据中的64bit 进行置换。 得到新的数据。

其中置换后的数据被分为两部分, L0、R0, L0是输出的左32位,R0是右32位

按照一定的规则 迭代置换 运用之前计算得到的 秘钥 加密, 16轮之后, 即得到所需的L16, R16。将两部分合并成一个分组,进行逆置换,逆置换正好是初始置换的逆运算,由此即得到密文输出。

其初始置换数据:

58,50,42,34,26,18,10,2,

60,52,44,36,28,20,12,4,

62,54,46,38,30,22,14,6,

64,56,48,40,32,24,16,8,

57,49,41,33,25,17,9,1,

59,51,43,35,27,19,11,3,

61,53,45,37,29,21,13,5,

63,55,47,39,31,23,15,7,

逆置换:

在初始置换规则表中, 观察会发现, 原本第一位的被挪到了第40位, 而在逆置换中, 需要将第四十位置换到第一位即可。

40,8,48,16,56,24,64,32,

39,7,47,15,55,23,63,31,

38,6,46,14,54,22,62,30,

37,5,45,13,53,21,61,29,

36,4,44,12,52,20,60,28,

35,3,43,11,51,19,59,27,

34,2,42,10,50,18,58 26,

33,1,41, 9,49,17,57,25,

正如上面所提到的:核心就是 置换,混合。 解密则是其逆过程。

3DES

3DES(即Triple DES)是DES向AES过渡的加密算法,它使用2条56位的密钥对数据进行三次加密。是DES的一个更安全的变形。

设Ek()和Dk()代表DES算法的加密和解密过程,K代表DES算法使用的密钥,M代表明文,C代表密文

3DES加密过程为:C=Ek3(Dk2(Ek1(M)))

3DES解密过程为:M=Dk1(EK2(Dk3(C)))

即用 k1对数据加密 k2对加密过的数据解密 k3 对解密之后的数据再度加密.

K1、K2、K3决定了算法的安全性,若三个密钥互不相同,本质上就相当于用一个长为168位的密钥进行加密。多年来,它在对付强力攻击时是比较安全的。若数据对安全性要求不那么高,K1可以等于K3。在这种情况下,密钥的有效长度为112位。

不难发现 如果 k1=k2 的话, 与 DES算法是等价的, 这样有效的实现了与现有DES系统的向后兼容问题。因为当K1=K2时,三重DES的效果就和原来的DES一样,有助于逐渐推广三重DES。

AES
  1. 运算速度快,在有反馈模式、无反馈模式的软硬件中,Rijndael都表现出非常好的性能。
  2. 对内存的需求非常低,适合于受限环境。
  3. Rijndael 是一个分组迭代密码, 分组长度和密钥长度设计灵活。
  4. AES标准支持可变分组长度,分组长度可设定为32 比特的任意倍数,最小值为128 比特,最大值为256 比特。
  5. AES的密钥长度比DES大, 它也可设定为32 比特的任意倍数,最小值为12 比特,最大值为256 比特,所以用穷举法是不可能破解的。
  6. AES算法的设计策略是WTS。WTS 是针对差分分析和线性分析提出的,可对抗差分密码分析和线性密码分析。

而AES的优点, 正是 DES的缺点所在。

AES的具体实现,就不再这里提到了, 感兴趣的可以看参考链接。 在本篇中, 更关注 其核心思想是什么, 适用范围在何处,局限性又有哪些。

而 DES 3DES AES 其核心都是 分组, 置换, 加密混淆, 迭代。 这样一套处理体系。

用密码生成函数 替换了 原本固定的密码表。

密码块链接

需要注意到的是, 即使我们已经拥有了很强大的对称加密算法,但这就能够保证不被破解了吗?

假设我们认为暴力破解是不可行的, 那么在已知部分明文的情况下, 破解出密码并不是不可能的事情, 因为在加密算法中, 我们对于同一数据的加密结果总是相同的。而在 http传输中, HTTP/1.1 这必然是一个很常见的明文输入,我们匹配了所有的输入, 发现出现次数最多的 就可以代表其实 HTTP/1.1

考虑这样一种情况, 在两个人的通讯过程中,其中一个人的惯用词汇是 你猜, 几乎在100句聊天中, 就会出现80句以上的你猜。

那么我们只要抓取到 出现次数最多的密文, 必然就可以认定, 这个密文所对应的明文是 你猜。这样就能够大大减小我们破解密码的难度。

那么自然的, 就要求存在这样的方式, 使得对于相同的输入, 其加密后的密文 是不相同的。 即使小明说了一百句你猜。 对于其表现形式来说,其密文都是不同的。

因此,根本无法从杂乱无章的密文中, 判断出来究竟哪一句对应的明文是 小明的口头禅, 你猜。

这怎么实现呢?无非是在每次传输都附带一串数据,仅用于本数据的加密。 这依赖的是 异或 运算。

CBC

CBC模式的全称是Cipher Block Chaining模式(密文分组链接模式),之所以叫这个名字,是因为密文分组像链条一样相互连接在一起。

明文: Mi, 密文 Ci, 加密算法: Ks, 随机的k 比特数: Ri, 异或运算的结果: Ti 异或运算: a⊕b

首先需要知道的是: 对于异或运算:

  1. a ⊕ a = 0
  2. a ⊕ b = b ⊕ a
  3. a ⊕b ⊕ c = a ⊕ (b ⊕ c) = (a ⊕ b) ⊕ c
  4. d = a ⊕ b ⊕ c 可以推出 a = d ⊕ b ⊕ c
  5. a ⊕ b ⊕ a = b

对于密文: Ci = Ks(Mi ⊕ Ri), 即将Mi 与 Ri执行异或运算, 通过Ks加密算法对其进行加密, 最终得到密文 Ci。

对于解密: 根据第四条运算法则: d = a ⊕ b ⊕ c 可以推出 a = d ⊕ b ⊕ c

解密后的数据: Ti = Mi ⊕ Ri 则有 Mi = Ti ⊕ Ri, 即对密文进行解密 与 Ri做异或运算, 即可得到初始明文 Mi。

这就要求 我们对 每一个块(在对称加密算法中, 正是将数据分成一个又一个的块), 都发送相应的Ri 用于处理数据。 Ri可以明文发送, 即使Ri被劫持到, 在没有秘钥的情况下, 依然是没有用的。

但这种方式同样会带来一个缺点,即对于每一个块, 我们都需要发送等长的Ri给对方(异或预算必然要求两者长度一样,才能进行异或运算。) 这样我们的数据长度就变为原来的两倍, 这实在是一笔不小的开销。

因此通常采用的方式是CBC, 其基本思想是, 仅随第一个报文发送一个随机值, 在之后的通讯中 通过计算获取 相应的编码。

因此,仅需要N+1个块, 就能够实现 对相同的明文,产生不同的密文。

公开秘钥加密

但在对称秘钥体系中, 会有这样一个问题, 如果一切依托于现实, 我们可以彼此约定一个只有双方知道的秘钥, 完成加密解密的相关工作。

但在网络通讯中, 这并不现实, TCP链接时时刻刻都在被销毁与创建中不断循环, 我们不可能在路由器两端使用固定的密码, 如果这样做的话 就意味着线路两端的路由器都要完成解密 然后加密的操作。 用自己的密码加密之后 发送给下一个节点。

我们也不可能将密码直接明文 在 通讯中直接传输, 那样的话, 和不加密又有什么区别呢?

这好像是个难题。

直到公开密码的出现:

是以这样的方式使用:

对于小B来说, 存在两个秘钥, 一个是 公知的 Kb+ 以及 只有自己存有的秘钥 Kb-, 当小A想要给小B发送消息时, 首先会取得 Kb+ 用一个公知的 加密算法 进行加密,得到密文 Kb+(m), 而当小B接收到 Kb+(m) 时, 用一个公知的 解密算法 配以私钥 Kb- 进行解密, 最终使得 Kb-(Kb+(m)) = m。 这样需要解密密文, 就必须要两个秘钥才能够解密。

虽然这样会引入一个新的问题, 即 任何人都可以 截获 发送给小B的消息, 然后用自己准备好的明文, 通过 Kb+对明文加密, 发送给小B。

这就要求我们能够验证数据的来源方究竟是谁, 需要做端点鉴别, 即确确实实是小A自身发送给小B的消息, 而不是别人冒用名义 发送 分手吧 这样的悲惨消息。

RSA算法

对于RSA算法, 个人比较感兴趣, 在这里描述的可能就会多一点。

对于任何数据流, 我们都不能忘记其本质 依然是 bit流, 而任何bit流 自然也能够用 唯一的 整数 去表示。

加密这个bit流 自然实际上就是加密这个 唯一数值。

在RSA算法中, 并没有将数据看成是 字符的集合, 而是数值, 这样我们就可以使用种种数学公式加以匹配。

RSA包含两个部分:

加密解密算法

两个秘钥, 公钥和私钥。

秘钥的生成
  1. 选择两个大素数, 这个值越大越好, 甚至已经超出了常规意义上的 大数值。 该值越大, 破解RSA算法越困难。 推荐公司使用时, p q 两个数的乘积 需要 为 1024bit数量级。

  2. 计算 n=pq 和 z=(p - 1)(q - 1)

  3. 选择小于n的一个数值 e, 且 e 和 z 互素。

  4. 求解一个数d, 使得 ed - 1 可以被 z整除。换成数值表达则是:

    ed mod z = 1

  5. 则公钥是 (n, e), 私钥是 (n, d);

加解密过程
  1. 要求被加密的整数 m, m < n
  2. 求解 c = (m ^ e) mod n, c即是加密后的密文
  3. 求解 m = (c ^ d) mod n.

看上去实在是很简单的过程。

但是 我们需要注意到的是这样几个问题, p q 本身就是数百bit的值。
被加密的整数一般也都不会非常小。

需要计算 m ^ e c ^ d, 这种种操作本身就是非常消耗计算力的过程。

更何况还需要求得大素数q p 本身。

工作原理

将上述 加解密过程 合并起来看:

求解m时 即是求解: ((m^e) mod n)^d mod n

在模运算中有这样一个性质:

a ^ b mod n = ((a mod n)^b) mod n 

则上述公式可以变为:

m^ed mod n

因此现在就需要证明:

m ^ ed mod n = m

在数论中, 有:

如果 p q 是素数,且有 n = pq 和 z = (p -1) * (q - 1) 则 X^y mod n = X ^ (y mod z) mod n

因此上述被替换成:

m ^ (ed mod z) mod n => m ^ 1 mod n => (m 小于 n) m

同时如果注意到另一个特点:

如果加解密流程微调:

c = (m ^ d) mod n

m = (m ^ e) mod n

原因则是: m ^ ed mod n 等价于 m ^ de mod n

这个特性意味着什么呢?

意味着: 如果小B用自己的 私钥 加密数据,那么 小A 可以用 小B的公钥进行解密, 得到原始数据。 暂时这个功能好像还看不出来什么作用, 我们稍后再提。

RSA的大素数选择

这同样是一个难点. 如果不感兴趣可以跳过.

RSA算法的核心正在于 找到两个大素数 p q 并且这个数值越大越好, 破解的难度就越高。

我们要将两个数相乘 很简单, 但是因式分解, 确定它是素数 又是一件很难的事情了。

惯用 也是最简单的方法, 正是采用试除法进行处理, 即从 2 开始逐个测试, 但目前而言, 我们需要的数值有数百位, 仅仅64位的long型 9223372036854775807 已经是这样大的一个数值, 更何况数百位?

素性测试
  1. 费马小定理:

    如果p是一个质数,而整数a不是p的倍数,则有a^(p-1)≡1(mod p)

    即: a的(p-1)次方除以p的余数恒等于1。

    费马小定理是判定一个数是否为素数的必要条件,并非充分条件,因为存在着一些伪素数满足费马小定理却不是素数.

    也就是说, 如果一个数是素数, 那么必然满足费马小定理. 换句话说, 如果存在一个数, 不满足费马小定理, 此时可以断定这个数必然不是素数.

  2. Fermat 素性检验

    则考虑a=2, 我们可以将选定的数用 a=2做检验, 如果判断不满足 费马小定理. 此时必然不是素数, 比起试除法要快很多。

    但遗憾的是,仅能通过不满足来检验不是素数, 如果满足我们也没办法确定他是素数.

    因此继续考虑a=3的情况,一个合数可能在a=2时通过了测试,但a=3时的计算结果却排除了素数的可能。于是,人们扩展了伪素数的定义,称满足 a^(n-1) mod n = 1 的合数n叫做以a为底的伪素数(pseudoprime to base a)。

    前10亿个自然数中同时以2和3为底的伪素数只有1272个,这个数目不到刚才的1/4。

    这告诉我们如果同时验证a=2和a=3两种情况,算法出错的概率降到了0.000025。

    容易想到,选择用来测试的a越多,算法越准确。通常我们的做法是,随机选择若干个小于待测数的正整数作为底数a进行若干次测试,只要有一次没有通过测试就可以判定这个数为合数。这就是Fermat素性测试。

    人们自然会想,如果考虑了所有小于n的底数a,出错的概率是否就可以降到0呢?遗憾的是,居然就有这样的合数,它可以通过所有a(前提是n与a互素)的测试。Carmichael第一个发现这样极端的伪素数,他把它们称作Carmichael数。前10亿个自然数中Carmichael数也有600个之多。这样高的出错率, 说明费马素性检验 依然不能够帮我们准确找到一个大素数. 依然需要加强算法.

  3. Miller-Rabin素性测试

    基于下面的定理:

    如果p是素数,x是小于p的正整数,且x^2 mod p = 1,那么要么x=1,要么x=p-1。这是显然的,因为x^2 mod p = 1相当于p能整除x^2-1,也即p能整除(x+1)(x-1)。由于p是素数,那么只可能是x-1能被p整除(此时x=1)或x+1能被p整除(此时 x=p-1)。

    以 a2,n341为例,演示一下该测试是如何进行的。(2^340%341==1,但是341并不是一个质数)

    根据模运算的规则: a^b mod n = (a mod n)^b mod n

    2 ^ 340 mod 341 = (2^170 mod 341)^2 mod 341 == 1

    此时必然有: 2^170 mod 341 == 1 或 2^170 mod 341 = 340 而 2^170 mod 341 结果是等于 1的

    继续: 要求满足: 2^85 mod 341==1 或者 2^85 mod 341 == 340 但此时 两者都不满足, 所以341不是素数。

    所以测试的要点则是: 尽可能提取因子2, 把n-1表示成 d * 2 ^ r, 如果n是一个素数,那么或者a ^ d mod n=1,或者存在某个i使得a ^ (d * 2 ^ i) mod n = n - 1.

    但需要注意的是: Miller-Rabin 素性测试同样是不确定算法,我们把可以通过以a为底的Miller-Rabin测试的合数称作以a为底的强伪素数(strong pseudoprime)。第一个以2为底的强伪素数为2047。第一个以2和3为底的强伪素数则大到1373653。

    对 于大数的素性判断,目前Miller-Rabin算法应用最广泛。一般底数仍然是随机选取,但当待测数不太大时,选择测试底数就有一些技巧了。比如,如果 被测数小于4 759 123 141,那么只需要测试三个底数2, 7和61就足够了。当然,你测试的越多,正确的范围肯定也越大。如果你每次都用前7个素数(2, 3, 5, 7, 11, 13和17)进行测试,所有不超过341 550 071 728 320的数都是正确的。如果选用2, 3, 7, 61和24251作为底数,那么10^16内唯一的强伪素数为46 856 248 255 981。这样的一些结论使得Miller-Rabin算法在OI中非常实用。通常认为,Miller-Rabin素性测试的正确率可以令人接受,随机选取 k个底数进行测试算法的失误率大概为4^(-k)。

而 Miller-Rabin素性测试 也就是我们的终极法宝。

那么大素数是怎么生成的?

其实生成一个大素数非常简单,最直观的方法就是随机搜索,例如要生成一个100位的大素数,我们先随机生成一个数字序列,然后用Miller-Rabin素性测试对其进行测试即可,如果不是素数的话再随机生成一个,如此循环下去~

当然我们可以采用随机搜索法(每次生成一个完全不一样的随机数),也可以采用随机递增搜索法(生成一个随机数之后,每次对其加2)

生成一个n位十进制大素数的步骤如下:

  1. 产生一个n位的随机数p,且最高位不能为0
  2. 若最低位为偶数,则将它加1,保证该数为奇数以节省时间
  3. 测试该数能否被10000以下的素数(共1228个)整除,这样可以快速排除许多合数,节省时间
  4. 在2到p-1这间随机生成一个数a,以a为底对p进行Miller-Rabin素性测试,若不通过说明p为合数。若通过则再选取一个a对p进行测试。选取a时应该选取尽可能小的素数,以提高运算速度。大概进行5次Miller-Rabin素性测试后,精确性就比较高了
  5. 若p每次测试都通过,则认为p是素数。否则p←p+2,再次对p进行测试
会话秘钥

我们已经看到了,对于对称密码而言,密码本身的发送是一个问题,再者对待千千万万不同的用户, 我们需要不同的密码去应对每一个请求。

而RSA算法,加密起来确实无人可解, 但是运算太耗费时间, 需要的计算量是 对称秘钥算法的 成百上千倍。

那么,我们有没有一个比较好的处理办法能够解决这些问题呢?

当然是将两者结合起来使用了哇。

我们在一个会话刚刚开始时, 通过 RSA算法 加密 秘钥, 将秘钥发送给对方, 在之后的会话过程中, 使用对称秘钥进行会话即可。

完整性

需要验证的是 数据未被修改过。

这就表示 对于我们的 明文, 需要有对应的数据 能够 验证, 确确实实没有被变更.

密码散列函数

也即哈希函数, hash相信每个开发人员都不陌生, 但在这里 我们对于hash的要求会更高一点:

要求对于任意两个不同的报文, H(x) == H(y) 的概率 几乎为0

这一点也就保证了 对于任意报文而言,通过相同的hash函数,我们几乎不可能找到相同的报文。

MD5正是这样这样一个hash 函数.

SHA SHA-1 也是起到同样的作用。

需要注意到的一点是: MD5, SHA-1 并不能够被称作是加密算法。

就我个人理解来说: 凡是被加密的数据,那么必然能够还原回初始版本, 也即 解密, 而hash函数无疑是不具备这个特点的。

我们想要破解 hash函数 进行还原的唯一办法是,穷举法, 对撞。 因为对于密码散列函数而言,对于不同的文本其hash值几乎是完全不同的。那么反过来说, 对于散列值必然有其唯一对应的 原始文本。

报文鉴别

对于单纯的hash来说, 如果存在第三方,截获了 A发送的消息, 同时自己伪造了新的消息,并加入了新的散列值。伪装成A将消息发送给B。

对方收到了消息,傻傻的以为A绝情的要分手。

那么我们就需要一种办法, 验证消息确确实实是 A发送的, 而不是来源于别人。

密令就是一个不错的方式, 当地下党接头时, 可能也采用的是原始的对暗号的方式来确定对方的身份。

假定双方已经持有一个 共享秘钥s, 当A发送数据时, 通过 明文 H(m + s) 生成哈希值, 当B收到数据时, 自身已经持有s, 因此可以同样通过 H(m + s) 验证 数据是否是对方发送的, 也能够验证数据的完整性。

而 H(m + s) 就被成为是 报文鉴别码 (MAC).

但这就要求双方都已经持有共享秘钥, 在部分场景, 如 在路由器 链路选择算法中, 可以由网络管理员配置秘钥, 以确定数据的来源.

而在服务器和个人交互中呢? 我们如何确定共享秘钥?

哎? 前面不是提到过会话秘钥吗, 这个应该就可以吧. 那么在双方的初次交互过程中, 又如何确定会话的对象是 你理想中的 服务器呢? 在初次交流中, 已经伪装成了 知心哥哥, 将秘钥发给你, 无碍交流, 最终才发现这是又一次残忍的欺骗.

数字签名

看来MAC已经没有办法帮助我们阻挡坏人了. 那么该怎么办呢?

带数字签名的数据报文 是面向大众的, 就意味着任何一个人都可能要验证数据签名的准确性(这也意味着, 只要通过认证, 那么我就可以确定你是我心目中的那个人.), 因此其秘钥是周知的, 但既然是周知的, 就意味着可能会有第三方用同样的方式, 用秘钥生成签名, 做以伪装, 且无法鉴别.

这就依赖一个很有趣的特性了.

还记得在RSA 公开秘钥算法原理中的一个特性吗?

c = (m ^ e) mod n

m = (m ^ d) mod n;

c = (m ^ d) mod n

m = (m ^ e) mod n;

这两种方式是等价的吗? 都能够求出来最终的明文.

在需要进行数字签名时, 即用A的私钥签署明文 m, 这样唯有在明文没有被篡改的情况下, 持有公钥即可验证 报文的 源, 以及报文完整性.

但仅仅是这样还不够好, 我们知道RSA计算对资源的消耗时相当大的, 当数据越多 消耗自然也就越大.

因此, 此时加密我们并非加密明文, 而是 加密明文对应的 散列值. 这样 加密所需要的计算量会大大减小.

公钥认证

就数字签名的形成还有问题吗?

有.

问题在于公钥, 我们如何得到对方的 可信的 公钥?

如果不能够确定公钥的准确性, 那么依然会存在第三方伪造信息的可能性.

这就需要一个第三方权威机构来做这件事情, 将公钥与某一机构绑定 通常是由 CA(Certificate Authority)完成. 而一旦CA认证了某个实体的身份, 就会生成一个相应的证书, 证书中包含了 公钥 以及 实体的唯一标识.

端点鉴别

也即如何证明你是你, 我是我的一个过程.

最简单的方式, 莫过于告诉你, 我是谁, 然后你就傻傻的相信就好.

如果我们是熟悉的陌生人, 那么我去验证数据源所属的IP地址 是否是你的地址也不失为一种确定 你是你的 方式. 这种问题在于, IP地址是可以伪造的.

更近一步的方式, 我们不是有秘钥吗? 先传输秘钥, 然后再...

等等, 秘钥怎么传输? 如果是会话秘钥, 通过RSA传输的先决条件是, 对方持有你的 公钥. 如果明文传输, emmm 太可怕了还是不要想.

那么我们通过CA获取 公钥不就可以了么? 双方都经过认证. 这样就一定可以确定 双方身份了吧.

NONONO!道高一尺, 魔高一丈.

有一种攻击手法叫 回放. 今天你转了 200块给我, 虽然数据已经加密, 身份已经确定, 此时 第三方 偷偷记录你们的交流信息, 虽然都看不懂, 无法解读, 但这不重要, 在第二天, 按照顺序将数据原原本本的再发送一遍. 在服务器端看来, 一切都很正常, 对数据的加密, 解读 都完美无误, 然而, 你会惊讶的发现, 有人偷了你的两百块.

不重数是解决上述问题的一个好办法. 不重数是在一个协议生存周期只 使用一次的数值. 当双方开始通信, 通过 RSA加密会话秘钥, 使用会话秘钥开始通信的时候, 首先 服务器发送 明文 不重数 给客户端, 客户端使用会话秘钥 加密 再发送回来. 服务端进行验证. 确定对方是活跃状态.

SSL

网络安全的技术手段, 从 机密性 完整性 到 端点鉴别都已经有所涉猎.

而现在几乎已经成为浏览器强制标准的 HTTPS 也正是这种种手段的综合运用. 也即SSL技术.

SSL(Secure Sockets Layer 安全套接层),及其继任者传输层安全(Transport Layer Security, TLS)是为网络通信提供安全及数据完整性的一种安全协议.

那么久以上三部分来看, 如果不使用SSL会出现什么问题呢?

在没有机密性的前提下, 账号密码 这一类的东西只能够通过明文传输, 如果有人获取到了你向服务器提交的请求, 那么你的账号安全问题就会受到威胁.

不要说base64, md5等算法. 避免不了核心问题. 在网络传输中, 只需要了解你的明文是多少, 而不需要知道明文的真正含义, 就已经可以完成种种不可告人的目的了.

缺失了完整性, 报文会被别人修改, 重新发送给对方, 比如你在淘宝上本来买了一颗苹果, 最终却收到了一台洗衣机. 毕竟获取你的 RSA的公钥是可被获取的.

缺失了端点鉴别, 很有可能别人会利用重放攻击, 向服务器再次模拟你的请求流程, 重复购买你已经买过的东西.

SSL是处在传输层的服务, 因此它能够使得任何基于TCP的服务安全性得到保障.

宏观描述

握手

  1. 建立TCP链接
  2. 在TCP链接建立以后, 客户端向服务器发送hello报文, 服务器用其证书进行响应, 证书中有其公钥. 验证服务器的真实性.
  3. 发送给服务器主秘钥, 即用 服务器的公钥进行加密, 将秘钥发送给服务器, 在以后的通话中即使用会话秘钥.

密钥导出

但密钥并非只用一个, 对于MAC秘钥, 会话密钥, 从服务器到客户端, 以及从客户端到服务器,采用的是不同的秘钥.

传输

但仅仅这样就能够使得传输不出现问题了吗? 并不是, 如果存在中间人, 仅仅是在传输的过程中对次序进行颠倒, 调整. 这样在服务器端检验是发现不了任何问题的.

我们知道TCP报文是有序号的, 这也不能够防止乱序吗?

但需要知道的地方是, 加密的仅仅是从应用层传输下来的数据报, 而TCP自身的序号仍然是未经过加密的, 因此只需要修改TCP序号即可完成.

而处理的办法则是, 并非在数据中实际的包含一个字段, 而是在计算MAC的时候, 将序号纳入数据 进行计算.

SSL记录

  1. 类型, 用于指定该字段是握手报文, 还是包含应用数据的报文, 关闭SSL链接也使用此报文
  2. 版本, 自解释
  3. 长度
  4. 数据
  5. MAC (其中4, 5 会整体进行加密)

完整描述

在SSL中并没有强制性的要求双方究竟使用哪一种加密算法, 无论是对称秘钥算法还是公开秘钥算法, 又或者是某一种特定的MAC.

真正的握手阶段如下:

  1. 客户端发送它自身支持的密码算法列表, 连同客户的一个不重数.

  2. 服务器选择算法, 即 MAC, 公开密钥算法, 对称算法. 将选择, 自身证书, 自身的不重数返回给客户.

    由于双向认证需求,服务端需要对客户端进行认证,会同时发送一个 client certificate request, 表示请求客户端的证书;

  3. 客户端验证证书, 提取公钥, 生成一个前主秘钥(PMS), 之后用 公钥加密PMS, 将PMS发送给服务器.

  4. 使用相同的秘钥导出函数, 客户端与服务器独立地从 PMS中计算出 相应的 主秘钥(MS),
    之后该MS被切片生成所需要的四个密码(两个会话秘钥, 两个MAC秘钥)

    由于服务端发起了 client certificate request, 客户端将户端的证书 clientCA一并发出;

  5. 客户端发送所有握手报文的MAC

  6. 服务器发送所有握手报文的MAC

而之所以在56步要做这样的处理 是因为, 在发送密码算法列表的时候, 此时双方还没有生成 交换密码, 是以明文发送的, 如果第三方从其中 删除掉了 较强的 加密算法, 就能够达成自身不可告人的目的. 大大减低破解难度.

  1. 结束SSL连接时, 在TCP原版中, 是直接发送 TCP FIN即可结束当前会话. 而在SSL中, 加入了类型字段, 指示当前类型为 结束会话, 虽然 类型本身为明文发送, 但是在 计算MAC时, 会使用类型计算MAC.

扩展:

通过以上介绍, 很容易联想到一个问题, 即我们常常会使用的git, 使用git时即需要通过 生成自己的公钥, 私钥, 同时将公钥保存在git服务器中, 以完成我们的提交工作. 这里即使 会话秘钥, 非对称公开秘钥的联合使用. 总算不用对着 git的操作流程, 一脸懵逼.

网络安全的保障

有这样多可以被攻击的手段, 并且在之前仅仅只是提到了截取数据, 修改报文等操作, 还有更多的攻击, 并不想要窃取你的数据, 各种各样的病毒, 通过计算机的种种漏洞侵入到你的设备, 控制电脑. 又或是攻击服务器, 致使服务器瘫痪.

因此, 需要能够对进入计算机的任何外来数据都做以校验, 拦截, 才能够有效阻止邪恶*的入侵.

防火墙

所谓“防火墙”是指一种将 内部网和公众访问网(如Internet) 分开的方法,它实际上是一种建立在现代通信网络技术和信息安全技术基础上的应用性安全技术,隔离技术。越来越多地应用于专用网络与公用网络的互联环境之中,尤其以接入Internet网络为最甚。

防火墙代主要是借助 硬件和软件 的作用于内部和外部网络的环境间产生一种保护的屏障,从而实现对计算机不安全网络因素的阻断。

防火墙是在两个网络通讯时执行的一种访问控制尺度,能最大限度阻止网络中的黑客访问你的网络。是指设置在不同网络(如可信任的企业内部网和不可信的公共网)或网络安全域之间的一系列部件的组合。它是不同网络或网络安全域之间信息的唯一出入口,能根据企业的安全政策控制(允许、拒绝、监测)出入网络的信息流,且本身具有较强的抗攻击能力。它是提供信息安全服务,实现网络和信息安全的基础设施。

防火墙具有三个目标:

  1. 从外部到内部, 以及从内向外的所有流量都需要通过防火墙

  2. 仅允许被授权的流量通过防火墙

  3. 防火墙自身需要免于渗透. 最坚固的堡垒往往是从内部被攻破.

防火墙分为三类:

传统的分组过滤器

一个机构往往有将其内部网络 与 ISP相关联的网关路由器, 所有的流量无论进出都需要通过这个服务器, 因此它也自然承载了 分组过滤的 功能.

过滤规则一般有如下几种方式:

  • Ip源地址, 目的地址
  • 协议类型字段: TCP UDP
  • TCP UDP 源, 目的端口
  • TCP 的标志bit, SYN, ACK
  • ICMP报文类型
  • 进出网络采取不同的规则
  • 不同的路由器采取不同的规则
状态分组过滤器

这是基于 TCP连接的相关知识进行拦截, 防火墙能够观测TCP连接的三次握手, 将通过三次握手的连接记录在一张表 , 如果收到了 TCP FIN 或 超过 60s未活跃, 则认为当前连接已经结束, 从表中删除.

正是通过这种方式, 阻拦所有的外部恶意请求, 即通过外界主动向内发送数据的. 因为此时必然不在表中.

应用程序网关

如果需要进行内部用户受限的操作又该如何呢?

在前面所提到的两种策略中, 并不能够探测到应用层的数据, 即不能够对特定应用的 特定用户 进行区分限流.

应用程序网关, 是特定应用程序的服务器, 所有应用程序相关的数据都必须通过它. 如telnet服务器, 所有用户在向外界访问时, 必须输入自身的用户名密码, 如果校验不通过, 即不能够进行访问. 这正是因为 应用程序网关将自身当做服务器, 读取了来自客户端的数据, 从而决定是否拥有访问权限, 当校验通过后, 又将自身当做客户端, 向真正的服务器发起请求, 获取数据.

而缺点也是比较明显的, 每个应用程序都需要对应的网关服务器, 其次所有的数据都需要代为转发, 当用户 和 应用程序数量一旦上来, 对性能是个较大的考验. 最后 , 不仅是客户需要知道如何连接到网关服务器, 服务器也需要知道 如何连接到哪一个外部服务器.

在防火墙的描述中, 我们会发现依然有局限性, 并不能够在执行分组过滤的同时 检测应用层数据, 否则就需要应用程网关来作为中介.

我们需要一种能够结合两者特点的, 且并非只是针对特定的应用程序.

当设备观测到可疑流量, 可疑分组时, 需要向网络管理员发出警告. 能够观测到潜在恶意流量时产生告警的设备 被称为 入侵检测系统(IDS), 滤除可疑流量的设备被称为 入侵防止系统(IPS).

至于更多的就不多做介绍.

总结

从最简单的代替密码. 当我们将密码本进一步扩展, 且根据文本所处的不同位置采用不同的密码本 就成了多码代替, 进一步扩展, 我们将密码本换成了 初始密码 与 密码函数的组合 就变成了 对称秘钥的基本雏形. 但对称密码不利于在网络传输中直接使用, 因此 公开秘钥体系应运而生.

相应的, 为了验证报文的完整性, 就需要 MD5, SHA-1这样算法 来将报文转换成 对应的 hash值. 验证文本与hash值的一致性即可. 由于存在可能性是将整个报文直接替换, 而非修改 生成新的 报文与hash值, 因此需要将 报文与共享秘钥 合并 生成MAC 加以验证数据源.

但这依然不够, 对于服务器需要面对的群体对象实在太多, 很难实现和每个人都共享对应的秘钥, 倒不如将自身的公钥公开, 通过私钥加密报文的hash值, 就变成了数字签名, 客户端只需要通过权威机构获取其证书, 即可解析公钥, 完成解密验证过程.

通过同样的方式, 我们也能够完成 验证 你是你(即端点鉴别)的这样一个过程.

为了防止别人窃取到服务器与客户端的聊天记录, 在第二天进行回放操作, 因此又有了不重数做以补充.

然而我们在一段会话中 不可能全部使用 RSA进行加密, 代价太过高昂, 因此通过RSA加密一段 对称秘钥, 用于会话中. 这即是 会话秘钥.

而SSL SSH等 则是对上述种种技术的综合运用.

防火墙的主要功用是, 通过分组过滤, 分组检测等功能, 阻止恶意入侵.

网络安全中心最重要的几个板块.

机密性, 完整性, 端点鉴别, 运行时安全 即如是.

上一篇:利用 GitHub 和 Hexo 搭建个人博客【保姆教程】


下一篇:.NET 9 的新增功能