3 * A JavaScript implementation of the PwdHash hashing algorithm.
4 * Version 1.0 Copyright (C) Stanford University 2004-2006
5 * Author: Collin Jackson
6 * Other Contributors: Dan Boneh, John Mitchell, Nick Miyake, and Blake Ross
7 * Distributed under the BSD License
8 * See http://crypto.stanford.edu/PwdHash for more info.
9 * Requires the Javascript MD5 library, available here: http://pajhome.org.uk/crypt/md5
13 * Initialize page with default hashing parameters.
16 document
.inputform
.term
.value
= "http://www.example.com/";
17 document
.hashpass
.sitePassword
.value
= "";
18 document
.hashpass
.hashedPassword
.value
= "Press Generate";
19 document
.hashpass
.hashedPassword
.disabled
= true;
22 var SPH_kPasswordPrefix
= "@@";
25 * Returns a conforming hashed password generated from the form's field values.
29 var uri
= document
.inputform
.term
.value
;
30 var domain
= (new SPH_DomainExtractor()).extractDomain(uri
);
31 var size
= SPH_kPasswordPrefix
.length
;
32 var data
= document
.hashpass
.sitePassword
.value
;
33 if (data
.substring(0, size
) == SPH_kPasswordPrefix
)
34 data
= data
.substring(size
);
35 var result
= new String(new SPH_HashedPassword(data
, domain
));
40 * Obtain a conforming hashed password and put it in the hashed password field
42 function GenerateToTextField()
44 if ((document
.hashpass
.sitePassword
.value
!= "") && (document
.inputform
.term
.value
!= "")) {
45 document
.hashpass
.hashedPassword
.value
= Generate();
46 document
.hashpass
.hashedPassword
.disabled
= false;
49 if (document
.hashpass
.hashedPassword
.value
!= "") {
50 document
.hashpass
.hashedPassword
.value
= "";
60 Copyright 2005 Collin Jackson
62 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
64 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
65 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
66 * Neither the name of Stanford University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
68 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
73 * Domain name extractor. Turns host names into domain names
74 * Adapted from Chris Zarate's public domain genpass tool:
75 * http://labs.zarate.org/passwd/
78 function SPH_DomainExtractor() { }
80 SPH_DomainExtractor
.prototype = {
82 extractDomain: function(host
) {
84 var s
; // the final result
86 // Begin Chris Zarate's code
87 host
=host
.replace('http:\/\/','');
88 host
=host
.replace('https:\/\/','');
89 re
=new RegExp("([^/]+)");
90 host
=host
.match(re
)[1];
94 s
=host
[host
.length
-2]+'.'+host
[host
.length
-1];
95 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.pl|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.net|gd.cn|gen.nz|gmina.pl|go.jp|go.kr|go.th|gob.mx|gov.br|gov.cn|gov.ec|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.com|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|net.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|res.in|ru.com|sa.com|sc.cn|school.nz|school.za|se.com|se.net|sh.cn|shop.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|turystyka.pl|tv.br|tw.cn|uk.co|uk.com|uk.net|us.com|uy.com|vet.br|web.za|web.com|www.ro|xj.cn|xz.cn|yk.ca|yn.cn|za.com';
96 domains
=domains
.split('|');
97 for(var i
=0;i
<domains
.length
;i
++) {
99 s
=host
[host
.length
-3]+'.'+s
;
106 // End Chris Zarate's code
116 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
117 * Digest Algorithm, as defined in RFC 1321.
118 * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
119 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
120 * Distributed under the BSD License
121 * See http://pajhome.org.uk/crypt/md5 for more info.
125 * Configurable variables. You may need to tweak these to be compatible with
126 * the server-side, but the defaults work in most cases.
128 var hexcase
= 0; /* hex output format. 0 - lowercase; 1 - uppercase */
129 var b64pad
= ""; /* base-64 pad character. "=" for strict RFC compliance */
130 var chrsz
= 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
133 * These are the functions you'll usually want to call
134 * They take string arguments and return either hex or base-64 encoded strings
136 function hex_md5(s
){ return binl2hex(core_md5(str2binl(s
), s
.length
* chrsz
));}
137 function b64_md5(s
){ return binl2b64(core_md5(str2binl(s
), s
.length
* chrsz
));}
138 function str_md5(s
){ return binl2str(core_md5(str2binl(s
), s
.length
* chrsz
));}
139 function hex_hmac_md5(key
, data
) { return binl2hex(core_hmac_md5(key
, data
)); }
140 function b64_hmac_md5(key
, data
) { return binl2b64(core_hmac_md5(key
, data
)); }
141 function str_hmac_md5(key
, data
) { return binl2str(core_hmac_md5(key
, data
)); }
144 * Perform a simple self-test to see if the VM is working
146 function md5_vm_test()
148 return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
152 * Calculate the MD5 of an array of little-endian words, and a bit length
154 function core_md5(x
, len
)
157 x
[len
>> 5] |= 0x80 << ((len
) % 32);
158 x
[(((len
+ 64) >>> 9) << 4) + 14] = len
;
165 for(var i
= 0; i
< x
.length
; i
+= 16)
172 a
= md5_ff(a
, b
, c
, d
, x
[i
+ 0], 7 , -680876936);
173 d
= md5_ff(d
, a
, b
, c
, x
[i
+ 1], 12, -389564586);
174 c
= md5_ff(c
, d
, a
, b
, x
[i
+ 2], 17, 606105819);
175 b
= md5_ff(b
, c
, d
, a
, x
[i
+ 3], 22, -1044525330);
176 a
= md5_ff(a
, b
, c
, d
, x
[i
+ 4], 7 , -176418897);
177 d
= md5_ff(d
, a
, b
, c
, x
[i
+ 5], 12, 1200080426);
178 c
= md5_ff(c
, d
, a
, b
, x
[i
+ 6], 17, -1473231341);
179 b
= md5_ff(b
, c
, d
, a
, x
[i
+ 7], 22, -45705983);
180 a
= md5_ff(a
, b
, c
, d
, x
[i
+ 8], 7 , 1770035416);
181 d
= md5_ff(d
, a
, b
, c
, x
[i
+ 9], 12, -1958414417);
182 c
= md5_ff(c
, d
, a
, b
, x
[i
+10], 17, -42063);
183 b
= md5_ff(b
, c
, d
, a
, x
[i
+11], 22, -1990404162);
184 a
= md5_ff(a
, b
, c
, d
, x
[i
+12], 7 , 1804603682);
185 d
= md5_ff(d
, a
, b
, c
, x
[i
+13], 12, -40341101);
186 c
= md5_ff(c
, d
, a
, b
, x
[i
+14], 17, -1502002290);
187 b
= md5_ff(b
, c
, d
, a
, x
[i
+15], 22, 1236535329);
189 a
= md5_gg(a
, b
, c
, d
, x
[i
+ 1], 5 , -165796510);
190 d
= md5_gg(d
, a
, b
, c
, x
[i
+ 6], 9 , -1069501632);
191 c
= md5_gg(c
, d
, a
, b
, x
[i
+11], 14, 643717713);
192 b
= md5_gg(b
, c
, d
, a
, x
[i
+ 0], 20, -373897302);
193 a
= md5_gg(a
, b
, c
, d
, x
[i
+ 5], 5 , -701558691);
194 d
= md5_gg(d
, a
, b
, c
, x
[i
+10], 9 , 38016083);
195 c
= md5_gg(c
, d
, a
, b
, x
[i
+15], 14, -660478335);
196 b
= md5_gg(b
, c
, d
, a
, x
[i
+ 4], 20, -405537848);
197 a
= md5_gg(a
, b
, c
, d
, x
[i
+ 9], 5 , 568446438);
198 d
= md5_gg(d
, a
, b
, c
, x
[i
+14], 9 , -1019803690);
199 c
= md5_gg(c
, d
, a
, b
, x
[i
+ 3], 14, -187363961);
200 b
= md5_gg(b
, c
, d
, a
, x
[i
+ 8], 20, 1163531501);
201 a
= md5_gg(a
, b
, c
, d
, x
[i
+13], 5 , -1444681467);
202 d
= md5_gg(d
, a
, b
, c
, x
[i
+ 2], 9 , -51403784);
203 c
= md5_gg(c
, d
, a
, b
, x
[i
+ 7], 14, 1735328473);
204 b
= md5_gg(b
, c
, d
, a
, x
[i
+12], 20, -1926607734);
206 a
= md5_hh(a
, b
, c
, d
, x
[i
+ 5], 4 , -378558);
207 d
= md5_hh(d
, a
, b
, c
, x
[i
+ 8], 11, -2022574463);
208 c
= md5_hh(c
, d
, a
, b
, x
[i
+11], 16, 1839030562);
209 b
= md5_hh(b
, c
, d
, a
, x
[i
+14], 23, -35309556);
210 a
= md5_hh(a
, b
, c
, d
, x
[i
+ 1], 4 , -1530992060);
211 d
= md5_hh(d
, a
, b
, c
, x
[i
+ 4], 11, 1272893353);
212 c
= md5_hh(c
, d
, a
, b
, x
[i
+ 7], 16, -155497632);
213 b
= md5_hh(b
, c
, d
, a
, x
[i
+10], 23, -1094730640);
214 a
= md5_hh(a
, b
, c
, d
, x
[i
+13], 4 , 681279174);
215 d
= md5_hh(d
, a
, b
, c
, x
[i
+ 0], 11, -358537222);
216 c
= md5_hh(c
, d
, a
, b
, x
[i
+ 3], 16, -722521979);
217 b
= md5_hh(b
, c
, d
, a
, x
[i
+ 6], 23, 76029189);
218 a
= md5_hh(a
, b
, c
, d
, x
[i
+ 9], 4 , -640364487);
219 d
= md5_hh(d
, a
, b
, c
, x
[i
+12], 11, -421815835);
220 c
= md5_hh(c
, d
, a
, b
, x
[i
+15], 16, 530742520);
221 b
= md5_hh(b
, c
, d
, a
, x
[i
+ 2], 23, -995338651);
223 a
= md5_ii(a
, b
, c
, d
, x
[i
+ 0], 6 , -198630844);
224 d
= md5_ii(d
, a
, b
, c
, x
[i
+ 7], 10, 1126891415);
225 c
= md5_ii(c
, d
, a
, b
, x
[i
+14], 15, -1416354905);
226 b
= md5_ii(b
, c
, d
, a
, x
[i
+ 5], 21, -57434055);
227 a
= md5_ii(a
, b
, c
, d
, x
[i
+12], 6 , 1700485571);
228 d
= md5_ii(d
, a
, b
, c
, x
[i
+ 3], 10, -1894986606);
229 c
= md5_ii(c
, d
, a
, b
, x
[i
+10], 15, -1051523);
230 b
= md5_ii(b
, c
, d
, a
, x
[i
+ 1], 21, -2054922799);
231 a
= md5_ii(a
, b
, c
, d
, x
[i
+ 8], 6 , 1873313359);
232 d
= md5_ii(d
, a
, b
, c
, x
[i
+15], 10, -30611744);
233 c
= md5_ii(c
, d
, a
, b
, x
[i
+ 6], 15, -1560198380);
234 b
= md5_ii(b
, c
, d
, a
, x
[i
+13], 21, 1309151649);
235 a
= md5_ii(a
, b
, c
, d
, x
[i
+ 4], 6 , -145523070);
236 d
= md5_ii(d
, a
, b
, c
, x
[i
+11], 10, -1120210379);
237 c
= md5_ii(c
, d
, a
, b
, x
[i
+ 2], 15, 718787259);
238 b
= md5_ii(b
, c
, d
, a
, x
[i
+ 9], 21, -343485551);
240 a
= safe_add(a
, olda
);
241 b
= safe_add(b
, oldb
);
242 c
= safe_add(c
, oldc
);
243 d
= safe_add(d
, oldd
);
245 return Array(a
, b
, c
, d
);
250 * These functions implement the four basic operations the algorithm uses.
252 function md5_cmn(q
, a
, b
, x
, s
, t
)
254 return safe_add(bit_rol(safe_add(safe_add(a
, q
), safe_add(x
, t
)), s
),b
);
256 function md5_ff(a
, b
, c
, d
, x
, s
, t
)
258 return md5_cmn((b
& c
) | ((~b
) & d
), a
, b
, x
, s
, t
);
260 function md5_gg(a
, b
, c
, d
, x
, s
, t
)
262 return md5_cmn((b
& d
) | (c
& (~d
)), a
, b
, x
, s
, t
);
264 function md5_hh(a
, b
, c
, d
, x
, s
, t
)
266 return md5_cmn(b
^ c
^ d
, a
, b
, x
, s
, t
);
268 function md5_ii(a
, b
, c
, d
, x
, s
, t
)
270 return md5_cmn(c
^ (b
| (~d
)), a
, b
, x
, s
, t
);
274 * Calculate the HMAC-MD5, of a key and some data
276 function core_hmac_md5(key
, data
)
278 var bkey
= str2binl(key
);
279 if(bkey
.length
> 16) bkey
= core_md5(bkey
, key
.length
* chrsz
);
281 var ipad
= Array(16), opad
= Array(16);
282 for(var i
= 0; i
< 16; i
++)
284 ipad
[i
] = bkey
[i
] ^ 0x36363636;
285 opad
[i
] = bkey
[i
] ^ 0x5C5C5C5C;
288 var hash
= core_md5(ipad
.concat(str2binl(data
)), 512 + data
.length
* chrsz
);
289 return core_md5(opad
.concat(hash
), 512 + 128);
293 * Add integers, wrapping at 2^32. This uses 16-bit operations internally
294 * to work around bugs in some JS interpreters.
296 function safe_add(x
, y
)
298 var lsw
= (x
& 0xFFFF) + (y
& 0xFFFF);
299 var msw
= (x
>> 16) + (y
>> 16) + (lsw
>> 16);
300 return (msw
<< 16) | (lsw
& 0xFFFF);
304 * Bitwise rotate a 32-bit number to the left.
306 function bit_rol(num
, cnt
)
308 return (num
<< cnt
) | (num
>>> (32 - cnt
));
312 * Convert a string to an array of little-endian words
313 * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
315 function str2binl(str
)
318 var mask
= (1 << chrsz
) - 1;
319 for(var i
= 0; i
< str
.length
* chrsz
; i
+= chrsz
)
320 bin
[i
>>5] |= (str
.charCodeAt(i
/ chrsz
) & mask
) << (i
%32);
325 * Convert an array of little-endian words to a string
327 function binl2str(bin
)
330 var mask
= (1 << chrsz
) - 1;
331 for(var i
= 0; i
< bin
.length
* 32; i
+= chrsz
)
332 str
+= String
.fromCharCode((bin
[i
>>5] >>> (i
% 32)) & mask
);
337 * Convert an array of little-endian words to a hex string.
339 function binl2hex(binarray
)
341 var hex_tab
= hexcase
? "0123456789ABCDEF" : "0123456789abcdef";
343 for(var i
= 0; i
< binarray
.length
* 4; i
++)
345 str
+= hex_tab
.charAt((binarray
[i
>>2] >> ((i
%4)*8+4)) & 0xF) +
346 hex_tab
.charAt((binarray
[i
>>2] >> ((i
%4)*8 )) & 0xF);
352 * Convert an array of little-endian words to a base-64 string
354 function binl2b64(binarray
)
356 var tab
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
358 for(var i
= 0; i
< binarray
.length
* 4; i
+= 3)
360 var triplet
= (((binarray
[i
>> 2] >> 8 * ( i
%4)) & 0xFF) << 16)
361 | (((binarray
[i
+1 >> 2] >> 8 * ((i
+1)%4)) & 0xFF) << 8 )
362 | ((binarray
[i
+2 >> 2] >> 8 * ((i
+2)%4)) & 0xFF);
363 for(var j
= 0; j
< 4; j
++)
365 if(i
* 8 + j
* 6 > binarray
.length
* 32) str
+= b64pad
;
366 else str
+= tab
.charAt((triplet
>> 6*(3-j
)) & 0x3F);
378 Copyright 2005 Collin Jackson
380 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
382 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
383 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
384 * Neither the name of Stanford University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
386 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
392 * Combination of page URI and plaintext password.
393 * Treated as a string, it is the hashed password.
396 function SPH_HashedPassword(password
, realm
) {
397 var hashedPassword
= this._getHashedPassword(password
, realm
);
398 this.toString = function() { return hashedPassword
; }
401 var SPH_kPasswordPrefix
= "@@";
403 SPH_HashedPassword
.prototype = {
406 * Determine the hashed password from the salt and the master password
408 _getHashedPassword: function(password
, realm
) {
409 var hash
= b64_hmac_md5(password
, realm
);
410 var size
= password
.length
+ SPH_kPasswordPrefix
.length
;
411 var nonalphanumeric
= password
.match(/\W/) != null;
412 var result
= this._applyConstraints(hash
, size
, nonalphanumeric
);
417 * Fiddle with the password a bit after hashing it so that it will get through
418 * most website filters. We require one upper and lower case, one digit, and
419 * we look at the user's password to determine if there should be at least one
420 * alphanumeric or not.
422 _applyConstraints: function(hash
, size
, nonalphanumeric
) {
423 var startingSize
= size
- 4; // Leave room for some extra characters
424 var result
= hash
.substring(0, startingSize
);
425 var extras
= hash
.substring(startingSize
).split('');
427 // Some utility functions to keep things tidy
428 function nextExtra() { return extras
.length
? extras
.shift().charCodeAt(0) : 0; }
429 function nextExtraChar() { return String
.fromCharCode(nextExtra()); }
430 function rotate(arr
, amount
) { while(amount
--) arr
.push(arr
.shift()) }
431 function between(min
, interval
, offset
) { return min
+ offset
% interval
; }
432 function nextBetween(base
, interval
) {
433 return String
.fromCharCode(between(base
.charCodeAt(0), interval
, nextExtra()));
435 function contains(regex
) { return result
.match(regex
); }
437 // Add the extra characters
438 result
+= (contains(/[A-Z]/) ? nextExtraChar() : nextBetween('A', 26));
439 result
+= (contains(/[a-z]/) ? nextExtraChar() : nextBetween('a', 26));
440 result
+= (contains(/[0-9]/) ? nextExtraChar() : nextBetween('0', 10));
441 result
+= (contains(/\W/) && nonalphanumeric
? nextExtraChar() : '+');
442 while (contains(/\W/) && !nonalphanumeric
) {
443 result
= result
.replace(/\W/, nextBetween('A', 26));
446 // Rotate the result to make it harder to guess the inserted locations
447 result
= result
.split('');
448 rotate(result
, nextExtra());
449 return result
.join('');