r58178 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r58177‎ | r58178 | r58179 >
Date:02:41, 27 October 2009
Author:skizzerz
Status:deferred
Tags:
Comment:
* update to version 2.0
Modified paths:
  • /trunk/extensions/SecurePasswords/SecurePasswords.i18n.php (modified) (history)
  • /trunk/extensions/SecurePasswords/SecurePasswords.php (modified) (history)

Diff [purge]

Index: trunk/extensions/SecurePasswords/SecurePasswords.i18n.php
@@ -10,6 +10,7 @@
1111 */
1212 $messages['en'] = array(
1313 'securepasswords-desc' => 'Creates more secure password hashes and adds a password strength checker',
 14+ 'securepasswords-invalid' => '', #dummy value, never shown to user, DO NOT TRANSLATE
1415 'securepasswords-valid' => 'Your password is invalid or too short.
1516 It must:',
1617 'securepasswords-minlength' => 'be at least $1 {{PLURAL:$1|character|characters}} long',
Index: trunk/extensions/SecurePasswords/SecurePasswords.php
@@ -20,15 +20,15 @@
2121 'usercheck' => true, #Should we disallow passwords that are the same as the username?
2222 'wordcheck' => function_exists( 'pspell_check' ), #Should we check the password against a dictionary to make sure that it is not a word?
2323 );
24 -
2524 $wgSecurePasswordSpecialChars = '.|\/!@#$%^&*\(\)-_=+\[\]{}`~,<>?\'";: '; # Character class of special characters for a regex
 25+$wgSecurePasswordsSecretKeys = array(false, false, false); #MUST be customized in LocalSettings.php!
2626
2727 $wgExtensionCredits['other'][] = array(
2828 'path' => __FILE__,
2929 'name' => 'SecurePasswords',
3030 'author' => 'Ryan Schmidt',
3131 'url' => 'http://www.mediawiki.org/wiki/Extension:SecurePasswords',
32 - 'version' => '1.1.1',
 32+ 'version' => '2.0',
3333 'description' => 'Creates more secure password hashes and adds a password strength checker',
3434 'descriptionmsg' => 'securepasswords-desc',
3535 );
@@ -40,100 +40,71 @@
4141 $wgHooks['isValidPassword'][] = 'efSecurePasswordsValidate'; //used to enforce password strength
4242 $wgHooks['NormalizeMessageKey'][] = 'efSecurePasswordsMessage'; //used to override the message to show what the password requirements are
4343
 44+//new method
