英特尔® Software Guard Extensions(英特尔® SGX)教程系列的第二部分介绍了我们将开发的应用(简单的密码管理程序)的高级规格。 全新的设计工作支持我们从一开始专为英特尔 SGX 而构建。 这意味着,除说明应用要求,我们还将分析英特尔 SGX 设计决策和整个应用架构会如何相互影响。
阅读本系列第一部分教程,或在介绍英特尔® Software Guard Extensions 教程系列的文章中查找所有已发表教程的列表。
密码管理程序一览
多数人可能熟悉密码管理程序及其功能,但在详细了解应用设计之前有必要回顾其基本信息。
密码管理程序的主要目标是:
- 减少最终用户需要记住的密码数量。
- 帮助最终用户创建安全性更高的密码 —— 相比其通常选择的密码。
- 确保针对每个账户使用不同密码具有实用性。
密码管理日益成为互联网用户的问题。多年来,多项研究在努力对这一问题进行量化。 Microsoft 于 2007 年发布的一项调查—— 距离本文写作时接近 10 年 —— 估计,普通人拥有 25 个需要密码的账户。 2014 年,Dashlane 估计其美国用户平均拥有 130 个账户,而其全球用户所拥有账户的平均数量为 90。 问题并未就此结束:用户都不善于挑选“好”密码(众所周知),经常在多个站点重复使用相同密码,这已经导致了一些备受关注的攻击事件。 这些问题基本可归为两类:黑客工具难以猜透的密码通常也不易于用户记住,以及密码数量的增多让问题更加复杂,因为用户需要花费更多心思记住哪个密码与哪个账户相关联。
借助密码管理程序,您只需记住一个安全性极高的密码,便可访问密码数据库。 通过了密码管理程序的身份验证之后,您便可查找您存储的任何密码,并根据需要将其复制粘贴在身份验证字段中。 当然,密码管理程序的主要弱点恰恰在于密码数据库本身:由于包含所有的用户密码,因此它对于攻击者极具诱惑力。 因为这个原因,密码数据库需使用安全性较高的加密技术进行加密,这样解密其中的数据就需要用户的主密码。
我们制作本教程的目标是帮助构建一个简单的密码管理程序 —— 它能够在遵守良好安全实践的情况下提供与商用产品相同的核心功能,并将其用作针对英特尔 SGX 进行设计的学习工具。 教程密码管理程序(我们称之为“支持英特尔® Software Guard Extensions 的教程密码管理程序”,名字虽然有点长,但具有描述性)并非要用作商用产品,因此不会、也没必要包含所有保护功能。
基本的应用要求
一些基本的应用要求将可帮助缩小应用的范围,以便我们专注于英特尔 SGX 集成,而非应用设计和开发的细节。 再次强调,我们的目标不是创建商用产品,支持英特尔 SGX 的教程密码管理程序无需在多个操作系统或所有可能的 CPU 架构上运行。 我们只需要一个合理的起点。
为此,我们的基本应用要求是:
第一个要求可能有点奇怪,因为本教程系列旨在说明英特尔 SGX 应用开发,但实际的应用需要考虑传统的安装基础。 对于一些应用而言,将执行范围限制于支持英特尔 SGX 的平台的范围内可能比较合适,但对于教程密码管理程序,我们将放宽要求。 支持英特尔 SGX 的平台将支持强化的执行环境,但不支持的平台仍可正常运行。 这种使用方法适用于密码管理程序。使用密码管理程序时,用户可能需要将其密码数据库与其他较旧的系统进行同步。 它还是实施双代码路径的学习机会。
第二个要求是访问非英特尔 SGX 代码路径中的某些密码算法以及我们需要的一些库。 64 位要求通过确保原生 64 位类型的访问简化了应用开发,而且提升了某些针对 64 位代码而优化的密码算法的性能。
第三个要求是访问非英特尔 SGX 代码路径中的 RDRAND 指令。 这可极大简化随机数生成,确保对高质量熵源的访问。 支持 RDSEED 指令的系统也可利用它。 (有关 RDRAND 和 RDSEED 指令的信息,请访问英特尔® 数字随机数生成器软件实施指南)。
第四个要求是尽量减少开发人员(和最终用户)需要的软件数量。 无需下载和安装第三方库、框架、应用或实用程序。 然而,该要求有一个不利的副作用:没有第三方框架,我们只可使用四个选项创建用户界面。 这些选项包括:
- Win32 API
- Microsoft 基础类 (MFC)
- Windows Presentation Foundation (WPF)
- Windows Forms
前两个选项使用原生/非托管代码实施,后两个需要使用 .NET*。
用户界面框架
对于教程密码管理程序,我们在 C# 中使用 Windows Presentation Foundation 开发 GUI。 该设计决策会影响我们的下列要求:
为何使用 WPF? 主要是因为它可简化 UI 设计,并会带来我们实际上需要的复杂性。 具体而言,通过使用 .NET 框架,我们可以讨论将托管代码、特别是高级语言与安全区代码进行混合。 不过,请注意,WPF 和 Windows Forms 都可选择,两种环境都能有效运行。
您可能记得,安全区必须使用原生 C 或 C++ 代码编写,与安全区交互的桥函数必须为原生 C(而非 C++)函数。 尽管 Win32 API 和 MFC 支持使用完全原生的 C/C++ 代码开发密码管理程序,但这两种方法不会对希望学习英特尔 SGX 应用开发的人员造成负担。 借助基于托管代码的 GUI,我们不仅可获得集成设计工具的优势,而且可讨论一些可为英特尔 SGX 应用开发人员带来潜在价值的功能。 简言之,您并非要学习 MFC 或原始 Win32,但可能希望了解如何将 .NET 粘合至安全区。
为将托管和非托管代码连接起来,我们将使用 C++/CLI(针对通用语言基础设施而修改的 C++)。 它可大幅简化数据封送,而且因其便捷性和易用性被许多开发人员形容为 IJW(“确实有效”)。
图 1: 原生和 C# 英特尔® Software Guard Extensions 应用的最小组件结构。
图 1 所示为英特尔 SGX 应用从原生代码迁移至 C# 时其最小组件结构受到的影响。 在完全原生的应用中,应用层可直接与安全区 DLL 交互,因为安全区桥函数可纳入应用的可执行文件。 然而,在混合模式应用中,安全区桥函数需要与托管代码块相隔离,因为它们需要是完全原生的代码。 另一方面,C# 应用不可直接与桥函数交互,在 C++/CLI 中这意味着创建另一个媒介:在托管 C# 应用和原生安全区桥 DLL 之间封送数据的 DLL。
密码库要求
密码数据库或我们所称的密码库是密码管理程序的核心。 这种加密文件将保存最终用户的账户信息和密码。 我们教程应用的基本要求是:
密码库应可移植的要求意味着我们应在将密码库文件复制到另一台计算机后仍可访问其内容,无论它们是否支持英特尔 SGX。 换言之,用户体验应该一致:密码管理程序应该无缝运行(当然,前提是系统满足基础硬件和操作系统要求)。
加密静态密码库意味着密码库文件在未使用时应被加密。 密码库至少须在磁盘上进行加密(若没有可移植性要求,我们有望使用英特尔 SGX 的封闭特性满足加密要求),且若无必要,在内存中不应处于未加密状态。
经过验证的加密可确保加密的密码库在加密之后未被修改。 它还可帮助我们方便地验证用户的密码:如果解密密钥不正确,在对验证标签进行验证时解密会失败。 这样,我们无需检测解密数据是否正确。
密码
从多方面考虑,任何账户信息都是敏感信息,它可准确告知攻击者将攻击目标锁定哪些登录名和站点,不过密码无疑是密码库最重要的要素。 确定攻击目标账户后就可发起攻击。 为此,我们将介绍有关密码库中所存储密码的更多要求:
嵌入加密功能。 每个用户账户的密码存储在密码库时经过了加密,整个密码库在写入磁盘时进行了加密。 该方法支持我们在密码库解密后避免密码被曝光。 对密码库进行全面解密可帮助用户浏览其账户详情,但以这种方式清晰显示所有密码文本则并不合适。
账户密码只在用户请求查看它时被解密。 这可防止密码在内存中和用户显示屏上被曝光。
密码算法
确定加密需求后,我们需选择特定的密码算法。根据我们的现有应用要求,相关选项受到一些严格限制。 该教程密码管理程序必须在英特尔 SGX 和非英特尔 SGX 平台上提供无缝用户体验,不可依赖第三方库。 这意味着我们需要选择一种算法以及支持的密钥和验证标签大小,这对于 Windows CNG API 和英特尔 SGX 可信密码库都一样。 实际上,我们只有一个选项: 高级加密标准-Galois 计算器模式 (AES-GCM),带有 128 位密钥。 这可以说是该应用中可使用的最佳加密模式,尤其是因为 128 位 GCM 的有效验证标签强度小于 128 位,但它足以我们的目的。 请记住:我们的目标不是创建一款商用产品,而是提供一款实用的英特尔 SGX 开发学习工具。
选择 GCM 时,我们同时做出了一些其他的设计决策,包括 IV 长度(12 字节最适合该算法)和验证标签。
加密密钥和用户身份验证
选择加密方法后,我们可将关注点转向加密密钥和用户身份验证。 用户如何在密码管理程序中进行身份验证并解锁密码库?
简单的方法是直接使用密钥推导函数 (KDF) 从用户密码中推导加密密钥。 不过,这个简单的方法虽然有效,却存在一个缺点:如果用户修改了其密码,加密密钥也会随之改变。 因此,我们将采用更为常见的做法:对加密密钥进行加密。
在该方法中,主加密密钥使用高质量熵源随机生成,绝不会变化。 用户密码用于推导辅助加密密钥,后者用户对主密钥进行加密。 这种方法具备一些重要优势:
- 当用户密码改变时,数据无需再次加密
- 加密密钥绝不会改变,因此理论上它能以十六进制表示法进行记录,锁定在物理安全的位置。 即使当用户忘记其密码时,数据仍可解密。 由于密钥绝不会改变,因此它只需记录一次。
- 理论上,这种方法满足用户访问数据的需求绰绰有余。 每位用户可使用自己的密码加密主密钥的副本。
所有这些措施并非都需要用于教程密码管理程序或与其相关,但构成了一种有效的安全实践。
主密钥被称为密码库密钥,通过用户密码推导的辅助密钥被称为总密钥。 用户可输入密码进行身份验证,密码管理程序可从中推导出总密钥。 如果总密钥成功解密密码库密钥,用户则通过了身份验证,密码库被解密。 如果密码不正确,密码库密钥解密失败,因此密码库不可解密。
最后一个要求,即根据 SHA-256 构建 KDF,源于我们发现的一项限制,即哈希算法同时支持 Windows CNG API 和英特尔 SGX 可信密码库。
帐户详情
最后一个高级要求与在密码库中实际存储什么内容相关。 对于该教程,我们将力求简单。 图 2 所示为主 UI 屏幕的早期模型。
图 2:教程密码管理程序主屏幕的早期模型。
最后一个要求与简化代码相关。 通过固定密码库中存储账户的数量,我们可更轻松地确定密码库大小的上限。 这在我们开始设计安全区时至关重要。 当然,实际的密码管理程序不具备这一优势,但它可满足本教程的目的。
即将推出
在本教程第三部分,我们将详细探讨如何针对英特尔 SGX 设计教程密码管理程序。 我们将确定机密(应用的这一部分应包含在安全区内)、安全区如何与核心应用交互以及安全区如何影响对象模型。 敬请关注!
阅读本系列第一部分教程英特尔® Software Guard Extensions 教程系列: 第一部分,英特尔® SGX 基础,或在介绍英特尔® Software Guard Extensions 教程系列文章中查找所有已发表教程的列表。