Location: PHPKode > scripts > NuCaptcha (PHP) > nucaptcha-client-php-1.0.12882/nucaptcha-client-php-1.0.12882/php/leapsymmetric.php
<?php
/**
 * @package   NuCaptcha PHP clientlib
 * @author    <hide@address.com> Leap Marketing Technologies Inc
 * @license   LGPL License 2.1 (see included license.txt)
 * @link      http://www.nucaptcha.com/api/php
 */

// we only want to redefine the client version of this class if we haven't already defined the non-client version
if(class_exists("lmSymmetricMessage", false))
{
	return;
}

/**
 * class lmSymmetricMessage
 * This class is intended to create a secure transfer protocol that
 * is validated by a symmetric key.  Validity is assumed by knowledge
 * of the enciphering key.
 *
 * This class is intended to be a singleton.  The constructor/destructor
 * is used to ensure that resources are cleaned up properly.
 *
 * Security Features:
 * - It needs to have a timestamp internal to the data package
 * - It should pass a new symmetric key used for further communication
 */
class lmSymmetricMessage
{
	static private $m_METHODID	= 0;	// Method is for future .. default is 0
	static private $m_type		= 1;
	static private $m_PIPE		= '.';
	static private $m_magic		= "LEAPSM";				// Some magic data to prepend to the encrypted data to ensure validity

	/**
	 * Length of a valid base64 Encoded IV
	 *
	 * @var int
	 */
	static private $sIvLength = 24;

	static private $e_VERSION		= 0;
	static private $e_SENDERID		= 1;
	static private $e_KEYID			= 2;
	static private $e_METHOD		= 3;
	static private $e_IV			= 4;
	static private $e_DATA			= 5;

	/*
	 * Leap custom padding scheme. Similar to pkcs7 but padding is mid-message (after the magic).
	 * This is the default.
	 */
	const PADDING_LEAP  = 'leap';

	/* From PKCS #7: http://tools.ietf.org/html/rfc2315
	    2.   Some content-encryption algorithms assume the
             input length is a multiple of k octets, where k > 1, and
             let the application define a method for handling inputs
             whose lengths are not a multiple of k octets. For such
             algorithms, the method shall be to pad the input at the
             trailing end with k - (l mod k) octets all having value k -
             (l mod k), where l is the length of the input. In other
             words, the input is padded at the trailing end with one of
             the following strings:

                      01 -- if l mod k = k-1
                     02 02 -- if l mod k = k-2
                                 .
                                 .
                                 .
                   k k ... k k -- if l mod k = 0

             The padding can be removed unambiguously since all input is
             padded and no padding string is a suffix of another. This
             padding method is well-defined if and only if k < 256;
             methods for larger k are an open issue for further study.
	 *
	 * rlukashuk:
	 * Some encyption implementations require the padding to exist even if the original message is block aligned.
	 * Use this to force a padding at the end of message.
	 *
	 * Note: this is the same as pkcs5 but it is not restricted to a block size of 8
	 */
	const PADDING_PKCS7 = 'pkcs7';

	/*
	 * The default encryption method, used by most clientlibs
	 */
	const METHOD_AES128_CBC_LEAP = 0;

	/*
	 * Same as METHOD_AES128_CBC_LEAP but payload is run through gzdefalte before padding
	 */
	const METHOD_AES128_CBC_LEAP_GZ = 2;

	/*
	 * Use PKCS7 padding instead of LEAP padding
	 */
	const METHOD_AES128_CBC_PKCS7 = 1;

	// add other methods here ...


	/**
	 * PadPCKS7:
	 * See http://tools.ietf.org/html/rfc2315
	 *
	 * @param string $text
	 * @param int $blocksize
	 * @return string The input string padded out.  The output string is always larger than the input string
	 */
	static private function PadPKCS7 ($text, $blocksize)
	{
		$pad = $blocksize - (strlen($text) % $blocksize);
		return $text . str_repeat(chr($pad), $pad);
	}

