Criptografar senha nos arquivos de configuração? [fechadas]

130

Eu tenho um programa que lê as informações do servidor de um arquivo de configuração e gostaria de criptografar a senha nessa configuração que pode ser lida pelo meu programa e descriptografada.

Requisitos:

  • Criptografar senha de texto sem formatação para ser armazenada no arquivo
  • Descriptografar a senha criptografada lida no arquivo do meu programa

Alguma recomendação sobre como eu faria isso? Eu estava pensando em escrever meu próprio algoritmo, mas acho que seria terrivelmente inseguro.

Petey B
fonte

Respostas:

172

Uma maneira simples de fazer isso é usar a criptografia baseada em senha em Java. Isso permite criptografar e descriptografar um texto usando uma senha.

Isso basicamente significa inicializar um javax.crypto.Ciphercom o algoritmo "AES/CBC/PKCS5Padding"e obter uma chave javax.crypto.SecretKeyFactorycom o "PBKDF2WithHmacSHA512"algoritmo.

Aqui está um exemplo de código (atualizado para substituir a variante menos segura baseada em MD5):

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public class ProtectedConfigFile {

    public static void main(String[] args) throws Exception {
        String password = System.getProperty("password");
        if (password == null) {
            throw new IllegalArgumentException("Run with -Dpassword=<password>");
        }

        // The salt (probably) can be stored along with the encrypted data
        byte[] salt = new String("12345678").getBytes();

        // Decreasing this speeds down startup time and can be useful during testing, but it also makes it easier for brute force attackers
        int iterationCount = 40000;
        // Other values give me java.security.InvalidKeyException: Illegal key size or default parameters
        int keyLength = 128;
        SecretKeySpec key = createSecretKey(password.toCharArray(),
                salt, iterationCount, keyLength);

        String originalPassword = "secret";
        System.out.println("Original password: " + originalPassword);
        String encryptedPassword = encrypt(originalPassword, key);
        System.out.println("Encrypted password: " + encryptedPassword);
        String decryptedPassword = decrypt(encryptedPassword, key);
        System.out.println("Decrypted password: " + decryptedPassword);
    }

    private static SecretKeySpec createSecretKey(char[] password, byte[] salt, int iterationCount, int keyLength) throws NoSuchAlgorithmException, InvalidKeySpecException {
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
        PBEKeySpec keySpec = new PBEKeySpec(password, salt, iterationCount, keyLength);
        SecretKey keyTmp = keyFactory.generateSecret(keySpec);
        return new SecretKeySpec(keyTmp.getEncoded(), "AES");
    }

    private static String encrypt(String property, SecretKeySpec key) throws GeneralSecurityException, UnsupportedEncodingException {
        Cipher pbeCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        pbeCipher.init(Cipher.ENCRYPT_MODE, key);
        AlgorithmParameters parameters = pbeCipher.getParameters();
        IvParameterSpec ivParameterSpec = parameters.getParameterSpec(IvParameterSpec.class);
        byte[] cryptoText = pbeCipher.doFinal(property.getBytes("UTF-8"));
        byte[] iv = ivParameterSpec.getIV();
        return base64Encode(iv) + ":" + base64Encode(cryptoText);
    }

    private static String base64Encode(byte[] bytes) {
        return Base64.getEncoder().encodeToString(bytes);
    }

    private static String decrypt(String string, SecretKeySpec key) throws GeneralSecurityException, IOException {
        String iv = string.split(":")[0];
        String property = string.split(":")[1];
        Cipher pbeCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        pbeCipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(base64Decode(iv)));
        return new String(pbeCipher.doFinal(base64Decode(property)), "UTF-8");
    }

    private static byte[] base64Decode(String property) throws IOException {
        return Base64.getDecoder().decode(property);
    }
}

Um problema permanece: onde você deve armazenar a senha usada para criptografar as senhas? Você pode armazená-lo no arquivo de origem e ofuscá-lo, mas não é muito difícil encontrá-lo novamente. Como alternativa, você pode atribuí-lo como uma propriedade do sistema ao iniciar o processo Java ( -DpropertyProtectionPassword=...).

