Changing Active Directory passwords via LDAP using PHP

The background

A user's password is stored in the unicodePwd attribute of the user object in the Active Directory. This attribute cannot be read and can only be set under certain LDAP operations:

  1. An atomic modify request containing an delete operation with the current password and an add operation with the new password
  2. If bound with sufficient privileges an LDAP replace can be used to set a new password without supplying the current password.

Additionally, the following conditions must also be met:

  • The operations must be performed using LDAPS
  • The LDAPS connection must use at least 127-bit encryption

The problems

Firstly, you need to ensure that the Active Directory domain controller you intend to use to perform the change supports LDAPS.

The second problem is that the LDAP support in the PHP for LDAP_MODIFY only allows one attribute to modified at a time, which as mentioned previously is not suitable for changing the unicodePwd entry.

The Solution

The way around this is to generate an LDIF entry an pass this to the OpenLDAP ldapmodify command.

dn: <User DN>
changetype: modify
delete: unicodePwd
unicodePwd:: <Base64 encoded old password>
add: unicodePwd
unicodePwd:: <Base64 encoded new password>

Use the above LDIF file with the following ldapmodify command:

/usr/bin/ldapmodify -f <LDIF file> -H ldaps://ad.mydomain -D '<User DN>' -x -w <Users old password>

An example

function EncodePwd($pw) {

  $newpw = '';
  $pw = "\"" . $pw . "\"";
  $len = strlen($pw);
  for ($i = 0; $i < $len; $i++)
      $newpw .= "{$pw{$i}}\000";
  $newpw = base64_encode($newpw);
  return $newpw;

  $newpw64 = EncodePwd($newpw);
  $oldpw64 = EncodePwd($oldpw);

dn: $userdn
changetype: modify
delete: unicodePwd
unicodePwd:: $oldpw64
add: unicodePwd
unicodePwd:: $newpw64

  $cmd = sprintf("/usr/bin/ldapmodify -H %s -D '%s' -x -w %s", $adserver, $userdn, $oldpw);

  if (($fh = popen($cmd, 'w')) === false )
     die("Open failed: ${php_errormsg}\n");

  fwrite($fh, "$ldif\n");