使用NitroPepper实现超安全密码存储

2020-11-07 11:13:09

Nitro Enclaves为安全处理非常敏感的数据提供隔离的计算环境。在本文中,我们将使用Nitro Enclaves构建一个极其安全的密码存储机制。

在发表了我最近发表的关于EC2硝基飞地的文章(第一部分,第二部分)后,有些事情困扰着我。我花了几天时间才意识到这是什么:除了ACM参考架构,我想不出Nitro Enclaves的有效用例。这让我不禁要问--硝基飞地是在寻找问题的解决方案吗?

几个小时的草绘和思考给我留下了一些不可行或令人费解的实现想法。但后来我突然想到。硝基飞地可用于提供超级安全的密码存储机制。在这篇文章的最后,我们将构建一个Nitro Enclave应用程序,它可以保护用户密码,即使攻击者完全控制了应用程序服务器和数据库。这个Nitro Enclave应用程序被称为NitroPepper。

本文由三个部分组成。第一节介绍了密码存储机制,并解释了NitroPepper解决的问题。第2节介绍了NitroPepper的实现细节。在第3节中,我们将介绍在您自己的EC2实例上运行NitroPepper的所有步骤。

NitroPepper架构需要几个组件,我们将在第2节深入讨论这些组件。我们使用的组件包括:

一个简单的Python Flask前端(GitHub),它公开了两个REST API方法:new_user和login。此前端仅用于演示目的。

AWS Nitro包含使用Python接口(GitHub)扩展的NSM API。此接口提供NitroPepper和Nitro安全模块(NSM)之间的接口。

任何使用用户登录的系统都需要某种方式来存储用户密码。几十年前,以纯文本存储密码是可以接受的。但随着越来越多有价值的数据被存储在网上,人们开始攻击数据库并提取其中的内容。为了防止密码泄露,人们逐渐开发出更复杂的密码存储方法。在这一节中,我们将简要回顾密码存储的历史。

如果你已经熟悉散列算法、盐和胡椒,可以跳过这一节,直接跳到“一个独特的胡椒用户的案例”这一段。

需要注意的是:在下面的部分中,我使用MD5作为散列机制的示例。众所周知,MD5非常不安全,您永远不应该将其用于现实世界中的密码散列。相反,您可以使用SHA-512、SHA3或其他高度安全的算法。我在本文中使用MD5的原因是因为它的输出简洁美观,便于阅读。

在几乎被遗忘的过去,应用程序没有连接到互联网,数据库从未受到攻击,密码可以明文存储。用户表中的几行可能如下所示:

要登录到此系统,用户需要向应用程序提供其用户名和密码。应用程序检查数据库中是否存在用户名/密码组合。如果找到密码,则允许用户进入。

随着系统的连通性和可访问性越来越高,数据库黑客和转储也变得更加常见。当攻击者使用以纯文本形式存储的密码获得对数据库的访问权限时,他们可以使用此信息以任何用户身份登录。更糟糕的是,用户通常会重复使用密码。这意味着攻击者可以使用在不安全的站点A找到的密码尝试登录到另一个站点B,即使该站点确实安全地存储了他们的密码。

提高密码存储安全性的第一个发展是散列。像MD5或SHA这样的散列算法接受一个输入(在我们的例子中是密码),并为该输入生成一个固定长度的字符串。该算法是一种数学运算,它将始终为任何给定的输入生成相同的输出。例如:

散列函数的本质是它是单向的。换句话说,您可以从密码生成散列,但不能从其散列生成密码。因此,如果有人能够访问散列bd2bf17a10bc97a50bfb551aa2de9e76,他们不会知道源密码是SUPER_Secret1。

基于此技术构建的应用程序将不再存储纯文本密码,而是存储散列版本:

当用户登录到此应用程序时,他们会提供用户名和明文密码。应用程序在每次登录尝试时都会对密码进行散列,并将结果与数据库中存储的散列进行比较。如果它们匹配,则允许用户进入。

使用此解决方案时,密码不再以纯文本形式存储。然而,仍然存在明显的弱点。首先,一些算法,如MD5和SHA1,已经被“破坏”了。当算法被破坏时,攻击者可以独立地生成导致给定散列的输入字符串。然后,他们可以使用此输入字符串作为密码。另一个问题是给定密码的散列值总是相同的。攻击者可以利用这一事实,使用常见输入和模式的散列构建词典或彩虹表。当他们访问散列数据库时,他们只需要在字典中查找它们来确定原始密码。

