How can i programatically get the Enforce Password History group policy setting?
Research Effort
You can find the Group Policy option under:
**Computer Configuration\Windows Settings\Security Settings\Account Policies\Password Policy**
Enforce password history
This security setting determines the number of unique new passwords that have to be associated with a user account before an old password can be reused. The value must be between 0 and 24 passwords.
This policy enables administrators to enhance security by ensuring that old passwords are not reused continually.
Like all group policy options, it is stored in the registry. Unfortunately it is stored in an undocumented registry location:
HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\F
In an undocumented binary blob format.
There is a WMI class RSOP_SecuritySettingBoolean, but without any context, it's just a name hanging out there - which i have no idea how to read through COM/native.
NetValidatePasswordPolicy
Windows does provide an NetValidatePasswordPolicy
API that will allow it to validate your password for things like:
- too many bad attempts
- account lockout
- lockout automatic reset
- minimum password age
- maximum password age
- password reuse
and it will do all these things following the group policy in effect on the computer. And it all works great, except for password history.
The function requires you to pass a list of password hashes, e.g.:
$2a$14$mACnM5lzNigHMaf7O1py1OLCBgGL4tYUF0N/4rS9CwDsI7ytwL4D6
$2a$14$mACnM5lzNigHMaf7O1py1O3vlf6.BA8k8x3IoJ.Tq3IB/2e7g61Km
$2a$12$.TtQJ4Jr6isd4Hp.mVfZeuh6Gws4rOQ/vdBczhDx.19NFK0Y84Dle
$2a$12$Ro0CUfOqk6cXEKf3dyaM7OhSCvnwM9s4wIX9JeLapehKK5YdLxKcm
as well as the hash of the new candidate password, e.g.:
$2a$10$1JsBs47iuMNsV166PKV.u.56hlT5/tRe9V5t5FIdfA0axpDSQuNN
This will cause NetValidatePasswordPolicy to check if your current hash matches any of the existing hashes. Unfortunately, the entire concept of checking previous password hashes only works when you use a weak password algorithm (such as PBKDF2 that Windows uses).
Because i use a modern, expensive, salted, password hashing algorithm (BCrypt/SCrypt), a hash cannot be deterministically generated from a password alone - i can't simply check the old list. I would have to rehash the user's new password against every previously stored salt.
- not only is this an expensive operation; potentially taking 12 seconds to check all 24 stored hashes
- i don't know when to stop, because i don't know the Password History group policy value (e.g. i would be checking all 24 stored hashes, when i only need to check zero).
I have considered calling NetValidatePasswordPolicy
with a dummy list of 24 stored password hashes, e.g.:
a
b
c
d
- ...
v
w
x
The API will then tell me that it password has did not match any in the n
history. But the API is also designed to return to you what you have to keep. It might then return:
$2a$10$1JsBs47iuMNsV166PKV.u.56hlT5/tRe9V5t5FIdfA0axpDSQuNN
a
b
c
And from that i might be able to infer that the password history length is four.
But i've not gotten there yet.
I'm three days into this, and losing patience.
- why did Microsoft obfuscate the group policy?
- why did Microsoft not allow people to read it?
- why is it undocumented?
- how do i get it all the same?
Turns out that using RSOP_SecuritySettingBoolean (resultant set of policy) is a bad idea; as it only works on domain-joined machines.
Querying Active Directory likewise only works for computers joined to a domain; and on works for users who have the ability to query the domain controller (which is something that can be un-granted).
The real solution is to use
NetUserModalsGet
, which can return you structures like:and
The sample code would be:
The documentation says the max value is
DEF_MAX_PWHIST
, which is defined in Lmaccess.h as:But that's not true. The policy allows a user to set a maximum of 24.