fixes 41757: [pwd] improve password storage in DB
authorAsier Lostalé <asier.lostale@openbravo.com>
Wed, 09 Oct 2019 10:16:50 +0200
changeset 36618 2e7550a9ae5e
parent 36578 d3341b186540 (current diff)
parent 36617 4e2c05b7b614 (diff)
child 36619 9765dea5db5c
fixes 41757: [pwd] improve password storage in DB
referencedata/sampledata/F_B_International_Group/AD_USER.xml
src-core/src/org/openbravo/utils/CryptoSHA1BASE64.java
src-db/database/sourcedata/AD_COLUMN.xml
--- a/modules/org.openbravo.client.application/src/org/openbravo/client/application/navigationbarcomponents/UserInfoWidgetActionHandler.java	Wed Oct 02 18:02:37 2019 +0200
+++ b/modules/org.openbravo.client.application/src/org/openbravo/client/application/navigationbarcomponents/UserInfoWidgetActionHandler.java	Wed Oct 09 10:16:50 2019 +0200
@@ -11,7 +11,7 @@
  * under the License. 
  * The Original Code is Openbravo ERP. 
  * The Initial Developer of the Original Code is Openbravo SLU 
- * All portions are Copyright (C) 2009-2018 Openbravo SLU
+ * All portions are Copyright (C) 2009-2019 Openbravo SLU
  * All Rights Reserved. 
  * Contributor(s):  ______________________________________.
  ************************************************************************
@@ -29,6 +29,7 @@
 import org.codehaus.jettison.json.JSONArray;
 import org.codehaus.jettison.json.JSONException;
 import org.codehaus.jettison.json.JSONObject;
+import org.openbravo.authentication.hashing.PasswordHash;
 import org.openbravo.base.exception.OBException;
 import org.openbravo.base.secureApp.HttpSecureAppServlet;
 import org.openbravo.base.secureApp.LoginUtils;
@@ -50,7 +51,6 @@
 import org.openbravo.portal.PortalAccessible;
 import org.openbravo.service.db.DalConnectionProvider;
 import org.openbravo.service.password.PasswordStrengthChecker;
