How to encrypt bytes using the TPM (Trusted Platform Module)

Ian*_*oyd 106 windows encryption tpm trusted-computing

How can I encrypt bytes using a machine's TPM module?

CryptProtectData

Windows provides a (relatively) simple API to encrypt a blob using the CryptProtectData API, which we can wrap an easy to use function:

public Byte[] ProtectBytes(Byte[] plaintext)
{
   //...
}
Run Code Online (Sandbox Code Playgroud)

The details of ProtectBytes are less important than the idea that you can use it quite easily:

  • here are the bytes I want encrypted by a secret key held in the System
  • give me back the encrypted blob

The returned blob is an undocumented documentation structure that contains everything needed to decrypt and return the original data (hash algorithm, cipher algorithm, salt, HMAC signature, etc).

为了完整性,这里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.
}
Run Code Online (Sandbox Code Playgroud)

如何使用TPM进行相同的操作?

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

Now it's time to take this one step further. I want to encrypt some data (e.g. a hard drive encryption master key) that can only be decrypted by the local TPM. In other words, I want to replace the Qualcomm Trusted Execution Environment (TEE) in the block diagram below for Android, with the TPM in Windows:

在此输入图像描述

Note: I realize that the TPM doesn't do data-signing (or if it does, it does not guarantee that signing the same data will give the same binary output every time). Which is why I'd be willing to replace "RSA signing" with "encrypting a 256-bit blob with a hardware bound key".

So where's the code?

The problem is that TPM programming is completely undocumented on MSDN. There is no API available to perform any operations. Instead you have to find yourself a copy of the Trusted Computing Group's Software Stack (aka TSS), figure out what commands to send to the TPM, with payloads, in what order, and call Window's Tbsip_Submit_Command function to submit commands directly:

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
);
Run Code Online (Sandbox Code Playgroud)

Windows has no higher level API to perform actions.

It's the moral equivalent of trying to create a text file by issuing SATA I/O commands to your hard drive.

Why not just use Trousers

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

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

  • OpenSSL的
  • 并行线程

我只是希望代码用我的TPM加密某些东西.

以上只CryptProtectData需要功能体中的内容.

What is the equivalent code to encrypt data using the TPM? As others have noted, you probably have to consult the three TPM manuals, and construct the blobs yourself. It probably involves the TPM_seal command. Although I think I don't want to seal data, I think I want to bind it:

Binding – encrypts data using TPM bind key, a unique RSA key descended from a storage key. Sealing – encrypts data in a similar manner to binding, but in addition specifies a state in which TPM must be in order for the data to be decrypted (unsealed)

I try to read the three required volumes in order to find the 20 lines of code I need:

But I have no idea what I'm reading. If there was any kind of tutorial or examples, I might have a shot. But I'm completely lost.

So we ask Stackoverflow

In the same way I was able to provide:

Byte[] ProtectBytes_Crypt(Byte[] plaintext)
{
   //...
   CryptProtectData(...); 
   //...
}
Run Code Online (Sandbox Code Playgroud)

can someone provide the corresponding equivalent:

Byte[] ProtectBytes_TPM(Byte[] plaintext)
{
   //...
   Tbsip_Submit_Command(...);
   Tbsip_Submit_Command(...);
   Tbsip_Submit_Command(...);
   //...snip...
   Tbsip_Submit_Command(...);
   //...
}
Run Code Online (Sandbox Code Playgroud)

that does the same thing, except rather than a key locked away in System LSA, is locked away in the TPM?

Start of Research

I don't know exactly what bind means. But looking at TPM Main - Part 3 Commands - Specification Version 1.2, there is a mention of bind:

10.3 TPM_UnBind

TPM_UnBind takes the data blob that is the result of a Tspi_Data_Bind command and decrypts it for export to the User. The caller must authorize the use of the key that will decrypt the incoming blob. TPM_UnBind operates on a block-by-block basis, and has no notion of any relation between one block and another.

What's confusing is there is no Tspi_Data_Bind command.

Research Effort

