Skip to content
Advertisement

How can I mimick OpenLDAP’s slappasswd using NodeJS?

My goal is to use NodeJS to create LDAP password hashes that are similar to what comes out of the slappasswd command-line tool.

Here’s how LDAP passwords can be produced with command-line:

slappasswd -h '{SSHA}' -s 'P@ssw0rd'
{SSHA}1RHPt8m4AWLjK8Px1MT6FEBJOBJpdzqT

The result is a base64 encoded, salted SHA1 password.

Here’s what I tried initially to recreate it:

#!/usr/bin/env node

import sha1 from 'crypto-js/sha1.js';

let password = 'P@ssW0rd';
let salt = btoa(0xA5);  // Not random, just a proof of concept temporary value.
let hash = sha1(password + salt);
console.log('{SSHA}' + btoa(hash));

But, I got a much longer string than what the slappasswd command produced and I’m not sure why.

{SSHA}NDVkN2JjODQ2ZDk3Yjc2YmViNTU3MzUzYjBiNzExN2ZmYzMxYWY5ZA==

I did some digging around on the net and found this on an LDAP password generator web page:

<script src="lib/cryptojs/core.js"></script>
<script src="lib/cryptojs/sha1.js"></script>
<script src="lib/cryptojs/enc-base64.js"></script>

<script>
    function slappasswd(password) {
      var salt = CryptoJS.lib.WordArray.random(128/8).toString().substr(0,4);
      var hash = CryptoJS.SHA1(password + salt);
      var base = CryptoJS.enc.Latin1.parse(hash.toString(CryptoJS.enc.Latin1) + salt).toString(CryptoJS.enc.Base64);
      return '{SSHA}' + base;
    }
...

The web page produces a string that is the same length as what comes out of slappasswd, so I assume it’s an accurate recreation of the slappasswd logic.

Using this information, my next attempt looks like this:

#!/usr/bin/env node

import * as CryptoJS from 'crypto-js';

let password = 'P@ssW0rd';
let salt = CryptoJS.lib.WordArray.random(128/8).toString().substr(0,4);
let hash = sha1(password + salt);
let base = CryptoJS.enc.Latin1.parse(hash.toString(CryptoJS.enc.Latin1) + salt).toString(CryptoJS.enc.Base64);
console.log('{SSHA}' + base);

However, I get errors.

First, there is TypeError: Cannot read properties of undefined (reading 'WordArray')

If I replace let salt = with let salt = btoa(0xA5) from my first attempt code, I then get the error: ReferenceError: sha1 is not defined

My feeling is that I’ve got the import wrong somehow.

I’m trying to do the ES6 module equivalent of var CryptoJS = require("crypto-js");, but failing somewhere.

So my question is two-fold:

  1. Can my first attempt be made to produce a string length similar to what slappassword outputs?
  2. If not, what can I do to fix the errors I’m getting in the second attempt?

Ideally, I’d like to understand where I went wrong in my first attempt rather than simply copying and pasting someone else’s code (second attempt) without fully grasping it.

Advertisement

Answer

Here is alternative of python/php implementations for NodeJS.

Import Crypto module

const crypto = require('crypto');

It will be used to create LDAP password hashes (SSHA)

function generate_hash(passwd, salt) {
  if (!salt) {
     const buf = crypto.randomBytes(4);
     salt = buf.toString('base64');
  }
  let ctx = crypto.createHash('sha1');
  ctx.update(passwd, 'utf-8');
  ctx.update(salt, 'binary');
  let digest = ctx.digest('binary');
  return '{SSHA}' + Buffer.from(digest + salt, 'binary').toString('base64');
} 

It will be used to verify hash

function verify_hash(passwd, hash) {
  let bhash = Buffer.from(hash.substr(6), 'base64');
  let salt = bhash.toString('binary', 20);
  let newssha = generate_hash(passwd, salt);
  return hash === newssha;
}

Test it together

 const hash = generate_hash("qwe1234");
 let test = verify_hash("qwe1234", hash);
 console.log(test);  //Output: true
 let test = verify_hash("XXXX", hash);
 console.log(test);  //Output: false

Hope it help you. Please let me know.

Try Now

User contributions licensed under: CC BY-SA
6 People found this is helpful
Advertisement