svn commit: r1772589 - in /ofbiz/trunk/framework: base/src/main/java/org/apache/ofbiz/base/crypto/HashCrypt.java security/config/security.properties security/data/PasswordSecurityDemoData.xml security/entitydef/entitymodel.xml

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

svn commit: r1772589 - in /ofbiz/trunk/framework: base/src/main/java/org/apache/ofbiz/base/crypto/HashCrypt.java security/config/security.properties security/data/PasswordSecurityDemoData.xml security/entitydef/entitymodel.xml

shijh
Author: shijh
Date: Mon Dec  5 01:32:51 2016
New Revision: 1772589

URL: http://svn.apache.org/viewvc?rev=1772589&view=rev
Log:
Implemented: LoginWorker HashCrypt the type of hash for one-way encryption

Added PBKDF2 hash type to encrypt password.

Thanks: wangjunyuan for this contribution.

Modified:
    ofbiz/trunk/framework/base/src/main/java/org/apache/ofbiz/base/crypto/HashCrypt.java
    ofbiz/trunk/framework/security/config/security.properties
    ofbiz/trunk/framework/security/data/PasswordSecurityDemoData.xml
    ofbiz/trunk/framework/security/entitydef/entitymodel.xml

Modified: ofbiz/trunk/framework/base/src/main/java/org/apache/ofbiz/base/crypto/HashCrypt.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/base/src/main/java/org/apache/ofbiz/base/crypto/HashCrypt.java?rev=1772589&r1=1772588&r2=1772589&view=diff
==============================================================================
--- ofbiz/trunk/framework/base/src/main/java/org/apache/ofbiz/base/crypto/HashCrypt.java (original)
+++ ofbiz/trunk/framework/base/src/main/java/org/apache/ofbiz/base/crypto/HashCrypt.java Mon Dec  5 01:32:51 2016
@@ -22,6 +22,10 @@ import java.io.UnsupportedEncodingExcept
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
 
 import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.codec.binary.Hex;
@@ -29,11 +33,12 @@ import org.apache.commons.lang.RandomStr
 import org.apache.ofbiz.base.util.Debug;
 import org.apache.ofbiz.base.util.GeneralRuntimeException;
 import org.apache.ofbiz.base.util.StringUtil;
-import org.apache.ofbiz.base.util.UtilValidate;
 import org.apache.ofbiz.base.util.UtilIO;
