URLs curtas e ’seguras’ usando base62, base64 e md5
In: php
8
abr
2009
O PROBLEMA
Recentemente tivemos a necessidade de criar uma forma de reduzir as URLs que eram enviadas por email, para evitar problemas com quebra de linha nas mensagens que são enviadas em text/plain. Essas URLs tem uma caracterÃstica especial: Autenticam automaticamente o usuário através de um token e redirecionam para um conteúdo especÃfico.
Sendo assim, não poderÃamos utilizar um sistema público de encurtador de URLs nem deixar exposto o identificador da URL (Ex.: http://url.com/u/001), pois os usuários poderiam tentar combinações e acabar acessando informações restritas de outras contas do sistema.
A SOLUÇÃO
Inspirado no post do startupi falando sobre o alpha do migre.me, pesquisei sobre o algoritmo de base62 visando economizar caracteres na identificação da URL.
function base62_encode
($num){
$chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$base = 62;
$result = '';
while($num >= $base) {
$r = $num%$base;
$result = $chars[$r].$result;
$num = $num/$base;
}
return $chars[$num].$result;
}
function base62_decode
($id){
$chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$base = 62;
for ($i=0; $i<strlen
($id); $i++) {
$value = strpos($chars, $id[$i]);
$num = $value * pow($base, strlen($id)-($i+1
));
$final += $num;
}
return $final;
}
Veja o exemplo abaixo:
8 equivale 8
9 equivale 9
10 equivale a
11 equivale b
12 equivale c
(...)
1033 equivale gF
1034 equivale gG
1035 equivale gH
1036 equivale gI
E assim por diante.
Mesmo assim, um usuário mais esperto consegue deduzir a lógica dessa compressão em base62 e pode começar a fazer suas tentativas. É aà que entra o algoritmo de codificação usando base64 e md5. Confesso, acho a idéia desse algoritmo um pouco maluca, mas funciona muito bem.
function codificar
($valor, $chave){
if (($chave = codificacao_chave
($chave)) !== false){
list($inicio, $fim) = $chave;
} else {
return false;
}
$base = base64_encode($valor);
$base = str_replace('=', '', $base);
$base = strrev($base);
$md5 = md5($valor);
$base = (substr($md5, 0
, $inicio) . $base . substr($md5, -$fim));
return $base;
}
function decodificar
($valor, $chave){
if (($chave = codificacao_chave
($chave)) !== false){
list($inicio, $fim) = $chave;
} else {
return false;
}
$hash = (substr($valor, 0
, $inicio) . substr($valor, -$fim));
$base = substr($valor, 0
, -$fim);
$base = substr($base, $inicio);
$base = strrev($base);
$cmp = (strlen($base) % 4
);
if ($cmp > 0
) $base .= str_repeat('=', $cmp);
$base = base64_decode($base);
$md5 = md5($base);
$md5 = (substr($md5, 0
, $inicio) . substr($md5, -$fim));
return (($md5 == $hash) ?
$base : false);
}
function codificacao_chave
($chave){
$valor = md5($chave);
$t = strlen($valor);
$dig1 = '';
$dig2 = '';
for ($i = 0; $i < $t; $i++){
$ch = substr($valor, $i, 1
);
if (is_numeric($ch) && ($ch !== '0')){
if ($dig1 == ''){
$dig1 = $ch;
} elseif ($dig2 == ''){
$dig2 = $ch;
} else {
break;
}
}
}
return (($dig1 != '') && ($dig2 != '')) ?
array($dig1, $dig2) : false;
}
Em ação:
100000 equivale 1wADMwATM6c65b (usando a chave 'chave-secreta')
200000 equivale 0wADMwAjM3f793 (usando a chave 'chave-secreta')
(...)
Caso algum dos caracteres seja modificado, a função de decodificação irá retornar false e você pode redirecionar o usuário para uma página de acesso negado.
ARREMATE
Armazene isto em uma tabela com PRIMARY_KEY numérica e um campo para a URL original. Depois é só usar os dois algoritmos e gerar URLs como as abaixo:
ID:1000 http://url.com/u/1wADMwATM6c65b
ID:2000 http://url.com/u/0wADMwAjM3f793
Não gostou da solução? Faça um fork dela e me avise!