terça-feira, 24 de janeiro de 2012

Como a senha de usuário é armazenada no Linux?

O Linux é um sistema multiusuário pois permite acesso simultâneo de múltiplos usuários ao computador. O sistema controla o acesso em contas de usuário para distinguir as pessoas diferentes que o usam. Cada usuário tem uma conta pessoal com um nome e uma senha.

Toda conta no sistema Linux tem uma entrada no arquivo '/etc/passwd'. Este arquivo contém entradas, uma linha por usuário, que especificam diversos atributos para cada conta. Cada entrada neste arquivo tem o mesmo formato, com campos separados por "dois pontos":

nome_do_usuário:senha:UID:GID:Nome Real:/diretório/pessoal:shell

Exemplo de arquivo '/etc/passwd':

root:x:0:0:root:/root:/bin/bash
daemon:x:2:2:daemon:/sbin:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
postgres:x:26:26:PostgreSQL Server:/var/lib/pgsql:/bin/bash
fulano:x:500:500:Fulano da Silva:/home/fulano:/bin/bash
siclano:x:501:501:Siclano Souza:/home/siclano:/bin/bash
beltrano:x:502:503::/home/beltrano:/bin/bash

As distribuições atuais utilizam adicionalmente uma ferramenta de proteção de senha, o 'shadow'. Esta ferramenta serve para ocultar a representação criptografada das senhas do arquivo '/etc/passwd', substituindo por um x, e então armazenando-as no arquivo '/etc/shadow', que não possui permissão de leitura aos usuários comuns. O arquivo '/etc/shadow' armazena as senhas em um formato criptografado, com algumas informações adicionais relacionadas as senhas dos usuários. Contém uma entrada por linha, separada em campos, para cada usuário definido no '/etc/passwd'.

nome_do_usuário:senha:última alteração:mínimo:máximo:aviso de expiração:inativo:expiração:

O primeiro campo contém o nome que identifica a conta, o segundo contém a representação criptografada da senha do usuário. Os campos seguintes contém: dias passados desde 01/01/1970 da última alteração da senha; dias nos quais antes a senha não pode ser alterada; dias nos quais a senha precisa ser alterada; dias prévios para aviso de expiração; dias após expiração nos quais a conta será considerada inativa ou desativada; dias desde 01/01/1970 para a conta expirar e o último campo é reservado para uso futuro.

Exemplo de arquivo '/etc/shadow':

root:$6$utLIMf6gY40$Eoe.2M7uqTK1UBtnz3yz4T:14768:0:99999:7:::
daemon:*:14715:0:99999:7:::
lp:*:14715:0:99999:7:::
shutdown:*:14715:0:99999:7:::
ftp:*:14715:0:99999:7:::
nobody:*:14715:0:99999:7:::
postgres:!!:14855::::::
fulano:$6$nsd3oKxkg0$kIEvBac347MZXYfOnrc56ybz:14768:0:99999:7:::
siclano:$6$AaNgoXJSPsv/$cupVfet0y4cIYLpPSWyNQM:14771:0:99999:7:::
beltrano:$6$Qc3AYPleBx0$ikGowHMwzkqRz9QGZzQfwfu:14770:0:99999:7:::

As senhas armazenadas no arquivo '/etc/passwd', ou atualmente no arquivo '/etc/shadow', são codificadas na forma de hashes, gerados pela função crypt(). Atualmente as distribuições Linux utilizam como padrão o algoritmo SHA-512 ao invés do método DES tradicional do Unix.

A função crypt() recebe dois argumentos, a senha digitada pelo usuário e uma string denominada "salt". Esta string tem o propósito de "salgar" ou "inchar" o hash gerado pelo algoritmo de dispersão. Assim, o hash armazenado não é simplesmente proveniente da senha mas da senha mais o salt. Isto dificulta uma tentativa de descoberta por uso de "hash tables" pois a função crypt() precisa ser executada em cada comparação.

A versão da função crypt() na glibc2 possui uma caracteristica adicional. A string salt pode iniciar com caracteres que identificam o algoritmo de criptografia, ao invés do DES tradicional. Estes caracteres seguem o padrão $ID$, onde ID é um valor referente a um dos algoritmos a seguir:

ID     Algoritmo
1      MD5
2a     Blowfish
5      SHA-256
6      SHA-512

A string completa com o hash, que representa a senha, é armazenada no arquivo '/etc/shadow' no formato $ID$SALT$SENHA. O caractere "$" (cifrão) delimita cada item da string. Por exemplo a linha a seguir (Nota: há uma quebra de linha no meio do hash para facilitar a apresentação, no arquivo é somente uma única linha):

nome_do_usuário:$6$eKn9QGMQ$Ofi1OoClVpw/cCbTsD4YUKgfurcQoBxsZ9Tlk5VBjTp
AjSYFt.M9shPtQVnouNr4/3PRDP/eMqkoWQpuBxsRk1:15362:0:99999:7:::

Os caracteres em salt e na senha criptografada são extraídos do conjunto {a-z A-Z 0-9 ./}. Em um sistema Linux, o salt é criado de forma aleatória durante a configuração da senha do usuário.

A seguir um código exemplo utilizando a função crypt(), em linguagem C, que retorna a string completa $ID$SALT$SENHA (compilar no GCC com a opção -lcrypt):

#include <stdio.h>
#include <unistd.h>
#include <crypt.h>

int main(void) {
  char salt[] = "$6$eKn9QGMQ";
  char *password;
 
  password = crypt(getpass("Password:"), salt);
 
  puts(password);
  return 0;
}

A função crypt() também está incluída em outras linguagens de programação, como Perl, PHP, Python e Ruby. Na linguagem Perl pode-se usar a linha de comando a seguir, no prompt do shell, para invocar o interpretador Perl com a função crypt():

$ perl -e 'print crypt("senha","\$algoritmohash\$salt\$") . "\n"'

Por exemplo:

$ perl -e 'print crypt("1234","\$6\$eKn9QGMQ\$") . "\n"'

Como visto, o sistema Linux possui um mecanismo interessante e bastante eficiente para controlar as contas e senhas dos usuários. Veja mais no artigo "Administrando usuários e grupos no sistema Linux" (http://dan-scientia.blogspot.com/2010/07/administrando-usuarios-e-grupos-no.html).

Um comentário:

  1. Prezado Daniel, parabéns pela excelente documentação sobre o esquema de criptografia de senhas no Linux.

    Abs. Robson Vaz

    ResponderExcluir