Strengthen your password

General Add comments
by:

Standard behavior of SNC instances is that no password checking is done.
Security departments however demand passwords that are ‘strong’.

code2
What a strong password is depends on the demands of the company but generally the next demands covers most needs:
1. Password must have a minimal length
2. Password must contain certain type of characters ( a capital, number, etc).
3. New password must differ from the current password.
4. User is forced to change his password after a certain period.
5. Password may not be used the last so many times.

Changing your password is done with ‘Installation Exit’ ValidatePassword .
If you like to go a level further you can enable ‘Installation Exit’ ValidatePasswordStronger to override.

Now the new password can be checked on length and with a few RegEx checks can be done like:
-Capital is password
-Number in password
Etc.

code1
The script in ValidatePasswordStronger can easily be edit to meet your own needs.
(see http://wiki.servicenow.com/index.php?title=Strengthening_Password_Validation_Rules)

One can edit this installation exit and add some checks off your own.
i.e. If you do not want the username being part of the password add the next to ValidatePasswordStronger:

[cc lang=”javascript”]
var user_name = gs.getUserName();

var user_password = request.getParameter(“user_password”);

if (user_password.indexOf(user_name) >= 0) {
gs.addErrorMessage(“USER ID IS PART OF PASSWORD: ” + rules);
return false;
}
[/cc]

There are 3 other items to be addressed.
1. User must update his password after a certain period in time.
2. How can we make sure the user has update his password with the same password again.
3. How can we check that the password has not been used before.
To have the users update the password within a certain time period the next steps need to be done:
1. Modifying the ValidatePassword Installation Exit.
2. Register an event for password changes.
3. Create a script to respond to password changes.
4. Create a daily script to check when the last password change has been effected.
In the wiki there is an article about this subject:
http://wiki.servicenow.com/index.php?title=Force_Password_Reset_90_Days_After_Last_Change

For checking if a password is used in the recent past a table with a history of used passwords must be setup. SNC however uses keyservers outside the instance to check and created keys together with encrypted passwords. Every time a password is encrypted a new key is generated for that encryption which means that when the user changes his password into the same one he already used before we still get a differed encrypted code.
Since we do not want to save the used passwords as plain text I have created a script include that will hash the password with the MD5 methode and that is saved into a table. We cannot decrypted the password from this hash, but when a used password is hashed again it will come to the same hash and we can compare the hashes.

code3
So I created the PwdHash script include ( it is quite long):
(see http://pajhome.org.uk/crypt/md5/md5.html)

[cc lang=”javascript”]

var PwdHash = Class.create();

PwdHash.prototype = {
initialize : function() {
},

hashNow : function(s) {

/*
* Configurable variables. You may need to tweak these to be compatible with
* the server-side, but the defaults work in most cases.
background test:
var user_password=’Geheim12′;
var pwdHashed = new PwdHash();
var pdh=pwdHashed.hashNow(user_password);
gs.print(pdh);
var user_password=’Geheim23′;
var pwdHashed = new PwdHash();
var pdh=pwdHashed.hashNow(user_password);
gs.print(pdh);
*/
var hexcase = 0; /* hex output format. 0 – lowercase; 1 – uppercase */
var b64pad = “”; /* base-64 pad character. “=” for strict RFC compliance */

/*
* These are the functions you’ll usually want to call
* They take string arguments and return either hex or base-64 encoded strings
*/
function hex_md5(s) { return rstr2hex(rstr_md5(str2rstr_utf8(s))); }
function b64_md5(s) { return rstr2b64(rstr_md5(str2rstr_utf8(s))); }
function any_md5(s, e) { return rstr2any(rstr_md5(str2rstr_utf8(s)), e); }
function hex_hmac_md5(k, d)
{ return rstr2hex(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
function b64_hmac_md5(k, d)
{ return rstr2b64(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
function any_hmac_md5(k, d, e)
{ return rstr2any(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)), e); }
/*
* Calculate the MD5 of a raw string
*/
function rstr_md5(s) {
return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
}

/*
* Calculate the HMAC-MD5, of a key and some data (raw strings)
*/
function rstr_hmac_md5(key, data) {
var bkey = rstr2binl(key);
if(bkey.length > 16) bkey = binl_md5(bkey, key.length * 8);
var ipad = Array(16), opad = Array(16);
for(var i = 0; i < 16; i++) {
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5C5C5C5C;
}

var hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
}

/*
* Convert a raw string to a hex string
*/
function rstr2hex(input) {
try { hexcase } catch(e) { hexcase=0; }
var hex_tab = hexcase ? “0123456789ABCDEF” : “0123456789abcdef”;
var output = “”;
var x;
for(var i = 0; i < input.length; i++) {
x = input.charCodeAt(i);
output += hex_tab.charAt((x >>> 4) & 0x0F) + hex_tab.charAt( x & 0x0F);
}
return output;
}

/*
* Convert a raw string to a base-64 string
*/
function rstr2b64(input) {
try { b64pad } catch(e) { b64pad=”; }
var tab = “ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/”;
var output = “”;
var len = input.length;
for(var i = 0; i < len; i += 3) {
var triplet = (input.charCodeAt(i) << 16) | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0) | (i + 2 < len ? input.charCodeAt(i+2) : 0);
for(var j = 0; j < 4; j++) {
if(i * 8 + j * 6 > input.length * 8) output += b64pad;
else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
}
}
return output;
}

/*
* Convert a raw string to an arbitrary string encoding
*/
function rstr2any(input, encoding) {
var divisor = encoding.length;
var i, j, q, x, quotient;

/* Convert to an array of 16-bit big-endian values, forming the dividend */
var dividend = Array(Math.ceil(input.length / 2));
for(i = 0; i < dividend.length; i++) {
dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
}

/*
* Repeatedly perform a long division. The binary array forms the dividend,
* the length of the encoding is the divisor. Once computed, the quotient
* forms the dividend for the next step. All remainders are stored for later
* use.
*/
var full_length = Math.ceil(input.length * 8 / (Math.log(encoding.length) / Math.log(2)));
var remainders = Array(full_length);
for(j = 0; j < full_length; j++) {
quotient = Array();
x = 0;
for(i = 0; i < dividend.length; i++) {
x = (x << 16) + dividend[i];
q = Math.floor(x / divisor);
x -= q * divisor;
if(quotient.length > 0 || q > 0) quotient[quotient.length] = q;
}
remainders[j] = x;
dividend = quotient;
}

/* Convert the remainders to the output string */
var output = “”;
for(i = remainders.length – 1; i >= 0; i–)
output += encoding.charAt(remainders[i]);

return output;
}

/*
* Encode a string as utf-8.
* For efficiency, this assumes the input is valid utf-16.
*/
function str2rstr_utf8(input) {
var output = “”;
var i = -1;
var x, y;

while(++i < input.length) {
/* Decode utf-16 surrogate pairs */
x = input.charCodeAt(i);
y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF) {
x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
i++;
}

/* Encode output as utf-8 */
if(x <= 0x7F) output += String.fromCharCode(x);
else if(x <= 0x7FF) output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),0x80 | ( x & 0x3F));
else if(x <= 0xFFFF) output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),0x80 | ((x >>> 6 ) & 0x3F),0x80 | ( x & 0x3F));
else if(x <= 0x1FFFFF) output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),0x80 | ((x >>> 12) & 0x3F),0x80 | ((x >>> 6 ) & 0x3F),0x80 | ( x & 0x3F));
// gs.log(‘PwdHash: ‘+x);
}
return output;
}

