25 de maio de 2009

Obtendo o MAC Address para gerar um Hardware Key em Delphi

Há certas situações em que é interessante ter um número único para identificar uma estação de trabalho. Por exemplo, a instalação de um software pode ter a política de licença de uso baseado nessa identificação da estação. O SAP Business One usa um esquema assim para gerar um código e associá-lo ao servidor de licenças da aplicação. Para conseguir o arquivo com as licenças adquiridas, é preciso informar no site da SAP esse código gerado. Caso se informe um código errado, as licenças simplesmente não são liberadas e o software não funciona.

Vou mostrar aqui um método para gerar um código Hardware Key baseado no MAC Address, isto é, na informação da placa de rede instalada na máquina. Essa é uma boa abordagem pois é teoricamente garantido que não haverão duas placas de rede com o mesmo MAC Address, mesmo que sejam de fabricantes diferentes. A ideia é capturar o endereço - que é uma informação binária - e gerar uma representação textual (string) dele. Para obter o endereço, vou usar uma função da API do Windows chamada GetAdaptersInfo. Ela aceita 2 parâmetros: um ponteiro para a estrutura IP_ADAPTER_INFO e o outro o tamanho da estrutura alocada:
function GetAdaptersInfo(pAdapterInfo: PIP_ADAPTER_INFO; var pOutBufLen: ULONG): DWORD;stdcall;

Parece que essa função não está mapeada no Delphi mas localizei no site do projeto JEDI fonte com o mapeamento das funções da biblioteca Iphlpapi.dll do Windows - incluindo a GetAdaptersInfo e as estruturas necessárias. Também tenho cópia desses fontes, caso não encontrem lá.

Esta é uma daquelas funções malucas do Windows que é preciso chamar 2 vezes: a primeira para obter a quantidade de memória a ser alocada e a segunda para efetivamente recuperar os dados desejados.
dwBufLen := 0;
AdapterInfo := Nil;
GetAdaptersInfo(PIP_ADAPTER_INFO (AdapterInfo), dwBufLen);
if (dwBufLen > 0) then begin
GetMem (AdapterInfo, dwBufLen);
dwStatus := GetAdaptersInfo(PIP_ADAPTER_INFO (AdapterInfo), dwBufLen);
...

O endereço que precisamos é um array de bytes, declarado como segue:
type TWMacId = array [0..MAX_ADAPTER_ADDRESS_LENGTH - 1] of BYTE;

Listo abaixo o código para a função que recupera o MAC Address. Neste exemplo, estou recuperando apenas a primeira placa de rede encontrada; se houverem outras, o algorítmo simplesmente as ignora:
function GetMACaddress : TWMacId;
var i : integer;
AdapterInfo : Pointer;
pAdapterInfo : PIP_ADAPTER_INFO;
dwBufLen, dwStatus : LongWord;
begin
dwBufLen := 0;
AdapterInfo := Nil;{ Inicialmente, limpa o retorno }
for i := 0 to MAX_ADAPTER_ADDRESS_LENGTH - 1 do
Result [i] := 0;
GetAdaptersInfo(PIP_ADAPTER_INFO (AdapterInfo), dwBufLen);
if (dwBufLen > 0) then begin
GetMem (AdapterInfo, dwBufLen);
dwStatus := GetAdaptersInfo(PIP_ADAPTER_INFO (AdapterInfo), dwBufLen);
if (dwStatus = ERROR_SUCCESS) then begin
pAdapterInfo := PIP_ADAPTER_INFO (AdapterInfo);

{ Copia o endereço p/ nossa variável de retorno }
for i := 0 to MAX_ADAPTER_ADDRESS_LENGTH - 1 do
Result[i] := pAdapterInfo.Address[i];
FreeMem (AdapterInfo);
end;
end;
end;

