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.
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.