Cifrar contraseña en los archivos de configuración? [cerrado]

130

Tengo un programa que lee la información del servidor de un archivo de configuración y me gustaría cifrar la contraseña en esa configuración que mi programa puede leer y descifrar.

Requerimientos

  • Cifre la contraseña de texto sin formato que se almacenará en el archivo
  • Descifrar la contraseña cifrada leída desde el archivo de mi programa

¿Alguna recomendación sobre cómo haría para hacer esto? Estaba pensando en escribir mi propio algoritmo, pero siento que sería terriblemente inseguro.

Petey B
fuente

Respuestas:

172

Una forma sencilla de hacerlo es utilizar el cifrado basado en contraseña en Java. Esto le permite cifrar y descifrar un texto utilizando una contraseña.

Esto básicamente significa inicializar un javax.crypto.Cipheralgoritmo con "AES/CBC/PKCS5Padding"y obtener una clave javax.crypto.SecretKeyFactorycon el "PBKDF2WithHmacSHA512"algoritmo.

Aquí hay un ejemplo de código (actualizado para reemplazar la variante menos segura basada en 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);
    }
}

Queda un problema: ¿dónde debe almacenar la contraseña que utiliza para cifrar las contraseñas? Puede almacenarlo en el archivo fuente y ofuscarlo, pero no es demasiado difícil volver a encontrarlo. Alternativamente, puede asignarlo como una propiedad del sistema cuando inicia el proceso Java ( -DpropertyProtectionPassword=...).

El mismo problema persiste si usa KeyStore, que también está protegido por una contraseña. Básicamente, necesitará tener una contraseña maestra en algún lugar, y es bastante difícil de proteger.

Johannes Brodwall
fuente
3
Gracias por el ejemplo de código, es más o menos como terminé haciéndolo. En lo que respecta a la contraseña que protege las contraseñas, me encontré con el mismo problema, por ahora no recurrí al método de ofuscación, pero aún no he encontrado una solución aceptable, gracias por sus sugerencias.
Petey B
77
"Alternativamente, puede asignarlo como una propiedad del sistema cuando inicia el proceso Java (-DpropertyProtectionPassword = ...)". Tenga en cuenta que esto permitiría extraer la contraseña usando "ps fax" en (GNU / Linux) / UNIX.
Ztyx
77
@Ben Es una práctica común codificar en Base64 para permitirle almacenar el valor resultante en un archivo de texto o en una columna de base de datos basada en cadenas, o similar.
RB.
44
@ V.7 no. MD5 no es absolutamente seguro para el hashing de contraseñas y nunca fue diseñado para ese propósito. Nunca lo uses para eso. En estos días, Argon2 es el mejor. Ver owasp.org/index.php/Password_Storage_Cheat_Sheet y paragonie.com/blog/2016/02/how-safely-store-password-in-2016
Kimball Robinson
3
Mucho mejor de esta manera. Por supuesto, una sal aleatoria segura y un recuento de iteraciones de un (conservador, bajo) de 40K sería mejor, pero al menos ha indicado estas cosas en los comentarios y PBKDF2 y AES / CBC son mejoras definitivas. Creo que es genial cómo manejaste esto, actualizando la respuesta; Voy a eliminar la advertencia. Votó su comentario para que las personas no se sorprendan de encontrar el código actualizado (pueden ver las ediciones para encontrar el código antiguo, supongo). También podría ser una buena idea limpiar tus viejos comentarios.
Maarten Bodewes
20

Sí, definitivamente no escriba su propio algoritmo. Java tiene muchas API de criptografía.

Si el sistema operativo en el que está instalando tiene un almacén de claves, puede usarlo para almacenar sus claves de cifrado que necesitará para cifrar y descifrar los datos confidenciales en su configuración u otros archivos.

JeeBee
fuente
44
¡+1 por usar un KeyStore! No es más que ofuscación si está almacenando la clave en el archivo Jar.
ninesided
2
Si todo lo que se necesita es no tener la contraseña almacenada en texto claro, entonces los almacenes de claves son excesivos.
Thorbjørn Ravn Andersen
20

Echa un vistazo a jasypt , que es una biblioteca que ofrece capacidades de cifrado básicas con un mínimo esfuerzo.

Kaitsu
fuente
16

Creo que el mejor enfoque es garantizar que su archivo de configuración (que contiene su contraseña) solo sea accesible para una cuenta de usuario específica . Por ejemplo, es posible que tenga un usuario específico de la aplicación appuserpara el que solo las personas de confianza tienen la contraseña (y para la cual sudeben hacerlo).

De esa manera, no hay una sobrecarga de criptografía molesta y todavía tiene una contraseña segura.

