diff --git a/.gitignore b/.gitignore index 70e39ba..1e3cefb 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ log.txt secureData*.txt logfile.txt rmc_log.txt +logfileConf.txt diff --git a/JFramework/auth/src/main/java/it/richkmeli/jframework/auth/AuthDatabaseJframeworkManager.java b/JFramework/auth/src/main/java/it/richkmeli/jframework/auth/AuthDatabaseJframeworkManager.java index 69b74cc..9c7361d 100644 --- a/JFramework/auth/src/main/java/it/richkmeli/jframework/auth/AuthDatabaseJframeworkManager.java +++ b/JFramework/auth/src/main/java/it/richkmeli/jframework/auth/AuthDatabaseJframeworkManager.java @@ -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 diff --git a/JFramework/crypto/clientSecureData.txt b/JFramework/crypto/clientSecureData.txt new file mode 100644 index 0000000..ce03289 --- /dev/null +++ b/JFramework/crypto/clientSecureData.txt @@ -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 \ No newline at end of file diff --git a/JFramework/crypto/serverSecureData.txt b/JFramework/crypto/serverSecureData.txt new file mode 100644 index 0000000..04976ee --- /dev/null +++ b/JFramework/crypto/serverSecureData.txt @@ -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 \ No newline at end of file diff --git a/JFramework/crypto/src/main/java/it/richkmeli/jframework/crypto/Crypto.java b/JFramework/crypto/src/main/java/it/richkmeli/jframework/crypto/Crypto.java index 6990a2f..12f9f19 100644 --- a/JFramework/crypto/src/main/java/it/richkmeli/jframework/crypto/Crypto.java +++ b/JFramework/crypto/src/main/java/it/richkmeli/jframework/crypto/Crypto.java @@ -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) { diff --git a/JFramework/crypto/src/main/java/it/richkmeli/jframework/crypto/controller/PasswordManager.java b/JFramework/crypto/src/main/java/it/richkmeli/jframework/crypto/controller/PasswordManager.java index 7d2b934..d108919 100644 --- a/JFramework/crypto/src/main/java/it/richkmeli/jframework/crypto/controller/PasswordManager.java +++ b/JFramework/crypto/src/main/java/it/richkmeli/jframework/crypto/controller/PasswordManager.java @@ -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; } } diff --git a/JFramework/crypto/src/test/java/crypto/CryptoTest.java b/JFramework/crypto/src/test/java/crypto/CryptoTest.java index 9c7420b..2f07e0c 100644 --- a/JFramework/crypto/src/test/java/crypto/CryptoTest.java +++ b/JFramework/crypto/src/test/java/crypto/CryptoTest.java @@ -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; @@ -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()); } }