Por que char [] é preferido sobre String para senhas?

3422

No Swing, o campo da senha possui um método getPassword()(retornos char[]) em vez do método usual getText()(retornos String). Da mesma forma, encontrei uma sugestão para não usar Stringpara lidar com senhas.

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

Ahamed
fonte

Respostas:

4290

Strings são imutáveis . Isso significa que, depois de criar String, se outro processo puder despejar memória, não há como (além da reflexão ) você poder se livrar dos dados antes que a coleta de lixo comece .

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

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

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

Jon Skeet
fonte
3
Se um processo tiver acesso à memória do seu aplicativo, isso já é uma violação de segurança, certo?
Yeti
5
@Yeti: Sim, mas não é preto e branco. Se eles puderem obter apenas um instantâneo da memória, você deseja reduzir quanto dano esse instantâneo pode causar ou reduzir a janela durante a qual um instantâneo realmente sério pode ser obtido.
Jon Skeet
11
Um método de ataque comum é executar um processo que aloca muita memória e, em seguida, procura dados úteis restantes, como senhas. O processo não precisa de nenhum acesso mágico ao espaço de memória de outro processo; apenas confia em outros processos que morrem sem limpar primeiro os dados confidenciais e o sistema operacional também não limpa a memória (ou buffers de página) antes de disponibilizá-lo para um novo processo. A limpeza de senhas armazenadas em char[]locais corta essa linha de ataque, algo que não é possível ao usar String.
Ted Hopp
Se o SO não limpar a memória antes de entregá-la a outro processo, o SO terá grandes problemas de segurança! No entanto, tecnicamente, a limpeza é feita com truques no modo protegido e, se a CPU estiver com problemas (por exemplo, Intel Meldown), ainda é possível ler o conteúdo antigo da memória.
Mikko Rantalainen em 21/04
1222

