Author: thomasm
Date: Wed Apr 10 12:52:15 2013
New Revision: 1466455
URL: http://svn.apache.org/r1466455
Log:
OAK-697 Security: support for PBKDF2 password hashing
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/PasswordUtility.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/util/PasswordUtilityTest.java
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/PasswordUtility.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/PasswordUtility.java?rev=1466455&r1=1466454&r2=1466455&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/PasswordUtility.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/PasswordUtility.java
Wed Apr 10 12:52:15 2013
@@ -17,10 +17,16 @@
package org.apache.jackrabbit.oak.spi.security.user.util;
import java.io.UnsupportedEncodingException;
+import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+
import javax.annotation.Nullable;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
@@ -39,6 +45,8 @@ public final class PasswordUtility {
private static final int NO_ITERATIONS = 1;
private static final String ENCODING = "UTF-8";
+ private static final String PBKDF2_PREFIX = "PBKDF2";
+
public static final String DEFAULT_ALGORITHM = "SHA-256";
public static final int DEFAULT_SALT_SIZE = 8;
public static final int DEFAULT_ITERATIONS = 1000;
@@ -171,7 +179,7 @@ public final class PasswordUtility {
//------------------------------------------------------------< private
>---
- private static String generateHash(String pwd, String algorithm, String
salt, int iterations) throws NoSuchAlgorithmException,
UnsupportedEncodingException {
+ public static String generateHash(String pwd, String algorithm, String
salt, int iterations) throws NoSuchAlgorithmException,
UnsupportedEncodingException {
StringBuilder passwordHash = new StringBuilder();
passwordHash.append('{').append(algorithm).append('}');
if (salt != null && !salt.isEmpty()) {
@@ -182,7 +190,13 @@ public final class PasswordUtility {
if (iterations > NO_ITERATIONS) {
passwordHash.append(iterations).append(DELIMITER);
}
- passwordHash.append(generateDigest(data.toString(), algorithm,
iterations));
+ String digest;
+ if (algorithm.startsWith(PBKDF2_PREFIX)) {
+ digest = generatePBKDF2(pwd, salt, algorithm, iterations, 128);
+ } else {
+ digest = generateDigest(data.toString(), algorithm,
iterations);
+ }
+ passwordHash.append(digest);
} else {
// backwards compatible to jr 2.0: no salt, no iterations
passwordHash.append(Text.digest(algorithm,
pwd.getBytes(ENCODING)));
@@ -195,13 +209,57 @@ public final class PasswordUtility {
byte[] salt = new byte[saltSize];
random.nextBytes(salt);
- StringBuilder res = new StringBuilder(salt.length * 2);
- for (byte b : salt) {
+ return convertBytesToHex(salt);
+ }
+
+ /**
+ * Convert a byte array to a hex encoded string.
+ *
+ * @param bytes the byte array
+ * @return the hex encoded string
+ */
+ public static String convertBytesToHex(byte[] bytes) {
+ StringBuilder res = new StringBuilder(bytes.length * 2);
+ for (byte b : bytes) {
res.append(Text.hexTable[(b >> 4) & 15]);
res.append(Text.hexTable[b & 15]);
}
return res.toString();
}
+
+ /**
+ * Convert a hex encoded string to a byte array.
+ *
+ * @param s the hex encoded string
+ * @return the byte array
+ */
+ public static byte[] convertHexToBytes(String s) {
+ int len = s.length();
+ if (len % 2 != 0) {
+ throw new IllegalArgumentException("Not a hex encoded byte array:
" + s);
+ }
+ byte[] bytes = new byte[len / 2];
+ for (int i = 0; i < bytes.length; i++) {
+ bytes[i] = (byte) (
+ (Character.digit(s.charAt(i + i), 16) << 4) +
+ Character.digit(s.charAt(i + i + 1), 16));
+ }
+ return bytes;
+ }
+
+ private static String generatePBKDF2(String pwd, String salt, String
algorithm, int iterations, int keyLength) throws NoSuchAlgorithmException {
+ // for example PBKDF2WithHmacSHA1
+ SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm);
+ byte[] saltBytes = convertHexToBytes(salt);
+ KeySpec keyspec = new PBEKeySpec(pwd.toCharArray(), saltBytes,
iterations, keyLength);
+ try {
+ Key key = factory.generateSecret(keyspec);
+ byte[] bytes = key.getEncoded();
+ return convertBytesToHex(bytes);
+ } catch (InvalidKeySpecException e) {
+ throw new NoSuchAlgorithmException(algorithm, e);
+ }
+ }
private static String generateDigest(String data, String algorithm, int
iterations) throws UnsupportedEncodingException, NoSuchAlgorithmException {
byte[] bytes = data.getBytes(ENCODING);
@@ -212,12 +270,7 @@ public final class PasswordUtility {
bytes = md.digest(bytes);
}
- StringBuilder res = new StringBuilder(bytes.length * 2);
- for (byte b : bytes) {
- res.append(Text.hexTable[(b >> 4) & 15]);
- res.append(Text.hexTable[b & 15]);
- }
- return res.toString();
+ return convertBytesToHex(bytes);
}
/**
Modified:
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/util/PasswordUtilityTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/util/PasswordUtilityTest.java?rev=1466455&r1=1466454&r2=1466455&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/util/PasswordUtilityTest.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/util/PasswordUtilityTest.java
Wed Apr 10 12:52:15 2013
@@ -25,6 +25,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -147,4 +148,15 @@ public class PasswordUtilityTest {
previous = pw;
}
}
+
+ @Test
+ public void testPBKDF2WithHmacSHA1() throws Exception {
+ String algo = "PBKDF2WithHmacSHA1";
+ // test vector from http://tools.ietf.org/html/rfc6070
+ String hash = PasswordUtility.generateHash(
+ "pass\0word", algo,
+ PasswordUtility.convertBytesToHex("sa\0lt".getBytes()), 4096);
+
assertEquals("{PBKDF2WithHmacSHA1}7361006c74-4096-56fa6aa75548099dcc37d7f03425e0c3",
hash);
+ }
+
}
\ No newline at end of file