Back-End

29 set, 2011

Dados criptografados na sessão PHP

Publicidade

Neste artigo, apresento um exemplo de forte criptografia em PHP para dados
em uma sessão segura. Essa é uma simples
implementação que pode ser usada para melhorar a segurança de aplicações PHP,
especialmente em ambientes compartilhados nos quais usuários diferentes têm acesso
aos mesmos recursos. Como você sabe, os dados nas sessões PHP são gerenciados
por padrão usando arquivos temporários. Em um ambiente compartilhado, um
usuário malicioso que é capaz de acessar esses arquivos temporários pode
facilmente ler esses dados porque eles estão armazenados em plaintext (dados em
um arquivo de sessão é a serialização do array $_SESSION).

Em teoria, os dados na sessão deveriam
ser armazenados em pastas que somente são acessíveis pelo dono do website, mas
nunca diga nunca (falando nisso, você pode gerenciar o local dos dados na
sessão usando a função session_save_path ou mudando a session_save_path no
php.ini).

Para assegurar a
os dados na sessão, eu usei uma criptografia forte para criptografar o conteúdo
usando a extensão mcrypt do PHP. Eu escolhei a cifra simétrica Rijandel-256 para criptografar os
dados da sessão e a função openssl_random_pseudo_bytes() para gerar uma
chave randômica de 256 bit.

A ideia é usar um
cookie variável para armazenar a chave que será usada para criptografar os
dados na sessão. Dessa maneira, a chave é armazenada somente no cliente (o
browser) e somente o cliente é capaz de descriptografar os dados da sessão no
servidor. Cada vez que criptografamos os dados da sessão, nós geramos mais uma
vez o vetor IV de maneira randômica usando a função mcrypt_create_iv(). É muito importante gerar um único IV em cada criptografia. Essa prática
otimizada melhora a segurança do algoritmo criptografado.

É importante notar
que essa implementação não é segura contra um ataque de hijacking. Se outra pessoa for capaz de capturar o
cookie variável de um cliente e tiver acesso aos arquivos temporários das
sessões, dentro do servidor, ele ou ela será capaz de descriptografar os dados
da sessão. Nosso objetivo é
proteger os dados da sessão contra ataques em ambientes compartilhados.

A ideia de
criptografar os dados na sessão não é nova, por exemplo, Chris Shiflett propôs uma implementação em seu livro “Essential PHP Security” (O’Reilly, 2006). Shiflett usou uma variável $_SERVER para
armazenar a chave usada para criptografar os dados da sessão. Kevin Schroeder, meu colega na Zend Technologies, implementou um algoritimo de
criptografia de sessão muito similar estendendo a classe Zend_Session do Zend Framework (você pode encontrá-lo aqui). Na minha solução, eu
usei algumas das melhores práticas relacionadas a fortes criptografias para
implementar um handler seguro de sessões.

Abaixo
está o código-fonte da minha implementação:

/**
* ------------------------------------------------
* Encrypt PHP session data using files
* ------------------------------------------------
* The encryption is built using mcrypt extension
* and the randomness is managed by openssl
*
* @author Enrico Zimuel (enrico@zimuel.it)
* @copyright GNU General Public License
*/
class SecureSession {
const CIPHER= MCRYPT_RIJNDAEL_256;
const CIPHER_MODE= MCRYPT_MODE_CBC;
/**
* Key for encryption/decryption
*
* @var string
*/
private static $_key;
/**
* Path of the session file
*
* @var string
*/
private static $_path;
/**
* Session name (optional)
*
* @var string
*/
private static $_name;
/**
* Size of the IV vector for encryption
*
* @var integer
*/
private static $_ivSize;
/**
* Cookie variable name of the key
*
* @var string
*/
private static $_keyName;
/**
* Generate a random key
* fallback to mt_rand if PHP < 5.3 or no openssl available
*
* @param integer $length
* @return string
*/
private static function _randomKey($length=32) {
if(function_exists('openssl_random_pseudo_bytes')) {
$rnd = openssl_random_pseudo_bytes($length, $strong);
if($strong === TRUE)
return $rnd;
}
for ($i=0;$i<$length;$i++) {
$sha= sha1(mt_rand());
$char= mt_rand(0,30);
$rnd.= chr(hexdec($sha[$char].$sha[$char+1]));
}
return $rnd;
}
/**
* Open the session
*
* @param string $save_path
* @param string $session_name
* @return bool
*/
public static function open($save_path, $session_name) {
self::$_path= $save_path.'/';
self::$_name= $session_name;
self::$_keyName= "KEY_$session_name";
self::$_ivSize= mcrypt_get_iv_size(self::CIPHER, self::CIPHER_MODE);

if (empty($_COOKIE[self::$_keyName])) {
$keyLength= mcrypt_get_key_size(self::CIPHER, self::CIPHER_MODE);
self::$_key= self::_randomKey($keyLength);
$cookie_param = session_get_cookie_params();
setcookie(
self::$_keyName,
base64_encode(self::$_key),
$cookie_param['lifetime'],
$cookie_param['path'],
$cookie_param['domain'],
$cookie_param['secure'],
$cookie_param['httponly']
);
} else {
self::$_key= base64_decode($_COOKIE[self::$_keyName]);
}
return true;
}
/**
* Close the session
*
* @return bool
*/
public static function close() {
return true;
}
/**
* Read and decrypt the session
*
* @param integer $id
* @return string
*/
public static function read($id) {
$sess_file = self::$_path.self::$_name."_$id";
$data= @file_get_contents($sess_file);
if (empty($data)) {
return false;
}
$iv= substr($data,0,self::$_ivSize);
$encrypted= substr($data,self::$_ivSize);
$decrypt = mcrypt_decrypt(
self::CIPHER,
self::$_key,
$encrypted,
self::CIPHER_MODE,
$iv
);
return rtrim($decrypt, "?");
}
/**
* Encrypt and write the session
*
* @param integer $id
* @param string $data
* @return bool
*/
public static function write($id, $data) {
$sess_file = self::$_path.self::$_name."_$id";
$iv= mcrypt_create_iv(self::$_ivSize, MCRYPT_RAND);
if ($fp = @fopen($sess_file, "w")) {
$encrypted= mcrypt_encrypt(
self::CIPHER,
self::$_key,
$data,
self::CIPHER_MODE,
$iv
);
$return = fwrite($fp, $iv.$encrypted);
fclose($fp);
return $return;
} else {
return false;
}
}
/**
* Destroy the session
*
* @param int $id
* @return bool
*/
public static function destroy($id) {
$sess_file = self::$_path.self::$_name."_$id";
setcookie (self::$_keyName, '', time() - 3600);
return(@unlink($sess_file));
}
/**
* Garbage Collector
*
* @param int $max
* @return bool
*/
public static function gc($max) {
foreach (glob(self::$_path.self::$_name.'_*') as $filename) {
if (filemtime($filename) + $max < time()) {
@unlink($filename);
}
}
return true;
}
}
// Set the custom PHP session handler
ini_set('session.save_handler', 'user');
session_set_save_handler(array('SecureSession', 'open'),
array('SecureSession', 'close'),
array('SecureSession', 'read'),
array('SecureSession', 'write'),
array('SecureSession', 'destroy'),
array('SecureSession', 'gc')
);

Você pode fazer o
download da classe SecureSession aqui.

Para poder usar a
classe SecureSession, você tem que incluí-la no seu projeto PHP antes da função
session_start.

Depois disso, você pode usar a sessão PHP da maneira
normal.

?

Texto original disponível em http://www.zimuel.it/en/encrypt-php-session-data/