/*
* Encode a string as utf-16
*/
function str2rstr_utf16le(input) {
var output = “”;
for(var i = 0; i < input.length; i++)
output += String.fromCharCode( input.charCodeAt(i) & 0xFF,
(input.charCodeAt(i) >>> 8) & 0xFF);
return output;
}

function str2rstr_utf16be(input) {
var output = “”;
for(var i = 0; i < input.length; i++)
output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF, input.charCodeAt(i) & 0xFF);
return output;
}

/*
* Convert a raw string to an array of little-endian words
* Characters >255 have their high-byte silently ignored.
*/
function rstr2binl(input) {
var output = Array(input.length >> 2);
for(var i = 0; i < output.length; i++)
output[i] = 0;
for(var i = 0; i < input.length * 8; i += 8)
output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32);
return output;
}

/*
* Convert an array of little-endian words to a string
*/
function binl2rstr(input) {
var output = “”;
for(var i = 0; i < input.length * 32; i += 8)
output += String.fromCharCode((input[i>>5] >>> (i % 32)) & 0xFF);
return output;
}

/*
* Calculate the MD5 of an array of little-endian words, and a bit length.
*/
function binl_md5(x, len) {
/* append padding */
x[len >> 5] |= 0x80 << ((len) % 32);
x[(((len + 64) >>> 9) << 4) + 14] = len;

var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;

for(var i = 0; i < x.length; i += 16) {
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;

a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819);
b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426);
c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416);
d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682);
d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329);

a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
c = md5_gg(c, d, a, b, x[i+11], 14, 643717713);
b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083);
c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438);
d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501);
a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473);
b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);

