فهرست منبع

Added PBKDF2PasswordEncoder.

 - Also moved some logic into a new class, AbstractPasswordEncoder.
Both PBKDF2PasswordEncoder and the now-simplified
StandardPasswordEncoder extend AbstractPasswordEncoder.
 - Added tests for PBKDF2PasswordEncoder

Issue gh-2158
Rob Worsnop 12 سال پیش
والد
کامیت
0ab7126e64

+ 72 - 0
crypto/src/main/java/org/springframework/security/crypto/password/AbstractPasswordEncoder.java

@@ -0,0 +1,72 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.springframework.security.crypto.password;
+
+import org.springframework.security.crypto.codec.Hex;
+import org.springframework.security.crypto.keygen.BytesKeyGenerator;
+import org.springframework.security.crypto.keygen.KeyGenerators;
+
+import static org.springframework.security.crypto.util.EncodingUtils.concatenate;
+import static org.springframework.security.crypto.util.EncodingUtils.subArray;
+
+/**
+ * Abstract base class for password encoders
+ *
+ * @author Rob Worsnop
+ */
+public abstract class AbstractPasswordEncoder implements PasswordEncoder {
+
+	private final BytesKeyGenerator saltGenerator;
+
+	protected AbstractPasswordEncoder() {
+		this.saltGenerator = KeyGenerators.secureRandom();
+	}
+
+	@Override
+	public String encode(CharSequence rawPassword) {
+		byte[] salt = this.saltGenerator.generateKey();
+		byte[] encoded = encodeAndConcatenate(rawPassword, salt);
+		return String.valueOf(Hex.encode(encoded));
+	}
+
+	@Override
+	public boolean matches(CharSequence rawPassword, String encodedPassword) {
+		byte[] digested = Hex.decode(encodedPassword);
+		byte[] salt = subArray(digested, 0, this.saltGenerator.getKeyLength());
+		return matches(digested, encodeAndConcatenate(rawPassword, salt));
+	}
+
+	protected abstract byte[] encode(CharSequence rawPassword, byte[] salt);
+
+	protected byte[] encodeAndConcatenate(CharSequence rawPassword, byte[] salt) {
+		return concatenate(salt, encode(rawPassword, salt));
+	}
+
+	/**
+	 * Constant time comparison to prevent against timing attacks.
+	 */
+	protected static boolean matches(byte[] expected, byte[] actual) {
+		if (expected.length != actual.length) {
+			return false;
+		}
+
+		int result = 0;
+		for (int i = 0; i < expected.length; i++) {
+			result |= expected[i] ^ actual[i];
+		}
+		return result == 0;
+	}
+}

+ 90 - 0
crypto/src/main/java/org/springframework/security/crypto/password/PBKDF2PasswordEncoder.java

@@ -0,0 +1,90 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.springframework.security.crypto.password;
+
+import java.security.GeneralSecurityException;
+
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+import org.springframework.security.crypto.codec.Utf8;
+
+import static org.springframework.security.crypto.util.EncodingUtils.concatenate;
+
+/**
+ * A {@code PasswordEncoder} implementation that uses PBKDF2 with a configurable number of
+ * iterations and a random 8-byte random salt value.
+ * <p>
+ * The width of the output hash can also be configured.
+ * <p>
+ * The algorithm is invoked on the concatenated bytes of the salt, secret and password.
+ *
+ * @author Rob Worsnop
+ */
+public class PBKDF2PasswordEncoder extends AbstractPasswordEncoder {
+	private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";
+	private static final int DEFAULT_HASH_WIDTH = 160;
+	private static final int DEFAULT_ITERATIONS = 1024;
+
+	private final byte[] secret;
+	private final int hashWidth;
+	private final int iterations;
+
+	/**
+	 * Constructs a PBKDF2 password encoder with no additional secret value. There will be
+	 * 1024 iterations and a hash width of 160.
+	 */
+	public PBKDF2PasswordEncoder() {
+		this("");
+	}
+
+	/**
+	 * Constructs a standard password encoder with a secret value which is also included
+	 * in the password hash. There will be 1024 iterations and a hash width of 160.
+	 *
+	 * @param secret the secret key used in the encoding process (should not be shared)
+	 */
+	public PBKDF2PasswordEncoder(CharSequence secret) {
+		this(secret, DEFAULT_ITERATIONS, DEFAULT_HASH_WIDTH);
+	}
+
+	/**
+	 * Constructs a standard password encoder with a secret value as well as iterations
+	 * and hash.
+	 *
+	 * @param secret
+	 * @param iterations
+	 * @param hashWidth
+	 */
+	public PBKDF2PasswordEncoder(CharSequence secret, int iterations, int hashWidth) {
+		this.secret = Utf8.encode(secret);
+		this.iterations = iterations;
+		this.hashWidth = hashWidth;
+	}
+
+	@Override
+	protected byte[] encode(CharSequence rawPassword, byte[] salt) {
+		try {
+			PBEKeySpec spec = new PBEKeySpec(rawPassword.toString().toCharArray(),
+					concatenate(salt, this.secret), this.iterations, this.hashWidth);
+			SecretKeyFactory skf = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
+			return concatenate(salt, skf.generateSecret(spec).getEncoded());
+		}
+		catch (GeneralSecurityException e) {
+			throw new IllegalStateException("Could not create hash", e);
+		}
+	}
+}

+ 31 - 0
crypto/src/test/java/org/springframework/security/crypto/password/PBKDF2PasswordEncoderTests.java

@@ -0,0 +1,31 @@
+package org.springframework.security.crypto.password;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+
+public class PBKDF2PasswordEncoderTests {
+    private PBKDF2PasswordEncoder encoder = new PBKDF2PasswordEncoder("secret");
+
+    @Test
+    public void matches() {
+        String result = encoder.encode("password");
+        assertFalse(result.equals("password"));
+        assertTrue(encoder.matches("password", result));
+    }
+
+    @Test
+    public void matchesLengthChecked() {
+        String result = encoder.encode("password");
+        assertFalse(encoder.matches("password", result.substring(0,result.length()-2)));
+    }
+
+    @Test
+    public void notMatches() {
+        String result = encoder.encode("password");
+        assertFalse(encoder.matches("bogus", result));
+    }
+
+}