4445 function efSecurePasswordsCrypt( &$password, &$salt, &$wgPasswordSalt, &$hash ) {
45 - $hash = 'SP:';
 46+ global $wgSecurePasswordsSecretKeys, $wgUser;
 47+ if($wgSecurePasswordsSecretKeys == array(false, false, false)) {
 48+ die('You need to customize $wgSecurePasswordsSecretKeys in your LocalSettings.php file.
 49+ See http://www.mediawiki.org/wiki/Extension:SecurePasswords for more information');
 50+ }
 51+ $hash = 'S2:';
4652
4753 if( $salt === false ) {
4854 $salt = substr( wfGenerateToken(), 0, 8 );
4955 }
 56+ $a = efSecurePasswordsHashOrder( $wgUser->getId() );
5057
51 - $h = function_exists( 'hash' );
52 - $m = function_exists( 'mcrypt_encrypt' );
53 - $g = function_exists( 'gzcompress' );
54 -
55 - if( $h ) {
56 - $hash_algos = hash_algos();
57 - $algos = array();
58 - foreach( $hash_algos as $algo ) {
59 - switch( $algo ) {
60 - case 'md5':
61 - $algos[] = array( 'A', 'md5' );
62 - break;
63 - case 'sha1':
64 - $algos[] = array( 'B', 'sha1' );
65 - break;
66 - case 'sha512':
67 - $algos[] = array( 'C', 'sha512' );
68 - break;
69 - case 'ripemd160':
70 - $algos[] = array( 'D', 'ripemd160' );
71 - break;
72 - case 'ripemd320':
73 - $algos[] = array( 'E', 'ripemd320' );
74 - break;
75 - case 'whirlpool':
76 - $algos[] = array( 'F', 'whirlpool' );
77 - break;
78 - case 'tiger192,4':
79 - $algos[] = array( 'G', 'tiger192,4' );
80 - break;
81 - case 'snefru':
82 - $algos[] = array( 'H', 'snefru' );
83 - break;
84 - case 'gost':
85 - $algos[] = array( 'I', 'gost' );
86 - break;
87 - case 'haval256,5':
88 - $algos[] = array( 'J', 'haval256,5' );
89 - break;
90 - }
 58+ $hash_algos = hash_algos();
 59+ $algos = array();
 60+ //only use algorithms deemed "secure"
 61+ foreach( $hash_algos as $algo ) {
 62+ switch( $algo ) {
 63+ case 'sha512':
 64+ $algos[] = array( $a[0], 'sha512' );
 65+ break;
 66+ case 'ripemd160':
 67+ $algos[] = array( $a[1], 'ripemd160' );
 68+ break;
 69+ case 'ripemd320':
 70+ $algos[] = array( $a[2], 'ripemd320' );
 71+ break;
 72+ case 'whirlpool':
 73+ $algos[] = array( $a[3], 'whirlpool' );
 74+ break;
 75+ case 'gost':
 76+ $algos[] = array( $a[4], 'gost' );
 77+ break;
 78+ case 'tiger192,4':
 79+ $algos[] = array( $a[5], 'tiger192,4' );
 80+ break;
 81+ case 'haval256,5':
 82+ $algos[] = array( $a[6], 'haval256,5' );
 83+ break;
 84+ case 'sha256':
 85+ $algos[] = array( $a[7], 'sha256' );
 86+ break;
 87+ case 'sha384':
 88+ $algos[] = array( $a[8], 'sha384' );
 89+ break;
 90+ case 'ripemd128':
 91+ $algos[] = array( $a[9], 'ripemd128' );
 92+ break;
 93+ case 'ripemd256':
 94+ $algos[] = array( $a[10], 'ripemd256' );
 95+ break;
9196 }
92 -
93 - $r1 = rand( 0, count( $algos ) - 1 );
94 - $r2 = rand( 0, count( $algos ) - 1 );
95 - } else {
96 - $algos = array( array( 'A', 'md5' ), array( 'B', 'sha1' ) );
97 - $r1 = rand( 0, 1 );
98 - $r2 = rand( 0, 1 );
9997 }
10098
 99+ $r1 = rand( 0, count( $algos ) - 1 );
 100+ $r2 = rand( 0, count( $algos ) - 1 );
101101 $type = $algos[$r1][0] . $algos[$r2][0];
102 -
103 - if( $h ) {
104 - $pw1 = hash( $algos[$r1][1], $password );
105 - $pw2 = hash( $algos[$r2][1], $salt . '-' . $pw1 );
106 - } else {
107 - if( $r1 === 0 ) {
108 - $pw1 = md5( $password );
109 - } else {
110 - $pw1 = sha1( $password );
111 - }
112 -
113 - if( $r2 === 0 ) {
114 - $pw2 = md5( $salt . '-' . $pw1 );
115 - } else {
116 - $pw2 = sha1( $salt . '-' . $pw1 );
117 - }
118 - }
119 -
120 - if( $m ) {
121 - global $wgSecretKey;
122 - $size = mcrypt_get_iv_size( MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC );
123 - $ksize = mcrypt_get_key_size( MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC );
124 - $iv = mcrypt_create_iv( $size, MCRYPT_RAND );
125 - $key = substr( $wgSecretKey, 0, $ksize - 1 );
126 - $pw3 = mcrypt_encrypt( MCRYPT_RIJNDAEL_256, $key, $pw2, MCRYPT_MODE_CBC, $iv );
127 - $pw4 = base64_encode( $pw3 ) . '|' . base64_encode( $iv );
128 - } else {
129 - $pw4 = $pw2;
130 - }
131 -
132 - if( $g ) {
133 - $pwf = base64_encode( gzcompress( $pw4 ) );
134 - } else {
135 - $pwf = base64_encode( $pw4 );
136 - }
137 -
 102+ $pw1 = hash_hmac( $algos[$r2][1], $salt . '-' . hash_hmac( $algos[$r1][1], $password, $wgSecurePasswordsSecretKeys[0] ), $wgSecurePasswordsSecretKeys[1] );
 103+ $size = mcrypt_get_iv_size( MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC );
 104+ $ksize = mcrypt_get_key_size( MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC );
 105+ $iv = mcrypt_create_iv( $size, MCRYPT_RAND );
 106+ $key = substr( $wgSecurePasswordsSecretKeys[2], 0, $ksize - 1 );
 107+ $pw2 = mcrypt_encrypt( MCRYPT_RIJNDAEL_256, $key, $pw1, MCRYPT_MODE_CBC, $iv );
 108+ $pwf = base64_encode( gzcompress( base64_encode( $pw2 ) . '|' . base64_encode( $iv ) ) );
138109 $hash .= $type . ':' . $salt . ':' . $pwf;
139110
140111 // sometimes the mcrypt is invalid, so we need to do a quick check to make sure that comparing will work in the future
@@ -144,8 +115,52 @@
145116 }
146117
147118 function efSecurePasswordsCompare( &$hash, &$password, &$userId, &$result ) {
 119+ global $wgSecurePasswordsSecretKeys;
148120 $bits = explode( ':', $hash, 4 );
 121+ if( $bits[0] == 'SP' && count( $bits ) == 4 ) {
 122+ //old check
 123+ return efSecurePasswordsOldCompare( $hash, $password, $userId, $result );
 124+ } elseif( $bits[0] != 'S2' || count( $bits ) != 4 ) {
 125+ //must be a default hash or something, let mw handle it
 126+ return true;
 127+ }
149128
 129+ $type1 = substr( $bits[1], 0, 1 );
 130+ $type2 = substr( $bits[1], 1, 1 );
 131+ $salt = $bits[2];
 132+ $hash1 = $bits[3];
 133+
 134+ $a = efSecurePasswordsHashOrder( $userId );
 135+ $algos = array(
 136+ $a[0] => 'sha512',
 137+ $a[1] => 'ripemd160',
 138+ $a[2] => 'ripemd320',
 139+ $a[3] => 'whirlpool',
 140+ $a[4] => 'gost',
 141+ $a[5] => 'tiger192,4',
 142+ $a[6] => 'haval256,5',
 143+ $a[7] => 'sha256',
 144+ $a[8] => 'sha384',
 145+ $a[9] => 'ripemd128',
 146+ $a[10] => 'ripemd256',
 147+ );
 148+ $pw = hash_hmac( $algos[$type2], $salt . '-' . hash_hmac( $algos[$type1], $password, $wgSecurePasswordsSecretKeys[0] ), $wgSecurePasswordsSecretKeys[1] );
 149+ $h1 = gzuncompress( base64_decode( $hash1 ) );
 150+ $ksize = mcrypt_get_key_size( MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC );
 151+ $key = substr( $wgSecurePasswordsSecretKeys[2], 0, $ksize - 1 );
 152+ $bits = explode( '|', $h1 );
 153+ $iv = base64_decode( $bits[1] );
 154+ $h2 = base64_decode( $bits[0] );
 155+ $hf = mcrypt_decrypt( MCRYPT_RIJNDAEL_256, $key, $h2, MCRYPT_MODE_CBC, $iv );
 156+
 157+ $result = ( $pw === $hf );
 158+ return false;
 159+}
 160+
 161+//does an old compare used in version 1.x of SecurePasswords
 162+function efSecurePasswordsOldCompare( &$hash, &$password, &$userId, &$result ) {
 163+ $bits = explode( ':', $hash, 4 );
 164+
150165 if( $bits[0] != 'SP' || count( $bits ) != 4 ) {
151166 return true;
152167 }
@@ -155,7 +170,6 @@
156171 $salt = $bits[2];
157172 $hash1 = $bits[3];
158173
159 - $h = function_exists( 'hash' );
160174 $m = function_exists( 'mcrypt_encrypt' );
161175 $g = function_exists( 'gzcompress' );
162176
@@ -172,22 +186,8 @@
173187 'J' => 'haval256,5',
174188 );
175189
176 - if( $h ) {
177 - $pw1 = hash( $algos[$type1], $password );
178 - $pwf = hash( $algos[$type2], $salt . '-' . $pw1 );
179 - } else {
180 - if( $r1 === 0 ) {
181 - $pw1 = md5( $password );
182 - } else {
183 - $pw1 = sha1( $password );
184 - }
185 -
186 - if( $r2 === 0 ) {
187 - $pwf = md5( $salt . '-' . $pw1 );
188 - } else {
189 - $pwf = sha1( $salt . '-' . $pw1 );
190 - }
191 - }
 190+ $pw1 = hash( $algos[$type1], $password );
 191+ $pwf = hash( $algos[$type2], $salt . '-' . $pw1 );
192192
193193 if( $g ) {
194194 $h1 = gzuncompress( base64_decode( $hash1 ) );
@@ -208,6 +208,7 @@
209209 }
210210
211211 $result = ($hf === $pwf);
 212+
212213 return false;
213214 }
214215
@@ -236,57 +237,57 @@
237238
238239 global $wgValidPasswords, $wgContLang, $wgSecurePasswordsSpecialChars;
239240 $lang = $wgContLang->getPreferredVariant( false );
240 -
 241+ wfLoadExtensionMessages( 'SecurePasswords' );
241242 // check password length
242243 if( strlen( $password ) < $wgValidPasswords['minlength'] ) {
243 - $result = 'securepasswords-valid';
 244+ $result = 'securepasswords-invalid';
244245 return false;
245246 }
246247
247248 // check for a lowercase letter, if needed
248249 if( $wgValidPasswords['lowercase'] && !preg_match( '/[a-z]/', $password ) ) {
249 - $result = 'securepasswords-lowercase';
 250+ $result = 'securepasswords-invalid';
250251 return false;
251252 }
252253
253254 // check for an uppercase letter, if needed
254255 if( $wgValidPasswords['uppercase'] && !preg_match( '/[A-Z]/', $password ) ) {
255 - $result = 'securepasswords-uppercase';
 256+ $result = 'securepasswords-invalid';
256257 return false;
257258 }
258259
259260 // check for a digit, if needed
260261 if( $wgValidPasswords['digit'] && !preg_match( '/[0-9]/', $password ) ) {
261 - $result = 'securepasswords-digit';
 262+ $result = 'securepasswords-invalid';
262263 return false;
263264 }
264265
265266 // check for a special character, if needed
266267 if( $wgValidPasswords['special'] && !preg_match( '/[' . $wgSecurePasswordsSpecialChars . ']/', $password ) ) {
267 - $result = 'securepasswords-special';
 268+ $result = 'securepasswords-invalid';
268269 return false;
269270 }
270271
271272 // check for the username, if needed
272273 if( $wgValidPasswords['usercheck'] && $wgContLang->lc( $password ) == $wgContLang->lc( $user->getName() ) ) {
273 - $result = 'securepasswords-username';
 274+ $result = 'securepasswords-invalid';
274275 return false;
275276 }
276277
277278 // check for words, if needed
278279 if( $wgValidPasswords['wordcheck'] && function_exists( 'pspell_check' ) ) {
279 - $link = pspell_new( $lang );
 280+ $link = pspell_new( $lang, '', '', ( PSPELL_FAST | PSPELL_RUN_TOGETHER ) );
280281 if( $link ) {
281282 if( pspell_check( $link, $password ) ) {
282 - $result = 'securepasswords-word';
 283+ $result = 'securepasswords-invalid';
283284 return false;
284285 }
285286 }
286287 if( $lang != 'en' ) {
287 - $link = pspell_new( 'en' );
 288+ $link = pspell_new( 'en', '', '', ( PSPELL_FAST | PSPELL_RUN_TOGETHER ) );
288289 if( $link ) {
289290 if( pspell_check( $link, $password ) ) {
290 - $result = 'securepasswords-word';
 291+ $result = 'securepasswords-invalid';
291292 return false;
292293 }
293294 }
@@ -298,13 +299,13 @@
299300 }
300301
301302 function efSecurePasswordsMessage( &$key, &$useDB, &$langCode, &$transform ) {
 303+ global $wgTitle, $wgMessageCache, $wgValidPasswords, $wgUser;
302304 // do we have the right key?
303 - if( $key != 'passwordtooshort' ) {
 305+ if( $key != 'securepasswords-invalid' ) {
304306 return true;
305307 }
306308
307309 // don't replace the message if we're viewing Special:AllMessages
308 - global $wgTitle, $wgMessageCache, $wgValidPasswords;
309310 if( is_object( $wgTitle ) && $wgTitle instanceOf Title ) {
310311 $page = $wgTitle->getText();
311312 $ns = $wgTitle->getNamespace();
@@ -315,7 +316,7 @@
316317
317318 if( $ns === NS_SPECIAL ) {
318319 list( $title, $sp ) = SpecialPage::resolveAliasWithSubpage( $page );
319 - if( $title == 'AllMessages' ) {
 320+ if( strtolower( $title ) == 'allmessages' ) {
320321 return true;
321322 }
322323 }
@@ -328,27 +329,73 @@
329330 }
330331 wfLoadExtensionMessages('SecurePasswords');
331332 $key = 'securepasswords-password';
332 - $msg = wfMsg( 'securepasswords-valid' ) . ' ';
 333+ $msg = wfMsg( 'securepasswords-valid' ) . "\n* ";
333334 $msg .= wfMsgExt( 'securepasswords-minlength', array( 'parsemag' ), $wgValidPasswords['minlength'] );
334335 if( $wgValidPasswords['lowercase'] ) {
335 - $msg .= ', ' . wfMsg( 'securepasswords-lowercase' );
 336+ $msg .= "\n* " . wfMsg( 'securepasswords-lowercase' );
336337 }
337338 if( $wgValidPasswords['uppercase'] ) {
338 - $msg .= ', ' . wfMsg( 'securepasswords-uppercase' );
 339+ $msg .= "\n* " . wfMsg( 'securepasswords-uppercase' );
339340 }
340341 if( $wgValidPasswords['digit'] ) {
341 - $msg .= ', ' . wfMsg( 'securepasswords-digit' );
 342+ $msg .= "\n* " . wfMsg( 'securepasswords-digit' );
342343 }
343344 if( $wgValidPasswords['special'] ) {
344 - $msg .= ', ' . wfMsg( 'securepasswords-special', str_replace( '\\', '', $wgSecurePasswordsSpecialChars ) );
 345+ $msg .= "\n* " . wfMsg( 'securepasswords-special', str_replace( '\\', '', $wgSecurePasswordsSpecialChars ) );
345346 }
346347 if( $wgValidPasswords['usercheck'] ) {
347 - $msg .= ', ' . wfMsgExt( 'securepasswords-username', array( 'parsemag' ), $user->getName() );
 348+ $msg .= "\n* " . wfMsgExt( 'securepasswords-username', array( 'parsemag' ), $wgUser->getName() );
348349 }
349350 if( $wgValidPasswords['wordcheck'] ) {
350 - $msg .= ', ' . wfMsg( 'securepasswords-word' );
 351+ $msg .= "\n* " . wfMsg( 'securepasswords-word' );
351352 }
352353 $wgMessageCache->addMessage( 'securepasswords-password', $msg, 'en' );
353354
354355 return true;
355356 }
 357+
 358+function efSecurePasswordsHashOrder( $userid ) {
 359+ if( $userid > 999999 ) {
 360+ $userid = substr( $userid, 0, 6 );
 361+ }
 362+ $o = floor( ( ( $userid * 3 ) + 513829 ) / 5 ) + 925487; #just two random prime numbers with no significance attached
 363+ $s = str_split( $o );
 364+ $r = array();
 365+ $f = false;
 366+ $n = 64;
 367+ //in case it is impossible to get all values 65-90 from the above values, we break after 1000 iterations
 368+ for($i = 0; $i < 1000; $i++) {
 369+ $n += $s[0];
 370+ if( $n < 65 ) $n = 65;
 371+ elseif( $n > 90 ) $n = 65 + $s[0];
 372+ if( !in_array( chr( $n ), $r ) ) $r[] = chr( $n );
 373+ $n -= $s[1];
 374+ if( $n < 65 ) $n = 65;
 375+ elseif( $n > 90 ) $n = 90 - $s[0];
 376+ if( !in_array( chr( $n ), $r ) ) $r[] = chr( $n );
 377+ $n += 2 * $s[2];
 378+ if( $n < 65 ) $n = 65;
 379+ elseif( $n > 90 ) $n = 65 + $s[0];
 380+ if( !in_array( chr( $n ), $r ) ) $r[] = chr( $n );
 381+ $n -= 2 * $s[3];
 382+ if( $n < 65 ) $n = 65;
 383+ elseif( $n > 90 ) $n = 90 - $s[0];
 384+ if( !in_array( chr( $n ), $r ) ) $r[] = chr( $n );
 385+ $n += 3 * $s[4];
 386+ if( $n < 65 ) $n = 65;
 387+ elseif( $n > 90 ) $n = 65 + $s[0];
 388+ if( !in_array( chr( $n ), $r ) ) $r[] = chr( $n );
 389+ $n -= 3 * $s[5];
 390+ if( $n < 65 ) $n = 65;
 391+ elseif( $n > 90 ) $n = 90 - $s[0];
 392+ if( !in_array( chr( $n ), $r ) ) $r[] = chr( $n );
 393+ if( count( $r ) == 26 ) {
 394+ $f = true;
 395+ break;
 396+ }
 397+ }
 398+ if( !$f ) {
 399+ return array( 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' );
 400+ }
 401+ return $r;
 402+}
\ No newline at end of file

Follow-up revisions

RevisionCommit summaryAuthorDate
r58180Follow-up r58178: Ignore new message for translatewikiraymond05:38, 27 October 2009

Status & tagging log