Agora, falta formatar esse resultado como um texto. Vou transformar os bytes retornados usando a representação hexadecimal de cada um deles, numa ordem aleatória. Isto é, peguei aleatoriamente os bytes de número 4, 2, 1, 5 e 3. Segue a função completa:
function GetHardwareKey : String;
var id : TWMacId;
begin
id := getMacAddress;
Result := Format ( '%.2X', [id [4]]) +
Format ( '%.2X', [id [2]]) +
Format ( '%.2X', [id [1]]) +
Format ( '%.2X', [id [5]]) +
Format ( '%.2X', [id [3]]);
end;

Se achar que usar somente o MAC Address não é suficiente para gerar um código seguro para sua aplicação, use essa informação como base e mescle-a com outros dados do computador. Por exemplo, inclua dados da CPU, do sistema operacional, etc.

Mais Informações
Endereço MAC, JEDI Project.

16 comentários :

Fabio disse...

Até onde esta identificação é confiável? Será que há alguma forma de alterá-la, ou fazer o Windows identificar de forma "diferente"? Será que muda dependendo da versão do Windows? Obrigado

Gustavo Fabbro disse...

Fábio

O MAC Address pode ser modificado - veja por exemplo o link http://www.nthelp.com/NT6/change_mac_w2k.htm.

Mas, se quer gerar uma identificação única, monte seu próprio algorítmo que misture o Mac Address com outras informações do computador ou do Windows. Dessa forma, fica mais difícil alguém mal intencionado descobrir como falsificar seu código.

O programa neste post retorna um código independente da versão do Windows. Caso você mescle o MAC Address com versão do SO para gerar um código, aí sim deve levar em conta alterações na versão do SO.

Geraldo disse...

Luís Gustavo, gostei muito do projeto de liberação de uso de sistema, mas te confesso que do jeito que esta explicado não consigo desenvolve-lo pois sou meio inesperiênte ainda, será que teria como me enviar um passo a passo. Desde já te agradeço.

E'mail: geraldo.tekmicro@gmail.com

Luís Gustavo Fabbro disse...

Geraldo

O foco do post era outro e não me preocupei em detalhar um algorítmo de licenciamento. Segue uma visão geral do processo :
1) sua aplicação é instalada num computador
2) como no primeiro acesso a licença ainda não existe, gera o hardware key para o usuário enviar para vc.
3) Remotamente, vc criptografa o hardwarekey junto com informações de licença (recursos liberados no programa, qtde de usuários simulâneos ou outra característica que deseje).
4) A informação criptografada é devolvida para o usuário, que deve instalá-la localmente de forma que o seu programa consiga acessá-la e validar.
5) Toda vez que o usuário entrar no programa, a informação criptografada é validada pra saber se o computador em uso é o mesmo para o qual a licença foi atribuída.

Os passos podem variar dependendo da arquitetura que sua aplicação tem.

Andrews disse...

ola, Luís, onde consigo essa função GetAdaptersInfo, ond ela está na jedi? obrigado

Luís Gustavo Fabbro disse...

Andrews

A função GetAdaptersInfo está na unit IpHlpApi. Há uma versão dela publicada no endereço http://www.koders.com/delphi/fidE47BDA7E986603E83E5266F72CC1ADDDAADF3EC9.aspx?s=pos. As outras units necessárias pra compilar podem ser obtidas a partir do mesmo endereço.

[]s

Andrews disse...

hummmmmm blz obrigado já baixei as unit's obrigado, agora vo tentar modificar aqui seus exemplos para ver se consigo apenas listar o nome dos adaptadores de redes

Andrews disse...

boa tarde Luíz, utilizando esta unit IpHlpApi, é possivel obter o nome dos adaptadores de rede? ex: Rele Local, Rele Local 1... ?
pois estou mexendo com configuração de ip e precissava do nome do adaptador para mostrar para o usuario

Luís Gustavo Fabbro disse...

Andrews

A unit apenas mapeia as funções e estruturas do Windows para uso no Delphi. Isso quer dizer que é possível fazer as mesmas coisas descritas na documentação da API da Microsoft.