It is horrifying how nobody has ever bothered to document the TPM or its operation. It's as if they spent all their time coming up with this cool thing to play with, but didn't want to deal with the painful step of making it usable for something.

Starting with the (now) free book A Practical Guide to TPM 2.0: Using the Trusted Platform Module in the New Age of Security:

Chapter 3 - Quick Tutorial on TPM 2.0

The TPM has access to a self-generated private key, so it can encrypt keys with a public key and then store the resulting blob on the hard disk. This way, the TPM can keep a virtually unlimited number of keys available for use but not waste valuable internal storage. Keys stored on the hard disk can be erased, but they can also be backed up, which seemed to the designers like an acceptable trade-off.

How can I encrypt a key with the TPM's public key?

Chapter 4 - Existing Applications That Use TPMs

Applications That Should Use the TPM but Don’t

In the past few years, the number of web-based applications has increased. Among them are web-based backup and storage. A large number of companies now offer such services, but as far as we are aware, none of the clients for these services let the user lock the key for the backup service to a TPM. If this were done, it would certainly be nice if the TPM key itself were backed up by duplicating it on multiple machines. This appears to be an opportunity for developers.

How does a developer lock a key to the TPM?

Chapter 9 - Heirarchies

USE CASE: STORING LOGIN PASSWORDS

A typical password file stores salted hashes of passwords. Verification consists of salting and hashing a supplied password and comparing it to the stored value. Because the calculation doesn’t include a secret, it’s subject to an offline attack on the password file.

This use case uses a TPM-generated HMAC key. The password file stores an HMAC of the salted password. Verification consists of salting and HMACing the supplied password and comparing it to the stored value. Because an offline attacker doesn’t have the HMAC key, the attacker can’t mount an attack by performing the calculation.

This could work. If the TPM has a secret HMAC key, and only my TPM knows the HMAC key, then I could replace "Sign (aka TPM encrypt with it's private key)" with "HMAC". But then in the very next line he reverses himself completely:

TPM2_Create, specifying an HMAC key

It's not a TPM secret if I have to specify the HMAC key. The fact that the HMAC key isn't secret makes sense when you realize this is the chapter about cryptographic utilities that the TPM provides. Rather than you having to write SHA2, AES, HMAC, or RSA yourself, you can re-use what the TPM already has laying around.

Chapter 10 - Keys

As a security device, the ability of an application to use keys while keeping them safe in a hardware device is the TPM’s greatest strength. The TPM can both generate and import externally generated keys. It supports both asymmetric and symmetric keys.

Excellent! How do you do it!?

Key Generator

Arguably, the TPM’s greatest strength is its ability to generate a cryptographic key and protect its secret within a hardware boundary. The key generator is based on the TPM’s own random number generator and doesn’t rely on external sources of randomness. It thus eliminates weaknesses based on weak softwaresoftware with an insufficient source of entropy.

Does the TPM have the ability to generate cryptographic keys and protect its secrets within a hardware boundary? Is so, how?

Chapter 12 - Platform Configuration Registers

PCRs for Authorization

USE CASE: SEALING A HARD DISK ENCRYPTION KEY TO PLATFORM STATE

Full-disk encryption applications are far more secure if a TPM protects theencryption key than if it’s stored on the same disk, protected only by a password. First, the TPM hardware has anti-hammering protection (see Chapter 8 for a detailed description of TPM dictionary attack protection), making a brute-force attack on the password impractical. A key protected only by software is far more vulnerable to a weak password. Second, a software key stored on disk is far easier to steal. Take the disk (or a backup of the disk), and you get the key. When a TPM holds the key, the entire platform, or at least the disk and the motherboard, must be stolen.

Sealing permits the key to be protected not only by a password but by a policy. A typical policy locks the key to PCR values (the software state) current at the time of sealing. This assumes that the state at first boot isn’t compromised. Any preinstalled malware present at first boot would be measured into the PCRs, and thus the key would be sealed to a compromised software state. A less trusting enterprise might have a standard disk image and seal to PCRs representing that image. These PCR values would be precalculated on a presumably more trusted platform. An even more sophisticated enterprise would use TPM2_PolicyAuthorize, and provide several tickets authorizing a set of trusted PCR values. See Chapter 14 for a detailed description of policy authorize and its application to solve the PCRbrittleness problem.