a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562);
b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353);
c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174);
d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189);
a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
c = md5_hh(c, d, a, b, x[i+15], 16, 530742520);
b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);

a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415);
c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571);
d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359);
d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649);
a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259);
b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);

a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
}
return Array(a, b, c, d);
}

/*
* These functions implement the four basic operations the algorithm uses.
*/
function md5_cmn(q, a, b, x, s, t) {
return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t) {
return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t) {
return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t) {
return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t) {
return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}

/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function safe_add(x, y) {
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}

/*
* Bitwise rotate a 32-bit number to the left.
*/
function bit_rol(num, cnt) {
return (num << cnt) | (num >>> (32 – cnt));
}

return hex_md5(s);
},

/*
* Perform a simple self-test to see if the VM is working
*/
md5_vm_test : function () {
return hex_md5(“abc”).toLowerCase() == “900150983cd24fb0d6963f7d28e17f72″;
},

}

[/cc]

which returns the hashed code for the new password.
The hashed code is then check against the table that holds per user a amount of passwords. The table is called u_password_history and has the reference field to sys_user and the text field that holds the password.
For checking the password in the history another script include is created:

[cc lang=”javascript”]

var PwdHistCheck = Class.create();

PwdHistCheck.prototype = {
initialize : function(userid,nwpasswd) {
this.userid = userid;
this.nwpasswd = nwpasswd;
},

isUsed : function() {

var pwdchk = new GlideRecord(‘u_password_history’);
pwdchk.addQuery(‘u_userid’,this.userid);
pwdchk.query();
var ans=false;
gs.log(‘PWD Hist check!!’+this.nwpasswd);
while (pwdchk.next()) {
gs.log(‘PWD Hist while!! ‘+pwdchk.u_password_used);
if (pwdchk.u_password_used == this.nwpasswd) {
ans=true;
}
}
return ans;
},
}

[/cc]

This will return a true or false whether the password was already in the table.
To make this all function the next is added to the ValidatePasswordStronger:

[cc lang=”javascript”]

var pwdHashObj = new PwdHash();
var pwdHashed = pwdHashObj.hashNow(user_password+”);
var pwdHist = new PwdHistCheck(gs.getUserID(),pwdHashed);
var pwdHistOk=pwdHist.isUsed();

if (pwdHistOk) {
gs.addErrorMessage(“PASSWORD IS RECENTLY USED: ” + rules);
pwdHashed=”; pwdHist=”; pwdHistOk=”;
return false;
}

if (typeof GlideEventManager != ‘undefined’) {
var EventManager = GlideEventManager;
} else {
var EventManager = Packages.com.glide.policy.EventManager;
}

EventManager.queue(“sys_user.password.changed”, “”, gs.getUserID(), pwdHashed);
return true; // password is OK
}
}

[/cc]

The script action called by the event: sys_user.password.changed (as described in above mention articles) is slightly changed to update the password history record once the new password is validated:

[cc lang=”javascript”]

//
// When the user password changes then set the u_password_last_reset field
// to now so we know when to force another update
//
var gr = new GlideRecord(“sys_user”);
if (gr.get(event.parm1.toString())) {
// Do something based on the Password Changing
gs.log(“The user ” +gr.user_name+” password is changed.”);
gr.u_password_last_reset = gs.now();
gr.update();
}
var gp = new GlideRecord(“u_password_history”);
gp.u_userid=gr.sys_id;
gp.u_password_used=event.parm2.toString();
gp.insert();

[/cc]

What more is needed.
Another demand can be that the user may not use an old password he/she used the last so many times. This can be accomplice by adding a system property with the amount of password history entries kept in the database and have a job run every day to check and cleanup these entries.

Last item:
Now what if the user types the same password again as a new one. If we do not have this password history mechanism in place then that looks like an impossible task to check. And at the moment the password history is set up there is no history yet.
The keys being used to decrypt the password are unreachable and kept outside the instance.
But we can still check if the user retyped the current password at the moment of changing with a simple task.
The trick here is to validate the user with his new password before changing or updating his password. If he validates he must have reused his existing password:
In ValidatePasswordStronger:

[cc lang=”javascript”]

var user = Packages.com.glide.sys.User;
var authed = user.authenticate(user_name, user_password);
if (authed) {
gs.addErrorMessage(“NEW PASSWORD IS THE SAME AS OLD PASSWORD: ” + rules);
return false;
}

[/cc]

That’s it! A lot of code and a great result!

Still questions? You can mail me at: [cryptex]paul.de.haas@LogicalisSMC.com[/cryptex].
Good luck!

Leave a Reply