Hack #37. Keep Track of Secure Site Passwords

Generate random passwords for every site based on a master password.

Everyone has too many passwords to remember. Every site—from Expedia to Amazon.com to Gmail to individual blogs and mailing lists—has its own system. Some services—such as Microsoft's Passport, Google's Blogger, and SixApart's TypeKey—have tried to stem the tide by providing a cross-site login system. But even these are proliferating at an alarming rate. Most people eventually just give up and use one password everywhere. Some people use a "secure" password for sensitive sites like online banking and e-commerce sites, and an "insecure" password for mailing lists and blogs. All of these systems are doomed to failure.

What we really need is a personalized system of generating passwords locally and retrieving them on demand. Mac OS X has the Keychain application, but it works only on Mac OS X. Firefox has its Password Manager, but it doesn't store the passwords securely, and it works only on sites that allow the browser to remember passwords in the first place. (But see "Allow Password Remembering" [Hack #32] for a way around that.)

This hack defines a local master password that you can enter to generate a random password for each web site you visit. It never stores the master password on disk; you simply enter it whenever you need to log into a web site. So even if someone steals your laptop, she won't be able to access any of your stored passwords, because you haven't stored them anywhere.

The Code

This user script runs on all pages. The first half of the code is taken up with the MD5 hash algorithm, which the script uses to generate each password. The long mpwd_getHostname function is devoted to determining the portion of the current domain, minus the country-specific top-level domain name. This means that you can reuse the same site-specific password on http://www.amazon.com and http://www.amazon.co.uk.

The rest of the script checks for password fields on the current page and adds a Master password field to each form to allow you to enter your master password. If you enter it correctly, the script fills the original password field with your random site-specific password, or generates a new one and stores it locally.