Embora outras sugestões aqui pareçam válidas, há outro bom motivo. Com simplicidade, Stringvocê tem chances muito maiores de imprimir acidentalmente a senha em logs , monitores ou em 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: [C@5829428e
Konrad Garus
fonte
40
@voo, mas duvido que você registre via escrita direta para transmitir e concatenação. estrutura de log transformaria o char [] em boa saída
bestsss
41
@ Thr4wn A implementação padrão de toStringé classname@hashcode. [Crepresenta char[], o resto é código hash hexadecimal.
precisa saber é o seguinte
15
Idéia interessante. Gostaria de salientar que isso não transpõe para Scala, que possui um toString significativo para matrizes.
Mauhiz
37
Eu escreveria um Passwordtipo de classe para isso. É menos obscuro e mais difícil de passar acidentalmente para algum lugar.
rightfold
8
Por que alguém presumiria que o array char seria lançado como um objeto? Não sei ao certo por que todo mundo gosta dessa resposta. Suponha que você tenha feito isso: System.out.println ("Password" .toCharArray ());
GC_
680

Para citar um documento oficial, o guia Java Cryptography Architecture diz isso sobre char[]vs. Stringsenhas (sobre criptografia baseada em senha, mas 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 do tipo Stringsão imutáveis, ou seja, não há métodos definidos que permitam alterar (substituir) ou zerar o conteúdo de um String pós-uso. Esse recurso torna os Stringobjetos inadequados para armazenar informações confidenciais de segurança, como senhas de usuários. Você sempre deve coletar e armazenar informações confidenciais de segurança em uma charmatriz.

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 log):

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

Algumas informações, como números de segurança social (SSNs) e senhas, são altamente confidenciais. Essas informações não devem ser mantidas por mais tempo que o necessário, nem onde possam ser vistas, mesmo pelos administradores. Por exemplo, ele não deve ser enviado para arquivos de log e sua presença não deve ser detectável por meio de pesquisas. Alguns dados transitórios podem ser mantidos em estruturas de dados mutáveis, como matrizes de caracteres, e limpos imediatamente após o uso. A limpeza de estruturas de dados reduziu a eficácia em sistemas Java runtime 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 o uso de bibliotecas de nível inferior que não possuem conhecimento semântico dos dados com os quais estão lidando. Como exemplo, uma biblioteca de análise de seqüência de caracteres de baixo nível pode registrar o texto em que trabalha. Um aplicativo pode analisar um SSN com a biblioteca. Isso cria uma situação em que os SSNs estão disponíveis para administradores com acesso aos arquivos de log.

Bruno
fonte
4
essa é exatamente a referência falsa / falsa que falo abaixo da resposta de Jon, é uma fonte bem conhecida com muitas críticas.
bestsss 22/01
37
@bestass você também pode citar uma referência?
user961954
10
@ bestest desculpe, mas Stringé muito bem compreendido e como ele se comporta na JVM ... existem boas razões para usar char[]no lugar de Stringquando lidar com senhas de maneira segura.
SnakeDoc
3
mais uma vez, embora a senha seja passada como uma string do navegador para a solicitação como 'string', não char? portanto, não importa o que você faça, é uma string; nesse ponto, ela deve ser usada e descartada, nunca armazenada na memória?
Dawesi 5/08/19
3
@ Dawesi - At which point- isso é específico do aplicativo, mas a regra geral é fazê-lo assim que você obtém algo que deveria ser uma senha (texto simples ou não). Você o obtém do navegador como parte de uma solicitação HTTP, por exemplo. Você não pode controlar a entrega, mas pode controlar seu próprio armazenamento; assim que obtê-lo, coloque-o em um caractere [], faça o que você precisa fazer com ele, defina tudo como '0' e deixe o gc recupere-o.
Luis.espinal 31/03/19
354

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

alephx
fonte
13
Não seguro por padrão. Se estivermos falando de um aplicativo da Web, a maioria dos contêineres da Web passará a senha para o HttpServletRequestobjeto em texto sem formatação. Se a versão da JVM for 1.6 ou inferior, ela estará no espaço permitido. Se estiver na versão 1.7, ainda será legível até que seja coletado. (Toda vez que é.)
AVGVSTVS
4
@avgvstvs: strings não são movidas automaticamente para o espaço permgen, que se aplica somente a strings intern'ed. Além disso, o espaço permgen também está sujeito à coleta de lixo, apenas em menor proporção. O problema real com o espaço permgen é o tamanho fixo, que é exatamente a razão pela qual ninguém deve chamar intern()cordas arbitrárias sem pensar . Mas você está certo de que as Stringinstâncias existem em primeiro lugar (até serem coletadas) e transformá-las em char[]matrizes depois não as altera.
Holger
3
@Holger, consulte docs.oracle.com/javase/specs/jvms/se6/html/… "Caso contrário, uma nova instância da classe String será criada contendo a sequência de caracteres Unicode fornecida pela estrutura CONSTANT_String_info; essa instância da classe é o resultado de derivação literal de string. Finalmente, o método interno da nova instância String é chamado. " Na 1.6, a JVM chamaria o estagiário para você quando detectasse sequências idênticas.
AVGVSTVS
3
@Holger, você está correto Eu confluí pool constante e pool de strings, mas também é falso que o espaço permgen seja aplicado apenas a strings internamente. Antes da 1.7, o constant_pool e o string_pool residiam no espaço permgen. Isso significa que a única classe de Strings que foram alocadas para o heap foi como você disse, new String()ou StringBuilder.toString() eu gerenciei aplicativos com muitas constantes de strings e tivemos muitos permgen creep como resultado. Até 1.7.
AVGVSTVS
5
@avgvstvs: bem, as constantes de string são, como manda o JLS, sempre internadas; portanto, a declaração de que strings estendidas acabavam no espaço permitido, aplicada a constantes de string implicitamente. A única diferença é que as constantes de string foram criadas no espaço permgen em primeiro lugar, enquanto chamar intern()uma string arbitrária pode causar a alocação de uma string equivalente no espaço permgen. O último poderia obter GC'ed, se não houvesse uma seqüência literal do mesmo conteúdo compartilhando esse objeto ...
Holger
220

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 precisa ler a senha do seu sistema e ignora completamente o fato de que o invasor já precisa de acesso suficiente para seqüestrar a memória da JVM para fazer isso. Um invasor com tanto acesso pode capturar seus principais eventos, tornando isso completamente inútil (AFAIK, por favor, corrija-me se estiver errado).

Atualizar

Graças aos comentários, tenho que atualizar minha resposta. Aparentemente, existem dois casos em que isso pode adicionar uma (muito) pequena melhoria de segurança, pois 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ê deve assumir que é e deve ser paranóico em relação aos core dumps (pode ser válido se os sistemas não forem gerenciados por um administrador).
  • Seu software deve 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, desativar os dumps principais e o arquivo de troca resolveriam os dois problemas. No entanto, eles exigiriam direitos de administrador e podem reduzir a funcionalidade (menos memória para usar) e extrair RAM de um sistema em execução ainda seria uma preocupação válida.

josefx
fonte
33
Eu substituiria "completamente inútil" por "apenas uma pequena melhoria de segurança". Por exemplo, você pode obter acesso a um despejo de memória se tiver acesso de leitura a um diretório tmp, uma máquina mal configurada e uma falha no aplicativo. Nesse caso, você não seria capaz de instalar um keylogger, mas poderia analisar o dump principal.
Joachim Sauer
44
Limpar dados não criptografados da memória assim que você terminar com eles é considerado uma prática recomendada, não porque é à prova de falhas (não é); mas porque reduz o nível de exposição a ameaças. Ataques em tempo real não são impedidos com isso; mas porque ele serve como uma ferramenta de mitigação de danos, reduz significativamente a quantidade de dados expostos em um ataque retroativo a um instantâneo de memória (por exemplo, uma cópia da memória de seus aplicativos que foi gravada em um arquivo de troca ou que foi lida com falta de memória) de um servidor em execução e movido para outro antes de seu estado falhar).
Dan Is Fiddling Por Firelight
9
Tendo a concordar com a atitude dessa resposta. Atrevo-me a propor que a maioria das violações de segurança ocorre em um nível de abstração muito mais alto do que os bits na memória. Certamente, provavelmente existem cenários em sistemas de defesa hiper seguros, onde isso pode ser uma preocupação considerável, mas pensar seriamente nesse nível é um exagero para 99% dos aplicativos em que o .NET ou Java estão sendo aproveitados (como relacionado à coleta de lixo).
kingdango
10
Após a penetração Heartbleed da memória do servidor, revelando senhas, eu substituiria a string "apenas uma pequena melhoria de segurança" por "absolutamente essencial para não usar String em senhas, mas em vez disso, use um char []".
Peter VDL
9
O @PetervdL heartbleed permitiu apenas a leitura de uma coleção específica de buffers reutilizados (usada para dados críticos de segurança e E / S de rede sem limpeza entre - por motivos de desempenho), você não pode combinar isso com uma String Java, pois eles são projetados por não serem reutilizáveis . Nem você pode ler na memória aleatória usando Java para obter o conteúdo de uma String. Os problemas de linguagem e design que levam ao heartbleed simplesmente não são possíveis com o Java Strings.
josefx
86

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

Eu acho que a motivação é ter certeza de que você pode apagar todo o rastreamento da senha na memória imediatamente e com segurança depois que ela for 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 Stringdessa maneira.

Mas isso por si só não é uma boa resposta; por que não apenas garantir que uma referência char[]ou Stringnão escape? Então não há problema de segurança. Mas o fato é que os Stringobjetos podem ser intern()edificados em teoria e mantidos vivos dentro da piscina constante. Suponho que o uso char[]proíba essa possibilidade.

Sean Owen
fonte
4
Eu não diria que o problema é que suas referências irão ou não "escapar". Só que as strings permanecerão inalteradas na memória por um tempo adicional, enquanto char[]podem ser modificadas e, então, é irrelevante se elas são coletadas ou não. E como a internação de strings precisa ser feita explicitamente para não literais, é o mesmo que dizer que a char[]pode ser referenciada por um campo estático.
Groo
1
não é a senha na memória como uma string do post do formulário?
Dawesi
66

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

Estou pensando, por exemplo, na classe PrivateKey . 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, nesse caso, farejar a senha por si só não ajudaria muito, desde que o acesso físico ao arquivo-chave seja adequadamente restrito. Como invasor, você seria muito melhor se obtivesse a chave diretamente em vez da senha. As informações desejadas podem ser coletor vazado, core dumps, sessão de depurador ou arquivos de troca são apenas alguns exemplos.

E, como se vê, não há nada que permita limpar as informações privadas de um PrivateKeyda memória, porque não há API que permita limpar os bytes que formam as informações correspondentes.

Esta é uma situação ruim, pois este artigo descreve como essa circunstância pode ser potencialmente explorada.

A biblioteca OpenSSL, por exemplo, substitui as seções críticas da memória antes que as chaves privadas sejam liberadas. Como o Java é coletado pelo 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.

gravar
fonte
Uma maneira de contornar isso é usar uma implementação PrivateKeyque na verdade não carrega seu conteúdo privado na memória: por exemplo, por meio de um token de hardware PKCS # 11. Talvez uma implementação de software do PKCS # 11 possa cuidar da limpeza manual da memória. Talvez usar algo como a loja NSS (que compartilha a maior parte de sua implementação com o PKCS11tipo de loja em Java) seja melhor. O KeychainStore(armazenamento de chaves OSX) carrega o conteúdo completo da chave privada em suas PrivateKeyinstâncias, mas não deve ser necessário. (Não sei o que o WINDOWS-MYKeyStore faz no Windows.)
de Bruno
@Bruno Claro, os tokens baseados em hardware não sofrem com isso, mas e as situações em que você é mais ou menos forçado a usar uma chave de software? Nem toda implantação tem orçamento para pagar um HSM. Em algum momento, os armazenamentos de chaves de software precisarão carregar a chave na memória; portanto, na IMO, devemos pelo menos ter a opção de limpar a memória à vontade novamente.
grava
Absolutamente, eu estava pensando se algumas implementações de software equivalentes a um HSM poderiam se comportar melhor em termos de limpeza da memória. Por exemplo, ao usar a autenticação de cliente com o Safari / OSX, o processo do Safari nunca realmente vê a chave privada, a biblioteca SSL subjacente fornecida pelo sistema operacional fala diretamente com o daemon de segurança que solicita ao usuário o uso da chave do chaveiro. Embora tudo isso seja feito em software, uma separação semelhante como essa pode ajudar se a assinatura for delegada a uma entidade diferente (mesmo baseada em software) que descarregaria ou limparia melhor a memória.
Bruno
@Bruno: Idéia interessante, uma camada adicional de indireção que cuida da limpeza da memória resolveria isso de forma transparente. Escrever um invólucro PKCS # 11 para o armazenamento de chaves do software já pode fazer o truque?
grava
51

Como Jon Skeet afirma, não há maneira, exceto usando 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 char [] da String foi copiado como parte de um ciclo do GC, é possível que a cópia anterior esteja em algum lugar na memória.

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

Peter Lawrey
fonte
2
Melhor fazer também algo para impedir a impressão do tamanho da senha que obtemos '***********'.
chux - Restabelece Monica
@chux, você pode usar um caractere de largura zero, embora isso possa ser mais confuso do que útil. Não é possível alterar o comprimento de uma matriz de caracteres sem usar Inseguro. ;)
Peter Lawrey
5
Por causa da desduplicação de string do Java 8, acho que fazer algo assim pode ser bastante destrutivo ... você pode acabar limpando outras seqüências de caracteres em seu programa que, por acaso, tinham o mesmo valor da senha String. Improvável, mas possível ...
jamp
1
@PeterLawrey Ele deve ser ativado com um argumento da JVM, mas está lá. Pode ler sobre isso aqui: blog.codecentric.de/en/2014/08/…
jamp
1
Há uma grande chance de que a senha ainda esteja no Scannerbuffer interno do e, como você não o usou System.console().readPassword(), na forma legível na janela do console. Mas para os casos de uso mais práticos, a duração da usePasswordexecução é o problema real. Por exemplo, ao fazer uma conexão com outra máquina, leva um tempo significativo e informa ao invasor que é o momento certo para procurar a senha no heap. A única solução é impedir que os invasores leiam a memória heap…
Holger
42

