HomeNewsletterCommunityMVP FeedToolsArchiveBlogToday's NewsAboutServicesQuick Links Subscribe free
← Back to Blog
Windows Hello for Business WHfBmsDS-KeyCredentialLinkKey TrustActive DirectoryKey AdminsPowerShell

Where Windows Hello for Business Keys Really Live: The msDS-KeyCredentialLink Deep Dive

IA
Imran Awan
31 July 2026

Every Windows Hello for Business (WHfB) key-trust and hybrid deployment depends on one Active Directory attribute that almost nobody ever opens: msDS-KeyCredentialLink. It is where a user's WHfB public key physically lives in your directory. When sign-in works, you never think about it. When it breaks - stale keys, orphaned keys, a user with six entries and none of them valid - it is the first place you should look and the last place most admins know how to read.

This is the deep dive. We will decode exactly what that attribute stores, show you how to enumerate and audit it with PowerShell, explain how Microsoft Entra Connect populates it, and - critically - how to find and remove orphaned keys without locking anyone out. Along the way you will see why this same attribute is a favourite target for attackers, which is the subject of the next post in this series.

The problem: an invisible attribute that decides whether Hello works

In the key-trust model, a domain controller (DC) authenticates a WHfB sign-in by checking the user's public key against the value stored in their msDS-KeyCredentialLink attribute. If the key the client presents is not in that attribute, the DC has nothing to validate against and sign-in fails - often with a generic "that option is temporarily unavailable" or a silent fall-back to password. The attribute is multi-valued, so a user can accumulate keys from every device they have ever provisioned Hello on, and there is no built-in clean-up. Over years, that list grows, and stale entries make troubleshooting genuinely confusing.

Why it happens: what msDS-KeyCredentialLink actually stores

It is a multi-valued, forward-linked attribute on user (and computer) objects. Each value is not a simple string - it is a DN-Binary: a binary "Key Credential" structure paired with the distinguished name of the object that owns it. Raw, one value looks like this:

msDS-KeyCredentialLink (raw DN-Binary)
B:828:0002000020...C0FF:CN=jdoe,OU=Users,DC=contoso,DC=com # B = binary | 828 = hex char count | the blob | owner DN

Decode that binary blob and it carries the fields that matter for troubleshooting:

FieldWhat it is
VersionStructure version (WHfB uses version 2)
KeyIDSHA-256 hash of the public key - the unique identity of this credential
KeyMaterialThe actual RSA/ECC public key (the DC validates the sign-in against this)
KeyUsageNGC for a Windows Hello Next Generation Credential key
KeySourceWhere the key was written from (Active Directory)
DeviceIdGUID of the device the key was created on - your link back to a specific machine
KeyCreationTimeWhen the credential was registered
KeyApproximateLastLogonTimeStampRoughly when it was last used - your signal for "is this key stale?"

The two fields you care about operationally are DeviceId (which machine does this key belong to?) and KeyApproximateLastLogonTimeStamp (has it been used recently, or is it a ghost from a device that was retired two years ago?).

How the key gets there

You do not write this attribute by hand. In a hybrid key-trust deployment, provisioning registers the user's public key with the Azure Device Registration Service, and then Microsoft Entra Connect, on its next sync cycle, pulls that public key down and writes it into the on-premises msDS-KeyCredentialLink attribute. That is the step people forget:

Gotcha: A newly provisioned user cannot sign in with Hello key trust until Entra Connect has synced their key into Active Directory. If a user sets up Hello and immediately gets password prompts, do not start deleting things - check the Entra Connect sync cycle first. The key may simply not have landed in AD yet.

On-premises-only key trust is similar, except the Enterprise Device Registration Service writes the key directly to Active Directory. And note: even cloud Kerberos trust still syncs the key into this attribute (it just does not use it for the ticket) - so the mere presence of a key here does not prove which trust model a device uses.

Note: Write access to msDS-KeyCredentialLink is granted to two special groups created for WHfB: Key Admins (domain scope) and Enterprise Key Admins (forest-root scope). Entra Connect's account effectively writes through this permission. Anyone you place in these groups can write key credentials for other users - treat membership as highly privileged.

