130

如何使用机器的 TPM 模块加密字节?

加密保护数据

Windows 提供了一个(相对)简单的 API 来使用 API 加密 blob CryptProtectData,我们可以将其包装一个易于使用的函数:

public Byte[] ProtectBytes(Byte[] plaintext)
{
   //...
}

的细节ProtectBytes不如您可以很容易地使用它的想法重要:

  • 这是我想通过保存在System
  • 把加密的 blob 还给我

返回的blob是一个未记录的文档结构,其中包含解密和返回原始数据所需的一切(散列算法、密码算法、盐、HMAC 签名等)。

ProtectBytes为了完整起见,这是使用Crypt API保护字节的示例伪代码实现:

public Byte[] ProtectBytes(Byte[] plaintext)
{
   //Setup our n-byte plaintext blob
   DATA_BLOB dataIn;
   dataIn.cbData = plaintext.Length;
   dataIn.pbData = Addr(plaintext[0]);

   DATA_BLOB dataOut;

   //dataOut = EncryptedFormOf(dataIn)
   BOOL bRes = CryptProtectData(
         dataIn,
         null,     //data description (optional PWideChar)
         null,     //optional entropy (PDATA_BLOB)
         null,     //reserved
         null,     //prompt struct
         CRYPTPROTECT_UI_FORBIDDEN || CRYPTPROTECT_LOCAL_MACHINE,
         ref dataOut);
   if (!bRes) then
   {
      DWORD le = GetLastError();
      throw new Win32Error(le, "Error calling CryptProtectData");
   }

   //Copy ciphertext from dataOut blob into an actual array
   bytes[] result;
   SetLength(result, dataOut.cbData);
   CopyMemory(dataOut.pbData, Addr(result[0]), dataOut.cbData);

   //When you have finished using the DATA_BLOB structure, free its pbData member by calling the LocalFree function
   LocalFree(HANDLE(dataOut.pbData)); //LocalFree takes a handle, not a pointer. But that's what the SDK says.
}

如何对 TPM 做同样的事情?

上面的代码仅用于加密本地机器的数据。使用System帐户作为密钥生成器对数据进行加密(细节虽然有趣,但并不重要)。最终结果是我可以加密只能由本地机器解密的数据(例如硬盘加密主密钥)。

现在是时候更进一步了。我想加密一些只能由本地 TPM 解密的数据(例如硬盘加密主密钥)。换句话说,我想用Windows 中的 TPM 替换下面针对 Android 的框图中的 Qualcomm 可信执行环境 ( TEE ):

在此处输入图像描述

注意:我意识到 TPM 不进行数据签名(或者如果这样做,它不保证对相同的数据进行签名时每次都会给出相同的二进制输出)。这就是为什么我愿意将“RSA 签名”替换为“使用硬件绑定密钥加密 256 位 blob”

那么代码在哪里呢?

问题是 TPM 编程在 MSDN 上完全没有记录。没有可用于执行任何操作的 API。相反,您必须为自己找到一份Trusted Computing Group 的软件堆栈(又名 TSS),找出要发送到 TPM 的命令、有效负载、按什么顺序,然后调用Window 的Tbsip_Submit_Command函数直接提交命令:

TBS_RESULT Tbsip_Submit_Command(
  _In_     TBS_HCONTEXT hContext,
  _In_     TBS_COMMAND_LOCALITY Locality,
  _In_     TBS_COMMAND_PRIORITY Priority,
  _In_     const PCBYTE *pabCommand,
  _In_     UINT32 cbCommand,
  _Out_    PBYTE *pabResult,
  _Inout_  UINT32 *pcbOutput
);

Windows 没有更高级别的 API 来执行操作。

这相当于尝试通过向硬盘发出 SATA I/O 命令来创建文本文件

为什么不只使用裤子

可信计算组 (TCG) 确实定义了自己的 API:TCB 软件堆栈(TSS)。这个 API 的一个实现是由一些人创建的,叫做TrouSerS。然后一个人将该项目移植到 Windows

该代码的问题在于它不能移植到 Windows 世界中。例如,你不能在 Delphi 中使用它,你不能在 C# 中使用它。这个需要:

  • OpenSSL
  • 线程

我只想让代码用我的 TPM 加密一些东西。

以上CryptProtectData只需要函数体中的内容。

使用 TPM 加密数据的等效代码是什么?正如其他人所指出的,您可能必须查阅三本 TPM 手册,然后自己构建 blob。它可能涉及TPM_seal命令。虽然我觉得我不想数据,但是我想我想绑定一下:

绑定– 使用 TPM 绑定密钥加密数据,这是从存储密钥派生的唯一 RSA 密钥。 密封– 以与绑定类似的方式加密数据,但另外指定了 TPM 必须处于的状态才能解密数据(未密封)

我尝试阅读所需的三卷,以找到我需要的 20 行代码:

但我知道我在读什么。如果有任何类型的教程或示例,我可能会有机会。但我完全迷失了。

所以我们问 Stackoverflow

以同样的方式,我能够提供:

Byte[] ProtectBytes_Crypt(Byte[] plaintext)
{
   //...
   CryptProtectData(...); 
   //...
}

有人可以提供相应的等价物:

Byte[] ProtectBytes_TPM(Byte[] plaintext)
{
   //...
   Tbsip_Submit_Command(...);
   Tbsip_Submit_Command(...);
   Tbsip_Submit_Command(...);
   //...snip...
   Tbsip_Submit_Command(...);
   //...
}

除了在 LSA 中锁定的密钥之外System,在 TPM 中锁定的密钥是否相同?

研究开始

我不知道确切的绑定是什么意思。但是查看 TPM Main - 第 3 部分命令 - 规范版本 1.2,提到了bind

10.3 TPM_UnBind

TPM_UnBind 获取作为 Tspi_Data_Bind 命令结果的数据 blob 并将其解密以导出给用户。调用者必须授权使用将解密传入 blob 的密钥。TPM_UnBind 以逐块为基础进行操作,并且不知道一个块与另一个块之间的任何关系。

令人困惑的没有Tspi_Data_Bind命令。

研究工作

令人震惊的是,从来没有人费心记录 TPM 或其操作。就好像他们把所有的时间都花在了想出这个很酷的东西上来玩,但不想处理让它用于某些东西的痛苦步骤

从(现在)免费书籍TPM 2.0 实用指南开始:在新的安全时代使用可信平台模块

第 3 章 - TPM 2.0 快速教程

TPM 可以访问自己生成的私钥,因此它可以使用公钥加密密钥,然后将生成的 blob 存储在硬盘上。这样,TPM 可以保留几乎无限数量的可用密钥,但不会浪费宝贵的内部存储空间。存储在硬盘上的密钥可以擦除,但也可以备份,这在设计者看来是一个可以接受的折衷方案。

如何使用 TPM 的公钥加密密钥?

第 4 章 - 使用 TPM 的现有应用程序

应该使用 TPM 但不使用的应用程序

在过去的几年中,基于 Web 的应用程序的数量有所增加。其中包括基于 Web 的备份和存储。现在有大量公司提供此类服务,但据我们所知,这些服务的客户端都没有让用户将备份服务的密钥锁定到 TPM。如果这样做了,如果 TPM 密钥本身通过在多台机器上复制来备份它肯定会很好。这似乎是开发商的机会。