Although a password could also protect the key, there is a security gain even without a TPM key password. An attacker could boot the platform without supplying a TPMkey password but could not log in without the OS username and password. The OSsecurity protects the data. The attacker could boot an alternative OS, say from a live DVD or USB stick rather that from the hard drive, to bypass the OS login security. However, this different boot configuration and software would change the PCRvalues. Because these new PCRs would not match the sealed values, the TPM would not release the decryption key, and the hard drive could not be decrypted.

Excellent! This is exactly the use case I happen to want. It's also the use case the Microsoft uses the TPM for. How do I do it!?

So I read that entire book, and it provided nothing useful. Which is quite impressive because it's 375 pages. You wonder what the book contained - and looking back on it, I have no idea.

So we give up on the definitive guide to programming the TPM, and turn instead to some documentation from Microsoft:

From the Microsoft TPM Platform Crypto-Provider Toolkit. It mentions exactly what I want to do:

The Endorsement Key or EK

The EK is designed to provide a reliable cryptographic identifier for the platform. An enterprise might maintain a database of the Endorsement Keys belonging to the TPMs of all of the PCs in their enterprise, or a data center fabric controller might have a database of the TPMs in all of the blades. On Windows you can use the NCrypt provider described in the section "Platform Crypto Provider in Windows 8" to read the public part of the EK.

Somewhere inside the TPM is an RSA private key. That key is locked away in there - never to be seen by the outside world. I want the TPM to sign something with it's private key (i.e. encrypt it with it's private key).

So I want the most basic operation that can possibly exist:

在此输入图像描述

Encrypt something with your private key. I'm not even (yet) asking for the more complicated stuff:

  • "sealing" it based on PCR state
  • creating a key and storing it in volatile or non-volatile memroy
  • creating a symmetric key and trying to load it into the TPM

I am asking for the most basic operation a TPM can do. Why is it impossible to get any information about how to do it?

I can get random data

I suppose I was being glib when I said RSA signing was the most basic thing the TPM can do. The most basic thing the TPM can be asked to do is give me random bytes. That I have figured out how to do:

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;
}
Run Code Online (Sandbox Code Playgroud)

The Fancy Thing

I realize the volume of people using the TPM is very low. That is why nobody on Stackoverflow has an answer. So I can't really get too greedy in getting a solution to my common problem. But the thing I'd really want to do is to "seal" some data:

在此输入图像描述

  • present the TPM some data (e.g. 32 bytes of key material)
  • have the TPM encrypt the data, returning some opaque blob structure
  • later ask the TPM to decrypt the blob
  • the decryption will only work if the TPM's PCR registers are the same as they were during encryption.

In other words:

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

Byte[] UnprotectBytes_TPM(Byte[] protectedBlob)
{
   //...
}
Run Code Online (Sandbox Code Playgroud)

Cryptography Next Gen (Cng, aka BCrypt) supports TPM

The original Cryptography API in Windows was knows as the Crypto API.

Starting with Windows Vista, the Crypto API has been replaced with Cryptography API: Next Generation (internally known as BestCrypt, abbreviated as BCrypt, not to be confused with the password hashing algorithm).

Windows ships with two BCrypt providers:

The Platform Crypto provider is not documented on MSDN, but does have documentation from a 2012 Microsoft Research site:

TPM Platform Crypto-Provider Toolkit

The TPM Platform Crypto Provider and Toolkit contains sample code, utilities and documentation for using TPM-related functionality in Windows 8. Subsystems described include the TPM-backed Crypto-Next-Gen (CNG) platform crypto-provider, and how attestation-service providers can use the new Windows features. Both TPM1.2 and TPM2.0-based systems are supported.

It seems that Microsoft's intent is to surface TPM crypto functionality with the Microsoft Platform Crypto Provider of the Cryptography NG API.

Public key encryption using Microsoft BCrypt

Given that:

a way forward might be to figure out how to do digital signing using the Microsoft Cryptography Next Gen API.

My next step will be to come up with the code to do encryption in BCrypt, with an RSA public key, using the standard provider (MS_PRIMITIVE_PROVIDER). E.g.:

  • 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 56 16 6E F6 7F B9 CF 2A 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 9A 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

With that code functioning, i may be able to switch to using the TPM Provider (MS_PLATFORM_CRYPTO_PROVIDER).

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

它大致相当于拥有汽车的每个人,但没有人知道如何开始.它可以做真正有用和酷的事情,只要我们可以通过第1步.

奖金阅读

mni*_*tic 7

\n

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

\n
\n\n

取决于您的意图和情况:

\n\n
    \n
  • 您拥有哪种类型的 TPM(1 个系列或 2 个系列)?
  • \n
  • TPM 处于什么状态?被拥有了吗?已经配置了吗?
  • \n
  • 你的编程语言是什么?
  • \n
  • 您要加密还是签名?(这对于问题的其余部分来说是模糊的)
  • \n
  • 您要加密的数据有多大?
  • \n
  • 您想使用对称密钥还是非对称密钥?
  • \n
  • 您想要使用 TPM 上已存在的密钥,还是想让 TPM 首先创建该密钥?
  • \n
  • “加密”可能是指“包装密钥”吗?
  • \n
  • 您是否要将加密数据锁定到系统配置,以便只有当系统恢复到相同配置时才能解密?
  • \n
  • 您想要解密时需要授权吗?
  • \n
  • 也许您根本不需要加密,而是将数据存储在 TPM 中?
  • \n
  • 如果您将数据存储在 TPM 中,您是否需要授权或系统处于特定配置才能检索?
  • \n
\n\n

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

\n\n
\n

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

\n
\n\n

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

\n\n
\n

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

\n
\n\n

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

\n\n
\n

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

\n
\n\n

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

\n\n
\n

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

\n
\n\n

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

\n\n
\n

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

\n
\n\n

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

\n\n
\n

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

\n
\n\n

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

\n\n
\n

相反,您必须找到可信计算组的软件堆栈(又名 TSS)的副本,找出要发送到 TPM 的命令、有效负载、按什么顺序,然后调用 Window 的\n Tbsip_Submit_Command 函数直接提交命令:

\n
\n\n

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

\n\n
\n

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

\n
\n\n

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

\n\n
\n

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

\n
\n\n

正确的。

\n\n
\n

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

\n
\n\n

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

\n\n
\n

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

\n
\n\n

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

\n\n
\n

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

