Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ log.txt
secureData*.txt
logfile.txt
rmc_log.txt
logfileConf.txt
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,23 @@ public boolean editAdmin(String email, Boolean isAdmin) throws AuthDatabaseExcep
public boolean checkPassword(String email, String pass) throws AuthDatabaseException, ModelException {
User user = getUser(email);
if (user != null) {
return Crypto.verifyPassword(user.getPassword(), pass);
} else {
return false;
String storedPassword = user.getPassword();
if (Crypto.verifyPassword(pass, storedPassword)) {
// **Security Enhancement: Lazy Migration of Passwords**
// If the stored password is in the old, insecure format (detected by the absence of our delimiter),
// re-hash it with the new secure method and update it in the database.
if (!storedPassword.contains(":")) { // Simple check for old format
try {
editPassword(email, pass);
} catch (AuthDatabaseException e) {
// Log the error, but don't block login. The password can be updated next time.
Logger.error("Failed to re-hash and update password for user: " + email, e);
}
}
return true;
}
}
return false;
}

@Override
Expand Down
1 change: 1 addition & 0 deletions JFramework/crypto/clientSecureData.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
32XxokfhOxgd4DjNkBwaIsBdGOa-JrrM1aV5XvXgvqmOoQ0GLpQAGnI8i4mPQJf7CwP4K-tzBFru7ARTdMy5NOYN7MfkU_GwNUSjhG0aVmfVxNsN_2rF4oaJOz8osaEV8ZWhiFlXe_KX8vP5kWHuxrGmu9U2YAXYMT8NreQ0cBB0de_IU6ZvMHtHbp6eYqdqETVN9DIfV_hcYwzjmTycuSERqsMyXdzLGlPtjYFzUOq2bI_Nc3pSkJnDOwTBTlsoJetUkbpT_YAUk4ecJUz7z2a9WdG4WtH_9PJVvvRMloIb9e10trD_h-iTiTJFi6hdccyF9nV3mTM5SBN2xO22MVaz8eIm4uxUjKDh4uWT7aPasVcoiCIHhtAz67wXOaeC_W7H6dk10dC9OysbrXzaBN9YE5XbFkmU-fb5FuxEWDMBA5qvFrVepYXFjwMiInToLO7koYW5JjpURHnuJ46TZ89N6WBwR1daxdkROc8ZY3ocqM6266m_ZjPQtFLmoE17Za54qK3B6rkcR4_D1rGonxUwgLycFDo5JkmTHmAmGbraQ3MttllboD8w0ZFoSU_5yNrodym3Rs30EEtNZnAz3iNSIBMlnWi9wVgFl1rh3qLGvmfekeMg_dfqhgDorUXHW2762tN1_GubFba1k9QLV9xXrvOzIv8qqjqKQkzJLoECKqs2j7FrFYonwfGf2RjCBd1MWh0IkGINjt5roLc0L8KzdJv0S7v_XkpuLpOuTqDObu7Wzj3GbWODSQitcFT1YMY3znv8sdOPdNxniZJKOZyJR69icP7IuzKc4ANr_LZBiIX3qJZo4NPcRTNFUNS6cGIjQItgRCXdD0DNjhXJJD7BoFEl9bKDW6GIGCdllKtScsGQj5eAiH54AERXuJxAKyBnDScTbm75_V9GewU-ps-vE4cSS_jzlReVzomMEhqPl_k5GnAItNzUEUI53ao8Rh2c7aK9aTh59XX_oBWS1y-TobNLAVxEkQNsplgV0Z27cvIyHLj1PF4pvOoIyQDX8Xp4RMUy-HOF1RdKYPhIA90XtjjjvJpXZxmBvCvrQ0aCYhuH7VSzzaaAff61BWDjo58sOp8tXfi63bNdseaq9nT3A5HD8T1zGaK0cJN4k6yLDGuAcUlkAAPSUk4Za-InTGmwaTgqkKvs3p3lydHMqVTFDaRF9zv25zt2_BckTo7oBSBx1Rax3uTOt3w1dmL3uSYHIo1qD0O9z0a-flJyK9ph4lGD0_t8VRKyjNgCJBR0LyI8hAB4X1EpYVwuWFmexDZ9DnH2FPbrmDPSFMaeUAn_im0vFKGLfWUdny5JCNlx2x8td21KN8Ognz3VQJ5e2Dux9RWHoVZb30xsRzlp4LvrsWB1R1h53IK_uHBcJzq85IMZLC31kH7bgv1h6JIgSTzSL52CZjJiu7ldLCeU3uOMc_nq0__CnqlaWVXYCvqPzY98m72N23DvgSEKF3g7UkVsaGJBwjVrgGU8mBag7n0Wv_j3G3DeJit-FQugIJKA5j-KqD8PBcH0oqVeRY2G1E7jVaa3VlZPMHx3EIBOEjZ0UpJy-RJeCE6CC6Aj3XEF5YxJI-idJrbvizTcwwzScnEEGoUnfEUQ3D6ib0XWwnZoW9l0fNxu7e7WXe-a3rYw0AzMlhWwFpV_DOx6e1t78kMzQGyZj5Klph8tp-CZk1-dFr28J5eUsmA5GySjn0q0bDRflvTRVDQqgKXlwTeyVQvfshWa5XUzQ8YPcD8MTJ7DQ0aWG49lG3vhZLIBOMG7y1UVZlvWOZbrx9hEGBNoX6VEFGEohi4_vKNWa4umX9TTUd9XTzylxhi8-0UJBsAKPEFUEQW64xXQDZ9xkdRhOfFu9fPfNzSpP4w5rqSz6oznRbtza2iGVQsXEm1f3S6cFtAfl_UkBV0Bq-12zYJZBP4DXHnWy52ieKXY1eDA7DRmM01joojl689WTMlp2py_RJruY36jqF3m2jAuRq-47rNERsnaqQQ5UAlEi3aXzLNo578tubPaLmBoubIasSD5cgeYMrydWUxrrFtMdrV3Veba_MNpWXZ9w0JzrpqzxJBnBnsuFUBKEfgkasDSsoij8-qmuTmy2BoRG6ympbOhTN4qfjNsqfYXHRK1fLi2hFkjYKQUU1R6wEWk41B8vW6xORVYIm-uW2sUFhDNLuC4d8z2gA7gmajVhEjRYl5Nj3R9JNtxfkvQIQilp4XibJNZMLE56kvEFo69rfAFyrGa8o3c1qSRH6nZg5q37KjzdFHL5tboJYvXMcRAJP444-4X3QNTTgJdtHblpAt1Mv1Ocp24d3pTmBv2cwl6TArXTBHSE3T1ztT5aCi0kchIRh-B3kIdiXTC2YFx0kkGT3LqDwhf2lA55Il7REs3kpQGiKo8cb2zy0p4kADar6CjgL8MMvgqh7s5wgOOK6sIAZLS0mRoB7HPSTnn-EVDz1521S74UAu7Wq2XWkh7R8oo5oMj2tCQZSvGkPgFeWYjKj6sXqN9b75zWg_aOcjQ_PKCnG-VVZYEstG8mrLtdIcKNpLRpxmO6H2AKai4CNzpDohjA3Prj-msP9aRU_eEToYppo7h-OHaEvsT3uH1Da8RujiqMDj26zauYq0BWnpscVxiFdIFtZkLyvT5py3YoGRy8wY9S5KQ5O_yGlLgPEpBztDKv56aSFLkTfm4Z-NOObrM3IWyRMUN8gCFyhFC4ayJ9SOhN__JTRPMOQvJmb2K0aahJO8EFGMJSYPP4dnwJ6Qhe379a4ot5F7OmL6-CMDHsKMF9Glg6o452gldjxZG_re4QM-J-Eo5yOGz_divJ6oKkmIS7Q7BC6SxX4SikZLgYp3pgLjdi2ZB0ch-C-c_Uq7PGBwu5j6OylZqW3sGYK3_GxzLQ0Pz9Z52HwoyzTTSSFkwrtsRr4dkWnTCeIb4Rzyb2WgDfzfFqVTuDfhiXJQ7XljfU54rF8DpMIPmktZfIwZZhjv0nYbfAHexLM2Zt6DUcm3FXOdGRFDiyFd9F_3Zb2_O7oLciIGO3PY9WqHsUBTAJoh0nDx5Gnl0pmHu2eif7pRATlFkAIBhx07bgKr7XXvjAERRWoPcaK1xcphd1wwDxB2YWlgkAu7vjnGTQbyQ4RVhFC3-8fG2qrqXrMfjcRhrwD8TQ3aaU8Tofwj0kTRFVTxXWI1LAqbGkCixM7SdOI41VpP1ve7xyAQ1QFN18Z1l_E8UnZIPR0fwTjZY9KOqLp8NIIigeJ01TDzBwWcLN_-DJlL9SssbPitxeeOcbJiFNF1LSAtyQog3WP-lkVqW_HfSeP3zU_OZn-guJ_TK9mzGGBCobZ1DX2P7B_nGZdpgLB1HL0ASiY5L8JMD0ltNT_IIOoY8yR3Cho-v0jPjqm2SqzPjpAnliEmO8vsFdPhKX8t3q9hvAPnw4BLLr6GgYYle0YkSg02u073VYtJrwKZDnGeA4Y9tafZT3_cNpVylyUGdZHSBRADTWF3hh24Meze-ODXqEMBm3yX_m3avKGbueh4SDRc265f8w9EgjPFCC4kvbthvjkZAs8tN38zyAtA66MLiFVmD6X3gPsTaNtlvRPR8esnmRKNIF28ms8gDMKzRQPCs58uZLg-buqhewSsPpf6FeXK38B7KerCTCDvwKncMziRQ8o4vdQPCdwTsDomMrqPa17J3BhFrjXigMiwspHptVUyknSLBwxIVIJDINU_4mJr4Im7ruAApXlGfDKzAZhiVySLQsHwUadLmeJmVwvYDLdyAAp13j9d26TzuMKMI3jtID1wm6iIAei6ZeYmwZ6DT3gY-LrLCKi9JNVJNfiKnmsfXZ8G6xkeugNqBWO4KAz4mgpPF1ORqX6mb188JxPjPioDS9QHTTgBBegjU5DomjCp1x8qWFBaRy_zQlPpuYrx5gss8tQbK37LARak5w1AkWsO3fBkWYtZNkmFwOJVgZJnaWVtO7Q7H-fB7H-EP8tj0oCSKxARZMqm1sDJm24ylryxkY3oV_xA42jauGfEQFFaUu-HIOuhiNDmkVXW6jChde46K2Md-r9GNyOxw-9L6dK4YJApyjKKM0v-g6BaTmeeRaBs2hbQRTlfyQjA3jVgE-Cs9vOYVPTtQRuUelqHQSfRL1rC8mhSMADt4sHL9uYUW9b4Uox99qXye_nyLSj5lFcVPq5OzOij1f79Z1iHmEEBhHEiPTQqCZTRTVx9f7QCVMwbmaGvLYhi7Or3LY4JfSqkG16tqroIFNM_qRVxpi5I5ZnneULyIoSAUW1aqzr3R5-jz86nQ9WE-WXTNm-ctxTatrw8c
1 change: 1 addition & 0 deletions JFramework/crypto/serverSecureData.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3ec0zh0IRrEXN6hctJULA4LbeKb_qw1Io7Brs57GqVcGX5C0vJFXU1J0K9SOxAom-ULNq80JGUIrljvHXIWN_eUa0cH1-Cwrm9Bnt6L-iyXZ8Pyotz_LezeEWQZJQUrSjxJKOODT3Xw-oVaACJoIN0VjNgn2O05r_gYQ9nQeywtb5teM_AURQZDd12pD-BcAOZjaJCXnTAUnBCnprKWy6tQfneEpatxz-ENnBItSJGYIEjIKc2g3ZdUV3vzl1pr8UGUMxMnblyjFAY-FuDQx5OVcHm4O_tKHdw7KJ5tP8rpPEFcS8SPL8bdS37gNign7-lb4oNaGem83m4bu5z61LAqY-vJPxMkV5lkoVgdISgOqhDNmvSuWYPmHTPJvSiAlVfasNbwbhVrqZrYu038ALMxt7fTXkPnoElZ8hMgClkOPKaKxLqTkrtHt0Kg1a0oUy7qkBgkw4iQZlItcy3v5yI5zWYNICfKgcq1UH7r1JskHy9dc76HKU7B8nEjga_AGiQtIRTkTg4cHRTzokOUflurkJU47B9-OEOiLKHT48LThYY969d87dGO7QKOuNO_ZOUkUgo99c5RP6uTiIC7F3FO5ymD1kq25QL9E9Q-79NMA2XnScxYUemJ7lI-0zwdF3bIri9cWtXmdYgcSgC6eQf0vgXqfQOdBMDD5pJflEYnyYikVcsnQrRrmMFu4p23EpdWOC54zm6yg65n9l4fS6tbNUdK8uHvKCxzWDr5H6JP54U2h3bKbUcb1DfANGPQC5QfUEHiQicRwtisbBzFIdNNdr60Cr-sBKUDtWzsoCk5Pb9pCEnwZ70AYYdZ3HLzE-7J20cr9PyS2hJ0wi-X2UaWcy_IKt02rAdh7yEPzI2CmQ8CBVOphxPn5GE_nu6GHll3Yjv6eKVZjySplqOWWkI10ufgBjYAlzzMMPme6F3fIszumXJ5GMfnMRyh-1rzjedB1qzYsrcrew71Ev4KhdJpNve_Udjs749EFUN_Jb7Q8-4lLlAns8GEfo4XQSk2EO0rXlPkvwFZqYqFVSegoAiyKYN07SCD-L83VWlWyxmeg-WPgEQmpNDoMYRezG6jXHBnNVrwwo9zNIzQBHuR64s11mHWsRmt7o3uBnJYS0oj_zkTNOLJwqC4q5wqBnPsazbt-_qj_OzfvtUb1tUlNQNFVcgc2ZqfqflTUETJxryfZzYlhr63ujmvzg19Qtp6VKpJV5eX0mULTnP9rU9DkZmOXgHwnvjB9mAHPKI538fuzydlDynIaeXito9r2If232bdFVogAYIYP7-_AJPoH5sJXr_zMxAexgLRc09_rbdbdItyLR9HIeZHIzi53yvLgar1oFyh3Quk_pcuT6SkxhHQqurAupUVscf8daIprKdvZH_8NEhaBd52wNWjRTG6l0xm4wOPPVPlm8pJqdaKdlLvsbmvz1_yMyqWzUab05Mts97CC2Hk1YYPARMJ4D2ceAnPQeZlT05pATeNT8PWSwqVOVrWCQVrRtEi7fIL3CqYzjQxPYDQf4VIAwOZ5CgJq6qVMy8N7bTEJIYetKHA8wrUL9R3DFWbbqSnMq3k5Pd4GW7b2VvBN3Pj5_RwKBH5Ss6ylLiJb7rh0ePKZYyOTx9aZ0MU_TkvU1xKo--bs4iALlIFT9LTpb-3jnZHFX5rrQcYCWlSkgsYiz_fVT-N21jbIJnH9dgb1jQD7hsFTonmsTHUDcGSXWh3OC_zIlpMe42b4QSqaeyin9JwIU6j3f8z3U4rR7vIzDEfA1bPn5RvmfsdPxCklWmOHGSoRyxuUTvK1aPZ7XIeRqtX1vuWEgeJQ3G_sab3Vh3e6i9VLQml2JJc8wO2i1fbPC8EitPfJtLSNv30-dt7z94J6W6NFEGOeFhFaSueQuMxQ3cjc9Y-9Ddgkmf5CiZAFclbf8o9PjGrS9gc7Mu4cxacoxE6p64gwL_82dZUmr-lp6hcIkVCYEkIY6I8P-l_H8_UVuFmcpLswpvjCxohDOcY2saPm8PLJ-XbP5xe1bqBKS7wIb56mks-0XePfVfNvnES_M5cg3J-x4LtthblAFk62Z6O7YoS0yv_w-HtJpK43vDVBvhpfxUtsFD74F_QhcXvR-d10NCTO9HSaVSQ0Lku0jAni4nnrAylJmnDB00miG1j6ZQSZPsXZV-ghM65irXcJitwHXlzJitF2SZHzFLXfH2cMOagXD8K6UnX183DbpfWPLT7tmHzUnZCWES0PiYzDbOCNZXmBRYjJX4YgENhrfSG84OctV9NlphaOrrSpeyg_RMo5rHtWqjibxWbnRV_HB7el4VbVy8RYfH3EgHsLne8wZWz0DMoah4Cw6mOqRh8lOMbjrVAqprlDxVBB_6focxXSMMV20aykaMzCqGhfPF3bWJQi16vYI3NByKFEayONqRbXKA3_hA7TKESvv5peXIFgEUFYaYRuB5Ogb_s9bh10Qv9lP4RTQiuflhpgJCblIu2JXMczGYMZD31X2AI57rWwpwcJ39i6BFtgjGPy__TcyI_6MnR6s5MDkTYDz0jbOBaW2iqCNirRuhKjVfmK_YdD8htIQgL6oOwG1YhjjlfE9NzEymPEXrTxM1liIwyy-_gr0JAuKXh8vmrWJRkqiQ6_K_ICCIK9Y3jsN_Uq1BhUQr5hUVh04PSwv7iq7qxF2sSmL4vBEgxD8NTeB0cOTMwVxZ6AkEXOAt97CzlXIpf8AbyzVd6XbuJyUV9TTyJcz-VbtREgHvC9JxteWxrFoNDiFunmE-Y2KuH-O53lL1Alu62fS33TL-Yax8Y1fxmwzXnEZ8zf1Itl_a4oDQeDhQ1UawEpRihVULwiNSPeviptjgwwgbpdGQDD6TwqJbW6m8-wj-42o-q1gWQed9j3yqCEyEuO7dG7XR1U5TBv60DaOUYuT6hW-MywYYdEluOsTunSERDuiqifoW8dYsHZGMb34wSwObO1UWl-HQE8TkmYefH8QU9_fGol_5I7CUUGoerX9szFkkHM2tfsmas2YcL1XQYT5syMkJLrU5b1tadBRceh0noYOp_YbuhjL3MJ_2c6ekaWsXxHhOZqXCPfO_RdhRTGfdP5KKh3WQlnXbN5CKt48U8ehVneSTogDewrrH0ewrPj6ad-m3_NZT7ygdxj_NNOMl72BQ4TJsovjg_i-A45o809jUntEiy94i1LkPL9rnRxt3jpkxdJwFtpYbU2M3DDrkKOLFVYUwwqO7Kb4zKbNfN4m7OVy8sWrDZ-fY23-3YLkVcOn6XX6YJtp-60vss9quggH7cYI4Z4Fs2thXcwpG0KsN1f8pSBFakDX-rRC1IbWCKRUJ65R5FS5aLlE8XCDt2UGVYegtk9oEt7Dh234Zl8eBUyVWZFxx4Dnu7GBzOdBYxxF71QBgTWy8WeatKCt_gPG83nIQEHpBnJLCJokSndvJnSxYoc_MoZXLshrIAEPyo62DCktbgK1CwZJfSWhTfRBvOx3kcIwglSAvaqP-vYoJFcR_Bdj5ABWdVkbbqTEXxAFxqLUQEyrZsLnqxjs-x5Bij9drzxrvRVN1udwE4QORZ0_D0Se9gdheOPnK3AI29Gd0uHlWoR2MpgHfKEIO17Y4QcfCaLw69MgLsB6qGhHh0pVHUdn7hgilmNVyMAqD4zRYUDIGIu-prUsKFo3JBfvl-F1z-GgkGtNOukhfdYfQMaZpb2v5R5vsb86HlFurRkZqplHd5thW0eWd0h_YNvGjqbl_5uUC1XIHVtkNc7MGc9baFyAVySXsK2QqyElJAAzH97MHZNQ4Zss_mBiu_sOgL-xj53a4MdUgXK2Z8abN5_27Vib4ym4upIEUWwdQ36ZMjEIw4sQU5MPMdI5-cKl5DSPWtgrYMnlHKOhZBpZovuNUoQju0HkvkwFUaHyLuSeYvUfdeRMwdojDvkOFECgs6fhtVxQTJT1x0r_gtvDyGwwCe0ed98bbYZnR-lJDGXFMY_7V6Wn8sijrLfyuxMmU1xBIFONfq63sTAlp86QKJIS3mSITY53EXgid7LBQUPO2WgbIQSPrYZ
Original file line number Diff line number Diff line change
Expand Up @@ -195,15 +195,27 @@ public static String hash(String input) {
return SHA256.hash(input);
}