开发人员如何锁定 TPM 的密钥?

第 9 章 - 等级制度

用例:存储登录密码

典型的密码文件存储密码的加盐哈希。验证包括对提供的密码进行加盐和散列处理,并将其与存储的值进行比较。因为计算不包括秘密,所以它会受到对密码文件的离线攻击。

此用例使用 TPM 生成的 HMAC 密钥。密码文件存储加盐密码的 HMAC。验证包括对提供的密码进行加盐和 HMAC 处理,并将其与存储的值进行比较。由于离线攻击者没有 HMAC 密钥,因此攻击者无法通过执行计算来发起攻击。

可以工作。如果 TPM 有一个秘密 HMAC 密钥,并且只有我的 TPM 知道 HMAC 密钥,那么我可以用“HMAC”替换“签名(又名 TPM 用它的私钥加密)”。但在接下来的一行中,他完全颠倒了自己:

TPM2_Create,指定 HMAC 密钥

如果我必须指定 HMAC 密钥,这不是 TPM 机密。当您意识到这是关于 TPM 提供的加密实用程序的章节时,HMAC 密钥不是秘密的事实是有意义的。您不必自己编写 SHA2、AES、HMAC 或 RSA,您可以重新使用 TPM 已经存在的内容。

第 10 章 - 钥匙

作为一种安全设备,应用程序在使用密钥的同时将其安全地保存在硬件设备中的能力是 TPM 的最大优势。TPM 可以生成和导入外部生成的密钥。它支持非对称和对称密钥。

出色的!你怎么做呢!?

密钥生成器

可以说,TPM 的最大优势在于它能够生成加密密钥并在硬件边界内保护其秘密。密钥生成器基于 TPM 自己的随机数生成器,不依赖于外部随机源。因此,它消除了基于熵源不足的弱软件软件的弱点。

TPM是否能够生成加密密钥并在硬件边界内保护其机密?是这样,怎么样?

第 12 章 - 平台配置寄存器

授权 PCR

用例:将硬盘加密密钥密封到平台状态

如果 TPM 保护加密密钥,则全盘加密应用程序比将其存储在同一个磁盘上(仅受密码保护)要安全得多。首先,TPM 硬件具有反锤击保护(TPM 字典攻击保护的详细描述见第 8 章),使得对密码的暴力攻击变得不切实际。仅受软件保护的密钥更容易受到弱密码的攻击。其次,存储在磁盘上的软件密钥更容易被窃取。拿走磁盘(或磁盘的备份),你就得到了密钥。当 TPM 持有密钥时,整个平台,或者至少是磁盘和主板,都必须被盗。

密封允许密钥不仅受密码保护,而且受策略保护。典型的策略将密钥锁定为在密封时当前的 PCR 值(软件状态)。这假设第一次启动时的状态没有受到损害。首次启动时存在的任何预装恶意软件都将被测量到 PCR 中,因此密钥将被密封到受损的软件状态。不太信任的企业可能有一个标准的磁盘映像和代表该映像的 PCR 的印章。这些 PCR 值将在可能更受信任的平台上预先计算。更复杂的企业将使用 TPM2_PolicyAuthorize,并提供多个票证来授权一组受信任的 PCR 值。参见第 14 章,详细描述策略授权及其解决 PCR 脆性问题的应用。

尽管密码也可以保护密钥,但即使没有 TPM 密钥密码,也可以获得安全性。攻击者可以在不提供 TPMkey 密码的情况下启动平台,但在没有操作系统用户名和密码的情况下无法登录。OSsecurity 保护数据。攻击者可以启动替代操作系统,例如从实时 DVD 或 USB 记忆棒,而不是从硬盘驱动器,以绕过操作系统登录安全性。但是,这种不同的引导配置和软件会改变 PCR 值。由于这些新的 PCR 与密封值不匹配,TPM 不会释放解密密钥,因此硬盘驱动器无法解密。

出色的!这正是我碰巧想要的用例。这也是微软使用 TPM 的用例。我该怎么做!?

所以我读了整本书,它没有提供任何有用的信息。这令人印象深刻,因为它有 375 页。你想知道这本书包含什么 - 回顾它,我不知道。

因此,我们放弃了 TPM 编程的权威指南,转而使用 Microsoft 的一些文档:

来自Microsoft TPM Platform Crypto-Provider Toolkit。它确切地提到了我想要做的事情:

背书密钥或 EK

EK 旨在为平台提供可靠的加密标识符。企业可能维护属于其企业中所有 PC 的 TPM 的背书密钥的数据库,或者数据中心结构控制器可能具有所有刀片中的 TPM 的数据库。在 Windows 上,您可以使用“Windows 8 中的平台加密提供程序”部分中描述的 NCrypt 提供程序来读取 EK 的公共部分。

TPM 内部的某处是 RSA 私钥。那把钥匙被锁在那里——永远不会被外界看到。我希望 TPM 用它的私钥签署一些东西(即用它的私钥加密它)。

所以我想要可能存在的最基本的操作:

在此处输入图像描述

用你的私钥加密一些东西。我什至(还)没有要求更复杂的东西:

  • 根据 PCR 状态“密封”它
  • 创建密钥并将其存储在易失性或非易失性内存中
  • 创建对称密钥并尝试将其加载到 TPM

我要求 TPM 可以做的最基本的操作。为什么无法获得有关如何操作的任何信息?

我可以得到随机数据

当我说 RSA 签名是 TPM 可以做的最基本的事情时,我想我是在胡说八道。可以要求 TPM 做的最基本的事情是给我随机字节已经弄清楚该怎么做:

public Byte[] GetRandomBytesTPM(int desiredBytes)
{
   //The maximum random number size is limited to 4,096 bytes per call
   Byte[] result = new Byte[desiredBytes];

   BCRYPT_ALG_HANDLE hAlgorithm;

   BCryptOpenAlgorithmProvider(
         out hAlgorithm,
         BCRYPT_RNG_ALGORITHM, //AlgorithmID: "RNG"
         MS_PLATFORM_CRYPTO_PROVIDER, //Implementation: "Microsoft Platform Crypto Provider" i.e. the TPM
         0 //Flags
   );
   try
   {                
      BCryptGenRandom(hAlgorithm, @result[0], desiredBytes, 0);
   }
   finally
   {
      BCryptCloseAlgorithmProvider(hAlgorithm);
   }

   return result;
}

花哨的东西

我意识到使用 TPM 的人数非常少。这就是为什么 Stackoverflow 上没有人有答案的原因。所以我真的不能太贪婪地为我的常见问题找到解决方案。但我真正想做的是“密封”一些数据:

在此处输入图像描述

  • 向 TPM 提供一些数据(例如 32 字节的密钥材料)
  • 让 TPM 加密数据,返回一些不透明的 blob 结构
  • 稍后要求 TPM 解密 blob
  • 仅当 TPM 的 PCR 寄存器与加密期间相同时,解密才会起作用。

换句话说:

Byte[] ProtectBytes_TPM(Byte[] plaintext, Boolean sealToPcr)
{
   //...
}

Byte[] UnprotectBytes_TPM(Byte[] protectedBlob)
{
   //...
}

下一代密码学(Cng,又名 BCrypt)支持 TPM

