1 /**
  2  * 密码强度检测
  3  *
  4  * @fileOverView 基础组件
  5  * @author  <a href="mailto:zhang.gd@foxmail.com">Zhang Guangda</a>
  6  * @date    2012-10-25
  7  */
  8 define(function() {
  9 
 10 	/*
 11 	 * SNDA 密码强度检测对象
 12 	 * Level = 0
 13 	 * 错误:errorId 意义
 14 	 *       1	 长度、字符不符合
 15 	 *       2   密码属于社会工程学中
 16 	 *       3   密码与用户名相同
 17 	 *			 4   连续或重复的数字
 18 	 *       5   连续或重复的字母
 19 	 */
 20 	function PasswordStrength()
 21 	{	
 22 		this.minLen = 6;
 23 		this.maxLen = 30;
 24 		this.pwdCharSet="A-Za-z0-9";
 25 		this.caseInsensitive=false;
 26 		this.Description="";
 27 		this.userName="";
 28 		this.applyRulesDesc=new Array();
 29 		this.errorId="0";
 30 	}
 31 
 32 	PasswordStrength.prototype.$ = function(s) {
 33 		return document.getElementById(s);
 34 	}
 35 
 36 	PasswordStrength.prototype.setMinLen = function(n){
 37 		if(isNaN(n))
 38 		{
 39 			return ;
 40 		}
 41 		n = Number(n);
 42 		if(n>1)
 43 		{
 44 			this.minLen = n;
 45 		}
 46 	}
 47 	PasswordStrength.prototype.setMaxLen = function(n){
 48 		if(isNaN(n))
 49 		{
 50 			return ;
 51 		}
 52 		n = Number(n);
 53 		if(n >=this.minLen)
 54 		{
 55 			this.maxLen = n;
 56 		}
 57 	}
 58 	PasswordStrength.prototype.setPwdCharSet = function(s){
 59 		this.pwdCharSet=s;
 60 	}
 61 	PasswordStrength.prototype.getDescription =function() {
 62 		return this.Description;
 63 	}
 64 	//大小写敏感
 65 	PasswordStrength.prototype.setCaseInsensitive = function(s){
 66 		this.caseInsensitive=s;
 67 	}
 68 	PasswordStrength.prototype.setUserName = function(s) {
 69 		this.userName=s;
 70 	}
 71 	PasswordStrength.prototype.getApplyRulesDesc =function () {
 72 		return this.applyRulesDesc;
 73 	}
 74 	PasswordStrength.prototype.getErrorId = function (){
 75 		return this.errorId;
 76 	}
 77 
 78 	//检查密码的长度和字符是否满足
 79 	PasswordStrength.prototype.checkPwd = function(s) {
 80 		var lvRegExp=new RegExp("^["+this.pwdCharSet+"]{"+this.minLen+","+this.maxLen+"}$",this.caseInsensitive?"":"i");
 81 		return lvRegExp.test(s);
 82 	}
 83 	//检查密码是否是社会工程字典中的密码
 84 	PasswordStrength.prototype.checkDict = function(s) {
 85 		var lvDicts=new Array("shanda","asdfg","asdfgh","qwert","qwerty","zxcvb","zxcvbn",
 86 													"Password","Passwd","Woaini","Iloveyou","Woaiwojia","521521","5201314","7758521","1314520","1314521",
 87 													"520520","201314","211314","7758258","147258369","159357",
 88 													"test","snda","12345","123456","1234567","12345678","123456789","asdf","qwer","zxcv","654321","123123",
 89 													"123321","123abc");
 90 	    // var lvDicts=new Array();
 91 		if(this.caseInsensitive==false)
 92 		{
 93 			s=s.toUpperCase();
 94 		}
 95 		for(var i=0;i<lvDicts.length;i++)
 96 		{
 97 			if(s==(this.caseInsensitive?lvDicts[i]:lvDicts[i].toUpperCase()))
 98 			{
 99 				return s;
100 			}
101 	  }									 
102 	  return "";
103 	}
104 	PasswordStrength.prototype.getLevel = function(s)
105 	{
106 		this.applyRulesDesc=new Array();
107 		this.errorId="0";
108 		this.Description="";
109 		if(this.checkPwd(s))
110 		{
111 			if(this.checkDict(s)=="")
112 			{
113 				//密码与用户名是否相同
114 				var lvUserName=this.userName;
115 				var lvPwd=s;
116 				if(lvUserName == null)
117 				{
118 					lvUserName="";
119 				}
120 				if(this.caseInsensitive==false)
121 				{
122 					lvUserName=lvUserName.toUpperCase();
123 					lvPwd=lvPwd.toUpperCase();
124 	      }
125 				if(lvUserName==lvPwd)
126 				{
127 						this.errorId="3";
128 						this.applyRulesDesc.push("规则 2:	密码与用户名相同");
129 						//this.Description="密码与用户名相同";
130 						this.Description="对不起,请勿使用与用户名相同的密码";
131 						return 0;
132 				}
133 				//add rule: half of pwd can not be exist in username
134 				var lvHalfPwdLen=Math.floor((lvPwd.length+1)/2);
135 				for(var lvPwdIndex=0,lvLoopIndex=lvPwd.length-lvHalfPwdLen;lvPwdIndex<=lvLoopIndex;lvPwdIndex++)
136 				{
137 					var lvCompHalfPwd=lvPwd.substring(lvPwdIndex,lvHalfPwdLen+lvPwdIndex);
138 					if(lvUserName.indexOf(lvCompHalfPwd)>=0)
139 					{
140 						this.applyRulesDesc.push("规则 20:	密码中的字符与用户名的字符不能有半数相同");
141 						//this.Description="密码中的字符与用户名的字符有半数相同";
142 						this.Description="对不起,请不要将账号的一部分作为密码";
143 						return 0;				
144 					}
145 				}
146 				
147 				var ls = 0;
148 				if( (this.caseInsensitive? s.match(/[a-z]/g): s.match(/[a-z]/ig)))
149 				{
150 				    this.applyRulesDesc.push("规则 4: 密码包含字母,权重加1");
151 				    ls++;
152 	            }
153 				if (s.match("[0-9]","ig"))
154 				{
155 					this.applyRulesDesc.push("规则 5: 密码含有数字,权重加1");
156 					ls++;
157 				}
158 				if (s.length < 6 && ls > 1)
159 				{
160 					this.applyRulesDesc.push("规则 7: 密码长度小于6且权重大于1,权重减1");
161 					ls--;
162 				}	
163 				if (s.length >= 8 && ls == 2)
164 				{
165 					this.applyRulesDesc.push("规则 8: 密码长度大于等于8且权重等于2,权重加1");
166 					ls++;
167 				}
168 				if (ls > 1 && !this.samePwd(s))
169 				{
170 					this.applyRulesDesc.push("规则 9: 连续重复的数字/字母,权重减1");
171 					ls--;
172 				}
173 				if (ls > 1 && !this.increasePwd(s) )
174 				{
175 					this.applyRulesDesc.push("规则 12: 连续递增的数字(仅仅是数字)长度占到总密码长度的50%以上,权重减1");
176 					ls--;
177 				}
178 				if (ls > 1 && !this.descendingPwd(s))
179 				{
180 					this.applyRulesDesc.push("规则 12: 连续递减的数字(仅仅是数字)长度占到总密码长度的50%以上,权重减1");
181 					ls--;
182 				}
183 				if(ls==1 && this.checkDigitial(s)) //连续/重复数字   权重-1
184 				{
185 					this.errorId="4"
186 					this.applyRulesDesc.push("规则 10: 当密码中全是连续/重复的数字,权重减1");
187 					//this.Description="密码为连续或重复的数字";
188 					this.Description="对不起,请不要将连续或重复的数字作为密码";
189 					ls--;
190 				}
191 				if(ls==1 && this.checkCharLetter(s))	//连续/重复字母 权重-1
192 				{
193 					this.errorId="5"
194 					this.applyRulesDesc.push("规则 10: 当密码中全是连续/重复的字母,权重减1");
195 					//this.Description="密码为连续或重复的字母";
196 					this.Description="对不起,请不要以连续或重复的字母作为密码";
197 					ls--;
198 				}
199 				if(ls>1 && this.checkDigitialCharLetters(s)) //密码包含字母和数字,且字母和数字皆连续,权重-1
200 				{
201 					this.applyRulesDesc.push("规则 11: 密码包含字母和数字,且字母和数字皆连续,权重减1");
202 					ls--;
203 				}
204 				if(ls==2 && this.checkCharLetterCounts(s,1))	//一个字母和数字组合情况,权重-1
205 				{
206 					this.applyRulesDesc.push("规则 14: 权重为2,只有一种字母与数字组合,权重减1");
207 					ls--;
208 				}
209 				if(ls==3 && this.checkCharLetterCounts(s,2)) //小于等于2个字母和数字组合情况,权重-1
210 				{
211 					this.applyRulesDesc.push("规则 14: 权重为3,只有一种或二种字母与数字组合,权重减1");
212 					ls--;
213 			    }
214 				return ls;
215 		  }
216 	 	  else
217 	 	  {
218 	 			this.errorId="2";
219 	 			this.applyRulesDesc.push("规则 1:密码属于符合社会工程学词典");
220 	 			//this.Description="你不能使用"+s+"作为密码,该密码非常容易被猜测!"; 
221 	 			this.Description="对不起,您的密码过于简单";			
222 	 			//return 0;
223 	 			return 1;
224 	 	  }
225 		}
226 		else
227 		{
228 			this.errorId="1";
229 			this.applyRulesDesc.push("规则 3:密码长度必须在["+this.minLen+","+this.maxLen+"],且密码字符必须在["+this.pwdCharSet+"]");
230 			//this.Description="密码长度必须在["+this.minLen+","+this.maxLen+"]之间,且密码字符必须在["+this.pwdCharSet+"]";
231 			this.Description="对不起,密码只能由"+this.minLen+"-"+this.maxLen+"位数字和字母组成";
232 			return 0;
233 		}
234 	}
235 	PasswordStrength.prototype.checkCharLetterCounts = function(p,pvCounts) {
236 		var s=p.toUpperCase();
237 		if(this.caseInsensitive)
238 		{
239 			s=p;
240 	  }
241 	  var lvRegExp=new RegExp("^[A-Za-z]$","");
242 	  var lvLastChar="";
243 	  var lvCharCounts=0;
244 	  for(var i=0;i<s.length;i++)
245 	  {
246 	  	var c=s.substring(i,i+1);
247 	  	if(lvRegExp.test(c))
248 	  	{
249 	  		if(lvLastChar.indexOf(c)==-1)
250 	  		{
251 	  			lvCharCounts++;
252 	  			lvLastChar=lvLastChar+c;
253 	  		}
254 	  	}
255 	  }  
256 	  return (lvCharCounts<=pvCounts);
257 	}
258 	PasswordStrength.prototype.convertCharLetterInt= function(p){
259 		var s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
260 		for(var i=0;i<s.length;i++)
261 		{
262 			if(s.substring(i,i+1)== (this.caseInsensitive?p:p.toUpperCase()))
263 			{
264 				return i;
265 			}
266 		}
267 		return -1;
268 	}
269 	PasswordStrength.prototype.checkDigitialCharLetters = function(p){
270 		var s=p.toUpperCase();
271 		if(this.caseInsensitive)
272 		{
273 			s=p;
274 	  }
275 	  var c=s.substring(0,1);
276 	  var lvRegExp=new RegExp("^[0-9]$","");
277 	  var lvDigitialChar="";
278 	  var lvCharLetter="";
279 	  var lvNeedChecking=true;
280 	  if(lvRegExp.test(c)) //数字开始
281 	  {
282 	  	lvDigitialChar=c;
283 	  	var lvDigitialing=true;
284 	  	for(var i=1;i<s.length;i++)
285 	  	{
286 	  		c=s.substring(i,i+1);
287 	  		if(lvDigitialing)
288 	  		{
289 		  		if(lvRegExp.test(c))
290 		  		{
291 		  			lvDigitialChar=lvDigitialChar+c;
292 		  		}
293 		  		else
294 		  		{
295 		  			lvDigitialing=false;
296 		  		}
297 	  		}
298 	  		else
299 	 			{
300 	 				if(lvRegExp.test(c))
301 	 				{
302 	 					lvNeedChecking=false;
303 	 					break;
304 	 				}
305 	 			}
306 	    }
307 	    if(lvNeedChecking)
308 	    {
309 	    	lvCharLetter=s.substring(lvDigitialChar.length,s.length);
310 	    }  	
311 	  }  
312 	  else //字符开始
313 	 	{
314 	 		lvCharLetter=c;
315 	 		var lvCharLettering=true;
316 	 		for(var i=1;i<s.length;i++)
317 	 		{
318 	 			c=s.substring(i,i+1);
319 	 			if(lvCharLettering)
320 	 			{
321 	 					if(lvRegExp.test(c)==false)
322 	 					{
323 	 							lvCharLetter=lvCharLetter+c;
324 	 					}			
325 	 					else
326 	 					{
327 	 							lvCharLettering=false;
328 	 					}
329 	 		  }
330 	 		  else
331 	 		  {
332 	 		  	if(lvRegExp.test(c)==false)
333 	 		  	{
334 	 		  		lvNeedChecking=false;
335 	 		  		break;
336 	 		  	}
337 	 		  }
338 	 		}
339 	 		if(lvNeedChecking)
340 	 		{
341 	 			lvDigitialChar=s.substring(lvCharLetter.length,s.length);
342 	 	  }	
343 	 	}
344 	 	return (lvNeedChecking && this.checkDigitial(lvDigitialChar) && this.checkCharLetter(lvCharLetter));
345 	}
346 	PasswordStrength.prototype.checkCharLetter= function(p){
347 		var s=p.toUpperCase();
348 		if(this.caseInsensitive)
349 		{
350 			s=p;
351 	  }	
352 		var lvLastChar=s.substring(0,1);
353 		var lvLetterDiff=0;
354 		if(isNaN(lvLastChar))
355 		{
356 				for(var i=1;i<s.length;i++)
357 				{
358 					var c=s.substring(i,i+1);
359 					if(isNaN(c))
360 					{
361 						if(i==1)
362 						{
363 							lvLetterDiff=this.convertCharLetterInt(c)-this.convertCharLetterInt(lvLastChar);
364 							if(lvLetterDiff!=0 && lvLetterDiff!=1 && lvLetterDiff!=-1)
365 							{
366 								return false;	
367 							}			
368 						}
369 						else
370 						{
371 							if((this.convertCharLetterInt(c)-this.convertCharLetterInt(lvLastChar))!=lvLetterDiff)
372 							{
373 								return false;	
374 							}			
375 							
376 						}
377 						lvLastChar=c;				
378 					}
379 					else
380 					{
381 						return false;
382 					}			
383 				}
384 				return true;			
385 		}
386 		else
387 		{
388 			return false;
389 		}
390 	}
391 	PasswordStrength.prototype.checkDigitial = function(s){
392 		var lvLastChar=s.substring(0,1);
393 		var lvDigitialDiff=0;
394 		if(isNaN(lvLastChar)==true)
395 		{
396 			return false;
397 	  }	
398 		for(var i=1;i<s.length;i++)
399 		{
400 			var c=s.substring(i,i+1);
401 			if(isNaN(c)==false)
402 			{
403 				if(i==1)
404 				{
405 					lvDigitialDiff=parseInt(c)-parseInt(lvLastChar);
406 					if(lvDigitialDiff!=0 && lvDigitialDiff!=1 && lvDigitialDiff!=-1)
407 					{
408 						return false;	
409 					}			
410 				}
411 				else
412 				{
413 					if((parseInt(c)-parseInt(lvLastChar))!=lvDigitialDiff)
414 					{
415 						return false;	
416 					}			
417 				}
418 				lvLastChar=c;				
419 			}
420 			else
421 			{
422 				return false;
423 			}			
424 		}	
425 		return true;
426 	}
427 	PasswordStrength.prototype.samePwd = function(s)
428 	{
429 		if(this.caseInsensitive==false)
430 		{
431 			s=s.toUpperCase();
432 		}
433 		var errorLength = 0;
434 		var tmpLength = 1;
435 		var i = 0;
436 		for(i=1;i<s.length;i++)
437 		{
438 			if(!isNaN(s.substr(i,1)) && !isNaN(s.substr(i-1,1)))
439 			{
440 			    if(s.substr(i,1) == s.substr(i-1,1))
441 			    {
442 				    tmpLength++;	
443 			    }
444 			    else
445 			    {
446 				    tmpLength = 1;
447 			    }
448 			    if(tmpLength > errorLength)
449 			    {
450 				    errorLength = tmpLength;
451 			    }
452 			}
453 		}
454 		if(errorLength>=(s.length/2))
455 		{
456 			return false;
457 		}
458 		else
459 		{
460 			return true;
461 		}
462 	}
463 	PasswordStrength.prototype.increasePwd = function(s)
464 	{
465 		var errorLength = 0;
466 		var tmpLength = 1;
467 		var i = 0;
468 		for(i=1;i<s.length;i++)
469 		{
470 			if(!isNaN(s.substr(i,1)) && !isNaN(s.substr(i-1,1)))
471 			{
472 				if(Number(s.substr(i,1)) == Number(s.substr(i-1,1)) + 1)
473 				{
474 					tmpLength++;	
475 				}
476 				else
477 				{
478 					tmpLength = 1;
479 				}
480 				if(tmpLength > errorLength)
481 				{
482 					errorLength = tmpLength;
483 				}
484 			}
485 		}
486 		if(errorLength>=(s.length/2))
487 		{
488 			return false;
489 		}
490 		else
491 		{
492 			return true;
493 		}
494 	}
495 	PasswordStrength.prototype.descendingPwd = function(s)
496 	{
497 		var errorLength = 0;
498 		var tmpLength = 1;
499 		var i = 0;
500 		for(i=1;i<s.length;i++)
501 		{
502 			if(!isNaN(s.substr(i,1)) && !isNaN(s.substr(i-1,1)))
503 			{
504 				if(Number(s.substr(i,1)) == Number(s.substr(i-1,1)) - 1)
505 				{
506 					tmpLength++;	
507 				}
508 				else
509 				{
510 					tmpLength = 1;
511 				}
512 				if(tmpLength > errorLength)
513 				{
514 					errorLength = tmpLength;
515 				}
516 			}
517 		}
518 		if(errorLength>=(s.length/2))
519 		{
520 			return false;
521 		}
522 		else
523 		{
524 			return true;
525 		}
526 	}
527 
528 	var instance = new PasswordStrength();
529 	$we.pwdStrength = function(pwd, username) {
530 		instance.setUserName(username || "");
531 		var level = instance.getLevel(pwd);
532 		return {
533 			errno: parseInt(instance.errorId),
534 			level: parseInt(level),
535 			msg: instance.Description || ""
536 		};
537 	};
538 
539 	return $we.pwdStrength;
540 });
541