Talking to a TPM 2.0: Startup, Provisioning, and Reading Raw Commands
By Davis Muro • 7 minutes read •
TPM 2.0 is often treated as a magic switch you “turn on in BIOS.” In reality, it’s just a device on a bus that speaks a very concrete binary protocol.
If you’ve ever wondered:
- What actually happens when the TPM “starts”
- How to tell whether a TPM is really provisioned/manufactured
- What all those handles (0x4000… / 0x8000…) mean
- How to read a raw TPM command/response at the byte level
this post walks through those pieces using real command dumps.
1. Startup: _TPM_Init() vs TPM2_Startup()
Before you can send “normal” commands (like GetRandom or CreatePrimary), the TPM has to go through a startup sequence.
There are two distinct layers:
_TPM_Init()A hardware/firmware signal that the TPM was just powered on or reset. Think of this like pulling the reset line: volatile state is lost, transient objects are gone, and the TPM expects a startup command.TPM2_Startup(SU_CLEAR | SU_STATE)A command you send over the TPM’s interface to tell it what type of startup to perform:SU_CLEAR: cold boot, initialize everything from scratchSU_STATE: resume from a previously saved state (e.g., sleep), if supported
On typical PCs, firmware (UEFI/BIOS) issues TPM2_Startup for you. On dev boards, embedded systems, or some test setups, you may be responsible for sending it yourself. If you skip it, the TPM won’t behave like a normal device.
1.1 What Happens If You Skip Startup
If you try to send a command before TPM2_Startup, you’ll run into error codes like:
TPM_RC_INITIALIZE— “I haven’t been started yet.”
Here’s an example using TPM2_GetRandom before startup. The request is:
==============================
REQUEST
==============================
TPM Command Tag (hex): 0x8001
- TPM_ST_NO_SESSIONS: command has no session area.
TPM Command Length (hex): 0x0000000c
- 0x0c = 12 bytes total command size.
TPM Command Code (hex): 0x0000017b
- TPM_CC_GetRandom.
TPM Command (hex), annotated:
Offset Bytes Meaning
------ ----- -------
00 80 01 tag = 0x8001 (command, no sessions)
02 00 00 00 0c length = 0x0000000c (12 bytes)
06 00 00 01 7b commandCode = 0x0000017b (GetRandom)
0a 00 10 bytesRequested = 0x0010 (16 bytes)
The TPM’s response:
==============================
RESPONSE
==============================
TPM Response Tag (hex): 0x8001
- Response without sessions.
TPM Response Size (hex): 0x0000000a
- 0x0a = 10 bytes total response size (minimal error response).
TPM Error Response core: 00 00 01 00
- responseCode = 0x00000100 = TPM_RC_INITIALIZE.
Offset Bytes Meaning
------ ----- -------
00 80 01 tag = 0x8001 (response, no sessions)
02 00 00 00 0a length = 0x0000000a (10 bytes)
06 00 00 01 00 responseCode = 0x00000100 (TPM_RC_INITIALIZE)
The takeaway: until _TPM_Init() has occurred and a successful TPM2_Startup(SU_CLEAR or SU_STATE) has been processed, the TPM refuses to act like a fully initialized device.
2. “Is This TPM Manufactured?” — Provisioning and Hierarchies
With “normal” PCs, the TPM you see is usually already provisioned by the platform vendor. With raw TPM modules or engineering samples, that’s not always true.
When people say a TPM is “manufactured” or “provisioned,” they usually mean:
- The endorsement hierarchy is configured (and may have vendor certificates).
- The storage (owner) hierarchy has a valid primary seed.
- Default or vendor-defined authorization values are set.
- Commands like
TPM2_CreatePrimarywork as expected under those hierarchies.
2.1 Commands Involved in Provisioning
Depending on the vendor, provisioning flows may involve:
TPM2_Clear
Reset owner and endorsement hierarchies to a factory-like state (subject to platform policy).TPM2_HierarchyChangeAuth
Set new authorization values (passwords or policies) for:- Owner (storage) hierarchy
- Endorsement hierarchy
- Lockout hierarchy
Vendor tools that:
- Create and persist an Endorsement Key (EK)
- Create an SRK-like primary key in the storage hierarchy
- Populate NV indices with vendor data
On many motherboards or laptops, UEFI/BIOS runs all of this once, then exposes a “Ready to use” TPM to the OS.
2.2 How to Tell If a TPM Is Provisioned
You can probe TPM state with capability and property queries:
TPM2_GetCapability(TPM_CAP_TPM_PROPERTIES, ...)
For things like:TPM_PT_PERMANENT(flags about hierarchy enablement and lockout)TPM_PT_STARTUP_CLEAR(what clears on startup)
TPM2_GetCapability(TPM_CAP_HANDLES, ...)
To see whether certain handle ranges are in use.
A very practical test is: try to create a primary in the storage or endorsement hierarchy. If the TPM is correctly manufactured, a simple TPM2_CreatePrimary under the storage hierarchy (0x40000001) should succeed.
3. Handles and Object Creation: CreatePrimary vs Create
Handles are where a lot of people get confused. They look like arbitrary 32‑bit numbers, but the ranges are meaningful.
3.1 Handle Types (Quick Reference)
For this post, the main ones are:
Hierarchies (fixed handles)
- Storage/Owner:
0x40000001 - Endorsement:
0x4000000B - Platform:
0x4000000C - Null:
0x40000007
- Storage/Owner:
Transient objects:
0x8000_0000–0x80FF_FFFF- Live in TPM RAM.
- Disappear on reset/power loss.
Persistent objects:
0x8100_0000–0x81FF_FFFF- Survive across resets.
- You “pin” a transient object with
TPM2_EvictControl.
In typical flows you:
- Use a hierarchy handle as the parent when calling
TPM2_CreatePrimary. - Get back a transient object handle in the
0x8000_0000range. - Use that transient handle as parent in
TPM2_Create,TPM2_Load, etc. - Optionally turn that transient handle into a persistent one with
TPM2_EvictControl.
3.2 TPM2_CreatePrimary: Getting a First Handle
TPM2_CreatePrimary creates a primary object directly under a hierarchy (storage, endorsement, etc.):
Input:
- Hierarchy handle (e.g.
0x40000001for storage) - Authorization for that hierarchy (often empty during initial provisioning)
- Template for the new object (type, algorithms, attributes)
- Hierarchy handle (e.g.
Output:
- A transient object handle (e.g.
0x80000000) - The object’s public area (
TPM2B_PUBLIC) - Creation data, hash, and ticket (metadata about how/where it was created)
- A transient object handle (e.g.
a real CreatePrimary command dump (simplified and annotated).
3.2.1 Example: Raw CreatePrimary Command, Success, and Error
Command:
==============================
REQUEST
==============================
TPM Command Tag (hex): 0x8002
- TPM_ST_SESSIONS: command includes a session area (for auth).
TPM Command Length (hex): 0x0000003f
- 0x3f = 63 bytes total command size.
TPM Command Code (hex): 0x00000131
- TPM_CC_CreatePrimary.
TPM Command (hex):
80 02 00 00 00 3f 00 00 01 31 40 00 00 01 00 00 00 09
40 00 00 09 00 00 00 00 00 00 00 00 1a 00 01 00 0b 00
03 00 72 00 00 00 06 00 80 00 43 00 10 08 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00
Breaking down just the key fields:
Offset Bytes Meaning
------ ----- -------
00 80 02 tag = 0x8002 (command with sessions)
02 00 00 00 3f length = 0x0000003f (63 bytes)
06 00 00 01 31 commandCode = 0x00000131 (CreatePrimary)
0A 40 00 00 01 parent = 0x40000001 (storage hierarchy)
0E 00 00 00 09 authSize = 9 bytes in auth area
12 40 00 00 09 sessionHandle = 0x40000009
(simple auth session handle here)
16 00 00 nonceSize = 0 (no nonce)
18 00 sessionAttributes = 0
19 00 00 hmacSize = 0 (empty auth)
1B 00 1a inPublic.size = 0x001a (26 bytes)
1D 00 01 type = TPM_ALG_RSA (0x0001)
1F 00 0b nameAlg = TPM_ALG_SHA256 (0x000B)
21 00 03 00 72 objectAttributes (bitfield)
25 00 00 authPolicy.size = 0 (no policy)
27 00 06 parameter size / start of RSA params
29 00 80 00 43 00 10 08 ...
(RSA keyBits, exponent, scheme, symmetric info)
... (rest of template / padding)
Successful CreatePrimary Response (Annotated)
==============================
RESPONSE
==============================
TPM Response Tag (hex): 0x8002
- TPM_ST_SESSIONS: response includes a session area.
TPM Response Size (hex): 0x000001ea
- 0x1ea = 490 bytes total response size.
TPM Response Code (hex): 0x00000000
- TPM_RC_SUCCESS.
TPM Response (hex):
80 02 00 00 01 ea 00 00 00 00 80 00 00 00 00 00
01 d3 01 1a 00 01 00 0b 00 03 04 72 00 00 00 06
00 80 00 43 00 10 08 00 00 00 00 00 01 00 cc f4
c7 e3 16 f1 63 41 80 93 d2 19 7d 52 5a 90 9e 54
82 ea 4a 5e 47 52 f7 12 7a 59 18 bf a1 d5 62 1e
44 ca 2d 40 de 0b 3d 64 e4 51 e6 f0 f5 f6 a4 e1
a3 6f 22 cf 9e 63 56 4d e2 78 57 21 68 51 d7 aa
cf 28 59 48 61 6c 63 c1 46 b6 cb a8 14 25 af d8
23 1b c2 03 62 1c c5 7e 1a ab c3 b4 76 6c 71 fa
4c 61 54 c6 f6 18 0e 33 65 fa 1c 0c ab fa 20 07
f8 37 2a e5 15 00 7e 87 95 cf ca f7 75 87 5c c6
f9 f2 76 27 58 28 2f c7 d7 e7 df 68 93 14 e7 4d
fb 8e 4f a2 d9 da c8 d3 90 69 f3 84 8c c2 ff 85
c1 23 cb c6 61 60 a5 7e 82 9c 83 5b 1e 05 1f 77
68 c8 32 6d 91 e5 da 02 52 de 43 e3 2a d7 52 d6
da f4 c6 5d 87 c1 ba 2f 74 c8 87 fe 09 ca ba 40
e9 f8 df 96 17 d0 a7 7a dc 13 4f 08 f1 a7 30 c8
3f d0 04 0d be 9a a9 be ad 4d 3f 6e 33 2b 98 b4
e9 3d 08 1a ea 20 ca d0 7e d3 66 7c e2 ef 00 37
00 00 00 00 00 20 e3 b0 c4 42 98 fc 1c 14 9a fb
f4 c8 99 6f b9 24 27 ae 41 e4 64 9b 93 4c a4 95
99 1b 78 52 b8 55 01 00 10 00 04 40 00 00 01 00
04 40 00 00 01 00 00 00 20 5d a0 41 ba c0 ee 31
35 ae bb 0c ad fb a4 97 c6 a1 87 7f ae 83 2d d3
d1 f8 f7 a8 71 b8 25 e8 54 80 21 40 00 00 01 00
30 f3 79 37 38 d5 69 6c 1c 36 59 9e ec 09 bf 22
9e 92 aa 3a 9d 76 59 69 1e 1f ca 41 5d d0 db a3
6b 6b ed 5e 38 98 6b 19 76 d7 64 6b 47 e9 4c e0
9f 00 22 00 0b 4a 08 3c 3b 94 77 e9 82 76 05 11
87 bc f5 3d ef ef af 5d 97 56 bd fa df 6f a9 32
14 ce 8b 00 85 00 00 01 00 00
Breaking down the key parts of the successful response:
Offset Bytes Meaning
------ ----- -------
00 80 02 tag = 0x8002 (response with sessions)
02 00 00 01 ea length = 0x000001ea (490 bytes)
06 00 00 00 00 responseCode = 0x00000000 (TPM_RC_SUCCESS)
The next fields are the normal CreatePrimary return parameters:
0A 80 00 00 00 handle = 0x80000000
- Transient handle for the new primary object.
0E 00 00 outPublic.size MSB
10 00 01 d3 01 ... outPublic (TPM2B_PUBLIC)
- Public area for the created key:
* type = TPM_ALG_RSA (0x0001)
* nameAlg = TPM_ALG_SHA256 (0x000B)
* objectAttributes = 0x00030472
* RSA parameters (key size, exponent, scheme)
* public modulus (unique.rsa)
... outCreationData (TPM2B_CREATION_DATA)
- Creation-time state and locality info.
... creationHash (TPM2B_DIGEST)
- Hash of the creation data.
... creationTicket (TPMT_TK_CREATION)
- Ticket proving this object was created by the TPM.
... outName (TPM2B_NAME)
- The computed name of the new object.
... sessionArea
- Session response (auth, nonce, attributes, HMAC if used).
The exact byte offsets for outPublic, outCreationData, creationHash, creationTicket, and outName depend on their internal size fields, but the important pattern is:
- After the 10‑byte header,
CreatePrimaryreturns:- A transient handle for the new key.
- A fully encoded
TPM2B_PUBLICstructure. - Creation metadata and integrity (creation data, hash, ticket).
- The object name.
- Optional session response data.
This is the “happy path” layout you want to see when debugging: header → handle → public template → creation info → sessions.
Error Response for Contrast
==============================
RESPONSE
==============================
TPM Response Tag (hex): 0x8002
- Response without sessions (minimal error response).
TPM Response Size (hex): 0x0000000a
- 0x0a = 10 bytes total.
TPM Response (hex):
80 01 00 00 00 0a 00 00 01 d5
Offset Bytes Meaning
------ ----- -------
00 80 01 tag = 0x8001 (no session area)
02 00 00 00 0a length = 0x0000000a
06 00 00 01 d5 responseCode = 0x000001d5 (error)
0x000001D5 is a non‑success TPM_RC_* code (the exact symbolic name depends on the spec revision and context). The important contrast is:
- Successful commands:
tag = 0x8002(with sessions),responseCode = 0x00000000,- full parameter area (handle, public, creation data, name, session).
- Errors:
tag = 0x8002(often similar to request),- non‑zero
responseCode, - often only the 10‑byte header (tag, length, code), with no parameters.
3.3 TPM2_Create: Child Objects Under a Parent
Once you have a primary key handle (e.g., 0x80000000), you can call TPM2_Create to create child objects under that parent.
The flow:
TPM2_CreatePrimaryunder0x40000001→ returns0x80000000.TPM2_Create(parent = 0x80000000, template, inSensitive)→ returnsoutPrivateandoutPublic.TPM2_Load(parent = 0x80000000, outPrivate, outPublic)→ returns a new transient handle (e.g.,0x80000001).
High level:
- Parent handle: defines which hierarchy/tree your object lives under.
- outPrivate: opaque blob encrypted under the parent.
- outPublic: the object’s public description.
- Handle returned by Load: what you’ll use in subsequent commands (sign, decrypt, etc.).
TPM2_Create – Example Request
High‑level description:
- Parent handle: 0x80000000 (loaded parent key)
- New object: 2048‑bit RSA key, sign usage
- Auth/session: simple password session with empty auth
- Sensitive data: empty userAuth, no extra data
==============================
REQUEST
==============================
TPM Command Tag (hex): 0x8002 ; TPM_ST_SESSIONS
TPM Command Length (hex): 0x0000004A ; 74 bytes total (example)
TPM Command Code (hex): 0x00000153 ; TPM_CC_Create
TPM Command (hex):
80 02 ; tag = TPM_ST_SESSIONS
00 00 00 4A ; commandSize = 0x4A (74 bytes)
00 00 01 53 ; commandCode = TPM_CC_Create
80 00 00 00 00 01 ; parentHandle = 0x80000001 (example)
00 09 ; authAreaSize = 9 bytes
40 00 00 09 ; sessionHandle = TPM_RS_PW (password session)
00 ; nonceSize = 0
00 ; sessionAttributes = 0
00 ; hmacSize = 0 (no password)
; inSensitive (TPM2B_SENSITIVE_CREATE)
00 00 ; size of inSensitive (0, no auth/user data)
; inPublic (TPM2B_PUBLIC)
00 1E ; size = 30 bytes (example, truncated)
00 01 ; type = TPM_ALG_RSA
00 10 ; nameAlg = TPM_ALG_SHA256
00 00 ; objectAttributes (part1, truncated)
00 72 ; objectAttributes (part2, truncated, e.g. sign, userWithAuth)
00 00 ; authPolicy size = 0
00 01 00 01 ; parameters: keyBits = 2048, exponent = 0x00010001
00 00 ; unique (RSA modulus) size = 0 (TPM will generate)
; outsideInfo (TPM2B_DATA)
00 00 ; size = 0
; creationPCR (TPML_PCR_SELECTION)
00 00 00 00 ; count = 0 (no PCR selection)
TPM Command (binary – truncated, corresponding to hex above):
10000000 00000010 ...
... ; omitted: direct binary equivalent of the hex above
TPM2_Create – Example Successful Response
- Tag: TPM_ST_SESSIONS
- Response code: TPM_RC_SUCCESS
- Returned:
- outPrivate: the encrypted sensitive portion of the new object
- outPublic: the public template of the created key
- creationData / creationHash / creationTicket: data binding object to current state
==============================
RESPONSE
==============================
TPM Response Tag (hex): 0x8002 ; TPM_ST_SESSIONS
TPM Response Size (hex): 0x00000150 ; 336 bytes total (example)
TPM Response Code (hex):0x00000000 ; TPM_RC_SUCCESS
TPM Response (hex):
80 02 ; tag
00 00 01 50 ; responseSize
00 00 00 00 ; responseCode = success
; outPrivate (TPM2B_PRIVATE) – truncated
00 60 ; size = 0x60 (96 bytes, example)
AA AA AA ... AA ; private buffer (sensitive, encrypted by TPM)
; outPublic (TPM2B_PUBLIC) – truncated
00 40 ; size = 0x40 (64 bytes, example)
00 01 ; type = TPM_ALG_RSA
00 10 ; nameAlg = TPM_ALG_SHA256
00 00 00 72 ; objectAttributes (truncated)
00 00 ; authPolicy size = 0
00 01 00 01 ; parameters: 2048‑bit, exponent 65537
00 20 ; unique size = 32 bytes (truncated example)
BB BB BB ... BB ; beginning of public modulus (truncated)
; creationData (TPM2B_CREATION_DATA) – truncated
00 30 ; size = 0x30 (48 bytes, example)
CC CC CC ... CC ; creation data
; creationHash (TPM2B_DIGEST) – truncated
00 20 ; size = 0x20 (32 bytes, SHA‑256)
DD DD DD ... DD ; hash of creation data
; creationTicket (TPMT_TK_CREATION) – truncated
80 01 ; tag = TPM_ST_CREATION
00 00 00 00 ; hierarchy = TPM_RH_OWNER (example)
00 20 ; digest size
EE EE EE ... EE ; ticket digest
TPM Response (binary – truncated, corresponding to hex above):
10000000 00000010 ...
... ; omitted: direct binary equivalent of the hex above
4. Putting It All Together
From the TPM’s perspective, a “normal” provisioning flow looks roughly like:
- Reset / power on
_TPM_Init()is asserted.
- Startup
- Firmware or your code sends
TPM2_Startup(SU_CLEAR).
- Firmware or your code sends
- (Optional) Manufacturing / provisioning
- Vendor or platform runs
TPM2_Clear,TPM2_HierarchyChangeAuth, creates EK/SRK, etc.
- Vendor or platform runs
- Your application starts using the TPM
- Query properties (
TPM2_GetCapability). - Create a primary key:
TPM2_CreatePrimary(parent = 0x40000001).
- Use that key as a parent for children:
TPM2_Create,TPM2_Load, etc.
- Query properties (
5. A Concrete Example: Signing Data with a TPM
Everything above can feel abstract until you actually use a TPM for something real.
To make this concrete, I’ve put together a small sample project that walks through a complete, minimal TPM signing flow using TSS:
👉 TPMSign
What the Project Demonstrates
The project shows how to:
- Initialize and verify TPM state
- Assumes the TPM has completed
_TPM_Init()andTPM2_Startup. - Verifies that a usable storage hierarchy exists.
- Assumes the TPM has completed
- Create (or reuse) a primary key
- Uses
TPM2_CreatePrimaryunder the storage hierarchy (0x40000001). - Produces a transient handle in the 0x8000_0000 range.
- Uses
- Create a signing key under that primary
- Uses
TPM2_Create+TPM2_Load. - The private portion of the key never leaves the TPM.
- Uses
- Sign user-supplied data
- Hashes the input message
- Uses
TPM2_Signto generate a signature inside the TPM. - Returns a standard RSA signature that can be verified outside the TPM.
At no point does the signing key’s private material leave the TPM — all cryptographic operations are bound to TPM handles, exactly as described earlier in this post.
If you understood the annotated command dumps in this post, the project should feel familiar — it’s the same flow, just applied to a practical task