Windows 中最初的 Cryptography API 被称为 Crypto API。

从 Windows Vista 开始,Crypto API 已被Cryptography API: Next Generation(内部称为BestCrypt,缩写为BCrypt ,不要与密码散列算法混淆)取代。

Windows 附带两个 BCrypt提供程序

Platform Crypto提供程序没有记录在 MSDN 上,但确实有来自 2012 Microsoft Research 站点的文档:

TPM 平台加密提供者工具包

TPM 平台加密提供程序和工具包包含用于在 Windows 8 中使用 TPM 相关功能的示例代码、实用程序和文档。描述的子系统包括 TPM 支持的 Crypto-Next-Gen (CNG) 平台加密提供程序,以及证明服务提供程序如何可以使用新的 Windows 功能。支持基于 TPM1.2 和 TPM2.0 的系统。

Microsoft 的意图似乎是通过 Cryptography NG API 的Microsoft Platform Crypto Provider来展示 TPM 加密功能。

使用 Microsoft BCrypt 的公钥加密

鉴于:

一个前进的方向可能是弄清楚如何使用Microsoft Cryptography Next Gen API进行数字签名。

我的下一步将是使用标准提供程序(MS_PRIMITIVE_PROVIDER)使用 RSA 公钥在 BCrypt 中进行加密。例如:

  • modulus: 0xDC 67 FA F4 9E F2 72 1D 45 2C B4 80 79 06 A0 94 27 50 8209 DD 67 CE 57 B8 6C 4A 4F 40 9F D2 D1 69 FB 995D 85 0C 07 A1 F9 47 1B 7 F 16 2 A B9 F6 58 36 37 99 29 AA 4F A8 12 E8 4F C7 82 2B 9D 72 2A 9C DE 6F C2 EE 12 6D CF F0 F2 B8 C4 DD 7C 5C 1A C8 17 51 A9 AC DF 08 22 04 9D 2B D7 F9 4B 09 DE 9 EB 5C 51 1A D8 F8 F9 56 9E F8 FB 37 9B 3F D3 74 65 24 0D FF 34 75 57 A4 F5 BF 55
  • publicExponent: 65537

使用该代码功能,我可能能够切换到使用 TPM 提供程序 ( MS_PLATFORM_CRYPTO_PROVIDER)。

2016 年 2 月 22 日:随着 Apple 被迫帮助解密用户数据,人们对如何让 TPM 执行最简单的任务——加密某些东西——重新产生了兴趣。

这大致相当于每个人都拥有一辆车,但没有人知道如何启动它。它可以做非常有用和酷的事情,只要我们能通过Step 1

微软密钥存储 API

Microsoft 的TPM Base Services存档文档主页说我们可能希望使用密钥存储 API:

笔记

TPM 可用于密钥存储操作。但是,我们鼓励开发人员将密钥存储 API 用于这些场景。密钥存储 API提供了创建、签名或加密以及保存加密密钥的功能,对于这些目标场景,它们比 TBS 更高级且更易于使用。

Key Storage API档案的介绍说:

密钥存储架构

CNG 提供了一个私钥存储模型,允许适应当前和未来创建使用密码学特性(如公钥或私钥加密)的应用程序的需求,以及存储密钥材料的需求。密钥存储路由器是该模型的中心例程,在 Ncrypt.dll 中实现。应用程序通过密钥存储路由器访问系统上的密钥存储提供程序 (KSP),该路由器对应用程序和存储提供程序本身隐藏了诸如密钥隔离等细节。下图展示了 CNG 密钥隔离架构的设计和功能。

在此处输入图像描述

他们注意到支持硬件安全模块(可能是 TPM 的术语):

如上所述,可以支持广泛的硬件存储设备。在每种情况下,所有这些存储设备的接口都是相同的。它包括执行各种私钥操作的功能以及与密钥存储和管理有关的功能。

我唯一不知道的是您是否必须要求使用 HSM,或者它是否在可用时自动发生(以及如何知道它何时不可用 - 所以您无论如何都不要尝试继续)。

奖金阅读

4

4 回答 4

7

底漆

以下是关于 TPM 1.2 的内容。请记住,Microsoft 要求所有未来的 Windows 版本都需要 TPM 2.0。2.0一代与1.2有着根本的不同

由于 TPM 设计原则,没有单线解决方案。将 TPM 视为资源有限的微控制器。它的主要设计目标是便宜,同时仍然安全。因此,TPM 被剥夺了安全操作所不需要的所有逻辑。因此,TPM 仅在您至少有一些或多或少的软件时才能工作,并以正确的顺序发出大量命令。这些命令序列可能会变得非常复杂。这就是 TCG 使用定义明确的 API 指定 TSS 的原因。如果您想采用 Java 方式,甚至还有高级Java API。我不知道 C# / .net 的类似项目

发展

在您的情况下,我建议您查看 IBM 的软件 TPM。

在包中,您会发现 3 个非常有用的组件:

  • 软件 TPM 模拟器
  • 一个轻量级的 tpm 库
  • 一些基本的命令行实用程序

您不一定需要软件 TPM 仿真器,您也可以连接到机器的 HW TPM。但是,您可以拦截发出的命令并查看响应,从而了解它们是如何组装的以及它们如何与命令规范相对应。

高水平

先决条件:

  1. TPM 已激活
  2. 已加载 TPM 驱动程序
  3. 您已获得 TPM 的所有权

为了密封 blob,您需要执行以下操作:

  1. 创建密钥
  2. 将密钥块存储在某处
  3. 确保密钥已加载到 TPM
  4. 密封斑点

要开封,您需要:

  1. 获取密钥块
  2. 将密钥加载到 TPM
  3. 打开密封的斑点

您可以将密钥块存储在用于存储受保护字节的数据结构中。

您需要的大多数 TPM 命令都是经过授权的。因此,您需要在需要时建立授权会话。AFAIR 这些主要是 OSAP 会议。

TPM 命令

目前我无法运行调试版本,因此无法为您提供确切的顺序。因此,请考虑这是您必须使用的命令的无序列表:

  • TPM_OSAP
  • TPM_CreateWrapKey
  • TPM_LoadKey2
  • TPM_Seal

如果您也想读取当前 PCR 值:

  • TPM_PCRRead
于 2015-05-28T09:11:23.743 回答
6

如何使用机器的 TPM 模块加密字节?

取决于您的意图和情况:

  • 您有什么样的 TPM(1 个家庭或 2 个家庭)?
  • TPM 处于什么状态?它被拥有了吗?是否已配置?
  • 你的编程语言是什么?
  • 您要加密还是签名?(这与问题的其余部分含糊不清)
  • 您要加密的数据有多大?
  • 您要使用对称密钥还是非对称密钥?
  • 您要使用 TPM 上已存在的密钥,还是要先创建密钥?
  • 您所说的“加密”是指“包装密钥”吗?
  • 是否要将加密数据锁定到系统配置,以便只有在系统回到相同配置时才能解密?
  • 是否需要授权才能解密?
  • 也许您根本不需要加密,而是将数据存储在 TPM 中?
  • 如果您将数据存储在 TPM 中,您是否需要授权,或者系统处于特定配置中以进行检索?