How to verify: enumerate and audit the attribute

Native PowerShell can read the attribute, but it returns the raw DN-Binary - useful for "does a key exist and how many," not for decoding fields:

Windows PowerShell (RSAT ActiveDirectory module)
# How many keys does this user carry, and do they have any at all? Get-ADUser jdoe -Properties msDS-KeyCredentialLink | Select-Object Name, @{n='Keys';e={$_.'msDS-KeyCredentialLink'.Count}} Name Keys ---- ---- jdoe 3 # three registered keys - probably three devices, or stale entries

To decode the fields (DeviceId, creation time, last logon) you need a parser. The community DSInternals module (by Michael Grafnetter) reads the structure natively and is the standard tool for this - it is what turns "3 keys" into "3 keys, one from a device deleted in 2024":

Windows PowerShell (DSInternals - community module)
Get-ADReplAccount -SamAccountName jdoe -Server dc01 | Select-Object -Expand KeyCredentials | Format-Table Usage, CreationTime, DeviceId, Source Usage CreationTime DeviceId Source ----- ------------ -------- ------ NGC 2026-01-14 09:22 3f9a...device-still-active AzureAD NGC 2024-03-02 11:05 <device deleted> AzureAD # orphan
Note: DSInternals is a community PowerShell module, not a Microsoft product - vet it and pin a known version before running it against production DCs. Microsoft's own tooling exposes the raw attribute via Get-ADUser; decoding the blob is where the community tool earns its place.

The fix: find and remove orphaned keys safely

An orphaned key is a credential whose device no longer exists (the machine was wiped, retired, or its Entra device record deleted) but whose key was never removed from the user. Orphans are not usually harmful, but they clutter audits and they are indistinguishable from an attacker-planted key unless you correlate each one to a real, live device.

The audit approach: for each key, resolve its DeviceId against your Entra/AD device inventory. A key whose device is gone, or whose last-logon timestamp is ancient, is a removal candidate. Remove a specific value with native PowerShell:

Windows PowerShell (Key Admins rights required)
# Remove ONE specific stale value (get $staleValue from the audit first). # This is a WRITE operation - test in a lab and target one orphan at a time. Set-ADUser jdoe -Remove @{ 'msDS-KeyCredentialLink' = $staleValue }
Watch out: Remove the active key by mistake and you lock the user out of Hello sign-in until they re-provision - which, on a hybrid device, needs line of sight to a DC. Never bulk-clear this attribute. Always identify the live key by its DeviceId and last-logon timestamp first, remove one orphan at a time, and confirm the user can still sign in before touching the next.

Proof it worked: the ongoing audit

A healthy directory has a defensible answer to "why does each user have the keys they have." The audit script for this post produces exactly that: per user, how many keys, when each was created and last used, and which ones have no matching live device. Run it monthly and orphans never pile up.

Tip: Alert on unexpected new keys, not just stale ones. A key credential appearing on a privileged account that the user did not just provision is one of the clearest signals of the shadow-credentials attack. If your audit baseline is clean, a surprise key stands out immediately - which is exactly the detection we build in the next post.

References

PowerShell Scripts — msDS-KeyCredentialLink Audit

Script for this post is in Daily-Tasks.

Get-KeyCredentialLinkAudit.ps1 — read-only: enumerates msDS-KeyCredentialLink across users, counts keys per account and flags accounts carrying multiple keys for review
View all scripts on GitHub
Share this post
LinkedIn X / Twitter Reddit Bluesky

More from EndpointWeekly

Windows Hello for Business
Shadow Credentials: Detecting the msDS-KeyCredentialLink Attack…
The same attribute that stores WHfB keys - msDS-KeyCredentialLink - is one of Active…
Windows Hello for Business
Key Trust vs Certificate Trust vs Cloud Kerberos Trust: Which…
Three trust models, and most admins cannot say for certain which one a given device is…
Windows Hello for Business
Windows Hello for Business Cloud Kerberos Trust: The Complete…
Cloud Kerberos trust deploys Windows Hello for Business with no PKI and no AD FS. This…