+import org.apache.ofbiz.base.util.UtilProperties;
+import org.apache.ofbiz.base.util.UtilValidate;
 
 /**
- * Utility class for doing SHA-1/MD5 One-Way Hash Encryption
+ * Utility class for doing SHA-1/MD5/PBKDF2 One-Way Hash Encryption
  *
  */
 public class HashCrypt {
@@ -41,6 +46,16 @@ public class HashCrypt {
     public static final String module = HashCrypt.class.getName();
     public static final String CRYPT_CHAR_SET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./";
 
+    private static final String PBKDF2_SHA1 ="pbkdf2_sha1";
+    
+    private static final String PBKDF2_SHA256 ="pbkdf2_sha256";
+    
+    private static final String PBKDF2_SHA384 ="pbkdf2_sha384";
+    
+    private static final String PBKDF2_SHA512 ="pbkdf2_sha512";
+
+    private static final int PBKDF2_ITERATIONS = UtilProperties.getPropertyAsInteger("security.properties", "password.encrypt.pbkdf2.iterations", 1000);
+    
     public static MessageDigest getMessageDigest(String type) {
         try {
             return MessageDigest.getInstance(type);
@@ -55,6 +70,8 @@ public class HashCrypt {
             return doCompareTypePrefix(crypted, defaultCrypt, password.getBytes());
         } else if (crypted.startsWith("$")) {
             return doComparePosix(crypted, defaultCrypt, password.getBytes(UtilIO.getUtf8()));
+        } else if (crypted.startsWith("pbkdf2")) {
+            return doComparePbkdf2(crypted, password);
         } else {
             // FIXME: should have been getBytes("UTF-8") originally
             return doCompareBare(crypted, defaultCrypt, password.getBytes());
@@ -106,15 +123,24 @@ public class HashCrypt {
      */
     @Deprecated
     public static String cryptPassword(String hashType, String salt, String password) {
+     if (hashType.startsWith("PBKDF2")) {
+            return password != null ? pbkdf2HashCrypt(hashType, salt, password) : null;
+        }
         // FIXME: should have been getBytes("UTF-8") originally
         return password != null ? cryptBytes(hashType, salt, password.getBytes()) : null;
     }
 
     public static String cryptUTF8(String hashType, String salt, String value) {
+     if (hashType.startsWith("PBKDF2")) {
+            return value != null ? pbkdf2HashCrypt(hashType, salt, value) : null;
+        }
         return value != null ? cryptBytes(hashType, salt, value.getBytes(UtilIO.getUtf8())) : null;
     }
 
     public static String cryptValue(String hashType, String salt, String value) {
+     if (hashType.startsWith("PBKDF2")) {
+            return value != null ? pbkdf2HashCrypt(hashType, salt, value) : null;
+        }
         return value != null ? cryptBytes(hashType, salt, value.getBytes()) : null;
     }
 
@@ -142,6 +168,90 @@ public class HashCrypt {
         }
     }
 
+    public static String pbkdf2HashCrypt(String hashType, String salt, String value){
+        char[] chars = value.toCharArray();
+        if (UtilValidate.isEmpty(salt)) {
+            salt = getSalt();
+        }
+        try {
+            PBEKeySpec spec = new PBEKeySpec(chars, salt.getBytes(UtilIO.getUtf8()), PBKDF2_ITERATIONS, 64 * 4);
+            SecretKeyFactory skf = SecretKeyFactory.getInstance(hashType);
+            byte[] hash = Base64.encodeBase64(skf.generateSecret(spec).getEncoded());
+            String pbkdf2Type = null;
+            switch (hashType) {
+                case "PBKDF2WithHmacSHA1":
+                    pbkdf2Type = PBKDF2_SHA1;
+                    break;
+                case "PBKDF2WithHmacSHA256":
+                    pbkdf2Type = PBKDF2_SHA256;
+                    break;
+                case "PBKDF2WithHmacSHA384":
+                    pbkdf2Type = PBKDF2_SHA384;
+                    break;
+                case "PBKDF2WithHmacSHA512":
+                    pbkdf2Type = PBKDF2_SHA512;
+                    break;
+                default:
+                    pbkdf2Type = PBKDF2_SHA1;
+            }
+            return pbkdf2Type + "$" + PBKDF2_ITERATIONS + "$" + salt + "$" + new String(hash);
+        } catch (InvalidKeySpecException e) {
+            throw new GeneralRuntimeException("Error while creating SecretKey", e);
+        } catch (NoSuchAlgorithmException e) {
+            throw new GeneralRuntimeException("Error while computing SecretKeyFactory", e);
+        }
+    }
+    
+    public static boolean doComparePbkdf2(String storedPassword, String originalPassword){
+        try {
+            String[] parts = storedPassword.split("\\$");
+            String hashHead = parts[0];
+            int iterations = Integer.parseInt(parts[1]);
+            byte[] salt = parts[2].getBytes();
+            byte[] hash = Base64.decodeBase64(parts[3].getBytes());
+            
+            PBEKeySpec spec = new PBEKeySpec(originalPassword.toCharArray(), salt, iterations, hash.length * 8);
+            String hashType = null;
+            switch (hashHead.substring(hashHead.indexOf("_")+4)) {
+                case "256":
+                    hashType = "PBKDF2WithHmacSHA256";
+                    break;
+                case "384":
+                    hashType = "PBKDF2WithHmacSHA384";
+                    break;
+                case "512":
+                    hashType = "PBKDF2WithHmacSHA512";
+                    break;
+                default:  
+                    hashType = "PBKDF2WithHmacSHA1";
+            }
+            SecretKeyFactory skf = SecretKeyFactory.getInstance(hashType);
+            byte[] testHash = skf.generateSecret(spec).getEncoded();
+            int diff = hash.length ^ testHash.length;
+            
+            for (int i = 0; i < hash.length && i < testHash.length; i++) {
+                diff |= hash[i] ^ testHash[i];
+            }
+            
+            return diff == 0;
+        } catch (NoSuchAlgorithmException e) {
+            throw new GeneralRuntimeException("Error while computing SecretKeyFactory", e);
+        } catch (InvalidKeySpecException e) {
+            throw new GeneralRuntimeException("Error while creating SecretKey", e);
+        }
+    }
+    
+    private static String getSalt() {
+        try {
+            SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
+            byte[] salt = new byte[16];
+            sr.nextBytes(salt);
+            return salt.toString();
+        } catch (NoSuchAlgorithmException e) {
+            throw new GeneralRuntimeException("Error while creating salt", e);
+        }
+    }
+    
     /**
      * @deprecated use digestHash("SHA", null, str)
      */

Modified: ofbiz/trunk/framework/security/config/security.properties
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/security/config/security.properties?rev=1772589&r1=1772588&r2=1772589&view=diff
==============================================================================
--- ofbiz/trunk/framework/security/config/security.properties (original)
+++ ofbiz/trunk/framework/security/config/security.properties Mon Dec  5 01:32:51 2016
@@ -82,9 +82,13 @@ password.encrypt=true
 password.email_password.require_password_change=true
 
 # -- specify the type of hash to use for one-way encryption, will be passed to java.security.MessageDigest.getInstance() --
-# -- options may include: SHA, MD5, etc
+# -- options may include: SHA, MD5, PBKDF2WithHmacSHA1, PBKDF2WithHmacSHA256, PBKDF2WithHmacSHA384, PBKDF2WithHmacSHA512 and etc
 password.encrypt.hash.type=SHA
 
+# -- if the type of hash to use for one-way encryption is PBKDF2WithHmacSHA1 or PBKDF2WithHmacSHA256 or PBKDF2WithHmacSHA384 or PBKDF2WithHmacSHA512
+# -- the type of hash to use for one-way encryption needs iteration
+password.encrypt.pbkdf2.iterations=1000
+
 # -- this is helpful to recover old accounts or to be able to login at all sometimes --
 # -- SHOULD GENERALLY NOT BE TRUE FOR PRODUCTION SITES, but is useful for interim periods when going to password encryption --
 password.accept.encrypted.and.plain=false

Modified: ofbiz/trunk/framework/security/data/PasswordSecurityDemoData.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/security/data/PasswordSecurityDemoData.xml?rev=1772589&r1=1772588&r2=1772589&view=diff
==============================================================================
--- ofbiz/trunk/framework/security/data/PasswordSecurityDemoData.xml (original)
+++ ofbiz/trunk/framework/security/data/PasswordSecurityDemoData.xml Mon Dec  5 01:32:51 2016
@@ -21,7 +21,7 @@ under the License.
 <entity-engine-xml>
     <!-- from the securityext component: SecurityExtData.xml -->
     <UserLogin userLoginId="admin" currentPassword="{SHA}47b56994cbc2b6d10aa1be30f70165adb305a41a" passwordHint=""/>
-    <UserLogin userLoginId="flexadmin" currentPassword="{SHA}47b56994cbc2b6d10aa1be30f70165adb305a41a" passwordHint=""/>
+    <UserLogin userLoginId="flexadmin" currentPassword="pbkdf2_sha1$1000$[B@4cb8144c$uAmNaxG3cmfr/VOWPuniw7J/Rw7VM7WFpax3PELBq2Q=" passwordHint=""/>
     <UserLogin userLoginId="demoadmin" currentPassword="{SHA}47b56994cbc2b6d10aa1be30f70165adb305a41a" passwordHint=""/>
     <UserLogin userLoginId="ltdadmin" currentPassword="{SHA}47b56994cbc2b6d10aa1be30f70165adb305a41a" passwordHint=""/>
     <UserLogin userLoginId="ltdadmin1" currentPassword="{SHA}47b56994cbc2b6d10aa1be30f70165adb305a41a" passwordHint=""/>

Modified: ofbiz/trunk/framework/security/entitydef/entitymodel.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/security/entitydef/entitymodel.xml?rev=1772589&r1=1772588&r2=1772589&view=diff
==============================================================================
--- ofbiz/trunk/framework/security/entitydef/entitymodel.xml (original)
+++ ofbiz/trunk/framework/security/entitydef/entitymodel.xml Mon Dec  5 01:32:51 2016
@@ -62,7 +62,7 @@ under the License.
             package-name="org.apache.ofbiz.security.login"
             title="User Login Entity">
       <field name="userLoginId" type="id-vlong-ne"></field>
-      <field name="currentPassword" type="short-varchar"></field>
+      <field name="currentPassword" type="long-varchar"></field>
       <field name="passwordHint" type="description"></field>
       <field name="isSystem" type="indicator"></field>
       <field name="enabled" type="indicator"></field>
@@ -89,7 +89,7 @@ under the License.
       <field name="userLoginId" type="id-vlong-ne"></field>
       <field name="fromDate" type="date-time"></field>
       <field name="thruDate" type="date-time"></field>
-      <field name="currentPassword" type="short-varchar"></field>
+      <field name="currentPassword" type="long-varchar"></field>
       <prim-key field="userLoginId"/>
       <prim-key field="fromDate"/>
       <relation type="one" fk-name="USER_LPH_USER" rel-entity-name="UserLogin">