这些用例中的每一个(以及更多)——或它们的组合——都代表了不同的实现路径。将 TPM 视为加密设备的瑞士军刀:没有什么是您不能用它做的,但易用性会因多功能性而受到影响。这个问题在加密、签名和锁定到系统配置之间不断变化,但这个答案的主要部分将考虑 Seal 命令来满足问题中描述的大部分需求。

现在是时候更进一步了。我想加密一些只能由本地 TPM 解密的数据(例如硬盘加密主密钥)。

这就是 Bind 命令的用途(被 TPM 2 的 Create 命令取代)。您加载从 TPM 绑定密钥派生的密钥并使用它(或直接使用硬件绑定密钥)进行加密。这种方式只能通过访问相同的 TPM 来解密数据。

换句话说,我想用 Windows 中的 TPM 替换下面针对 Android 的框图中的 Qualcomm 可信执行环境 (TEE):

不确定复制整个过程是否是个好主意。一方面,不需要在过程中的任何地方使用签名操作。看起来,在开发 Android 5 时,Keystore API 仅限于签名和验证操作。我最好的猜测是磁盘加密团队尽最大努力使用他们所拥有的,并设计了一种算法,其中一个中间密钥是通过签名操作派生的,使用存储的 TEE 密钥,从而将整个过程与硬件联系起来——绑定密钥仅在平台上可用——因为签名是当时唯一的方法。但是,如果可以访问 TPM,则无需以这种方式限制自己,它为您提供了比您知道自己需要的更多的功能!

我意识到 TPM 不进行数据签名

这是错误的,两个版本的 TPM 都支持签名。

(或者如果是这样,它不保证签署相同的数据每次都会给出相同的二进制输出)

这是没有意义的。使用相同的密钥对相同的数据进行签名产生相同的签名。您可能会将签名操作与引用操作混淆,后者会混入随机数。

这就是为什么我愿意将“RSA 签名”替换为“使用硬件绑定密钥加密 256 位 blob”。

这实际上应该是首选选项,尽管 TPM 两者都可以。看上面。

问题是 TPM 编程在 MSDN 上完全没有记录。没有可用于执行任何操作的 API。

不幸的是,没有太多可记录的。Win API 仅限于几个 TBS 函数,这些函数从驱动程序中删除了一层。

相反,您必须为自己找到一份 Trusted Computing Group 的软件堆栈(又名 TSS),弄清楚要发送到 TPM 的命令、有效负载、按什么顺序,然后调用 Window 的 Tbsip_Submit_Command 函数直接提交命令:

实际上,不,如果您有 TSS,则不必使用Tbsip_submit_Command(). 这就是拥有 TSS 的全部意义——低级别的细节被抽象掉了。

Windows 没有更高级别的 API 来执行操作。

TPM 1 仍然如此,但对于 TPM 2,有TSS.MSR

这相当于尝试通过向硬盘发出 SATA I/O 命令来创建文本文件。

正确的。

为什么不直接使用 Trousers ... 该代码的问题在于它不能移植到 Windows 世界中。例如,你不能在 Delphi 中使用它,你不能在 C# 中使用它。它需要:OpenSSL、pThread

目前尚不清楚这是一个无法克服的挑战。通过互操作访问 TrouSerS 应该比重写所有数据结构代码更可取。另外,doTSS在写这个问题的时候也有。

使用 TPM 加密数据的等效代码是什么?它可能涉及 TPM_seal 命令。虽然我觉得我不想封数据,但是我想我想绑定一下:

该问题包含描述两个命令之间差异的引用,因此不应有太多混淆。密封类似于绑定,但增加了系统状态必须相同才能使数据解封的约束。

以同样的方式,我能够提供:

Byte[] ProtectBytes_Crypt(Byte[] plaintext)
{
   //...
   CryptProtectData(...); 
   //...
}

有人可以提供相应的等价物:

Byte[] ProtectBytes_TPM(Byte[] plaintext)
{
   //...
   Tbsip_Submit_Command(...);
   Tbsip_Submit_Command(...);
   Tbsip_Submit_Command(...);
   //...snip...
   Tbsip_Submit_Command(...);
   //...
}

除了在系统 LSA 中锁定的密钥之外,在 TPM 中锁定的密钥是否相同?

首先,值得指出的是,TPM 有两个主要版本,彼此之间完全不兼容。因此,实际上您为 TPM 1 编写的任何代码都不会适用于 TPM 2。TBS API 是两者之间唯一的通用代码,并且公平地说,这可能是该 API 从未增长的原因之一。答案的主要部分将提供 TPM 1 的代码,原因有两个:

  • 该问题包含 TPM 1 的特定概念,因此使用 TPM 1 的人更有可能在这里搜索它们
  • TPM 2 有一个 Microsoft 实现的 TSS。

其次,让我们把问题说得更具体一些。我重新解释如下:

How do I write code in C#, using only the TBS API, to interface with
an already owned and provisioned TPM to, without user interaction,
encrypt no more than 128 bytes of arbitrary data with an asymmetric
key already resident in the TPM and bound to it, but not protected
with a password, so that in order to decrypt the data the system may
need to be in the same state it was in at encryption time based on an
easily configurable variable?

Seal 命令最适合于此,因为当 PCR 选择大小设置为零时,它执行与 Bind 命令相同的功能,但可以轻松更改 PCR 选择以包含您可能需要的任何 PCR。这让人不禁想知道为什么 Bind 命令被包含在规范中,并且如前所述,它已在 TPM 2 规范中被删除,并且两者结合在一个 Create 命令中。

以下是使用 TPM 1.2 Seal 命令加密仅使用 TBS 函数的数据的 C# 代码(注意:此代码未经测试,未经调试可能无法工作)

[DllImport ("tbs.dll")]
unsafe static extern UInt32 Tbsi_Context_Create (UInt32 * version, IntPtr * hContext);

[DllImport ("tbs.dll")]
unsafe static extern UInt32 Tbsip_Context_Close (IntPtr hContext);

[DllImport ("tbs.dll")]
unsafe static extern UInt32 Tbsip_Submit_Command (
    IntPtr hContext, UInt32 Locality, 
    UInt32 Priority, 
    byte * pCommandBuf, 
    UInt32 CommandBufLen, 
    byte * pResultBuf, 
    UInt32 * pResultBufLen);

