Jump to content
Sign in to follow this  
Kevin

[AS2] Sweater - Implement bcrypt

Recommended Posts

Hey everyone, it's been a long time.

Today I implemented bcrypt in sweater and I decided i'd teach you how to do it without downloading my version of sweater directly, because who knows ? maybe you have your own version and don't want to replace the whole files.

I'll show you how to update everyone's passwords too, so no worries about that. (thanks to @Ben who helped me fix a bug so-to-speak).

 

Alright, let's begin with what's pretty obvious, the Hashing file (Cryptography.php). What you have to do is, replace everything in there with this:

Spoiler
<?php

namespace Sweater\Crypto;

trait Cryptography {
	
	private $strCharacterSet = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM0123456789`[email protected]#$()_+-={}|[]:,.";

	function generateRandomKey() {
		$strKeyLength = mt_rand(7, 10);
		$strRandomKey = "";

		foreach(range(0, $strKeyLength) as $strCurrentLength) {
			$strRandomKey .= substr($this->strCharacterSet, mt_rand(0, strlen($this->strCharacterSet)), 1);
		}

		return $strRandomKey;
	}

	function encryptPassword($strPassword, $strMD5 = true) {
		if($strMD5 !== false) {
			$strPassword = md5($strPassword);
		}

		$strHash = substr($strPassword, 16, 16) . substr($strPassword, 0, 16);
		return $strHash;
	}

	function getLoginHash($strPassword, $strRandomKey) {
		$strHash = $this->encryptPassword($strPassword, false);
		$strHash .= $strRandomKey;
		$strHash .= 'Y(02.>\'H}t":E1';
		$strHash = $this->encryptPassword($strHash);

		return $strHash;
	}
	
}

?>

Yeah I know, it looks like kitsune's now :)

Next, you probably want to set this in Client.php to public:

private $strRandomKey;

Change that to public.

Now, since I don't think any of you have modified the login handler in sweater, or if you did then you're competent enough to see what I modified in the handleLogin and replace the stuff.

If you didn't modify it, replace this whole function with yours in LoginHandler.php:

Spoiler
function handleLogin($arrData, Client $objClient){
		$strUser = $arrData['body']['login']['nick'];
		$strPass = $arrData['body']['login']['pword'];
		Silk\Logger::Log('Client is attempting to login with username \'' . $strUser . '\'');
		$blnExist = $this->objDatabase->playerExists($strUser);
		if($blnExist === false){
			$objClient->sendError(100);
			return $this->removeClient($objClient->resSocket);
		}
		$arrUser = $this->objDatabase->getRow($strUser);
		$intUser = $arrUser['ID'];
		$strPassword = $arrUser['Password'];
		$strRandom = $objClient->getRandomKey();
		if($this->arrServer['Type'] == 'Login'){
			Silk\Logger::Log('Handling login hashing', 'DEBUG');
			$strUppedPass = strtoupper($strPassword);
			$strEncrypt = $strUppedPass;
			if(password_verify($strPass, $strPassword) !== true) {
				Silk\Logger::Log('Failed login attempt for user \'' . $strUser . '\'', Silk\Logger::Debug);
				$objClient->sendError(101);
				$this->removeClient($objClient->resSocket);
			}elseif($arrUser['ID'] > 99999999999){
				Silk\Logger::Log('Failed login attempt for user \'' . $strUser . '\'', Silk\Logger::Debug);
				$objClient->sendError(101);
				$this->removeClient($objClient->resSocket);
			}else {
				$objClient->sendXt('sd', -1, $this->getServers());
				$strHash = md5(strrev($objClient->strRandomKey));
				Silk\Logger::Log('Random string: ' . $strHash);
				$objClient->arrBuddies = json_decode($arrUser['Buddies'], true);
				$strServers = $this->objDatabase->getServerPopulation();
				$intBuddiesOnline = $this->objDatabase->getOnlineBuddiesCount($objClient);
				$objClient->sendXt('l', -1, $intUser, $strHash, '', $strServers);
				$this->objDatabase->updateColumn($intUser, 'LoginKey', $strHash);
				$this->removeClient($objClient->resSocket);
				Silk\Logger::Log('User \'' . $strUser . '\' has successfully logged in!', Silk\Logger::Debug);
			}
		} else {
			Silk\Logger::Log('Handling game hashing', Silk\Logger::Debug);
			$strLoginKey = $this->objDatabase->getLoginKey($intUser);
			$strHash = $this->encryptPassword($strLoginKey . $objClient->strRandomKey) . $strLoginKey;
			if($strHash != $strLoginKey){
				$objClient->sendXt('f#lb', -1, $strHash);
				$objClient->sendXt('l', -1);
				$objClient->setClient($arrUser);
				$this->updateStats();
			} else {
				$objClient->sendError(101);
				$this->removeClient($objClient->resSocket);
			}
		}
	}

Replace the handleRndK function too with this:

function handleRndK($arrData, Client $objClient){
	$objClient->strRandomKey = "e4a2dbcca10a7246817a83cd" . $objClient->strNickname;
	$objClient->sendData('<msg t="sys"><body action="rndK" r="-1"><k>' . $objClient->strRandomKey . '</k></body></msg>'); 
	$objClient->setRandomKey($objClient->strRandomKey);
}

You will also want to change your password column so our new password hash can fit in the column, so just run this SQL command:

ALTER TABLE `users` CHANGE `Password` `Password` VARCHAR(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL COMMENT 'Password hash';

Now you probably want to update everyone's md5 password to bcrypt, so you can simply run this PHP script:

Spoiler
<?php

//Change this to conform to your database installation !
$config = array(
  "Host" => "localhost",
  "Name" => "sweater",
  "User" => "root",
  "Pass" => ""
);	

$con = new PDO('mysql:host='. $config["Host"] . ';dbname=' . $config["Name"], $config["User"], $config["Pass"]);

$passwords = $con->prepare('SELECT Password, ID FROM users');
$passwords->execute();
$getPasswords = $passwords->fetchAll();

foreach($getPasswords as $password){
    $hashedPassword = strtoupper($password["Password"]);
    $userId = $password["ID"];
    $staticKey = 'e4a2dbcca10a7246817a83cd';
    $fancyPassword = getLoginHash($hashedPassword, $staticKey);
    
    $updatePasswords = $con->prepare('UPDATE users SET Password = :password WHERE ID = ' . $userId);
    $updatePasswords->bindValue(':password', $fancyPassword);
    $updatePasswords->execute();
}

function encryptPassword($password, $md5 = true) {
    if($md5 !== false) {
        $password = md5($password);
    }

    $hash = substr($password, 16, 16) . substr($password, 0, 16);
    return $hash;
}

function getLoginHash($password, $staticKey) {
    $hash = encryptPassword($password, false);
    $hash .= $staticKey;
    $hash .= 'Y(02.>\'H}t":E1';
    $hash = encryptPassword($hash);
    $hash = password_hash($hash, PASSWORD_DEFAULT, [ 'cost' => 12 ]);

    return $hash;
}


?>

 

For the register, since you are using sweater you are probably using my 'old' register, so use this new one.

Once you've completed all of these, you're done. Hope this helped you. Cya.

  • Like 7

Share this post


Link to post
Share on other sites

Nice work. Hopefully this'll encourage more CPPS owners to switch over to bcrypt, or at the very least a stronger hashing algorithm for passwords. 

  • Like 1

Share this post


Link to post
Share on other sites
Sign in to follow this  

×