Erinevus lehekülje "ITI0011:harjutus 16" redaktsioonide vahel
(ei näidata sama kasutaja 11 vahepealset redaktsiooni) | |||
3. rida: | 3. rida: | ||
Kirjutada programm, mis loeb etteantud properties-failist kasutajanime ja parooli ning salvestab turvalisema variandi teise faili. Teise faili salvestatakse sama kasutaja andmed, aga parool on soolatud räsina (salted hash). Mõlemad failid on ''properties''-tüüpi failid (vt viiteid allpool). | Kirjutada programm, mis loeb etteantud properties-failist kasutajanime ja parooli ning salvestab turvalisema variandi teise faili. Teise faili salvestatakse sama kasutaja andmed, aga parool on soolatud räsina (salted hash). Mõlemad failid on ''properties''-tüüpi failid (vt viiteid allpool). | ||
− | Algne fail: | + | Algne fail (user.properties): |
<pre> | <pre> | ||
username=Juku | username=Juku | ||
9. rida: | 9. rida: | ||
</pre> | </pre> | ||
− | Teie peate implementeerima failist lugemise ja kirjutamise meetodid - | + | Teie peate implementeerima failist lugemise ja kirjutamise meetodid. Failist lugemise jaoks <code>readDataFromFile</code>, mis loeb kõik võti-väärtus paarid ning salvestab need <code>HashMap</code> tüüpi andmestruktuuri, kus näiteks meie näite puhul tekiks üks element, mille võti (''key'') oleks "username" ja väärtus (''value'') oleks "Juku". Faili kirjutamise jaoks on vaja implementeerida kaks meetodit: <code>saveNewUserData</code> ja <code>saveDataToFile</code>. <code>saveDataToFile</code> on üldisem meetod, mis kirjutab etteantud võti-väärtus paarid ''properties''-tüüpi faili. Ehk siis selles meetodis peab olema faili kirjutamise osa. <code>saveNewUserData</code> on vahemeetod, mis võtab ette antud ülesande jaoks olulised andmed. Need andmed tuleb teisendada kujutiseks, mis saadetakse edasi <code>saveDataToFile</code> meetodisse. Allolevas mallis on kujutise osa <code>null</code>, selle asemel peaks olema aga instants <code>HashMap</code> tüüpi andmestruktuurist, kuhu on sisse pandud kõik vajalikud väljad. |
− | Programm kontrollib, kas on olemas räsitud parooliga fail "newUser.properties". Kui see puudub, siis tehakse see fail | + | Programm kontrollib, kas on olemas räsitud parooliga fail "newUser.properties". Kui see puudub, siis tehakse see fail SaveNewUserData meetodiga. Uues failis peavad olema võtmesõnad: "username", "password" ja "salt". |
Kui aga käivitamisel eksisteerib "newUser.properties" mõnest eelnevast käitamisest, siis programm kutsub esile authenticate meetodi, mis laseb kasutajal parooli pakkuda. | Kui aga käivitamisel eksisteerib "newUser.properties" mõnest eelnevast käitamisest, siis programm kutsub esile authenticate meetodi, mis laseb kasutajal parooli pakkuda. | ||
− | Esmalt oleks mõistlik realiseerida <code> | + | Esmalt oleks mõistlik realiseerida <code>readDataFromFile</code> meetod, kuna uue faili andmed on otseses sõltuvuses meie antud faili sisust - mida võib vabalt muuta, aga kindlasti peavad olema võtmesõnad "username" ja "password". Meetod <code>readDataFromFile</code> peab tagastama <code>HashMap</code> tüüpi objekti, mille võtmed ja väärtused on täpselt need, mis <code>user.properties</code> failis. |
− | |||
− | |||
Lugemine soolatud räsimise kohta: https://crackstation.net/hashing-security.htm | Lugemine soolatud räsimise kohta: https://crackstation.net/hashing-security.htm | ||
23. rida: | 21. rida: | ||
Kiire ülevaade Java Propertiesist: http://www.mkyong.com/java/java-properties-file-examples/ | Kiire ülevaade Java Propertiesist: http://www.mkyong.com/java/java-properties-file-examples/ | ||
− | |||
== Mall == | == Mall == | ||
31. rida: | 28. rida: | ||
* Created by odin on 20.04.15. | * Created by odin on 20.04.15. | ||
*/ | */ | ||
− | + | ||
import java.io.*; | import java.io.*; | ||
import java.security.MessageDigest; | import java.security.MessageDigest; | ||
37. rida: | 34. rida: | ||
import java.util.Properties; | import java.util.Properties; | ||
import java.util.Scanner; | import java.util.Scanner; | ||
− | + | ||
public class Main { | public class Main { | ||
− | + | ||
− | + | /** | |
− | + | * The input file for plain user data. | |
− | + | */ | |
− | + | public static final String INPUT_FILE = "user.properties"; | |
− | + | /** | |
+ | * The output file for hashed user data. | ||
+ | */ | ||
+ | public static final String OUTPUT_FILE = "newUser.properties"; | ||
+ | /** | ||
+ | * The length of salt key. | ||
+ | */ | ||
+ | public static final int LENGTH_OF_SALT = 10; | ||
+ | /** | ||
+ | * The max number of tries for password guessing. | ||
+ | */ | ||
+ | public static final int MAX_CHANCES_COUNT = 3; | ||
+ | |||
public static void main(String[] args) { | public static void main(String[] args) { | ||
try { | try { | ||
// check whether a user with a hashed password already exists | // check whether a user with a hashed password already exists | ||
if(new File(OUTPUT_FILE).isFile()) { | if(new File(OUTPUT_FILE).isFile()) { | ||
− | HashMap<String, String> data = | + | HashMap<String, String> data = readDataFromFile(OUTPUT_FILE); |
+ | if (data == null) { | ||
+ | System.out.println("Data from " + OUTPUT_FILE + " cannot be read"); | ||
+ | System.exit(2); | ||
+ | } | ||
String username = data.get("username"); | String username = data.get("username"); | ||
String password = data.get("password"); | String password = data.get("password"); | ||
56. rida: | 69. rida: | ||
} else { | } else { | ||
// "register" new user with salted hash | // "register" new user with salted hash | ||
− | HashMap<String, String> data = | + | HashMap<String, String> data = readDataFromFile(INPUT_FILE); |
+ | if (data == null) { | ||
+ | System.out.println("data cannot be read"); | ||
+ | System.out.println("(file does not exist, is corrupted, " | ||
+ | + "or readDataFromFile not implemented)."); | ||
+ | System.out.println("Exiting."); | ||
+ | System.exit(1); | ||
+ | } | ||
String username = data.get("username"); | String username = data.get("username"); | ||
String password = data.get("password"); | String password = data.get("password"); | ||
String salt = md5MyString("saltyRandomString").substring(0, LENGTH_OF_SALT); | String salt = md5MyString("saltyRandomString").substring(0, LENGTH_OF_SALT); | ||
password = md5MyString(password + salt); | password = md5MyString(password + salt); | ||
− | saveNewUserData(username, password, salt); | + | boolean result = saveNewUserData(username, password, salt); |
+ | if (!result) { | ||
+ | System.out.println("Storing hashed data did not work properly."); | ||
+ | System.exit(3); | ||
+ | } | ||
} | } | ||
} catch (Exception e) { | } catch (Exception e) { | ||
67. rida: | 91. rida: | ||
} | } | ||
} | } | ||
− | + | ||
− | + | /** | |
+ | * Let's the user to try to authenticate with the given username. | ||
+ | * The hashed password along with the salt is used to validate | ||
+ | * the user guess. The user has MAX_CHANGED_COUNT number of tries | ||
+ | * before the "game" ends. | ||
+ | * @param username The username to be tested. | ||
+ | * @param password Hashed password. | ||
+ | * @param salt The salt used with password. | ||
+ | * @throws Exception simplifaction | ||
+ | */ | ||
+ | public static void authenticate(String username, String password, String salt) throws Exception { | ||
Scanner userInput = new Scanner(System.in); | Scanner userInput = new Scanner(System.in); | ||
String guess; | String guess; | ||
85. rida: | 119. rida: | ||
System.out.println("Sorry, You didn't guess " + username + "'s password"); | System.out.println("Sorry, You didn't guess " + username + "'s password"); | ||
} | } | ||
− | + | ||
− | + | /** | |
+ | * Reads all the properties from the given input file and returns | ||
+ | * a key-value mapping of the contents in the file. | ||
+ | * In case the file is corrupted or something goes wrong, returns null. | ||
+ | * @param filename File to be read. | ||
+ | * @return Hash map of settings in the file, NULL if something goes wrong. | ||
+ | */ | ||
+ | public static HashMap<String, String> readDataFromFile(String filename) { | ||
+ | // TODO: this method wants to be implemented... | ||
return null; | return null; | ||
} | } | ||
− | + | ||
− | + | /** | |
+ | * Writes username, password and salt values to | ||
+ | * the corresponding keys (username, password, salt). | ||
+ | * in the file OUTPUT_FILE. | ||
+ | * In case the data is successfully stored, returns true. | ||
+ | * If something goes wrong, returns false. | ||
+ | * You have to use saveDataToFile method inside this method. | ||
+ | * You have to modify the call to saveDataToFile in order to | ||
+ | * pass the correct key-value map. The actual writing | ||
+ | * will be done in saveDataToFile method. | ||
+ | * @param username The value of username. | ||
+ | * @param password The value of hashed password. | ||
+ | * @param salt The value of salt. | ||
+ | * @return true if data is stored, false otherwise. | ||
+ | */ | ||
+ | public static boolean saveNewUserData(String username, String password, String salt) { | ||
+ | // TODO: write your code here... | ||
+ | // instead of null, | ||
+ | // send user data (username, password, salt) to be saved | ||
+ | return saveDataToFile(OUTPUT_FILE, null); | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Writes key-values from data into properties file which is given | ||
+ | * by filename. If the file does not exist, the file is created. | ||
+ | * @param filename The file name to be saved. | ||
+ | * @param data The data to be stored in the file. | ||
+ | * @return true, if the file is successfully stored, false otherwise. | ||
+ | */ | ||
+ | public static boolean saveDataToFile(String filename, HashMap<String, String> data) { | ||
+ | // TODO: write your code here too ..... | ||
+ | return false; | ||
} | } | ||
− | + | ||
− | /* | + | /** |
− | * Maagia, sellest ei pea aru saama. | + | * Gets MD5 hash of the given string. |
− | + | * @param str The string to be hashed. | |
− | + | * @return Hashed string | |
− | + | * @throws Exception | |
− | + | */ | |
− | + | public static String md5MyString(String str) throws Exception { | |
+ | /* | ||
+ | * Maagia, sellest ei pea aru saama. | ||
+ | * tl;dr: | ||
+ | * Hashimist on vaja, et andmete hoiustamisel poleks | ||
+ | * privaatsed andmed (loe: paroolid) lihtsasti loetavad | ||
+ | * */ | ||
MessageDigest m = MessageDigest.getInstance("MD5"); | MessageDigest m = MessageDigest.getInstance("MD5"); | ||
m.update(str.getBytes("UTF8")); | m.update(str.getBytes("UTF8")); | ||
109. rida: | 188. rida: | ||
return result; | return result; | ||
} | } | ||
− | + | ||
} | } | ||
</source> | </source> |
Viimane redaktsioon: 22. aprill 2015, kell 06:55
Kirjeldus
Kirjutada programm, mis loeb etteantud properties-failist kasutajanime ja parooli ning salvestab turvalisema variandi teise faili. Teise faili salvestatakse sama kasutaja andmed, aga parool on soolatud räsina (salted hash). Mõlemad failid on properties-tüüpi failid (vt viiteid allpool).
Algne fail (user.properties):
username=Juku password=raskeparool123
Teie peate implementeerima failist lugemise ja kirjutamise meetodid. Failist lugemise jaoks readDataFromFile
, mis loeb kõik võti-väärtus paarid ning salvestab need HashMap
tüüpi andmestruktuuri, kus näiteks meie näite puhul tekiks üks element, mille võti (key) oleks "username" ja väärtus (value) oleks "Juku". Faili kirjutamise jaoks on vaja implementeerida kaks meetodit: saveNewUserData
ja saveDataToFile
. saveDataToFile
on üldisem meetod, mis kirjutab etteantud võti-väärtus paarid properties-tüüpi faili. Ehk siis selles meetodis peab olema faili kirjutamise osa. saveNewUserData
on vahemeetod, mis võtab ette antud ülesande jaoks olulised andmed. Need andmed tuleb teisendada kujutiseks, mis saadetakse edasi saveDataToFile
meetodisse. Allolevas mallis on kujutise osa null
, selle asemel peaks olema aga instants HashMap
tüüpi andmestruktuurist, kuhu on sisse pandud kõik vajalikud väljad.
Programm kontrollib, kas on olemas räsitud parooliga fail "newUser.properties". Kui see puudub, siis tehakse see fail SaveNewUserData meetodiga. Uues failis peavad olema võtmesõnad: "username", "password" ja "salt". Kui aga käivitamisel eksisteerib "newUser.properties" mõnest eelnevast käitamisest, siis programm kutsub esile authenticate meetodi, mis laseb kasutajal parooli pakkuda.
Esmalt oleks mõistlik realiseerida readDataFromFile
meetod, kuna uue faili andmed on otseses sõltuvuses meie antud faili sisust - mida võib vabalt muuta, aga kindlasti peavad olema võtmesõnad "username" ja "password". Meetod readDataFromFile
peab tagastama HashMap
tüüpi objekti, mille võtmed ja väärtused on täpselt need, mis user.properties
failis.
Lugemine soolatud räsimise kohta: https://crackstation.net/hashing-security.htm
Kui pikad tekstid pole päris Teie rada, siis järgmine link on ka piisav lugemine: http://security.stackexchange.com/questions/51959/why-are-salted-hashes-more-secure
Kiire ülevaade Java Propertiesist: http://www.mkyong.com/java/java-properties-file-examples/
Mall
<source lang="java"> /**
* Created by odin on 20.04.15. */
import java.io.*; import java.security.MessageDigest; import java.util.HashMap; import java.util.Properties; import java.util.Scanner;
public class Main {
/** * The input file for plain user data. */ public static final String INPUT_FILE = "user.properties"; /** * The output file for hashed user data. */ public static final String OUTPUT_FILE = "newUser.properties"; /** * The length of salt key. */ public static final int LENGTH_OF_SALT = 10; /** * The max number of tries for password guessing. */ public static final int MAX_CHANCES_COUNT = 3; public static void main(String[] args) { try { // check whether a user with a hashed password already exists if(new File(OUTPUT_FILE).isFile()) { HashMap<String, String> data = readDataFromFile(OUTPUT_FILE); if (data == null) { System.out.println("Data from " + OUTPUT_FILE + " cannot be read"); System.exit(2); } String username = data.get("username"); String password = data.get("password"); String salt = data.get("salt"); authenticate(username, password, salt); } else { // "register" new user with salted hash HashMap<String, String> data = readDataFromFile(INPUT_FILE); if (data == null) { System.out.println("data cannot be read"); System.out.println("(file does not exist, is corrupted, " + "or readDataFromFile not implemented)."); System.out.println("Exiting."); System.exit(1); } String username = data.get("username"); String password = data.get("password"); String salt = md5MyString("saltyRandomString").substring(0, LENGTH_OF_SALT); password = md5MyString(password + salt); boolean result = saveNewUserData(username, password, salt); if (!result) { System.out.println("Storing hashed data did not work properly."); System.exit(3); } } } catch (Exception e) { e.printStackTrace(); } } /** * Let's the user to try to authenticate with the given username. * The hashed password along with the salt is used to validate * the user guess. The user has MAX_CHANGED_COUNT number of tries * before the "game" ends. * @param username The username to be tested. * @param password Hashed password. * @param salt The salt used with password. * @throws Exception simplifaction */ public static void authenticate(String username, String password, String salt) throws Exception { Scanner userInput = new Scanner(System.in); String guess; System.out.println("Hello, You now have " + MAX_CHANCES_COUNT + " chances to guess " + username + "'s password."); int chances; for (chances = MAX_CHANCES_COUNT - 1; chances >= 0; chances--) { guess = userInput.next(); if (md5MyString(guess + salt).equals(password)) { System.out.println("That's correct!"); break; } else if (chances > 0) { System.out.println("That's invalid! " + chances + " guess" + (chances > 1 ? "es" : "") + " left."); } } if (chances < 0) System.out.println("Sorry, You didn't guess " + username + "'s password"); } /** * Reads all the properties from the given input file and returns * a key-value mapping of the contents in the file. * In case the file is corrupted or something goes wrong, returns null. * @param filename File to be read. * @return Hash map of settings in the file, NULL if something goes wrong. */ public static HashMap<String, String> readDataFromFile(String filename) { // TODO: this method wants to be implemented... return null; } /** * Writes username, password and salt values to * the corresponding keys (username, password, salt). * in the file OUTPUT_FILE. * In case the data is successfully stored, returns true. * If something goes wrong, returns false. * You have to use saveDataToFile method inside this method. * You have to modify the call to saveDataToFile in order to * pass the correct key-value map. The actual writing * will be done in saveDataToFile method. * @param username The value of username. * @param password The value of hashed password. * @param salt The value of salt. * @return true if data is stored, false otherwise. */ public static boolean saveNewUserData(String username, String password, String salt) { // TODO: write your code here... // instead of null, // send user data (username, password, salt) to be saved return saveDataToFile(OUTPUT_FILE, null); } /** * Writes key-values from data into properties file which is given * by filename. If the file does not exist, the file is created. * @param filename The file name to be saved. * @param data The data to be stored in the file. * @return true, if the file is successfully stored, false otherwise. */ public static boolean saveDataToFile(String filename, HashMap<String, String> data) { // TODO: write your code here too ..... return false; } /** * Gets MD5 hash of the given string. * @param str The string to be hashed. * @return Hashed string * @throws Exception */ public static String md5MyString(String str) throws Exception { /* * Maagia, sellest ei pea aru saama. * tl;dr: * Hashimist on vaja, et andmete hoiustamisel poleks * privaatsed andmed (loe: paroolid) lihtsasti loetavad * */ MessageDigest m = MessageDigest.getInstance("MD5"); m.update(str.getBytes("UTF8")); byte s[] = m.digest(); String result = ""; for (int i = 0; i < s.length; i++) { result += Integer.toHexString((0x000000ff & s[i]) | 0xffffff00).substring(6); } return result; }
} </source>