byte[] ProtectBytes_TPM (byte[] plaintext) {

    void AddUInt32Reversed (byte[] a, System.UInt32 o, ref int i) {
        byte[] bytes = System.BitConverter.GetBytes (o);
        Array.Reverse (bytes);
        Array.Copy (bytes, 0, a, i, bytes.Length);
        i += bytes.Length;
    }
    void AddUInt16Reversed (byte[] a, System.UInt16 o, ref int i) {
        byte[] bytes = System.BitConverter.GetBytes (o);
        Array.Reverse (bytes);
        Array.Copy (bytes, 0, a, i, bytes.Length);
        i += bytes.Length;
    }
    void AddBool (byte[] a, byte b, ref int i) {
        a[i] = b;
        i += 1;
    }
    void AddBlob (byte[] a, byte[] b, ref int i) {
        Array.Copy (b, 0, a, i, b.Length);
        i += b.Length;
    }
    byte[] Xor (byte[] text, byte[] key) {
        byte[] xor = new byte[text.Length];
        for (int i = 0; i < text.Length; i++) {
            xor[i] = (byte) (text[i] ^ key[i % key.Length]);
        }
        return xor;
    }

    int offset;

    Random rnd = new Random ();

    IntPtr hContext = IntPtr.Zero;
    unsafe {
        UInt32 version = 1;
        IntPtr handle = hContext;
        UInt32 result = Tbsi_Context_Create ( & version, & handle);

        if (result == 0) {
            hContext = handle;
        }
    }

    byte[] cmdBuf = new byte[768];

    //OSAP
    System.UInt32 outSize;

    byte[] oddOsap = new byte[20];
    byte[] evenOsap = new byte[20];
    byte[] nonceEven = new byte[20];
    byte[] nonceOdd = new byte[20];
    System.UInt32 hAuth = 0;

    offset = 0;
    AddUInt16Reversed (cmdBuf, 0x00C1, ref offset);
    offset = 6;
    AddUInt32Reversed (cmdBuf, 0x0000000B, ref offset);

    offset = 2 + 4 + 4; //2 for tag, 4 for size and 4 for command code

    AddUInt16Reversed (cmdBuf, 0x0004, ref offset); //Entity Type SRK = 0x0004
    AddUInt32Reversed (cmdBuf, 0x40000000, ref offset); //Entity Value SRK = 0x40000000
    rnd.NextBytes (oddOsap);
    AddBlob (cmdBuf, oddOsap, ref offset);
    uint cmdSize = (System.UInt32) offset;
    offset = 2;
    AddUInt32Reversed (cmdBuf, cmdSize, ref offset);

    outSize = (System.UInt32) (Marshal.SizeOf (hAuth) + nonceEven.Length + evenOsap.Length);

    byte[] response = new byte[outSize];
    unsafe {
        UInt32 result = 0;

        //uint cmdSize = (uint)offset;
        uint resSize = outSize;
        fixed (byte * pCmd = cmdBuf, pRes = response) {
            result = Tbsip_Submit_Command (hContext, 0, 200, pCmd, cmdSize, pRes, & resSize);
        }
    }

    byte contSession = 0;
    System.UInt32 hKey = 0x40000000; //TPM_KH_SRK;
    System.UInt32 pcrInfoSize = 0;
    byte[] srkAuthdata = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
    uint inDataSize = (uint) plaintext.Length;

    offset = 2 + 4 + 4; //2 for tag, 4 for size and 4 for return code
    byte[] hauthbytes = new byte[Marshal.SizeOf (hAuth)];
    Array.Copy (response, offset, hauthbytes, 0, hauthbytes.Length);
    Array.Reverse (hauthbytes);
    hAuth = System.BitConverter.ToUInt32 (hauthbytes, 0);
    offset += Marshal.SizeOf (hAuth);
    Array.Copy (response, offset, nonceEven, 0, nonceEven.Length);
    offset += nonceEven.Length;
    Array.Copy (response, offset, evenOsap, 0, evenOsap.Length);

    //shared-secret = HMAC(srk_auth, even_osap || odd_osap)
    byte[] sharedSecretBuf = new byte[evenOsap.Length + oddOsap.Length];
    Array.Copy (evenOsap, 0, sharedSecretBuf, 0, evenOsap.Length);
    Array.Copy (oddOsap, 0, sharedSecretBuf, evenOsap.Length, oddOsap.Length);
    System.Security.Cryptography.HMACSHA1 sharedSecretHmac = new System.Security.Cryptography.HMACSHA1 (srkAuthdata);
    byte[] sharedSecret = sharedSecretHmac.ComputeHash (sharedSecretBuf);

    byte[] authSha1InBuf = new byte[sharedSecret.Length + nonceEven.Length];
    Array.Copy (sharedSecret, 0, authSha1InBuf, 0, sharedSecret.Length);
    Array.Copy (nonceEven, 0, authSha1InBuf, sharedSecret.Length, nonceEven.Length);
    System.Security.Cryptography.SHA1Managed sha1 = new System.Security.Cryptography.SHA1Managed ();
    byte[] authSha1 = sha1.ComputeHash (authSha1InBuf);
    byte[] encAuth = Xor (srkAuthdata, authSha1);

    //inParamDigest = sha1(1S ~ 6S) 
    int paramInDigestInBufSize =
        sizeof (System.UInt32) + 
        encAuth.Length +
        Marshal.SizeOf (pcrInfoSize) +
        Marshal.SizeOf (inDataSize) +
        (int) inDataSize;
    byte[] paramInDigestInBuf = new byte[paramInDigestInBufSize];
    offset = 0;
    AddUInt32Reversed (paramInDigestInBuf, 0x00000017, ref offset);
    AddBlob (paramInDigestInBuf, encAuth, ref offset);
    AddUInt32Reversed (paramInDigestInBuf, 0x0, ref offset); //PCR info size
    AddUInt32Reversed (paramInDigestInBuf, inDataSize, ref offset);
    AddBlob (paramInDigestInBuf, plaintext, ref offset);

    byte[] paramInDigest = sha1.ComputeHash (paramInDigestInBuf);

    int pubAuthInBufSize = paramInDigest.Length + nonceEven.Length + nonceOdd.Length + Marshal.SizeOf (contSession);
    byte[] pubAuthInBuf = new byte[pubAuthInBufSize];

    offset = 0;
    AddBlob (pubAuthInBuf, paramInDigest, ref offset);
    AddBlob (pubAuthInBuf, nonceEven, ref offset);
    AddBlob (pubAuthInBuf, nonceOdd, ref offset);
    AddBool (pubAuthInBuf, contSession, ref offset);
    System.Security.Cryptography.HMACSHA1 pubAuthHmac = new System.Security.Cryptography.HMACSHA1 (sharedSecret);
    byte[] pubAuth = pubAuthHmac.ComputeHash (pubAuthInBuf);

    //Seal
    offset = 0;
    AddUInt16Reversed (cmdBuf, 0x00C2, ref offset); // TPM_TAG_RQU_AUTH1_COMMAND;
    offset = 6;
    AddUInt32Reversed (cmdBuf, 0x00000017, ref offset); // TPM_ORD_SEAL;
    offset = 2 + 4 + 4; //2 for tag, 4 for size and 4 for command code

    AddUInt32Reversed (cmdBuf, hKey, ref offset);
    AddBlob (cmdBuf, encAuth, ref offset);
    AddUInt32Reversed (cmdBuf, pcrInfoSize, ref offset);
    AddUInt32Reversed (cmdBuf, inDataSize, ref offset);
    AddBlob (cmdBuf, plaintext, ref offset);

    AddUInt32Reversed (cmdBuf, hAuth, ref offset);
    AddBlob (cmdBuf, nonceOdd, ref offset);
    AddBool (cmdBuf, contSession, ref offset);
    AddBlob (cmdBuf, pubAuth, ref offset);
    cmdSize = (System.UInt32) offset;
    offset = 2;
    AddUInt32Reversed (cmdBuf, cmdSize, ref offset);

    outSize = 768;
    uint responseSize = 0;

    response = new byte[outSize];
    unsafe {
        UInt32 result = 0;

        uint resSize = outSize;
        fixed (byte * pCmd = cmdBuf, pRes = response) {
            result = Tbsip_Submit_Command (hContext, 0, 200, pCmd, cmdSize, pRes, & resSize);
        }
        responseSize = resSize;
    }

    byte[] retBuffer = new byte[responseSize - 10];
    Array.Copy (response, 10, retBuffer, 0, retBuffer.Length);
    Tbsip_Context_Close (hContext);
    return retBuffer;

}