Por todas essas razões, deve-se escolher uma matriz char [] em vez de String para uma senha.

1. Como as Strings são imutáveis ​​em Java, se você armazenar a senha como texto sem formatação, ela estará disponível na memória até que o Garbage Collector a limpe, e como a String é usada no pool de String para reutilização, há uma chance muito grande de permanecem na memória por um longo período, 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, esse é outro motivo pelo 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 das Strings poder ser alterado porque qualquer alteração produzirá uma nova String, enquanto que se você usar um char [], poderá definir todos os elementos como em branco ou zero. Portanto, armazenar uma senha em uma matriz de caracteres reduz claramente o risco de segurança de roubar uma senha.

2) próprio Java recomenda o uso do método getPassword () do JPasswordField, que retorna um char [], em vez do método obsoleto getText (), que retorna senhas em texto não criptografado, indicando os motivos de segurança. É bom seguir os conselhos da equipe Java e aderir aos padrões, em vez de ir contra eles.

3. Com String, há sempre o risco de imprimir texto sem formatação em um arquivo de log ou console, mas se você usar uma matriz, não imprimirá o conteúdo de uma matriz, mas seu local de memória será impresso. Embora não seja um motivo real, ainda faz sentido.

String strPassword="Unknown";
char[] charPassword= new char[]{'U','n','k','w','o','n'};
System.out.println("String password: " + strPassword);
System.out.println("Character password: " + charPassword);

