91 votes

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

En supposant que j'essaie de tirer d'une api RESTful qui utilise une authentification de base / des certificats de base, quelle serait la meilleure façon de stocker ce nom d'utilisateur et ce mot de passe dans mon programme ? Pour l'instant, ils sont juste là, en clair.

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

Existe-t-il un moyen de faire cela de manière plus sécurisée ?

Merci

5 votes

La réponse dépend des éléments suivants : Voulez-vous distribuer l'application ? L'utilisateur/mot de passe est-il basé sur l'utilisateur de l'application, ou s'agit-il d'une sorte de clé API ? Voulez-vous protéger l'utilisateur/mot de passe de l'utilisateur local (une sorte de DRM) ?

0 votes

Il s'agit en fait d'un programme fonctionnant sur un backend, mais c'est surtout une question de style. Je ne devrais pas avoir le nom d'utilisateur et le mot de passe d'un compte qui contient des informations classifiées en clair.

2 votes

Jetez un coup d'œil à ce fil de discussion stackoverflow.com/questions/12198228/ et vous aurez l'idée générale.

122voto

TheLima Points 1291

Remarque importante :

Si vous concevez le système d'authentification dans son ensemble, vous ne devez pas stocker les mots de passe, même s'ils sont chiffrés. Vous stockez un hash, et vérifiez si les mots de passe fournis lors de la connexion correspondent au même hash. De cette façon, une faille de sécurité sur votre base de données évite d'exposer les mots de passe de vos utilisateurs.

Cela étant dit, pour les situations où vous allez stocker des données telles quelles (dans ce cas, des mots de passe), alors avec un état d'esprit de l'intérieur vers l'extérieur, voici quelques étapes pour protéger votre processus :


Dans un premier temps, vous devez modifier votre gestion des mots de passe de la manière suivante String a character array .

La raison en est qu'un String est un immutable et donc ses données ne seront pas nettoyées immédiatement même si l'objet est réglé sur null Les données sont plutôt destinées à la collecte de déchets, ce qui pose des problèmes de sécurité car des programmes malveillants pourraient avoir accès à ces données. String (mot de passe) avant qu'il ne soit nettoyé.

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


La deuxième étape consiste à crypter vos informations d'identification, en ne les décryptant que temporairement pendant le processus d'authentification. Ou de les hacher côté serveur, de stocker ce hachage et d'"oublier" le mot de passe original.

De la même manière que pour la première étape, cela permet de s'assurer que votre temps de vulnérabilité est aussi réduit que possible.

Il est recommandé de ne pas coder en dur vos informations d'identification, mais de les stocker de manière centralisée, configurable et facile à maintenir, par exemple dans un fichier de configuration ou de propriétés, ou dans une base de données.

