如何使用 TPM (可信平台模块)加密字节

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

CryptProtectData

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

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

比起 ProtectBytes的细节,你可以很容易地使用它的想法更重要:

  • 这是我想用 System中的密钥加密的字节
  • 把加密的东西还给我

返回的 一团是一个未记录的 < sup > 文档 结构,它包含解密和返回原始数据所需的所有内容(哈希算法、密码算法、 salt、 HMAC 签名等)。

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

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 Trusted Execution Environment (T) :

enter image description here

注意 : 我意识到 TPM 不做数据签名(或者如果它做了,它不能保证每次签名相同的数据都会得到相同的二进制输出)。这就是为什么我愿意用 “用硬件密钥加密一个256位的小块”代替 “ RSA 签名”

密码呢?

问题是 TPM 编程是 完全没有记录在 MSDN 上。没有可用于执行任何操作的 API。相反,你必须为自己找到一份 可信计算集团的软件栈(又名 TSS)的拷贝,弄清楚要以什么顺序向 TPM 发送哪些命令和有效载荷,然后调用 Window 的 < strong > 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 的一个实现是由一些人创建的,称为 裤子。然后是 将该项目移植到 Windows

这个代码的问题在于它不能移植到 Windows 世界。例如,你不能在德尔斐使用它,也不能在 c # 中使用它。它要求:

  • OpenSSL
  • PThread

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

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

使用 TPM 加密数据的等效代码是什么?正如其他人所指出的,您可能必须查阅三个 TPM 手册,并自己构建 blobs。它可能涉及到 TPM_seal命令。虽然我认为我不想要 海豹突击队的数据,但是我认为我想要 绑定的数据:

Binding -使用 TPM bind key 对数据进行加密,这是从存储密钥派生而来的唯一 RSA 密钥。 Sealing -以类似于绑定的方式加密数据,但是另外指定一种状态,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(...);
//...
}

除了锁定在 System LSA 中的密钥被锁定在 TPM 中以外,它们做同样的事情吗?

研究开始

我不知道 绑定是什么意思。但是看看 TPM Main-Part 3命令-规范版本1.2,其中提到了 绑定:

10.3 TPM _ UnBind

TPM _ UnBind 获取 Tspi _ Data _ Bind 命令的结果数据块,并将其解密以导出到 User。调用方必须授权使用将解密传入 blob 的密钥。 TPM _ UnBind 以块为基础进行操作,并且不知道一个块与另一个块之间的任何关系。

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

研究工作

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

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

第3章-TPM 2.0快速教程

TPM 可以访问自生成的私钥,因此它可以使用公钥对密钥进行加密,然后将结果块存储在硬盘上。这样,TPM 可以保留几乎无限数量的密钥供使用,但不会浪费宝贵的内部存储空间。存储在硬盘上的密钥可以被擦除,但是它们也可以被备份,在设计者看来这是一个可以接受的交易。

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

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

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

在过去的几年中,基于网络的应用程序的数量有所增加。其中包括基于 Web 的备份和存储。现在很多公司都提供这样的服务,但据我们所知,这些服务的客户机都不允许用户将备份服务的密钥锁定到 TPM。如果这样做了,那么如果 TPM 密钥本身能够通过在多台机器上复制来备份,那么当然会很好。这似乎是开发人员的一个机会。

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

第九章-阶级制度

用例: 存储登录密码

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

这个用例使用 TPM 生成的 HMAC 密钥。密码文件存储加盐密码的 HMAC。验证包括对提供的密码进行盐处理和 HMACing,并将其与存储值进行比较。因为脱机攻击者没有 HMAC 密钥,所以攻击者不能通过执行计算来发动攻击。

这个 可以能用。如果 TPM 有一个秘密的 HMAC 密钥,并且只有我的 TPM 知道 HMAC 密钥,那么我可以用“ HMAC”替换“ Sign (aka TPM crypt with it’s private key)”。但在接下来的一行中,他完全颠倒了自己的观点:

TPM2 _ Create,指定一个 HMAC 密钥

如果我必须指定 HMAC 密钥,那就不是 TPM 秘密了。事实上,HMAC 密钥并不是秘密的,当您意识到这是 TPM 提供的关于加密实用程序的章节时,这一点是有意义的。您不必自己编写 SHA2、 AES、 HMAC 或 RSA,而是可以重用 TPM 已经包含的内容。

第十章-要点

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

太棒了! 你是怎么做到的! ?

密钥生成器

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

TPM 是否有能力在硬件边界内生成加密密钥并保护其秘密?是吗,怎么回事?

第12章-平台配置登记册

用于授权的聚合酶链式反应

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