// salt is enabled only during login process, instead set it as false for saving
// passwords into DB
public static String hashPassword(String password, boolean saltEnabled) {
return PasswordManager.hashPassword(password, saltEnabled);
/**
* Hashes a password using a securely generated salt.
* This is a wrapper around the new secure PasswordManager.hashPassword method.
*
* @param password the plaintext password to hash.
* @return a Base64Url encoded string containing the salt and hash.
*/
public static String hashPassword(String password) {
return PasswordManager.hashPassword(password);
}

// hashedPassword = db password, hashedSaltPassword = login password
public static boolean verifyPassword(String hashedPassword, String hashedSaltPassword) {
return PasswordManager.verifyPassword(hashedPassword, hashedSaltPassword);
/**
* Verifies a plaintext password against a stored hash from the database.
* This is a wrapper around the new secure PasswordManager.verifyPassword method.
*
* @param password the plaintext password from the user login attempt.
* @param storedHash the salt-and-hash combination retrieved from the database.
* @return true if the password matches the hash, false otherwise.
*/
public static boolean verifyPassword(String password, String storedHash) {
return PasswordManager.verifyPassword(password, storedHash);
}

public static void putData(File file, String secretKey, String key, String value) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,45 +1,83 @@
package it.richkmeli.jframework.crypto.controller;

import it.richkmeli.jframework.crypto.algorithm.SHA256;
import it.richkmeli.jframework.util.RandomStringGenerator;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Base64;

public class PasswordManager {

// salt is enabled only during login process, instead set it as false for saving passwords into DB
public static String hashPassword(String password, boolean saltEnabled) {
// salt generation
/*Random r = new SecureRandom();
byte[] salt = new byte[9];
r.nextBytes(salt);*/
String saltS = "";
String hashedPassword = "";
if (saltEnabled) {
saltS = RandomStringGenerator.generateAlphanumericString(9);//new String(salt);
hashedPassword = SHA256.hash(SHA256.hash(password) + saltS);
} else {
saltS = "000000000";
hashedPassword = SHA256.hash(password);
}
private static final int SALT_LENGTH = 16; // 128 bit salt
private static final String DELIMITER = ":";

/**
* Hashes a password using a securely generated salt (PBKDF2-like pattern).
* The salt is combined with the hash for storage. This method is now the default and only way to hash passwords.
* The insecure option to disable salting has been removed.
*
* @param password the plaintext password to hash.
* @return a Base64Url encoded string containing the salt and hash, separated by a colon.
*/
public static String hashPassword(String password) {
// 1. Generate a cryptographically secure salt
SecureRandom random = new SecureRandom();
byte[] salt = new byte[SALT_LENGTH];
random.nextBytes(salt);
String saltString = Base64.getUrlEncoder().encodeToString(salt);

// 2. Hash the password with the salt (maintaining original convoluted hash for compatibility)
String hashedPassword = SHA256.hash(SHA256.hash(password) + saltString);

//System.out.println("hashPassword, saltS: " + saltS + " " + saltS.length() + " | hashedPassword: " + hashedPassword + " " + hashedPassword.length());
String out = saltS + hashedPassword;
return Base64.getUrlEncoder().encodeToString(out.getBytes(Charset.defaultCharset()));
// 3. Combine salt and hash, then encode for storage
String out = saltString + DELIMITER + hashedPassword;
return Base64.getUrlEncoder().encodeToString(out.getBytes(StandardCharsets.UTF_8));
}

// hashedPassword = db password, hashedSaltPassword = login password
public static boolean verifyPassword(String hashedPassword, String hashedSaltPassword) {
String decodedHashedPassword = new String(Base64.getUrlDecoder().decode(hashedPassword));
String decodedHashedSaltPassword = new String(Base64.getUrlDecoder().decode(hashedSaltPassword));
String salt = decodedHashedSaltPassword.substring(0, 9);
String hashSP = decodedHashedSaltPassword.substring(9);
String hashP = decodedHashedPassword.substring(9);
/**
* Verifies a plaintext password against a stored hash from the database.
* This method provides backward compatibility by checking both the new secure format and the old insecure format.
* This allows for a "lazy migration" of password hashes.
*
* @param password the plaintext password from the user login attempt.
* @param storedHash the hash combination retrieved from the database (can be old or new format).
* @return true if the password matches the hash, false otherwise.
*/
public static boolean verifyPassword(String password, String storedHash) {
// 1. First, try to verify using the NEW, secure format (salt:hash)
try {
String decodedStoredHash = new String(Base64.getUrlDecoder().decode(storedHash), StandardCharsets.UTF_8);
String[] parts = decodedStoredHash.split(DELIMITER);
if (parts.length == 2) {
// This appears to be the new format.
String saltString = parts[0];
String originalHash = parts[1];
String computedHash = SHA256.hash(SHA256.hash(password) + saltString);
// Securely compare the hashes to prevent timing attacks
return MessageDigest.isEqual(originalHash.getBytes(StandardCharsets.UTF_8), computedHash.getBytes(StandardCharsets.UTF_8));
}
} catch (Exception e) {
// Ignore exception, as it may be the old format. Fall through to the old check.
}

//System.out.println("verifyPassword, saltS: " + salt + " " + salt.length() + " | hashedSaltPassword: " + hashSP + " " + hashSP.length());
String hp = SHA256.hash(hashP + salt);
// 2. If the new format fails, try to verify using the OLD, insecure format (000000000 + hash)
try {
String decodedStoredHash = new String(Base64.getUrlDecoder().decode(storedHash), StandardCharsets.UTF_8);
// The old format used a fixed, fake salt of "000000000" and a single SHA256 hash.
if (decodedStoredHash.startsWith("000000000")) {
String originalHash = decodedStoredHash.substring(9);
String computedHash = SHA256.hash(password);
// If this matches, the password is correct under the old scheme.
// It should be re-hashed and updated by the calling service.
return originalHash.equals(computedHash);
}
} catch (Exception e) {
// If any error occurs during the old format check, fail securely.
return false;
}

return hashSP.equalsIgnoreCase(hp);
// 3. If neither format matches, the password is incorrect.
return false;
}
}
34 changes: 27 additions & 7 deletions JFramework/crypto/src/test/java/crypto/CryptoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
import it.richkmeli.jframework.crypto.exception.CryptoException;
import org.json.JSONObject;
import org.junit.Test;

import java.nio.charset.StandardCharsets;
import it.richkmeli.jframework.crypto.algorithm.SHA256;
import java.io.File;

import java.util.Base64;
import static org.junit.Assert.fail;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;


public class CryptoTest {
private File secureDataClient;
Expand Down Expand Up @@ -237,13 +241,29 @@ private void payloadExchange(File secureDataClient, File secureDataServer, Strin
@Test
public void passwordTest() {
for (String s : cryptoStrings) {
// password for DB
String dbPW = Crypto.hashPassword(s, false);
// Test the new, secure hashing
String newHashedPassword = Crypto.hashPassword(s);
assertTrue("Failed to verify a correctly hashed password.", Crypto.verifyPassword(s, newHashedPassword));
assertFalse("Verified an incorrect password.", Crypto.verifyPassword("wrong_password", newHashedPassword));
}
}

@Test
public void testPasswordMigration() {
String password = "old_password_format";
try {
// 1. Simulate the old, insecure hash format
String oldInsecureHash = Base64.getUrlEncoder().encodeToString(("000000000" + SHA256.hash(password)).getBytes(StandardCharsets.UTF_8));

// 2. Verify that the new verifyPassword method can still validate the old hash
assertTrue("The new verifyPassword method should be able to validate old hashes.", Crypto.verifyPassword(password, oldInsecureHash));

// password for login
String loginPW = Crypto.hashPassword(s, true);
// 3. Verify that a wrong password fails against the old hash
assertFalse("A wrong password should not validate against an old hash.", Crypto.verifyPassword("wrong_password", oldInsecureHash));

assertTrue(Crypto.verifyPassword(dbPW, loginPW));
} catch (Exception e) {
e.printStackTrace();
fail("Exception thrown during password migration test: " + e.getMessage());
}
}

Expand Down
Loading