From 991e47e4ad1fb574f817e4b4d72534b1f65265cc Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 23:44:50 +0000 Subject: [PATCH] refactor(crypto): Enforce salted password hashing Implements salted password hashing using a random salt for each password, replacing the previous unsalted mechanism. Introduces a lazy migration path to automatically and securely upgrade users' password hashes to the new format upon their next login, ensuring no disruption to existing users. Adds comprehensive unit tests for the new PasswordManager logic to verify both new hashes and backward compatibility with old hashes. Updates the .gitignore to exclude generated test artifacts and log files from version control. Co-authored-by: richkmeli <7313162+richkmeli@users.noreply.github.com> --- .gitignore | 2 + .../auth/AuthDatabaseJframeworkManager.java | 21 +++++- JFramework/crypto/clientSecureData.txt | 1 + JFramework/crypto/serverSecureData.txt | 1 + .../richkmeli/jframework/crypto/Crypto.java | 12 ++- .../crypto/controller/PasswordManager.java | 74 ++++++++++++------- .../src/test/java/crypto/CryptoTest.java | 22 ++++-- .../controller/PasswordManagerTest.java | 57 ++++++++++++++ .../test/java/orm/DatabaseManagerTest.java | 20 ++--- .../auth/AuthDatabaseManagerTest.java | 4 +- 10 files changed, 157 insertions(+), 57 deletions(-) create mode 100644 JFramework/crypto/clientSecureData.txt create mode 100644 JFramework/crypto/serverSecureData.txt create mode 100644 JFramework/crypto/src/test/java/it/richkmeli/jframework/crypto/controller/PasswordManagerTest.java diff --git a/.gitignore b/.gitignore index 70e39ba..4bd5679 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ log.txt secureData*.txt logfile.txt rmc_log.txt +*secureData*.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..ce6b262 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 @@ -54,7 +54,7 @@ public User getUser(String email) throws AuthDatabaseException, ModelException { public boolean addUser(User user) throws AuthDatabaseException { //Logger.info("AuthDatabaseManager, addUser. User: " + user.email); //String hash = Crypto.hash(user.getPassword()); - String password = Crypto.hashPassword(user.getPassword(), false); + String password = Crypto.hashPassword(user.getPassword()); user.setPassword(password); try { return create(user); @@ -81,7 +81,7 @@ public boolean isUserPresent(String email) throws AuthDatabaseException, ModelEx @Override public boolean editPassword(String email, String pass) throws AuthDatabaseException, ModelException { - String password = Crypto.hashPassword(pass, false); + String password = Crypto.hashPassword(pass); try { return update(new User(email, password, null)); } catch (DatabaseException e) { @@ -102,7 +102,22 @@ 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); + boolean passwordMatches = Crypto.verifyPassword(user.getPassword(), pass); + if (passwordMatches) { + // Check if the hash needs to be upgraded + String decodedDbHash = new String(java.util.Base64.getUrlDecoder().decode(user.getPassword())); + if (decodedDbHash.startsWith("000000000")) { + // Old hash format, upgrade it + try { + editPassword(email, pass); + Logger.info("Password for user " + email + " has been migrated to the new hash format."); + } catch (AuthDatabaseException | ModelException e) { + Logger.error("Failed to migrate password for user " + email, e); + // Don't fail the login, but log the error. The user can still log in. + } + } + } + return passwordMatches; } else { return false; } diff --git a/JFramework/crypto/clientSecureData.txt b/JFramework/crypto/clientSecureData.txt new file mode 100644 index 0000000..3a147a5 --- /dev/null +++ b/JFramework/crypto/clientSecureData.txt @@ -0,0 +1 @@ +oQMPy0fTLRdPWzyQN_a8Z3ZSx86PNn0Kmx7YaPDJWWmrCMtYyRqpKexxWLuhC_7-3Sh7bv4ntIZ2QyHfTPsfZwEXjcgsDuplwLT3sshGCzesJGseey0uVT6UHxrTWq-4eiebbeH9OcuD9BbHLSde4Wtvphgnj7nnTlFNWpq2fsd8a1qnCMU0-irML-6_vwuH57CO9oSCuqqwoKYPXPY1H5MMBdeUqnltGXpZWLpuqUkyTgULjkwJMxxsFFrcVt0dO2Ms4mAZOT0Ih0FBe06eRQAVTIZDIudwLoHVqdSdh4pBzjs6OS77GduFc9AfY0Yy_cv28bYbCmTD5OTL8GhB6An_03_alHPI165-PQ3RiNZazGiS88xgtNyI5NB5GVyBY3HLhaWxi2Phvk6uTnBpJUewJ1v8aJ3Rtz36H-GqonMXS4EkkVlnn8gQdp8sXHz40Crm-NC_Ilsee6Z8X6h8it_ODyp0rgjE8zRx9oOUlUdsraYG7jwqXPDY7Jvm8-o4jaarkkj78-vIql6Xzc_CMu0a8YSpOVG1IvRAk1h0sH9eoS8iZRlKvIJB_RTOjq1L-teK50htuBTF53oQreSPUG77LIoZqSvpBbdtqFeSOyDlZdRjLF0nzKlNrMFgUCcE-ynfGgHBaP6xuK_6zGreouB96__AiiL66yEYp3QX2AILlTWnTRalP08QOP3JPBLUodIqM93ugkfxwROQrSTFiTQxKvl--KrHVbTYkpKFslvsq80dJ9Hs2mbDu41FZbiD9wgXbOTkRxWg8Nrlsbx5cRMGk70YImwZql6KdDiXPyDek0YOycZRkAt2GvqRYotYCh9WvMparnQ1dHHCgAO5FTNJSQeSRaKdLhsJEfKvXNSiKFCDLsnaEa3pWXt7-IXNve3oyJIMcwH7JsnuMDTY-fQjV4EwvRbuxP0soZe2NcOcxMGaIYkorTNZqB9GyebYobgAMZ0rdJsFrVcrjukxpV_oyRyNwbvRVZGby0Au_NGazUNz41qJXlywynyBwCQtA_k7FNe4Gq2pzoFzSog7mh9Gz_9VlBrSqxSj3X6NfRI4J8C9FWq6_FmrznUiWg1miWTsWXdoWn0iZEq1mBQQ0rPvSkuPtG_1RaUW2p3neywD3zGtFL4YgOdzejCtbkN5pcubvL-6bd5d6l7yyXcQu6gQ0m7yhNn4vXE-xMDjkRK_Dr6ljZ4UXBt3MQfBuPaHi0ONAuzAx2bqfTWGJ2sjH1ez3jUu1AS1JaADHveBNMJxtwkvm4wc6ZfI1aL75U6hSPkNTG8yyG8AJIAuROIIBkQPNJUyZSxDXI0QHJNj6cMNWD2F5DtJaCsEzVfmVjsjfslAmUDsVBDXcLwY4wV7b_hCTho2GAPgcRN2Rj6G0XM7eRtRqOteJu_NqxWjLVUoPk2Arq7anF68Eh4z6Bw19eiPXAY-0bblCVcEROaxCUfjemeWILJEfPlXLSvNsox5fRfam1fWihEEyqv2s6A3Wpm7G69Ou2t8ZC_mgv938EJ2Yv8MORSiERxnbrslz7UuBmut9ckLqbZV9jdQlxOmKg2EOPRTTlbHB0a3mh4fRzLPFRsaco_7xkwwg_eqwltTQlyFLtSckuXS1HzpZQdWr1sulZa4zU3qU_TkTkUMfp1g2G2XaudX_AhppgLqn8tCdd0iyG5ItqsVT5BR1RuOrAntPEeSCAVCHDte4YPim3dnSk43Ga6Ox9et1peOndugZHueowj4p3vWWufrgAVQBCmvLWD-hE465x9kzwLDr6RMDKWVFGFAJTeQpYVff82VeY36lto2G06Rj2ItTzVYf5wa0VCe1aR0-xUndgKXOVRS3ZJJmxWNQnONIiT03qRP9x8BIjBxokiUaaSEjGmWP8Us3dT8I7HbHfe4y9j8b85rhxb1e2YW7IO2RdIQJ79Qz5A6rF64fT0LgUkOf8fsqghNHBxkYFDFT2TXcBE_koyxNqVCg7p2IT8Kg140RDIvodZHYPfT2RDIH7Sw5qLoT-r8icUJReHVJjgisSeuITEOhRenYTj_y0qstnFRviKiOnWZaHe2dsVHcmtn9mCn3DL8NnVb53x01ch_6QFL4EjqQUvALXDFNuZ0nF6-isjwcGLI80x5uREQwVNKMbwcs5_D92xeroI2ZpP7VXeYlRuGGC3M4IevFEALE7Ql0DSF_AvCakajXddoWtXJSmPS6souGvvxfLNitb2L_dIsNmcEpbbwvZbTu7wmvhLHlwZMiEu-5bNA9_Em4aSFIEkMz2O727q7UghW9QTEEKKEIRray2Ol8Hs6trCjEMivcIzi-oZ_uA019b0QzbW4t5UudqgzDlEw008X9u1aT5Dly4zmRq836u06xgjTOe2XtBDBkJGBCwUMfH0GydfceTVMN-WHgrEn3SMsDWnmjtkyCMye41xzCFcb_AOvdUhsuSr-plDS8UHWZrxStwLGMR-JxGbbeVPU9ns8xbXnjXzUoJWXLEUOANkztXXv87gzAknqd7aq5OlfXxr7_nHImOtx3c6gEAxW1KG5VlOSZTcs-8g4gp3Y4VrW9GfU8ZU_nUam0luRhyDlilkWr0FrpnM6J0bExYVgXkCTQki2AqJuGkAbp97AlJU5oSX85kNkxX9cXAsOOfO_xj3HbuweyCQ6XLHCIi7dpW6q0xmf9uSCosbtqaA0SIqDxECs9rWLidik5iCe7h0Lqe3OxuLEUdneVkJRdoMLfagdyOSDpDZ0ZVHH3vxaQl6O4qgkvTRN6-PBmxSGNnMLrvzgj3UAntNKQVmK64UUxJtjkuSrItW-hmdjuUI32Yx8bXzA0YMMOc8O7imD1zwubhtwQ4TdTOSlYEH37Jc3yafXKOsjkjBCNmFrQt1Q_eXQXo8leRgCpyWwRGcS9JGnjSHWavDX1vHekAqIf04hkyNDcqdhZ4k6FS3wwI5oR7Nh4CmuOVou7sC849yGsn7pqtcPdoGm4uQN7JFDIpmJa8e6lIxizsSM9QmK7dZwwvrmz3b-3KGtdsGjD-CIw_UQfxevHcOt6VgI4A3n3O8yE8JStsULqbiO1VwYzRU4NeNFCXjLi99NNwtaCP7r2c_Bg1_I3Hsb9H7pnUHsxkyHQ5BmmeD_GxFyhKqopBLaZtAhOm1HkhaRfr_3koq2c8-iRcVhacd_-V2k9Gdrta30ChXA7SALh7GhGJYc9Mo4_3AT4fCUR-0Of3kiYgvOoQx6QGp8enYTZSvZhbnMEKFV4TV_tGw7rueHEeC0gVPQkgSGudwX2MgqP6NqvmJmgnjKfKDy5ZjF5L6xWkG69JfsH0JJvOoNrpdZjKFijII5JRXTdFqZ2KeOqgZ1O_9Vs9avNYXGDjlRHL-TKBfUEd4pgQ85-PDt_J3a2tVaW5FvQTRdqF9SLhKCuywkyyGoC1cgZtsElcDpgCuI1yhl4LBGtBa_wKhjZ50-tZ3Y0NUy1fR_Cn1M8TA_wOW6_ropPcSKSua2mybJ5EAhWfRgpvioHmEmXorLbSufse_qiIYrqJYY21PsZx7e-AeCzwXDYOjR6EqU2CsSSF3L6pU6n23pV4JKorGjdicBqTQO_RtN73uss0B3GCru2VjsTlicoerUixN50hZ7UiibmOo8KTKaevv_NKp83ROP-FDDhWaq_6xzuh6yhKzpSb0GLfV4MXNcrtD0AIPGm4c_ukHWuTqlSHAc5gCVZtuO19A3xLO2NSuKpz2p2vXBGH_t0rZw2kH5OwdYU3DpbRSDa1FiXv1WetC5xCgKOhUAterKasEUU2Mgb0zdXM42Xr3WbG8VI1HraTMo1wAXp71QI_4P6-0TNIs_s6ubrUpHFo-I5uxUSI9Yn8P2PkdleIM2qs3CujXLD8KdqUk2-icfqShWlRU_GEpqQl_461g_c2t_67W4qf9k6Tlk8XKoKynxitLV2a6VnkS5OOIxgdLOEbazBj0Vvmvhl5GmrsOL9-gLHVdjiURacGg7LWFfMI9i_k63EVWGT23-aXwNRxL5Kkk8Y4ow4zFlVoGaiMO4KnaV6zTzDbuBGmCjic34gX13PTLdqXcZDC04tbAj1bCJZkDa4REjLMS_Ay9Ue1DlBABfEGV5E9yvzoyQhKds1gTIhw8gQwYvBy0llgMK-XuPL75-hHN6bt-prAQ9Xb5_ctnwOdXQWF1dKXUF1aFU3-3wJNimKkf8o5K84tY3hmmecDkA6YxlnPgajTXFZQglmLni4ziY9ba6lJNjzYQUrrmkGDrx_DlCd8D-t71doxD7sQGR8H589kltHa1rqRxFPwetKbvJb6JLQlHi1P2sK_xG \ No newline at end of file diff --git a/JFramework/crypto/serverSecureData.txt b/JFramework/crypto/serverSecureData.txt new file mode 100644 index 0000000..d60e44d --- /dev/null +++ b/JFramework/crypto/serverSecureData.txt @@ -0,0 +1 @@ +1ZpgrHRLDlhqud-BKq4M4LkofsqlBvBSVfx4EKfn8wwuZowoa7liIEQWzrDzEM-jd9gE3VxRVm3brcaBEal4Oftfb8QohhvAw2PMu48ZV1r1tbNEqc5FwoemtUJgUNYJMbHbMKAUxp5LpZOUhdUT2LP25x9SDP8FK4AwFZHEsUMg5-7LOGk6JYb4Zb29YZUIXpBR5K-9deN-mvyYz5dSUyi55COUZykAKlfQdzYjsGxjLALl-9Oi6Ds5oDT8DXmmxmQ2WKvcxlD8kwb1uhAc59jkuFmcq7ua-z-d2rJrSYFMZ4sRWp4In8ksbauGZZ-UPk1HAegOLPJLR6oXxijyd3I7FnqIx8LNQ-cEIjFvJ9YGwIfLat9oAhYdmdFD7sebwuAID-wqOGA7bRZd0zX9UijriQrzaYzUx25x50mA2Y7iLjnQK4opzQctbrHzZ6nnNdcP3ioQ0N2S-aWaeN7sr5edafJ_njpioTRqKWRBT-AmjlGVXKxJQBuGAHksWNkZx3UE3oZIFV7D2rFV7ng0dpktmX2VLiAl01fcCQDRR5TswPIyuYRkIQ8EdjAfuLkxupIu9IHedkw-E-QhpVfmAyiJrWMFP3JkuoQoLhjkhUzVE187Hvb8BbgVOaZMyS1VuUdfIVlevS9fRmfsDwv0fUyCMrW84OFX-IvHwphYGgp3727Ym2hvFjlfPRGVccs4IkmtKPDouqR6_Q83YhBvbF2y3CE4I0sokEooDZS-IcoIpFN1cXM026ab49OiSiEX0Hv_v5C8XY8ltD1CBpHzYSAgs_DUFfsuaqW3BJFUxkqA3Udx_WRpcbDFur0SgElPXARf6tGh2vFSXVi-HDs45hKmzQ-ySgzENT62RI87nYAFkNMY7CPqwegaRvWLYUfUdzEAWx0McHf8jF63LKmy9LTh6NrPWzjjqgMuCACKfdF85afSFRb5l59aQgNRSJbqSX0DSjhKbKF5wrX-e_XMP1LvjTUmb2doLmKGPzXRPTkQZo_JA4HA0yj4U76u9BkZrNMoBvIR8-y8Kp4ctD6z_nHqg5m0PZbnhc9uMsBU6iLBPVbofegwE-6xS12ev97QFZ2gZCFvyTATfV4XPCFG67jDx0_m7cEWDK8mHMnRs2dpcKjWKgjLJ10cSNAlixc6YE3kgWcecEbfZPqujfYd-8aOjz3kSXNIOoj-bXgT1C2s2CSrB7qwIo0rHG_i7OFAATOw8dSdnZ6mYDOJQW76m3wy0yo785VtRbyjsc6NF1as4zveWZwKWriMv2DmudpjZOlQiR_ZkrfSORNdLLZqCL0yXOPcgVVkdXMmmBNri7Y8lG6aFnIkh1UcwLYXud9lFAU6Vb5SZEjY0Lgumx4I2O3TAyCRA6TdEZ8autHxwF2aomjhnO8dFiLFSK4Haws_L26Dxa837uxFE4nzgkUf7fOOJ6KP2GDbi4aoplYVXMW-KfysMimVNSF6oRVBUq-C-nLIQhnXFnSkDE3TNgyxUzWdNiIIxqzgf8uGV8-6X5IzhKm3jbvZxztVsTJaCS-aHDZLZ2GX9H3dWYvpjhDrmk-3l9x78UdNf1U5CnjYUmMyhBbZSeLYWYy2tgYkp8TATXnK5f3H_FTIVEN9sZCb6_rbZKVtCOvXuvMaUXS01nHnFeVlotKgf6zNI2gd09iVwAC1f5Ct8El4Xdsx_b9MCHF3U8fO-FCatWugz3QJRyAiCv7ytXD3hjbJwN1-GhVOQuumQ7mFT044MFa4vgn04BHu3KYMHUQVxGQZakZ6sQffTYMVhbULt5nTcxTKb65VS7sC9-6c_p_mTfYJFimmc5EIFEQZt6Z6HRO4BZPCkaKO95N_fA3QrAEKub6Foi4vSSNcEAFkzd4O066BJ0PjoQo353rCRYnNNZhnR0pqi0ag0Bj-2IKAslbC9GE8en65i7pq2vkbbbDFfeu2csFlrUQEEI_Mtml3fDnQireq1bR5_q3wc-0CToMmpgfrTOhEbvzxeT1ZFU1xsWEKbI9-s4y-u4HNzjPFedF5y5vNwjHMvDaWwHLGlxM2JVam_3ds_vY3iUUXtnNM4t7QfK5qsUH8UW0YNIknIWDoE3EfYCV_STvotP3L3KTjO5_LZGBtzl4It6IX9BnwRcTZk7MaZMWxqPrChe-Wx9SreiUem-AnSE7PrW2Bv3bA3z8f00CprSR4AEmlzka5ciYFQjrbZ-tPSUx4oNEMr3c5egMORXAf5BW2Fgs8MsePzfi-XIvuvr-PKnRqB2uHODLrHJTs3VdbFeE0yhH6cPtnJvQpXQti-jNPM-Fq5p_reS_nJiRYuNbBHQcpQiSpTJMMpthAXZCkoEbvH8xkKaJGnFUuuVGbTAHx8lcSKQVtcws1EkpaTtjIv0mvu3Ke2Pd_48SfUlaZaOTxh_8nhGKNMFhXRm81RST87GL2A7dv0UPgnA0xELaF0XJxitCp6sn4hi1F9J7gLmMSL4b_W_oQpizlrzBri83wub_PiEpNaYdNG1PPxMnQ0HAJbmLEq4RSs1l9FHgfDTflFf-VFOvgE-zYn4hbmyTx7SYoqtIixRou4z8R6IcKzvde_ITK4D1OnUJUQP2N5_LrsIpa-8JdM3Nts9z_iq0sHt3JrzZC-Rktwal8IzeBLG46baSyH7zKSRmLrKflscyKJUz8j8nuiI16dPI315pUyifU7phQpuRTFOQlYa6k9eaxIXK34iyC1LzpUFUZTLd3r6jhwpPe1wFPqtuhthIuok2s-DRtjeEPuFBmKNQSAJtQzjWbRVijoyPm0-A5RQoC6xQeuGziLmaFwC0uyIAtmNYFEP2wZG31I_IZ2nz0rdjzC5CeXjG2hy_IyFdaLrCfDrKyH88VI0dFMx5jDtl_34KavJXDZUUbKdFKo7fVAmxrSYFMVsbvgNTV-hbg0VPXAwx4Q5n0rniv7SLNvwn52SafOsZWLdJ2zF0e47oraImkHgIoj4UWtNan-CVcUfvZgeTVy_6-GozIWtPgOlrXZqfC6ciGMwFxF_8GE1cTuv1csDCUGwfLtClkHrrGU_4q0ADaF4LSjvTDhDWytXfhbH0EoNE2S5OQJ5lXD2-luYnf4b2_KxJ3IlI3KJJmZopXMWyEfs1N8pWnOX5YygbFsvveq3BMIaAWoL5ZmKG8sy7Vw-cHcO5iYfEEVfB4FbDLHOvLaQxXqLHrfp5Yze733CFGbInuIG7r8d_u5t2CnxaBRzOe9CU_K5A5T6GOl-rO54075TTt1RaPUzXeRcgy5O2Op7K-59_avwyhgH5Oyso8mFqJt9lTDDSwuUARBerzchb3rxISHo2ZnAba0Y3sxjlzNO91dj0qbknegC1UgYr43scAQzFbNfY4OHk7QR0gxhy9kJJudPTIq86Ij3NBxslYIUgfa0Db46IAnJ1o3kLDPQqsGCmQXXi99hJP8jmhHjBaL35EorMG0Qc-tMhz5hfxGHO_1Lx4QCqhYC6EZHyjzAg0VtCEsgBXUfEp_3WzhgwLjYMJ27mvkGPsvttlp40DPLI9LEI34UWiDAalNbDX3m3tZhv_0mEMi13U5SHkY5-1tsoNTav3n7zTF8xG8oDgk3hXRKFRL50I7G-YAog1dRkb3jQrmR8e3Rca5nKNy2k6SKFiBUyaJWtGqmN4dvzvlX_LjJbKJpHcDDZFIv_AhPTnfNqPHzO7GYowgFksmzpMjf084qpJ3gllCYDq2zRGbMo7z0Lc76wpnW2G04_sVXiwpYZ3JlV3LM4widWOgD9KOwZPDhJ1HaG85bHp8mVU3qWEW8J3nwdiQdVUFxakyxSlu2QXyq9OXhQUwS5_pkyIBimrpbxELz-8bPtoG422zm9ydywuJirOWqHTKGwlXsmHF6aTqpk5yF4d6yWzfchFd5qbcBit_jBGbmziVjLyNpJli7YmXCAiv3J_UIITuYGy-l9bF5sG3S8jNcs5L0HyNlf7JogdFh6tAJ6A0q-7OO2yOaYdRCz3ogFTpeY8HvkooAXw968RWLatZkm8gEE75Uofo2LqYPZPmzWU8CPPONg2HwYOPiEN1C59StQHX4BedQUoKNdjQQ== \ 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..92971de 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,13 @@ 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); + 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); + // hashedPassword = db password, password = login password + public static boolean verifyPassword(String hashedPassword, String password) { + return PasswordManager.verifyPassword(hashedPassword, password); } 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..a05421f 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 @@ -8,38 +8,56 @@ 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); - } - - //System.out.println("hashPassword, saltS: " + saltS + " " + saltS.length() + " | hashedPassword: " + hashedPassword + " " + hashedPassword.length()); - String out = saltS + hashedPassword; + /** + * Hashes a password using a randomly generated salt. + * The salt is prepended to the hash. + * The format is: salt + SHA256(SHA256(password) + salt) + * + * @param password the password to hash + * @return Base64 encoded string of salt + hash + */ + public static String hashPassword(String password) { + String salt = RandomStringGenerator.generateAlphanumericString(9); + String hashedPassword = SHA256.hash(SHA256.hash(password) + salt); + + String out = salt + hashedPassword; return Base64.getUrlEncoder().encodeToString(out.getBytes(Charset.defaultCharset())); } - // 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 password against a stored hash. + * It supports both old (unsalted) and new (salted) hash formats. + * + * @param hashedPassword the stored hash from the database + * @param password the plaintext password to verify + * @return true if the password matches the hash, false otherwise + */ + public static boolean verifyPassword(String hashedPassword, String password) { + String decodedHashedPassword; + try { + decodedHashedPassword = new String(Base64.getUrlDecoder().decode(hashedPassword)); + } catch (IllegalArgumentException e) { + // Not a valid base64 string, so it can't be a valid hash. + return false; + } + + if (decodedHashedPassword.length() < 9) { + return false; // Invalid format + } - //System.out.println("verifyPassword, saltS: " + salt + " " + salt.length() + " | hashedSaltPassword: " + hashSP + " " + hashSP.length()); - String hp = SHA256.hash(hashP + salt); + String salt = decodedHashedPassword.substring(0, 9); + String hashFromDB = decodedHashedPassword.substring(9); - return hashSP.equalsIgnoreCase(hp); + if (salt.equals("000000000")) { + // unsalted password in DB. + // hash is SHA256(password) + String calculatedHash = SHA256.hash(password); + return hashFromDB.equalsIgnoreCase(calculatedHash); + } else { + // salted password in DB + // hash is SHA256(SHA256(password) + salt) + String calculatedHash = SHA256.hash(SHA256.hash(password) + salt); + return hashFromDB.equalsIgnoreCase(calculatedHash); + } } } diff --git a/JFramework/crypto/src/test/java/crypto/CryptoTest.java b/JFramework/crypto/src/test/java/crypto/CryptoTest.java index 9c7420b..4afe3b6 100644 --- a/JFramework/crypto/src/test/java/crypto/CryptoTest.java +++ b/JFramework/crypto/src/test/java/crypto/CryptoTest.java @@ -9,6 +9,7 @@ import java.io.File; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class CryptoTest { @@ -237,13 +238,20 @@ 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); - - // password for login - String loginPW = Crypto.hashPassword(s, true); - - assertTrue(Crypto.verifyPassword(dbPW, loginPW)); + if (s == null || s.isEmpty()) { + continue; // Skip empty passwords for this test + } + // Test new hash format + String newHashedPassword = Crypto.hashPassword(s); + assertTrue("Failed to verify new password format for: '" + s + "'", Crypto.verifyPassword(newHashedPassword, s)); + assertFalse("Incorrectly verified new password format with wrong password", Crypto.verifyPassword(newHashedPassword, s + "wrong")); + + // Test old hash format verification + String salt = "000000000"; + String hash = it.richkmeli.jframework.crypto.algorithm.SHA256.hash(s); + String oldFormatHash = java.util.Base64.getUrlEncoder().encodeToString((salt + hash).getBytes(java.nio.charset.Charset.defaultCharset())); + assertTrue("Failed to verify old password format for: '" + s + "'", Crypto.verifyPassword(oldFormatHash, s)); + assertFalse("Incorrectly verified old password format with wrong password", Crypto.verifyPassword(oldFormatHash, s + "wrong")); } } diff --git a/JFramework/crypto/src/test/java/it/richkmeli/jframework/crypto/controller/PasswordManagerTest.java b/JFramework/crypto/src/test/java/it/richkmeli/jframework/crypto/controller/PasswordManagerTest.java new file mode 100644 index 0000000..e4d3bc2 --- /dev/null +++ b/JFramework/crypto/src/test/java/it/richkmeli/jframework/crypto/controller/PasswordManagerTest.java @@ -0,0 +1,57 @@ +package it.richkmeli.jframework.crypto.controller; + +import it.richkmeli.jframework.crypto.algorithm.SHA256; +import org.junit.Test; + +import java.nio.charset.Charset; +import java.util.Base64; + +import static org.junit.Assert.*; + +public class PasswordManagerTest { + + @Test + public void hashPassword() { + String password = "test_password"; + String hashedPassword = PasswordManager.hashPassword(password); + assertNotNull(hashedPassword); + assertNotEquals("", hashedPassword); + + // verify format: salt + hash + String decodedHashedPassword = new String(Base64.getUrlDecoder().decode(hashedPassword)); + assertEquals(9 + 64, decodedHashedPassword.length()); + } + + @Test + public void verifyPassword_newFormat() { + String password = "test_password"; + String hashedPassword = PasswordManager.hashPassword(password); + assertTrue(PasswordManager.verifyPassword(hashedPassword, password)); + } + + @Test + public void verifyPassword_oldFormat() { + String password = "test_password"; + // Manually create an old-format hash + String salt = "000000000"; + String hash = SHA256.hash(password); + String oldFormatHash = Base64.getUrlEncoder().encodeToString((salt + hash).getBytes(Charset.defaultCharset())); + + assertTrue(PasswordManager.verifyPassword(oldFormatHash, password)); + } + + @Test + public void verifyPassword_wrongPassword() { + String password = "test_password"; + String wrongPassword = "wrong_password"; + String hashedPassword = PasswordManager.hashPassword(password); + assertFalse(PasswordManager.verifyPassword(hashedPassword, wrongPassword)); + } + + @Test + public void verifyPassword_invalidHash() { + String password = "test_password"; + String invalidHash = "invalid_hash"; + assertFalse(PasswordManager.verifyPassword(invalidHash, password)); + } +} diff --git a/JFramework/orm/src/test/java/orm/DatabaseManagerTest.java b/JFramework/orm/src/test/java/orm/DatabaseManagerTest.java index 429ee99..394d37e 100644 --- a/JFramework/orm/src/test/java/orm/DatabaseManagerTest.java +++ b/JFramework/orm/src/test/java/orm/DatabaseManagerTest.java @@ -63,22 +63,22 @@ private void createAuthdb() throws DatabaseException { password, false); authDatabaseManager.addUser(u); - assertTrue(authDatabaseManager.checkPassword(email, Crypto.hashPassword(password, true))); + assertTrue(authDatabaseManager.checkPassword(email, password)); assertFalse(authDatabaseManager.isAdmin(email)); } - assertTrue(authDatabaseManager.checkPassword("richk@i.it", Crypto.hashPassword("00000000", true))); + assertTrue(authDatabaseManager.checkPassword("richk@i.it", "00000000")); } private void createDevicedb() throws DatabaseException { String email = "richk@i.it"; - authDatabaseManager.addUser(new UserTest(email, PasswordManager.hashPassword("00000000", false), false)); + authDatabaseManager.addUser(new UserTest(email, "00000000", false)); deviceDatabaseManager.addDevice(new Device("device1", "192.168.0.100", "9000", "20-10-2018", "testencryptionkey", email, "start##start##start", "")); for (int i = 0; i < ENTRIES; i++) { String device = "device" + RandomStringGenerator.generateAlphanumericString(8) + "_" + i; email = RandomStringGenerator.generateAlphanumericString(8) + "@" + RandomStringGenerator.generateAlphanumericString(8) + "." + RandomStringGenerator.generateAlphanumericString(2); - authDatabaseManager.addUser(new UserTest(email, PasswordManager.hashPassword("00000000", false), false)); + authDatabaseManager.addUser(new UserTest(email, "00000000", false)); String encryptionKey = RandomStringGenerator.generateAlphanumericString(32); String commands = RandomStringGenerator.generateAlphanumericString(50); String commandsOutput = RandomStringGenerator.generateAlphanumericString(100); @@ -95,13 +95,13 @@ private void createDevicedb() throws DatabaseException { private void createRMCdb() throws DatabaseException { String email = "richk@i.it"; - authDatabaseManager.addUser(new UserTest(email, PasswordManager.hashPassword("00000000", false), false)); + authDatabaseManager.addUser(new UserTest(email, "00000000", false)); rmcDatabaseManager.addRMC(new RMC(email, "ClientID_1")); for (int i = 0; i < ENTRIES; i++) { email = RandomStringGenerator.generateAlphanumericString(8) + "@" + RandomStringGenerator.generateAlphanumericString(8) + "." + RandomStringGenerator.generateAlphanumericString(2); String clientID = RandomStringGenerator.generateAlphanumericString(32); - authDatabaseManager.addUser(new UserTest(email, PasswordManager.hashPassword("00000000", false), false)); + authDatabaseManager.addUser(new UserTest(email, "00000000", false)); rmcDatabaseManager.addRMC(new RMC(email, clientID)); assertFalse(rmcDatabaseManager.getAllRMCs().isEmpty()); @@ -136,7 +136,7 @@ private void readAuthdb() throws DatabaseException { private void readDevicedb() throws DatabaseException { // test read String device = "deviceread"; - authDatabaseManager.addUser(new UserTest("richk@i.it", PasswordManager.hashPassword("00000000", false), false)); + authDatabaseManager.addUser(new UserTest("richk@i.it", "00000000", false)); deviceDatabaseManager.addDevice(new Device(device, "192.168.0.100", "9000", "20-10-2018", "testencryptionkey", "richk@i.it", "start##start##start", "")); @@ -150,7 +150,7 @@ private void readRMCdb() throws DatabaseException { // test read String email = "richk@i.it"; String clientID = "clientIDread"; - authDatabaseManager.addUser(new UserTest(email, PasswordManager.hashPassword("00000000", false), false)); + authDatabaseManager.addUser(new UserTest(email, "00000000", false)); rmcDatabaseManager.addRMC(new RMC(email, clientID)); assertNotNull(rmcDatabaseManager.getRMCs(email)); @@ -262,11 +262,11 @@ private void updateAuthdb() throws DatabaseException { "testencryptionkey", "richk@i.it", "start##start##start", "")); authDatabaseManager.addUser(new UserTest(email, "00000000", true)); - assertTrue(authDatabaseManager.checkPassword(email, PasswordManager.hashPassword("00000000", true))); + assertTrue(authDatabaseManager.checkPassword(email, "00000000")); assertTrue(authDatabaseManager.isAdmin(email)); authDatabaseManager.editPassword(email, "00000001"); - assertTrue(authDatabaseManager.checkPassword(email, PasswordManager.hashPassword("00000001", true))); + assertTrue(authDatabaseManager.checkPassword(email, "00000001")); authDatabaseManager.editAdmin(email, false); assertFalse(authDatabaseManager.isAdmin(email)); diff --git a/JFramework/orm/src/test/java/orm/dataexample/auth/AuthDatabaseManagerTest.java b/JFramework/orm/src/test/java/orm/dataexample/auth/AuthDatabaseManagerTest.java index 87c1f8f..77b9c11 100644 --- a/JFramework/orm/src/test/java/orm/dataexample/auth/AuthDatabaseManagerTest.java +++ b/JFramework/orm/src/test/java/orm/dataexample/auth/AuthDatabaseManagerTest.java @@ -38,7 +38,7 @@ public UserTest getUser(String email) throws DatabaseException { public boolean addUser(UserTest user) throws DatabaseException { //Logger.info("AuthDatabaseManager, addUser. User: " + user.email); //String hash = Crypto.hash(user.getPassword()); - String password = Crypto.hashPassword(user.getPassword(), false); + String password = Crypto.hashPassword(user.getPassword()); user.setPassword(password); return create(user); } @@ -53,7 +53,7 @@ public boolean isUserPresent(String email) throws DatabaseException { } public boolean editPassword(String email, String pass) throws DatabaseException { - String password = Crypto.hashPassword(pass, false); + String password = Crypto.hashPassword(pass); return update(new UserTest(email, password, null)); }