如果一个 TPM 保护加密密钥,那么全磁盘加密应用程序比存储在同一个磁盘上只用密码保护的应用程序要安全得多。 首先,TPM 硬件具有防撞击保护(参见第8章对 TPM 字典式攻击保护的详细描述) ,这使得密码的穷举法不切实际。只有软件保护的密钥更容易受到弱密码的攻击。其次,存储在磁盘上的软件密钥更容易被盗取。拿到磁盘(或者磁盘的备份) ,就可以得到密钥。当 TPM 持有钥匙时, 整个平台,或者至少是磁盘和主板,必须被盗。

密封允许密钥不仅受到密码的保护,而且受到策略的保护。一个典型的策略在密封时锁定 PCR 值(软件状态)电流。这里假设第一次引导时的状态没有被破坏。在第一次引导时出现的任何预安装的恶意软件将被测量到 PCR 中,因此密钥将被密封到一个受到威胁的软件状态。信任度较低的企业可能有一个标准的磁盘映像,并对表示该映像的 PCR 进行密封。这些 PCR 值将在一个可能更可信的平台上预先计算。更复杂的企业将使用 TPM2 _ PolicyAuthorize,并提供几张授权一组可信 PCR 值的票据。见第14章详细描述策略授权及其在解决 PCR 脆性问题中的应用。

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

太棒了!这正是我想要的用例。这也是微软使用 TPM 的用例。我该怎么做! ?

所以我把整本书都读了一遍,没有提供任何有用的信息。令人印象深刻,因为它有375页。你想知道这本书包含了什么,但是回头看看,我却一无所知。

因此,我们放弃了编写 TPM 的权威指南,转而求助于微软的一些文档:

来自 < strong > MicrosoftTPM Platform Crypto-Provider Toolkit 。它提到了我想要做的事情:

背书密钥或 EK

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

TPM 中的某个地方有一个 RSA 私钥。那把钥匙被锁在里面,外面的世界永远看不到。我希望 TPM 用它的私有密钥来签名一些东西(即用它的私有密钥来加密它)。

因此,我想要最 基本的操作,可能存在的:

enter image description here

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

  • 基于 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 没有人知道答案。所以我真的不能太贪婪,以获得一个解决我的共同问题。但是 真的想要做的是对 “封印”做一些数据:

enter image description here

  • 向 TPM 提供一些数据(例如32字节的关键材料)
  • 让 TPM 加密数据,返回一些不透明的 blob 结构
  • 然后请求 TPM 解密这个斑点
  • 只有当 TPM 的 PCR 寄存器与加密时相同时,解密才会起作用。

换句话说:

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


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

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

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

从 Windows Vista 开始,Crypto API 被 加密 API: 下一代(内部称为 最佳地穴,缩写为 地穴,不要与 密码哈希算法混淆)取代。

Windows 提供两个 BCrypt 供应商:

  • Microsoft Primitive Provider (MS_PRIMITIVE_PROVIDER) 违约: 所有基元(散列、对称加密、数字签名等)的默认软件实现
  • Microsoft 平台加密提供程序 (MS_PLATFORM_CRYPTO_PROVIDER) : 提供 TPM 访问的提供程序

平台加密提供商没有在 MSDN 上进行文档记录,但是有来自2012年 Microsoft Research 站点的文档:

平台加密提供工具包

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

看起来微软的目的是使用 密码学 API 的 微软平台加密提供商来实现 TPM 加密功能。

使用 MicrosoftBCrypt 的公钥加密

鉴于此:

前进的方向可能是弄清楚如何使用 下一代 API进行数字签名。

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

  • modulus:0xDC 67 FA F49E F2721D 452C B4807906 A09427508209 DD 67 CE 57 B86C 4A 4F 409F D2 D169 FB 995D 850C 07 A1F9471B 56166E F67F B9 CF 2A 5836379929 AA 4F A812 E84F C7822B 9D 722A 9C DE 6F C2 EE 126D CF F0 F2B8 C4 DD 7C 5C 1A C81751 A9 AC DF 0822049D 2B D7 F94B 09 DE 9A EB 5C 511A D8 F8 F9569E F8 FB 379B 3F D37465240D FF 347557 A4 F5 BF 55
  • publicExponent:65537

有了这些代码,我也许可以切换到使用 TPM 提供程序(MS_PLATFORM_CRYPTO_PROVIDER)。

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

这大致相当于每个人都拥有一辆汽车,但是没有人知道如何发动一辆汽车。只要我们能通过 第一步,它就能做很有用很酷的事情。

微软密钥存储 API