String password: Unknown
Character password: [C@110b053

Referenciado neste blog . Eu espero que isso ajude.

Ser humano
fonte
10
Isso é redundante. Esta resposta é uma versão exata da resposta escrita por @SrujanKumarGulla stackoverflow.com/a/14060804/1793718 . Por favor, não copie e cole ou duplique a mesma resposta duas vezes.
Sorte
Qual é a diferença entre 1.) System.out.println ("Senha do caractere:" + charPassword); e 2.) System.out.println (charPassword); Porque está dando o mesmo "desconhecido" que a saída.
28717
3
@ Lucky Infelizmente, a resposta mais antiga a que você vinculou foi plagiada no mesmo blog que esta resposta e agora foi excluída. Consulte meta.stackoverflow.com/questions/389144/… . Essa resposta simplesmente cortou e colou no mesmo blog e não adicionou nada, portanto, deveria ter sido simplesmente um comentário com link para a fonte original.
Skomisa 18/09/19
41

Edit: Voltando a esta resposta após um ano de pesquisa de segurança, percebo que isso implica a lamentável implicação de que você realmente compararia senhas em texto simples. Por favor não. Use um hash unidirecional seguro com um sal e um número razoável de iterações . Considere usar uma biblioteca: é difícil acertar essas coisas!