	/**
	 * PadPCKS7:
	 * See http://tools.ietf.org/html/rfc2315
	 *
	 * @param string $text
	 * @return string The input with the padding removed or false on error.  The output string is always smaller than the input string.
	 */
	static private function UnpadPKCS7($text)
	{
		$pad = ord($text{strlen($text)-1});
		if ($pad > strlen($text)) return false;
		if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) return false;
		return substr($text, 0, -1 * $pad);
	}

	/**
	 * GetMethodDetails:
	 * Get specifics on the method mode
	 *
	 * @param int $method see method constants
	 * @param string $cipher out param
	 * @param string $mode out param
	 * @param string $padding out param
	 */
	static private function GetMethodDetails($method, &$cipher, &$mode, &$padding, &$compress)
	{
		switch($method)
		{
			case self::METHOD_AES128_CBC_LEAP:
				$cipher = MCRYPT_RIJNDAEL_128;
				$mode  = MCRYPT_MODE_CBC;
				$padding = self::PADDING_LEAP;
				$compress = false;
				break;

			case self::METHOD_AES128_CBC_LEAP_GZ:
				$cipher = MCRYPT_RIJNDAEL_128;
				$mode  = MCRYPT_MODE_CBC;
				$padding = self::PADDING_LEAP;
				$compress = true;
				break;

			case self::METHOD_AES128_CBC_PKCS7:
				$cipher = MCRYPT_RIJNDAEL_128;
				$mode  = MCRYPT_MODE_CBC;
				$padding = self::PADDING_PKCS7;
				$compress = false;
				break;

			default:
				throw new LeapException('Invalid method ' . $method, LMSC_INVALIDDATA);
		}
	}

	/**
	 * SymmetricEncipher:
	 * This will encipher a message and return it (as binary)
	 *
	 * @param binary $key
	 * @param binary $iv
	 * @param binary $message
	 * @param int $method See method constants
	 * @return binary
	 */
	static public function SymmetricEncipher($key, $iv, &$message, $method=self::METHOD_AES128_CBC_LEAP)
	{
		self::GetMethodDetails($method, $cipher, $mode, $padding, $compress);

		if( $compress )
		{
			$message = gzcompress($message, 5);
		}

		if( $padding == self::PADDING_LEAP )
		{
			// Figure out how much padding is required.  If full key size, don't pad.
			$keysize = (lmSymmetricMessage::GetKeySizeInBits($method) / 8);
			$carry   = ((strlen(lmSymmetricMessage::$m_magic) + 1 + strlen($message)) % $keysize);
			$padsize = ($keysize - $carry);		// Will be -1 .. Must +1 on decode

			// If no padding required
			if (0 == $carry)
			{
				// No extra padding required
				$msg = lmSymmetricMessage::$m_magic."0".$message;
			}
			else
			{
				// Add the padding
				$msg = lmSymmetricMessage::$m_magic.str_repeat(dechex($padsize), $padsize+1).$message;
			}

			// Encipher the data
			$enc = mcrypt_encrypt($cipher, $key, $msg, $mode, $iv);
			return $enc;
		}
		else if( $padding == self::PADDING_PKCS7 )
		{
			$msg = self::PadPKCS7(lmSymmetricMessage::$m_magic."0".$message, lmSymmetricMessage::GetKeySizeInBytes($method));
			$enc = mcrypt_encrypt($cipher, $key, $msg, $mode, $iv);
			return $enc;
		}

		throw new LeapException('Invalid padding method ' . $padding, LMSC_INVALIDDATA);
	}

	/**
	 * SymmetricDecipher:
	 * This will decipher a message and return it
	 *
	 * @param binary $key
	 * @param binary $iv
	 * @param binary $encmessage - the message
	 * @param bool $throw - whether or not to throw an exception on an error
	 * @param int $method - See method constants
	 * @return binary/bool - the deciphered message, or false if an error
	 */
	static public function SymmetricDecipher($key, $iv, &$encmessage, $throw=true, $method=self::METHOD_AES128_CBC_LEAP)
	{
		self::GetMethodDetails($method, $cipher, $mode, $padding, $compress);

		// Decipher the message
		$dec =  mcrypt_decrypt(	$cipher, $key, $encmessage, $mode, $iv);

		// Check to see that our magic is on the front
		$magic = substr($dec, 0, strlen(lmSymmetricMessage::$m_magic));
		if (0 != strcmp($magic, lmSymmetricMessage::$m_magic))
		{
			if (true === $throw)
			{
				throw new LeapException(
					sprintf(
						"Invalid key - could not decipher. Magic (%s) does not match expected (%s)",
						$magic,
						lmSymmetricMessage::$m_magic
					),
					LMSC_INVALIDKEY
				);
			}
			else return false;
		}

		if( $padding == self::PADDING_PKCS7 )
		{
			// remove additional pkcs7 padding
			$dec =  self::UnpadPKCS7($dec);
			if( false === $dec )
			{
				throw new LeapException('Error unpadding PKCS7', LMSC_INVALIDDATA);
			}
		}
		else if( $padding != self::PADDING_LEAP )
		{
			throw new LeapException('Invalid padding method ' . $padding, LMSC_INVALIDDATA);
		}

		// Get our padding size
		$pad = hexdec(substr($dec, strlen(lmSymmetricMessage::$m_magic), 1))+1;

		// Return the decoded data
		$res =  substr($dec, strlen(lmSymmetricMessage::$m_magic)+$pad);

		if( $compress )
		{
			$res = gzuncompress($res);
		}
		return $res;
	}

	/**
	 * This will generate an initialization vector.  This can be passed
	 * in the open with no loss to security.
	 *
	 * @return string (binary)
	 */
	static public function GenerateIV($method=self::METHOD_AES128_CBC_LEAP)
	{
		self::GetMethodDetails($method, $cipher, $mode, $padding, $compress);
		return self::MCryptRandom(mcrypt_get_iv_size($cipher, $mode));
	}

	/**
	 * Use mcrypte_create_iv to generate random data. Uses MCRYPT_DEV_URANDOM
	 * as a source
	 *
	 * @param int $size Size in bytes of random data to generate
	 * @return string
	 */
	static private function MCryptRandom($size)
	{
		lmGlobalPerformance::EnterSection('GenerateMcryptIV');

		// From http://ca2.php.net/manual/en/function.mcrypt-create-iv.php
		// Prior to 5.3.0, MCRYPT_RAND was the only one supported on Windows.
		if(false === lmHelper::checkWindowsVersion(50300))
		{
			$iv = mcrypt_create_iv($size, MCRYPT_RAND);
		}
		else
		{
			$iv = mcrypt_create_iv($size, MCRYPT_DEV_URANDOM);
		}
		lmGlobalPerformance::LeaveSection('GenerateMcryptIV');

		return $iv;
	}

	/**
	 * GenerateSymmetricKey:
	 * This will generate a random symmetric key.
	 *
	 * @return string (binary) - generated key
	 */
	static public function GenerateSymmetricKey($method=self::METHOD_AES128_CBC_LEAP)
	{
		return self::MCryptRandom(self::GetKeySizeInBytes($method));
	}

	/**
	 * GetKeySizeInBits:
	 * This will do as the name says.  Return the key size in bits.
	 *
	 * @return int
	 */
	static public function GetKeySizeInBits($method=self::METHOD_AES128_CBC_LEAP)
	{
		// Note: all methods are currently 128-bit
		return (self::GetKeySizeInBytes($method) * 8);	// See comments above - GenerateSymmetricKey
	}

	/**
	 * Report the default key size, in bytes.
	 *
	 * @return int
	 */
	static public function GetKeySizeInBytes($method=self::METHOD_AES128_CBC_LEAP)
	{
		// Note: all methods are currently 128-bit
	    return 16;
	}

	/**
	 * EncipherMessage:
	 * This will encipher a message with a symmetric cipher defined by
	 * the $method parameter.  It will encode the output into a message structure.
	 *
	 * @param string $base64key - the key, encoded as base64
	 * @param string $message - the message to encode.
	 * @param int $senderid - the id of the sender (encoded in the message structure)
	 * @param int $keyid - the id of the key that was used
	 * @return string - SymmetricKeyStructure(v1)
	 */
	static public function EncipherMessage($base64key, &$message, $senderid, $keyid, $method = self::METHOD_AES128_CBC_LEAP)
	{
		// Grab the pipe character
		$pipe = lmSymmetricMessage::$m_PIPE;

		// Generate the IV
		$iv = lmSymmetricMessage::GenerateIV($method);

		// Encipher the message and return the package
		$encoded = lmSymmetricMessage::$m_type . $pipe.
				$senderid . $pipe.
				$keyid . $pipe.
				$method . $pipe.
				lmBase64::EncodeBinary($iv) . $pipe.
				lmBase64::EncodeBinary(lmSymmetricMessage::SymmetricEncipher(lmBase64::DecodeBinary($base64key), $iv, $message, $method));

		return $encoded;
	}

	/**
	 * DecipherMessage:
	 * This will decipher a given message with a given key.  It will ensure that
	 * the senderid is correct before it even tries.
	 *
	 * @param string $base64key - the key to use to decipher
	 * @param string $encmessage - message structure output from EncipherMessage
	 * @param int $method - optional out param.  set if not null.
	 * @return string - deciphered message
	 */
	static public function DecipherMessage($base64key, &$encmessage, &$method=self::METHOD_AES128_CBC_LEAP)
	{
		// Explode the message into its parts
		$msg = self::SplitEncMessage($encmessage);

		self::VerifyVersion($msg);

		// Ensure the IV is the correct size
		if (self::$sIvLength != strlen($msg[lmSymmetricMessage::$e_IV]))
		{
			throw new LeapException("Invalid encoded IV Length: ".strlen($msg[lmSymmetricMessage::$e_IV]), LMSC_INVALIDIVLENGTH);
		}

		$methodid = intval($msg[lmSymmetricMessage::$e_METHOD]);

		$res = lmSymmetricMessage::SymmetricDecipher(
					lmBase64::DecodeBinary($base64key),
					lmBase64::DecodeBinary($msg[lmSymmetricMessage::$e_IV]),
					lmBase64::DecodeBinary($msg[lmSymmetricMessage::$e_DATA]),
					true,
					$methodid
				);

		// set out param and return
		$method = $methodid;

		return $res;
	}

	/**
	 * Split an encrypted message into its elements
	 *
	 * @param string $encmessage
	 * @return array
	 */
	static private function SplitEncMessage($encmessage)
	{
	    return explode(self::$m_PIPE, $encmessage);
	}

	/**
	 * Make sure the version matches in the encrypted message. This is a good
	 * way to validate if the message was exploded properly.
	 *
	 * Throws a LeapException if the version doesn't match.
	 *
	 * @param array $msg exploded version of the encrypted token
	 */
	static private function VerifyVersion(Array $msg)
	{
	    // Check to see if it's the correct type
	    if (self::$m_type != $msg[self::$e_VERSION])
	    {
			$error = sprintf("DecipherMessage unexpected version number: '%s' -- expected %s Full Token: '%s'",
				$msg[self::$e_VERSION],
				self::$m_type,
				join(self::$m_PIPE, $msg)
			);

			throw new LeapException($error, LMSC_INVALIDVERSION);
	    }
	}

	/**
	 * GetSenderID:
	 * This will return the SenderID encoded inside the message structure
	 *
	 * @param string $encmessage - the encoded message structure
	 * @return int - int SenderID
	 */
	static public function GetSenderID(&$encmessage)
	{
	    // Explode the message into its parts.  We don't really need the whole
	    // message, just the first 2 elements.  Those are guaranteed to be in
	    // the first 16 bytes
	    $msg = self::SplitEncMessage(substr($encmessage, 0, 16));

		// verify that we got at least 2 elements
		if(sizeof($msg) <= 2)
		{
			$error = sprintf(
				"Encrypted message did not break into enough pieces (only %s). Token: '%s'",
				sizeof($msg),
				$encmessage
			);
			throw new LeapException($error, LMSC_INVALIDVERSION);
		}

	    self::VerifyVersion($msg);

	    // return the sender ID
	    return $msg[lmSymmetricMessage::$e_SENDERID];
	}

	/**
	 * GetKeyID:
	 * This will return the KeyID encoded inside the message structure
	 *
	 * @param string $encmessage - the encoded message structure
	 * @return int - int KeyID
	 */
	static public function GetKeyID(&$encmessage)
	{
	    // Explode the message into its parts.  We don't really need the whole
	    // message, just the first 3 elements.  Those are guaranteed to be in
	    // the first 32 bytes
	    $msg = self::SplitEncMessage(substr($encmessage, 0, 32));

	    self::VerifyVersion($msg);

	    // return the sender ID
	    return $msg[lmSymmetricMessage::$e_KEYID];
	}

	/**
	 * Returns the method id of the symmetric message
	 *
	 * @param string $encmessage
	 * @return int
	 */
	static public function GetMethod($encmessage)
	{
	    $pieces = self::SplitEncMessage($encmessage);

	    self::VerifyVersion($pieces);

	    return intval($pieces[self::$e_METHOD]);
	}

	/**
	 * Returns the IV of the symmetric message
	 *
	 * @param string $encmessage
	 * @return string
	 */
	static public function GetIv($encmessage)
	{
	    // for asymmetric keys, the IV is the 6th element
	    $pieces = self::SplitEncMessage($encmessage);

	    self::VerifyVersion($pieces);

	    return $pieces[self::$e_IV];
	}

	/**
	 * IsInvalid:
	 * This will determine whether a message is definitely INVALID.  This does not
	 * mean the message is valid, it just looks for markers that indicate that it's
	 * definitely not valid
	 *
	 * We should be able to quickly test the message form for correctness, given:
	 * version(int)
	 * senderid(int)
	 * keyid(int)
	 * method(int)
	 * iv(string(24))
	 * message(binary encoded string)
	 *
	 * @param string $encmessage
	 * @return bool
	 */
	static public function IsInvalid(&$encmessage)
	{
		try
		{
			self::IsInvalidException($encmessage);
			return false;
		}
		catch(LeapException $e)
		{
			return true;
		}
	}

	/**
	 * Same as IsInvalid, but throws an exception instead.
	 *
	 * @param string $encmessage
	 */
	static public function IsInvalidException(&$encmessage)
	{
		if(strlen($encmessage) == 0)
		{
			throw new LeapException("encmessage is empty", LMSC_INVALIDDATA);
		}

		// Explode the message
		$msg = self::SplitEncMessage($encmessage);

		// VERSION element must be an int or numeric
		if ( true !== ctype_digit($msg[lmSymmetricMessage::$e_VERSION]) )
		{
		    throw new LeapException('VERSION is not numeric (' . $msg[lmSymmetricMessage::$e_VERSION] . ')', LMSC_INVALIDDATA);
		}

		// If the version isn't proper
		if (lmSymmetricMessage::$m_type != $msg[lmSymmetricMessage::$e_VERSION])
		{
		    throw new LeapException('type does not match', LMSC_INVALIDDATA);
		}

		// SENDERID element must be an int or numeric
		if ( true !== ctype_digit($msg[lmSymmetricMessage::$e_SENDERID]) )
		{
		    throw new LeapException('SENDERID is not numeric', LMSC_INVALIDDATA);
		}

		// KEYID element must be an int or numeric
		if ( true !== ctype_digit($msg[lmSymmetricMessage::$e_KEYID]) )
		{
		    throw new LeapException('KEYID is not numeric', LMSC_INVALIDDATA);
		}

		// METHOD element must be an int or numeric
		if ( true !== ctype_digit($msg[lmSymmetricMessage::$e_METHOD]) )
		{
		    throw new LeapException('METHOD is not numeric', LMSC_INVALIDDATA);
		}

		if (self::$sIvLength != strlen($msg[lmSymmetricMessage::$e_IV]))
		{
		    throw new LeapException('length of IV is not 24 (' . strlen($msg[lmSymmetricMessage::$e_IV]) . ')', LMSC_INVALIDDATA);
		}

		// DATA/MESSAGE must be base64
		// FIXME this should really be a validator method inside lmBase64
		$pattern = "/[^-,_A-Za-z0-9]/";
		if ( preg_match($pattern, $msg[lmSymmetricMessage::$e_DATA]) )
		{
			throw new LeapException("Payload is not base64 encoded", LMSC_INVALIDDATA);
		}

		// Everything seems ok
		return false;
	}
}
Return current item: NuCaptcha (PHP)