密码学和信息安全在如今的互联网行业当中非常重要,相关的理论知识和算法也在计算机系统的方方面面都被用到。虽然我们不一定会从事安全相关的工作,但对密码学以及信息安全的基本知识和概念有所了解还是很有必要的。
日拱一卒,欢迎大家打卡一起学习。
这节课上我们会关注安全和密码学相关的概念,这些概念和之前介绍的一些工具也有关联。比如git当中用到的hash函数或者是SSH当中密钥生成函数或者是对称/非对称密码体系。
本节课不能作为计算机系统安全以及密码学的替代。没有从事训练,不要轻易从事安全相关的工作,也不要修改或创造加密相关算法。
这节课是对基础密码学概念的一个非正式的介绍。这节课上我们不会教你如何设计安全系统或者是加密协议,但我们希望能够让你对频繁使用的程序以及协议有一个总体上的了解。
熵
熵用来衡量混乱程度,这是一个非常有用的概念,在很多领域当中都有广泛应用。比如当我们决定密码强度的时候。
上图是关于密码强度的漫画,漫画当中说"correcthorsebatterystaple"比"Tr0ub4dor&3"这样的密码更加安全,但是它是怎么定义安全程度的呢?
在计算机领域当中,熵的计算单位是bit,当均匀地从一系列值当中随机选择时,它的熵等于log_2(可能性总数)。抛一枚均匀的硬币的熵是1 bit,一个六面骰子的熵大约是2.58 bit。
你可以认为黑客们知道密码的模型(最短长度、最长长度、包含的字符种类等),但不知道密码是如何被随机选择的(比如通过骰子)。
多少bit的熵才足够呢?这取决于你的威胁模型。对于在线穷举的猜测,漫画告诉我们大约40bit的熵就足够了。而对于离线的枚举,一般需要更强的密码(比如80bit或更多)。
hash函数
密码hash函数可以将任意大小的数据映射成一个固定大小的输出,并且还有一些特殊的属性。一个hash函数的定义大体如下:
hash(value: array) -> vector (N对于该函数固定)
SHA1是一个很好的例子,它被用在Git当中。它可以将任意长度的输入转化成160bit的输出(可以被表示成长度40的十六进制数)。我们可以使用sha1sum命令来使用SHA1函数:
$ printf 'hello' | sha1sum aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d $ printf 'hello' | sha1sum aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d $ printf 'Hello' | sha1sum f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0
从更高维度来说,hash函数拥有不可逆性并且结果看上去非常随机。想要从hash之后的结果倒推出输入非常困难,几乎不可能完成。一个hash函数拥有如下特性:
确定性:同样的输入得到同样的输出
不可逆性:对于hash(m) = h,很难通过h倒推得到m
很难目标碰撞:对于输入m_1很难找到另外一个输入m_2使得它们hash之后的结果一样
碰撞抵抗:很难找到一组m_1, m_2使得hash(m_1) = hash(m_2)(这比上一条要求更高)
注意:虽然SHA-1对于某些用途还在使用,但它已经不再被认为是一个很强的密码hash函数了。你可以参考密码hash函数的生命周期这篇文章(https://valerieaurora.org/hash.html)。另外,推荐特定的hash函数并非是本节课的重点,如果你想要使用它,最好先系统学习密码学和信息安全。
应用
git,用作内容寻址存储。hash函数是一个非常广的概念(并非只有密码hash函数),为什么git选择了密码hash函数呢?
文件信息摘要:我们通常在一些镜像网站下载一些软件,比如Linux ISO文件。对于非官方来源下载的软件,我们希望它和官网一样,没有被篡改。所以通常官方渠道除了会提供下载文件之外,还会提供文件的hash值。我们可以对比下载的文件的hash值和官方提供的hash值是否一致来判断文件是否被篡改过
承诺机制(commitment schemes):假设你希望commit一个特定的值,但希望之后再同步它。比如玩猜数游戏,为了保证公平我需要先提供给你目标数的hash值。这样可以确保目标值不被篡改,但又不会泄漏具体的结果。当玩家做出猜测之后,将玩家猜测的结果hash之后和提供的hash值比对来验证玩家是否猜测正确
密钥生成函数
密钥生成函数(key derivation functions KDFs)是一个和密码hash函数近似的概念,它用在许多场景,比如生成固定长度的输出结果,用作一些密码学算法当中充当密钥。通常KDF生成函数比较缓慢,这是为了抵抗暴力破解攻击。
运行缓慢可以让暴力穷举枚举的破解方法消耗更多的时间。
应用
从密码生成可以在其他加密算法中使用的密钥,比如对称加密算法(见下)。
存储登录凭证时不可直接存储明文密码。正确的方法是针对每个用户随机生成一个盐 salt = random(), 并存储盐,以及密钥生成函数对连接了盐的明文密码生成的哈希值KDF(password + salt)。在验证登录请求时,使用输入的密码连接存储的盐重新计算哈希值KDF(input + salt),并与存储的哈希值对比。
对称加密
关于密码学,你可能首先想到的就是隐藏消息内容。对称加密通过以下几个方法来完成这个功能:
keygen() -> key (this function is randomized) encrypt(plaintext: array, key) -> array (the ciphertext) decrypt(ciphertext: array , key) -> array (the plaintext)
加密算法encrypt生成密文(ciphertext),在没有key的情况下,我们很难将密文破译。
解密函数要确保正确解密需要保证:decypt(entrypt(m, k), k) = m
AES是现在常用的一种对称加密算法。
应用
在一个不被信任的云服务器上存储文件,可以和KDFs结合起来,这样你就可以使用密码加密文件。生成密钥:key = KDF(passphrase),接着存储encrpy(file, key)
非对称加密
非对称的意思是会使用两个功能不同的密钥。一个是私钥,不对外公开。一个是公钥,可以被公开分享,并且不会影响安全性(不像对称加密不能分享密钥)。
非对称加密提供以下几个函数来实现加密/解密和签名/验证(sign/verify):
keygen() -> (public key, private key) (this function is randomized) encrypt(plaintext: array, public key) -> array (the ciphertext) decrypt(ciphertext: array , private key) -> array (the plaintext) sign(message: array , private key) -> array (the signature) verify(message: array , signature: array , public key) -> bool (whether or not the signature is valid)
加密/解密函数和对称加密当中类似。密文可以使用公钥进行加密,得到密文(ciphertext),在没有私钥的情况下,很难得到原文(plaintext)。
解密函数保证decrypt(encrypt(m, public_key), private_key) = m
对称加密和非对称加密可以类比于机械锁,对称加密系统就像是门锁:只要有钥匙就可以进行开锁和上锁。非对称加密就像是一个可以取下的挂锁,你可以把一个打开的挂锁给别人(公钥)上锁,并保留钥匙(私钥)。其他人可以上锁,但只有私钥才能开锁。
签名/验证函数和书面签名有相似的属性——很难被伪造。不论消息是什么,在没有私钥的情况下,很难生产可以使得verify(message, signature, public_key)返回True的签名。当然,在私钥和公钥对应时,verify会返回True,即verify(message, signature, public_key)=true
应用
PGP电子邮件加密:用户可以将所使用的公钥在线发布,比如:PGP密钥服务器或 Keybase。任何人都可以向他们发送加密的电子邮件。
聊天加密:像 Signal 和 Keybase 使用非对称密钥来建立私密聊天。
软件签名:Git 支持用户对提交(commit)和标签(tag)进行GPG签名。任何人都可以使用软件开发者公布的签名公钥验证下载的已签名软件。
密钥分发
非对称加密非常好用,但是也有一个巨大的挑战,就是如何将公钥分发/对应现实世界的实体。关于这个问题有很多解决方案。信号(signal)使用一种简单的方法:在第一次使用的时候信任它,同时支持用户面对面、线下交换公钥。
PGP的策略不同,它使用信任网络。keybase主要通过另外一种叫做社交证明(social proof)和一些其他的设计。
每个模型都有它的特点,我们更倾向于keybase的模式。
案例分析
密钥管理
每个人都有必要使用密码管理器比如keepassxc,pass,1password等。密码管理器可以让我们不同的网站使用不同的、随机且高熵的密码,并且会将所有的密码使用对称加密的方式来存储起来。
使用密码管理器可以让你不需要重复使用密码,并且可以使用高熵密码,最重要的是你只需要记住一个密码就可以尽可能避免密码泄漏、被撞库等隐患。
两步验证
两步验证(2FA)要求用户使用密码以及另外一个身份验证器来消除密码泄漏以及钓鱼攻击的风险。
全盘加密
对笔记本电脑的硬盘进行全盘加密是防止因设备丢失而信息泄露的简单且有效方法。Linux的cryptsetup + LUKS, Windows的BitLocker,或者macOS的FileVault都使用一个由密码保护的对称密钥来加密盘上的所有信息。
私密消息
使用singal或keybase可以保证端到端的通信安全。
但这需要使用联系人的公钥,要确保安全的话,需要在线下方式验证singal或者keybase的公钥。
SSH
我们在之前的一堂课讨论了SSH和SSH密钥的使用。让我们从密码学的角度来分析一下它们。
当你运行ssh-keygen时,它会生成一个非对称加密的密钥对,public_key和private_key。这是随机生成的,通常会使用操作系统提供的熵(从硬件事件上获取)。公钥可以被分发出去,私钥需要加密保存在磁盘上。
ssh-keygen程序会提示用户输入一个密码,并将它输入密钥生成函数来生成密钥。这会被对称加密算法进行加密。
在使用的时候,当服务器知道了客户端的公钥之后(存储在.ssh/authorized_keys文件中),尝试链接的客户端可以使用非对称加密签名来验证客户的身份。
简单来说,服务器选择一个随机数发送给客户。客户使用私钥进行签名之后再发送给服务器,服务器随后使用存储的公钥来验证签名。这种方式可以很方便地验证客户是否持有对应的私钥,如果验证通过的话,那么允许客户登录访问。
资源
去年的笔记,更多地聚焦在计算机使用者的安全和隐私上:https://missing.csail.mit.edu/2019/security/
这篇文章解答了对于不同的应用应该使用什么加密算法的问题:https://latacora.micro.blog/2018/04/03/cryptographic-right-answers.html
练习
熵
假设一个密码是从四个小写的单词拼接组成,每个单词都是从一个含有10万单词的字典中随机选择,且每个单词选中的概率相同。一个符合这样构造的例子是correcthorsebatterystaple。这个密码有多少比特的熵?
假设另一个密码是用八个随机的大小写字母或数字组成。一个符合这样构造的例子是rg8Ql34g。这个密码又有多少比特的熵?
哪一个密码更强?
假设一个攻击者每秒可以尝试1万个密码,这个攻击者需要多久可以分别破解上述两个密码?
密码hash函数 从Debian镜像站下载一个光盘映像(比如这个来自阿根廷镜像站的映像:http://debian.xfree.com.ar/debian-cd/10.2.0/amd64/iso-cd/debian-10.2.0-amd64-netinst.iso)。使用sha256sum命令对比下载映像的哈希值和官方Debian站公布的哈希值。如果你下载了上面的映像,官方公布的哈希值可以参考这个文件:https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/SHA256SUMS。
对称加密 使用 OpenSSL的AES模式加密一个文件: openssl aes-256-cbc -salt -in {源文件名} -out {加密文件名}。使用cat或者hexdump对比源文件和加密的文件,再用 openssl aes-256-cbc -d -in {加密文件名} -out {解密文件名} 命令解密刚刚加密的文件。最后使用cmp命令确认源文件和解密后的文件内容相同。
非对称加密
在你自己的电脑上使用更安全的ED25519算法生成一组SSH 密钥对。为了确保私钥不使用时的安全,一定使用密码加密你的私钥。
配置GPG:https://www.digitalocean.com/community/tutorials/how-to-use-gpg-to-encrypt-and-sign-messages。
给Anish发送一封加密的电子邮件(Anish的公钥:https://keybase.io/anish)。
使用git commit -S命令签名一个Git提交,并使用git show --show-signature命令验证这个提交的签名。或者,使用git tag -s命令签名一个Git标签,并使用git tag -v命令验证标签的签名。
全部0条评论
快来发表一下你的评论吧 !