From 6802634e16cc05efaf672674a088481a884d74c2 Mon Sep 17 00:00:00 2001 From: Hungry Hobo Date: Sat, 18 May 2013 12:06:05 +0200 Subject: [PATCH 1/4] Add a method for calculating a value for N depending on CPU speed --- .../java/com/lambdaworks/crypto/SCrypt.java | 63 +++++++++++++++++++ .../crypto/test/SCryptUtilTest.java | 24 +++++++ 2 files changed, 87 insertions(+) diff --git a/src/main/java/com/lambdaworks/crypto/SCrypt.java b/src/main/java/com/lambdaworks/crypto/SCrypt.java index 6274238..3065b41 100644 --- a/src/main/java/com/lambdaworks/crypto/SCrypt.java +++ b/src/main/java/com/lambdaworks/crypto/SCrypt.java @@ -6,6 +6,8 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; import java.security.GeneralSecurityException; import static java.lang.Integer.MAX_VALUE; @@ -23,6 +25,12 @@ public class SCrypt { private static final boolean native_library_loaded; + // for timedIterations() + private static final byte[] BENCH_PASSWD = "secret".getBytes(); + private static final byte[] BENCH_SALT = "1234".getBytes(); + private static final int BENCH_DK_LEN = 32; + private static final int BENCH_INITIAL_N = 64; + static { LibraryLoader loader = LibraryLoaders.loader(); native_library_loaded = loader.load("scrypt", true); @@ -211,4 +219,59 @@ public static int integerify(byte[] B, int Bi, int r) { return n; } + + /** + * Determines a CPU cost value (i.e. a value for the N parameter) that will cause password + * verification to take (roughly) a given time on the current CPU for the specified + * r and p values.
+ * N is rounded to the nearest power of two because only powers of two are valid + * choices for N. The actual time spent will be between about .7*milliseconds + * and 1.4*milliseconds. + * + * @param milliseconds the time scrypt should spend verifying a password + * @param r memory cost parameter + * @param p parallelization parameter + * + * @return a value for N such that scrypt(N, r, p) runs for roughly milliseconds + * + * @throws GeneralSecurityException when HMAC_SHA256 is not available. + */ + public static int timedIterations(int milliseconds, int r, int p) throws GeneralSecurityException { + ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); + boolean cpuTimeSupported = threadBean.isCurrentThreadCpuTimeSupported(); + boolean origEnabledFlag = false; + if (cpuTimeSupported) { + origEnabledFlag = threadBean.isThreadCpuTimeEnabled(); + if (!origEnabledFlag) + threadBean.setThreadCpuTimeEnabled(true); + } + + int N = BENCH_INITIAL_N; + long lastDelta = 0; + while (true) { + // prefer CPU time over real world time so the result is load independent + long startTime = cpuTimeSupported ? threadBean.getCurrentThreadUserTime() : System.nanoTime(); + SCrypt.scrypt(BENCH_PASSWD, BENCH_SALT, N, r, p, BENCH_DK_LEN); + long endTime = cpuTimeSupported ? threadBean.getCurrentThreadUserTime() : System.nanoTime(); + long delta = (endTime-startTime) / 1000000; + + // start over if a speed increase is detected due to the code being JITted + if (delta < lastDelta) { + N = BENCH_INITIAL_N; + lastDelta = 0; + continue; + } + + if (delta > milliseconds) { + if (cpuTimeSupported) + threadBean.setThreadCpuTimeEnabled(origEnabledFlag); + // round to the nearest power of two + if (delta-delta/4 > milliseconds) + N /= 2; + return N; + } + N *= 2; + lastDelta = delta; + } + } } diff --git a/src/test/java/com/lambdaworks/crypto/test/SCryptUtilTest.java b/src/test/java/com/lambdaworks/crypto/test/SCryptUtilTest.java index f673657..78b302f 100644 --- a/src/test/java/com/lambdaworks/crypto/test/SCryptUtilTest.java +++ b/src/test/java/com/lambdaworks/crypto/test/SCryptUtilTest.java @@ -3,7 +3,12 @@ package com.lambdaworks.crypto.test; import com.lambdaworks.codec.Base64; +import com.lambdaworks.crypto.SCrypt; import com.lambdaworks.crypto.SCryptUtil; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; +import java.security.GeneralSecurityException; +import java.util.Random; import org.junit.Assert; import org.junit.Test; import static org.junit.Assert.*; @@ -56,4 +61,23 @@ public void format_0_rp_max() throws Exception { assertEquals(r, params >> 8 & 0xff); assertEquals(p, params >> 0 & 0xff); } + + @Test + public void testTimedIterations() throws GeneralSecurityException { + byte[] salt = "1234".getBytes(); + int dkLen = 32; + + ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); + boolean cpuTimeSupported = threadBean.isCurrentThreadCpuTimeSupported(); + Random random = new Random(); + for (int i=0; i<5; i++) { + int targetDuration = 100 + random.nextInt(900); + int numIterations = SCrypt.timedIterations(targetDuration, 8, 1); + long startTime = cpuTimeSupported ? threadBean.getCurrentThreadUserTime() : System.nanoTime(); + SCrypt.scrypt(passwd.getBytes(), salt, numIterations, 8, 1, dkLen); + long endTime = cpuTimeSupported ? threadBean.getCurrentThreadUserTime() : System.nanoTime(); + long actualDuration = (endTime-startTime) / 1000000; + assertTrue(actualDuration>targetDuration*5/10 && actualDuration Date: Sun, 26 May 2013 19:19:28 +0200 Subject: [PATCH 2/4] Move timedIterations() to SCryptUtil (thanks GrmpCerber) --- .../java/com/lambdaworks/crypto/SCrypt.java | 63 ------------------- .../com/lambdaworks/crypto/SCryptUtil.java | 63 +++++++++++++++++++ .../crypto/test/SCryptUtilTest.java | 2 +- 3 files changed, 64 insertions(+), 64 deletions(-) diff --git a/src/main/java/com/lambdaworks/crypto/SCrypt.java b/src/main/java/com/lambdaworks/crypto/SCrypt.java index 3065b41..6274238 100644 --- a/src/main/java/com/lambdaworks/crypto/SCrypt.java +++ b/src/main/java/com/lambdaworks/crypto/SCrypt.java @@ -6,8 +6,6 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; -import java.lang.management.ManagementFactory; -import java.lang.management.ThreadMXBean; import java.security.GeneralSecurityException; import static java.lang.Integer.MAX_VALUE; @@ -25,12 +23,6 @@ public class SCrypt { private static final boolean native_library_loaded; - // for timedIterations() - private static final byte[] BENCH_PASSWD = "secret".getBytes(); - private static final byte[] BENCH_SALT = "1234".getBytes(); - private static final int BENCH_DK_LEN = 32; - private static final int BENCH_INITIAL_N = 64; - static { LibraryLoader loader = LibraryLoaders.loader(); native_library_loaded = loader.load("scrypt", true); @@ -219,59 +211,4 @@ public static int integerify(byte[] B, int Bi, int r) { return n; } - - /** - * Determines a CPU cost value (i.e. a value for the N parameter) that will cause password - * verification to take (roughly) a given time on the current CPU for the specified - * r and p values.
- * N is rounded to the nearest power of two because only powers of two are valid - * choices for N. The actual time spent will be between about .7*milliseconds - * and 1.4*milliseconds. - * - * @param milliseconds the time scrypt should spend verifying a password - * @param r memory cost parameter - * @param p parallelization parameter - * - * @return a value for N such that scrypt(N, r, p) runs for roughly milliseconds - * - * @throws GeneralSecurityException when HMAC_SHA256 is not available. - */ - public static int timedIterations(int milliseconds, int r, int p) throws GeneralSecurityException { - ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); - boolean cpuTimeSupported = threadBean.isCurrentThreadCpuTimeSupported(); - boolean origEnabledFlag = false; - if (cpuTimeSupported) { - origEnabledFlag = threadBean.isThreadCpuTimeEnabled(); - if (!origEnabledFlag) - threadBean.setThreadCpuTimeEnabled(true); - } - - int N = BENCH_INITIAL_N; - long lastDelta = 0; - while (true) { - // prefer CPU time over real world time so the result is load independent - long startTime = cpuTimeSupported ? threadBean.getCurrentThreadUserTime() : System.nanoTime(); - SCrypt.scrypt(BENCH_PASSWD, BENCH_SALT, N, r, p, BENCH_DK_LEN); - long endTime = cpuTimeSupported ? threadBean.getCurrentThreadUserTime() : System.nanoTime(); - long delta = (endTime-startTime) / 1000000; - - // start over if a speed increase is detected due to the code being JITted - if (delta < lastDelta) { - N = BENCH_INITIAL_N; - lastDelta = 0; - continue; - } - - if (delta > milliseconds) { - if (cpuTimeSupported) - threadBean.setThreadCpuTimeEnabled(origEnabledFlag); - // round to the nearest power of two - if (delta-delta/4 > milliseconds) - N /= 2; - return N; - } - N *= 2; - lastDelta = delta; - } - } } diff --git a/src/main/java/com/lambdaworks/crypto/SCryptUtil.java b/src/main/java/com/lambdaworks/crypto/SCryptUtil.java index ca29a00..03c30d6 100644 --- a/src/main/java/com/lambdaworks/crypto/SCryptUtil.java +++ b/src/main/java/com/lambdaworks/crypto/SCryptUtil.java @@ -3,6 +3,8 @@ package com.lambdaworks.crypto; import java.io.UnsupportedEncodingException; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; import java.security.GeneralSecurityException; import java.security.SecureRandom; @@ -28,6 +30,12 @@ * @author Will Glozer */ public class SCryptUtil { + // for timedIterations() + private static final byte[] BENCH_PASSWD = "secret".getBytes(); + private static final byte[] BENCH_SALT = "1234".getBytes(); + private static final int BENCH_DK_LEN = 32; + private static final int BENCH_INITIAL_N = 64; + /** * Hash the supplied plaintext password and generate output in the format described * in {@link SCryptUtil}. @@ -109,4 +117,59 @@ private static int log2(int n) { if (n >= 4 ) { n >>>= 2; log += 2; } return log + (n >>> 1); } + + /** + * Determines a CPU cost value (i.e. a value for the N parameter) that will cause password + * verification to take (roughly) a given time on the current CPU for the specified + * r and p values.
+ * N is rounded to the nearest power of two because only powers of two are valid + * choices for N. The actual time spent will be between about .7*milliseconds + * and 1.4*milliseconds. + * + * @param milliseconds the time scrypt should spend verifying a password + * @param r memory cost parameter + * @param p parallelization parameter + * + * @return a value for N such that scrypt(N, r, p) runs for roughly milliseconds + * + * @throws GeneralSecurityException when HMAC_SHA256 is not available. + */ + public static int timedIterations(int milliseconds, int r, int p) throws GeneralSecurityException { + ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); + boolean cpuTimeSupported = threadBean.isCurrentThreadCpuTimeSupported(); + boolean origEnabledFlag = false; + if (cpuTimeSupported) { + origEnabledFlag = threadBean.isThreadCpuTimeEnabled(); + if (!origEnabledFlag) + threadBean.setThreadCpuTimeEnabled(true); + } + + int N = BENCH_INITIAL_N; + long lastDelta = 0; + while (true) { + // prefer CPU time over real world time so the result is load independent + long startTime = cpuTimeSupported ? threadBean.getCurrentThreadUserTime() : System.nanoTime(); + SCrypt.scrypt(BENCH_PASSWD, BENCH_SALT, N, r, p, BENCH_DK_LEN); + long endTime = cpuTimeSupported ? threadBean.getCurrentThreadUserTime() : System.nanoTime(); + long delta = (endTime-startTime) / 1000000; + + // start over if a speed increase is detected due to the code being JITted + if (delta < lastDelta) { + N = BENCH_INITIAL_N; + lastDelta = 0; + continue; + } + + if (delta > milliseconds) { + if (cpuTimeSupported) + threadBean.setThreadCpuTimeEnabled(origEnabledFlag); + // round to the nearest power of two + if (delta-delta/4 > milliseconds) + N /= 2; + return N; + } + N *= 2; + lastDelta = delta; + } + } } diff --git a/src/test/java/com/lambdaworks/crypto/test/SCryptUtilTest.java b/src/test/java/com/lambdaworks/crypto/test/SCryptUtilTest.java index 78b302f..24e53fe 100644 --- a/src/test/java/com/lambdaworks/crypto/test/SCryptUtilTest.java +++ b/src/test/java/com/lambdaworks/crypto/test/SCryptUtilTest.java @@ -72,7 +72,7 @@ public void testTimedIterations() throws GeneralSecurityException { Random random = new Random(); for (int i=0; i<5; i++) { int targetDuration = 100 + random.nextInt(900); - int numIterations = SCrypt.timedIterations(targetDuration, 8, 1); + int numIterations = SCryptUtil.timedIterations(targetDuration, 8, 1); long startTime = cpuTimeSupported ? threadBean.getCurrentThreadUserTime() : System.nanoTime(); SCrypt.scrypt(passwd.getBytes(), salt, numIterations, 8, 1, dkLen); long endTime = cpuTimeSupported ? threadBean.getCurrentThreadUserTime() : System.nanoTime(); From b2069dc71f278fd2704425964da4d5a175e10f4b Mon Sep 17 00:00:00 2001 From: Hungry Hobo Date: Sun, 26 May 2013 19:21:39 +0200 Subject: [PATCH 3/4] Refactor, improve comment --- .../java/com/lambdaworks/crypto/test/SCryptUtilTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/lambdaworks/crypto/test/SCryptUtilTest.java b/src/test/java/com/lambdaworks/crypto/test/SCryptUtilTest.java index 24e53fe..266eb9c 100644 --- a/src/test/java/com/lambdaworks/crypto/test/SCryptUtilTest.java +++ b/src/test/java/com/lambdaworks/crypto/test/SCryptUtilTest.java @@ -77,7 +77,10 @@ public void testTimedIterations() throws GeneralSecurityException { SCrypt.scrypt(passwd.getBytes(), salt, numIterations, 8, 1, dkLen); long endTime = cpuTimeSupported ? threadBean.getCurrentThreadUserTime() : System.nanoTime(); long actualDuration = (endTime-startTime) / 1000000; - assertTrue(actualDuration>targetDuration*5/10 && actualDuration targetDuration*0.5); + assertTrue(actualDuration < targetDuration*1.6); } } } From 5e3d9a65775084a6efd75e0ec339667a87ddb014 Mon Sep 17 00:00:00 2001 From: Hungry Hobo Date: Mon, 27 May 2013 23:18:23 +0200 Subject: [PATCH 4/4] Print test parameters on failure --- .../java/com/lambdaworks/crypto/test/SCryptUtilTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/lambdaworks/crypto/test/SCryptUtilTest.java b/src/test/java/com/lambdaworks/crypto/test/SCryptUtilTest.java index 266eb9c..c81723d 100644 --- a/src/test/java/com/lambdaworks/crypto/test/SCryptUtilTest.java +++ b/src/test/java/com/lambdaworks/crypto/test/SCryptUtilTest.java @@ -79,8 +79,9 @@ public void testTimedIterations() throws GeneralSecurityException { long actualDuration = (endTime-startTime) / 1000000; // check that actual duration is within targetDuration - 50% and targetDuration + 60% - assertTrue(actualDuration > targetDuration*0.5); - assertTrue(actualDuration < targetDuration*1.6); + String failMessage = "Target duration=" + targetDuration + ", actual=" + actualDuration; + assertTrue(failMessage, actualDuration > targetDuration*0.5); + assertTrue(failMessage, actualDuration < targetDuration*1.6); } } }