Resposta original: E o fato de o String.equals () usar avaliação de curto-circuito e, portanto, é vulnerável a um ataque de tempo? Pode ser improvável, mas você poderia teoricamente cronometrar a comparação da senha para determinar a sequê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 tempo:

Teoria dos grafos
fonte
Mas isso também poderia estar na comparação char [], em algum lugar estaríamos fazendo o mesmo na validação de senha. então, como char [] é melhor que string?
Mohit Kanwar
3
Você está absolutamente correto, o erro pode ser cometido de qualquer maneira. Saber o problema é a coisa mais importante aqui, considerando que não existe um método explícito de comparação de senhas em Java para senhas baseadas em String ou baseadas em char []. Eu diria que a tentação de usar compare () para Strings é um bom motivo para usar char []. Dessa forma, você pelo menos tem controle sobre como a comparação é feita (sem estender a String, o que é um problema).
Graph Theory
Além de comparar senhas em texto puro não é a coisa certa de qualquer maneira, a tentação de usar Arrays.equalspara char[]é tão alta quanto para String.equals. Se alguém se importava, havia uma classe de chave dedicada encapsulando a senha real e cuidando dos problemas - oh espere, os pacotes de segurança reais têm classes de chave dedicadas, essas perguntas e respostas são apenas um hábito fora delas, por exemplo JPasswordField, para usar char[]em vez de String(onde os algoritmos reais usam de byte[]qualquer maneira).
Holger
O software relevante para segurança deve fazer algo como sleep(secureRandom.nextInt())antes de rejeitar uma tentativa de login de qualquer maneira, isso não apenas remove a possibilidade de ataques cronometrados, mas também neutraliza as tentativas de força bruta.
Holger
36

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

Dê uma olhada na biblioteca Spring Security amplamente usada aqui e pergunte a si mesmo - os caras do Spring Security são incompetentes ou as senhas char [] simplesmente não fazem muito sentido. Quando um hacker desagradável agarra despejos de memória da sua RAM, verifique se ele obterá todas as senhas, mesmo se você usar maneiras sofisticadas de escondê-las.

No entanto, o Java muda o tempo todo e alguns recursos assustadores, como o recurso de redução de redundância de seqüência do Java 8, podem internar objetos String sem o seu conhecimento. Mas isso é uma conversa diferente.

