From 0179c436714d3b4abe63081438883ffc5037556c Mon Sep 17 00:00:00 2001 From: Jens Bannmann Date: Mon, 20 Jul 2015 09:49:41 +0200 Subject: [PATCH 1/2] SCryptUtil now accepts char[] passwords in addition to Strings - issue #31 --- .../com/lambdaworks/crypto/SCryptUtil.java | 71 ++++++++++++++++--- 1 file changed, 63 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/lambdaworks/crypto/SCryptUtil.java b/src/main/java/com/lambdaworks/crypto/SCryptUtil.java index ca29a00..a11bc78 100644 --- a/src/main/java/com/lambdaworks/crypto/SCryptUtil.java +++ b/src/main/java/com/lambdaworks/crypto/SCryptUtil.java @@ -2,9 +2,12 @@ package com.lambdaworks.crypto; -import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.SecureRandom; +import java.util.Arrays; import static com.lambdaworks.codec.Base64.*; @@ -40,11 +43,35 @@ public class SCryptUtil { * @return The hashed password. */ public static String scrypt(String passwd, int N, int r, int p) { + byte[] bytes = passwd.getBytes(StandardCharsets.UTF_8); + String result = scryptInternal(bytes, N, r, p); + wipeArray(bytes); + return result; + } + + /** + * 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(char[] passwd, int N, int r, int p) { + byte[] bytes = toBytes(passwd); + String result = scryptInternal(bytes, N, r, p); + wipeArray(bytes); + return result; + } + + private static String scryptInternal(byte[] passwordBytes, 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); + byte[] derived = SCrypt.scrypt(passwordBytes, salt, N, r, p, 32); String params = Long.toString(log2(N) << 16L | r << 8 | p, 16); @@ -54,22 +81,48 @@ public static String scrypt(String passwd, int N, int r, int p) { 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?"); } } + private static byte[] toBytes(char[] chars) { + CharBuffer charBuffer = CharBuffer.wrap(chars); + ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer); + byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit()); + wipeArray(byteBuffer.array()); + return bytes; + } + /** * 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) { + byte[] bytes = passwd.getBytes(StandardCharsets.UTF_8); + boolean result = checkInternal(bytes, hashed); + wipeArray(bytes); + return result; + } + + /** + * 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(char[] passwd, String hashed) { + byte[] bytes = toBytes(passwd); + boolean result = checkInternal(bytes, hashed); + wipeArray(bytes); + return result; + } + + private static boolean checkInternal(byte[] passwordBytes, String hashed) { try { String[] parts = hashed.split("\\$"); @@ -85,7 +138,7 @@ public static boolean check(String passwd, String hashed) { int r = (int) params >> 8 & 0xff; int p = (int) params & 0xff; - byte[] derived1 = SCrypt.scrypt(passwd.getBytes("UTF-8"), salt, N, r, p, 32); + byte[] derived1 = SCrypt.scrypt(passwordBytes, salt, N, r, p, 32); if (derived0.length != derived1.length) return false; @@ -94,8 +147,6 @@ public static boolean check(String passwd, String hashed) { 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?"); } @@ -109,4 +160,8 @@ private static int log2(int n) { if (n >= 4 ) { n >>>= 2; log += 2; } return log + (n >>> 1); } + + private static void wipeArray(byte[] array) { + Arrays.fill(array, (byte) 0); + } } From 12d73a38b2ed3c0c38cde98a84056b5b4d50f539 Mon Sep 17 00:00:00 2001 From: Jens Bannmann Date: Mon, 20 Jul 2015 10:04:39 +0200 Subject: [PATCH 2/2] Ensure arrays are wiped in any case --- .../com/lambdaworks/crypto/SCryptUtil.java | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/lambdaworks/crypto/SCryptUtil.java b/src/main/java/com/lambdaworks/crypto/SCryptUtil.java index a11bc78..101653e 100644 --- a/src/main/java/com/lambdaworks/crypto/SCryptUtil.java +++ b/src/main/java/com/lambdaworks/crypto/SCryptUtil.java @@ -44,9 +44,11 @@ public class SCryptUtil { */ public static String scrypt(String passwd, int N, int r, int p) { byte[] bytes = passwd.getBytes(StandardCharsets.UTF_8); - String result = scryptInternal(bytes, N, r, p); - wipeArray(bytes); - return result; + try { + return scryptInternal(bytes, N, r, p); + } finally { + wipeArray(bytes); + } } /** @@ -61,9 +63,11 @@ public static String scrypt(String passwd, int N, int r, int p) { */ public static String scrypt(char[] passwd, int N, int r, int p) { byte[] bytes = toBytes(passwd); - String result = scryptInternal(bytes, N, r, p); - wipeArray(bytes); - return result; + try { + return scryptInternal(bytes, N, r, p); + } finally { + wipeArray(bytes); + } } private static String scryptInternal(byte[] passwordBytes, int N, int r, int p) { @@ -89,9 +93,11 @@ private static String scryptInternal(byte[] passwordBytes, int N, int r, int p) private static byte[] toBytes(char[] chars) { CharBuffer charBuffer = CharBuffer.wrap(chars); ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer); - byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit()); - wipeArray(byteBuffer.array()); - return bytes; + try { + return Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit()); + } finally { + wipeArray(byteBuffer.array()); + } } /** @@ -103,9 +109,11 @@ private static byte[] toBytes(char[] chars) { */ public static boolean check(String passwd, String hashed) { byte[] bytes = passwd.getBytes(StandardCharsets.UTF_8); - boolean result = checkInternal(bytes, hashed); - wipeArray(bytes); - return result; + try { + return checkInternal(bytes, hashed); + } finally { + wipeArray(bytes); + } } /** @@ -117,9 +125,11 @@ public static boolean check(String passwd, String hashed) { */ public static boolean check(char[] passwd, String hashed) { byte[] bytes = toBytes(passwd); - boolean result = checkInternal(bytes, hashed); - wipeArray(bytes); - return result; + try { + return checkInternal(bytes, hashed); + } finally { + wipeArray(bytes); + } } private static boolean checkInternal(byte[] passwordBytes, String hashed) {