All TPM chips contain an Endorsement Key (EK) that is unique for every TPM and can identify it. The EK can't be changed or removed.
The public-key part of the TPM Endorsement Key (EKpub) can thererfore serve as a unique and tamper-proof machine identifier. A more compact secure identifier can be computed by taking the SHA-256 hash of EKpub.
Retrieve EKpub from TPM:
(Get-TpmEndorsementKeyInfo -Hash "Sha256").PublicKeyHash
This command unfortunately require admin privileges. This is strange, since the underlying Win32 APIs does not require admin privileges.
The C++ TpmIdentifier project in this folder demonstrates how to retrieve EKpub from TPM and compute the SHA-256 hash without admin privileges. It also demonstrates how to compute the CRC-32 checksum if a more compact 32bit machine identifier is needed.
Retrieve EKpub from TPM without admin privileges:
// Add System.IO.Hashing & Microsoft.Windows.CsWin32 NuGet packages
// Add NativeMethods.txt to project folder with NCryptOpenStorageProvider, NCryptGetProperty, NCRYPT_PCP_RSA_EKPUB_PROPERTY & BCRYPT_RSAKEY_BLOB lines to enable PInvoke calls
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using Windows.Win32;
using Windows.Win32.Security.Cryptography;
byte[] EKblob; // RSA public key blob in BCRYPT_RSAKEY_BLOB format
{
// Connect to TPM chip
NCryptFreeObjectSafeHandle handle;
PInvoke.NCryptOpenStorageProvider(out handle, CngProvider.MicrosoftPlatformCryptoProvider.Provider, 0);
// Get EKpub blob
EKblob = new byte[1024];
uint blobLen = 0;
PInvoke.NCryptGetProperty(handle, PInvoke.NCRYPT_PCP_RSA_EKPUB_PROPERTY, EKblob, out blobLen, 0);
Array.Resize(ref EKblob, (int)blobLen);
handle.Close();
}
byte[] EKpub; // ASN.1 DER encoding of the RSA public key
{
// Extract RSA modulus and exponent
var tmp = GCHandle.Alloc(EKblob, GCHandleType.Pinned);
var header = (BCRYPT_RSAKEY_BLOB)Marshal.PtrToStructure(tmp.AddrOfPinnedObject(), typeof(BCRYPT_RSAKEY_BLOB))!;
tmp.Free();
// Exponent & Modulus follows the header in the blob (https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/ns-bcrypt-bcrypt_rsakey_blob)
var rsa = new RSAParameters();
rsa.Exponent = new byte[header.cbPublicExp];
Array.Copy(EKblob, Marshal.SizeOf(header), rsa.Exponent, 0, header.cbPublicExp);
rsa.Modulus = new byte[header.cbModulus];
Array.Copy(EKblob, Marshal.SizeOf(header) + header.cbPublicExp, rsa.Modulus, 0, header.cbModulus);
EKpub = RSA.Create(rsa).ExportRSAPublicKey();
}
// Compute EKpub hash & checksum
var sha256 = SHA256.HashData(EKpub);
Console.WriteLine("SHA-256 of EKpub: " + BitConverter.ToString(sha256));
var crc32 = System.IO.Hashing.Crc32.Hash(EKpub);
Array.Reverse(crc32); // to big endian
Console.WriteLine("CRC-32 of EKpub: " + BitConverter.ToString(crc32));