From 8cd6cfda6e99fd09acb1f0d158d57dbba7356cbc Mon Sep 17 00:00:00 2001 From: =?utf8?q?Felix=20D=C3=B6rre?= Date: Tue, 2 Dec 2014 02:13:21 +0100 Subject: [PATCH] add: import scrypt 1.4.0 --- lib/scrypt/com/lambdaworks/crypto/PBKDF.java | 87 +++++++ lib/scrypt/com/lambdaworks/crypto/SCrypt.java | 214 ++++++++++++++++++ .../com/lambdaworks/crypto/SCryptUtil.java | 112 +++++++++ .../crypto/test/CryptoTestUtil.java | 22 ++ .../lambdaworks/crypto/test/PBKDFTest.java | 141 ++++++++++++ .../lambdaworks/crypto/test/SCryptTest.java | 84 +++++++ .../crypto/test/SCryptUtilTest.java | 59 +++++ 7 files changed, 719 insertions(+) create mode 100644 lib/scrypt/com/lambdaworks/crypto/PBKDF.java create mode 100644 lib/scrypt/com/lambdaworks/crypto/SCrypt.java create mode 100644 lib/scrypt/com/lambdaworks/crypto/SCryptUtil.java create mode 100644 tests/com/lambdaworks/crypto/test/CryptoTestUtil.java create mode 100644 tests/com/lambdaworks/crypto/test/PBKDFTest.java create mode 100644 tests/com/lambdaworks/crypto/test/SCryptTest.java create mode 100644 tests/com/lambdaworks/crypto/test/SCryptUtilTest.java diff --git a/lib/scrypt/com/lambdaworks/crypto/PBKDF.java b/lib/scrypt/com/lambdaworks/crypto/PBKDF.java new file mode 100644 index 00000000..43dc2f42 --- /dev/null +++ b/lib/scrypt/com/lambdaworks/crypto/PBKDF.java @@ -0,0 +1,87 @@ +// Copyright (C) 2011 - Will Glozer. All rights reserved. + +package com.lambdaworks.crypto; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.security.GeneralSecurityException; +import static java.lang.System.arraycopy; + +/** + * An implementation of the Password-Based Key Derivation Function as specified + * in RFC 2898. + * + * @author Will Glozer + */ +public class PBKDF { + /** + * Implementation of PBKDF2 (RFC2898). + * + * @param alg HMAC algorithm to use. + * @param P Password. + * @param S Salt. + * @param c Iteration count. + * @param dkLen Intended length, in octets, of the derived key. + * + * @return The derived key. + * + * @throws GeneralSecurityException + */ + public static byte[] pbkdf2(String alg, byte[] P, byte[] S, int c, int dkLen) throws GeneralSecurityException { + Mac mac = Mac.getInstance(alg); + mac.init(new SecretKeySpec(P, alg)); + byte[] DK = new byte[dkLen]; + pbkdf2(mac, S, c, DK, dkLen); + return DK; + } + + /** + * Implementation of PBKDF2 (RFC2898). + * + * @param mac Pre-initialized {@link Mac} instance to use. + * @param S Salt. + * @param c Iteration count. + * @param DK Byte array that derived key will be placed in. + * @param dkLen Intended length, in octets, of the derived key. + * + * @throws GeneralSecurityException + */ + public static void pbkdf2(Mac mac, byte[] S, int c, byte[] DK, int dkLen) throws GeneralSecurityException { + int hLen = mac.getMacLength(); + + if (dkLen > (Math.pow(2, 32) - 1) * hLen) { + throw new GeneralSecurityException("Requested key length too long"); + } + + byte[] U = new byte[hLen]; + byte[] T = new byte[hLen]; + byte[] block1 = new byte[S.length + 4]; + + int l = (int) Math.ceil((double) dkLen / hLen); + int r = dkLen - (l - 1) * hLen; + + arraycopy(S, 0, block1, 0, S.length); + + for (int i = 1; i <= l; i++) { + block1[S.length + 0] = (byte) (i >> 24 & 0xff); + block1[S.length + 1] = (byte) (i >> 16 & 0xff); + block1[S.length + 2] = (byte) (i >> 8 & 0xff); + block1[S.length + 3] = (byte) (i >> 0 & 0xff); + + mac.update(block1); + mac.doFinal(U, 0); + arraycopy(U, 0, T, 0, hLen); + + for (int j = 1; j < c; j++) { + mac.update(U); + mac.doFinal(U, 0); + + for (int k = 0; k < hLen; k++) { + T[k] ^= U[k]; + } + } + + arraycopy(T, 0, DK, (i - 1) * hLen, (i == l ? r : hLen)); + } + } +} diff --git a/lib/scrypt/com/lambdaworks/crypto/SCrypt.java b/lib/scrypt/com/lambdaworks/crypto/SCrypt.java new file mode 100644 index 00000000..62742389 --- /dev/null +++ b/lib/scrypt/com/lambdaworks/crypto/SCrypt.java @@ -0,0 +1,214 @@ +// Copyright (C) 2011 - Will Glozer. All rights reserved. + +package com.lambdaworks.crypto; + +import com.lambdaworks.jni.*; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.security.GeneralSecurityException; + +import static java.lang.Integer.MAX_VALUE; +import static java.lang.System.arraycopy; + +/** + * An implementation of the scrypt + * key derivation function. This class will attempt to load a native library + * containing the optimized C implementation from + * http://www.tarsnap.com/scrypt.html and + * fall back to the pure Java version if that fails. + * + * @author Will Glozer + */ +public class SCrypt { + private static final boolean native_library_loaded; + + static { + LibraryLoader loader = LibraryLoaders.loader(); + native_library_loaded = loader.load("scrypt", true); + } + + /** + * Implementation of the scrypt KDF. + * Calls the native implementation {@link #scryptN} when the native library was successfully + * loaded, otherwise calls {@link #scryptJ}. + * + * @param passwd Password. + * @param salt Salt. + * @param N CPU cost parameter. + * @param r Memory cost parameter. + * @param p Parallelization parameter. + * @param dkLen Intended length of the derived key. + * + * @return The derived key. + * + * @throws GeneralSecurityException when HMAC_SHA256 is not available. + */ + public static byte[] scrypt(byte[] passwd, byte[] salt, int N, int r, int p, int dkLen) throws GeneralSecurityException { + return native_library_loaded ? scryptN(passwd, salt, N, r, p, dkLen) : scryptJ(passwd, salt, N, r, p, dkLen); + } + + /** + * Native C implementation of the scrypt KDF using + * the code from http://www.tarsnap.com/scrypt.html. + * + * @param passwd Password. + * @param salt Salt. + * @param N CPU cost parameter. + * @param r Memory cost parameter. + * @param p Parallelization parameter. + * @param dkLen Intended length of the derived key. + * + * @return The derived key. + */ + public static native byte[] scryptN(byte[] passwd, byte[] salt, int N, int r, int p, int dkLen); + + /** + * Pure Java implementation of the scrypt KDF. + * + * @param passwd Password. + * @param salt Salt. + * @param N CPU cost parameter. + * @param r Memory cost parameter. + * @param p Parallelization parameter. + * @param dkLen Intended length of the derived key. + * + * @return The derived key. + * + * @throws GeneralSecurityException when HMAC_SHA256 is not available. + */ + public static byte[] scryptJ(byte[] passwd, byte[] salt, int N, int r, int p, int dkLen) throws GeneralSecurityException { + if (N < 2 || (N & (N - 1)) != 0) throw new IllegalArgumentException("N must be a power of 2 greater than 1"); + + if (N > MAX_VALUE / 128 / r) throw new IllegalArgumentException("Parameter N is too large"); + if (r > MAX_VALUE / 128 / p) throw new IllegalArgumentException("Parameter r is too large"); + + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(passwd, "HmacSHA256")); + + byte[] DK = new byte[dkLen]; + + byte[] B = new byte[128 * r * p]; + byte[] XY = new byte[256 * r]; + byte[] V = new byte[128 * r * N]; + int i; + + PBKDF.pbkdf2(mac, salt, 1, B, p * 128 * r); + + for (i = 0; i < p; i++) { + smix(B, i * 128 * r, r, N, V, XY); + } + + PBKDF.pbkdf2(mac, B, 1, DK, dkLen); + + return DK; + } + + public static void smix(byte[] B, int Bi, int r, int N, byte[] V, byte[] XY) { + int Xi = 0; + int Yi = 128 * r; + int i; + + arraycopy(B, Bi, XY, Xi, 128 * r); + + for (i = 0; i < N; i++) { + arraycopy(XY, Xi, V, i * (128 * r), 128 * r); + blockmix_salsa8(XY, Xi, Yi, r); + } + + for (i = 0; i < N; i++) { + int j = integerify(XY, Xi, r) & (N - 1); + blockxor(V, j * (128 * r), XY, Xi, 128 * r); + blockmix_salsa8(XY, Xi, Yi, r); + } + + arraycopy(XY, Xi, B, Bi, 128 * r); + } + + public static void blockmix_salsa8(byte[] BY, int Bi, int Yi, int r) { + byte[] X = new byte[64]; + int i; + + arraycopy(BY, Bi + (2 * r - 1) * 64, X, 0, 64); + + for (i = 0; i < 2 * r; i++) { + blockxor(BY, i * 64, X, 0, 64); + salsa20_8(X); + arraycopy(X, 0, BY, Yi + (i * 64), 64); + } + + for (i = 0; i < r; i++) { + arraycopy(BY, Yi + (i * 2) * 64, BY, Bi + (i * 64), 64); + } + + for (i = 0; i < r; i++) { + arraycopy(BY, Yi + (i * 2 + 1) * 64, BY, Bi + (i + r) * 64, 64); + } + } + + public static int R(int a, int b) { + return (a << b) | (a >>> (32 - b)); + } + + public static void salsa20_8(byte[] B) { + int[] B32 = new int[16]; + int[] x = new int[16]; + int i; + + for (i = 0; i < 16; i++) { + B32[i] = (B[i * 4 + 0] & 0xff) << 0; + B32[i] |= (B[i * 4 + 1] & 0xff) << 8; + B32[i] |= (B[i * 4 + 2] & 0xff) << 16; + B32[i] |= (B[i * 4 + 3] & 0xff) << 24; + } + + arraycopy(B32, 0, x, 0, 16); + + for (i = 8; i > 0; i -= 2) { + x[ 4] ^= R(x[ 0]+x[12], 7); x[ 8] ^= R(x[ 4]+x[ 0], 9); + x[12] ^= R(x[ 8]+x[ 4],13); x[ 0] ^= R(x[12]+x[ 8],18); + x[ 9] ^= R(x[ 5]+x[ 1], 7); x[13] ^= R(x[ 9]+x[ 5], 9); + x[ 1] ^= R(x[13]+x[ 9],13); x[ 5] ^= R(x[ 1]+x[13],18); + x[14] ^= R(x[10]+x[ 6], 7); x[ 2] ^= R(x[14]+x[10], 9); + x[ 6] ^= R(x[ 2]+x[14],13); x[10] ^= R(x[ 6]+x[ 2],18); + x[ 3] ^= R(x[15]+x[11], 7); x[ 7] ^= R(x[ 3]+x[15], 9); + x[11] ^= R(x[ 7]+x[ 3],13); x[15] ^= R(x[11]+x[ 7],18); + x[ 1] ^= R(x[ 0]+x[ 3], 7); x[ 2] ^= R(x[ 1]+x[ 0], 9); + x[ 3] ^= R(x[ 2]+x[ 1],13); x[ 0] ^= R(x[ 3]+x[ 2],18); + x[ 6] ^= R(x[ 5]+x[ 4], 7); x[ 7] ^= R(x[ 6]+x[ 5], 9); + x[ 4] ^= R(x[ 7]+x[ 6],13); x[ 5] ^= R(x[ 4]+x[ 7],18); + x[11] ^= R(x[10]+x[ 9], 7); x[ 8] ^= R(x[11]+x[10], 9); + x[ 9] ^= R(x[ 8]+x[11],13); x[10] ^= R(x[ 9]+x[ 8],18); + x[12] ^= R(x[15]+x[14], 7); x[13] ^= R(x[12]+x[15], 9); + x[14] ^= R(x[13]+x[12],13); x[15] ^= R(x[14]+x[13],18); + } + + for (i = 0; i < 16; ++i) B32[i] = x[i] + B32[i]; + + for (i = 0; i < 16; i++) { + B[i * 4 + 0] = (byte) (B32[i] >> 0 & 0xff); + B[i * 4 + 1] = (byte) (B32[i] >> 8 & 0xff); + B[i * 4 + 2] = (byte) (B32[i] >> 16 & 0xff); + B[i * 4 + 3] = (byte) (B32[i] >> 24 & 0xff); + } + } + + public static void blockxor(byte[] S, int Si, byte[] D, int Di, int len) { + for (int i = 0; i < len; i++) { + D[Di + i] ^= S[Si + i]; + } + } + + public static int integerify(byte[] B, int Bi, int r) { + int n; + + Bi += (2 * r - 1) * 64; + + n = (B[Bi + 0] & 0xff) << 0; + n |= (B[Bi + 1] & 0xff) << 8; + n |= (B[Bi + 2] & 0xff) << 16; + n |= (B[Bi + 3] & 0xff) << 24; + + return n; + } +} diff --git a/lib/scrypt/com/lambdaworks/crypto/SCryptUtil.java b/lib/scrypt/com/lambdaworks/crypto/SCryptUtil.java new file mode 100644 index 00000000..ca29a00c --- /dev/null +++ b/lib/scrypt/com/lambdaworks/crypto/SCryptUtil.java @@ -0,0 +1,112 @@ +// Copyright (C) 2011 - Will Glozer. All rights reserved. + +package com.lambdaworks.crypto; + +import java.io.UnsupportedEncodingException; +import java.security.GeneralSecurityException; +import java.security.SecureRandom; + +import static com.lambdaworks.codec.Base64.*; + +/** + * Simple {@link SCrypt} interface for hashing passwords using the + * scrypt key derivation function + * and comparing a plain text password to a hashed one. The hashed output is an + * extended implementation of the Modular Crypt Format that also includes the scrypt + * algorithm parameters. + * + * Format: $s0$PARAMS$SALT$KEY. + * + *
+ *
PARAMS
32-bit hex integer containing log2(N) (16 bits), r (8 bits), and p (8 bits)
+ *
SALT
base64-encoded salt
+ *
KEY
base64-encoded derived key
+ *
+ * + * s0 identifies version 0 of the scrypt format, using a 128-bit salt and 256-bit derived key. + * + * @author Will Glozer + */ +public class SCryptUtil { + /** + * Hash the supplied plaintext password and generate output in the format described + * in {@link SCryptUtil}. + * + * @param passwd Password. + * @param N CPU cost parameter. + * @param r Memory cost parameter. + * @param p Parallelization parameter. + * + * @return The hashed password. + */ + public static String scrypt(String passwd, int N, int r, int p) { + try { + byte[] salt = new byte[16]; + SecureRandom.getInstance("SHA1PRNG").nextBytes(salt); + + byte[] derived = SCrypt.scrypt(passwd.getBytes("UTF-8"), salt, N, r, p, 32); + + String params = Long.toString(log2(N) << 16L | r << 8 | p, 16); + + StringBuilder sb = new StringBuilder((salt.length + derived.length) * 2); + sb.append("$s0$").append(params).append('$'); + sb.append(encode(salt)).append('$'); + sb.append(encode(derived)); + + return sb.toString(); + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException("JVM doesn't support UTF-8?"); + } catch (GeneralSecurityException e) { + throw new IllegalStateException("JVM doesn't support SHA1PRNG or HMAC_SHA256?"); + } + } + + /** + * Compare the supplied plaintext password to a hashed password. + * + * @param passwd Plaintext password. + * @param hashed scrypt hashed password. + * + * @return true if passwd matches hashed value. + */ + public static boolean check(String passwd, String hashed) { + try { + String[] parts = hashed.split("\\$"); + + if (parts.length != 5 || !parts[1].equals("s0")) { + throw new IllegalArgumentException("Invalid hashed value"); + } + + long params = Long.parseLong(parts[2], 16); + byte[] salt = decode(parts[3].toCharArray()); + byte[] derived0 = decode(parts[4].toCharArray()); + + int N = (int) Math.pow(2, params >> 16 & 0xffff); + int r = (int) params >> 8 & 0xff; + int p = (int) params & 0xff; + + byte[] derived1 = SCrypt.scrypt(passwd.getBytes("UTF-8"), salt, N, r, p, 32); + + if (derived0.length != derived1.length) return false; + + int result = 0; + for (int i = 0; i < derived0.length; i++) { + result |= derived0[i] ^ derived1[i]; + } + return result == 0; + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException("JVM doesn't support UTF-8?"); + } catch (GeneralSecurityException e) { + throw new IllegalStateException("JVM doesn't support SHA1PRNG or HMAC_SHA256?"); + } + } + + private static int log2(int n) { + int log = 0; + if ((n & 0xffff0000 ) != 0) { n >>>= 16; log = 16; } + if (n >= 256) { n >>>= 8; log += 8; } + if (n >= 16 ) { n >>>= 4; log += 4; } + if (n >= 4 ) { n >>>= 2; log += 2; } + return log + (n >>> 1); + } +} diff --git a/tests/com/lambdaworks/crypto/test/CryptoTestUtil.java b/tests/com/lambdaworks/crypto/test/CryptoTestUtil.java new file mode 100644 index 00000000..418c5f9e --- /dev/null +++ b/tests/com/lambdaworks/crypto/test/CryptoTestUtil.java @@ -0,0 +1,22 @@ +// Copyright (C) 2011 - Will Glozer. All rights reserved. + +package com.lambdaworks.crypto.test; + +public class CryptoTestUtil { + public static byte[] decode(String str) { + byte[] bytes = new byte[str.length() / 2]; + int index = 0; + + for (int i = 0; i < str.length(); i += 2) { + int high = hexValue(str.charAt(i)); + int low = hexValue(str.charAt(i + 1)); + bytes[index++] = (byte) ((high << 4) + low); + } + + return bytes; + } + + public static int hexValue(char c) { + return c >= 'a' ? c - 87 : c - 48; + } +} diff --git a/tests/com/lambdaworks/crypto/test/PBKDFTest.java b/tests/com/lambdaworks/crypto/test/PBKDFTest.java new file mode 100644 index 00000000..92a08b62 --- /dev/null +++ b/tests/com/lambdaworks/crypto/test/PBKDFTest.java @@ -0,0 +1,141 @@ +// Copyright (C) 2011 - Will Glozer. All rights reserved. + +package com.lambdaworks.crypto.test; + +import com.lambdaworks.crypto.PBKDF; +import org.junit.Test; + +import java.math.BigInteger; + +import static org.junit.Assert.*; +import static com.lambdaworks.crypto.test.CryptoTestUtil.*; + +public class PBKDFTest { + @Test + public void pbkdf2_hmac_sha1_rfc6070() throws Exception { + String alg = "HmacSHA1"; + byte[] P, S; + int c, dkLen; + String DK; + + P = "password".getBytes("UTF-8"); + S = "salt".getBytes("UTF-8"); + c = 1; + dkLen = 20; + DK = "0c60c80f961f0e71f3a9b524af6012062fe037a6"; + + assertArrayEquals(decode(DK), PBKDF.pbkdf2(alg, P, S, c, dkLen)); + + P = "password".getBytes("UTF-8"); + S = "salt".getBytes("UTF-8"); + c = 2; + dkLen = 20; + DK = "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"; + + assertArrayEquals(decode(DK), PBKDF.pbkdf2(alg, P, S, c, dkLen)); + + P = "password".getBytes("UTF-8"); + S = "salt".getBytes("UTF-8"); + c = 4096; + dkLen = 20; + DK = "4b007901b765489abead49d926f721d065a429c1"; + + assertArrayEquals(decode(DK), PBKDF.pbkdf2(alg, P, S, c, dkLen)); + + P = "password".getBytes("UTF-8"); + S = "salt".getBytes("UTF-8"); + c = 16777216; + dkLen = 20; + DK = "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"; + + assertArrayEquals(decode(DK), PBKDF.pbkdf2(alg, P, S, c, dkLen)); + + P = "passwordPASSWORDpassword".getBytes("UTF-8"); + S = "saltSALTsaltSALTsaltSALTsaltSALTsalt".getBytes("UTF-8"); + c = 4096; + dkLen = 25; + DK = "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"; + + assertArrayEquals(decode(DK), PBKDF.pbkdf2(alg, P, S, c, dkLen)); + + P = "pass\0word".getBytes("UTF-8"); + S = "sa\0lt".getBytes("UTF-8"); + c = 4096; + dkLen = 16; + DK = "56fa6aa75548099dcc37d7f03425e0c3"; + + assertArrayEquals(decode(DK), PBKDF.pbkdf2(alg, P, S, c, dkLen)); + } + + @Test + public void pbkdf2_hmac_sha1_rfc3962() throws Exception { + String alg = "HmacSHA1"; + byte[] P, S; + int c, dkLen; + String DK; + + P = "password".getBytes("UTF-8"); + S = "ATHENA.MIT.EDUraeburn".getBytes("UTF-8"); + c = 1; + + dkLen = 16; + DK = "cdedb5281bb2f801565a1122b2563515"; + assertArrayEquals(decode(DK), PBKDF.pbkdf2(alg, P, S, c, dkLen)); + + dkLen = 32; + DK = "cdedb5281bb2f801565a1122b25635150ad1f7a04bb9f3a333ecc0e2e1f70837"; + assertArrayEquals(decode(DK), PBKDF.pbkdf2(alg, P, S, c, dkLen)); + + P = "password".getBytes("UTF-8"); + S = "ATHENA.MIT.EDUraeburn".getBytes("UTF-8"); + c = 2; + + dkLen = 16; + DK = "01dbee7f4a9e243e988b62c73cda935d"; + assertArrayEquals(decode(DK), PBKDF.pbkdf2(alg, P, S, c, dkLen)); + + dkLen = 32; + DK = "01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86"; + assertArrayEquals(decode(DK), PBKDF.pbkdf2(alg, P, S, c, dkLen)); + + P = "password".getBytes("UTF-8"); + S = "ATHENA.MIT.EDUraeburn".getBytes("UTF-8"); + c = 1200; + + dkLen = 16; + DK = "5c08eb61fdf71e4e4ec3cf6ba1f5512b"; + assertArrayEquals(decode(DK), PBKDF.pbkdf2(alg, P, S, c, dkLen)); + + dkLen = 32; + DK = "5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13"; + assertArrayEquals(decode(DK), PBKDF.pbkdf2(alg, P, S, c, dkLen)); + + P = "password".getBytes("UTF-8"); + S = new BigInteger("1234567878563412", 16).toByteArray(); + c = 5; + + dkLen = 16; + DK = "d1daa78615f287e6a1c8b120d7062a49"; + assertArrayEquals(decode(DK), PBKDF.pbkdf2(alg, P, S, c, dkLen)); + + dkLen = 32; + DK = "d1daa78615f287e6a1c8b120d7062a493f98d203e6be49a6adf4fa574b6e64ee"; + assertArrayEquals(decode(DK), PBKDF.pbkdf2(alg, P, S, c, dkLen)); + } + + @Test + public void pbkdf2_hmac_sha256_scrypt() throws Exception { + String alg = "HmacSHA256"; + byte[] P, S; + int c, dkLen; + String DK; + + P = "password".getBytes("UTF-8"); + S = "salt".getBytes("UTF-8"); + c = 4096; + dkLen = 32; + DK = "c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a"; + + assertArrayEquals(decode(DK), PBKDF.pbkdf2(alg, P, S, c, dkLen)); + } +} diff --git a/tests/com/lambdaworks/crypto/test/SCryptTest.java b/tests/com/lambdaworks/crypto/test/SCryptTest.java new file mode 100644 index 00000000..bd34bb2e --- /dev/null +++ b/tests/com/lambdaworks/crypto/test/SCryptTest.java @@ -0,0 +1,84 @@ +// Copyright (C) 2011 - Will Glozer. All rights reserved. + +package com.lambdaworks.crypto.test; + +import com.lambdaworks.crypto.SCrypt; +import org.junit.Test; + +import static org.junit.Assert.*; +import static com.lambdaworks.crypto.test.CryptoTestUtil.*; +import static com.lambdaworks.crypto.SCrypt.*; + +public class SCryptTest { + @Test + public void scrypt_paper_appendix_b() throws Exception { + byte[] P, S; + int N, r, p, dkLen; + String DK; + + // empty key & salt test missing because unsupported by JCE + + P = "password".getBytes("UTF-8"); + S = "NaCl".getBytes("UTF-8"); + N = 1024; + r = 8; + p = 16; + dkLen = 64; + DK = "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640"; + + assertArrayEquals(decode(DK), SCrypt.scrypt(P, S, N, r, p, dkLen)); + + P = "pleaseletmein".getBytes("UTF-8"); + S = "SodiumChloride".getBytes("UTF-8"); + N = 16384; + r = 8; + p = 1; + dkLen = 64; + DK = "7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887"; + + assertArrayEquals(decode(DK), scrypt(P, S, N, r, p, dkLen)); + + P = "pleaseletmein".getBytes("UTF-8"); + S = "SodiumChloride".getBytes("UTF-8"); + N = 1048576; + r = 8; + p = 1; + dkLen = 64; + DK = "2101cb9b6a511aaeaddbbe09cf70f881ec568d574a2ffd4dabe5ee9820adaa478e56fd8f4ba5d09ffa1c6d927c40f4c337304049e8a952fbcbf45c6fa77a41a4"; + + assertArrayEquals(decode(DK), SCrypt.scrypt(P, S, N, r, p, dkLen)); + } + + @Test(expected = IllegalArgumentException.class) + public void scrypt_invalid_N_zero() throws Exception { + byte[] P = "pleaseletmein".getBytes("UTF-8"); + byte[] S = "SodiumChloride".getBytes("UTF-8"); + scrypt(P, S, 0, 1, 1, 64); + } + + @Test(expected = IllegalArgumentException.class) + public void scrypt_invalid_N_odd() throws Exception { + byte[] P = "pleaseletmein".getBytes("UTF-8"); + byte[] S = "SodiumChloride".getBytes("UTF-8"); + scrypt(P, S, 3, 1, 1, 64); + } + + @Test(expected = IllegalArgumentException.class) + public void scrypt_invalid_N_large() throws Exception { + byte[] P = "pleaseletmein".getBytes("UTF-8"); + byte[] S = "SodiumChloride".getBytes("UTF-8"); + int r = 8; + int N = Integer.MAX_VALUE / 128; + scrypt(P, S, N, r, 1, 64); + } + +// @Test(expected = IllegalArgumentException.class) +// public void scrypt_invalid_r_large() throws Exception { +// byte[] P = "pleaseletmein".getBytes("UTF-8"); +// byte[] S = "SodiumChloride".getBytes("UTF-8"); +// int N = 1024; +// int r = Integer.MAX_VALUE / 128 + 1; +// int p = 0; +// scrypt(P, S, N, r, p, 64); +// } +} diff --git a/tests/com/lambdaworks/crypto/test/SCryptUtilTest.java b/tests/com/lambdaworks/crypto/test/SCryptUtilTest.java new file mode 100644 index 00000000..f673657e --- /dev/null +++ b/tests/com/lambdaworks/crypto/test/SCryptUtilTest.java @@ -0,0 +1,59 @@ +// Copyright (C) 2011 - Will Glozer. All rights reserved. + +package com.lambdaworks.crypto.test; + +import com.lambdaworks.codec.Base64; +import com.lambdaworks.crypto.SCryptUtil; +import org.junit.Assert; +import org.junit.Test; +import static org.junit.Assert.*; + +public class SCryptUtilTest { + String passwd = "secret"; + + @Test + public void scrypt() { + int N = 16384; + int r = 8; + int p = 1; + + String hashed = SCryptUtil.scrypt(passwd, N, r, p); + String[] parts = hashed.split("\\$"); + + assertEquals(5, parts.length); + assertEquals("s0", parts[1]); + Assert.assertEquals(16, Base64.decode(parts[3].toCharArray()).length); + assertEquals(32, Base64.decode(parts[4].toCharArray()).length); + + int params = Integer.valueOf(parts[2], 16); + + assertEquals(N, (int) Math.pow(2, params >> 16 & 0xffff)); + assertEquals(r, params >> 8 & 0xff); + assertEquals(p, params >> 0 & 0xff); + } + + @Test + public void check() { + String hashed = SCryptUtil.scrypt(passwd, 16384, 8, 1); + + assertTrue(SCryptUtil.check(passwd, hashed)); + assertFalse(SCryptUtil.check("s3cr3t", hashed)); + } + + @Test + public void format_0_rp_max() throws Exception { + int N = 2; + int r = 255; + int p = 255; + + String hashed = SCryptUtil.scrypt(passwd, N, r, p); + assertTrue(SCryptUtil.check(passwd, hashed)); + + String[] parts = hashed.split("\\$"); + int params = Integer.valueOf(parts[2], 16); + + assertEquals(N, (int) Math.pow(2, params >>> 16 & 0xffff)); + assertEquals(r, params >> 8 & 0xff); + assertEquals(p, params >> 0 & 0xff); + } +} -- 2.39.2