代码分析:

[DllImport ("tbs.dll")]
...

这些是 Tbs.h 中为数不多的一些功能,也是我们将在此处使用的唯一功能。它们基本上允许您打开设备的句柄并通过发送和接收原始字节与它进行通信。

    void AddUInt32Reversed (byte[] a, System.UInt32 o, ref int i) { ... }
    void AddUInt16Reversed (byte[] a, System.UInt16 o, ref int i) { ... }
    void AddBool (byte[] a, byte b, ref int i) { ... }
    void AddBlob (byte[] a, byte[] b, ref int i) { ... }

TPM 是大端,Windows 是小端。因此,对于我们发送的任何数据,都必须颠倒字节顺序。我们只需要担心在这里反转 32 位和 16 位无符号整数。

    ...
    UInt32 result = Tbsi_Context_Create ( & version, & handle);
    ...

这里我们使用Tbsi_Context_Create()打开一个句柄来与 TPM 对话。该TBS_CONTEXT_PARAMS参数只是一个带有一个无符号 32 位 int 字段的 C 结构,必须将其设置为 1 才能与 TPM 1.2 实例通信,并且我们将其设置为。

    byte[] cmdBuf = new byte[768];

这在TPM PC Client Spec中指定为最小缓冲区大小。这对我们这里的需求来说已经绰绰有余了。

TPM 1.2 规范第 3 部分说:

TPM_Seal requires the encryption of one parameter (“Secret”). For the
sake of uniformity with other commands that require the encryption of
more than one parameter, the string used for XOR encryption is
generated by concatenating a nonce (created during the OSAP session)
with the session shared secret and then hashing the result.

我们需要使用在 OSAP 会话期间生成的随机数对这个“秘密”参数进行异或加密。Seal 命令输入句柄之一也是 OSAP 句柄:

The authorization session handle used for keyHandle authorization.
Must be an OSAP session for this command.

所以我们需要先建立这个 OSAP 会话。OSAP 在TPM 1.2 Spec Part 1中进行了描述。OSAP 或 Object-Specific Authorization Protocol 被发明来处理您想要使用需要多次授权但不想每次都提供授权的 TPM 对象的用例:改为使用 OSAP 会话,这依赖于关于“共享秘密”的概念,即HMAC它将对象授权数据与每一方生成的随机数混合在一起,以防止回复攻击。因此,“共享秘密”只有在这个会话中的两个方知道:发起会话的一方(用户)和接受它的一方(TPM);此外,双方必须拥有相同的对象授权数据,才能使“共享秘密”相同;此外,一个会话中使用的“共享密钥”在另一个会话中将无效。该规范中的图表描述了该过程:

OSAP

在这种特殊情况下,我们不会使用多个会话(事实上,该参数被 Seal 命令忽略!)并且我们将使用的密钥不需要授权,但不幸的是,我们仍然受规范的约束以建立 OSAP会议。

    offset = 0;
    AddUInt16Reversed (cmdBuf, 0x00C1, ref offset);
    offset = 6;
    AddUInt32Reversed (cmdBuf, 0x0000000B, ref offset);

    offset = 2 + 4 + 4; //2 for tag, 4 for size and 4 for command code

    AddUInt16Reversed (cmdBuf, 0x0004, ref offset); //Entity Type SRK = 0x0004
    AddUInt32Reversed (cmdBuf, 0x40000000, ref offset); //Entity Value SRK = 0x40000000
    rnd.NextBytes (oddOsap);
    AddBlob (cmdBuf, oddOsap, ref offset);
    uint cmdSize = (System.UInt32) offset;

TPM_OSAP 命令操作数是:

TPM_OSAP 操作数

每个 TPM 1.2 命令的布局如下:

  2 bytes       4 bytes             4 bytes
+---------+------------------+------------------+---------------------------
|   Tag   |       Size       |   Command code   |    Command body    ....
+---------+------------------+------------------+---------------------------

标记是一个两字节的值,指示后面是输入还是输出,以及命令参数后面是否有任何 auth 数据值。对于 TPM_OSAP,标签必须是 TPM_TAG_RQU_COMMAND (0x00C1) 根据规范,这意味着“没有授权的命令”。

Size 是一个四字节的值,以字节为单位指定命令的大小,包括标记和大小本身。一旦我们计算了它,我们将在稍后设置这个值。

命令代码是一个四字节的值,用作命令 ID:它告诉 TPM 如何解释命令的其余部分。我们这里的命令代码是 TPM_OSAP (0x0000000B)。

接下来要设置的两件事是实体类型和实体值。由于我们要使用 TPM 中已经存在的密钥,我们将使用实体类型“SRK”(0x0004),并且由于我们在假设 TPM 已经拥有的情况下工作,因此可以安全地假设它拥有根据规范在永久句柄 0x40000000 下加载 SRK,因此我们将使用此永久句柄值作为实体值。(SRK 代表“存储根密钥”,是大多数其他 TPM 拥有的密钥派生的根密钥)

    result = Tbsip_Submit_Command (hContext, 0, 200, pCmd, cmdSize, pRes, & resSize);

最后我们计算命令大小并设置它,然后发送命令。

    offset = 2 + 4 + 4; //2 for tag, 4 for size and 4 for return code
    byte[] hauthbytes = new byte[Marshal.SizeOf (hAuth)];
    Array.Copy (response, offset, hauthbytes, 0, hauthbytes.Length);
    Array.Reverse (hauthbytes);
    hAuth = System.BitConverter.ToUInt32 (hauthbytes, 0);
    offset += Marshal.SizeOf (hAuth);
    Array.Copy (response, offset, nonceEven, 0, nonceEven.Length);
    offset += nonceEven.Length;
    Array.Copy (response, offset, evenOsap, 0, evenOsap.Length);

我们应该从 TPM_OSAP 上的 TPM 获取的数据是:

TPM_OSAP 响应

所以我们回来:

  • 与我们的主命令 (Seal) 一起使用的授权句柄
  • nonceEven:TPM 生成的随机数,用于主命令
  • nonceEvenOSAP:OSAP 随机数,它是我们在发送 TPM_OSAP 命令之前在我们这边生成的随机数的反随机数。这两个随机数将用于生成“共享秘密”。

我们提取这些值并将它们存储在变量中。

    byte[] sharedSecretBuf = new byte[evenOsap.Length + oddOsap.Length];
    Array.Copy (evenOsap, 0, sharedSecretBuf, 0, evenOsap.Length);
    Array.Copy (oddOsap, 0, sharedSecretBuf, evenOsap.Length, oddOsap.Length);
    System.Security.Cryptography.HMACSHA1 sharedSecretHmac = new System.Security.Cryptography.HMACSHA1 (srkAuthdata);
    byte[] sharedSecret = sharedSecretHmac.ComputeHash (sharedSecretBuf);

