1<?php 2/* 3Copyright (c) 2002-2010, Michael Bretterklieber <michael@bretterklieber.com> 4All rights reserved. 5 6Redistribution and use in source and binary forms, with or without 7modification, are permitted provided that the following conditions 8are met: 9 101. Redistributions of source code must retain the above copyright 11 notice, this list of conditions and the following disclaimer. 122. Redistributions in binary form must reproduce the above copyright 13 notice, this list of conditions and the following disclaimer in the 14 documentation and/or other materials provided with the distribution. 153. The names of the authors may not be used to endorse or promote products 16 derived from this software without specific prior written permission. 17 18THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 22INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 25OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 29This code cannot simply be copied and put under the GNU Public License or 30any other GPL-like (LGPL, GPL2) License. 31 32 $Id: CHAP.php 302857 2010-08-28 21:12:59Z mbretter $ 33*/ 34 35require_once 'PEAR.php'; 36 37/** 38* Classes for generating packets for various CHAP Protocols: 39* CHAP-MD5: RFC1994 40* MS-CHAPv1: RFC2433 41* MS-CHAPv2: RFC2759 42* 43* @package Crypt_CHAP 44* @author Michael Bretterklieber <michael@bretterklieber.com> 45* @access public 46* @version $Revision: 302857 $ 47*/ 48 49/** 50 * class Crypt_CHAP 51 * 52 * Abstract base class for CHAP 53 * 54 * @package Crypt_CHAP 55 */ 56class Crypt_CHAP extends PEAR 57{ 58 /** 59 * Random binary challenge 60 * @var string 61 */ 62 var $challenge = null; 63 64 /** 65 * Binary response 66 * @var string 67 */ 68 var $response = null; 69 70 /** 71 * User password 72 * @var string 73 */ 74 var $password = null; 75 76 /** 77 * Id of the authentication request. Should incremented after every request. 78 * @var integer 79 */ 80 var $chapid = 1; 81 82 /** 83 * Constructor 84 * 85 * Generates a random challenge 86 * @return void 87 */ 88 function __construct() 89 { 90 //$this->PEAR(); 91 $this->generateChallenge(); 92 } 93 94 /** 95 * Generates a random binary challenge 96 * 97 * @param string $varname Name of the property 98 * @param integer $size Size of the challenge in Bytes 99 * @return void 100 */ 101 function generateChallenge($varname = 'challenge', $size = 8) 102 { 103 $this->$varname = ''; 104 for ($i = 0; $i < $size; $i++) { 105 $this->$varname .= pack('C', 1 + mt_rand() % 255); 106 } 107 return $this->$varname; 108 } 109 110 /** 111 * Generates the response. Overwrite this. 112 * 113 * @return void 114 */ 115 function challengeResponse() 116 { 117 } 118 119} 120 121/** 122 * class Crypt_CHAP_MD5 123 * 124 * Generate CHAP-MD5 Packets 125 * 126 * @package Crypt_CHAP 127 */ 128class Crypt_CHAP_MD5 extends Crypt_CHAP 129{ 130 131 /** 132 * Generates the response. 133 * 134 * CHAP-MD5 uses MD5-Hash for generating the response. The Hash consists 135 * of the chapid, the plaintext password and the challenge. 136 * 137 * @return string 138 */ 139 function challengeResponse() 140 { 141 return pack('H*', md5(pack('C', $this->chapid) . $this->password . $this->challenge)); 142 } 143} 144 145/** 146 * class Crypt_CHAP_MSv1 147 * 148 * Generate MS-CHAPv1 Packets. MS-CHAP doesen't use the plaintext password, it uses the 149 * NT-HASH wich is stored in the SAM-Database or in the smbpasswd, if you are using samba. 150 * The NT-HASH is MD4(str2unicode(plaintextpass)). 151 * You need the hash extension for this class. 152 * 153 * @package Crypt_CHAP 154 */ 155class Crypt_CHAP_MSv1 extends Crypt_CHAP 156{ 157 /** 158 * Wether using deprecated LM-Responses or not. 159 * 0 = use LM-Response, 1 = use NT-Response 160 * @var bool 161 */ 162 var $flags = 1; 163 164 /** 165 * Constructor 166 * 167 * Loads the hash extension 168 * @return void 169 */ 170 function __construct() 171 { 172 //$this->Crypt_CHAP(); 173 $this->loadExtension('hash'); 174 } 175 176 /** 177 * Generates the NT-HASH from the given plaintext password. 178 * 179 * @access public 180 * @return string 181 */ 182 function ntPasswordHash($password = null) 183 { 184 //if (isset($password)) { 185 if (!is_null($password)) { 186 return pack('H*',hash('md4', $this->str2unicode($password))); 187 } else { 188 return pack('H*',hash('md4', $this->str2unicode($this->password))); 189 } 190 } 191 192 /** 193 * Converts ascii to unicode. 194 * 195 * @access public 196 * @return string 197 */ 198 function str2unicode($str) 199 { 200 $uni = ''; 201 $str = (string) $str; 202 for ($i = 0; $i < strlen($str); $i++) { 203 $a = ord($str{$i}) << 8; 204 $uni .= sprintf("%X", $a); 205 } 206 return pack('H*', $uni); 207 } 208 209 /** 210 * Generates the NT-Response. 211 * 212 * @access public 213 * @return string 214 */ 215 function challengeResponse() 216 { 217 return $this->_challengeResponse(); 218 } 219 220 /** 221 * Generates the NT-Response. 222 * 223 * @access public 224 * @return string 225 */ 226 function ntChallengeResponse() 227 { 228 return $this->_challengeResponse(false); 229 } 230 231 /** 232 * Generates the LAN-Manager-Response. 233 * 234 * @access public 235 * @return string 236 */ 237 function lmChallengeResponse() 238 { 239 return $this->_challengeResponse(true); 240 } 241 242 /** 243 * Generates the response. 244 * 245 * Generates the response using DES. 246 * 247 * @param bool $lm wether generating LAN-Manager-Response 248 * @access private 249 * @return string 250 */ 251 function _challengeResponse($lm = false) 252 { 253 if ($lm) { 254 $hash = $this->lmPasswordHash(); 255 } else { 256 $hash = $this->ntPasswordHash(); 257 } 258 259 $hash = str_pad($hash, 21, "\0"); 260 261 if (!extension_loaded('mcrypt') && extension_loaded('openssl')) { 262 // added openssl routines for dapphp/radius 263 $key = $this->_desAddParity(substr($hash, 0, 7)); 264 $resp1 = openssl_encrypt($this->challenge, 'des-ecb', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING); 265 266 $key = $this->_desAddParity(substr($hash, 7, 7)); 267 $resp2 = openssl_encrypt($this->challenge, 'des-ecb', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING); 268 269 $key = $this->_desAddParity(substr($hash, 14, 7)); 270 $resp3 = openssl_encrypt($this->challenge, 'des-ecb', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING); 271 } else { 272 $td = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_ECB, ''); 273 $iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND); 274 $key = $this->_desAddParity(substr($hash, 0, 7)); 275 mcrypt_generic_init($td, $key, $iv); 276 $resp1 = mcrypt_generic($td, $this->challenge); 277 mcrypt_generic_deinit($td); 278 279 $key = $this->_desAddParity(substr($hash, 7, 7)); 280 mcrypt_generic_init($td, $key, $iv); 281 $resp2 = mcrypt_generic($td, $this->challenge); 282 mcrypt_generic_deinit($td); 283 284 $key = $this->_desAddParity(substr($hash, 14, 7)); 285 mcrypt_generic_init($td, $key, $iv); 286 $resp3 = mcrypt_generic($td, $this->challenge); 287 mcrypt_generic_deinit($td); 288 mcrypt_module_close($td); 289 } 290 291 return $resp1 . $resp2 . $resp3; 292 } 293 294 /** 295 * Generates the LAN-Manager-HASH from the given plaintext password. 296 * 297 * @access public 298 * @return string 299 */ 300 function lmPasswordHash($password = null) 301 { 302 $plain = isset($password) ? $password : $this->password; 303 304 $plain = substr(strtoupper($plain), 0, 14); 305 while (strlen($plain) < 14) { 306 $plain .= "\0"; 307 } 308 309 return $this->_desHash(substr($plain, 0, 7)) . $this->_desHash(substr($plain, 7, 7)); 310 } 311 312 /** 313 * Generates an irreversible HASH. 314 * 315 * @access private 316 * @return string 317 */ 318 function _desHash($plain) 319 { 320 if (!extension_loaded('mcrypt') && extension_loaded('openssl')) { 321 // added openssl routines for dapphp/radius 322 $key = $this->_desAddParity($plain); 323 $hash = openssl_encrypt('KGS!@#$%', 'des-ecb', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING); 324 325 return $hash; 326 } else { 327 $key = $this->_desAddParity($plain); 328 $td = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_ECB, ''); 329 $iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND); 330 mcrypt_generic_init($td, $key, $iv); 331 $hash = mcrypt_generic($td, 'KGS!@#$%'); 332 mcrypt_generic_deinit($td); 333 mcrypt_module_close($td); 334 335 return $hash; 336 } 337 } 338 339 /** 340 * Adds the parity bit to the given DES key. 341 * 342 * @access private 343 * @param string $key 7-Bytes Key without parity 344 * @return string 345 */ 346 function _desAddParity($key) 347 { 348 static $odd_parity = array( 349 1, 1, 2, 2, 4, 4, 7, 7, 8, 8, 11, 11, 13, 13, 14, 14, 350 16, 16, 19, 19, 21, 21, 22, 22, 25, 25, 26, 26, 28, 28, 31, 31, 351 32, 32, 35, 35, 37, 37, 38, 38, 41, 41, 42, 42, 44, 44, 47, 47, 352 49, 49, 50, 50, 52, 52, 55, 55, 56, 56, 59, 59, 61, 61, 62, 62, 353 64, 64, 67, 67, 69, 69, 70, 70, 73, 73, 74, 74, 76, 76, 79, 79, 354 81, 81, 82, 82, 84, 84, 87, 87, 88, 88, 91, 91, 93, 93, 94, 94, 355 97, 97, 98, 98,100,100,103,103,104,104,107,107,109,109,110,110, 356 112,112,115,115,117,117,118,118,121,121,122,122,124,124,127,127, 357 128,128,131,131,133,133,134,134,137,137,138,138,140,140,143,143, 358 145,145,146,146,148,148,151,151,152,152,155,155,157,157,158,158, 359 161,161,162,162,164,164,167,167,168,168,171,171,173,173,174,174, 360 176,176,179,179,181,181,182,182,185,185,186,186,188,188,191,191, 361 193,193,194,194,196,196,199,199,200,200,203,203,205,205,206,206, 362 208,208,211,211,213,213,214,214,217,217,218,218,220,220,223,223, 363 224,224,227,227,229,229,230,230,233,233,234,234,236,236,239,239, 364 241,241,242,242,244,244,247,247,248,248,251,251,253,253,254,254); 365 366 $bin = ''; 367 for ($i = 0; $i < strlen($key); $i++) { 368 $bin .= sprintf('%08s', decbin(ord($key{$i}))); 369 } 370 371 $str1 = explode('-', substr(chunk_split($bin, 7, '-'), 0, -1)); 372 $x = ''; 373 foreach($str1 as $s) { 374 $x .= sprintf('%02s', dechex($odd_parity[bindec($s . '0')])); 375 } 376 377 return pack('H*', $x); 378 379 } 380 381 /** 382 * Generates the response-packet. 383 * 384 * @param bool $lm wether including LAN-Manager-Response 385 * @access private 386 * @return string 387 */ 388 function response($lm = false) 389 { 390 $ntresp = $this->ntChallengeResponse(); 391 if ($lm) { 392 $lmresp = $this->lmChallengeResponse(); 393 } else { 394 $lmresp = str_repeat ("\0", 24); 395 } 396 397 // Response: LM Response, NT Response, flags (0 = use LM Response, 1 = use NT Response) 398 return $lmresp . $ntresp . pack('C', !$lm); 399 } 400} 401 402/** 403 * class Crypt_CHAP_MSv2 404 * 405 * Generate MS-CHAPv2 Packets. This version of MS-CHAP uses a 16 Bytes authenticator 406 * challenge and a 16 Bytes peer Challenge. LAN-Manager responses no longer exists 407 * in this version. The challenge is already a SHA1 challenge hash of both challenges 408 * and of the username. 409 * 410 * @package Crypt_CHAP 411 */ 412class Crypt_CHAP_MSv2 extends Crypt_CHAP_MSv1 413{ 414 /** 415 * The username 416 * @var string 417 */ 418 var $username = null; 419 420 /** 421 * The 16 Bytes random binary peer challenge 422 * @var string 423 */ 424 var $peerChallenge = null; 425 426 /** 427 * The 16 Bytes random binary authenticator challenge 428 * @var string 429 */ 430 var $authChallenge = null; 431 432 /** 433 * Constructor 434 * 435 * Generates the 16 Bytes peer and authentication challenge 436 * @return void 437 */ 438 function __construct() 439 { 440 //$this->Crypt_CHAP_MSv1(); 441 $this->generateChallenge('peerChallenge', 16); 442 $this->generateChallenge('authChallenge', 16); 443 } 444 445 /** 446 * Generates a hash from the NT-HASH. 447 * 448 * @access public 449 * @param string $nthash The NT-HASH 450 * @return string 451 */ 452 function ntPasswordHashHash($nthash) 453 { 454 return pack('H*',hash('md4', $nthash)); 455 } 456 457 /** 458 * Generates the challenge hash from the peer and the authenticator challenge and 459 * the username. SHA1 is used for this, but only the first 8 Bytes are used. 460 * 461 * @access public 462 * @return string 463 */ 464 function challengeHash() 465 { 466 return substr(pack('H*',hash('sha1', $this->peerChallenge . $this->authChallenge . $this->username)), 0, 8); 467 } 468 469 /** 470 * Generates the response. 471 * 472 * @access public 473 * @return string 474 */ 475 function challengeResponse() 476 { 477 $this->challenge = $this->challengeHash(); 478 return $this->_challengeResponse(); 479 } 480} 481 482