EDITAR: supongo que no está exportando la configuración de su aplicación fuera de un entorno confiable (lo que no estoy seguro tendría sentido, dada la pregunta)

oxbow_lakes
fuente
4

Bueno, para resolver los problemas de la contraseña maestra: el mejor enfoque es no almacenar la contraseña en ningún lugar, la aplicación debe cifrar las contraseñas por sí misma, de modo que solo pueda descifrarlas. Entonces, si estaba usando un archivo .config, haría lo siguiente, mySettings.config :

encryptTheseKeys = secretKey, otroSecreto

secretKey = unprotectedPasswordThatIputHere

anotherSecret = anotherPass

someKey = unprotectedSettingIdontCareAbout

así que leería las claves que se mencionan en encryptTheseKeys, aplicaría el ejemplo de Brodwalls de arriba y las escribiría en el archivo con un marcador de algún tipo (digamos crypt :) para que la aplicación sepa que no debe hacerlo de nuevo, la salida se vería así:

encryptTheseKeys = secretKey, otroSecreto

secretKey = crypt: ii4jfj304fjhfj934fouh938

anotherSecret = crypt: jd48jofh48h

someKey = unprotectedSettingIdontCareAbout

Solo asegúrese de guardar los originales en su propio lugar seguro ...

usuario1007231
fuente
2
Sí, esto es de hace 3 años. Para evitar la clave maestra, terminé usando claves RSA emitidas por nuestra CA interna. El acceso a la clave privada está protegido mediante el cifrado con una huella digital del hardware de la máquina.
Petey B
Ya veo, suena bastante sólido. bonito.
user1007231
@ user1007231 - Dónde guardarlo: "¿Solo asegúrese de guardar los originales en su propio lugar seguro ..."?
nanosoft
@PeteyB - ¿No entendiste? ¿Puedes señalarme algunos enlaces que me puedan iluminar? Gracias
nanosoft
@nanosoft - Obtenga un "Aegis Secure Key USB" y guárdelo en un documento de texto allí o en papel en su billetera
user1007231
4

El gran punto, y el elefante en la habitación y todo eso, es que si su aplicación puede obtener la contraseña, ¡un pirata informático con acceso a la caja también puede obtenerla!

La única forma de evitar esto es que la aplicación solicita la "contraseña maestra" en la consola usando la entrada estándar, y luego la usa para descifrar las contraseñas almacenadas en el archivo. Por supuesto, esto hace completamente imposible que la aplicación se inicie desatendida junto con el sistema operativo cuando se inicia.

Sin embargo, incluso con este nivel de molestia, si un hacker logra obtener acceso de root (o incluso solo el acceso como el usuario que ejecuta su aplicación), podría volcar la memoria y encontrar la contraseña allí.

Lo que hay que garantizar es no permitir que toda la empresa tenga acceso al servidor de producción (y, por lo tanto, a las contraseñas), ¡y asegurarse de que sea imposible descifrar esta casilla!

Stolsvik
fuente
La solución real es almacenar su clave privada en otro lugar, como una tarjeta o un HSM: en.wikipedia.org/wiki/Hardware_security_module
atom88
0

Dependiendo de cuán seguro necesite los archivos de configuración o cuán confiable sea su aplicación, http://activemq.apache.org/encrypted-passwords.html puede ser una buena solución para usted.

Si no tiene demasiado miedo de que se descifre la contraseña y puede ser realmente simple de configurar usando un bean para almacenar la clave de contraseña. Sin embargo, si necesita más seguridad, puede establecer una variable de entorno con el secreto y eliminarla después del inicio. Con esto, debe preocuparse de que la aplicación / servidor se caiga y no que la aplicación no se reinicie automáticamente.

CPrescott
fuente
usar un HSM es la forma ideal: en.wikipedia.org/wiki/Hardware_security_module
atom88
-8

Si está utilizando Java 8, se puede evitar el uso del codificador y decodificador interno Base64 reemplazando

return new BASE64Encoder().encode(bytes);

con

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

y

return new BASE64Decoder().decodeBuffer(property);

con

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

Tenga en cuenta que esta solución no protege sus datos, ya que los métodos para descifrar se almacenan en el mismo lugar. Simplemente hace que sea más difícil de romper. Principalmente evita imprimirlo y mostrárselo a todos por error.

Antonio Raposo
fuente
26
Base64 no es encriptación.
jwilleke
1
Base64 no es ancryption y es el peor ejemplo que se puede brindar ... mucha gente cree que la base 64 es un algortihm cifrado, así que es mejor no confundirlos ...
Robob
observe que el método decode () en la parte superior del archivo fuente realiza el cifrado real. Sin embargo, esta codificación y decodificación base64 es necesaria para convertir de una cadena los bytes para pasar esta función a algo que pueda usar (un byte array byte [])
atom88