然后我们计算“共享密钥”。根据规范,计算中的值是两个 OSAP 随机数(一个由用户生成,一个由 TPM 生成)和我们要使用的密钥的授权值 - SRK。按照惯例,SRK auth 值是“众所周知的 auth”:一个归零的 20 字节缓冲区。从技术上讲,当获得 TPM 的所有权时,可以将此值更改为其他值,但这在实践中并未这样做,因此我们可以放心地假设“众所周知的身份验证”值是好的。

接下来让我们看一下 TPM_Seal 命令的内容:

TPM_Seal

这些参数中的大多数都是微不足道的,除了其中两个:encAuthpubAuth. 让我们一一看看。

encAuth是“密封数据的加密 AuthData”。我们这里的 AuthData 是以前的“众所周知的身份验证”,但是是的,我们仍然必须对其进行加密。因为我们使用的是 OSAP 会话,所以它是按照 ADIP 或授权数据插入协议加密的。来自规范:“ADIP 允许创建新实体并安全插入新实体 AuthData。新 AuthData 的传输使用基于 OSAP 会话共享密钥的密钥加密。” 此外:“对于强制 XOR 加密算法,创建者使用 OSAP 共享密钥的 SHA-1 哈希和会话随机数构建加密密钥。创建者 XOR 使用加密密钥作为一次性密码对新的 AuthData 进行加密,并且将此加密数据与创建请求一起发送到 TPM。”

下图解释了 ADIP 的运作方式:

ADIP

pubAuth是“输入和 keyHandle 的授权会话摘要”。规范的第 1 部分,在“OIAP 和 OSAP 示例的参数声明”中解释了如何解释上面的 TPM_Seal 参数表:“HMAC # 列详细说明了在 HMAC 计算中使用的参数。参数 1S、2S 等被连接起来并且散列到 inParamDigest 或 outParamDigest,如果有两个授权会话,则隐式称为 1H1 和可能的 1H2。对于第一个会话,1H1、2H1、3H1 和 4H1 被连接和 HMAC'ed。对于第二个会话,1H2、2H2、3H2,和 4H2 是连接和 HMAC 的。” 因此,我们必须从上面和 TPM_Seal 序数对明文、其大小、PCR 信息大小进行哈希处理encAuth,然后使用两个随机数和“继续会话”布尔值使用 OSAP 对 HMAC 进行哈希处理”

把它们放在一个图表中:

pubAuth 计算

请注意我们如何在此代码中将“PCR 信息大小”设置为零,因为我们只想加密数据而不将其锁定到系统状态。但是,如果需要,提供“PCR 信息”结构是微不足道的。

    offset = 0;
    AddUInt16Reversed (cmdBuf, 0x00C2, ref offset); 
    offset = 6;
    AddUInt32Reversed (cmdBuf, 0x00000017, ref offset); // TPM_ORD_SEAL;
    ...
    result = Tbsip_Submit_Command (hContext, 0, 200, pCmd, cmdSize, pRes, & resSize);

最后我们构造命令并发送它。

    byte[] retBuffer = new byte[responseSize - 10];
    Array.Copy (response, 10, retBuffer, 0, retBuffer.Length);
    Tbsip_Context_Close (hContext);
    return retBuffer;

我们使用Tbsip_Context_Close()函数来关闭我们的通信句柄。

我们在此处按原样返回响应。理想情况下,您希望再次反转字节并通过重新计算resAuth值来验证它以防止中间人攻击。


令人困惑的是没有 Tspi_Data_Bind 命令。

这是因为 Tspi_Data_Bind 是 TSS 命令,而不是 TPM 命令。原因是因为它不需要秘密(只使用公钥),所以它可以在不涉及 TPM 的情况下完成。然而,这引起了混乱,甚至不需要机密的命令现在也包含在 TPM 2 规范中。

如何使用 TPM 的公钥加密密钥?

取决于 TPM 版本。使用 TPM 1.2 的 TPM_CreateWrapKey 命令。使用 TPM 2 的 TPM2_Create 命令。

开发人员如何锁定 TPM 的密钥?

在 TPM 中创建它,或者包装它,或者使用任何其他可用的方法。

TPM2_Create,指定 HMAC 密钥

书中的文字很混乱。你没有指定HMAC key,你指定你想要一个 HMAC key

HMAC 密钥不是秘密的事实是有道理的

不,这没有意义。钥匙是秘密的。

...使用密钥,同时将它们安全地保存在硬件设备中...太棒了!你怎么做呢!?

有一些命令可以为两个版本的 TPM 创建或导入密钥。对于 TPM 1,只有一个根密钥(SRK),您可以通过创建包装密钥来建立密钥层次结构。使用 TPM 2,您可以拥有多个主密钥或根密钥。

TPM 是否能够生成加密密钥并在硬件边界内保护其机密?是这样,怎么样?

看上面。

出色的!这正是我碰巧想要的用例。这也是微软使用 TPM 的用例。我该怎么做!?

可能取决于驱动器的类型。对于非 SED 驱动器,驱动器加密密钥可能使用 TPM 密钥进行包装。对于 SED 驱动器,Admin1 密码(或类似密码)由 TPM 密封。

背书密钥或 EK... TPM 内部的某处是 RSA 私钥。那把钥匙被锁在那里——永远不会被外界看到。我希望 TPM 用它的私钥签署一些东西(即用它的私钥加密它)。

EK 不是签名密钥——它是加密密钥。但是,它不是通用加密密钥:它只能在某些情况下使用

但我真正想做的是“密封”一些数据

看上面。

于 2019-12-26T16:49:33.810 回答
4

受信任和加密的密钥

可信密钥和加密密钥是添加到现有内核密钥环服务中的两种新密钥类型。这两种新类型都是可变长度对称密钥,在这两种情况下,所有密钥都是在内核中创建的,用户空间只能看到、存储和加载加密的 blob。可信密钥需要可信平台模块 (TPM) 芯片以提高安全性,而加密密钥可用于任何系统。为方便起见,所有用户级 blob 都以十六进制 ascii 显示和加载,并且经过完整性验证。

可信密钥使用 TPM 来生成和密封密钥。密钥在 TPM 中以 2048 位 RSA 密钥密封,并且可选择密封到指定的 PCR(完整性测量)值,并且仅在 PCR 和 blob 完整性验证匹配时才由 TPM 解封。加载的可信密钥可以使用新的(未来的)PCR 值进行更新,因此可以轻松地将密钥迁移到新的 pcr 值,例如在更新内核和 initramfs 时。同一个 key 可以在不同的 PCR 值下保存多个 blob,因此可以轻松支持多个引导。

默认情况下,受信任的密钥被密封在 SRK 下,它具有默认的授权值(20 个零)。这可以在取得所有权时使用裤子的实用程序进行设置:tpm_takeownership -u -z.

Usage:
    keyctl add trusted name "new keylen [options]" ring
    keyctl add trusted name "load hex_blob [pcrlock=pcrnum]" ring
    keyctl update key "update [options]"
    keyctl print keyid

    options:
    keyhandle= ascii hex value of sealing key default 0x40000000 (SRK)
    keyauth=   ascii hex auth for sealing key default 0x00...i
        (40 ascii zeros)
    blobauth=  ascii hex auth for sealed data default 0x00...
        (40 ascii zeros)
    blobauth=  ascii hex auth for sealed data default 0x00...
        (40 ascii zeros)
    pcrinfo=   ascii hex of PCR_INFO or PCR_INFO_LONG (no default)
    pcrlock=   pcr number to be extended to "lock" blob
    migratable= 0|1 indicating permission to reseal to new PCR values,
                default 1 (resealing allowed)