下一代密码安全通过添加盐来解决散列问题。为什么这叫盐?因为盐和哈希肉很配。使用SALT时,我们仍然使用散列算法,但不是只对明文密码进行散列,而是在输入中添加一个随机字符串。下面是几个简单的例子:

此盐与用户信息一起存储在数据库中。请注意,SALT实际上是一个具有更大字符集的更长的字符串。

在口令中添加盐后,两个相同的口令(SUPER_Secret1)变得唯一(SUPER_Secret1-123455和SUPER_Secret1-669911),这会产生不同的散列。

登录到此系统时,用户仍提供其明文密码。然后,服务器从数据库中检索SALT并执行散列函数:MD5([Password]-[SALT])。当生成的散列与存储的密码散列匹配时,允许用户登录。

随着盐的加入,字典和彩虹表都变得毫无用处了。不幸的是,即使是这种解决方案在许多用例中也不够强大。

在上述解决方案中,所有信息都存储在单个源中--数据库。如果攻击者有权访问数据库和已知条目(例如,他们自己的用户名和密码),他们就可以尝试通过暴力破解来检索原始值。这可能需要一段时间,但处理能力每年都会变得更便宜,而且政府可以使用超级计算机,这可能会使破解密码比你想象的更容易。

公平地说,对于大多数应用程序来说,具有适当盐渍密码的存储机制将是足够安全的。但在医疗、金融或政府使用案例中,这可能还不够。这就是添加基于服务器的Pepper的用武之地。为什么叫胡椒…??好吧,你明白我的意思了。

共享秘密辣椒建立在盐的原理之上,但它不是将值存储在数据库中,而是存储在服务器文件系统或其他非数据库系统(如AWS Secrets Manager)上。为了能够解密数据库中的密码,攻击者需要同时访问数据库和辅助存储系统,这极大地提高了安全性。现在,简单的SQL注入攻击或对数据库备份的访问已不足以尝试暴力攻击。

这一次,当用户尝试登录时,应用程序将散列密码+盐+胡椒:md5([密码]-[盐]-[胡椒])。当结果与存储在数据库中的密码散列匹配时,允许该用户进入。

胡椒(密码学)的维基百科页面上写道:“在共享秘密胡椒的情况下,一个被泄露的密码(通过密码重复使用或其他攻击)加上用户的盐可以导致发现胡椒的攻击,使其失效。”此外,有权访问Web服务器的用户、工程师或黑客很可能有权访问数据库凭据和共享的秘密胡椒。如果这个人能够渗出数据库和共享的秘密胡椒,密码就可以再次被暴力破解。

解决方案是为每个用户生成唯一的胡椒。当然,盐对每个用户来说也是独一无二的。用户盐和独特的用户胡椒之间的区别在于它们的存储位置。盐与用户的散列密码半公开地存储在一起,而唯一的用户胡椒则存储在单独的介质上,例如硬件安全模块(HSM)。缺点是,这种单独的媒介需要能够为每个用户存储辣椒。在大规模环境中,这意味着安全模块还需要大量的存储和处理能力。但随着硝基飞地的引入,一种新的解决方案变得可行。

使用EC2 Nitro Enclaves,我们可以使用KMS对唯一的用户胡椒进行加密,并将加密后的数据与用户信息一起存储在数据库中。硝基飞地将是唯一可以解密胡椒的部件。因为飞地是完全隔离运行的,所以用户的胡椒永远不会暴露出来。即使某人可以不受限制地访问Web服务器和数据库,他们也永远无法解密用户的密码。提供这些功能的Enclave应用程序名为NitroPepper。

在下面的章节中,我们将描述一个具有存储在DynamoDB中的用户凭据的Web应用程序。当用户注册时,他们的密码会被发送给NitroPepper。NitroPepper与Nitro Security模块和KMS通信,以安全地散列密码。然后,NitroPepper返回散列密码和加密的胡椒。散列密码和加密的胡椒一起存储在DynamoDB中。

当现有用户尝试登录时,应用程序将从DynamoDB获取散列密码和加密的Pepper。然后,这些信息和他们试图登录的密码一起被转发给NitroPepper。NitroPepper将解密DynamoDB中的胡椒,并使用解密的值对用户尝试登录时使用的密码进行散列。如果结果与存储在数据库中的散列匹配,则允许用户登录。

NitroPepper飞地需要3 GB内存才能运行。这是很多的,当然可以优化,但它就是目前的情况。要运行3 GB的enclave,可以发出以下命令:sudo nitro-cli run-enclave--cpu-count 2--memory 3072--eif--path nitPepper.eif--enclave-ci6。第一次运行此命令时,我遇到了以下错误:

(Env)[ec2-user@ip-172-31-9-229 nitPepper]$sudo nitro-cli run-enclave--cpu-count 2--Memory 3072--eif--enclave-ci6开始分配内存...[E27]可用内存不足。用户提供的`内存‘为3072MB,超过了可用的超大页面内存。

要解决这个问题,我们需要通过运行ECHO";vm.nr_hugepages=1536";|sudo tee/etc/sysctl.d/99-nit.conf;sudo sysctl-p/etc/sysctl.d/99-nit.conf来增加大页面的数量。

在开发过程中的另一个时刻,我的停靠容器耗尽了内存。目前还不清楚内存是问题所在,因为Nitro Enclaves提供的唯一输出是无法打开/env文件:没有这样的文件或目录。一旦我将Nitro Enclaves内存从2 GB更改为3 GB,错误就消失了,再也没有出现过。

前端应用程序通过vsock(VM套接字的缩写)与NitroPepper通信。这在Python中非常简单。NitroPepper按如下方式绑定到vsock(Enclave_Port设置为5000):

Vsock=socket.套接字(socket.AF_VSOCK,socket.SOCK_STREAM)vsock.bind((socket.VMADDR_CID_ANY,Enclave_Port))vsock.listen()而True:conn,_addr=vsock.Accept()print(';接收到新连接)payload=Conn.recv(4096)。

前端应用程序连接到此套接字,如下所示。Enclave_CID硬编码为6,Enclave_Port为5000。

NitroPepper执行三种类型的KMS操作:GenerateRandom、Encrypt和Deccrypt。前两个操作应该相当简单;我们使用从父实例获得的凭据向https://kms.eu-central-1.amazonaws.com发送一个简单的POST请求,KMS返回我们请求的内容。

然而,硝基飞地没有可用的网络连接。要与KMS通信,Enclave需要连接到在父实例上运行的KMS代理。要运行KMS代理,只需执行sudo vsock-proxy 8000 kms.eu-Central-1.amazonaws.com443或将vsock-proxy作为服务启动。在Enclave中,您使用CID 3连接到vsock。此流量由vsock-Proxy接收,并将其转发到KMS。

在NitroPepper中,我想使用标准请求库进行HTTP调用,但是请求不支持vsock。幸运的是,使用Richard Fan(GitHub)编写的流量转发器解决了这个问题,在他的博客文章中详细描述了在AWS Nitro Enclaves上运行Python App。转发器在标准端口上接收流量,并将其转发到KMS vsock。问题解决了。

对于Nitro Enclaves证明,Enclave需要生成RSA密钥对并将公钥发送到KMS服务。生成RSA密钥对需要随机数生成,这通常由/dev/Random和/dev/urandom提供。然而,在硝基飞地,这些是不可用的。

相反,我们使用/dev/nsm,它同时用于随机数生成和创建证明文档。使用ioctl,我们可以与/dev/nsm通信。这个过程相当复杂,但幸运的是,我们不必重新发明轮子。在GitHub上,AWS提供了aws-nitro-enclaves-nsm-api,这是一个(用Rust编写的)库,它与/dev/nsm接口。但有一个问题:它只为C语言提供接口。

我将aws-nitro-enclaves-nsm-api派生到我自己的Git存储库,然后为Python编写额外的接口。这只需要大约80行代码,您可以在此提交中找到。您可以克隆存储库并运行Cargo Build来构建您自己的库,或者从NitroPepper存储库下载预编译库。

Libnsm库为nsm_get_Random()和nsm_get_attestation_doc()函数提供了接口,这就是我们为NitroPepper所需要的。

Pycrypdome库提供了RSA.Generate()函数来生成RSA密钥对。默认情况下,它使用标准的Linux随机数生成器(/dev/Random),但是可以用RSA.Generate(2048,Randfunc=nsm_randfunc)…覆盖它。或者我是这样想的。实际上,这会覆盖/dev/Random的一些用法(但不是全部用法)。为了克服这个问题,我给金字塔打了以下补丁:

@classMethod def_money_patch_crypto(cls,nsm_rand_func):";";";";";";";";";Crypto.Random.get_Random_bytes=NSM_rand_func def new_Random_read(self,n_bytes):返回NSM_rand_func(N_Bytes)Crypto.NSM_RAND_FUNC(N_Bytes)Crypto.";";";";";";";";";"

有了这一点,pycrypdome在Nitro Enclaves中运行没有问题,我们可以使用它来生成RSA密钥对。

到目前为止,最困难的部分是让“证明”运行。认证是指硝基飞地通过一种保证请求来自特定飞地的方法向KMS表明自己身份的过程。此过程仅用于解密操作。

让我们从概述开始。出于可读性考虑,此图中未包含KMS代理。

如您所见,该过程需要八个加密和解密步骤。首先,生成RSA密钥对(3)。公钥被发送到NSM API,该API生成嵌入该公钥的证明文档(4)。证明文档被附加到KMS解密呼叫(5)。KMS使用NitroPepper提供的公钥对请求的数据进行加密。然后,它以加密消息语法(CMS)信封的形式将加密数据发送回NitroPepper(6),该信封是在公钥密码标准#7或PKCS#7中定义的。返回到NitroPepper应用程序中,对信封进行解析(7),这产生了三个值:用于加密KMS响应的密钥,该KMS响应本身是用RSA密钥加密的、初始化向量(IV)和加密的KMS响应。然后,NitroPepper使用RSA私钥来解密对称密钥(8)。最后,对称密钥用于解密KMS响应(9)。

KMS解密调用需要证明文档的一些未记录的参数。下面的Python代码(源代码)显示了NitroPepper使用的请求正文:

CMS解密过程的代码可以在kms.py中找到。以下是核心功能的一小段:

Def_cms_parse_encaped_data(self,ciphertext_for_repient):";";";返回序列化CMS内容的对称密钥、IV、块大小和密文。";";";...。BLOCK_SIZE=encrypted_content_info[';content_encryption_algorithm';].encryption_block_size INIT_VECTOR=encrypted_content_info[';content_encryption_algorithm';].encryption_iv密文=encrypted_content_info[';encrypted_content';].native返回密钥,INIT_VECTOR,BLOCK_SIZE,ciphertext def_rsa_deccrypt(SELF,PRIVATE_KEY,ENCRYPTED_SYMM_KEY):";";";使用RSA私钥解密加密的对称密钥。";";";";";";";&#rsa=PKCS1_OAEP.new(PRIVATE_KEY)返回cipher_rsa.deccrypt(Encrypted_Symm_Key)def_aws_cms_cipher_deccrypt(self,ciphertext,key,block_size,init_Vector):";";";解密明文数据。Cipher=AES。new(key,AES.MODE_CBC,iv=init_Vector)返回unpad(cipher.deccrypt(密文),block_size)。

将公共RSA密钥发送到KMS的过程,以及KMS在返回响应之前对响应进行加密的过程,可以确保即使数据被拦截(在父实例上、网络上或物理主机上的其他租户),拦截器仍然无法访问您的数据。解密数据的唯一方法是使用RSA私钥,除飞地外,任何人都无法访问该密钥。

使用密码和盐生成密码散列的函数是hashlib.pbkdf2_hmac()。你可以在这里找到代码。可以在这里找到Python pbkdf的文档。在NitroPepper中,pbkdf2_hmac的用法如下。PBKDF2_Iterations设置为100.000次迭代。

此函数将把密码和胡椒作为字节,并使用sha512对它们进行100.000次散列。100.000次迭代使得这个过程相对缓慢,这使得暴力攻击变得不可行。

您可以使用以下AWS CLI命令创建前三个资源。在撰写本文时,AMI是最新的Amazon Linux2镜像。

AWS EC2运行实例--区域EU-Central-1--Image-id ami-00a205cb8e06c3c4e--count 1--实例类型m5a.xLarge--Enclave-Options';Enabled=true';--key-name[您的密钥名称]AWS DynamoDB Create-table-Region EU-Central-1--table-name nitPepper-user--计费模式Pay_per_Request--Attribute--。2012年10月17日&34;";Statement";:[{";Effect";:";Allow";,";Principal";:{";Service";:";ec2.amazonaws.com";},";Action";:";sts:AssumeRole";}]}';AWS IAM CREATE-ROLE--ROLE-NAME NITHPER-ROLE--假定-ROLE-POLICYALLOW_DOCUMENT$POLICYALLOW_DDB=';{";Version";:";2012-10-17";,";Statement";:[{";Effect";:";Allow";,";Action";:[";DynamoDB:PutItem";,";DynamoDB:GetItem&34;],";资源";:";arn:aws:dynamodb:*:*:table/nitropepper-users";}]}';aws IAM PUT-ROLE-POLICY--ROLE-NIT。

.