4 changed files with 393 additions and 105 deletions
@ -1,105 +1,118 @@
@@ -1,105 +1,118 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
<groupId>org.springframework.security</groupId> |
||||
<artifactId>spring-security-crypto</artifactId> |
||||
<version>4.0.3.CI-SNAPSHOT</version> |
||||
<name>spring-security-crypto</name> |
||||
<description>spring-security-crypto</description> |
||||
<url>http://spring.io/spring-security</url> |
||||
<organization> |
||||
<name>spring.io</name> |
||||
<url>http://spring.io/</url> |
||||
</organization> |
||||
<licenses> |
||||
<license> |
||||
<name>The Apache Software License, Version 2.0</name> |
||||
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> |
||||
<distribution>repo</distribution> |
||||
</license> |
||||
</licenses> |
||||
<developers> |
||||
<developer> |
||||
<id>rwinch</id> |
||||
<name>Rob Winch</name> |
||||
<email>rwinch@gopivotal.com</email> |
||||
</developer> |
||||
</developers> |
||||
<scm> |
||||
<connection>scm:git:git://github.com/spring-projects/spring-security</connection> |
||||
<developerConnection>scm:git:git://github.com/spring-projects/spring-security</developerConnection> |
||||
<url>https://github.com/spring-projects/spring-security</url> |
||||
</scm> |
||||
<repositories> |
||||
<repository> |
||||
<id>spring-snapshot</id> |
||||
<url>https://repo.spring.io/snapshot</url> |
||||
</repository> |
||||
</repositories> |
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>commons-logging</groupId> |
||||
<artifactId>commons-logging</artifactId> |
||||
<version>1.2</version> |
||||
<scope>compile</scope> |
||||
<optional>true</optional> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>ch.qos.logback</groupId> |
||||
<artifactId>logback-classic</artifactId> |
||||
<version>1.1.2</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>junit</groupId> |
||||
<artifactId>junit</artifactId> |
||||
<version>4.11</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.easytesting</groupId> |
||||
<artifactId>fest-assert</artifactId> |
||||
<version>1.4</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.mockito</groupId> |
||||
<artifactId>mockito-core</artifactId> |
||||
<version>1.10.19</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.slf4j</groupId> |
||||
<artifactId>jcl-over-slf4j</artifactId> |
||||
<version>1.7.7</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springframework</groupId> |
||||
<artifactId>spring-test</artifactId> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
<dependencyManagement> |
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>org.springframework</groupId> |
||||
<artifactId>spring-framework-bom</artifactId> |
||||
<version>4.1.6.RELEASE</version> |
||||
<type>pom</type> |
||||
<scope>import</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
</dependencyManagement> |
||||
<build> |
||||
<plugins> |
||||
<plugin> |
||||
<artifactId>maven-compiler-plugin</artifactId> |
||||
<configuration> |
||||
<source>1.7</source> |
||||
<target>1.7</target> |
||||
</configuration> |
||||
</plugin> |
||||
</plugins> |
||||
</build> |
||||
</project> |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
<groupId>org.springframework.security</groupId> |
||||
<artifactId>spring-security-crypto</artifactId> |
||||
<version>4.1.0.BUILD-SNAPSHOT</version> |
||||
<name>spring-security-crypto</name> |
||||
<description>spring-security-crypto</description> |
||||
<url>http://spring.io/spring-security</url> |
||||
<organization> |
||||
<name>spring.io</name> |
||||
<url>http://spring.io/</url> |
||||
</organization> |
||||
<licenses> |
||||
<license> |
||||
<name>The Apache Software License, Version 2.0</name> |
||||
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> |
||||
<distribution>repo</distribution> |
||||
</license> |
||||
</licenses> |
||||
<developers> |
||||
<developer> |
||||
<id>rwinch</id> |
||||
<name>Rob Winch</name> |
||||
<email>rwinch@gopivotal.com</email> |
||||
</developer> |
||||
</developers> |
||||
<scm> |
||||
<connection>scm:git:git://github.com/spring-projects/spring-security</connection> |
||||
<developerConnection>scm:git:git://github.com/spring-projects/spring-security</developerConnection> |
||||
<url>https://github.com/spring-projects/spring-security</url> |
||||
</scm> |
||||
<dependencyManagement> |
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>org.springframework</groupId> |
||||
<artifactId>spring-framework-bom</artifactId> |
||||
<version>4.2.5.RELEASE</version> |
||||
<type>pom</type> |
||||
<scope>import</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
</dependencyManagement> |
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>commons-logging</groupId> |
||||
<artifactId>commons-logging</artifactId> |
||||
<version>1.2</version> |
||||
<scope>compile</scope> |
||||
<optional>true</optional> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.bouncycastle</groupId> |
||||
<artifactId>bcprov-jdk15on</artifactId> |
||||
<version>1.54</version> |
||||
<scope>compile</scope> |
||||
<optional>true</optional> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>ch.qos.logback</groupId> |
||||
<artifactId>logback-classic</artifactId> |
||||
<version>1.1.2</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>junit</groupId> |
||||
<artifactId>junit</artifactId> |
||||
<version>4.12</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.assertj</groupId> |
||||
<artifactId>assertj-core</artifactId> |
||||
<version>3.3.0</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.easytesting</groupId> |
||||
<artifactId>fest-assert</artifactId> |
||||
<version>1.4</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.mockito</groupId> |
||||
<artifactId>mockito-core</artifactId> |
||||
<version>1.10.19</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.slf4j</groupId> |
||||
<artifactId>jcl-over-slf4j</artifactId> |
||||
<version>1.7.7</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springframework</groupId> |
||||
<artifactId>spring-test</artifactId> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
<repositories> |
||||
<repository> |
||||
<id>spring-snapshot</id> |
||||
<url>https://repo.spring.io/snapshot</url> |
||||
</repository> |
||||
</repositories> |
||||
<build> |
||||
<plugins> |
||||
<plugin> |
||||
<artifactId>maven-compiler-plugin</artifactId> |
||||
<configuration> |
||||
<source>1.7</source> |
||||
<target>1.7</target> |
||||
</configuration> |
||||
</plugin> |
||||
</plugins> |
||||
</build> |
||||
</project> |
||||
|
||||
@ -0,0 +1,150 @@
@@ -0,0 +1,150 @@
|
||||
/* |
||||
* Copyright 2002-2012 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.scrypt; |
||||
|
||||
import java.util.Base64; |
||||
import java.util.Base64.Decoder; |
||||
import java.util.Base64.Encoder; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import org.springframework.security.crypto.codec.Utf8; |
||||
import org.springframework.security.crypto.keygen.BytesKeyGenerator; |
||||
import org.springframework.security.crypto.keygen.KeyGenerators; |
||||
import org.springframework.security.crypto.password.PasswordEncoder; |
||||
import org.bouncycastle.crypto.generators.SCrypt; |
||||
|
||||
|
||||
|
||||
/** |
||||
* Implementation of PasswordEncoder that uses the SCrypt hashing function. Clients |
||||
* can optionally supply a cpu cost parameter, a memory cost parameter and a parallelization parameter. |
||||
* |
||||
* @author Shazin Sadakath |
||||
* |
||||
*/ |
||||
public class SCryptPasswordEncoder implements PasswordEncoder { |
||||
|
||||
private final Log logger = LogFactory.getLog(getClass()); |
||||
|
||||
private final int cpuCost; |
||||
|
||||
private final int memoryCost; |
||||
|
||||
private final int parallelization; |
||||
|
||||
private final int keyLength; |
||||
|
||||
private final BytesKeyGenerator saltGenerator; |
||||
|
||||
public SCryptPasswordEncoder() { |
||||
this(16384, 8, 1, 32, 64); |
||||
} |
||||
|
||||
/** |
||||
* @param cpu cost of the algorithm. must be power of 2 greater than 1 |
||||
* @param memory cost of the algorithm |
||||
* @param parallelization of the algorithm |
||||
* @param key length for the algorithm |
||||
* @param salt length |
||||
*/ |
||||
public SCryptPasswordEncoder(int cpuCost, int memoryCost, int parallelization, int keyLength, int saltLength) { |
||||
if (cpuCost <= 1) { |
||||
throw new IllegalArgumentException("Cpu cost parameter must be > 1."); |
||||
} |
||||
if (memoryCost == 1 && cpuCost > 65536) { |
||||
throw new IllegalArgumentException("Cpu cost parameter must be > 1 and < 65536."); |
||||
} |
||||
if (memoryCost < 1) { |
||||
throw new IllegalArgumentException("Memory cost must be >= 1."); |
||||
} |
||||
int maxParallel = Integer.MAX_VALUE / (128 * memoryCost * 8); |
||||
if (parallelization < 1 || parallelization > maxParallel) { |
||||
throw new IllegalArgumentException("Parallelisation parameter p must be >= 1 and <= " + maxParallel |
||||
+ " (based on block size r of " + memoryCost + ")"); |
||||
} |
||||
if (keyLength < 1 || keyLength > Integer.MAX_VALUE) { |
||||
throw new IllegalArgumentException("Key length must be >= 1 and <= "+Integer.MAX_VALUE); |
||||
} |
||||
if (saltLength < 1 || saltLength > Integer.MAX_VALUE) { |
||||
throw new IllegalArgumentException("Salt length must be >= 1 and <= "+Integer.MAX_VALUE); |
||||
} |
||||
|
||||
this.cpuCost = cpuCost; |
||||
this.memoryCost = memoryCost; |
||||
this.parallelization = parallelization; |
||||
this.keyLength = keyLength; |
||||
this.saltGenerator = KeyGenerators.secureRandom(saltLength); |
||||
} |
||||
|
||||
@Override |
||||
public String encode(CharSequence rawPassword) { |
||||
return digest(rawPassword, saltGenerator.generateKey()); |
||||
} |
||||
|
||||
@Override |
||||
public boolean matches(CharSequence rawPassword, String encodedPassword) { |
||||
if(encodedPassword == null || encodedPassword.length() < keyLength) { |
||||
logger.warn("Empty encoded password"); |
||||
return false; |
||||
} |
||||
return decodeAndCheckMatches(rawPassword, encodedPassword); |
||||
} |
||||
|
||||
private boolean decodeAndCheckMatches(CharSequence rawPassword, String encodedPassword) { |
||||
String[] parts = encodedPassword.split("\\$"); |
||||
|
||||
if (parts.length != 4) { |
||||
return false; |
||||
} |
||||
|
||||
Decoder decoder = Base64.getDecoder(); |
||||
long params = Long.parseLong(parts[1], 16); |
||||
byte[] salt = decoder.decode(parts[2]); |
||||
byte[] derived = decoder.decode(parts[3]); |
||||
|
||||
int cpuCost = (int) Math.pow(2, params >> 16 & 0xffff); |
||||
int memoryCost = (int) params >> 8 & 0xff; |
||||
int parallelization = (int) params & 0xff; |
||||
|
||||
byte[] generated = SCrypt.generate(Utf8.encode(rawPassword), salt, cpuCost, memoryCost, parallelization, keyLength); |
||||
|
||||
if (derived.length != generated.length) { |
||||
return false; |
||||
} |
||||
|
||||
int result = 0; |
||||
for (int i = 0; i < derived.length; i++) { |
||||
result |= derived[i] ^ generated[i]; |
||||
} |
||||
return result == 0; |
||||
} |
||||
|
||||
private String digest(CharSequence rawPassword, byte[] salt) { |
||||
byte[] derived = SCrypt.generate(Utf8.encode(rawPassword), salt, cpuCost, memoryCost, parallelization, 32); |
||||
|
||||
String params = Long.toString(((int) (Math.log(cpuCost) / Math.log(2)) << 16L) | memoryCost << 8 | parallelization, 16); |
||||
Encoder encoder = Base64.getEncoder(); |
||||
|
||||
StringBuilder sb = new StringBuilder((salt.length + derived.length) * 2); |
||||
sb.append("$").append(params).append('$'); |
||||
sb.append(encoder.encodeToString(salt)).append('$'); |
||||
sb.append(encoder.encodeToString(derived)); |
||||
|
||||
return sb.toString(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,120 @@
@@ -0,0 +1,120 @@
|
||||
/* |
||||
* Copyright 2002-2012 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.scrypt; |
||||
|
||||
import static org.assertj.core.api.Assertions.*; |
||||
|
||||
import org.junit.Test; |
||||
|
||||
/** |
||||
* @author Shazin Sadakath |
||||
* |
||||
*/ |
||||
public class SCryptPasswordEncoderTests { |
||||
|
||||
@Test |
||||
public void matches() { |
||||
SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(); |
||||
String result = encoder.encode("password"); |
||||
assertThat(result).isNotEqualTo("password"); |
||||
assertThat(encoder.matches("password", result)).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
public void unicode() { |
||||
SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(); |
||||
String result = encoder.encode("passw\u9292rd"); |
||||
assertThat(encoder.matches("pass\u9292\u9292rd", result)).isFalse(); |
||||
assertThat(encoder.matches("passw\u9292rd", result)).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
public void notMatches() { |
||||
SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(); |
||||
String result = encoder.encode("password"); |
||||
assertThat(encoder.matches("bogus", result)).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
public void customParameters() { |
||||
SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(512, 8, 4, 32, 16); |
||||
String result = encoder.encode("password"); |
||||
assertThat(result).isNotEqualTo("password"); |
||||
assertThat(encoder.matches("password", result)).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
public void differentPasswordHashes() { |
||||
SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(); |
||||
String password = "secret"; |
||||
assertThat(encoder.encode(password)).isNotEqualTo(encoder.encode(password)); |
||||
} |
||||
|
||||
@Test |
||||
public void samePasswordWithDifferentParams() { |
||||
SCryptPasswordEncoder oldEncoder = new SCryptPasswordEncoder(512, 8, 4, 64, 16); |
||||
SCryptPasswordEncoder newEncoder = new SCryptPasswordEncoder(); |
||||
|
||||
String password = "secret"; |
||||
String oldEncodedPassword = oldEncoder.encode(password); |
||||
assertThat(newEncoder.matches(password, oldEncodedPassword)).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
public void doesntMatchNullEncodedValue() { |
||||
SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(); |
||||
assertThat(encoder.matches("password", null)).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
public void doesntMatchEmptyEncodedValue() { |
||||
SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(); |
||||
assertThat(encoder.matches("password", "")).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
public void doesntMatchBogusEncodedValue() { |
||||
SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(); |
||||
assertThat(encoder.matches("password", "012345678901234567890123456789")).isFalse(); |
||||
} |
||||
|
||||
@Test(expected = IllegalArgumentException.class) |
||||
public void invalidCpuCostParameter() { |
||||
new SCryptPasswordEncoder(Integer.MIN_VALUE, 16, 2, 32, 16); |
||||
} |
||||
|
||||
@Test(expected = IllegalArgumentException.class) |
||||
public void invalidMemoryCostParameter() { |
||||
new SCryptPasswordEncoder(2, Integer.MAX_VALUE, 2, 32, 16); |
||||
} |
||||
|
||||
@Test(expected = IllegalArgumentException.class) |
||||
public void invalidParallelizationParameter() { |
||||
new SCryptPasswordEncoder(2, 8, Integer.MAX_VALUE, 32, 16); |
||||
} |
||||
|
||||
@Test(expected = IllegalArgumentException.class) |
||||
public void invalidSaltLengthParameter() { |
||||
new SCryptPasswordEncoder(2, 8, 1, 16, -1); |
||||
} |
||||
|
||||
@Test(expected = IllegalArgumentException.class) |
||||
public void invalidKeyLengthParameter() { |
||||
new SCryptPasswordEncoder(2, 8, 1, -1, 16); |
||||
} |
||||
|
||||
} |
||||
|
||||
Loading…
Reference in new issue