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! ;-)

Comment Form

Sobre

Ricardo Duarte, 26 anos, trabalha na Nuntec, atua como desenvolvedor web a mais de 12 anos, já tendo passado por diversas tecnologias. Este blog irá comentar um pouco destas experiências.

rduarte's tweets

  • Venicios Ribeiro: É uma excelente forma de ativar o cache. Existem tbm outras tecnicas muito simples, como o Gzip. à [...]
  • Andre Ferraro: Muito bom!! Estou fazendo o meu e vou postar no meu blog. Vlw!! [...]
  • paulo henrique: Bom dia! existe algum app para usar o twitter no e71, que seja free [...]
  • Ricardo: Bruno, Aplicativo para IM com vídeo eu não conheço, mas para áudio você pode usar o Nimbuzz [...]
  • Bruno: Olá boa tarde. Gostaria de saber qual software IM pro E71 suporta conversação com áudio e video. [...]