前言
随着资讯安全越来越受到用户的重视,密码学作为资讯安全的基石也逐渐变成计算机从业人员的必修课,所以我今天要说密码学中大家很容易混搞的三个东西:编码(Encode)、加密(Encrypt)跟凑(Hash)
虽然他们三者的比较已经很多人写完了,但还是有些人搞不明白,所以这篇决定换个方式:不提太多理论,而是举了大量的例子跟实际应用,如果这些例子你都知道了看懂了,那自然就会理解三者之间的差别。
编码(Encoding)
首先来看看最简单的编码,所谓的编码并不会修改数据、也没有任何加密的效果,简单就是换算方式来表达下面的数据,其中最有名的例子就是摩斯密码
在摩斯密码的编码系统中,每个英文字母都可以表示成点(点)跟划(破折号)的组合,或者表示成长音跟短音,比喻如说将 Hello World 编码(encode)组成摩斯密码的形式就是.... . .-.. .-.. --- .-- --- .-. .-.. -..
虽然这串东西乍一看很难懂,但只要知道摩斯密码的转换规则就能翻译回来,所以编码完全没有安全性可言,就只是换一种方式来表达数据而已
实例
1. encodeURI()和 decoderURI()
在 JavaScript 中有两个很实用的函数分别是encodeURI跟decodeURI,它们是用来把网址中的特殊字元(空格、标点符号等等)编码成符合URL的格式(转完变超丑的)
比喻比如假设 https://www.google.com/search?q=創世神 网址会被编码成 https://www.google.com/search?q=%E6%96%B0%E4%BA%AB%E5%AE%8F%E7%A6%8F,所以升级电脑不支持显示中文,或者某些比较旧的浏览器无法贴中文网址,也或者可以读取编码后的网址连到目标网站
中文 “新享宏福” 会被转换为 UTF-8 编码的十六进制形式,每个字节前加 %:
“新” → UTF-8 编码为 E6 96 B0 → 编码后 %E6%96%B0
“享” → UTF-8 编码为 E4 BA AB → 编码后 %E4%BA%AB
“宏” → UTF-8 编码为 E5 AE 8F → 编码后 %E5%AE%8F
“福” → UTF-8 编码为 E7%A6%8F → 编码后 %E7%A6%8F
2.Base64
Base64 是一种可以把二进位的数据编码成 ASCII 字元的方法,如果你想在纯文字的介面传送图片给朋友,可以先用指令 cat hello.png | base64 把图片编码成 Base64 格式,把生出来的这个大串然后把东西传给他
你的朋友收到这团看起来很乱码的东西后,只要用 Base64 进行解码就可以收到具体的图片了:echo {那一大串} | base64 -D > image.png
为了让编码后的那一大串能显示在一个画面中,我用的图片画质很差,请不要介意
虽然这团东西看起来像乱码,但是他的安全性确实是不够的,“防君子不防小人”,假装很安全类似于 kubernetes 中的 secret ......
3.霍夫曼编码
霍夫曼编码(Huffman Coding)是一种用于进行无失真压缩的编码演算法,说穿了他的概念就是把常用的字记组成缩写,从而降低数据量、达到压缩的效果
如果还是觉得有点抽象的话,可以想想你去早餐店跟阿姨点餐时:
我要“一个鸡腿堡加蛋”跟“大杯的冰红茶”
因为阿姨脑子里有一个早餐缩写的对照表,所以在你讲的同时阿姨就会快速在纸上记下“G堡蛋、大冰红”,这就是压缩技术的原理:把常出现的字用缩写记录下来,等日后要解压缩时再还原回去
加密(加密)
加密跟刚说的编码有点像,唯一不同的地方是加密跟解密必须讲金钥(密钥)才能进行。以最简单的凯萨加密法来说,他加密的方式就是把每个英文字母加上一个偏移量,这个偏移量就是用来执行加解密的密钥
假设我想要用凯萨加密法对Hello World进行加密,并且设置key=3,那加密完成的结果就会变 Khoor Zruog,只有知道 key 的人才有办法把 Khoor Zruoog 还原恢复回来 Hello World
说是这么说,但你我都知道英文字母不就那 26 个吗?照目前电脑的攻击速度,只要把偏移量从 0 到 25 都算过一遍,就算我不知道键也可以很容易就看出偏移量是 3
所以说凯萨加密法非常不安全,他最大的用处就是应该在课堂上传纸条,但又不想被中间的同学看懂,除此之外在现代已经没什么用处了
实例
1. AES
AES(先进的加密标准)是一种加密算法,所谓的加密算法就是说加密解密都是用同一个密钥,这和上面说的凯萨加密法一样,但是 AES 不像凯萨的密钥只有 0-25 这么少种,而是可以有超过 10³⁸ 种,所以安全性比凯萨非常非常多
除了安全性高不易被破解之外,AES 加密文件的速度也非常快,所以被美国政府用来加密机密文件。如果你也想用 AES 来加密 D盘 里那些神秘文件,尝试可以系统内建的 OpenSSL
加密:openssl aes-256-cbc -in -out ,输入密码(Key)加密后会变成一团丑不拉叽的乱码,完全不知道从何破解起,不像凯萨加密完成还是英文字母,一下子就猜到了
要解密时就同样的指令加个-d,轻轻松松
但如果你不小心忘记密码,那就真的 gg 了,因为 AES 加密后的文件加密用超级电脑来破解,也要超过十亿年,更不用说用你的电脑了
简易式加密法的缺点
AES 又快又安全,听起来超厉害对吧?但他这种相似式加密法有一个缺点:就是如果你想把加密后的文字/文件传给你的秘密情人爱丽丝,两个人就必须先讲好密码是什么,这样你传过去她才用你设置的密码解密
但因为网路环境是不安全的(尤其是在有 HTTPS 之前),所以除非你把 Alice 当面约出来讲密码是什么,否则只要是透过网路把密码传给对方,就有机会被中间人拿走
一旦中间人拿到了密码,那你用再强的加密算法都没有用。而且林没完全加密,是更恐怖的明明密码已经被偷走了,你还自以为档案有加密就超安全,真的怎么死的都不知道
2. RSA
为了解决上述的问题,于是出现了非对称加密算法,其中最著名的就是 RSA
RSA这类非对称加密法有一个很特别的地方,就是他会产生一组两个密钥分别称为公钥(Public Key)跟私钥(Private Key),并且用公钥加密的内容只能用私钥解,接下来框体内的内容请选修!
RSA 是应用最广泛的非对称加密算法之一,其密钥生成基于 “大整数分解质因数” 的数学难题(即:已知大整数n,很难分解出它的两个质因数p和q)。
生成步骤:
随机选择两个大质数:p和q(质数越大,安全性越高,实际应用中通常是 1024 位、2048 位甚至更长的二进制数)。
计算n:n = p × q(n是公钥和私钥的共同部分,称为 “模”)。
计算欧拉函数 φ(n):φ(n) = (p-1) × (q-1)(欧拉函数 φ(n) 表示小于n且与n互质的整数个数)。
选择公钥指数e:
满足两个条件:① 1 < e < φ(n);② e与φ(n)互质(即e和φ(n)的最大公约数为 1,记为gcd(e, φ(n)) = 1)。
实际中常选e = 65537(0x10001),这是一个公认的安全且计算高效的数值。
计算私钥指数d:
d是e的 “模逆元”,即满足 (e × d) ≡ 1 mod φ(n)(意思是e×d除以φ(n)的余数为 1)。
模逆元可通过 “扩展欧几里得算法” 计算。
最终:
公钥 = (e, n)(可公开)
私钥 = (d, n)(需保密,实际中还可能包含p、q等信息用于加速运算)
当你要传档案给 Alice 的情况,就先请 Alice 生一组密钥然后把公钥传给你。你有了同样的 Alice 产生的公钥之后,就用公钥传档案加密再传给她, Alice 收到就用她自己手上的私钥可以解开
安全性方面,因为私钥从头到尾都在 Alice 手上,没有完全传出去,所以中间人取得公钥加密后的档案也没办法解开
杂凑(Hashing)
接下来是:杂凑,不知道大家有没有注意过你的身体分证字号,他的最后一码其实是个验证码哦,他是前九码根据某种公式计算出来的
以 M140051653 这个假设身份证号来说,计算的方式是先把 M 转换成对应的数字 21,然后按照下图的方式进行计算,如果算出来正好等于最后一个码,那就代表这个身体分证字号是合法的
这样把各个位栏/字元丢进去某个公式计算的方式就叫杂凑(哈希),而这个计算公式就叫杂凑函数(哈希函数),过程可能会做各种加减乘除,最后算出一个值或字串
因为最后一个数字 3 是分散前几个数字计算、浓缩出来的,所以理所当然不可能由杂凑后的结果 3 回推出前几个数字分别是什么,所以杂凑的过程是不可逆的
实例
1.判断文件内容是否相同
如果你想验证某两个文件的内容是否相同,可以使用 md5 来计算文件的杂凑值,他会使用某个散列函数根据文件内容计算出一个长度 128 位的杂凑值,如果两个文件内容不同就会计算出不同的散列值
实际上有很低的机率(1/2^2⁸)会产生碰撞,造成不同文件有相同的哈希值,但因为机率太小一般情况下可以忽略不计
如上图,从杂凑值可以看出 data1 跟 data2 的杂凑值是相同的,代表他们的文件内容有很多机率一样,而 data2 跟 data3 的杂凑值不同,代表他们的内容一定不一样
2.验证档案是否被窜改
如果你开发过前端的话应该会发现,在你要使用 bootstrap 或 jquery 这类第三方库时,官方都会提供一个完整性属性,那就是库程序码经过 SHA-2 算法哈希的结果
这样子做有一个好处,因为使用 bootstrap 的网站太多了,如果有天很不幸官方提供的 bootstrap.js 网址被窃客偷换成恶意程序,就会有很多网站遭受殃
但是后面有了 hash,因为不同的程序码会导致不同的 hash 值,所以浏览器看到就不会加载怪怪的 bootstrap.js,只是因为他的hash结果跟知道的不一样
3.验证用户密码
大多数网站的云端资料库都不会直接储存用户的密码,因为那样太危险了,取而代之的是储存密码的杂凑值
当用户尝试要登入时,只要将用户输入的密码进行杂凑,再跟资料库里面的杂凑值比就可以了。只要输入的密码是正确的,用同一个公式进行计算一定会得到相同的结果
安全性方面,因为杂凑值无法反推回原密码,所以甚至有一天资料库不小心遗漏了,窃客还是拿不到用户的密码,只能得到密码的杂凑值
TOTP 2FA
身为一名 IT 从业者,为了提高安全性,应该都会帮自己还有公司的帐号启用2FA(二次认证),否则帐号被盗是真的会出大事。
像去年出现的一个案例是,npm 上有一个火爆的包名叫 UAParser.js 用来解析用户代理是什么类型,每个礼拜的下载次数都接近一千万。但去年作者的账号被盗了之后,厄客在 npm 热门上发布了好几个挖矿程序的版本,造成不小轰动,还好很快就找到了并把那些版本重新掉了。
所以说密码真的要保管好,还有重要的服务像 Github、Google、Facebook 的帐号最好都上 2FA,才不会 Facebook 被盗,连带用 Facebook 登入的Instagram、虾皮也被盗,那处理起来真的很麻烦
双因素认证
今天的主题是 2FA(二要素认证)。简单介绍一下,说我们都是使用帐号密码来验证身份,而 2FA 就是除了建立帐号密码之外的第二个关卡。就算帐密不小心外泄了,也不至于马上被盗用。而 2FA 又可以分为硬体跟软体两种形式
硬体型 2FA
像在 App Store 上买 App 时需要指纹,或者前阵子很红的 YubiKey,只有那只随身带硬盘在身上才能通过验证,都说明硬体型的 2FA。
软件类型 2FA
今天要讲的 OTP(One - Time Password )就是软体式的 2FA。像大部分银行在 App 转帐时都会要求你输入「短信验证码」、或者当你要从新电脑登入Google 时,也需要到手机上打开Gmail看验证码,这些都还不错。
因为这些验证码每次都不一样,所以我们把它称为一次性一次性的密码,每次都不一样自然安全性也比较高。
所以像这样用手机收简讯验证码有什么不好吗?
虽然简讯验证码每次都是随机的,但其实简讯在传输的过程中非常不安全。除开简讯不谈,即使验证码是用App、电子邮件来传输,只要每次你登入时,提供服务者试图把验证码「传」给你,那就有遭窃的可能。
透過簡訊執行二次驗證不再安全,美國國家標準技術研究所建議別再使用
如果我登入时对方不把验证码「传」给我,那我要怎么拿到验证码?
虽然听起来很神奇,但确实是可以靠密码学做到的哦,那就是今天要介绍的主角 TOTP
基于时间的一次性密码
TOTP 的全名是基于时间的一次性密码,看名字就知道他也是 OTP 的一种,只不过是由目前的时间来产生的 OTP,因此天生就随机性。因为直接看公式太硬了,干燥我用 Github 2FA 的例子来讲解他的运作原理
如果你的 Github 还没有实现 2FA 的话,可以跟着官方文件先设置好,等等看看下面的讲解才知道每个步骤在做什么哦~
步骤 1 — 服务器生成带有密钥的 QRCode
通常要实现 TOTP 2FA 的第一步,会让你扫描一个很大的 QRCode,而这个QRCode 中就带有服务器想要传给你的秘钥
如果把上图的 QRCode 解码出来会得到 otpauth://totp/GitHub:LarryLuTW?secret=X5CTBOMEYE3TXIIS 这个串东西鬼,意思是这个串 URI 是由 Github 发布的,而后面的secret=X5CTBOMEYE3TXIIS 就是 Github 想要传给你的秘钥
因此当你用手机上的 Authentication App 去扫描时(我是用 Authy,还有很多其他 App 可以选择),App 就会把这个字符串秘密存起来,以后就用这个秘密生成 TOTP
第 2 步 — 验证双方生成相同的 TOTP
有这串 secret 后面 App 要怎么生出下面六码 OTP 呢?答案就是把 secret跟现在时间去行动杂凑,然后取最后的六码作为 OTP
比喻比如我现在时间换算成 UNIX 时间戳是 1654994758(从 1970年1月1号,已经过了这么多秒),然后秘钥是 X5CTBOMEYE3TXIIS
因为 App 希望每 30 秒产生出不同的 OTP,所以他把这 timestamp/30 两个 X5CTBOMEYE3TXIIS 东西丢进去 HMAC 做杂凑,杂凑的结果转十进位取最后六码,就可以得到这 30 秒的 OTP 了 017201~
而 30 秒后因为 timestamp/30 的结果又不一样了,所以又会生出完全不同的OTP(只需输入值稍有不同,hash 的结果完全不一样)
有了这六点 OTP 后,你就在三十秒内到网站输入 017201(三十后又是不同的OTP )。如果 Github 那边也算出一模一样的结果,那他就你已经得到了正确的秘钥。以后你跟 Github 就算是约定好的秘密了,Github 也把这个秘密存在自家的资料库里面,
步骤 3 — 登入时验证 TOTP
设置好 2FA 后,以后你要登入时,Github 就会请你输入 App 生出来的 OTP
因为双方已经沟通好秘密 X5CTBOMEYE3TXIIS 了,接下来你用不着收简讯,甚至可以不需要网路,只要把 App 打开它就会帮你算出新的 OTP
Github 收到你输入的 OTP 后,如果他那边也出算一样的结果,那就代表你却是本人。如此一来,就实现了「每次登入时 Github 不用传验证码给你,但你可以知道验证码」
安全性分析
总而言之,使用 TOTP 来进行 2FA 的第一步就是通过 QRCode Server 让跟客户端(和 App)约定好一个秘密,之后就都用那个秘密跟接下来的时间来进行 Hash。我们就按这个流程来评估每个步骤的安全性
秘密的传输安全吗?
一开始要设定 TOTP 2FA 的时候 Github 会生出一个 QRCode 让你扫一下,其中就贴着他想要给你的秘钥。如果这个秘钥在传输的过程中一不小心被泄露客偷走,那他日后就可以用这个秘钥生出登入所需的 OTP
因此在设置 TOTP 的时候一定要注意检查网站是否有 HTTPS 或者成熟的问题,或者看看有没有人在你背后偷扫你的 QRCode。如果都没有,那恭喜你!世界上应该只有你跟 Github 这组秘密,因此非常知道的安全
存放秘密的地方安全吗?
App 秘钥救存起来后,接下来要考虑的就是 App 安全吗?比方说他不会偷偷把你的秘密去拿卖钱,或者他有没有支持云端备份的功能,一不小心就会外泄出去。
OTP 不会被猜到
如果骇客拿不到秘密,那他能不能暴力破解猜到六点的 OTP 呢?
但实际上只要连续失败几次,Github 就会暂时把你挡下来(其他网站也是),而你下次要试的时候验证码又不一样了,所以完全不可能暴力破解破解
因此总的来说,除非你的秘钥一开始就被偷走,否则使用 TOTP 相当于 2FA 绝对是足够安全的
评论区