-import org.openbravo.utils.FormatUtilities;
 
 /**
  * Action handler used to save the default user information of the 'Profile' widget and the password
@@ -100,7 +100,7 @@
     final String newPwd = json.getString("newPwd");
     final String confirmPwd = json.getString("confirmPwd");
 
-    if (!user.getPassword().equals(FormatUtilities.sha1Base64(currentPwd))) {
+    if (!PasswordHash.matches(currentPwd, user.getPassword())) {
       return createErrorResponse("currentPwd", "UINAVBA_CurrentPwdIncorrect");
     }
     if (currentPwd.equals(newPwd)) {
@@ -115,7 +115,7 @@
     if (!passwordStrengthChecker.isStrongPassword(newPwd)) {
       return createErrorResponse("newPwd", "CPPasswordNotStrongEnough");
     }
-    user.setPassword(FormatUtilities.sha1Base64(newPwd));
+    user.setPassword(PasswordHash.generateHash(newPwd));
     OBDal.getInstance().flush();
     return ApplicationConstants.ACTION_RESULT_SUCCESS;
   }
--- a/modules/org.openbravo.service.json/src/org/openbravo/service/json/JsonToDataConverter.java	Wed Oct 02 18:02:37 2019 +0200
+++ b/modules/org.openbravo.service.json/src/org/openbravo/service/json/JsonToDataConverter.java	Wed Oct 09 10:16:50 2019 +0200
@@ -11,7 +11,7 @@
  * under the License. 
  * The Original Code is Openbravo ERP. 
  * The Initial Developer of the Original Code is Openbravo SLU 
- * All portions are Copyright (C) 2009-2014 Openbravo SLU 
+ * All portions are Copyright (C) 2009-2019 Openbravo SLU 
  * All Rights Reserved. 
  * Contributor(s):  ______________________________________.
  ************************************************************************
@@ -39,6 +39,7 @@
 import org.codehaus.jettison.json.JSONArray;
 import org.codehaus.jettison.json.JSONException;
 import org.codehaus.jettison.json.JSONObject;
+import org.openbravo.authentication.hashing.PasswordHash;
 import org.openbravo.base.exception.OBException;
 import org.openbravo.base.model.Entity;
 import org.openbravo.base.model.Property;
@@ -57,7 +58,6 @@
 import org.openbravo.dal.service.OBQuery;
 import org.openbravo.model.common.enterprise.Organization;
 import org.openbravo.utils.CryptoUtility;
-import org.openbravo.utils.FormatUtilities;
 
 /**
  * Converts json data to Openbravo business object(s).
@@ -207,14 +207,7 @@
         return new BigDecimal(((Number) value).doubleValue());
       } else if (value instanceof String
           && property.getDomainType() instanceof HashedStringDomainType) {
-        String str = (String) value;
-        try {
-          return FormatUtilities.sha1Base64(str);
-        } catch (ServletException e) {
-          log.error("Error hashing password", e);
-          // TODO: translate error message
-          throw new Error("Could not encrypt password", e);
-        }
+        return PasswordHash.generateHash((String) value);
       } else if (value instanceof String
           && property.getDomainType() instanceof EncryptedStringDomainType) {
         String str = (String) value;
--- a/referencedata/sampledata/F_B_International_Group/AD_USER.xml	Wed Oct 02 18:02:37 2019 +0200
+++ b/referencedata/sampledata/F_B_International_Group/AD_USER.xml	Wed Oct 09 10:16:50 2019 +0200
@@ -11,7 +11,7 @@
   <UPDATEDBY><![CDATA[0]]></UPDATEDBY>
   <NAME><![CDATA[F&B ES User]]></NAME>
   <DESCRIPTION><![CDATA[F&BESUser]]></DESCRIPTION>
-  <PASSWORD><![CDATA[5tmN6fdcURFFUMsi++ZZDp9hOTA=]]></PASSWORD>
+  <PASSWORD><![CDATA[1$nPlwP82POytNXx86j6yqOw$RIj00i/hKq3UPdN+/99n4GcCsCCOHvYNI5WSP7DmQ1DvN0R5cVCCMh3nP2W5JtSmFu01xHX0ryjkR/61/ru5Bw==]]></PASSWORD>
   <PROCESSING><![CDATA[N]]></PROCESSING>
   <USERNAME><![CDATA[F&BESUser]]></USERNAME>
   <DEFAULT_AD_LANGUAGE><![CDATA[en_US]]></DEFAULT_AD_LANGUAGE>
@@ -62,7 +62,7 @@
   <UPDATEDBY><![CDATA[0]]></UPDATEDBY>
   <NAME><![CDATA[F&BESRSUser]]></NAME>
   <DESCRIPTION><![CDATA[F&BESRSUser]]></DESCRIPTION>
-  <PASSWORD><![CDATA[mYGlTdquy0OlPNRYfSCzbe2GZLI=]]></PASSWORD>
+  <PASSWORD><![CDATA[1$ObBtJPuk/tPCPrU34N9O+w$QO4doqck2oenCf/OxVItVeoxXFLHWUdBm9t1108XoGPsFQTphETBS0AYJA35h4v/oaTecFpH16tNcviR+YZ/Cg==]]></PASSWORD>
   <PROCESSING><![CDATA[N]]></PROCESSING>
   <USERNAME><![CDATA[F&BESRSUser]]></USERNAME>
   <DEFAULT_AD_LANGUAGE><![CDATA[en_US]]></DEFAULT_AD_LANGUAGE>
@@ -198,7 +198,7 @@
   <UPDATEDBY><![CDATA[0]]></UPDATEDBY>
   <NAME><![CDATA[F&B US User]]></NAME>
   <DESCRIPTION><![CDATA[F&BUSUser]]></DESCRIPTION>
-  <PASSWORD><![CDATA[whmWJYAvJLz7oiXG2BLIXX5/TDo=]]></PASSWORD>
+  <PASSWORD><![CDATA[1$gomC4011DoeWh5KlOEmxxw$/Sj7Ad9TpcPYs9YDZ9xdAa6LWSIEfpqfRogrWjn/hXjxAVmJMyAZe+Q5XQGTS2BTFuUacVievC8ho3qrgMLJlg==]]></PASSWORD>
   <PROCESSING><![CDATA[N]]></PROCESSING>
   <USERNAME><![CDATA[F&BUSUser]]></USERNAME>
   <DEFAULT_AD_LANGUAGE><![CDATA[en_US]]></DEFAULT_AD_LANGUAGE>
@@ -333,7 +333,7 @@
   <UPDATEDBY><![CDATA[0]]></UPDATEDBY>
   <NAME><![CDATA[F&BESRNUser]]></NAME>
   <DESCRIPTION><![CDATA[F&BESRNUser]]></DESCRIPTION>
-  <PASSWORD><![CDATA[/iOsBTJIAtxVkowsjlI6PWJ9XAo=]]></PASSWORD>
+  <PASSWORD><![CDATA[1$2DzyIQ7BN6164Qdbcn0xVw$zMTkmeSwM6ZDxDfifVYhlbu+9SvLYLw94TMCfFpG+MjpKzSvWOjxVOzbC6WX0v0aZNK+argS7qtL2LpfeR0s1Q==]]></PASSWORD>
   <PROCESSING><![CDATA[N]]></PROCESSING>
   <USERNAME><![CDATA[F&BESRNUser]]></USERNAME>
   <DEFAULT_AD_LANGUAGE><![CDATA[en_US]]></DEFAULT_AD_LANGUAGE>
@@ -408,7 +408,7 @@
   <UPDATEDBY><![CDATA[0]]></UPDATEDBY>
   <NAME><![CDATA[F&BAdmin]]></NAME>
   <DESCRIPTION><![CDATA[F&BAdmin]]></DESCRIPTION>
-  <PASSWORD><![CDATA[kpJO2wE6EhfrftY3R/gaESwRDdw=]]></PASSWORD>
+  <PASSWORD><![CDATA[1$VRFez9AtHA5guXXJgAERPQ$N7KqkSJiVeFdmMWEPTafv5ZDah2yiHjdU876QOMHhxHC/jDOT96WXb6B6X6FKP6qCz++qK5qOLNXO6tNWULxSw==]]></PASSWORD>
   <PROCESSING><![CDATA[N]]></PROCESSING>
   <USERNAME><![CDATA[F&BAdmin]]></USERNAME>
   <DEFAULT_AD_LANGUAGE><![CDATA[en_US]]></DEFAULT_AD_LANGUAGE>
@@ -558,7 +558,7 @@
   <UPDATEDBY><![CDATA[0]]></UPDATEDBY>
   <NAME><![CDATA[F&BUSECUser]]></NAME>
   <DESCRIPTION><![CDATA[F&BUSECUser]]></DESCRIPTION>
-  <PASSWORD><![CDATA[TFT0/MS4ujh2rQQGoBXj+B/qBak=]]></PASSWORD>
+  <PASSWORD><![CDATA[1$ESiNdb6Pw5kONiYFVbPNjw$/GP5NcCPmX6tcNyop0RCzV6Kj/P9mL5ajxJ2P2PvqGCTQolqZrMhFXUqfXmEeXhCxny9o4IOyklf3nj/rhHL+A==]]></PASSWORD>
   <PROCESSING><![CDATA[N]]></PROCESSING>
   <USERNAME><![CDATA[F&BUSECUser]]></USERNAME>
   <DEFAULT_AD_LANGUAGE><![CDATA[en_US]]></DEFAULT_AD_LANGUAGE>
@@ -582,7 +582,7 @@
   <UPDATEDBY><![CDATA[0]]></UPDATEDBY>
   <NAME><![CDATA[F&BUser]]></NAME>
   <DESCRIPTION><![CDATA[F&BUser]]></DESCRIPTION>
-  <PASSWORD><![CDATA[cBvX82hcThILtjIWtEZa5jD9dbQ=]]></PASSWORD>
+  <PASSWORD><![CDATA[1$7QD8Kj+nDvmflJrGGKq16w$e9YICfj31mSqlFoUt4YzyTtlyLzYlfAwaG5B4Hzn3Zowt1mB7ia4mqOEdqcT85DSLrW453c3YStiLiI7BSWI2g==]]></PASSWORD>
   <PROCESSING><![CDATA[N]]></PROCESSING>
   <USERNAME><![CDATA[F&BUser]]></USERNAME>
   <DEFAULT_AD_LANGUAGE><![CDATA[en_US]]></DEFAULT_AD_LANGUAGE>
@@ -606,7 +606,7 @@
   <UPDATEDBY><![CDATA[0]]></UPDATEDBY>
   <NAME><![CDATA[F&BUSWCUser]]></NAME>
   <DESCRIPTION><![CDATA[F&BUSWCUser]]></DESCRIPTION>
-  <PASSWORD><![CDATA[VyTwT1uMPxfg7HPao6/q2e1TiuI=]]></PASSWORD>
+  <PASSWORD><![CDATA[1$ca1rIhMDXhsYryW0jXdNcA$KWs11+EpSKp5+dHQimDFzDGiqAykVGvFqk9kMQUIdcxjKyGel76FfspUr23PfkoAAPperIokPF+inVgd7BEtFg==]]></PASSWORD>
   <PROCESSING><![CDATA[N]]></PROCESSING>
   <USERNAME><![CDATA[F&BUSWCUser]]></USERNAME>
   <DEFAULT_AD_LANGUAGE><![CDATA[en_US]]></DEFAULT_AD_LANGUAGE>
--- a/referencedata/sampledata/QA_Testing/AD_USER.xml	Wed Oct 02 18:02:37 2019 +0200
+++ b/referencedata/sampledata/QA_Testing/AD_USER.xml	Wed Oct 09 10:16:50 2019 +0200
@@ -11,7 +11,7 @@
   <UPDATEDBY><![CDATA[0]]></UPDATEDBY>
   <NAME><![CDATA[USAUser]]></NAME>
   <DESCRIPTION><![CDATA[USAUser]]></DESCRIPTION>
-  <PASSWORD><![CDATA[vGlGqXZ4nqhiXDpuUBc6OxagWVY=]]></PASSWORD>
+  <PASSWORD><![CDATA[1$88C9wR4U3fVlmUd3WDz92Q$gqgtylfNjnYKphjUzH1sbh/h/tEwvUVD0KBoHRieynlufct4nvSScCdrwa6tNnJc7WRUHxYrBgTXSUhoW5Blxg==]]></PASSWORD>
   <USERNAME><![CDATA[USAUser]]></USERNAME>
   <DEFAULT_AD_LANGUAGE><![CDATA[en_US]]></DEFAULT_AD_LANGUAGE>
   <ISLOCKED><![CDATA[N]]></ISLOCKED>
@@ -31,7 +31,7 @@
   <UPDATEDBY><![CDATA[100]]></UPDATEDBY>
   <NAME><![CDATA[QAAdmin]]></NAME>
   <DESCRIPTION><![CDATA[QAAdmin]]></DESCRIPTION>
-  <PASSWORD><![CDATA[ysB5jZDw0d9am0sB8WCWAPHwGhc=]]></PASSWORD>
+  <PASSWORD><![CDATA[1$2OSKDWtPUW+p6NHK1LfPpw$/VwQmG0YvMHfDTWAlGXHqIJF1HCNK1ycuEDW8nKyxHe38biRMp1GeyWXbBq5XqHCxoV/YN/qfHv0oUMO9D2VeQ==]]></PASSWORD>
   <PROCESSING><![CDATA[N]]></PROCESSING>
   <USERNAME><![CDATA[QAAdmin]]></USERNAME>
   <DEFAULT_AD_LANGUAGE><![CDATA[en_US]]></DEFAULT_AD_LANGUAGE>
@@ -221,7 +221,7 @@
   <UPDATED><![CDATA[2013-07-04 23:38:18.221]]></UPDATED>
   <UPDATEDBY><![CDATA[0]]></UPDATEDBY>
   <NAME><![CDATA[userC]]></NAME>
-  <PASSWORD><![CDATA[ZnZNBmewlKxX6xfVEsSomkpEyEQ=]]></PASSWORD>
+  <PASSWORD><![CDATA[1$FWSePP0BkaVufAlm2fv2Jw$74lQyOwCCePUAIBe0pRYR6h5gMISOSBJLPibCk67n4NOb74noo+vdnB2xyMya7iowuDYOTYzHDk/AgSAukt5jA==]]></PASSWORD>
   <PROCESSING><![CDATA[N]]></PROCESSING>
   <AD_ORGTRX_ID><![CDATA[357947E87C284935AD1D783CF6F099A1]]></AD_ORGTRX_ID>
   <FIRSTNAME><![CDATA[userC]]></FIRSTNAME>
@@ -243,7 +243,7 @@
   <UPDATEDBY><![CDATA[0]]></UPDATEDBY>
   <NAME><![CDATA[MainUser]]></NAME>
   <DESCRIPTION><![CDATA[MainUser]]></DESCRIPTION>
-  <PASSWORD><![CDATA[SNnWWSsALERMeuegOJ9boNCug1s=]]></PASSWORD>
+  <PASSWORD><![CDATA[1$FKAGPyiAuxDvfJ0wqtLK/Q$ZeaM2Zy7IUyefRyPNc21qkxSBxGi/pMJmFkCUu9jFWCM0CdGO/ole2aWEzE3MfpnABCMaekCzeecd+5Jc8UDXw==]]></PASSWORD>
   <USERNAME><![CDATA[MainUser]]></USERNAME>
   <DEFAULT_AD_LANGUAGE><![CDATA[en_US]]></DEFAULT_AD_LANGUAGE>
   <ISLOCKED><![CDATA[N]]></ISLOCKED>
@@ -263,7 +263,7 @@
   <UPDATEDBY><![CDATA[0]]></UPDATEDBY>
   <NAME><![CDATA[SpainUser]]></NAME>
   <DESCRIPTION><![CDATA[SpainUser]]></DESCRIPTION>
-  <PASSWORD><![CDATA[Ax/acA8Sw13SXzzfWZY4rl4GUB4=]]></PASSWORD>
+  <PASSWORD><![CDATA[1$scxSH4RfC/iW6VRemE0vRA$JwnjmrbrBVZrCpKkKUCYxTzOzgxdGhDyzvxhR8faAkgpiIjg3F2P9qK3mgle74ZPgWXDXb+h7JVGrtPuH2CL0g==]]></PASSWORD>
   <USERNAME><![CDATA[SpainUser]]></USERNAME>
   <DEFAULT_AD_LANGUAGE><![CDATA[en_US]]></DEFAULT_AD_LANGUAGE>
   <ISLOCKED><![CDATA[N]]></ISLOCKED>
--- a/src-core/src/org/openbravo/utils/CryptoSHA1BASE64.java	Wed Oct 02 18:02:37 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-/*
- ************************************************************************************
- * Copyright (C) 2001-2010 Openbravo S.L.U.
- * Licensed under the Apache Software License version 2.0
- * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to  in writing,  software  distributed
- * under the License is distributed  on  an  "AS IS"  BASIS,  WITHOUT  WARRANTIES  OR
- * CONDITIONS OF ANY KIND, either  express  or  implied.  See  the  License  for  the
- * specific language governing permissions and limitations under the License.
- ************************************************************************************
- */
-package org.openbravo.utils;
-
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-
-import javax.servlet.ServletException;
-
-public final class CryptoSHA1BASE64 {
-  public static String hash(String plaintext) throws ServletException {
-    MessageDigest md = null;
-
-    try {
-      md = MessageDigest.getInstance("SHA"); // SHA-1 generator instance
-    } catch (NoSuchAlgorithmException e) {
-      throw new ServletException(e.getMessage());
-    }
-
-    try {
-      md.update(plaintext.getBytes("UTF-8")); // Message summary
-      // generation
-    } catch (UnsupportedEncodingException e) {
-      throw new ServletException(e.getMessage());
-    }
-
-    byte raw[] = md.digest(); // Message summary reception
-    try {
-      String hash = new String(org.apache.commons.codec.binary.Base64.encodeBase64(raw), "UTF-8");
-      return hash;
-    } catch (UnsupportedEncodingException use) {
-      throw new ServletException(use);
-    }
-  }
-}
--- a/src-core/src/org/openbravo/utils/FormatUtilities.java	Wed Oct 02 18:02:37 2019 +0200
+++ b/src-core/src/org/openbravo/utils/FormatUtilities.java	Wed Oct 09 10:16:50 2019 +0200
@@ -1,6 +1,6 @@
 /*
  ************************************************************************************
- * Copyright (C) 2001-2015 Openbravo S.L.U.
+ * Copyright (C) 2001-2019 Openbravo S.L.U.
  * Licensed under the Apache Software License version 2.0
  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to  in writing,  software  distributed
@@ -11,6 +11,11 @@
  */
 package org.openbravo.utils;
 
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+
 import javax.servlet.ServletException;
 
 import org.apache.logging.log4j.LogManager;