O mesmo problema permanece se você usar o KeyStore, que também é protegido por uma senha. Basicamente, você precisará ter uma senha mestra em algum lugar e é muito difícil de proteger.

Johannes Brodwall
fonte
3
Obrigado pelo exemplo de código, é praticamente como eu acabei fazendo isso. No que diz respeito à senha que protege as senhas que encontrei no mesmo problema, segui o método ofuscado por enquanto, mas ainda não obtive uma solução aceitável, obrigado por suas sugestões.
22415 Petey B
7
"Como alternativa, você pode fornecê-lo como uma propriedade do sistema ao iniciar o processo Java (-DpropertyProtectionPassword = ...)". Observe que isso tornaria possível extrair a senha usando "ps fax" no (GNU / Linux) / UNIX.
Ztyx
7
@Ben É prática comum codificar para Base64 para permitir que você armazene o valor resultante em uma coluna de arquivo de texto ou banco de dados com base em string ou similar.
RB.
4
@ V.7 não. O MD5 não é absolutamente seguro para o hash de senha e nunca foi projetado para esse fim. Nunca use isso para isso. Hoje em dia, o Argon2 é o melhor. Veja owasp.org/index.php/Password_Storage_Cheat_Sheet e paragonie.com/blog/2016/02/how-safely-store-password-in-2016 #
Kimball Robinson
3
Muito melhor assim. É claro que um sal aleatório seguro e uma contagem de iterações de (conservadora, extremidade inferior) de 40K seriam melhores, mas pelo menos você indicou essas coisas nos comentários e PBKDF2 e AES / CBC são melhorias definitivas. Eu acho ótimo como você lidou com isso, atualizando a resposta; Vou remover o aviso. Votou seu comentário para que as pessoas não fiquem surpresas ao encontrar o código atualizado (elas podem olhar as edições para encontrar o código antigo, suponho). Pode ser uma boa idéia também limpar seus comentários antigos.
Maarten Bodewes
20

Sim, definitivamente não escreva seu próprio algoritmo. Java possui muitas APIs de criptografia.

Se o sistema operacional em que você está instalando tiver um armazenamento de chaves, você poderá usá-lo para armazenar suas chaves criptográficas que serão necessárias para criptografar e descriptografar os dados confidenciais em sua configuração ou em outros arquivos.

JeeBee
fonte
4
+1 por usar uma KeyStore! Não passa de ofuscação se você estiver armazenando a chave no arquivo Jar.
Ninesided
2
Se tudo o que for necessário é não ter a senha armazenada em texto não criptografado, os keystores são um exagero.
Thorbjørn Ravn Andersen
20

Confira jasypt , que é uma biblioteca que oferece recursos básicos de criptografia com o mínimo de esforço.

Kaitsu
fonte
16

Eu acho que a melhor abordagem é garantir que seu arquivo de configuração (contendo sua senha) seja acessível apenas a uma conta de usuário específica . Por exemplo, você pode ter um usuário específico do aplicativo appuserpara o qual apenas pessoas confiáveis ​​tenham a senha (e para a qual eles su).

Dessa forma, não há sobrecarga de criptografia irritante e você ainda tem uma senha segura.

EDIT: Estou assumindo que você não está exportando a configuração do aplicativo para fora de um ambiente confiável (o que não tenho certeza se faria sentido, dada a pergunta)

oxbow_lakes
fonte
4

Bem, para resolver os problemas da senha mestra - a melhor abordagem é não armazenar a senha em nenhum lugar, o aplicativo deve criptografar senhas para si mesmo - para que somente ele possa descriptografá-las. Portanto, se eu estivesse usando um arquivo .config, faria o seguinte, mySettings.config :

encryptTheseKeys = secretKey, anotherSecret

secretKey = desprotectedPasswordThatIputHere

anotherSecret = anotherPass

someKey = desprotectedSettingIdontCareAbout