微软的 TPM 基本服务 < a href = “ https://archive. ph/uA6Cm”rel = “ norefrer”> archive 文档主页显示,我们可能希望改用 Key Storage API:

注意

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

密钥存储 API< a href = “ https://archive. ph/O07Yf”rel = “ norefrer”> archive 的介绍说:

密钥存储体系结构

CNG 为私钥存储提供了一种模型 能够适应当前和未来的创造需求 使用公共或私有等加密特性的应用程序 密钥加密,以及存储密钥资料的要求。 密钥存储路由器是这个模型中的中心例程,它是 应用程序访问密钥存储 提供程序(KSP)的密钥存储路由器,其中 从应用程序和 存储提供程序本身 CNG 密钥隔离体系结构的设计与功能。

enter image description here

他们还指出,硬件安全模块(可能是 TPM 的术语)得到了支持:

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

我唯一不知道的是,如果你必须使用 问吧的 HSM,或者它会自动发生时,可用(以及如何知道什么时候不可用-所以你不要尝试继续无论如何)。

额外阅读

26209 次浏览

雷管

以下是关于 TPM 1.2的内容。请记住,微软需要一个 TPM 2.0为所有未来的 Windows 版本。2.0版本与1.2版本有着本质的不同

由于 TPM 的设计原则,不存在单行解决方案。把 TPM 想象成资源有限的微控制器。它的主要设计目标是既便宜又安全。所以 TPM 被剥夺了所有的逻辑,而这些逻辑对于一个安全的操作是不必要的。因此,TPM 只有在您至少有一些或多或少的 胖子软件时才能正常工作,并以正确的顺序发出许多命令。这些命令序列可能会变得非常复杂。这就是 TCG 使用定义良好的 API 指定 TSS 的原因。如果你想用 Java 的方式,甚至还有一个高级的 Java API。我不知道 C #/有类似的项目。网

发展

对于您的情况,我建议您查看 IBM 的软件 TPM。

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

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

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

高水平

先决条件:

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

为了密封一个斑点,您需要执行以下操作:

  1. 创建一个密钥
  2. 把钥匙放在某个地方
  3. 确保在 TPM 中加载密钥
  4. 封住那团东西

要解封,你需要:

  1. 获得密钥块
  2. 将密钥加载到 TPM
  3. 打开密封的水滴

可以将 key-blob 存储在用于存储受保护字节的数据结构中。

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

TPM 命令

目前我不能运行调试版本,所以我不能提供确切的序列。因此,考虑下面这个 无序命令列表,您将不得不使用:

  • TPM_OSAP
  • TPM_CreateWrapKey
  • TPM_LoadKey2
  • TPM_Seal

如果你也想阅读当前的 PCR 值:

  • TPM_PCRRead

可信密钥和加密密钥

可信密钥和加密密钥是添加到现有内核中的两种新密钥类型 密钥环服务。这两种新类型都是可变长度的对称密钥, 在这两种情况下,所有键都是在内核中创建的,用户空间可以看到, 存储,并且只加载加密的 blobs 可信平台模块(tPM)芯片,以提高安全性,同时加密 键可以在任何系统上使用。所有用户级别的斑点,显示和加载 方便,并完整性验证。

可信密钥使用 TPM 来生成和密封密钥。密钥是密封的 在 TPM 中的2048位 RSA 密钥下,并可选地密封到指定的 PCR (完整性测量)值,并且只有由 TPM 解密,如果 PCR 和 blob 完整性验证匹配。加载的可信密钥可以用新的 (将来) PCR 值,因此键很容易迁移到新的 PCR 值,例如 当内核和 initramfs 被更新时。同一个键可以保存许多 在不同 PCR 值下的 blobs,因此很容易支持多个引导。

默认情况下,受信任密钥在 SRK 下密封,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 加密/解密。从随机生成的内核创建新密钥 数字,并使用指定的“主”密钥加密/解密 ‘ master’key 可以是受信任密钥类型,也可以是用户密钥类型 加密密钥的缺点是,如果它们不是根于受信任密钥, 它们的安全性取决于加密它们的用户密钥 因此,应以尽可能安全的方式加载,最好在 靴子。

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

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'

可信密钥和加密密钥使用示例

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

$ 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”:

选择一: 省略「格式」

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

选项2: 显式地将“ format”定义为“ default”

$ 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 文件系统 关于用法可以在文件中找到 “文档/安全/密钥-ecryptfs.txt”。

当它说

指定 HMAC 密钥

这并不意味着 提供的 HMAC 键-它的意思是 “指向你想要使用的 HMAC 密钥”

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

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

取决于你的意图和情况:

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

