In this post I will demonstrate how to verify if a given password meet the current Windows password policy.
We will use the NetValidatePasswordPolicy API for that.
Instead of using C# pinvoke approach, we will call the API through Managed C++ then call the managed C++ DLL with C#. This approach will be much more cleaner and readable.
First, we create our Managed C++ DLL which contains our validation method :
using namespace System;
namespace pwdval
{
public ref class srPasswordValidator
{
public :int ValidatePassword(System::String ^paramPassword)
{
pin_ptr<const wchar_t> wchDomain = PtrToStringChars(paramPassword);
size_t convertedCharsPassword = 0;
size_t sizeInBytesPassword = ((paramPassword->Length + 1) * 2);
errno_t errPassword = 0;
char *chPassword = (char *)malloc(sizeInBytesPassword);
errPassword = wcstombs_s(&convertedCharsPassword,
chPassword, sizeInBytesPassword,
wchDomain, sizeInBytesPassword);
if (errPassword != 0)
throw gcnew Exception("Password could not be converted");
// first, find out the required buffer size, in wide characters
int nPasswordSize = MultiByteToWideChar(CP_ACP, 0, chPassword, -1, NULL, 0);
LPWSTR wPassword = new WCHAR[nPasswordSize];
// call again to make the conversion
MultiByteToWideChar(CP_ACP, 0, chPassword, -1, wPassword, nPasswordSize);
NET_API_STATUS stat;
NET_VALIDATE_PASSWORD_CHANGE_INPUT_ARG InputArg = {0};
NET_VALIDATE_OUTPUT_ARG* pOutputArg = NULL;
wchar_t* wzServer = 0;
//wchar_t wzPwd = chPassword;
InputArg.ClearPassword = wPassword;
InputArg.PasswordMatch = TRUE;
stat = NetValidatePasswordPolicy(wzServer, NULL, NetValidatePasswordChange, &InputArg, (void**)&pOutputArg);
NET_API_STATUS intStatus = pOutputArg->ValidationStatus;
NetValidatePasswordPolicyFree((void**)&pOutputArg);
delete []wPassword;
return intStatus;
}
};
}
This method returns an integer that we will convert to an Enum
enum enmResult
{
// <summary> 2234 - This operation is not allowed on this special group. </summary>
SpeGroupOp = 2234,
// <summary> 2235 - This user is not cached in user accounts database session cache. </summary>
NotInCache = 2235,
// <summary> 2236 - The user already belongs to this group. </summary>
UserInGroup = 2236,
// <summary> 2237 - The user does not belong to this group. </summary>
UserNotInGroup = 2237,
// <summary> 2238 - This user account is undefined. </summary>
AccountUndefined = 2238,
// <summary> 2239 - This user account has expired. </summary>
AccountExpired = 2239,
// <summary> 2240 - The user is not allowed to log on from this workstation. </summary>
InvalidWorkstation = 2240,
// <summary> 2241 - The user is not allowed to log on at this time. </summary>
InvalidLogonHours = 2241,
// <summary> 2242 - The password of this user has expired. </summary>
PasswordExpired = 2242,
// <summary> 2243 - The password of this user cannot change. </summary>
PasswordCantChange = 2243,
// <summary> 2244 - This password cannot be used now. </summary>
PasswordHistConflict = 2244,
// <summary> 2245 - The password does not meet the password policy requirements. Check the minimum password length, password complexity and password history requirements. </summary>
PasswordTooShort = 2245
}
You can find the complete enum definition in the sources downloadable at the end of the post.
Here is now hows we use the DLL to validate a password :
private object pwValidator;
public PwdVal()
{
foreach (string curModule in Directory.GetFiles(ModuleSearchPath, "srPasswordValidator.dll"))
{
try
{
Assembly objAssembly = Assembly.LoadFile(curModule);
System.Type objType = objAssembly.GetType("pwdval.srPasswordValidator");
if ((objType != null))
{
pwValidator = Activator.CreateInstance(objType);
break;
}
}
catch (Exception ex)
{
pwValidator = null;
}
}
}
private string ModuleSearchPath
{
get { return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); }
}
public bool ValidatePassword(string paramPassword, ref string paramReason)
{
try
{
if (pwValidator == null)
{
paramReason = "Could not validate the password - pwValidator is NULL";
return false;
}
object obj = pwValidator.GetType().GetMethod("ValidatePassword").Invoke(pwValidator, new object[] { paramPassword });
if (obj == null)
{
paramReason = "Could not validate the password - obj is NULL";
return false;
}
int val = Int32.Parse(obj.ToString());
enmResult intResult = (enmResult)val;
paramReason = intResult.ToString();
return intResult == enmResult.Success;
}
catch (Exception ex)
{
paramReason = ex.Message;
return false;
}
}
You can download sources and sample here : http://dl.free.fr/q04LOHSpM
Hi,
The link is no longer available. Possible to post the file again?
Thanks,
Paul
The link is updated. Cheers
Hello.
I’m trying to load your sample in VS2010 and am getting the error message “”An attempt was made to load an assembly from a network location which would have caused the assembly to be sandboxed in previous versions of the .NET Framework. This release of the .NET Framework does not enable CAS policy by default, so this load may be dangerous. If this load is not intended to sandbox the assembly, please enable the loadFromRemoteSources switch. See http://go.microsoft.com/fwlink/?LinkId=155569 for more information.” when trying to execute the “Assembly objAssembly = Assembly.LoadFile(curModule);” line.
I went to the website and tried the fix recommended but after a rebuild got another message, “”Could not load file or assembly ‘srPasswordValidator.dll’ or one of its dependencies. is not a valid Win32 application. (Exception from HRESULT: 0x800700C1)”
I’m new to this … am I missing something obvious? Thanks. – Dave
Hello.
Are you on a x64 system ?
If this is the case, you have to compile PasswordTest in x86 and the C++/CLI wrapper (srPasswordValidator) in Win32
Let me know if you still have issues.
Cheers
Thank you for your response. Yes, I am using an x64 system.
I compiled PasswordTest setting the platform target to “x86″ and the C++ code (srPasswordValidator) to “Win32″ (in the config manager), rebuilt both, and tried again.
The console app asked for a password but when I tried it I got the message “Could not validate the password – pwValidator is NULL”
I rebuilt srPasswordValidator.dll and copied it over into the \PasswordTest\bin\Debug (where the “Assembly.LoadFile” was looking for it) but got the same response.
Thanks for any help you can give me — I’d really like to get this working and see how it works. – Dave
If I’m not mistaking the DLL needs to be put in PasswordTest\bin\x86\Debug
Thanks again for your assistance — I copied your sample code to a 32-bit machine a co-worker lent me and it worked fine; I then tried building it again on my 64-bit machine using your suggestions and I got it to work.
The steps to get it working in Visual Studio 2010 on a 64-bit machine (for other C# application developers who never use C++) were very straightforward:
- Unzip the two directories (“srPasswordValidator” and “PasswordTest”) into a TEMP directory;
- Open up first project, “PasswordTest.” Let VS2010 do the conversion from VS2008 to VS2010.
- Right-click the “PasswordTest” project, select “Properties,” then “Build,” then set the “Platform Target” to “x86″
- Build the project
- Open up the second project, “srPasswordValidator.” Let VS2010 do the conversion from VS2008 to VS2010.
- Right-click the “srPasswordValidator” project, select “Properties”, and in the “Platform” dropdownlist select “Win32.”
- Build the project.
- Go down into the directory where the “PasswordTest” project is stored, then go down into its “bin\Debug” subdirectory.
- Run the “PasswordTest.exe” program and it works fine.
My only question when playing with it is that it doesn’t seem to catch passwords which violate the history rule (no repeating passwords for password changes). I put in my own current password and it accepted it without a problem. The code is running under my security context, right?
Thanks once more. – Dave