Vous devez crypter vos informations d'identification avant d'enregistrer le fichier. En outre, vous pouvez appliquer un deuxième cryptage au fichier lui-même (cryptage à deux niveaux pour les informations d'identification et à un niveau pour les autres contenus du fichier).

Il convient de noter que chacun des deux processus de cryptage mentionnés ci-dessus peut être lui-même à couches multiples. Chaque cryptage peut être une application individuelle de Norme de chiffrement triple des données (alias 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 les éléments suivants TLS (Transport Layer Security) ou SSL (Secure Sockets Layer) .


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

Par exemple, l'application de techniques d'obfuscation à votre compilation "à utiliser", pour éviter (même si c'est à court terme) 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é.


UPDATE 1 :

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

    //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, abordant chaque étape de la protection, dépasserait de loin ce que je pense être raisonnable pour cette question, puisqu'il s'agit de "quelles sont les étapes" pas "comment les appliquer" .

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

0 votes

Belle réponse. Il y a définitivement un thème commun ici. +1 pour la recommandation char[].

0 votes

J'adore cette réponse, mais au risque de devenir trop verbeux, j'aimerais voir un exemple de code source de chaque étape mis en œuvre.

3 votes

[*] - @Damien.Bell Pour ne pas laisser votre demande sans réponse, j'ai inclus un exemple qui couvre la première (~) et la deuxième étape. --- Quant à savoir pourquoi pas toutes les étapes, eh bien, comme vous pouvez le voir, ce n'est pas quelque chose que vous pouvez échantillonner avec un tout petit bout de code ; Et un exemple pour la protection du réseau exigerait encore plus que celui de la portée locale, même si pour être partiellement pseudo-codé. L'obscurcissement, aussi, a une très large gamme de méthodes de mise en œuvre, et bien qu'il soit simple dans le concept, le fait qu'il soit appliqué au code source lui-même signifie qu'il est difficile à expliquer dans des échantillons.

8voto

Athens Holloway Points 1222

Si vous utilisez l'authentification de base, vous devez la coupler avec le SSL pour éviter de transmettre vos informations d'identification en texte brut codé en base64. Vous ne voulez pas que quelqu'un qui renifle vos paquets puisse facilement obtenir vos informations d'identification. De plus, ne codifiez pas vos informations d'identification en dur dans votre code source. Rendez-les configurables, lisez-les à partir d'un fichier de configuration. Vous devez chiffrer les informations d'identification avant de les stocker dans un fichier de configuration et votre application doit déchiffrer les informations d'identification une fois qu'elle les a lues dans le fichier de configuration.

1 votes

Pouvez-vous fournir des exemples de la manière dont vous procédez par programme ?

0 votes

2voto

Mzzl Points 630

Il n'est généralement pas bon de crypter les informations d'identification. Quelque chose qui est crypté peut être décrypté. La meilleure pratique courante est de stocker les mots de passe en tant que hachis salé Un hachage ne peut pas être décrypté. Le sel est ajouté pour vaincre les tentatives de chiffrement par force brute avec Tables arc-en-ciel . Tant que chaque ID utilisateur possède son propre sel aléatoire, un attaquant devrait générer un ensemble de tables pour chaque valeur possible du sel, ce qui rendrait rapidement cette attaque impossible au cours de 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 peuvent seulement le "réinitialiser". Ils n'ont pas votre mot de passe en mémoire, mais seulement un hachage de celui-ci.

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

Pour assurer une protection supplémentaire contre l'identification par force brute des mots de passe, il est courant de forcer un ID utilisateur ou une IP distante à attendre quelques secondes après un certain nombre de tentatives de connexion avec un mauvais mot de passe. Sans cela, un attaquant par force brute peut deviner autant de mots de passe par seconde que votre serveur peut gérer. Il y a une énorme différence entre être capable de 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 voulez changer le mot de passe, vous devrez recompiler, arrêter et redémarrer votre service, et cela signifie également que toute personne qui met la main sur votre code source, a également vos mots de passe. La meilleure pratique consiste à ne jamais faire cela, mais à stocker les informations d'identification (nom d'utilisateur, hachage du mot de passe, sel du mot de passe) dans votre base de données.

2 votes

Je ne suis toujours pas sûr, mais je pense "... J'essaie de tirer de une api RESTful ..." indique que le PO ne parle pas de l'environnement côté serveur. Je pense qu'il parle d'une application côté client qui s'authentifie auprès d'un serveur. En tant que tel, le côté client devrait seulement protéger les informations d'identification dans le stockage (cryptage) et les envoyer en toute sécurité au serveur (TSL / SSL - qui appliquent intrinsèquement le cryptage et le chiffrement des messages) ___ Le chiffrement des messages (pour l'enregistrement ou la comparaison) devrait être effectué uniquement côté serveur, sinon il ne serait pas sécurisé. ___ Tout cela se trouve dans les commentaires de ma réponse.

0 votes

En outre, votre réponse indique l'utilisation d'une API probablement obsolète (jBcrypt - Elle est en version bêta v0.3, et sa dernière mise à jour date de janvier 2010, ce qui peut indiquer que le projet est mort). Java possède déjà ses propres classes standard de digestion des messages. et je ne pense pas qu'il y ait un besoin essentiel d'API tierces.

0 votes

Il semble que vous ayez raison au sujet de la confusion entre le côté client et le côté serveur. Je recommande toujours de placer les informations d'identification dans un magasin de données, et non dans le code source, mais vous avez raison de parler de chiffrement plutôt que de hachage dans ce cas.

1voto

piotrek Points 2230
  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 (logiciels à jour, correctement configurés, mots de passe forts, swap crypté, renifleurs de matériel, sécurité physique, etc.)
  2. sécuriser votre stockage le support que vous utilisez pour stocker vos informations d'identification doit être crypté. les informations d'identification décryptées doivent être stockées uniquement dans la mémoire de votre machine sécurisée.
  3. les personnes qui soutiennent qu'il faut faire confiance au matériel (probablement le maillon le plus faible)
  4. Ils doivent également en connaître le moins possible. C'est une protection contre la cryptanalyse à l'aide de tuyaux en caoutchouc.
  5. vos informations d'identification doivent répondre à toutes les recommandations en matière de sécurité (longueur appropriée, caractère aléatoire, objectif unique, etc.)
  6. votre connexion au service distant doit être sécurisée (SSL etc.)
  7. votre service distant doit être fiable (voir les points 1 à 4). de plus, il ne doit pas être sujet au piratage (si vos données/service ne sont pas sécurisés, il est inutile de sécuriser vos informations d'identification). de plus, il ne doit pas stocker vos informations d'identification.

plus probablement des milliers de choses que j'ai oubliées :)

0 votes

Votre réponse couvre à la fois les étapes de protection côté client et côté serveur dans un résumé, mais très clair et "à suivre" façon. J'ai beaucoup aimé ! [+1] --- Il y a quelques choses qui devraient être expliquées un tout petit peu plus en détail, et comme il y avait aussi quelques problèmes d'orthographe et de formatage, j'ai pris la liberté d'éditer. --- La structure générale, ainsi que la plupart du texte, est inchangée. J'ai juste ajouté ce qui me semblait manquer, et réorganisé le texte existant pour qu'il s'adapte. J'espère que cela ne vous dérange pas.

0 votes

Je ne me soucie pas de l'orthographe, des liens, de la grammaire, etc. merci pour cela. cependant, si vous voulez ajouter quelque chose, ne changez pas ma réponse. si vous pensez qu'il manque quelque chose, ajoutez un commentaire ou créez votre propre réponse. je préfère ne signer que sous mes propres mots

0 votes

Je comprends. --- Mon édition n'a pas vraiment changé le sens de ta réponse. La plus grande partie était de corriger l'orthographe et le format, et le formatage qui a besoin d'être corrigé nécessite de légères modifications du texte de toute façon. Les quelques explications supplémentaires n'étaient que des extensions de ce qui avait déjà été dit. --- Dans tous les cas, veuillez corriger l'orthographe (les majuscules au début des phrases sont le principal problème) et le format (séparer correctement le "sujet" du "contenu"), en appliquant les ajustements nécessaires au texte. Voir également le commentaire du n°7 "enclin" . --- Et, bien sûr, il serait bon de tenir compte de l'additionnel au moment de le faire.

0voto

Twilite Points 434

Si vous ne pouvez pas faire confiance à l'environnement dans lequel votre programme s'exécute, mais que vous devez vous authentifier via des mots de passe ou des certificats simples, vous ne pouvez rien faire pour sécuriser vos informations d'identification. Le mieux que vous puissiez faire est de les obscurcir avec les méthodes décrites dans les autres réponses.

Comme solution de contournement, j'exécuterais toutes les requêtes vers l'api RESTful à travers un proxy auquel vous pouvez faire confiance et faire l'authentification par mot de passe en clair à partir de là.

0 votes

"Si vous ne pouvez pas faire confiance à l'environnement dans lequel votre programme s'exécute, [...], vous ne pouvez rien faire pour sécuriser vos informations d'identification." - Si cela était vrai, presque toutes les applications qui ont une option de "remplissage automatique" pour les informations d'identification auraient de gros problèmes. ___ De nombreuses applications à deux volets (celles de cette question ?), comme les jeux multijoueurs et les applications Web, stockent les informations d'identification du compte localement et connaissent rarement de graves problèmes de sécurité. ___ Les données ne sont jamais sûres à 100 %, quel que soit l'environnement. Un environnement de confiance n'est qu'une étape supplémentaire de sécurité ("plus sûr").

0 votes

Dans le scénario donné, vous pouvez obscurcir vos informations d'identification, mais pas atteindre une sécurité (cryptographique) de 100 %. Le mieux que vous puissiez espérer est de faire en sorte qu'il soit si compliqué pour l'attaquant d'obtenir les mots de passe en texte clair que cela n'en vaille pas la peine. Pour obtenir les mots de passe stockés d'une application Web typique, il suffit d'aller dans le menu des options de votre navigateur et de sélectionner "Afficher les mots de passe".

0 votes

On ne peut jamais atteindre une sécurité à 100 %, que ce soit dans ce domaine ou dans celui de la santé. dans tout autre scénario . En effet, au final, tout se résume à des séquences de 0 y 1 en mémoire, ce qui est réalisé en suivant un ensemble spécifique de règles logiques, qui par nature est toujours réversible d'une manière ou d'une autre. ___ La base de la sécurité cryptographique est, a toujours été, et sera probablement toujours, "pour le rendre si difficile que ça n'en vaut pas la peine." ___ Enfin, vous confondez l'auto-remplissage/login du navigateur (qui concerne les sites web), avec l'auto-auth/login de l'application (qui est enregistré dans des fichiers cryptés et uniquement là).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X