Oleg Mikheev
fonte
Por que a desduplicação de string é assustadora? Isso só se aplica quando há pelo menos duas strings com o mesmo conteúdo. Portanto, que perigo surgiria se essas duas strings já idênticas compartilhassem a mesma matriz? Ou vamos perguntar o contrário: se não houver deduplicação de string, qual é a vantagem do fato de ambas as strings terem uma matriz distinta (com o mesmo conteúdo)? Em ambos os casos, haverá uma série de conteúdos que estar vivo, pelo menos enquanto a Cadeia de vida mais longa do que o conteúdo está vivo ...
Holger
@Holger qualquer coisa que esteja fora de seu controle é um risco potencial ... por exemplo, se dois usuários tiverem a mesma senha, esse recurso maravilhoso armazenará os dois em um único char [], tornando evidente que eles são iguais, não tendo certeza se isso é um risco enorme, mas ainda
Oleg Mikheev
Se você tiver acesso à memória heap e às duas instâncias de cadeia, não importa se as cadeias apontam para a mesma matriz ou para duas matrizes do mesmo conteúdo, é fácil descobrir cada uma. Especialmente, como é irrelevante de qualquer maneira. Se você estiver nesse ponto, pegue as duas senhas, idênticas ou não. O erro real está no uso de senhas de texto simples em vez de hashes salgados.
Holger
@Holger para verificar a senha, ele deve estar em texto não criptografado na memória por algum tempo, 10 ms, mesmo que seja apenas para criar um hash salgado. Então, se houver duas senhas idênticas mantidas na memória, mesmo por 10 ms, a desduplicação poderá entrar em ação. Se ele realmente estica as cordas, elas são mantidas na memória por muito mais tempo. O sistema que não reiniciar por meses coletará muitos deles. Apenas teorizando.
precisa
Parece que você tem um mal-entendido fundamental sobre a redução de redundância de string. Ele não “interna strings”, tudo o que faz é permitir que strings com o mesmo conteúdo aponte para a mesma matriz, o que na verdade reduz o número de instâncias de matriz que contêm a senha de texto sem formatação, pois todas, exceto uma instância de matriz, podem ser recuperadas e substituídas por outros objetos imediatamente. Essas strings ainda são coletadas como qualquer outra string. Talvez ajude, se você entender que a deduplicação é realmente feita pelo coletor de lixo, para cadeias que sobreviveram apenas a vários ciclos de GC.
21917 Holger
30

Strings são imutáveis ​​e não podem ser alteradas depois de criadas. Criar uma senha como uma sequência deixará referências dispersas para a senha na pilha ou no pool de Sequências. Agora, se alguém pegar um monte de lixo do processo Java e varrer cuidadosamente, poderá adivinhar as senhas. É claro que essas seqüências não usadas serão coletadas como lixo, mas isso depende de quando o GC entra em ação.

Por outro lado, char [] é mutável assim que a autenticação é concluída, você pode substituí-los por qualquer caractere como todos os M's ou barras invertidas. Agora, mesmo que alguém faça um despejo de pilha, ele pode não conseguir obter 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 e esperar o GC fazer isso.

Nerd
fonte
Eles serão analisados ​​apenas se a JVM em questão for> 1.6. Antes de 1.7, todas as strings eram armazenadas em permgen.
Avgvstvs
@avgvstvs: “todas as strings foram armazenadas em permgen” estão completamente erradas. Somente cadeias internas foram armazenadas lá e, se não fossem originárias de literais de cadeias referenciadas por código, elas ainda seriam coletadas no lixo. Apenas pense sobre isso. Se geralmente as cadeias nunca foram submetidas à GCG nas JVMs anteriores à 1.7, como um aplicativo Java poderia sobreviver por mais de alguns minutos?
Holger
@ Holger Isso é falso. Internado stringsE o Stringpool (pool de seqüências de caracteres usadas anteriormente) foram AMBOS armazenados em Permgen antes de 1.7. Além disso, consulte a seção 5.1: docs.oracle.com/javase/specs/jvms/se6/html/… A JVM sempre verificava Stringsse eles tinham o mesmo valor de referência e ligaria String.intern()para VOCÊ. O resultado foi que toda vez que a JVM constant_pooldetectasse Strings idênticas no ou no heap, as moveria para permgen. E trabalhei em várias aplicações com "rastejando permgen" até 1.7. Foi um problema real.
precisa saber é
Para recapitular: Até 1.7, as Strings começaram no heap, quando foram usadas, foram colocadas no constant_poolque estava localizado no permgen e, se uma string foi usada mais de uma vez, ela seria internada.
AVGVSTVS
@avgvstvs: Não existe um pool de strings usadas anteriormente. Você está jogando coisas totalmente diferentes juntas. Há um conjunto de strings de tempo de execução que contém literais e strings explicitamente internados, mas nenhum outro. E cada classe tem seu pool constante contendo constantes em tempo de compilação . Essas cadeias são adicionadas automaticamente ao pool de tempo de execução, mas somente essas , nem todas as cadeias.
Holger
21