keyctl print返回密封密钥的 ascii 十六进制副本,采用标准 TPM_STORED_DATA 格式。新密钥的密钥长度始终以字节为单位。可信密钥可以是 32 - 128 字节(256 - 1024 位),上限是适合 2048 位 SRK (RSA) 密钥长度,并具有所有必要的结构/填充。

加密密钥不依赖于 TPM,并且速度更快,因为它们使用 AES 进行加密/解密。新密钥由内核生成的随机数创建,并使用指定的“主”密钥进行加密/解密。“主”密钥可以是可信密钥或用户密钥类型。加密密钥的主要缺点是,如果它们不植根于受信任的密钥中,它们的安全性仅与加密它们的用户密钥一样安全。因此,应该以尽可能安全的方式加载主用户密钥,最好是在启动的早期。

加密密钥的解密部分可以包含简单的对称密钥或更复杂的结构。更复杂结构的格式是特定于应用程序的,由“格式”标识。

Usage:
    keyctl add encrypted name "new [format] key-type:master-key-name keylen"
        ring
    keyctl add encrypted name "load hex_blob" ring
    keyctl update keyid "update key-type:master-key-name"

format:= 'default | ecryptfs'
key-type:= 'trusted' | 'user'

可信和加密密钥使用示例

创建并保存一个名为“kmk”、长度为 32 字节的可信密钥:

$ keyctl add trusted kmk "new 32" @u
440502848

$ keyctl show
Session Keyring
       -3 --alswrv    500   500  keyring: _ses
 97833714 --alswrv    500    -1   \_ keyring: _uid.500
440502848 --alswrv    500   500       \_ trusted: kmk

$ keyctl print 440502848
0101000000000000000001005d01b7e3f4a6be5709930f3b70a743cbb42e0cc95e18e915
3f60da455bbf1144ad12e4f92b452f966929f6105fd29ca28e4d4d5a031d068478bacb0b
27351119f822911b0a11ba3d3498ba6a32e50dac7f32894dd890eb9ad578e4e292c83722
a52e56a097e6a68b3f56f7a52ece0cdccba1eb62cad7d817f6dc58898b3ac15f36026fec
d568bd4a706cb60bb37be6d8f1240661199d640b66fb0fe3b079f97f450b9ef9c22c6d5d
dd379f0facd1cd020281dfa3c70ba21a3fa6fc2471dc6d13ecf8298b946f65345faa5ef0
f1f8fff03ad0acb083725535636addb08d73dedb9832da198081e5deae84bfaf0409c22b
e4a8aea2b607ec96931e6f4d4fe563ba

$ keyctl pipe 440502848 > kmk.blob

从保存的 blob 中加载可信密钥:

$ keyctl add trusted kmk "load `cat kmk.blob`" @u
268728824

$ keyctl print 268728824
0101000000000000000001005d01b7e3f4a6be5709930f3b70a743cbb42e0cc95e18e915
3f60da455bbf1144ad12e4f92b452f966929f6105fd29ca28e4d4d5a031d068478bacb0b
27351119f822911b0a11ba3d3498ba6a32e50dac7f32894dd890eb9ad578e4e292c83722
a52e56a097e6a68b3f56f7a52ece0cdccba1eb62cad7d817f6dc58898b3ac15f36026fec
d568bd4a706cb60bb37be6d8f1240661199d640b66fb0fe3b079f97f450b9ef9c22c6d5d
dd379f0facd1cd020281dfa3c70ba21a3fa6fc2471dc6d13ecf8298b946f65345faa5ef0
f1f8fff03ad0acb083725535636addb08d73dedb9832da198081e5deae84bfaf0409c22b
e4a8aea2b607ec96931e6f4d4fe563ba

在新的 pcr 值下重新封装可信密钥:

$ keyctl update 268728824 "update pcrinfo=`cat pcr.blob`"
$ keyctl print 268728824
010100000000002c0002800093c35a09b70fff26e7a98ae786c641e678ec6ffb6b46d805
77c8a6377aed9d3219c6dfec4b23ffe3000001005d37d472ac8a44023fbb3d18583a4f73
d3a076c0858f6f1dcaa39ea0f119911ff03f5406df4f7f27f41da8d7194f45c9f4e00f2e
df449f266253aa3f52e55c53de147773e00f0f9aca86c64d94c95382265968c354c5eab4
9638c5ae99c89de1e0997242edfb0b501744e11ff9762dfd951cffd93227cc513384e7e6
e782c29435c7ec2edafaa2f4c1fe6e7a781b59549ff5296371b42133777dcc5b8b971610
94bc67ede19e43ddb9dc2baacad374a36feaf0314d700af0a65c164b7082401740e489c9
7ef6a24defe4846104209bf0c3eced7fa1a672ed5b125fc9d8cd88b476a658a4434644ef
df8ae9a178e9f83ba9f08d10fa47e4226b98b0702f06b3b8

可信密钥的初始消费者是 EVM,它在启动时需要高质量的对称密钥来 HMAC 保护文件元数据。使用受信任的密钥提供了强有力的保证,即 EVM 密钥没有受到用户级问题的影响,并且当密封到特定的引导 PCR 值时,可以防止引导和脱机攻击。使用上述可信密钥“kmk”创建并保存加密密钥“evm”:

选项 1:省略“格式”

$ keyctl add encrypted evm "new trusted:kmk 32" @u
159771175

选项 2:将“格式”明确定义为“默认”

$ keyctl add encrypted evm "new default trusted:kmk 32" @u
159771175

$ keyctl print 159771175
default trusted:kmk 32 2375725ad57798846a9bbd240de8906f006e66c03af53b1b3
82dbbc55be2a44616e4959430436dc4f2a7a9659aa60bb4652aeb2120f149ed197c564e0
24717c64 5972dcb82ab2dde83376d82b2e3c09ffc

$ keyctl pipe 159771175 > evm.blob

从保存的 blob 中加载加密密钥“evm”:

$ keyctl add encrypted evm "load `cat evm.blob`" @u
831684262

$ keyctl print 831684262
default trusted:kmk 32 2375725ad57798846a9bbd240de8906f006e66c03af53b1b3
82dbbc55be2a44616e4959430436dc4f2a7a9659aa60bb4652aeb2120f149ed197c564e0
24717c64 5972dcb82ab2dde83376d82b2e3c09ffc

预计可信和加密密钥的其他用途,例如磁盘和文件加密。特别是已经定义了新格式“ecryptfs”,以便使用加密密钥来挂载 eCryptfs 文件系统。有关使用的更多详细信息,请参见文件“Documentation/security/keys-ecryptfs.txt”。

于 2015-05-28T14:46:28.110 回答
2

当它说

指定 HMAC 密钥

这并不意味着提供HMAC 密钥 - 它意味着“指向您要使用的 HMAC 密钥”

正如书中所指出的,TPM 可以使用几乎无限数量的 HMAC 密钥。您必须告诉 TPM 使用哪一个。

于 2016-06-14T20:06:38.013 回答