\n\n
Byte[] ProtectBytes_Crypt(Byte[] plaintext)\n{\n   //...\n   CryptProtectData(...); \n   //...\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
Byte[] ProtectBytes_TPM(Byte[] plaintext)\n{\n   //...\n   Tbsip_Submit_Command(...);\n   Tbsip_Submit_Command(...);\n   Tbsip_Submit_Command(...);\n   //...snip...\n   Tbsip_Submit_Command(...);\n   //...\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

做同样的事情,除了密钥不是锁定在系统 LSA 中,而是锁定在 TPM 中?

\n
\n\n

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

\n\n
    \n
  • 该问题充满了 TPM 1 特定概念,因此使用 TPM 1 的人更有可能在这里寻找它们
  • \n
  • Microsoft 针对 TPM 2 实施了 TSS。
  • \n
\n\n

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

\n\n
\n
How do I write code in C#, using only the TBS API, to interface with\nan already owned and provisioned TPM to, without user interaction,\nencrypt no more than 128 bytes of arbitrary data with an asymmetric\nkey already resident in the TPM and bound to it, but not protected\nwith a password, so that in order to decrypt the data the system may\nneed to be in the same state it was in at encryption time based on an\neasily configurable variable?\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

Seal 命令最适合这种情况,因为当 PCR 选择大小设置为零时,它执行与 Bind 命令相同的功能,但可以轻松更改 PCR 选择以包括您可能想要的任何 PCR。这让人想知道为什么 Bind 命令根本包含在规范中,并且正如所指出的,它在 TPM 2 规范中被删除,并且这两个命令合并在一个 Create 命令中。

\n\n

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

\n\n
[DllImport ("tbs.dll")]\nunsafe static extern UInt32 Tbsi_Context_Create (UInt32 * version, IntPtr * hContext);\n\n[DllImport ("tbs.dll")]\nunsafe static extern UInt32 Tbsip_Context_Close (IntPtr hContext);\n\n[DllImport ("tbs.dll")]\nunsafe static extern UInt32 Tbsip_Submit_Command (\n    IntPtr hContext, UInt32 Locality, \n    UInt32 Priority, \n    byte * pCommandBuf, \n    UInt32 CommandBufLen, \n    byte * pResultBuf, \n    UInt32 * pResultBufLen);\n\nbyte[] ProtectBytes_TPM (byte[] plaintext) {\n\n    void AddUInt32Reversed (byte[] a, System.UInt32 o, ref int i) {\n        byte[] bytes = System.BitConverter.GetBytes (o);\n        Array.Reverse (bytes);\n        Array.Copy (bytes, 0, a, i, bytes.Length);\n        i += bytes.Length;\n    }\n    void AddUInt16Reversed (byte[] a, System.UInt16 o, ref int i) {\n        byte[] bytes = System.BitConverter.GetBytes (o);\n        Array.Reverse (bytes);\n        Array.Copy (bytes, 0, a, i, bytes.Length);\n        i += bytes.Length;\n    }\n    void AddBool (byte[] a, byte b, ref int i) {\n        a[i] = b;\n        i += 1;\n    }\n    void AddBlob (byte[] a, byte[] b, ref int i) {\n        Array.Copy (b, 0, a, i, b.Length);\n        i += b.Length;\n    }\n    byte[] Xor (byte[] text, byte[] key) {\n        byte[] xor = new byte[text.Length];\n        for (int i = 0; i < text.Length; i++) {\n            xor[i] = (byte) (text[i] ^ key[i % key.Length]);\n        }\n        return xor;\n    }\n\n    int offset;\n\n    Random rnd = new Random ();\n\n    IntPtr hContext = IntPtr.Zero;\n    unsafe {\n        UInt32 version = 1;\n        IntPtr handle = hContext;\n        UInt32 result = Tbsi_Context_Create ( & version, & handle);\n\n        if (result == 0) {\n            hContext = handle;\n        }\n    }\n\n    byte[] cmdBuf = new byte[768];\n\n    //OSAP\n    System.UInt32 outSize;\n\n    byte[] oddOsap = new byte[20];\n    byte[] evenOsap = new byte[20];\n    byte[] nonceEven = new byte[20];\n    byte[] nonceOdd = new byte[20];\n    System.UInt32 hAuth = 0;\n\n    offset = 0;\n    AddUInt16Reversed (cmdBuf, 0x00C1, ref offset);\n    offset = 6;\n    AddUInt32Reversed (cmdBuf, 0x0000000B, ref offset);\n\n    offset = 2 + 4 + 4; //2 for tag, 4 for size and 4 for command code\n\n    AddUInt16Reversed (cmdBuf, 0x0004, ref offset); //Entity Type SRK = 0x0004\n    AddUInt32Reversed (cmdBuf, 0x40000000, ref offset); //Entity Value SRK = 0x40000000\n    rnd.NextBytes (oddOsap);\n    AddBlob (cmdBuf, oddOsap, ref offset);\n    uint cmdSize = (System.UInt32) offset;\n    offset = 2;\n    AddUInt32Reversed (cmdBuf, cmdSize, ref offset);\n\n    outSize = (System.UInt32) (Marshal.SizeOf (hAuth) + nonceEven.Length + evenOsap.Length);\n\n    byte[] response = new byte[outSize];\n    unsafe {\n        UInt32 result = 0;\n\n        //uint cmdSize = (uint)offset;\n        uint resSize = outSize;\n        fixed (byte * pCmd = cmdBuf, pRes = response) {\n            result = Tbsip_Submit_Command (hContext, 0, 200, pCmd, cmdSize, pRes, & resSize);\n        }\n    }\n\n    byte contSession = 0;\n    System.UInt32 hKey = 0x40000000; //TPM_KH_SRK;\n    System.UInt32 pcrInfoSize = 0;\n    byte[] srkAuthdata = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };\n    uint inDataSize = (uint) plaintext.Length;\n\n    offset = 2 + 4 + 4; //2 for tag, 4 for size and 4 for return code\n    byte[] hauthbytes = new byte[Marshal.SizeOf (hAuth)];\n    Array.Copy (response, offset, hauthbytes, 0, hauthbytes.Length);\n    Array.Reverse (hauthbytes);\n    hAuth = System.BitConverter.ToUInt32 (hauthbytes, 0);\n    offset += Marshal.SizeOf (hAuth);\n    Array.Copy (response, offset, nonceEven, 0, nonceEven.Length);\n    offset += nonceEven.Length;\n    Array.Copy (response, offset, evenOsap, 0, evenOsap.Length);\n\n    //shared-secret = HMAC(srk_auth, even_osap || odd_osap)\n    byte[] sharedSecretBuf = new byte[evenOsap.Length + oddOsap.Length];\n    Array.Copy (evenOsap, 0, sharedSecretBuf, 0, evenOsap.Length);\n    Array.Copy (oddOsap, 0, sharedSecretBuf, evenOsap.Length, oddOsap.Length);\n    System.Security.Cryptography.HMACSHA1 sharedSecretHmac = new System.Security.Cryptography.HMACSHA1 (srkAuthdata);\n    byte[] sharedSecret = sharedSecretHmac.ComputeHash (sharedSecretBuf);\n\n    byte[] authSha1InBuf = new byte[sharedSecret.Length + nonceEven.Length];\n    Array.Copy (sharedSecret, 0, authSha1InBuf, 0, sharedSecret.Length);\n    Array.Copy (nonceEven, 0, authSha1InBuf, sharedSecret.Length, nonceEven.Length);\n    System.Security.Cryptography.SHA1Managed sha1 = new System.Security.Cryptography.SHA1Managed ();\n    byte[] authSha1 = sha1.ComputeHash (authSha1InBuf);\n    byte[] encAuth = Xor (srkAuthdata, authSha1);\n\n    //inParamDigest = sha1(1S ~ 6S) \n    int paramInDigestInBufSize =\n        sizeof (System.UInt32) + \n        encAuth.Length +\n        Marshal.SizeOf (pcrInfoSize) +\n        Marshal.SizeOf (inDataSize) +\n        (int) inDataSize;\n    byte[] paramInDigestInBuf = new byte[paramInDigestInBufSize];\n    offset = 0;\n    AddUInt32Reversed (paramInDigestInBuf, 0x00000017, ref offset);\n    AddBlob (paramInDigestInBuf, encAuth, ref offset);\n    AddUInt32Reversed (paramInDigestInBuf, 0x0, ref offset); //PCR info size\n    AddUInt32Reversed (paramInDigestInBuf, inDataSize, ref offset);\n    AddBlob (paramInDigestInBuf, plaintext, ref offset);\n\n    byte[] paramInDigest = sha1.ComputeHash (paramInDigestInBuf);\n\n    int pubAuthInBufSize = paramInDigest.Length + nonceEven.Length + nonceOdd.Length + Marshal.SizeOf (contSession);\n    byte[] pubAuthInBuf = new byte[pubAuthInBufSize];\n\n    offset = 0;\n    AddBlob (pubAuthInBuf, paramInDigest, ref offset);\n    AddBlob (pubAuthInBuf, nonceEven, ref offset);\n    AddBlob (pubAuthInBuf, nonceOdd, ref offset);\n    AddBool (pubAuthInBuf, contSession, ref offset);\n    System.Security.Cryptography.HMACSHA1 pubAuthHmac = new System.Security.Cryptography.HMACSHA1 (sharedSecret);\n    byte[] pubAuth = pubAuthHmac.ComputeHash (pubAuthInBuf);\n\n    //Seal\n    offset = 0;\n    AddUInt16Reversed (cmdBuf, 0x00C2, ref offset); // TPM_TAG_RQU_AUTH1_COMMAND;\n    offset = 6;\n    AddUInt32Reversed (cmdBuf, 0x00000017, ref offset); // TPM_ORD_SEAL;\n    offset = 2 + 4 + 4; //2 for tag, 4 for size and 4 for command code\n\n    AddUInt32Reversed (cmdBuf, hKey, ref offset);\n    AddBlob (cmdBuf, encAuth, ref offset);\n    AddUInt32Reversed (cmdBuf, pcrInfoSize, ref offset);\n    AddUInt32Reversed (cmdBuf, inDataSize, ref offset);\n    AddBlob (cmdBuf, plaintext, ref offset);\n\n    AddUInt32Reversed (cmdBuf, hAuth, ref offset);\n    AddBlob (cmdBuf, nonceOdd, ref offset);\n    AddBool (cmdBuf, contSession, ref offset);\n    AddBlob (cmdBuf, pubAuth, ref offset);\n    cmdSize = (System.UInt32) offset;\n    offset = 2;\n    AddUInt32Reversed (cmdBuf, cmdSize, ref offset);\n\n    outSize = 768;\n    uint responseSize = 0;\n\n    response = new byte[outSize];\n    unsafe {\n        UInt32 result = 0;\n\n        uint resSize = outSize;\n        fixed (byte * pCmd = cmdBuf, pRes = response) {\n            result = Tbsip_Submit_Command (hContext, 0, 200, pCmd, cmdSize, pRes, & resSize);\n        }\n        responseSize = resSize;\n    }\n\n    byte[] retBuffer = new byte[responseSize - 10];\n    Array.Copy (response, 10, retBuffer, 0, retBuffer.Length);\n    Tbsip_Context_Close (hContext);\n    return retBuffer;\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

代码分析:

\n\n
[DllImport ("tbs.dll")]\n...\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
    void AddUInt32Reversed (byte[] a, System.UInt32 o, ref int i) { ... }\n    void AddUInt16Reversed (byte[] a, System.UInt16 o, ref int i) { ... }\n    void AddBool (byte[] a, byte b, ref int i) { ... }\n    void AddBlob (byte[] a, byte[] b, ref int i) { ... }\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
    ...\n    UInt32 result = Tbsi_Context_Create ( & version, & handle);\n    ...\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
    byte[] cmdBuf = new byte[768];\n
Run Code Online (Sandbox Code Playgroud)\n\n

这在TPM PC 客户端规范中指定为最小缓冲区大小。足以满足我们这里的需求。

\n\n

TPM 1.2 规范第 3 部分规定如下:

\n\n
\n
TPM_Seal requires the encryption of one parameter (\xe2\x80\x9cSecret\xe2\x80\x9d). For the\nsake of uniformity with other commands that require the encryption of\nmore than one parameter, the string used for XOR encryption is\ngenerated by concatenating a nonce (created during the OSAP session)\nwith the session shared secret and then hashing the result.\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

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

\n\n
\n
The authorization session handle used for keyHandle authorization.\nMust be an OSAP session for this command.\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

所以我们需要先建立这个OSAP会话。OSAP 在TPM 1.2 规范第 1 部分中进行了描述。OSAP(即对象特定授权协议)的发明是为了处理这样的用例:您想要使用需要多次授权的 TPM 对象,但又不想每次都提供授权:而是使用 OSAP 会话,这依赖于“共享秘密”的概念,这是一个HMAC,它将对象授权数据与双方生成的随机数混合在一起,以防止回复攻击。因此,“共享秘密”仅为本次会话中的两侧所知:发起会话的一侧(用户)和接受会话的一侧(TPM);并且,双方必须具有相同的对象授权数据,“共享秘密”才相同;此外,一个会话中使用的“共享秘密”在另一个会话中将无效。规范中的下图描述了该过程:

\n\n

OSAP

\n\n

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

\n\n
    offset = 0;\n    AddUInt16Reversed (cmdBuf, 0x00C1, ref offset);\n    offset = 6;\n    AddUInt32Reversed (cmdBuf, 0x0000000B, ref offset);\n\n    offset = 2 + 4 + 4; //2 for tag, 4 for size and 4 for command code\n\n    AddUInt16Reversed (cmdBuf, 0x0004, ref offset); //Entity Type SRK = 0x0004\n    AddUInt32Reversed (cmdBuf, 0x40000000, ref offset); //Entity Value SRK = 0x40000000\n    rnd.NextBytes (oddOsap);\n    AddBlob (cmdBuf, oddOsap, ref offset);\n    uint cmdSize = (System.UInt32) offset;\n
Run Code Online (Sandbox Code Playgroud)\n\n

TPM_OSAP 命令操作数为:

\n\n

TPM_OSAP 操作数

\n\n

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

\n\n
  2 bytes       4 bytes             4 bytes\n+---------+------------------+------------------+---------------------------\n|   Tag   |       Size       |   Command code   |    Command body    ....\n+---------+------------------+------------------+---------------------------\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

Size 是一个四字节值,指定命令的大小(以字节为单位),包括标签和大小本身。计算完后,我们将在稍后设置该值。

\n\n

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

\n\n

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

\n\n
    result = Tbsip_Submit_Command (hContext, 0, 200, pCmd, cmdSize, pRes, & resSize);\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

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

我们应该从 TPM_OSAP 上的 TPM 返回的数据是:

\n\n

TPM_OSAP 响应

\n\n

所以我们回来了:

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

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

\n\n
    byte[] sharedSecretBuf = new byte[evenOsap.Length + oddOsap.Length];\n    Array.Copy (evenOsap, 0, sharedSecretBuf, 0, evenOsap.Length);\n    Array.Copy (oddOsap, 0, sharedSecretBuf, evenOsap.Length, oddOsap.Length);\n    System.Security.Cryptography.HMACSHA1 sharedSecretHmac = new System.Security.Cryptography.HMACSHA1 (srkAuthdata);\n    byte[] sharedSecret = sharedSecretHmac.ComputeHash (sharedSecretBuf);\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

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

\n\n

TPM_密封件

\n\n

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

\n\n

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

\n\n

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

\n\n

阿迪普

\n\n

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

\n\n

将所有内容放在图表中:

\n\n

pubAuth 计算

\n\n

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

\n\n
    offset = 0;\n    AddUInt16Reversed (cmdBuf, 0x00C2, ref offset); \n    offset = 6;\n    AddUInt32Reversed (cmdBuf, 0x00000017, ref offset); // TPM_ORD_SEAL;\n    ...\n    result = Tbsip_Submit_Command (hContext, 0, 200, pCmd, cmdSize, pRes, & resSize);\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
    byte[] retBuffer = new byte[responseSize - 10];\n    Array.Copy (response, 10, retBuffer, 0, retBuffer.Length);\n    Tbsip_Context_Close (hContext);\n    return retBuffer;\n
Run Code Online (Sandbox Code Playgroud)\n\n<


Sco*_*tus 6

底漆

以下所有内容均与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 lib
  • 一些基本的命令行工具

您不一定需要软件TPM仿真器,也可以连接到机器的HW TPM.但是,您可以拦截已发出的命令并查看响应,从而了解它们的组合方式以及它们与命令规范的对应关系.

高水平

先决条件:

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

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

  1. 创造一把钥匙
  2. 将key-blob存储在某个地方
  3. 确保密钥已加载到TPM中
  4. 密封blob

要开封,你需要:

  1. 获得密钥blob
  2. 将密钥加载到TPM
  3. 开封密封的斑点

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

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

TPM命令

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

  • TPM_OSAP
  • TPM_CreateWrapKey
  • TPM_LoadKey2
  • TPM_Seal

如果您还想读取当前的PCR值:

  • TPM_PCRRead

  • 由于 CodePlex 关闭,TPM 库现在位于 https://github.com/Microsoft/TSS.MSR (2认同)