para ler as chaves mencionadas nas encryptTheseKeys, aplique o exemplo Brodwalls acima e escreva-as de volta no arquivo com um marcador de algum tipo (digamos crypt :) para que o aplicativo saiba que não deve fazê-lo novamente, a saída ficaria assim:

encryptTheseKeys = secretKey, anotherSecret

secretKey = crypt: ii4jfj304fjhfj934fouh938

anotherSecret = crypt: jd48jofh48h

someKey = desprotectedSettingIdontCareAbout

Certifique-se de manter os originais em seu próprio local seguro ...

user1007231
fonte
2
Sim, isso é de 3 anos atrás. Para evitar a chave mestra, acabei usando chaves RSA emitidas por nossa CA interna. O acesso à chave privada é protegido por criptografia com uma impressão digital do hardware da máquina.
precisa
Entendo, parece bastante sólido. legais.
user1007231
@ user1007231 - Onde guardar - "Apenas mantenha os originais em seu próprio local seguro ..."?
Nanosoft 24/03
@ PeteyB - não entendeu? Você pode me indicar alguns links que podem me esclarecer. Obrigado
nanosoft
@nanosoft - Obter um "Aegis seguros USB Key" e armazenar em um doc texto lá ou em papel em sua carteira
user1007231
4

O grande ponto, e o elefante na sala e tudo isso, é que, se seu aplicativo pode se apossar da senha, um hacker com acesso à caixa também pode se apossar dela!

A única maneira de contornar isso é que o aplicativo solicita a "senha mestra" no console usando a Entrada padrão e, em seguida, usa isso para descriptografar as senhas armazenadas no arquivo. Obviamente, isso impossibilita a inicialização automática do aplicativo junto com o sistema operacional quando ele é inicializado.

No entanto, mesmo com esse nível de aborrecimento, se um hacker conseguir obter acesso root (ou apenas acessar como o usuário executando seu aplicativo), ele poderá despejar a memória e encontrar a senha lá.

O que deve ser garantido é não permitir que toda a empresa tenha acesso ao servidor de produção (e, portanto, às senhas) e verifique se é impossível quebrar essa caixa!

Stolsvik
fonte
A verdadeira solução é armazenar sua chave privada em outro lugar, como um cartão ou um HSM: en.wikipedia.org/wiki/Hardware_security_module
atom88
0

Dependendo de quão seguro você precise dos arquivos de configuração ou de quão confiável é seu aplicativo, http://activemq.apache.org/encrypted-passwords.html pode ser uma boa solução para você.

Se você não tem muito medo da senha ser descriptografada e pode ser realmente simples de configurar usando um bean para armazenar a chave da senha. No entanto, se você precisar de mais segurança, poderá definir uma variável de ambiente com o segredo e removê-la após o lançamento. Com isso, você precisa se preocupar com a queda do aplicativo / servidor e não com o reinício automático do aplicativo.

CPrescott
fonte
usar um HSM é a maneira ideal: en.wikipedia.org/wiki/Hardware_security_module
atom88
-8

Se você estiver usando o java 8, o uso do codificador e decodificador interno Base64 pode ser evitado substituindo-o

return new BASE64Encoder().encode(bytes);

com

return Base64.getEncoder().encodeToString(bytes);

e

return new BASE64Decoder().decodeBuffer(property);

com

return Base64.getDecoder().decode(property);

Observe que esta solução não protege seus dados, pois os métodos para descriptografar são armazenados no mesmo local. Isso apenas torna mais difícil quebrar. Evita principalmente imprimi-lo e mostrá-lo a todos por engano.

Antonio Raposo
fonte
26
Base64 não é criptografia.
precisa saber é o seguinte
1
Base64 não é ancryption e é o pior exemplo, você pode fornecer ... um monte de pessoas acreditam que base64 é um algortihm criptografia, então é melhor para não confundir-los ...
robob
observe que o método decode () na parte superior do arquivo de origem faz a criptografia real. No entanto, esta codificação base 64 e descodificação é necessária para converter a partir de uma cadeia dos bytes, a fim de passar esta função algo que pode usar (um byte matriz byte [])
atom88