String é imutável e vai para o pool de strings. Uma vez escrito, não pode ser substituído.

char[] é uma matriz que você deve substituir 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) {

Arrays.fill(pass, '0');
}

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

Isso não é necessariamente um invasor externo malicioso. Pode ser um usuário de suporte que tenha acesso ao servidor para fins de monitoramento. Ele poderia espiar um despejo de memória e encontrar as senhas.

ACV
fonte
2
ch = nulo; você não pode fazer isso #
Yugerten
Mas request.getPassword()já não cria a string e a adiciona ao pool?
Tv1
1
ch = '0'altera a variável local ch; não tem efeito na matriz. E seu exemplo é inútil de qualquer maneira, você começa com uma instância de string que você chama toCharArray(), criando uma nova matriz e, mesmo que você sobrescreva a nova matriz corretamente, ela não muda a instância da string, por isso não tem vantagem em usar apenas a string instância.
Holger
@ Holger obrigado. Corrigido o código de limpeza da matriz de caracteres.
ACV
3
Você pode simplesmente usarArrays.fill(pass, '0');
Holger
18

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

Stringsem Java são objetos imutáveis. É por isso que eles não podem ser modificados depois de criados e, portanto, a única maneira de remover o conteúdo da memória é fazer com que o lixo seja coletado. Somente então será possível substituir a memória liberada pelo objeto e os dados desaparecerão.

Agora, a coleta de lixo em Java não acontece em nenhum intervalo garantido. O Stringpode, assim, persistem na memória por um longo tempo, e se um processo deixa de funcionar durante este tempo, o conteúdo da corda pode acabar em um despejo de memória ou algum log.

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

Pritam Banerjee
fonte
@fallenidol De maneira alguma. Leia com atenção e você encontrará as diferenças.
Pritam Banerjee
12

String em java é imutável. Portanto, sempre que uma string é criada, ela permanece na memória até ser coletada como lixo. Portanto, 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, o valor original e o valor modificado permanecem na memória até a coleta de lixo.

Com a matriz de caracteres, o conteúdo da matriz pode ser modificado ou apagado quando o objetivo da senha for atendido. O conteúdo original da matriz não será encontrado na memória após sua modificação e mesmo antes da coleta de lixo.

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

Saathvik
fonte
2

É discutível se você deve usar String ou Char [] para esse fim, pois ambos têm suas vantagens e desvantagens. Depende do que o usuário precisa.

Como Strings em Java são imutáveis, sempre que algumas tentam manipular sua string, ela cria um novo Objeto e a String existente permanece inalterada. Isso pode ser visto como uma vantagem para armazenar uma senha como uma String, mas o objeto permanece na memória mesmo após o uso. Portanto, se alguém conseguiu a localização da memória do objeto, ela pode rastrear facilmente sua senha armazenada nessa localização.

Char [] é mutável, mas tem a vantagem de que após o uso, o programador pode limpar explicitamente a matriz ou substituir valores. Portanto, quando terminar de usá-lo, ele será limpo e ninguém poderá saber sobre as informações que você armazenou.

Com base nas circunstâncias acima, pode-se ter uma idéia de usar String ou de Char [] para seus requisitos.

Neeraj
fonte
0

Caso String:

    String password = "ill stay in StringPool after Death !!!";
    // some long code goes
    // ...Now I want to remove traces of password
    password = null;
    password = "";
    // above attempts wil change value of password
    // but the actual password can be traced from String pool through memory dump, if not garbage collected

Caso CHAR ARRAY:

    char[] passArray = {'p','a','s','s','w','o','r','d'};
    // some long code goes
    // ...Now I want to remove traces of password
    for (int i=0; i<passArray.length;i++){
        passArray[i] = 'x';
    }
    // Now you ACTUALLY DESTROYED traces of password form memory
Aditya Rewari
fonte