A while ago I wrote a script called Convert-RC4toAES.ps1. It does exactly what the name says: it finds accounts whose msDS-SupportedEncryptionTypes is sitting at zero or at RC4 only, and it widens them to RC4 plus AES128 plus AES256. It has a -WhatIf mode, an exclusion list, and a log file. It works fine. It is also, in retrospect, the easy half of the problem.
Setting the attribute is one line. I have never once been nervous about the line. What I have been nervous about, every single time, is the conversation that happens two days later, when an application owner calls to say that the nightly job stopped running and nobody can explain why. The attribute change is trivial. Predicting what it will take down is hard, and there is very little tooling that even tries.
So I built KrbEtypeInsight. It is a PowerShell module that answers one question and only one question: if I remove RC4 from this domain, what breaks, and whose is it? It writes nothing. It reads audit events, reads the directory, reads the per-controller policy, correlates the three, and hands you a list of principals with a severity, a score, and the names of the specific client machines that will stop authenticating.
The module lives in my TechbyJeff repo under Powershell/Active Directory/Kerberos/KrbEtypeInsight, next to the RC4 conversion script that started all this.
Why this suddenly stopped being optional
I started this as a personal itch. It turned into a deadline.
Microsoft has been walking RC4 out of Kerberos on a published schedule, and that schedule ran out this summer. The short version, tied to CVE-2026-20833:
| Date | Phase | What changed |
|---|---|---|
| 13 January 2026 | Audit | RC4DefaultDisablementPhase defaults to 1. The KDC logs warnings about RC4 service ticket issuance but keeps issuing. |
| 14 April 2026 | Enforcement, reversible | The phase value defaults to 2. Domain controllers assume RC4 is not enabled, with DefaultDomainSupportedEncTypes effectively 0x18. You can still roll back with the registry value. |
| July 2026 | Enforcement, permanent | RC4DefaultDisablementPhase is ignored entirely. The escape hatch is gone. |
Separately, Microsoft’s Windows Server blog said in December 2025 that by mid-2026 the assumed default supported encryption types on domain controllers would move from RC4 to AES-SHA1, the same change described from the other direction.
If you are reading this in the second half of 2026, the rollback lever no longer exists. Any account that was depending on RC4 has either already broken or sits one password-policy event away from breaking.
This module is a pre-flight check for a change that, in a lot of estates, has already been made on your behalf.
What a configuration audit cannot answer
Plenty of scripts dump msDS-SupportedEncryptionTypes across a domain and colour the RC4 rows red. I have written one. They are useful and they are not enough, because a configuration audit tells you what an account is allowed to do, and hardening breaks on what an account and its clients did.
Getting to a real answer means reading these sources against each other:
| Source | Question it answers |
|---|---|
| Events 4768, 4769, 4771 | What the KDC has been issuing |
msDS-SupportedEncryptionTypes, userAccountControl | What the directory says each principal supports |
Per-controller DefaultDomainSupportedEncTypes, trusts, krbtgt | What the domain permits by default |
No single source is sufficient. Events record what happened without saying why, the directory captures intent and stops there, and the domain baseline governs what an unconfigured account inherits, which is the case that bites hardest.
Put them together and the output becomes a prediction. Instead of “this account is configured for RC4”, you get something like this:
svc-payrollis Critical, score 53. 412 service ticket requests in 30 days, all RC4. The account holds no AES key material, so the KDC would returnKDC_ERR_NULL_KEY. 12 distinct clients depend on it, of which 3 advertise no AES encryption type at all:APPLIANCE-SCAN01$,LEGACY-ETL02$,KIOSK-114$.
The named clients at the end are what an application owner can act on, and no amount of configuration auditing produces them.
Where a reasonable-looking script goes wrong
Each of the following is a place where careful code gives you a confidently wrong answer.
Two Kerberos numbering systems that do not line up
RC4 is 23 as an RFC 3961 ticket encryption type, and bit 0x4 as an MS-KILE supported-types flag. AES256-SHA1 is 18 in one system and 0x10 in the other. These are not two encodings of the same number, they are two unrelated registries that happen to both describe ciphers.
A script that tests a ticket’s TicketEncryptionType against 4 is testing for DES-CBC-MD4, and will report every RC4 ticket in the estate as clean. That failure mode is silent, and believable enough that nobody goes looking, because the numbers look like they should line up.
The module keeps the two as separate types and never converts between them by arithmetic. The mapping is an explicit table, because it is not a computable relationship:
| |
Look at the last row. Bit 0x20 authorises AES256 for the session key only, never for the ticket, so TicketEtypes is empty where SessionKeyEtypes is populated. An account carrying only that bit issues AES session keys inside RC4 tickets: hardened in a spreadsheet, unhardened on the wire.
The two lists stay separate for a second reason, a capability question sitting alongside the cipher question:
| |
If you sum bits and treat any non-RC4 bit as progress, a FAST-capable RC4-only account reads as modernised.
Configuration does not tell you whether the key exists
Missing key material causes the rollbacks, and I kept building after the first prototype because of it.
Kerberos keys are derived when the password is set. An account whose password predates the domain’s AES support has no AES key, no matter what its attributes claim. Setting that account to AES-only breaks it: the KDC has nothing to encrypt with, so it returns KDC_ERR_NULL_KEY. msDS-SupportedEncryptionTypes cannot show you this, because the attribute describes intent and the key material describes reality.
A version 2 audit event can show you, because it reports the account’s available keys directly. KRB002 is the finding built on that:
| |
The recommended action on that finding is where people go wrong. Reset the password first, confirm AES appears in AvailableKeys on subsequent events, and only then change the attribute. Changing the attribute on an account with no AES key turns a hardening window into an incident.
The July 2026 enforcement makes this unforgiving. A broken account used to be recoverable with the registry override. That option is gone.
An unset attribute is not “supports nothing”
When msDS-SupportedEncryptionTypes is absent or zero, the KDC falls back to the domain controller’s DefaultDomainSupportedEncTypes registry value. Historically that value’s own unwritten default was 0x27, which includes the AES256 session-key bit:
| |
Reading a null attribute as “supports nothing” produces a flood of false Criticals against healthy accounts. Reading it as “supports RC4 only” gets closer and still misses, because the session-key bit is present and makes some accounts look partially AES-capable while their tickets stay RC4.
Since April 2026 the effective default has moved to 0x18, so the module reads the value from the controllers and assumes neither number. Whatever your controllers carry is what your unconfigured accounts inherit, and hardcoding a default misjudges the entire population of accounts that never had the attribute set. In most domains that population is the majority.
Controllers disagree about DefaultDomainSupportedEncTypes
DefaultDomainSupportedEncTypes lives in the registry on each domain controller. It does not replicate. Controllers can and do disagree, usually because someone set it during an earlier hardening push and a DC built afterwards never got it.
A domain where controllers disagree authenticates differently depending on which controller a client reaches. The symptom is intermittent authentication failure that survives every attempt to reproduce it, because reproducing it means hitting the right DC. Get-KrbDomainEtypeContext reads the value from every controller and reports the disagreement, leaving you to decide which controller is right.
Check DomainDefaultSource on every run. Anything other than Registry means the baseline was assumed, and every per-account finding inherits that assumption.
Measurement beat the specification on the SHA-2 bits
MS-KILE documents bits 0x40 and 0x80 as the RFC 8009 AES-SHA2 encryption types. The module originally mapped them to etypes 19 and 20 accordingly, following the specification.
A Windows Server 2025 KDC, build 26100, does not honour them. Tested directly against a lab controller:
msDS-SupportedEncryptionTypes | Result | Etype issued |
|---|---|---|
0x80 | KDC_ERR_ETYPE_NOTSUPP | none |
0xC0 | KDC_ERR_ETYPE_NOTSUPP | none |
0x90 (0x80 + 0x10) | ticket issued | 0x12, the SHA1 type. 0x80 ignored |
0x10 (control) | ticket issued | 0x12 |
An account carrying only those bits cannot obtain a service ticket at all. Because the module counted them toward SupportsAes, it reported such an account as AES-capable and safe: a false negative on an account that could not authenticate, and the worst direction for this kind of error to point.
The fix is in the catalog, with the reasoning recorded next to it so nobody later “corrects” it back to the spec:
| |
The bits are still decoded and named, so they appear in a report as themselves, and a CarriesUnhonouredSha2Bits property surfaces the discrepancy. If a future Windows release starts honouring them, the catalog entry is the single place to change, and the trigger for that change should be a fresh measurement.
One AES request per boot nearly killed KRB005
KRB005 names the client machines that advertise no AES support and depend on a service you are about to harden. Everything else in the output could, with effort, be reconstructed from a configuration dump. That list could not.
It also very nearly did not work, for a lovely reason.
Client capability is accumulated from what each client advertises in its own 4768 events. Originally that accumulation was a union: one AES advertisement anywhere in the window marked a client AES-capable. Reasonable, and completely wrong, because every domain-joined Windows machine emits exactly one AES256 request at boot regardless of its configuration:
| |
With a 30-day default window essentially every client boots at least once, so essentially every client came back AES-capable, and the named-client list could not fire for the population it describes. The fix excludes service ticket requests whose service is krbtgt from capability evidence:
The krbtgt service disqualifies the evidence. Filtering on the forwarded flag would keep the contaminating request and drop clean ones, because normal boots also emit forwarded krbtgt requests carrying ordinary RC4 lists.
This belongs in a post about a hardening tool because it generalises. Any tool that infers client capability from observed traffic has to reckon with traffic the client never chose to send.
Running it
Prerequisites are modest. PowerShell 7.6, which went LTS in March 2026, the Kerberos Authentication Service and Kerberos Service Ticket Operations audit subcategories enabled on the domain controllers, and RSAT for the directory-reading functions. The decode and correlation core runs offline with no domain present.
Rights differ per function. An earlier revision of my own README claimed Event Log Readers was sufficient for the whole module, and that was wrong:
| Function | Needs |
|---|---|
ConvertFrom-KrbEtype | nothing, pure decode |
Get-KrbEvent | Event Log Readers on each controller |
Get-KrbPrincipalEtype | read access to the directory, any authenticated user by default |
Get-KrbDomainEtypeContext | local administrator on each controller, or -SkipRegistry |
Get-KrbEtypeRisk, Export-KrbEtypeReport | whatever the above needed |
Domain Admin is not required for anything, and no function writes to the directory. The integration suite asserts that every principal’s msDS-SupportedEncryptionTypes is byte-identical before and after a full assessment run.
Remote Event Log Management ships disabled
Get-WinEvent -ComputerName uses the legacy Event Log RPC protocol, not WinRM, and its firewall rules are off by default on a fresh Windows install. A controller that pings, resolves, replicates cleanly and answers WinRM perfectly will still fail every event log call with “The RPC server is unavailable”.
| |
Audit schema version does not follow the OS version
The rich version 2 fields, available keys and client advertisement, arrived with the November 2022 cumulative update (KB5021131, the CVE-2022-37966 change). A newer build guarantees nothing. Measured in one domain on one day: a Server 2022 controller emitted version 2, and a Server 2025 controller emitted version 1 for all 75 of its events. Check rather than assume:
| |
Where the fields are absent, the module says so on every affected finding and presents no inference as an observation.
The assessment
One line:
Level Score Principal Reqs Clients NoAES Codes
----- ----- --------- ---- ------- ----- -----
Critical 100 svc-payroll 412 12 3 KRB002 KRB001 KRB005
Critical 80 MSSQLSvc/legacydb01:1433 89 4 0 KRB002 KRB001
Critical 53 APPLIANCE-SCAN01$ 31 0 0 KRB005 KRB008
Medium 10 svc-reporting 1204 47 0 KRB013
The column layout is the real default table view. The rows are illustrative, drawn from test fixtures with invented counts, because I am not publishing output captured from a production domain.
Pulling out the machines behind the NoAES column:
And a report to attach to the change record:
The export supports self-contained HTML, per-finding CSV, and full-fidelity JSON. The reports name accounts, service principal names and client addresses, so treat them as internal documents.
Reading the output
Fifteen finding codes exist. The ones that decide whether you proceed:
| Code | Severity | Meaning |
|---|---|---|
KRB001 | Critical | Every observed ticket used an encryption type the change removes |
KRB002 | Critical | Principal holds no AES key material, hardening yields KDC_ERR_NULL_KEY |
KRB004 | Critical | USE_DES_KEY_ONLY set, overriding the encryption type attribute |
KRB005 | Critical | Named clients advertise no AES support |
KRB011 | Critical | Encryption-type failures are already occurring |
KRB014 | High | Trust does not permit AES for cross-realm authentication |
KRB009 and KRB015 both mean “nothing was found wrong”, and the gap between them is the important one. KRB009 says so on the strength of version 2 events and directory configuration. KRB015 says the fields that would have revealed a problem were never written, so nothing was found wrong and nothing would have been found wrong had it been broken.
Reporting KRB015 as though it were KRB009 is how an assessment gives false assurance about exactly the principals it understood least. The design rule underneath: missing information should produce a lower-confidence finding, and leave the severity where it is.
The same principle governs cipher survival. The default target 0x18 names etypes 17 and 18, AES-SHA1. It does not name 19 and 20, the RFC 8009 types Windows Server 2025 can issue. Testing exact membership would classify the strongest cipher Windows produces as “removed by the change” and raise Criticals against the most modern machines in the estate. Survival is therefore judged by cipher family, and anything the catalog does not recognise is treated as surviving. Unknown is not the same as incapable.
Level and score
Level is the highest severity present. It answers “does this principal block the change”. Score is a capped weighted sum. It answers “how do I order a backlog when a hundred principals all come back Critical”. Neither answers the other’s question.
Deriving one from the other is the obvious simplification. It loses the distinction: four Medium findings and one Critical finding can reach the same total, and treating them as equivalent puts a tidy-up task ahead of an outage.
Blast radius scales the score and leaves the level alone:
| |
The uplift is logarithmic. Going from one client to ten is a change in kind, while going from three hundred to three thousand describes the same estate twice.
Scope, stage, verify
Scoping an RC4 removal project
Use a window of at least 30 days. Anything shorter drops the monthly batch job out of the collection, and a monthly batch job is reliably what hardening breaks.
| |
Validating the staged rollout
Everyone treats “add AES while leaving RC4 in place” as the safe intermediate step. Model it and check:
No Critical findings should come back. If any do, that step is less safe in your domain than the usual assumption allows.
Verifying during the maintenance window
KDC_ERR_ETYPE_NOTSUPP appearing here is the change breaking something. This is the roll-back-or-proceed signal.
Assessing a domain offline
Archive the security logs per controller with wevtutil, which exports in seconds because nothing leaves the machine, then process the files anywhere. This is how I would assess a domain I do not administer:
What it does not tell you
WillBreakOnHardening means “predicted to break given observed use”. An account configured without AES that produced no traffic in the collection window comes back as KRB012, a Medium meaning unknown. Absence of traffic is not evidence of safety, and a report should not be read as though it were.
The predictions have been validated by outcome, in both directions, at small scale. Three hardening dry runs withdrew RC4 at the KDC and compared what failed against predictions recorded beforehand. The one service the module named failed and nothing else did, an AES-capable service predicted safe kept working and moved to AES256 without intervention, and both clients named by KRB005 failed at the AS exchange with KDC_ERR_ETYPE_NOTSUPP, unable to obtain a TGT at all. No run produced a false negative. What remains untested is scale, and the predicted-to-break principals that have no traffic and therefore could not be exercised.
Three dry runs earn a certain amount of confidence and no more.
Get it
The module is at Powershell/Active Directory/Kerberos/KrbEtypeInsight in the TechbyJeff repo, released under GPL v3. The README covers the full command surface, and the Troubleshooting folder has a guide per failure mode, including the ones above.
If you still have RC4 anywhere in your estate, and after July 2026 you may have less of it than you think, the collection has to happen before the change does. Somebody is going to build that list of client machines. Audit events build it in an afternoon; a help desk queue builds it over a weekend.
Sources
- Beyond RC4 for Windows authentication, Microsoft Windows Server Blog
- How to manage Kerberos KDC usage of RC4 for service account ticket issuance changes related to CVE-2026-20833, Microsoft Support
- Active Directory Hardening: Navigating Kerberos RC4 Deprecation and the July 2026 Registry Override Removal
- KB5021131, managing the Kerberos protocol changes for CVE-2022-37966
- MS-KILE 2.2.7, Supported Encryption Types Bit Flags
- Announcing PowerShell 7.6 (LTS) GA Release
