Olá pessoal! Ano novo, projetos antigos, projetos novos, projetos que eram para “ontem” avisados “hoje”... E assim o ano começa. E seguindo a determinação de postar experiências aqui no blog, vamos a uma que explora um pequeno detalhe que por muitas vezes é passado desapercebido, gerando horas e horas de debug em nossos códigos :).

Recentemente fiz uma rotina de autenticação de usuários, usando a minha Classe de Conexão (que está um tanto diferente da postada aqui no NM Tech) em que fazemos um Select para verificar se o usuário e a senha estão corretos, retornando um objeto do tipo “Usuário”. Vamos a um modelo bem simples da tabela em questão (o banco de dados é o SQL Server 2005):

--Criação da tabela
create table USUARIO (
	ID int not null identity,
	LOGIN varchar(15),
	SEHNA varbinary(32).
);

--Inserindo um usuário "na mão"
insert into USUARIO (LOGIN,SENHA) values ('leonel',hashbytes('MD5','leonel');

Como podemos ver acima, o campo SENHA é do tipo BINÁRIO, e a senha do usuário em questão é gravada na forma de um hash MD5, retornado pela função hashbytes e em sua forma binária.

Agora, veja o código que utilizei para checar a autenticação deste usuário, executando a query via Classe de Conexão (ele está adaptado para ser mais didático):

public Usuario Autenticar(string pUsuario, string pSenha)
{
	ClearSQLParams();
	AddSQLParam("@USUARIO",pUsuario,ParameterDirection.Input);
	AddSQLParam("@SENHA",pSenha,ParameterDirection.Input);
	string SQL = "select ID from USUARIO where LOGIN = @USUARIO and SENHA = hashbytes('MD5',@SENHA)";
	int CodUsuario = 0;
	try
	{
		CodUsuario = getScalar(SQL);
		return SetById(CodUsuario);
	}
	catch
	{
		return null;
	}
}

Qual não é a minha surpresa sendo que eu envio o usuário e a senha correta na função Autenticar e o SQL em questão não volta nenhum registro?

Bem, a função hashbytes(), função a qual eu passo a variável @SENHA para comparar com o hash armazenado no banco de dados calcula esse hash em função dos bytes da string informada. Essa string “leonel” que você passou tem 6 bytes, igualzinha a que você inseriu direto no banco, certo?

Errado!

Quando eu inseri diretamente no banco de dados, ele realmente calculou o hash em cima de 6 bytes, pois o banco de dados entendeu como um tipo VARCHAR, que armazena 1 byte por caracter. Agora, quando eu passei via ADO.NET, no parâmetro “pSenha” da função Autenticar(), essa string, possui 12 bytes, ou seja, cada caractere tem 2 bytes (a string padrão do .NET é codificada em Unicode).

Quando, por sua vez, isso foi passado para o SQL Server, o parâmetro @SENHA foi criado pela classe de Conexão como sendo do tipo NVARCHAR (que armazena 2 bytes por caracter), já que o tipo foi inferido dinamicamente pelo .NET Framework (lembra, não definimos o tipo de dado hardcoded dentro da função AddSQLParam() da Classe de Conexão).

E como sabemos, um hash de uma sequencia de 6 bytes é diferente de um hash de uma sequencia de 12 bytes.

Então, como solucionar esse problema? Simples: Fazendo um cast da string da senha na hora da inserção, assim:

insert into USUARIO (LOGIN,SENHA) values ('leonel',hashbytes('MD5',cast('leonel' as nvarchar(6)));

Fazendo isso, a função hashbytes() calcula o MD5 à partir de uma cadeia de 12 bytes em vez de 6, que por sua vez é compatível com a string que será passada para a função Autenticar().

É isso aí! E pensar que para chegar a essa conclusão demorei um bom tempo debugando o código...

Um abraço!