it-swarm-pt.com

Por que o char [] é preferido em relação ao String para senhas?

No Swing, o campo de senha tem um método getPassword() (retorna char[]) em vez do método usual getText() (retorna String). Da mesma forma, me deparei com uma sugestão para não usar String para lidar com senhas.

Por que String representa uma ameaça à segurança quando se trata de senhas? Parece inconveniente usar char[].

3086
Ahamed

Strings são imutáveis ​​. Isso significa que uma vez que você tenha criado o String, se outro processo puder despejar memória, não há como (além de reflection ) você poder se livrar dos dados antes garbage collection kicks in.

Com uma matriz, você pode apagar explicitamente os dados depois de terminar. Você pode sobrescrever a matriz com qualquer coisa que desejar e a senha não estará presente em nenhum lugar do sistema, mesmo antes da coleta de lixo.

Então, sim, esse é uma preocupação de segurança - mas mesmo usando char[] apenas reduz a janela de oportunidade para um invasor, e é somente para esse tipo específico de ataque.

Conforme observado nos comentários, é possível que as matrizes sendo movidas pelo coletor de lixo deixem cópias dispersas dos dados na memória. Eu acredito que isso é específico da implementação - o coletor de lixo pode limpar toda a memória, evitando esse tipo de coisa. Mesmo que aconteça, ainda há o tempo durante o qual o char[] contém os caracteres reais como uma janela de ataque.

3941
Jon Skeet

Embora outras sugestões aqui pareçam válidas, há outro bom motivo. Com simples String você tem chances muito maiores de acidentalmente imprimir a senha em logs , monitores ou algum outro local inseguro. char[] é menos vulnerável.

Considere isto:

public static void main(String[] args) {
    Object pw = "Password";
    System.out.println("String: " + pw);

    pw = "Password".toCharArray();
    System.out.println("Array: " + pw);
}

Impressões:

String: Password
Array: [[email protected]
1136
Konrad Garus

Para citar um documento oficial, o guia Arquitetura de criptografia Java diz isso sobre as senhas char[] vs. String (sobre criptografia baseada em senha, mas isso é mais geralmente sobre senhas, é claro):

Parece lógico coletar e armazenar a senha em um objeto Do tipo Java.lang.String. No entanto, aqui está a ressalva: Objects de Tipo String são imutáveis, ou seja, não há métodos definidos que Permitem alterar (sobrescrever) ou zerar o conteúdo de um String após o uso. Esse recurso torna os objetos String impróprios para Armazenar informações confidenciais de segurança, como senhas de usuários. Você deve sempre coletar e armazenar informações confidenciais de segurança em um array char.

A Diretriz 2-2 das Diretrizes de Codificação Segura para a Linguagem de Programação Java, Versão 4.0 também diz algo semelhante (embora seja originalmente no contexto do logging):

Diretriz 2-2: Não registre informações altamente confidenciais

Algumas informações, como senhas de números de seguridade social (SSNs) e , São altamente confidenciais. Esta informação não deve ser mantida Por mais tempo do que o necessário nem onde possa ser vista, mesmo pelos administradores . Por exemplo, ele não deve ser enviado para arquivos de log e Sua presença não deve ser detectada por meio de pesquisas. Alguns dados transitórios Podem ser mantidos em estruturas de dados mutáveis, como matrizes de caracteres e Apagadas imediatamente após o uso. A limpeza de estruturas de dados reduziu a eficácia Em sistemas de tempo de execução Java típicos, à medida que os objetos são movidos na memória De forma transparente para o programador.

Essa diretriz também tem implicações para a implementação e uso de bibliotecas de baixo nível Que não possuem conhecimento semântico dos dados Com os quais estão lidando. Por exemplo, uma biblioteca de análise de cadeia de caracteres de baixo nível Pode registrar o texto em que está trabalhando. Um aplicativo pode analisar um SSN Com a biblioteca. Isso cria uma situação em que os SSNs são Disponíveis para administradores com acesso aos arquivos de log.

639
Bruno

Matrizes de caracteres (char[]) podem ser apagadas após o uso, definindo cada caractere como zero e Strings não. Se alguém puder ver de alguma forma a imagem da memória, ela poderá ver uma senha em texto sem formatação se forem usadas Strings, mas se char[] for usado, depois de limpar os dados com 0, a senha será segura.

326
alephx

Algumas pessoas acreditam que você precisa sobrescrever a memória usada para armazenar a senha quando não precisar mais dela. Isso reduz a janela de tempo que um invasor deve ler a senha do sistema e ignora completamente o fato de que o invasor já precisa de acesso suficiente para sequestrar a memória da JVM para fazer isso. Um invasor com tanto acesso pode capturar seus eventos principais, tornando isso completamente inútil (AFAIK, então, por favor, corrija-me se eu estiver errado).

Atualizar

Graças aos comentários, tenho que atualizar minha resposta. Aparentemente, existem dois casos em que isso pode adicionar uma melhoria de segurança (muito) menor, uma vez que reduz o tempo que uma senha pode pousar no disco rígido. Ainda acho que é um exagero para a maioria dos casos de uso.

  • Seu sistema de destino pode estar mal configurado ou você tem que assumir que é e você tem que ser paranoico sobre core dumps (pode ser válido se os sistemas não forem gerenciados por um administrador). 
  • Seu software tem que ser excessivamente paranóico para evitar vazamentos de dados com o invasor obtendo acesso ao hardware - usando coisas como TrueCrypt (descontinuado), VeraCrypt ou CipherShed .

Se possível, desabilitar os core dumps e o arquivo de troca cuidaria dos dois problemas. No entanto, eles exigiriam direitos de administrador e podem reduzir a funcionalidade (menos memória para uso) e puxar RAM de um sistema em execução ainda seria uma preocupação válida.

201
josefx
  1. Seqüências de caracteres são imutáveis ​​em Java, se você armazenar a senha como texto simples, estará disponível na memória até que Garbage collector a apague e, como Strings são usadas no pool de String para reutilização, há grandes chances de permanecer memória por um longo período, o que representa uma ameaça à segurança. Desde que qualquer pessoa que tenha acesso ao despejo de memória pode encontrar a senha em texto não criptografado 
  2. Java recomendação usando getPassword() método de JPasswordField que retorna um char [] e deprecated getText() método que retorna a senha em texto claro, indicando razão de segurança. 
  3. toString () há sempre o risco de imprimir texto simples no arquivo de log ou no console, mas se usar o Array, você não imprimirá o conteúdo do array, em vez disso, seu local de memória será impresso. 

    String strPwd = "passwd";
    char[] charPwd = new char[]{'p','a','s','s','w','d'};
    System.out.println("String password: " + strPwd );
    System.out.println("Character password: " + charPwd );
    

    Senha da string: passwd

    Senha do caractere: [C @ 110b2345

Considerações finais: Apesar de usar char [] não é apenas o suficiente, você precisa apagar o conteúdo para ser mais seguro. Eu também sugiro trabalhar com senha criptografada ou hash em vez de texto simples e limpá-lo da memória assim que a autenticação for concluída.

131
Srujan Kumar Gulla

Não acho que seja uma sugestão válida, mas posso pelo menos adivinhar o motivo.

Eu acho que a motivação é querer ter certeza de que você pode apagar todos os vestígios da senha na memória imediatamente e com certeza depois de ser usada. Com um char[] você pode sobrescrever cada elemento da matriz com um espaço em branco ou algo com certeza. Você não pode editar o valor interno de um String dessa maneira.

Mas isso sozinho não é uma boa resposta; por que não apenas garantir que uma referência ao char[] ou String não escape? Então não há problema de segurança. Mas a coisa é que os objetos String podem ser intern()ed em teoria e mantidos vivos dentro do pool constante. Eu suponho que usar char[] proíba essa possibilidade.

76
Sean Owen

A resposta já foi dada, mas gostaria de compartilhar um problema que descobri recentemente com bibliotecas padrão Java. Enquanto eles tomam muito cuidado agora de substituir as strings de senha por char[] em todos os lugares (o que obviamente é uma coisa boa), outros dados críticos de segurança parecem ser ignorados quando se trata de limpá-los da memória.

Estou pensando em o PrivateKey class. Considere um cenário em que você carregaria uma chave RSA privada de um arquivo PKCS # 12, usando-a para executar alguma operação. Agora, neste caso, farejar a senha sozinha não ajudaria muito, desde que o acesso físico ao arquivo de chave seja adequadamente restrito. Como um invasor, você ficaria muito melhor se obtivesse a chave diretamente em vez da senha. A informação desejada pode ser vazada múltiplas, core dumps, uma sessão de depurador ou arquivos de swap são apenas alguns exemplos.

E, como se constata, não há nada que lhe permita limpar a memória de uma PrivateKey da memória, porque não há API que permita apagar os bytes que formam as informações correspondentes.

Esta é uma situação ruim, já que este paper descreve como esta circunstância poderia ser potencialmente explorada.

A biblioteca OpenSSL, por exemplo, sobrescreve seções críticas de memória antes que chaves privadas sejam liberadas. Como o Java é coletor de lixo, precisaríamos de métodos explícitos para limpar e invalidar informações privadas para chaves Java, que devem ser aplicadas imediatamente após o uso da chave. 

61
emboss

Como Jon Skeet afirma, não há como usar a reflexão. 

No entanto, se a reflexão é uma opção para você, você pode fazer isso.

public static void main(String[] args) {
    System.out.println("please enter a password");
    // don't actually do this, this is an example only.
    Scanner in = new Scanner(System.in);
    String password = in.nextLine();
    usePassword(password);

    clearString(password);

    System.out.println("password: '" + password + "'");
}

private static void usePassword(String password) {

}

private static void clearString(String password) {
    try {
        Field value = String.class.getDeclaredField("value");
        value.setAccessible(true);
        char[] chars = (char[]) value.get(password);
        Arrays.fill(chars, '*');
    } catch (Exception e) {
        throw new AssertionError(e);
    }
}

quando correr

please enter a password
hello world
password: '***********'

Nota: se o caractere String [] foi copiado como parte de um ciclo de GC, existe uma chance de a cópia anterior estar em algum lugar na memória. 

Essa cópia antiga não apareceria em um dump de heap, mas se você tiver acesso direto à memória bruta do processo, poderá vê-la. Em geral, você deve evitar que alguém tenha esse acesso.

46
Peter Lawrey

Edit: Voltando a esta resposta após um ano de pesquisa de segurança, eu percebo que faz a infeliz implicação de que você realmente compare senhas de texto simples. Por favor não. Use um hash unidirecional seguro com um sal e um número razoável de iterações . Considere o uso de uma biblioteca: esse material é difícil de acertar!

Resposta original: E quanto ao fato de que String.equals () usa avaliação de curto-circuito e é, portanto, vulnerável a um ataque de temporização? Pode ser improvável, mas você poderia teoricamente marcar a comparação de senhas para determinar a seqüência correta de caracteres.

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        // Quits here if Strings are different lengths.
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            // Quits here at first different character.
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

Mais alguns recursos sobre ataques de temporização:

37
Graph Theory

Não há nada que o array char forneça a você, String, a menos que você o limpe manualmente após o uso, e eu não vi ninguém realmente fazendo isso. Então para mim a preferência de char [] vs String é um pouco exagerada.

Dê uma olhada no amplamente utilizado Biblioteca Spring Security aqui e pergunte-se - as senhas incompetentes ou char [] do Spring Security não fazem muito sentido. Quando algum hacker desagradável pega em memória de sua RAM, tenha certeza de que ela conseguirá todas as senhas, mesmo que você use maneiras sofisticadas de ocultá-las.

No entanto, o Java muda o tempo todo, e alguns recursos assustadores como o recurso de Deduplicação de Cadeia do Java 8 pode internar objetos String sem o seu conhecimento. Mas essa conversa é diferente.

33
Oleg Mikheev

As cordas são imutáveis ​​e não podem ser alteradas depois de criadas. Criar uma senha como uma cadeia de caracteres deixará referências de dispersão para a senha no heap ou no conjunto de cadeias. Agora, se alguém fizer um despejo de heap do processo Java e examinar cuidadosamente, ele poderá adivinhar as senhas. É claro que essas sequências não utilizadas serão coletadas, mas isso depende de quando o GC entra em ação.

Por outro lado, o char [] é mutável assim que a autenticação é feita, você pode sobrescrevê-los com qualquer caractere como todos os M's ou barras invertidas. Agora, mesmo que alguém tenha um dump de heap, ele pode não conseguir as senhas que não estão em uso no momento. Isso lhe dá mais controle no sentido de limpar o conteúdo do Objeto ao invés de esperar que o GC o faça.

29
Geek

1) Como Strings são imutáveis ​​em Java se você armazena a senha como texto simples, ela estará disponível na memória até que Garbage collector a apague e como String é usada no pool de String para reutilização, há uma grande chance de permanecer na memória para a longa duração, o que representa uma ameaça à segurança. Como qualquer pessoa que tenha acesso ao despejo de memória pode encontrar a senha em texto não criptografado e essa é outra razão pela qual você deve sempre usar uma senha criptografada em vez de texto sem formatação. Como as Strings são imutáveis, não há como o conteúdo de Strings ser alterado porque qualquer alteração produzirá uma nova String, enquanto se char [] você ainda pode definir todos os elementos como em branco ou zero. Portanto, armazenar a senha no array de caracteres reduz o risco de roubo da senha.

2) O próprio Java recomenda o uso do método getPassword () do JPasswordField, que retorna um método char [] e getText () obsoleto, que retorna a senha em texto claro indicando o motivo de segurança. É bom seguir os conselhos da equipe de Java e aderir ao padrão, em vez de ir contra isso.