这些用例中的每一个(还有更多)——或者它们的组合——都表现出不同的实现路径。可以把 TPM 想象成加密设备的瑞士军刀: 没有什么是您不能使用它的,但是这种多功能性使得它的易用性受到了影响。这个问题一直在加密、签名和系统配置锁定之间跳来跳去,但是这个答案的主要部分将考虑玺命令来满足问题中描述的大部分需求。

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

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

换句话说,我想替换 Qualcomm 可信执行 环境(TEE)在下面的 Android 框图中,带有 TPM 视窗:

不知道复制这整个过程是否是个好主意。首先,不需要在流程的任何地方使用签名操作。看起来,在 Android 5开发的时候,Keystore API 受到限制对签名和验证的操作。我最好的猜测是,磁盘加密团队尽了最大努力,利用他们已有的技术,设计了一种算法,其中一个中间密钥是通过 签字操作获得的,使用一个存储的 TEE 密钥,从而将整个过程绑定到一个只能在平台上使用的硬件绑定密钥上——因为当时只有签名才能做到这一点。但是,如果能够访问 TPM,就没有必要以这种方式限制自己,因为 TPM 提供的功能超出了您的需要!

我知道 TPM 不做数据签名

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

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

这说不通啊。用相同的密钥 威尔对相同的数据进行签名会产生相同的签名。您可能会将签名操作与引号操作混淆,引号操作将混合在一起。

所以我愿意把“ RSA 签名”换成“加密” 一个带有硬件绑定密钥的256位块”。

这实际上应该是首选的选项,尽管两者都可以通过 TPM 实现。

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

不幸的是,没有什么可记录的。Win API 仅限于两个 TBS 函数,它们与驱动程序相隔一个级别。

相反,您必须为自己找到一个可信计算的副本 组的软件栈(又名 TSS) ,找出要发送到哪些命令 TPM,有效载荷,以什么顺序,并调用窗口的 直接提交命令的 Tbsip _ Submit _ Command 函数:

实际上,不,如果你有一个 TSS 你就不必使用 Tbsip_submit_Command()。这就是使用 TSS 的全部意义——低层次的细节被抽象出来。

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

对于 TPM 1仍然是正确的,但是对于 TPM 2有 TSS.MSR

这在道义上相当于试图通过发出 你的硬盘驱动器的 SATA I/O 命令。

没错。

- 为什么不直接用裤子...-这个代码的问题就在于它是 不能移植到 Windows 世界。例如,你不能使用它 在德尔斐,你不能在 c # 中使用它,它要求: OpenSSL, PThread

目前尚不清楚这是否是一个不可逾越的挑战。通过互操作访问 TrouSerS 比重写所有数据结构化代码更可取。此外,在写这个问题的时候还有 doTSS

使用 TPM 加密数据的等效代码是什么 包含 TPM _ sea 命令。尽管我认为我不想密封 数据,我想我要绑定它:

这个问题包含一个引用,描述了两个命令之间的区别,所以不应该有太多的混淆。密封类似于绑定,附加的约束是要解密的数据的系统状态必须相同。

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

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的 TSS 有一个 Microsoft 实现。

第二,让我们把这个问题说得更具体一些,我重新解释一下:

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?

玺命令最适合这种情况,因为当 PCR 选择大小设置为零时,它执行与 Bind 命令相同的功能,但是 PCR 选择可以很容易地更改以包含您可能需要的任何 PCR。这让人想知道为什么 Bind 命令会包含在规范中,并且正如前面提到的,它在 TPM 2规范中被删除了,两者被合并在一个 Create 命令中。

下面是使用 TPM 1.2玺命令的 C # 代码,该命令只使用 TBS 函数 (注意: 此代码未经测试,不经调试不可能正常工作)对数据进行加密:

[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 是 big endian,Windows 是 little endian。所以我们发送的数据的字节顺序必须颠倒。在这里,我们只需要考虑如何反转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 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 会话期间生成的 nonce 对这个“ secret”参数进行 XOR 加密。玺命令的一个输入句柄也是一个 OSAP 句柄:

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

所以我们需要先建立这个 OSAP 会议。在 TPM 1.2规范第1部分中描述了 OSAP。OSAP (对象特定授权协议)是为了处理这样的用例而发明的: 你想要使用一个需要多次授权的 TPM 对象,但不想每次都提供授权: 相反,使用一个 OSAP 会话,它依赖于“共享秘密”的概念,这是一个 HMAC,它混合了对象授权数据和每边生成的 nonce 以防止应答攻击。因此,这个会话中的“共享秘密”只有双方知道: 发起会话的一方(用户)和接受会话的一方(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 operands

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

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

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

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

命令代码是一个4字节的值,作为命令 ID 服务: 它告诉 TPM 如何解释命令的其余部分。这里的命令代码是 TPM _ OSAP (0x0000000B)。

接下来要设置的两件事是实体类型和实体值。因为我们希望使用 TPM 中已经存在的密钥,所以我们将使用实体类型“ SRK”(0x0004) ,并且因为我们是在假设 TPM 已经被拥有的前提下工作的,所以可以安全地假设它在永久句柄0x4000000下加载了一个 SRK,所以我们将使用这个永久句柄值作为我们的实体值。(SRK 代表“ Storage Root Key”,是从中派生出大多数其他 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 response

所以我们回来了:

  • 与我们的主命令(玺)一起使用的授权句柄
  • nonceEven: TPM 生成的与 main 命令一起使用的 nonce
  • NonceEvOSAP: OSAP nonce,它是在发送 TPM _ OSAP 命令之前在我们这边生成的 nonce 的计数器。这两个 nonce 将用于生成“共享秘密”。

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

    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 nonce (一个由用户生成,一个由 TPM 生成)和我们想要使用的键的授权值—— SRK。按照惯例,SRK auth 值是“众所周知的 auth”: 一个取零的20字节缓冲区。从技术上讲,在获得 TPM 的所有权时,可以将这个值更改为其他值,但是在实践中并没有这样做,因此我们可以安全地假设“著名的 auth”值是好的。

接下来让我们看看 TPM _ Seal 命令中包含了什么:

TPM_Seal

大多数这些参数的构建都很简单,除了其中的两个: encAuthpubAuth。我们一个一个来看。

encAuth是“密封数据的加密 AuthData”这里的 AuthData 是之前的“众所周知的 auth”,但是我们仍然需要对它进行加密。因为我们使用的是 OSAP 会话,它是按照 ADIP 或授权数据插入协议加密的。来自规范: “ ADIP 允许创建新实体和安全插入新实体 AuthData。新 AuthData 的传输使用基于 OSAP 会话共享秘密的密钥加密。”另外: “对于强制性的 XOR 加密算法,创建者使用 OSAP 共享秘密的 SHA-1散列和会话 nonce 构建加密密钥。创建者 XOR 使用加密密钥作为一次性垫对新的 AuthData 进行加密,并将加密后的数据连同创建请求一起发送给 TPM。”因此,我们必须从会话 nonce 和“共享秘密”构建一个 XOR 密钥,然后用该密钥对“众所周知的 auth”进行 XOR 加密。

以下图解说明 ADIP 如何运作:

ADIP

pubAuth是“用于输入和 keyHandle 的授权会话摘要”规范的第1部分“ OIAP 和 OSAP 示例的参数声明”解释了如何解释上面的 TPM _ Seal 参数表: “ HMAC # 列详细说明了 HMAC 计算中使用的参数。参数1S、2S 等被连接并散列到 ParamDigest 或 outParamDigest 中,如果有两个授权会话,则隐式调用1H1,可能调用1H2。对于第一个会话,1H1、2H1、3H1和4H1被连接起来并被 HMAC’化。对于第二个会话,1H2、2H2、3H2和4H2被连接起来,并被 HMAC’化。”因此,我们必须散列明文,其大小,PCR 信息大小,encAuth从上面和 TPM _ Seal 序数,然后 HMAC 与两个 nonce 和“继续会话”布尔使用 OSAP“共享秘密”作为 HMAC 密钥。

把它们放在一个图表里:

pubAuth computation

注意我们是如何在这段代码中将“ 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 1.2的 TPM _ CreateWrapKey 命令。

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

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

TPM2 _ Create,指定一个 HMAC 密钥

书中的文字令人困惑。你没有指定 HMAC 钥匙,而是指定你想要 一把 HMAC 钥匙

HMAC 密钥不是秘密的事实说得通

不,这说不通,钥匙是秘密。

... 使用钥匙,同时保持他们在一个硬件设备的安全... 太好了! 你是怎么做到的! ?

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

TPM 是否有能力生成加密密钥和 在硬件边界内保护它的秘密? 是吗,怎么保护?

看上面。

太好了! 这正是我想要的用例 微软使用 TPM 的用例。我该怎么做! ?

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

背书密钥或 EK... TPM 中的某个地方是 RSA 私人钥匙,那把钥匙锁在里面,永远不会被发现 外面的世界。我想 TPM 签署的东西与它的私人 Key (即用它的私钥加密它)。

EK 不是签名密钥——它是一个加密密钥。但是,它不是一个通用的加密密钥: 它只能在特定的情况下使用

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

看上面。