@@ -66,13 +71,30 @@
         "\n", "\\n"), "\r", "").replace("<", "\\<").replace(">", "\\>");
   }
 
+  /**
+   * Hashes text using SHA-1 algorithm. This method is deprecate in favor of PasswordHash which
+   * supports more modern algorithms.
+   *
+   * @deprecated Use PasswordHash instead (since = "3.0PR20Q1", forRemoval = true)
+   */
+  @Deprecated
   public static String sha1Base64(String text) throws ServletException {
     if (text == null || text.trim().equals("")) {
       return "";
     }
-    String result = text;
-    result = CryptoSHA1BASE64.hash(text);
-    return result;
+
+    MessageDigest md = null;
+
+    try {
+      md = MessageDigest.getInstance("SHA"); // SHA-1 generator instance
+    } catch (NoSuchAlgorithmException e) {
+      throw new ServletException(e.getMessage());
+    }
+
+    md.update(text.getBytes(StandardCharsets.UTF_8));
+
+    byte[] raw = md.digest(); // Message summary reception
+    return Base64.getEncoder().encodeToString(raw);
   }
 
   public static String encryptDecrypt(String text, boolean encrypt) throws ServletException {
--- a/src-db/database/model/tables/AD_USER.xml	Wed Oct 02 18:02:37 2019 +0200
+++ b/src-db/database/model/tables/AD_USER.xml	Wed Oct 09 10:16:50 2019 +0200
@@ -41,7 +41,7 @@
         <default/>
         <onCreateDefault/>
       </column>
-      <column name="PASSWORD" primaryKey="false" required="false" type="NVARCHAR" size="40" autoIncrement="false">
+      <column name="PASSWORD" primaryKey="false" required="false" type="NVARCHAR" size="255" autoIncrement="false">
         <default/>
         <onCreateDefault/>
       </column>
--- a/src-db/database/sourcedata/AD_COLUMN.xml	Wed Oct 02 18:02:37 2019 +0200
+++ b/src-db/database/sourcedata/AD_COLUMN.xml	Wed Oct 09 10:16:50 2019 +0200
@@ -6319,7 +6319,7 @@
 <!--417-->  <COLUMNNAME><![CDATA[Password]]></COLUMNNAME>
 <!--417-->  <AD_TABLE_ID><![CDATA[114]]></AD_TABLE_ID>
 <!--417-->  <AD_REFERENCE_ID><![CDATA[C5C21C28B39E4683A91779F16C112E40]]></AD_REFERENCE_ID>
-<!--417-->  <FIELDLENGTH><![CDATA[40]]></FIELDLENGTH>
+<!--417-->  <FIELDLENGTH><![CDATA[255]]></FIELDLENGTH>
 <!--417-->  <ISKEY><![CDATA[N]]></ISKEY>
 <!--417-->  <ISPARENT><![CDATA[N]]></ISPARENT>
 <!--417-->  <ISMANDATORY><![CDATA[N]]></ISMANDATORY>
--- a/src-db/database/sourcedata/AD_REFERENCE.xml	Wed Oct 02 18:02:37 2019 +0200
+++ b/src-db/database/sourcedata/AD_REFERENCE.xml	Wed Oct 09 10:16:50 2019 +0200
@@ -4454,7 +4454,7 @@
 <!--16EC6DF4A59747749FDF256B7FBBB058-->  <ISACTIVE><![CDATA[Y]]></ISACTIVE>
 <!--16EC6DF4A59747749FDF256B7FBBB058-->  <NAME><![CDATA[Password (decryptable)]]></NAME>
 <!--16EC6DF4A59747749FDF256B7FBBB058-->  <DESCRIPTION><![CDATA[A password which is shown with * in the UI and stored in a way so that the cleartext value can be recovered.]]></DESCRIPTION>
-<!--16EC6DF4A59747749FDF256B7FBBB058-->  <HELP><![CDATA[The value of the password is encrypted when being saved, however in a way that the cleartext password (as entered) can be recovered. Example use-case is to store a password which needs to be passed later as cleartext to some external service (i.e. a email server password)]]></HELP>
+<!--16EC6DF4A59747749FDF256B7FBBB058-->  <HELP><![CDATA[The value of the password is encrypted when being saved, however in a way that the cleartext password (as entered) can be recovered. Example use-case is to store a password which needs to be passed later as clear text to some external service (i.e. a email server password).]]></HELP>
 <!--16EC6DF4A59747749FDF256B7FBBB058-->  <AD_MODULE_ID><![CDATA[0]]></AD_MODULE_ID>
 <!--16EC6DF4A59747749FDF256B7FBBB058-->  <ISBASEREFERENCE><![CDATA[Y]]></ISBASEREFERENCE>
 <!--16EC6DF4A59747749FDF256B7FBBB058-->  <MODEL_IMPL><![CDATA[org.openbravo.base.model.domaintype.EncryptedStringDomainType]]></MODEL_IMPL>
@@ -6612,7 +6612,7 @@
 <!--C5C21C28B39E4683A91779F16C112E40-->  <ISACTIVE><![CDATA[Y]]></ISACTIVE>
 <!--C5C21C28B39E4683A91779F16C112E40-->  <NAME><![CDATA[Password (not decryptable)]]></NAME>
 <!--C5C21C28B39E4683A91779F16C112E40-->  <DESCRIPTION><![CDATA[A password which is shown with * in the UI; the cleartext value cannot be recovered as only a hashed value is stored]]></DESCRIPTION>
-<!--C5C21C28B39E4683A91779F16C112E40-->  <HELP><![CDATA[Fro this type of passwords only a hashed value is stored in the database. It is possible to verify if the same value was entered (to implement a password check), however it is not possible to recover the cleartext value again.]]></HELP>
+<!--C5C21C28B39E4683A91779F16C112E40-->  <HELP><![CDATA[For this type of passwords a hashed value is stored in the database. It is possible to verify if the same value was entered (to implement a password check), however it is not possible to recover the clear text value again.]]></HELP>
 <!--C5C21C28B39E4683A91779F16C112E40-->  <AD_MODULE_ID><![CDATA[0]]></AD_MODULE_ID>
 <!--C5C21C28B39E4683A91779F16C112E40-->  <ISBASEREFERENCE><![CDATA[Y]]></ISBASEREFERENCE>
 <!--C5C21C28B39E4683A91779F16C112E40-->  <MODEL_IMPL><![CDATA[org.openbravo.base.model.domaintype.HashedStringDomainType]]></MODEL_IMPL>
--- a/src-db/database/sourcedata/referencedData/AD_USER.xml	Wed Oct 02 18:02:37 2019 +0200
+++ b/src-db/database/sourcedata/referencedData/AD_USER.xml	Wed Oct 09 10:16:50 2019 +0200
@@ -19,7 +19,7 @@
 <!--100-->  <AD_ORG_ID><![CDATA[0]]></AD_ORG_ID>
 <!--100-->  <ISACTIVE><![CDATA[Y]]></ISACTIVE>
 <!--100-->  <NAME><![CDATA[Openbravo]]></NAME>
-<!--100-->  <PASSWORD><![CDATA[PwOd6SgWF74HY4u51bfrUxjtB9g=]]></PASSWORD>
+<!--100-->  <PASSWORD><![CDATA[1$P06lYNiGRF/Zsj/gjIXFKA$5SDSkpKIvRhsScZSRss2fCpP/RKKW4P7xgqT1ZJ6LhXfg3PYKnZpMIVvwP46KVvIiANHHeNq+bGTmRW/pPvlKg==]]></PASSWORD>
 <!--100-->  <EMAILUSER><![CDATA[info]]></EMAILUSER>
 <!--100-->  <FIRSTNAME><![CDATA[Openbravo]]></FIRSTNAME>
 <!--100-->  <USERNAME><![CDATA[Openbravo]]></USERNAME>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src-test/src/org/openbravo/authentication/hashing/PasswordHashing.java	Wed Oct 09 10:16:50 2019 +0200
@@ -0,0 +1,153 @@
+/*
+ *************************************************************************
+ * The contents of this file are subject to the Openbravo  Public  License
+ * Version  1.1  (the  "License"),  being   the  Mozilla   Public  License
+ * Version 1.1  with a permitted attribution clause; you may not  use this
+ * file except in compliance with the License. You  may  obtain  a copy of
+ * the License at http://www.openbravo.com/legal/license.html 
+ * Software distributed under the License  is  distributed  on  an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+ * License for the specific  language  governing  rights  and  limitations
+ * under the License. 
+ * The Original Code is Openbravo ERP. 
+ * The Initial Developer of the Original Code is Openbravo SLU 
+ * All portions are Copyright (C) 2019 Openbravo SLU 
+ * All Rights Reserved. 
+ * Contributor(s):  ______________________________________.
+ ************************************************************************
+ */
+
+package org.openbravo.authentication.hashing;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+import java.util.Date;
+import java.util.Optional;
+
+import org.junit.Test;
+import org.openbravo.dal.service.OBDal;
+import org.openbravo.model.ad.access.User;
+import org.openbravo.test.base.OBBaseTest;
+import org.openbravo.test.base.TestConstants;
+
+/** Tests password hashing with different algorithms */
+public class PasswordHashing extends OBBaseTest {
+
+  private static final String SHA1_OPENBRAVO = "PwOd6SgWF74HY4u51bfrUxjtB9g=";
+  private static final String SHA512SALT_OPENBRAVO = "1$anySalt$iyWvhlUpOrXFPPeRVzWXXR/B4hQ5qs8ZjCLUPoncJIKHRy5HZeXm9/r20qXg8tRgKcfC8bp/u5fPPQ9qA/hheQ==";
+
+  @Test
+  public void sha1IsAKnownAlgorithm() {
+    assertThat(PasswordHash.getAlgorithm("whatever").getClass().getSimpleName(), is("SHA1"));
+  }
+
+  @Test
+  public void sha512SaltIsAKnownAlgorithm() {
+    assertThat(PasswordHash.getAlgorithm("1$salt$hash").getClass().getSimpleName(),
+        is("SHA512Salt"));
+  }
+
+  @Test(expected = IllegalStateException.class)
+  public void unknownAlgorithmsThrowException() {
+    PasswordHash.getAlgorithm("2$salt$hash");
+  }
+
+  @Test
+  public void oldHashesWork() {
+    assertThat(PasswordHash.matches("openbravo", SHA1_OPENBRAVO), is(true));
+  }
+
+  @Test
+  public void newHashesWork() {
+    assertThat(PasswordHash.matches("openbravo", SHA512SALT_OPENBRAVO), is(true));
+  }
+
+  @Test
+  public void saltPrventCollission() {
+    assertThat("same password should generate different salted hashes",
+        PasswordHash.generateHash("mySecret"), not(equalTo(PasswordHash.generateHash("mySecret"))));
+  }
+
+  @Test
+  public void validUserNameAndPasswordReturnAUser() {
+    Optional<User> user = PasswordHash.getUserWithPassword("Openbravo", "openbravo");
+    assertThat("Openbravo user is found", user.isPresent(), is(true));
+  }
+
+  @Test
+  public void invalidPasswordDoesNotReturnAUser() {
+    Optional<User> user = PasswordHash.getUserWithPassword("Openbravo", "wrongPassword");
+    assertThat("Openbravo user is found", user.isPresent(), is(false));
+  }
+
+  @Test
+  public void invalidUserDoesNotReturnAUser() {
+    Optional<User> user = PasswordHash.getUserWithPassword("wrongUser", "wrongPassword");
+    assertThat("User is found", user.isPresent(), is(false));
+  }
+
+  @Test
+  public void oldAlgorithmsGetPromoted() {
+    setSystemAdministratorContext();
+
+    // Given a user with a password hashed with old algorithm
+    User obUser = OBDal.getInstance().get(User.class, TestConstants.Users.OPENBRAVO);
+    obUser.setPassword(SHA1_OPENBRAVO);
+    OBDal.getInstance().flush();
+
+    // when credentials are checked first time
+    Optional<User> user = PasswordHash.getUserWithPassword("Openbravo", "openbravo");
+
+    // then password gets promoted to new algorithm
+    assertThat("password is promoted",
+        PasswordHash.getAlgorithm(user.get().getPassword()).getClass().getSimpleName(),
+        is("SHA512Salt"));
+  }
+
+  @Test
+  public void newAlgorithmsRemainUntouched() {
+    setSystemAdministratorContext();
+
+    // Given a user with a password hashed with old algorithm
+    User obUser = OBDal.getInstance().get(User.class, TestConstants.Users.OPENBRAVO);
+    obUser.setPassword(SHA512SALT_OPENBRAVO);
+    OBDal.getInstance().flush();
+
+    // when credentials are checked first time
+    Optional<User> user = PasswordHash.getUserWithPassword("Openbravo", "openbravo");
+
+    // then password gets promoted to new algorithm
+    assertThat("password is not changed", user.get().getPassword(), is(SHA512SALT_OPENBRAVO));
+  }
+
+  @Test
+  public void oldPasswordsCanBeExpired() {
+    try {
+      setSystemAdministratorContext();
+
+      // Given a user with an expired password hashed with old algorithm
+      User obUser = OBDal.getInstance().get(User.class, TestConstants.Users.OPENBRAVO);
+      obUser.setPassword(SHA1_OPENBRAVO);
+      obUser.setPasswordExpired(true);
+      OBDal.getInstance().flush();
+      OBDal.getInstance().refresh(obUser);
+      Date lastPasswordUpdate = obUser.getLastPasswordUpdate();
+
+      // when credentials are checked first time and password is automatically updated to new
+      // algorithm
+      Optional<User> opUser = PasswordHash.getUserWithPassword("Openbravo", "openbravo");
+      User user = opUser.get();
+      OBDal.getInstance().refresh(user);
+
+      // then password continues being expired
+      assertThat("Last password update timestamp didn't change", user.getLastPasswordUpdate(),
+          is(lastPasswordUpdate));
+      assertThat("Password is expired", user.isPasswordExpired(), is(true));
+    } finally {
+      OBDal.getInstance().rollbackAndClose();
+    }
+  }
+}
--- a/src-test/src/org/openbravo/test/AllAntTaskTests.java	Wed Oct 02 18:02:37 2019 +0200
+++ b/src-test/src/org/openbravo/test/AllAntTaskTests.java	Wed Oct 09 10:16:50 2019 +0200
@@ -22,6 +22,7 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.Suite;
 import org.openbravo.advpaymentmngt.test.DocumentNumberGeneration;
+import org.openbravo.authentication.hashing.PasswordHashing;
 import org.openbravo.base.weld.test.testinfrastructure.CdiInfrastructure;
 import org.openbravo.base.weld.test.testinfrastructure.DalPersistanceEventTest;
 import org.openbravo.base.weld.test.testinfrastructure.ParameterizedCdi;
@@ -232,6 +233,7 @@
     OBContextTest.class, //
     OldCallouts.class, //
     JSONSerialization.class, //
+    PasswordHashing.class, //
 
     // xml
     ClientExportImportTest.class, //
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/openbravo/authentication/hashing/HashingAlgorithm.java	Wed Oct 09 10:16:50 2019 +0200
@@ -0,0 +1,71 @@
+/*
+ *************************************************************************
+ * The contents of this file are subject to the Openbravo  Public  License
+ * Version  1.1  (the  "License"),  being   the  Mozilla   Public  License
+ * Version 1.1  with a permitted attribution clause; you may not  use this
+ * file except in compliance with the License. You  may  obtain  a copy of
+ * the License at http://www.openbravo.com/legal/license.html 
+ * Software distributed under the License  is  distributed  on  an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+ * License for the specific  language  governing  rights  and  limitations
+ * under the License. 
+ * The Original Code is Openbravo ERP. 
+ * The Initial Developer of the Original Code is Openbravo SLU 
+ * All portions are Copyright (C) 2019 Openbravo SLU 
+ * All Rights Reserved. 
+ * Contributor(s):  ______________________________________.
+ ************************************************************************
+ */
+
+package org.openbravo.authentication.hashing;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.util.Base64;
+
+/** Algorithm used to hash password to store in Database */
+abstract class HashingAlgorithm {
+
+  /**
+   * Generates a hash using current algorithm, hashes should look like: {@code version$salt$hashed}
+   * where:
+   * <p>
+   * <ul>
+   * <li>{@code version}: is a numeric value that determines the version of the
+   * {@code HashingAlgorithm} that performed the hash. It must match the value returned by
+   * {@link #getAlgorithmVersion()}.
+   * <li>{@code salt}: salt used in the hash.
+   * <li>{@code hashed}: actual hash, which is computed by {@link #hash(String, String)}.
+   * </ul>
+   */
+  protected abstract String generateHash(String password);
+
+  /**
+   * Each {@link HashingAlgorithm} must be versioned, passwords hashed in Database with older
+   * algorithms can be automatically upgraded to newer ones.
+   * 
+   * @see PasswordHash#getUserWithPassword(String, String)
+   */
+  protected abstract int getAlgorithmVersion();
+
+  /** Checks whether a plain text password matches with a hashed password */
+  protected abstract boolean check(String plainTextPassowed, String hashedPassword);
+
+  /** Returns the low level algorithm used to perform the hashing. */
+  protected abstract MessageDigest getHashingBaseAlgorithm();
+
+  /**
+   * Performs the low level hashing of {@code plainText} salted with {@code salt} value. Salt can be
+   * null in which case will be ignored.
+   */
+  protected final String hash(String plainText, String salt) {
+    MessageDigest md = getHashingBaseAlgorithm();
+    if (salt != null) {
+      md.update(salt.getBytes(StandardCharsets.UTF_8));
+    }
+
+    byte[] bytes = md.digest(plainText.getBytes(StandardCharsets.UTF_8));
+
+    return Base64.getEncoder().encodeToString(bytes);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/openbravo/authentication/hashing/PasswordHash.java	Wed Oct 09 10:16:50 2019 +0200
@@ -0,0 +1,140 @@
+/*
+ *************************************************************************
+ * The contents of this file are subject to the Openbravo  Public  License
+ * Version  1.1  (the  "License"),  being   the  Mozilla   Public  License
+ * Version 1.1  with a permitted attribution clause; you may not  use this
+ * file except in compliance with the License. You  may  obtain  a copy of
+ * the License at http://www.openbravo.com/legal/license.html 
+ * Software distributed under the License  is  distributed  on  an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+ * License for the specific  language  governing  rights  and  limitations
+ * under the License. 
+ * The Original Code is Openbravo ERP. 
+ * The Initial Developer of the Original Code is Openbravo SLU 
+ * All portions are Copyright (C) 2019 Openbravo SLU 
+ * All Rights Reserved. 
+ * Contributor(s):  ______________________________________.
+ ************************************************************************
+ */
+
+package org.openbravo.authentication.hashing;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.hibernate.criterion.Restrictions;
+import org.jboss.weld.exceptions.IllegalStateException;
+import org.openbravo.dal.core.OBContext;
+import org.openbravo.dal.service.OBDal;
+import org.openbravo.model.ad.access.User;
+
+/**
+ * Handles hashing passwords to be stored in database supporting different
+ * {@link HashingAlgorithm}s.
+ *
+ * @since 3.0PR20Q1
+ */
+public class PasswordHash {
+  public static final Logger log = LogManager.getLogger();
+  private static final int DEFAULT_CURRENT_ALGORITHM_VERSION = 1;
+
+  private static final Map<Integer, HashingAlgorithm> ALGORITHMS;
+  static {
+    ALGORITHMS = new HashMap<>(2);
+    ALGORITHMS.put(0, new SHA1());
+    ALGORITHMS.put(1, new SHA512Salt());
+  }
+
+  private PasswordHash() {
+  }
+
+  /**
+   * Checks if userName matches password, returning an {@link Optional} {@link User} in case it
+   * matches.
+   * <p>
+   * <b>Important Note</b>: In case password matches with the current one for the user and it was
+   * hashed with a {@link HashingAlgorithm} with a version lower than current default, hash will be
+   * promoted to the default algorithm. In this case, user's password field will be updated and DAL
+   * current transaction will be flushed to DB.
+   * 
+   * @param userName
+   *          user name to check
+   * @param password
+   *          user's password in plain text as provided by the user
+   * @return an {@code Optional} describing the {@code User} matching the provided {@code userName}
+   *         and {@code password} pair; or an empty {@code Optional} if there is no {@code User}
+   *         matching them
+   */
+  public static Optional<User> getUserWithPassword(String userName, String password) {
+    OBContext.setAdminMode(false);
+    try {
+      User user = (User) OBDal.getInstance()
+          .createCriteria(User.class)
+          .add(Restrictions.eq(User.PROPERTY_USERNAME, userName))
+          .setFilterOnActive(true)
+          .setFilterOnReadableClients(false)
+          .setFilterOnReadableOrganization(false)
+          .uniqueResult();
+
+      if (user == null || user.getPassword() == null) {
+        // no user for given userName
+        return Optional.empty();
+      }
+
+      HashingAlgorithm algorithm = getAlgorithm(user.getPassword());
+
+      if (!algorithm.check(password, user.getPassword())) {
+        // invalid password
+        return Optional.empty();
+      }
+
+      if (algorithm.getAlgorithmVersion() < DEFAULT_CURRENT_ALGORITHM_VERSION
+          && !user.isPasswordExpired()) {
+        log.debug("Upgrading password hash for user {}, from algorithm version {} to {}.",
+            user.getUsername(), algorithm.getAlgorithmVersion(), DEFAULT_CURRENT_ALGORITHM_VERSION);
+        String newPassword = ALGORITHMS.get(DEFAULT_CURRENT_ALGORITHM_VERSION)
+            .generateHash(password);
+        user.setPassword(newPassword);
+        OBDal.getInstance().flush();
+      }
+      return Optional.of(user);
+    } finally {
+      OBContext.restorePreviousMode();
+    }
+  }
+
+  /** Generates a hash for the {@code plainText} using current default {@link HashingAlgorithm} */
+  public static String generateHash(String plainText) {
+    return ALGORITHMS.get(DEFAULT_CURRENT_ALGORITHM_VERSION).generateHash(plainText);
+  }
+
+  /** Checks whether a plain text password matches with a hashed password */
+  public static boolean matches(String plainTextPassword, String hashedPassword) {
+    HashingAlgorithm algorithm = getAlgorithm(hashedPassword);
+    log.trace("Checking password with algorithm {}", () -> algorithm.getClass().getSimpleName());
+    return algorithm.check(plainTextPassword, hashedPassword);
+  }
+
+  /** Determines the algorithm used to hash a given password. */
+  static HashingAlgorithm getAlgorithm(String hash) {
+    HashingAlgorithm algorithm = ALGORITHMS.get(getVersion(hash));
+
+    if (algorithm == null) {
+      throw new IllegalStateException(
+          "Hashing algorithm version " + getVersion(hash) + " is not implemented");
+    }
+
+    return algorithm;
+  }
+
+  private static int getVersion(String hash) {
+    int idx = hash.indexOf('$');
+    if (idx == -1) {
+      return 0;
+    }
+    return Integer.parseInt(hash.substring(0, idx));
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/openbravo/authentication/hashing/SHA1.java	Wed Oct 09 10:16:50 2019 +0200
@@ -0,0 +1,57 @@
+/*
+ *************************************************************************
+ * The contents of this file are subject to the Openbravo  Public  License
+ * Version  1.1  (the  "License"),  being   the  Mozilla   Public  License
+ * Version 1.1  with a permitted attribution clause; you may not  use this
+ * file except in compliance with the License. You  may  obtain  a copy of
+ * the License at http://www.openbravo.com/legal/license.html 
+ * Software distributed under the License  is  distributed  on  an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+ * License for the specific  language  governing  rights  and  limitations
+ * under the License. 
+ * The Original Code is Openbravo ERP. 
+ * The Initial Developer of the Original Code is Openbravo SLU 
+ * All portions are Copyright (C) 2019 Openbravo SLU 
+ * All Rights Reserved. 
+ * Contributor(s):  ______________________________________.
+ ************************************************************************
+ */
+
+package org.openbravo.authentication.hashing;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import org.jboss.weld.exceptions.IllegalStateException;
+
+/**
+ * Passwords are hashed with SHA-1 algorithm represented as a {@code String} encoded in base 64.
+ * <p>
+ * Algorithm used before 3.0PR20Q1.
+ */
+class SHA1 extends HashingAlgorithm {
+  @Override
+  protected MessageDigest getHashingBaseAlgorithm() {
+    try {
+      return MessageDigest.getInstance("SHA-1");
+    } catch (NoSuchAlgorithmException wontHappen) {
+      throw new IllegalStateException(wontHappen);
+    }
+  }
+
+  @Override
+  protected boolean check(String plainTextPassword, String hashedPassword) {
+    return hash(plainTextPassword, null).equals(hashedPassword);
+  }
+
+  @Override
+  protected int getAlgorithmVersion() {
+    return 0;
+  }
+
+  @Override
+  protected String generateHash(String password) {
+    return hash(password, null);
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/openbravo/authentication/hashing/SHA512Salt.java	Wed Oct 09 10:16:50 2019 +0200
@@ -0,0 +1,71 @@
+/*
+ *************************************************************************
+ * The contents of this file are subject to the Openbravo  Public  License
+ * Version  1.1  (the  "License"),  being   the  Mozilla   Public  License
+ * Version 1.1  with a permitted attribution clause; you may not  use this
+ * file except in compliance with the License. You  may  obtain  a copy of
+ * the License at http://www.openbravo.com/legal/license.html 
+ * Software distributed under the License  is  distributed  on  an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+ * License for the specific  language  governing  rights  and  limitations
+ * under the License. 
+ * The Original Code is Openbravo ERP. 
+ * The Initial Developer of the Original Code is Openbravo SLU 
+ * All portions are Copyright (C) 2019 Openbravo SLU 
+ * All Rights Reserved. 
+ * Contributor(s):  ______________________________________.
+ ************************************************************************
+ */
+
+package org.openbravo.authentication.hashing;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Base64;
+import java.util.Random;
+
+import org.jboss.weld.exceptions.IllegalStateException;
+
+/**
+ * Passwords are hashed using SHA-512 algorithm with a random salt of 16 bytes represented as a
+ * {@code String} encoded in base 64.
+ * <p>
+ * The full hash looks like {@code 1$salt$hashedPassword}, where {@code 1} is this algorithm's
+ * version.
+ */
+class SHA512Salt extends HashingAlgorithm {
+  private static final Random RANDOM = new SecureRandom();
+
+  @Override
+  protected MessageDigest getHashingBaseAlgorithm() {
+    try {
+      return MessageDigest.getInstance("SHA-512");
+    } catch (NoSuchAlgorithmException wontHappen) {
+      throw new IllegalStateException(wontHappen);
+    }
+  }
+
+  @Override
+  protected boolean check(String plainTextPassword, String hashedPassword) {
+    String[] hashParts = hashedPassword.split("\\$");
+    String salt = hashParts[1];
+    String orginalHash = hashParts[2];
+
+    return hash(plainTextPassword, salt).equals(orginalHash);
+  }
+
+  @Override
+  protected int getAlgorithmVersion() {
+    return 1;
+  }
+
+  @Override
+  protected String generateHash(String password) {
+    byte[] rawSalt = new byte[16];
+    RANDOM.nextBytes(rawSalt);
+    String salt = Base64.getEncoder().withoutPadding().encodeToString(rawSalt);
+    String hash = hash(password, salt);
+    return getAlgorithmVersion() + "$" + salt + "$" + hash;
+  }
+}
--- a/src/org/openbravo/base/model/domaintype/HashedStringDomainType.java	Wed Oct 02 18:02:37 2019 +0200
+++ b/src/org/openbravo/base/model/domaintype/HashedStringDomainType.java	Wed Oct 09 10:16:50 2019 +0200
@@ -11,7 +11,7 @@
  * under the License. 
  * The Original Code is Openbravo ERP. 
  * The Initial Developer of the Original Code is Openbravo SLU 
- * All portions are Copyright (C) 2011 Openbravo SLU 
+ * All portions are Copyright (C) 2011-2019 Openbravo SLU 
  * All Rights Reserved. 
  * Contributor(s):  ______________________________________.
  ************************************************************************
@@ -24,7 +24,7 @@
  * cleartext value cannot be recovered.
  * 
  * @author shuehner
- * @see org.openbravo.utils.CryptoSHA1BASE64#hash(String)
+ * @see org.openbravo.authentication.hashing.PasswordHash
  */
 public class HashedStringDomainType extends StringDomainType {
 
--- a/src/org/openbravo/base/secureApp/LoginHandler.java	Wed Oct 02 18:02:37 2019 +0200
+++ b/src/org/openbravo/base/secureApp/LoginHandler.java	Wed Oct 09 10:16:50 2019 +0200
@@ -30,11 +30,11 @@
 import org.openbravo.authentication.AuthenticationExpirationPasswordException;
 import org.openbravo.authentication.AuthenticationManager;
 import org.openbravo.authentication.ChangePasswordException;
+import org.openbravo.authentication.hashing.PasswordHash;
 import org.openbravo.base.HttpBaseServlet;
 import org.openbravo.base.secureApp.LoginUtils.RoleDefaults;
 import org.openbravo.client.application.CachedPreference;
 import org.openbravo.dal.core.OBContext;
-import org.openbravo.dal.service.OBCriteria;
 import org.openbravo.dal.service.OBDal;
 import org.openbravo.database.ConnectionProvider;
 import org.openbravo.erpCommon.businessUtility.Preferences;
@@ -55,7 +55,6 @@
 import org.openbravo.server.ServerControllerHandler;
 import org.openbravo.service.db.DalConnectionProvider;
 import org.openbravo.service.password.PasswordStrengthChecker;
-import org.openbravo.utils.FormatUtilities;
 
 /**
  * 
@@ -587,33 +586,30 @@
    * 
    * @param userId
    *          the userId
-   * @param unHashedPassword
-   *          the password, the unhashed password as it is entered by the user.
+   * @param newPassword
+   *          the password, the plain text password as it is entered by the user.
    * @param language
    *          Default language for the user
    * @throws ServletException
    *           ServletException is thrown in case that password could not be hashed
    * 
    */
-  private void updatePassword(String userId, String unHashedPassword, String language)
-      throws ServletException {
+  private void updatePassword(String userId, String newPassword, String language) {
     try {
       OBContext.setAdminMode();
-      final OBCriteria<User> obc = OBDal.getInstance().createCriteria(User.class);
-      obc.add(Restrictions.eq(User.PROPERTY_ID, userId));
-      obc.setFilterOnReadableClients(false);
-      obc.setFilterOnReadableOrganization(false);
-      final User userOB = (User) obc.uniqueResult();
-      String oldPassword = userOB.getPassword();
-      String newPassword = FormatUtilities.sha1Base64(unHashedPassword);
-      if (oldPassword.equals(newPassword)) {
+      User user = (User) OBDal.getInstance()
+          .createCriteria(User.class)
+          .add(Restrictions.eq(User.PROPERTY_ID, userId))
+          .setFilterOnReadableClients(false)
+          .setFilterOnReadableOrganization(false)
+          .uniqueResult();
+
+      if (PasswordHash.matches(newPassword, user.getPassword())) {
         throwChangePasswordException("CPDifferentPassword", "CPSamePasswordThanOld", language);
-      } else if (!passwordStrengthChecker.isStrongPassword(unHashedPassword)) {
+      } else if (!passwordStrengthChecker.isStrongPassword(newPassword)) {
         throwChangePasswordException("CPWeakPasswordTitle", "CPPasswordNotStrongEnough", language);
       } else {
-        userOB.setPassword(newPassword);
-        OBDal.getInstance().save(userOB);
-        OBDal.getInstance().flush();
+        user.setPassword(PasswordHash.generateHash(newPassword));
         OBDal.getInstance().commitAndClose();
       }
     } finally {
--- a/src/org/openbravo/base/secureApp/LoginUtils.java	Wed Oct 02 18:02:37 2019 +0200
+++ b/src/org/openbravo/base/secureApp/LoginUtils.java	Wed Oct 09 10:16:50 2019 +0200
@@ -15,6 +15,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 
 import javax.servlet.ServletException;
@@ -25,6 +26,7 @@
 import org.apache.commons.lang.StringUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
+import org.openbravo.authentication.hashing.PasswordHash;
 import org.openbravo.base.HttpBaseUtils;
 import org.openbravo.base.exception.OBException;
 import org.openbravo.base.exception.OBSecurityException;
@@ -43,10 +45,10 @@
 import org.openbravo.erpCommon.utility.StringCollectionUtils;
 import org.openbravo.erpCommon.utility.Utility;
 import org.openbravo.model.ad.access.RoleOrganization;
+import org.openbravo.model.ad.access.User;
 import org.openbravo.model.ad.domain.Preference;
 import org.openbravo.model.ad.system.Client;
 import org.openbravo.service.db.DalConnectionProvider;
-import org.openbravo.utils.FormatUtilities;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
@@ -76,7 +78,7 @@
    * @param unHashedPassword
    *          the password, the unhashed password as it is entered by the user.
    * @return the user id or null if no user could be found or the user is locked.
-   * @see FormatUtilities#sha1Base64(String)
+   * @see PasswordHash
    */
   public static String getValidUserId(ConnectionProvider connectionProvider, String login,
       String unHashedPassword) {
@@ -106,13 +108,8 @@
   public static String checkUserPassword(ConnectionProvider connectionProvider, String login,
       String unHashedPassword) {
     try {
-      final String hashedPassword = FormatUtilities.sha1Base64(unHashedPassword);
-      final String userId = SeguridadData.valido(connectionProvider, login, hashedPassword);
-      if (userId.equals("-1")) {
-        return null;
-      }
-
-      return userId;
+      Optional<User> user = PasswordHash.getUserWithPassword(login, unHashedPassword);
+      return user.map(User::getId).orElse(null);
     } catch (final Exception e) {
       throw new OBException(e);
     }
--- a/src/org/openbravo/erpCommon/businessUtility/InitialClientSetup.java	Wed Oct 02 18:02:37 2019 +0200
+++ b/src/org/openbravo/erpCommon/businessUtility/InitialClientSetup.java	Wed Oct 09 10:16:50 2019 +0200
@@ -11,7 +11,7 @@
  * under the License.
  * The Original Code is Openbravo ERP.
  * The Initial Developer of the Original Code is Openbravo SLU
- * All portions are Copyright (C) 2010-2018 Openbravo SLU
+ * All portions are Copyright (C) 2010-2019 Openbravo SLU
  * All Rights Reserved.
  * Contributor(s):  ______________________________________.
  ************************************************************************
@@ -50,7 +50,6 @@
 import org.openbravo.model.common.currency.Currency;
 import org.openbravo.service.db.DalConnectionProvider;
 import org.openbravo.service.db.ImportResult;
-import org.openbravo.utils.FormatUtilities;
 
 /**
  * @author David Alsasua
@@ -526,8 +525,7 @@
     log4j.debug("insertUser() - Inserting user named " + strUserName);
     User user;
     try {
-      user = InitialSetupUtility.insertUser(client, null, strUserName,
-          FormatUtilities.sha1Base64(strPassword), role,
+      user = InitialSetupUtility.insertUser(client, null, strUserName, strPassword, role,
           InitialSetupUtility.getLanguage(strLanguage));
     } catch (Exception e) {
       return logErrorAndRollback("@CreateClientFailed@",
--- a/src/org/openbravo/erpCommon/businessUtility/InitialOrgSetup.java	Wed Oct 02 18:02:37 2019 +0200
+++ b/src/org/openbravo/erpCommon/businessUtility/InitialOrgSetup.java	Wed Oct 09 10:16:50 2019 +0200
@@ -52,7 +52,6 @@
 import org.openbravo.model.common.enterprise.OrganizationType;
 import org.openbravo.model.common.geography.Location;
 import org.openbravo.service.db.ImportResult;
-import org.openbravo.utils.FormatUtilities;
 
 public class InitialOrgSetup {
   private static final Logger log4j = LogManager.getLogger();
@@ -634,8 +633,7 @@
     log4j.debug("insertUser() - Organization User Name: " + strOrgUser);
 
     try {
-      user = InitialSetupUtility.insertUser(client, null, strOrgUser,
-          FormatUtilities.sha1Base64(strPassword), null, language);
+      user = InitialSetupUtility.insertUser(client, null, strOrgUser, strPassword, null, language);
     } catch (final Exception err) {
       return logErrorAndRollback("@CreateOrgFailed@",
           "insertUser() - ERROR - Not able to insert the user " + strOrgUser, err);
--- a/src/org/openbravo/erpCommon/businessUtility/InitialSetupUtility.java	Wed Oct 02 18:02:37 2019 +0200
+++ b/src/org/openbravo/erpCommon/businessUtility/InitialSetupUtility.java	Wed Oct 09 10:16:50 2019 +0200
@@ -36,6 +36,7 @@
 import org.apache.logging.log4j.Logger;
 import org.hibernate.criterion.Order;
 import org.hibernate.criterion.Restrictions;
+import org.openbravo.authentication.hashing.PasswordHash;
 import org.openbravo.base.provider.OBProvider;
 import org.openbravo.base.session.OBPropertiesProvider;
 import org.openbravo.base.session.SessionFactoryController;
@@ -694,7 +695,7 @@
     newUser.setName(name);
     newUser.setDescription(name);
     newUser.setUsername(name);
-    newUser.setPassword(password);
+    newUser.setPassword(PasswordHash.generateHash(password));
     newUser.setDefaultLanguage(defaultLanguage);
     if (role != null) {
       newUser.setDefaultRole(role);
--- a/src/org/openbravo/portal/GrantPortalAccessProcess.java	Wed Oct 02 18:02:37 2019 +0200
+++ b/src/org/openbravo/portal/GrantPortalAccessProcess.java	Wed Oct 09 10:16:50 2019 +0200
@@ -11,7 +11,7 @@
  * under the License. 
  * The Original Code is Openbravo ERP. 
  * The Initial Developer of the Original Code is Openbravo SLU 
- * All portions are Copyright (C) 2013-2017 Openbravo SLU 
+ * All portions are Copyright (C) 2013-2019 Openbravo SLU 
  * All Rights Reserved. 
  * Contributor(s):  ______________________________________.
  ************************************************************************
@@ -29,6 +29,7 @@
 import org.apache.logging.log4j.Logger;
 import org.codehaus.jettison.json.JSONObject;
 import org.hibernate.criterion.Restrictions;
+import org.openbravo.authentication.hashing.PasswordHash;
 import org.openbravo.base.provider.OBProvider;
 import org.openbravo.client.application.process.BaseProcessActionHandler;
 import org.openbravo.client.application.process.ResponseActionsBuilder.MessageType;
@@ -41,7 +42,6 @@
 import org.openbravo.model.ad.access.User;
 import org.openbravo.model.ad.access.UserRoles;
 import org.openbravo.model.common.enterprise.Organization;
-import org.openbravo.utils.CryptoSHA1BASE64;
 
 /**
  * This process grants the user the given role and resets her password
@@ -102,7 +102,7 @@
       }
 
       String newPassword = RandomStringUtils.randomAlphanumeric(PASSWORD_LENGHT);
-      user.setPassword(CryptoSHA1BASE64.hash(newPassword));
+      user.setPassword(PasswordHash.generateHash(newPassword));
 
       // flushing changes in admin mode
       OBDal.getInstance().flush();