O armazenamento de senhas de usuários de um sistema ou site é um assunto que nem sempre recebe a atenção necessária. Muitos armazenam a senha como texto puro ou utilizam métodos fracos de criptografia, gerando inúmeros casos de vazamento de senhas. Embora parte do problema seja a segurança do acesso aos servidores e bases de dados, devemos tentar sempre garantir que, uma vez com acesso aos dados o intruso não consiga obter o valor das senhas.
Para garantir que não seja possível obter o valor da senha de um usuário, mas ainda seja possível confirmar que a senha que o mesmo digitou é correta podemos utilizar uma função hash. Esta é uma função que à partir de um valor de entrada gera um valor de saída que não pode ser usado para restaurar o valor original, ou seja, é uma criptografia de mão única.
Por isto as funções de hash são tão boas para o armazenamento de senhas: uma vez criptografada a senha ninguém consegue saber qual era o seu valor original. Em teoria. Existem muitos algoritmos de funções hash como: MD5 e SHA1. O problema de utilizar estes algoritmos diretamente para criptografar a senha é que existem diversas bases de hashes pré calculados, por exemplo, vamos gerar um hash MD5 da senha: felipe0111
1 2 |
$senha = md5('felipe0111'); echo 'MD5 felipe0111: ', $senha, PHP_EOL; |
Este script gera a saída:
1 |
MD5 felipe0111: 9de4cb74c69bd0062eab3b58405ed4a8 |
Uma simples busca no Google pelo valor do hash já nos permite descobrir o valor da senha. Então não podemos utilizar simplesmente a função MD5 para gerar o hash. A função SHA1 é mais moderna, mas também existem diversas bases de hashes calculados.
Para facilitar a vida dos desenvolvedores e prover uma forma segura de armazenamento de senhas o PHP 5.5 adicionou algumas novas funções específicas para o tratamento de senhas. Estas funções são muito práticas e úteis, podendo (e devendo) ser utilizadas em qualquer código de armazenamento de senhas de usuários.
A maior vantagem de utilizar estas funções é que, se utilizadas corretamente, as senhas são atualizadas automaticamente conforme algoritmos de hash mais seguros são criados, graças à constante PASSWORD_DEFAULT que refere-se sempre à função de hash recomendada.
O uso da API é bastante simples, basicamente utilizamos apenas as funções password_hash, password_verify e password_needs_rehash. Então, por exemplo, ao gravar a senha de um usuário no banco de dados devemos antes passar o valor pela função password_hash:
1 2 |
//Converte a senha para o hash $password = password_hash($_POST['password'], PASSWORD_DEFAULT); |
Bastante simples. Notem o uso da constante PASSWORD_DEFAULT esta é uma constante que pode ser alterada dependendo da versão do PHP e vai sempre ter o algoritmo de hash mais forte que o PHP tiver, então é importante que o local de armazenamento deste hash (ex: campo da base de dados) tenha um tamanho suficiente, com margem para crescimento.
Com a senha gravada como hash, podemos utilizar a função password_verify para verificar se um valor passado confere com o hash. É importante utilizar esta função para comparar pois, se o hash tiver sido gerado com um algoritmo diferente do atualmente configurado em PASSWORD_DEFAULT o hash não irá bater. A função password_verify automaticamente identifica o algoritmo utilizado e usa o mesmo para conferir.
1 2 3 4 5 6 |
//Verifica se a senha passada no formulário confere com o hash armazenado if (password_verify($_POST['password'], $hash)) { echo 'Usuário autenticado!'; } else { echo 'Senha incorreta.'; } |
Outra vantagem da API de senhas do PHP é que podemos verificar se há um algoritmo mais forte para gerar o hash da senha. Ou seja, se numa versão nova do PHP for incluído um algoritmo de hash mais forte, a constante PASSWORD_DEFAULT terá seu valor alterado, mas nossas senhas permanecerão armazenadas com o algoritmo antigo. Isto não gera problemas na comparação com password_verify, mas pode tornar mais fácil a descoberta das senhas. Para isto há a função password_needs_rehash que indica se a senha precisa ser criptografada novamente:
1 2 3 4 5 6 7 8 9 10 11 |
//Verifica se a senha passada no formulário confere com o hash armazenado if (password_verify($_POST['password'], $hash)) { //Verifica se é necessário gerar novo hash if (password_needs_rehash($hash, PASSWORD_DEFAULT)) { $hash = password_hash($_POST['password']); //Atualizar valor do hash na base de dados... } echo 'Usuário autenticado!'; } else { echo 'Senha incorreta.'; } |
A API de hash de senhas do PHP é uma ótima adição à linguagem, dando organização e um método de trabalho claro para lidar com o armazenamento e verificação de senhas. Com poucas funções ela organiza bem todo o fluxo de autenticação com senhas e ao utiliza-la podemos minimizar os dados causados por uma invasão ou vazamento de dados.
Com o amplo suporte ao HTML5 nos navegadores mais modernos todos tem a capacidade de entender a tag <video> que permite colocar o elemento na página, porém não é qualquer formato de vídeo que pode ser distribuído, já limitações de acordo com o navegador e sua versão. Então antes de colocar o vídeo na web precisamos convertê-lo para um formato suportado.
O formato MP4 é o que hoje possui o suporte mais abrangente, mas uma das vantagens da tag <video> do HTML5 é permitir especificar diversos arquivos de vídeo, assim o navegador irá escolher automaticamente o arquivo com o formato suportado. De acordo com o site da W3Schools os formatos suportados para cada navegador são:
Navegador | MP4 | WebM | Ogg |
---|---|---|---|
Internet Explorer | SIM | NÃO | NÃO |
Chrome | SIM | SIM | SIM |
Firefox | NÃO | SIM | SIM |
Safari | SIM | NÃO | NÃO |
Opera | NÃO | SIM | SIM |
Utilizaremos como exemplo este vídeo que está no formato Quicktime (.mov). Para converter pode-se utilizar a ferramenta FFmpeg, disponível para Linux, Windows e MAC.
Para converter o vídeo para o formato MP4 podemos utilizar a linha de comando abaixo:
ffmpeg -i Rain_Fire.mov -c:v libx264 -pix_fmt yuv420p -profile:v baseline -preset slower -crf 23 -vf "scale=trunc(in_w/2)*2:trunc(in_h/2)*2" -movflags +faststart rain.mp4
Alguns parâmetros importantes do comando acima:
Para mais informações ver a documentação do FFmpeg sobre codificação de vídeos com H.264.
Outra possibilidade de formato de vídeo é o WebM, um formato recentemente adquirido pelo Google que o tornou de uso público. Para converter o vídeo para este formato podemos utilizar o comando abaixo:
ffmpeg -i Rain_Fire.mov -c:v libvpx -c:a libvorbis -pix_fmt yuv420p -b:v 2M -crf 5 rain.webm
De forma semelhante os parâmetros da conversão:
Mais informações na documentação do FFmpeg.
Com os arquivos no formato correto podemos então criar nossa página para exibi-lo ao usuário. Vamos montar uma estrutura de diretórios da seguinte forma:
Estrutura de diretórios
No diretório public ficarão os arquivos visíveis pelo servidor web e no diretório resource os arquivos de vídeo que iremos servir. No arquivo index.php está o HTML para a página e o arquivo stream.php será o responsável por ler os arquivos de vídeo e enviá-los ao navegador do usuário.
O streaming é uma forma de distribuição de dados aonde o conteúdo é fornecido ao usuário conforme necessário, sem a necessidade de que ele receba todo o conteúdo para visualiza-lo. Esta é a melhor forma de servir vídeos pois permite a rápida visualização pelo usuário (ele não tem de esperar o download do vídeo todo) e também diminui a banda de rede utilizada pelo servidor. Os navegadores utilizam o cabeçalho HTTP Range para indicar qual parte do vídeo eles desejam que seja enviada. Assim se o usuário clicar na barra de navegação do vídeo o navegador envia uma nova requisição para o período respectivo.
Com o arquivo no formato correto podemos criar um script PHP para fazer a leitura do arquivo e envia-lo para o usuário. Porém precisamos considerar o cabeçalho Range para que sejam enviados somente os bytes que o navegador solicitou. De acordo com a RFC2616 que especifica o protocolo HTTP versão 1.1 o conteúdo do campo tem um dos seguintes formatos:
O valor do campo vem na variável $_SERVER[‘HTTP_RANGE’] então podemos criar o código que irá ler o arquivo utilizando as funções de sistema de arquivos do PHP e colocar no arquivo stream.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
//Determina o caminho do arquivo de acordo com a extensão if (!isset($_GET['ext']) || $_GET['ext'] == 'mp4') { $path = dirname(__FILE__) . '/../resource/rain.mp4'; } else if ($_GET['ext'] == 'webm') { $path = dirname(__FILE__) . '/../resource/rain.webm'; } else { header('HTTP/1.1 400 Bad Request'); return; } // Determina o mimetype do arquivo $finfo = new finfo(FILEINFO_MIME); $mime = $finfo->file($path); // Define o tipo de conteúdo da resposta header('Content-type: ' . $mime); // Tamanho do arquivo $size = filesize($path); //Verifica se foi passado o cabeçalho Range if (isset($_SERVER['HTTP_RANGE'])) { // Parse do valor do campo list($specifier, $value) = explode('=', $_SERVER['HTTP_RANGE']); //Tratamos apenas o especificador de range "bytes" if ($specifier != 'bytes') { header('HTTP/1.1 400 Bad Request'); return; } // Determina os bytes de início/fim list($from, $to) = explode('-', $value); if (!$to) { $to = $size - 1; } // Cabeçalho da resposta header('HTTP/1.1 206 Partial Content'); header('Accept-Ranges: bytes'); // Tamanho da resposta header('Content-Length: ' . ($to - $from)); // Bytes enviados na resposta header("Content-Range: bytes {$from}-{$to}/{$size}"); // Abre o arquivo no modo bináro $fp = fopen($path, 'rb'); $chunkSize = 8192; // Tamanho dos blocos de leitura // Avança até o primeiro byte solicitado fseek($fp, $from); // Manda os dados while(true){ // Verifica se já chegou ao byte final if(ftell($fp) >= $to){ break; } // Envia o conteúdo echo fread($fp, $chunkSize); // Flush do buffer ob_flush(); flush(); } } else { // Se não possui o cabeçalho Range, envia todo o arquivo header('Content-Length: ' . $size); // Lê o arquivo readfile($path); } |
É essencial tratar o cabeçalho Range quando estamos servindo arquivos que serão consumidos via streaming pois se não o usuário não conseguirá avançar/retroceder no vídeo e terá de esperar o download completo do vídeo antes de começar a assisti-lo, o que é muito pouco prático para vídeos grandes.
Vamos agora criar o arquivo index.php que será acessado pelo usuário. Ele terá apenas o HTML necessário para a tag <video>:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?php // Para quando for executado pelo php -S redirecionar os arquivos if (php_sapi_name() === 'cli-server' && is_file(__DIR__ . parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH))) { return false; } ?> <!DOCTYPE html> <html> <head> <title>HTML5 Video + PHP</title> </head> <body> <video id="video" controls preload="auto" width="640" height="360" poster="poster.png"> <source src="stream.php?ext=webm" type='video/webm' /> <source src="stream.php?ext=mp4" type='video/mp4' /> </video> </body> </html> |
Com o arquivo index.php criado podemos agora acessá-lo e ver o vídeo. Caso não queira utilizar um servidor web para testar pode-se utilizar o servidor web embutido do PHP executando:
php -S localhost:8000 index.php
E então acessar no navegador o endereço http://localhost:8000
A tag <video> é utilizada para inserir o vídeo na página. Dentro dela pode haver qualquer número de elementos <source> cada um deles indicando um formato de vídeo diferente ou até mesmo um arquivo de áudio. Então pode-se, por exemplo, colocar um arquivo de vídeo e um de áudio e o navegador vai “juntar” os dois na reprodução. No nosso caso especificamos dois elementos source, um com o vídeo no formato MP4 e outro com o vídeo no formato WebM. Desta forma o navegador vai escolher o que for suportado. Os atributos da tag são:
Além destes diversos outros parâmetros estão disponíveis que permitem controlar o comportamento do vídeo.
Como mostrado acima é possível especificar uma imagem que irá aparecer no lugar do vídeo enquanto este não for carregado. Podemos gerar esta imagem com o FFmpeg utilizando o comando abaixo:
ffmpeg -i Rain_Fire.mov -r 1 -vframes 1 -ss 0:05 poster.png
O parâmetro mais importante acima é o -ss 0:05 que especifica de qual momento será criada a imagem
A página acima funciona em qualquer navegador moderno que suporte HTML5. Mas e os navegadores mais antigos como Internet Explorer 7 e 8 que ainda são bastante utilizados? Para estes casos podemos utilizar a biblioteca video.js que faz automaticamente o fallback para um player em Flash caso o navegador não tenha suporte a HTML5. A utilização é bastante simples e há diversos exemplos no site. Segue a versão do nosso index.php utilizando a biblioteca:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<?php // Para quando for executado pelo php -S redirecionar os arquivos if (php_sapi_name() === 'cli-server' && is_file(__DIR__ . parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH))) { return false; } ?> <!DOCTYPE html> <html> <head> <title>HTML5 Video + PHP</title> <link href="//vjs.zencdn.net/4.6/video-js.css" rel="stylesheet"> <script src="//vjs.zencdn.net/4.6/video.js"></script> </head> <body> <video id="video" class="video-js vjs-default-skin" controls preload="auto" width="640" height="360" poster="poster.png"> <source src="stream.php?ext=webm" type='video/webm' /> <source src="stream.php?ext=mp4" type='video/mp4' /> <p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p> </video> </body> </html> |
A tag <video> do HTML5 permite adicionar facilmente vídeos à paginas web, porém é necessário tomar cuidado com os formatos de vídeos suportados e fazer as conversões necessárias. É essencial que os arquivos sejam servidos via streaming para diminuir o tráfego de rede e melhorar a experiência do usuário e para tanto é essencial que o cabeçalho Range seja tratado de forma adequada, algo que pode ser feito facilmente pelo PHP. O código aqui mostrado tem apenas as funções básicas e pode com certeza ser muito melhorado.
O código de exemplo está disponível no Github: https://github.com/weckx/wcx-video-stream
Ao criar formulários com Zend Framework 2 normalmente utilizamos o InputFilter para adicionar validações aos campos. Porém, por padrão as mensagens de validação vem em inglês, o que não é bom para sistemas que serão utilizados no Brasil. Para corrigir isto o ZF2 vem com uma grande quantidade de traduções das mensagens de validação padrão, entre elas o pt_BR. Para habilitar é bastante simples. Basta adicionar ao método onBootstrap do seu módulo o código abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public function onBootstrap(MvcEvent $e) { //Cria o translator $translator = new \Zend\Mvc\I18n\Translator(new \Zend\I18n\Translator\Translator()); //Adiciona o arquivo de tradução $translator->addTranslationFile( 'phpArray', __DIR__ . '/../../vendor/zendframework/zendframework/resources/languages/pt_BR/Zend_Validate.php', 'default', 'pt_BR' ); //Define o tradutor padrão dos validadores \Zend\Validator\AbstractValidator::setDefaultTranslator($translator); } |
O código acima deve ser colocado na classe Module dos módulos que você quer que sejam traduzidos, ou no módulo principal da aplicação para que seja executado em todos os controllers. É importante notar que como estamos instanciando no método onBootstrap o código será executado para todas as páginas acessadas, o ideal é alterar isto para que seja chamado apenas nos controllers que possuem formulários para evitar um processamento desnecessário.
Recentemente atualizei minha certificação Zend Certified Engineer PHP 5.3 para a nova “Zend Certified PHP Engineer” que contempla o PHP 5.5. Achei o teste bastante semelhante aos anteriores (acho até que algumas questões eram as mesmas). Mas há alguns tópicos novos que não eram cobertos no teste anterior:
O teor do exame e o nível de dificuldade são bastante semelhantes ao anterior, mas é uma renovação bem vinda para manter a certificação atualizada. Os novos tópicos são bons para forçar o estudo e conhecer as novas funcionalidades. A lista completa dos tópicos cobertos pelo exame está em: http://www.zend.com/en/services/certification/php-certification/.
O exame pode ser comprado no site da Zend ou diretamente na Pearson Vue por 195 dólares. Pode ser feito em qualquer centro autorizado (existem diversos no Brasil). Quem já tiver a certificação do PHP 5.3 ganha um desconto de 70 dólares no valor da certificação. Se você passar na certificação ganha uma licença perpétua da versão atual do Zend Studio.
Segundo o site da Zend ainda este ano deve sair a certificação do Zend Framwork 2 enfim.
Um detalhe muito importante da função in_array do PHP é o terceiro parâmetro $strict que por padrão tem o valor FALSE. Mas qual diferença exatamente ele faz? Vamos analisar o seguinte código:
1 2 |
$array = array('arroz', 'alga', 'peixe'); var_dump(in_array('salmão', $array)); |
A saída será simplesmente:
1 |
bool(false) |
Porém, se adicionarmos ao array $array um item com o valor true:
1 2 |
$array = array('arroz', 'alga', 'peixe', true); var_dump(in_array('salmão', $array)); |
A saída então será:
1 |
bool(true) |
Ou seja, mesmo não havendo um item salmão no vetor $array a função in_array retornou true! Isto porque por padrão ela utiliza a comparação simples do PHP (ou “loose comparison”), ou seja, utilizando == (http://php.net/manual/en/types.comparisons.php) isto faz com que qualquer valor que não seja vazio ao ser comparado com true seja dado como igual. Isto pode causar um bug bastante difícil de rastrear já que o código a princípio funciona normalmente se for testado com arrays que não contenham o valor true. Para solucionar basta passar o terceiro parâmetro da função como true para que a função utilize a comparação estrita:
1 2 |
$array = array('arroz', 'alga', 'peixe', true); var_dump(in_array('salmão', $array, true)); |
Com isto a comparação voltará a funcionar como esperado. Este é o tipo de erro que pode ser prevenido facilmente se o desenvolvimento for feito utilizando a metodologia TDD, mas se for feito sem pode ser realmente complicado de ser encontrado.
O SSH é um protocolo de comunicação segura muito utilizado para acesso remoto via terminal, ou seja para executar comandos. Mas ele possui muitas outras funcionalidades, uma delas é a de tunelamento. Tunelamento é a capacidade de um protocolo de rede encapsular um outro protocolo dentro dele, tornando-se efetivamente o meio de transporte deste protocolo. Isto abre uma série de possibilidades, basicamente permitindo que você possa ter acesso a qualquer recurso de rede que o host aonde você consegue se conectar por SSH tem. Por exemplo, você está em um lugar aonde o acesso a determinados sites é bloqueado mas você tem acesso via SSH a um servidor que esta fora desta rede. Neste caso você pode criar um túnel dinâmico da seguinte forma:
ssh -N -D2342 usuario@servidor.com
Depois de conectar ao servidor configure o seu navegador para utilizar um proxy SOCKS no localhost, porta 2342. No caso do Firefox está configuração está disponível no menu Edit -> Preferences -> Network -> Settings :
Com esta configuração o Firefox não vai mais se conectar diretamente aos sites que você tentar acessar. Ao invés disso ele irá conectar no proxy SOCKS que configuramos ao iniciar o SSH e irá conectar através dele. Então o acesso está sendo feito através do servidor.com ao qual nos conectamos.
O Syslog é um padrão de mensagens de log de aplicações utilizado por diversos tipos de equipamentos de computação como: servidores, impressoras, roteadores, firewalls, etc. A ideia principal é permitir que todos os logs gerados por uma ou mais aplicações possam ser enviados para um mesmo local, que pode ser no próprio equipamento ou em um servidor remoto.
Neste post vamos entender como funciona a mensagem Syslog e como criar uma classe simples de envio com o PHP.