Se der uma olhada na documentação da estrutura IP_ADAPTER_INFO - que é retornada pela função GetAdaptersInfo - , verá que um dos campos é justamente o AdapterName.

[]s

Thiago disse...

Luiz, gostaria de saber porque o resultado exibido, depois de montar o exemplo acima, é zerado: 0000000000

Luís Gustavo Fabbro disse...

Thiago

Isso acontecerá se a função GetMACAddress falhar, situação em que todos os bytes do MAC Address estarão preenchidos com zeros. Você depurou essa função para ver o que o GetAdaptersInfo está retornando ? Ela está conseguindo localizar alguma placa de rede ?

[]s

Thiago disse...

Se fiz certo: Inaccessible value

Estou utilizando o vmware.
O engraçado é que outras 2 funções que seguem para o resultado de informações sobre o adaptador, o resultado é normal, dentro do esperado.

Luís Gustavo Fabbro disse...

Thiago

O "Inaccessible value" pode ser causado porque o AdapterInfo é um Pointer e não foi possível determinar seu conteúdo.

Ao depurar no passo-a-passo, o "for" até MAX_ADAPTER_ADDRESS_LENGTH é executado ? Que valores ele encontra para pAdapterInfo.Address[i] ?

Obs: Softwares de virtualização (como o vmware) costumam mascarar os adaptadores reais da máquina; é comum que vc tem que habilitá-los ou configurá-los manualmente. Consultando a configuração do VMWare, aparece alguma placa de rede habilitada ?

[]s

Ewerton Rodrigues disse...

Qual seria outra "informação" que eu poderia pegar junto com a Mac ? que tornaria este número realmente mais seguro e talvez bem mais difícil de coincidir com outro ou mesmo ser descoberto.

Vou tentar fazer o exemplo, mais já gostaria de implementar uma outra coisa para torna-la a função mais segura.

Grato desde já pela resposta

Luís Gustavo Fabbro disse...

Ewerton

Como digo no final do texto, você pode usar informações sobre a CPU, o sistema operacional (versão) ou outro hardware (serial do HD, por exemplo).

Há 2 posts no blog a respeito de como obter mais informações sobre a CPU : Obtendo informações da CPU em Delphi e Levantando os recursos existentes na CPU.

Informações sobre versão do Windows estão disponíveis via API, através da função GetVersionEx.

[]s

Anônimo disse...

Segue abaixo outra função que já testei com o Windows XP, 7 e 8 (32 e 64 bit):

Function MacAddress: String;
var
Lib: Cardinal;
GUID1, GUID2: TGUID;
Func: Function(GUID: PGUID): Longint; stdcall;
Begin
Result := '';
Lib := LoadLibrary('rpcrt4.dll');
If not (Lib = 0) Then
Try
@Func := GetProcAddress(Lib, 'UuidCreateSequential');
If Assigned(Func) Then
Begin
If (Func(@GUID1) = 0) and
(Func(@GUID2) = 0) and
(GUID1.D4[2] = GUID2.D4[2]) and
(GUID1.D4[3] = GUID2.D4[3]) and
(GUID1.D4[4] = GUID2.D4[4]) and
(GUID1.D4[5] = GUID2.D4[5]) and
(GUID1.D4[6] = GUID2.D4[6]) and
(GUID1.D4[7] = GUID2.D4[7]) Then
Begin
Result :=
IntToHex(GUID1.D4[2], 2) + '-' +
IntToHex(GUID1.D4[3], 2) + '-' +
IntToHex(GUID1.D4[4], 2) + '-' +
IntToHex(GUID1.D4[5], 2) + '-' +
IntToHex(GUID1.D4[6], 2) + '-' +
IntToHex(GUID1.D4[7], 2);
end;
end;
Finally
FreeLibrary(Lib);
end;
end;

Espero ter ajudado!

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.