Save the following user script as pwdcomposer.user.js:

		   // ==UserScript==
		   // @name		   Password Composer
		   // @namespace   http://joe.lapoutre.com/BoT/Javascript/
		   // @description  Generate site specific passwords based on a master password
		   // @include	   *
		   // @version	   1.08
		   // ==/UserScript==

		   // based on code by Johannes le Poutré and others
		   // and included here with their gracious permission

		   var clearText = false; // show generated passwds in cleartext
		   var topDomain = false; // use top domain instead of full host
		   function errLog(msg) {
			   if (typeof(GM_log == "function")) {
				   GM_log(msg);
			   } else {
				   window.status = msg;
			   }
		   }
		   
		   function hex_md5(s) {
			   return binl2hex(core_md5(str2binl(s), s.length * 8));
		   }
		   function core_md5(x, len) {
			   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);
			} 
			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);
		}
		function safe_add(x, y) {
			var lsw = (x & 0xFFFF) + (y & 0xFFFF);
			var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
			return (msw << 16) | (lsw & 0xFFFF);
		}
		function bit_rol(num, cnt) {
			return (num << cnt) | (num >>>(32 - cnt));
		}
		function str2binl(str) {
			var bin = Array();
			var mask = (1 << 8) - 1;
			for (var i = 0; i < str.length * 8; i += 8) {
				bin[i >> 5] |= (str.charCodeAt(i / 8) & mask) << (i % 32);
			}
			return bin;
		}
		function binl2hex(binarray) {
			var hex_tab = '0123456789abcdef';
			var str = '';
			for (var i = 0; i < binarray.length * 4; i++) {
				str+=hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8 + 4)) & 0xF)+
				hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8)) & 0xF);
			}
			return str;
		}

		function mpwd_getHostname() {
			var re = new RegExp('https*://([^/]+)');
			var url = document.location.href.toLowerCase();
			var host = url.match(re)[1];
			// look at minimum domain instead of host
			// see http://labs.zarate.org/passwd/
			if (topDomain) {
				host = host.split('.');
				if (host[2] != null) {
				s = host[host.length-2] + '.' + host[host.length-1];
				domains='ab.ca|ac.ac|ac.at|ac.be|ac.cn|ac.il|ac.in|ac.jp|'+
		'ac.kr|ac.nz|ac.th|ac.uk|ac.za|adm.br|adv.br|agro.pl|ah.cn|aid.pl|alt'+
		'.za|am.br|arq.br|art.br|arts.ro|asn.au|asso.fr|asso.mc|atm.pl|auto.p'+
		'l|bbs.tr|bc.ca|bio.br|biz.pl|bj.cn|br.com|cn.com|cng.br|cnt.br|co.ac'+
		'|co.at|co.il|co.in|co.jp|co.kr|co.nz|co.th|co.uk|co.za|com.au|com.br'+
		'|com.cn|com.ec|com.fr|com.hk|com.mm|com.mx|com.pl|com.ro|com.ru|com.'+
		'sg|com.tr|com.tw|cq.cn|cri.nz|de.com|ecn.br|edu.au|edu.cn|edu.hk|edu'+
		'.mm|edu.mx|edu.pl|edu.tr|edu.za|eng.br|ernet.in|esp.br|etc.br|eti.br'+
		'|eu.com|eu.lv|fin.ec|firm.ro|fm.br|fot.br|fst.br|g12.br|gb.com|gb.ne'+
		't|gd.cn|gen.nz|gmina.pl|go.jp|go.kr|go.th|gob.mx|gov.br|gov.cn|gov.e'+
		'c|gov.il|gov.in|gov.mm|gov.mx|gov.sg|gov.tr|gov.za|govt.nz|gs.cn|gsm'+
		'.pl|gv.ac|gv.at|gx.cn|gz.cn|hb.cn|he.cn|hi.cn|hk.cn|hl.cn|hn.cn|hu.c'+
		'om|idv.tw|ind.br|inf.br|info.pl|info.ro|iwi.nz|jl.cn|jor.br|jpn.com|'+
		'js.cn|k12.il|k12.tr|lel.br|ln.cn|ltd.uk|mail.pl|maori.nz|mb.ca|me.uk'+
		'|med.br|med.ec|media.pl|mi.th|miasta.pl|mil.br|mil.ec|mil.nz|mil.pl|'+
		'mil.tr|mil.za|mo.cn|muni.il|nb.ca|ne.jp|ne.kr|net.au|net.br|net.cn|n'+
		'et.ec|net.hk|net.il|net.in|net.mm|net.mx|net.nz|net.pl|net.ru|net.sg'+
		'|net.th|net.tr|net.tw|net.za|nf.ca|ngo.za|nm.cn|nm.kr|no.com|nom.br|'+
		'nom.pl|nom.ro|nom.za|ns.ca|nt.ca|nt.ro|ntr.br|nx.cn|odo.br|on.ca|or.'+
		'ac|or.at|or.jp|or.kr|or.th|org.au|org.br|org.cn|org.ec|org.hk|org.il'+
		'|org.mm|org.mx|org.nz|org.pl|org.ro|org.ru|org.sg|org.tr|org.tw|org.'+
		'uk|org.za|pc.pl|pe.ca|plc.uk|ppg.br|presse.fr|priv.pl|pro.br|psc.br|'+
		'psi.br|qc.ca|qc.com|qh.cn|re.kr|realestate.pl|rec.br|rec.ro|rel.pl|r'+
		'es.in|ru.com|sa.com|sc.cn|school.nz|school.za|se.com|se.net|sh.cn|sh'+
		'op.pl|sk.ca|sklep.pl|slg.br|sn.cn|sos.pl|store.ro|targi.pl|tj.cn|tm.'+
		'fr|tm.mc|tm.pl|tm.ro|tm.za|tmp.br|tourism.pl|travel.pl|tur.br|turyst'+
		'yka.pl|tv.br|tw.cn|uk.co|uk.com|uk.net|us.com|uy.com|vet.br|web.za|w'+	
		'eb.com|www.ro|xj.cn|xz.cn|yk.ca|yn.cn|za.com';
				domains=domains.split('|');
				for(var i=0; i<domains.length; i++) {
				if (s==domains[i]) {
				s=host[host.length-3]+'.'+s;
				break;
				}
				}
				} else {
				s = host.join('.');
				}
				return s;
			} else {
				// no manipulation (full host name)
				return host;
			}
		}
		
		// generate the password and populate original form
		function mpwd_doIt() {
			if (!mpwd_check_password()) { return; }
			var master = document.getElementById('masterpwd').value;
			var domain = document.getElementById('mpwddomain').value.toLowerCase();
			// remove panel before messing with passwd fields
			mpwd_remove();
			if (master != '' && master != null) {
				var i=0, j=0, p=hex_md5(master+':'+domain).substr(0,8);
				var inputs = document.getElementsByTagName('input');
				for(i=0;i<inputs.length;i++) {
				var inp = inputs[i];
				if(inp.getAttribute('type') == 'password') {
				inp.value=p;
				if (clearText) {
				inp.type = 'text';
				var cl = inp.getAttribute("class") || "";
				// hack to mark passwd fields by setting class name
				// intentde to find them on a second pass, if
				// type is modified to text
				if (cl.indexOf("mpwdpasswd") == -1) {
				inp.setAttribute("class", cl + " mpwdpasswd");
				}
				}
				// inp.focus();
				} else if(inp.getAttribute('type') == 'text') {
				var nm = inp.getAttribute('name').toLowerCase();
				var cl = inp.getAttribute("class") || "";
				// field named something like passwd or class mpwdpasswd
				if (nm.indexOf('password')!=-1 ||
				nm.indexOf('passwd')!=-1 ||
				cl.indexOf("mpwdpasswd") != -1) {
				inp.value=p;
				if (! clearText) inp.type = 'password';
				// inp.focus();
				}
				}
			}
			// give focus to first password field
			getPwdFld().focus();
		}
	};

	// check for multiple passwd fields per form (e.g. 'verify passwd')
	function hasMultiplePwdFields() {
		// find any form that has 2+ password fields as children
		// note literal '>' char in xpath expression!
		var xpres = document.evaluate(
			"count(//form[count(//input[@type='password']) > 1])",
			document, null, XPathResult.ANY_TYPE, null);
		return(xpres.numberValue > 0);
	}
	// find first password field
	function getPwdFld() {
		var L = document.getElementsByTagName('input');
		for (var i = 0; i < L.length; i++) {
			var nm, tp, cl;
			try { nm = L[i].getAttribute("name") || ""; } catch(e) { };
			try { tp = L[i].getAttribute("type") || ""; } catch(e) { };
			try { cl = L[i].getAttribute("class") || ""; } catch(e) { };
			if ((tp == "password") ||
				(tp == "text" && nm.toLowerCase().substring(0,5) == "passw") ||
				(cl.indexOf("mpwdpasswd") > -1)) {
				return L[i];
			}
		}
		return null;
	}
	function mpwd_remove() {
		var body = document.getElementsByTagName('body')[0];
		body.removeChild(document.getElementById('mpwd_bgd'));
		body.removeChild(document.getElementById('mpwd_panel'));
	}
	function mpwd_keyup(e) {
		mpwd_check_password();
		if (e.keyCode == 13 || e.keyCode == 10) {
			mpwd_doIt();
		} else if (e.keyCode == 27) {
			mpwd_remove();
		}
	}
	function mpwd_check_password() {
		var pwd = document.getElementById('masterpwd');
		var pwd2 = document.getElementById('secondpwd');
		if (!pwd2) return 1;
		if (pwd.value != pwd2.value && pwd2.value != '') {
			pwd2.style.background='#f77';
			pwd2.style.borderColor='red';
			return 0;
		} else {
			pwd2.style.background = 'white';
			pwd2.style.borderColor='#777';
			return 1;
		}
	}
	function mpwd_panel(event) {
		var pwdTop = 0;
		var pwdLeft = 0;
		if (document.getElementById('mpwd_panel')) {
			mpwd_remove();
			return;
		}
		try {
			var obj = getPwdFld();
			if (obj.offsetParent) {
				while (obj.offsetParent) {
				pwdTop += obj.offsetTop;
				pwdLeft += obj.offsetLeft;
				obj = obj.offsetParent;
				}
			}
		} catch (e) {
		  pwdTop = 10;
		  pwdLeft = 10;
		}
		// full document width and height as rendered in browser:
		var html = document.getElementsByTagName('html')[0];
		var pag_w = parseInt(document.defaultView.getComputedStyle(html,
			'').getPropertyValue('width'));
		var pag_h = parseInt(document.defaultView.getComputedStyle(html,
			'').getPropertyValue('height'));

		var div = document.createElement('div');
		div.style.color='#777';
		div.style.padding='5px';
		div.style.backgroundColor='white';
		div.style.border='1px solid black';
		div.style.borderBottom='3px solid black';
		div.style.borderRight='2px solid black';
		div.style.MozBorderRadius='10px';
		div.style.fontSize='9pt';	
		div.style.fontFamily='sans-serif';
		div.style.lineHeight='1.8em';
		div.style.position='absolute';
		div.style.width='230px';
		// keep panel at least 10 px away from right page edge
		div.style.left = ((250 + pwdLeft > pag_w)? pag_w - 250 : pwdLeft) +
	'px';
		div.style.top = pwdTop + 'px';
		div.style.zIndex = 9999; // make sure we're visible/on top
		div.setAttribute('id', 'mpwd_panel');
		div.appendChild(document.createTextNode('Master password: '));

		var icnShow = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAA'+
	'AAMCAIAAADZF8uwAAAAkUlEQVR4nGL4TwRgwCWxc%2BfOU6dOQRUZowKI6N%2B%2Ff93c'+
	'3Ly8vBCKMI3Ztm1bZ2dnenr65cuXcSoKCwt7%2BPDh4cOHs7KysCt6%2Ffo1Pz%2B%2Fs'+
	'7Ozk5MTkPH582csiiZMmDBlyhQIu6amZubMmVgUGRkZvXr1CsK%2Bffu2oaEhdjdBAFyK'+
	'aEV4AFQRLmOQAQAAAP%2F%2FAwB27VC%2BrCyA0QAAAABJRU5ErkJggg%3D%3D';
		var icnHide = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAA'+
	'AAMAgMAAAArG7R0AAAADFBMVEX%2F%2F%2F%2FMzMxmZmYzMzM7z8wMAAAAJ0lEQVQImW'+
	'NgAIMEhv%2FH0hgOMEYwHGAD0kD%2BAaAoBBuA8f%2F%2FH0AKARI5DD%2FY1kZdAAAAA'+
	'ElFTkSuQmCC';
		var show = document.createElement('img');
		show.setAttribute('src', (clearText) ? icnShow : icnHide);
		show.setAttribute('id', "icnShow");
		show.setAttribute('title', 'Show or hide generated password');
		show.style.paddingRight = '4px';
		show.style.display='inline'; // some stupid sites set this to block
		show.style.cursor = 'pointer';
		show.addEventListener('click', function(event) {
			clearText = !clearText;
			document.getElementById("icnShow").setAttribute('src',
				(clearText) ? icnShow : icnHide);
			document.getElementById('masterpwd').focus();
		}, true);
		div.appendChild(show);

		var pwd = document.createElement('input');
		pwd.style.border='1px solid #777';
		pwd.setAttribute('type','password');
		pwd.setAttribute('id','masterpwd');
		pwd.style.width = '100px';
		pwd.style.fontSize='9pt';
		pwd.style.color='#777';
		// fire action if RETURN key is typed
		div.appendChild(pwd);
		div.appendChild(document.createElement('br'));
		if (hasMultiplePwdFields()) {
			// only of a 'verify field' is on original page
			div.appendChild(document.createTextNode('Check password: '));
		var pwd2 = document.createElement('input');
		pwd2.setAttribute('type','password');
		pwd2.setAttribute('id','secondpwd');
		pwd2.style.width = '100px';
		pwd2.style.color='#777';
		pwd2.style.border='1px solid #777';	
		pwd2.style.fontSize='9pt';
		div.appendChild(pwd2);
		div.appendChild(document.createElement('br'));
	}
	
	div.appendChild(document.createTextNode('Domain: '));
	
	var subicn = document.createElement('img');
	subicn.setAttribute('src', 'data:image/png;base64,iVBORw0KGgoAAAAN'+
  'SUhEUgAAAAkAAAAJCAAAAADF%2BlnMAAAAGUlEQVR42mNogQGGlv8QgIvFAAL%2FCaqDA'+
  'QCbtDxVGHcjrgAAAABJRU5ErkJggg%3D%3D');
	subicn.setAttribute('id', "icnSubdom");
	subicn.setAttribute('title', 'Toggle use sub domain');
	subicn.style.display='inline';
	subicn.style.paddingRight = '4px';
	subicn.style.cursor = 'pointer';
	subicn.addEventListener('click', function(event) {
		toggleSubdomain();
		document.getElementById('masterpwd').focus();
	}, true);
	div.appendChild(subicn);

	var domn = document.createElement('input');
	domn.setAttribute('type','text');
	domn.setAttribute('value', mpwd_getHostname());
	domn.setAttribute('id','mpwddomain');	
	domn.setAttribute('title','Edit domain name for different password');
	domn.style.width = '150px';
	domn.style.border = 'none';
	domn.style.fontSize='9pt';
	domn.style.color='#777';
	div.appendChild(domn);

	div.addEventListener('keyup', mpwd_keyup, true);

	var bgd = document.createElement('div');
	bgd.setAttribute('id','mpwd_bgd');
	bgd.style.position='absolute';
	bgd.style.top='0px';
	bgd.style.left='0px';
	bgd.style.backgroundColor='black';
	bgd.style.opacity='0.35';
	bgd.style.height = pag_h + 'px';
	bgd.style.width = pag_w + 'px';
	bgd.style.zIndex='9998';
	bgd.addEventListener('click', mpwd_remove, true);
		var body = document.getElementsByTagName('body')[0];
		body.appendChild(bgd);
		body.appendChild(div);
		setTimeout("document.getElementById('masterpwd').focus();", 333);
		initSubdomainSetting();
	};

	// Setting: use sub domain
	function initSubdomainSetting() {
		if (typeof(GM_getValue) == 'function') {
			topDomain = GM_getValue('topDomain', false);
		}
		updateSubDomainSetting();
	}

	function toggleSubdomain() {
		topDomain = !topDomain;
		if (typeof(GM_setValue) == 'function') {
			GM_setValue('topDomain', topDomain);
		}
		updateSubDomainSetting();
	}
	
	function updateSubDomainSetting() {
		var icnPlus = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAA'+
	'AAJCAAAAADF%2BlnMAAAAHUlEQVR42mNogQGGlv8QAGExYLAYQACnLFwvDAAA6Fk4WdfT'+
	'%2FgAAAAAASUVORK5CYII%3D';
		var icnMin = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAA'+
	'AJCAAAAADF%2BlnMAAAAGUlEQVR42mNogQGGlv8QgIvFAAL%2FCaqDAQCbtDxVGHcjrgA'+
	'AAABJRU5ErkJggg%3D%3D';
		 document.getElementById("icnSubdom").setAttribute('src',
			(topDomain) ? icnMin : icnPlus);
		document.getElementById("mpwddomain").setAttribute('value',
			 mpwd_getHostname());
	}

	function mpwd_launcher() {
		// image 12px
		var bullet = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAA'+
	'AMCAYAAABWdVznAAAAX0lEQVR4nGL4%2F%2F8%2FAykYwkhL%2B08IR0VFIWkAUn%2F%2'+
	'B%2FMGJoRqysGoASRKtAWw9SOjMGRTN%2BG0AKgYLQzUT1AC3iVgNcGdBMVE2EOVpQhhV'+
	'AxCDBAhhFA3EYgAAAAD%2F%2FwMAKhyYBtU1wpoAAAAASUVORK5CYII%3D';
		var pwdTop = 0;
		var pwdLft = 0;
		var obj;
		try {
			obj = getPwdFld();
			if (obj.offsetParent) {
				while (obj.offsetParent) {
				pwdTop += obj.offsetTop;
				pwdLft += obj.offsetLeft;
				obj = obj.offsetParent;
				}
				}
				} catch (e) {
				    pwdTop = 10;
				pwdLft = 10;
				}
				// return if no passwd field is found
				if (! obj) return;
				var bull = document.createElement('img');
				bull.style.position='absolute';
				bull.style.top = pwdTop + 'px';
				bull.style.left = (pwdLft - 12) + 'px';
				bull.setAttribute('src', bullet);
				bull.setAttribute('title', 'Open Password Composer');
				bull.style.cursor = 'pointer';
				bull.addEventListener('click', mpwd_panel, true);
				bull.style.zIndex = 9999;
				document.getElementsByTagName('body')[0].appendChild(bull);
			}
			
			mpwd_launcher();
			// add menu command to manually launch passwd composer
			if (typeof(GM_registerMenuCommand == 'function')) {
				GM_registerMenuCommand("Show Password Composer", mpwd_panel);
			}

Running the Hack

After installing the user script (Tools Install This User Script), go to http://www.flickr.com and click "Sign up" to register a new account. In the registration form, you will see a small icon next to the password field titled "Open Password Composer," as shown in Figure 4-11.

Password Composer active

Figure 4-11. Password Composer active

Click the icon to open the Password Composer, and you have a chance to enter your master password, as shown in Figure 4-12.

Entering a master password

Figure 4-12. Entering a master password

Enter a password in the box, press Enter, and you will go back to the original page with the password field filled with a generated password.

You have the option of using this password for this entire site or just for a specific subdomain. This is especially useful on blogging sites such as Blogspot or TypePad, where each subdomain is really a different site owned by a different person. You can choose a different random password for each subdomain, or you can choose to share a single password across all subdomains.

The script is also smart enough to handle web forms that contain multiple password fields, such as sites that tell you to enter a password and then immediately enter it again in a separate field to make sure you didn't mistype it. The script autofills both password fields once you type your master password.

Get Greasemonkey Hacks now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.