16
Neeraj Gahlawat

A resposta curta e direta seria porque char[] é mutável enquanto que String os objetos não são.

Strings em Java são objetos imutáveis. É por isso que eles não podem ser modificados depois de criados e, portanto, a única maneira de o conteúdo deles ser removido da memória é coletar o lixo. Será somente quando a memória liberada pelo objeto puder ser sobrescrita e os dados desaparecerem.

Agora, a coleta de lixo no Java não acontece em nenhum intervalo garantido. A String pode, portanto, persistir na memória por um longo tempo, e se um processo travar durante esse tempo, o conteúdo da string pode acabar em um despejo de memória ou algum log. 

Com uma matriz character, você pode ler a senha, terminar de trabalhar com ela o mais rápido possível e alterar o conteúdo imediatamente.

16
Pritam Banerjee

String é imutável e vai para o pool de strings. Uma vez escrita, não pode ser sobrescrita.

char[] é uma matriz que você deve sobrescrever depois de usar a senha e é assim que deve ser feito:

char[] passw = request.getPassword().toCharArray()
if (comparePasswords(dbPassword, passw) {
 allowUser = true;
 cleanPassword(passw);
 cleanPassword(dbPassword);
 passw=null;
}

private static void cleanPassword (char[] pass) {
 for (char ch: pass) {
  ch = '0';
 }
}

Um cenário em que o invasor poderia usá-lo é um crashdump - quando a JVM falha e gera um despejo de memória - você poderá ver a senha.

Isso não é necessariamente um invasor externo mal-intencionado. Pode ser um usuário de suporte que tenha acesso ao servidor para fins de monitoramento. Ele poderia espreitar um crashdump e encontrar as senhas.

15
ACV

String em Java é imutável. Portanto, sempre que uma string é criada, ela permanecerá na memória até ser coletada como lixo. Então, qualquer pessoa que tenha acesso à memória pode ler o valor da string. 
Se o valor da string for modificado, ele acabará criando uma nova string. Portanto, tanto o valor original quanto o valor modificado permanecem na memória até serem coletados como lixo. 

Com o array de caracteres, o conteúdo do array pode ser modificado ou apagado assim que o objetivo da senha for exibido. O conteúdo original da matriz não será encontrado na memória depois de ser modificado e antes mesmo da coleta de lixo entrar em ação.

Por causa da preocupação de segurança, é melhor armazenar a senha como uma matriz de caracteres.

11
Saathvik

Usar senha em String ou Char [] é sempre discutível porque ambas as abordagens tinham seu próprio utilitário e desvantagem também. Isso depende da necessidade que o usuário espera ser atendido. As linhas abaixo podem ajudar a entender melhor quando usar o container: Como String em Java é imutável, sempre que alguém tentar manipular sua string, ele cria um novo objeto e o existente permanece não-temperado. Isso pode ser visto como uma vantagem para armazenar senhas como String, mas por outro lado. Objeto String permanece na memória mesmo após o uso. Então, de alguma forma, se alguém conseguiu a localização da memória pode facilmente rastrear sua senha armazenada nesse local. Enquanto vem para Char [] que é mutável, mas teve vantagem que após o uso, o programador pode explicitamente limpar a matriz ou substituir os valores. De modo que após o uso não é limpo e ninguém poderia saber sobre a informação que você tinha armazenado.

Com base nas circunstâncias acima, pode-se ter a ideia de ir com String ou Char [] para sua necessidade.

Obrigado.

1
Neeraj

Em suma, isso é por causa do que pode acontecer com a senha armazenada depois que você terminar de usá-la. 

Uma cadeia de caracteres pode (potencialmente) permanecer na memória por muito tempo depois que você parou de usá-la, mas uma matriz de caracteres pode ser esvaziada para que não contenha mais dados.

Tudo isso tem a ver com a maneira como os e coletores de lixo e objetos imutáveis ​​ trabalho.

0
hamza belmellouki