web-dev-qa-db-fra.com

Gestion des mots de passe utilisés pour l'authentification dans le code source

En supposant que j'essaye de tirer d'une API RESTful qui utilise l'authentification de base/certificats de base, quelle serait la meilleure façon de stocker ce nom d'utilisateur et ce mot de passe dans mon programme? En ce moment, il est juste assis là en texte clair.

UsernamePasswordCredentials creds = new UsernamePasswordCredentials("myName@myserver","myPassword1234");

Y a-t-il un moyen de le faire qui soit plus soucieux de la sécurité?

Merci

66
A_Elric

Avec un état d'esprit intérieur-extérieur, voici quelques étapes pour protéger votre processus:


Première étape, vous devez changer votre gestion de mot de passe de String à character array.

La raison en est qu'un String est un objet immutable, et donc ses données ne seront pas nettoyées immédiatement même si l'objet est défini sur null; Les données sont définies à la place pour la récupération de place, ce qui pose des problèmes de sécurité car des programmes malveillants peuvent accéder à ces données String (mot de passe) avant leur nettoyage.

C'est la principale raison pour laquelle la méthode getText() de JPasswordField de Swing est déconseillée, et pourquoi getPassword() utilise des tableaux de caractères .


La deuxième étape consiste à crypter vos informations d'identification, en les décryptant uniquement temporairement pendant le processus d'authentification.

De la même manière que pour la première étape, cela garantit que votre temps de vulnérabilité est aussi petit que possible.

Il est recommandé que vos informations d'identification ne soient pas codées en dur et que vous les stockiez à la place de manière centralisée, configurable et facilement maintenable, comme un fichier de configuration ou de propriétés.

Vous devez crypter vos informations d'identification avant d'enregistrer le fichier et, en outre, vous pouvez appliquer un deuxième cryptage au fichier lui-même (cryptage à 2 couches pour les informations d'identification et 1 couche à d'autres contenus de fichier).

Notez que chacun des deux processus de chiffrement mentionnés ci-dessus peut être lui-même multicouche. Chaque cryptage peut être une application individuelle de Triple Data Encryption Standard (AKA TDES et 3DES) , à titre d'exemple conceptuel.


Une fois que votre environnement local est correctement protégé (mais n'oubliez pas qu'il n'est jamais "sûr"!), La troisième étape consiste à appliquer une protection de base à votre processus de transmission, en utilisant TLS (Transport Layer Security) ou SSL (Secure Sockets Layer) ) .


La quatrième étape consiste à appliquer d'autres méthodes de protection.

Par exemple, en appliquant des techniques d'obscurcissement à votre compilation "à utiliser", pour éviter (même brièvement) l'exposition de vos mesures de sécurité au cas où votre programme serait obtenu par Mme. Eve, M. Mallory ou quelqu'un d'autre) (les méchants) et décompilé.


MISE À JOUR 1:

Par la demande de @ Damien.Bell, voici un exemple qui couvre les première et deuxième étapes:

    //These will be used as the source of the configuration file's stored attributes.
    private static final Map<String, String> COMMON_ATTRIBUTES = new HashMap<String, String>();
    private static final Map<String, char[]> SECURE_ATTRIBUTES = new HashMap<String, char[]>();
    //Ciphering (encryption and decryption) password/key.
    private static final char[] PASSWORD = "Unauthorized_Personel_Is_Unauthorized".toCharArray();
    //Cipher salt.
    private static final byte[] SALT = {
        (byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,
        (byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,};
    //Desktop dir:
    private static final File DESKTOP = new File(System.getProperty("user.home") + "/Desktop");
    //File names:
    private static final String NO_ENCRYPTION = "no_layers.txt";
    private static final String SINGLE_LAYER = "single_layer.txt";
    private static final String DOUBLE_LAYER = "double_layer.txt";

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws GeneralSecurityException, FileNotFoundException, IOException {
        //Set common attributes.
        COMMON_ATTRIBUTES.put("Gender", "Male");
        COMMON_ATTRIBUTES.put("Age", "21");
        COMMON_ATTRIBUTES.put("Name", "Hypot Hetical");
        COMMON_ATTRIBUTES.put("Nickname", "HH");

        /*
         * Set secure attributes.
         * NOTE: Ignore the use of Strings here, it's being used for convenience only.
         * In real implementations, JPasswordField.getPassword() would send the arrays directly.
         */
        SECURE_ATTRIBUTES.put("Username", "Hypothetical".toCharArray());
        SECURE_ATTRIBUTES.put("Password", "LetMePass_Word".toCharArray());

        /*
         * For demosntration purposes, I make the three encryption layer-levels I mention.
         * To leave no doubt the code works, I use real file IO.
         */
        //File without encryption.
        create_EncryptedFile(NO_ENCRYPTION, COMMON_ATTRIBUTES, SECURE_ATTRIBUTES, 0);
        //File with encryption to secure attributes only.
        create_EncryptedFile(SINGLE_LAYER, COMMON_ATTRIBUTES, SECURE_ATTRIBUTES, 1);
        //File completely encrypted, including re-encryption of secure attributes.
        create_EncryptedFile(DOUBLE_LAYER, COMMON_ATTRIBUTES, SECURE_ATTRIBUTES, 2);

        /*
         * Show contents of all three encryption levels, from file.
         */
        System.out.println("NO ENCRYPTION: \n" + readFile_NoDecryption(NO_ENCRYPTION) + "\n\n\n");
        System.out.println("SINGLE LAYER ENCRYPTION: \n" + readFile_NoDecryption(SINGLE_LAYER) + "\n\n\n");
        System.out.println("DOUBLE LAYER ENCRYPTION: \n" + readFile_NoDecryption(DOUBLE_LAYER) + "\n\n\n");

        /*
         * Decryption is demonstrated with the Double-Layer encryption file.
         */
        //Descrypt first layer. (file content) (REMEMBER: Layers are in reverse order from writing).
        String decryptedContent = readFile_ApplyDecryption(DOUBLE_LAYER);
        System.out.println("READ: [first layer decrypted]\n" + decryptedContent + "\n\n\n");
        //Decrypt second layer (secure data).
        for (String line : decryptedContent.split("\n")) {
            String[] pair = line.split(": ", 2);
            if (pair[0].equalsIgnoreCase("Username") || pair[0].equalsIgnoreCase("Password")) {
                System.out.println("Decrypted: " + pair[0] + ": " + decrypt(pair[1]));
            }
        }
    }

    private static String encrypt(byte[] property) throws GeneralSecurityException {
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
        SecretKey key = keyFactory.generateSecret(new PBEKeySpec(PASSWORD));
        Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
        pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(SALT, 20));

        //Encrypt and save to temporary storage.
        String encrypted = Base64.encodeBytes(pbeCipher.doFinal(property));

        //Cleanup data-sources - Leave no traces behind.
        for (int i = 0; i < property.length; i++) {
            property[i] = 0;
        }
        property = null;
        System.gc();

        //Return encryption result.
        return encrypted;
    }

    private static String encrypt(char[] property) throws GeneralSecurityException {
        //Prepare and encrypt.
        byte[] bytes = new byte[property.length];
        for (int i = 0; i < property.length; i++) {
            bytes[i] = (byte) property[i];
        }
        String encrypted = encrypt(bytes);

        /*
         * Cleanup property here. (child data-source 'bytes' is cleaned inside 'encrypt(byte[])').
         * It's not being done because the sources are being used multiple times for the different layer samples.
         */
//      for (int i = 0; i < property.length; i++) { //cleanup allocated data.
//          property[i] = 0;
//      }
//      property = null; //de-allocate data (set for GC).
//      System.gc(); //Attempt triggering garbage-collection.

        return encrypted;
    }

    private static String encrypt(String property) throws GeneralSecurityException {
        String encrypted = encrypt(property.getBytes());
        /*
         * Strings can't really have their allocated data cleaned before CG,
         * that's why secure data should be handled with char[] or byte[].
         * Still, don't forget to set for GC, even for data of sesser importancy;
         * You are making everything safer still, and freeing up memory as bonus.
         */
        property = null;
        return encrypted;
    }

    private static String decrypt(String property) throws GeneralSecurityException, IOException {
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
        SecretKey key = keyFactory.generateSecret(new PBEKeySpec(PASSWORD));
        Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
        pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
        return new String(pbeCipher.doFinal(Base64.decode(property)));
    }

    private static void create_EncryptedFile(
                    String fileName,
                    Map<String, String> commonAttributes,
                    Map<String, char[]> secureAttributes,
                    int layers)
                    throws GeneralSecurityException, FileNotFoundException, IOException {
        StringBuilder sb = new StringBuilder();
        for (String k : commonAttributes.keySet()) {
            sb.append(k).append(": ").append(commonAttributes.get(k)).append(System.lineSeparator());
        }
        //First encryption layer. Encrypts secure attribute values only.
        for (String k : secureAttributes.keySet()) {
            String encryptedValue;
            if (layers >= 1) {
                encryptedValue = encrypt(secureAttributes.get(k));
            } else {
                encryptedValue = new String(secureAttributes.get(k));
            }
            sb.append(k).append(": ").append(encryptedValue).append(System.lineSeparator());
        }

        //Prepare file and file-writing process.
        File f = new File(DESKTOP, fileName);
        if (!f.getParentFile().exists()) {
            f.getParentFile().mkdirs();
        } else if (f.exists()) {
            f.delete();
        }
        BufferedWriter bw = new BufferedWriter(new FileWriter(f));
        //Second encryption layer. Encrypts whole file content including previously encrypted stuff.
        if (layers >= 2) {
            bw.append(encrypt(sb.toString().trim()));
        } else {
            bw.append(sb.toString().trim());
        }
        bw.flush();
        bw.close();
    }

    private static String readFile_NoDecryption(String fileName) throws FileNotFoundException, IOException, GeneralSecurityException {
        File f = new File(DESKTOP, fileName);
        BufferedReader br = new BufferedReader(new FileReader(f));
        StringBuilder sb = new StringBuilder();
        while (br.ready()) {
            sb.append(br.readLine()).append(System.lineSeparator());
        }
        return sb.toString();
    }

    private static String readFile_ApplyDecryption(String fileName) throws FileNotFoundException, IOException, GeneralSecurityException {
        File f = new File(DESKTOP, fileName);
        BufferedReader br = new BufferedReader(new FileReader(f));
        StringBuilder sb = new StringBuilder();
        while (br.ready()) {
            sb.append(br.readLine()).append(System.lineSeparator());
        }
        return decrypt(sb.toString());
    }

Un exemple complet, traitant de chaque étape de protection, dépasserait de loin ce que je pense être raisonnable pour cette question, car il s'agit de "quelles sont les étapes", pas "comment les appliquer ".

Cela surpasserait de loin ma réponse (enfin l'échantillonnage), tandis que d'autres questions ici sur S.O. sont déjà orientés sur le "Comment faire" de ces étapes, étant beaucoup plus approprié, et offrant une bien meilleure explication et un échantillonnage sur la mise en œuvre de chaque étape individuelle.

99
XenoRo

Si vous utilisez l'authentification de base, vous devez l'associer à SSL pour éviter de transmettre vos informations d'identification en texte brut codé en base64. Vous ne voulez pas qu'il soit facile pour quelqu'un de renifler vos paquets d'obtenir vos informations d'identification. Ne codez pas non plus vos informations d'identification dans votre code source. Rendez-les configurables. les lire à partir d'un fichier de configuration. Vous devez crypter les informations d'identification avant de les stocker dans un fichier de configuration et votre application doit décrypter les informations d'identification une fois qu'elle les lit dans le fichier de configuration.

7
Athens Holloway
  1. ordinateur sécurisé qui initialise la demande (votre ordinateur). si cette machine n'est pas sécurisée, rien ne vous protégera. c'est un sujet complètement différent (logiciel à jour, correctement configuré, mots de passe forts, échange crypté, renifleurs matériels, sécurité physique, etc.)
  2. sécurisez votre stockage le support que vous utilisez pour stocker vos informations d'identification doit être crypté. les informations d'identification déchiffrées ne doivent être stockées que dans le ram de votre machine sécurisée
  3. les personnes qui soutiennent que le matériel doit être fiable (probablement le maillon le plus faible)
  4. ils devraient également en savoir le moins possible. c'est une protection contre la cryptanalyse en caoutchouc
  5. vos informations d'identification doivent répondre à toutes les recommandations de sécurité (longueur appropriée, caractère aléatoire, objectif unique, etc.)
  6. votre connexion au service à distance doit être sécurisée (SSL etc.)
  7. votre service à distance doit être fiable (voir points 1-4). De plus, il devrait être sujet au piratage (si vos données/services ne sont pas sécurisés, la sécurisation de vos informations d'identification est inutile). de plus, il ne doit pas stocker vos informations d'identification

et probablement mille choses que j'ai oubliées :)

2
piotrek

Ce n'est généralement pas un bon conseil pour chiffrer les informations d'identification. Quelque chose qui est chiffré peut être déchiffré. La meilleure pratique courante consiste à stocker les mots de passe sous la forme hachage salé . Un hachage ne peut pas être déchiffré. Le sel est ajouté pour vaincre la devinette de la force brute avec Rainbow Tables . Tant que chaque userId a son propre sel aléatoire, un attaquant devrait générer un ensemble de tables pour chaque valeur possible du sel, rendant rapidement cette attaque impossible pendant la durée de vie de l'univers. C'est la raison pour laquelle les sites Web ne peuvent généralement pas vous envoyer votre mot de passe si vous l'avez oublié, mais ils ne peuvent que le "réinitialiser". Ils n'ont pas votre mot de passe stocké, seulement un hachage.

Le hachage de mot de passe n'est pas très difficile à mettre en œuvre vous-même, mais c'est un problème si courant à résoudre que d'innombrables autres l'ont fait pour vous. J'ai trouvé jBcrypt facile à utiliser.

En tant que protection supplémentaire contre la devinette des mots de passe par force brute, il est généralement recommandé de forcer un ID utilisateur ou une adresse IP distante à attendre quelques secondes après un certain nombre de tentatives de connexion avec le mauvais mot de passe. Sans cela, un attaquant en force brute peut deviner autant de mots de passe par seconde que votre serveur peut en gérer. Il y a une énorme différence entre pouvoir deviner 100 mots de passe par période de 10 secondes ou un million.

J'ai l'impression que vous avez inclus la combinaison nom d'utilisateur/mot de passe dans votre code source. Cela signifie que si vous souhaitez modifier le mot de passe, vous devrez recompiler, arrêter et redémarrer votre service, et cela signifie également que toute personne qui obtient votre code source a également vos mots de passe. La meilleure pratique courante est de ne jamais le faire, mais de stocker les informations d'identification (nom d'utilisateur, hachage de mot de passe, sel de mot de passe) dans votre banque de données

1
Mzzl