1 // Copyright (C) 2011 - Will Glozer. All rights reserved.
3 package com.lambdaworks.crypto;
5 import java.io.UnsupportedEncodingException;
6 import java.security.GeneralSecurityException;
7 import java.security.SecureRandom;
8 import java.util.Base64;
11 * Simple {@link SCrypt} interface for hashing passwords using the <a
12 * href="http://www.tarsnap.com/scrypt.html">scrypt</a> key derivation function
13 * and comparing a plain text password to a hashed one. The hashed output is an
14 * extended implementation of the Modular Crypt Format that also includes the
15 * scrypt algorithm parameters. Format: <code>$s0$PARAMS$SALT$KEY</code>.
18 * <dt>32-bit hex integer containing log2(N) (16 bits), r (8 bits), and p (8
21 * <dt>base64-encoded salt</dt>
23 * <dt>base64-encoded derived key</dt>
25 * <code>s0</code> identifies version 0 of the scrypt format, using a 128-bit
26 * salt and 256-bit derived key.
30 public class SCryptUtil {
33 * Hash the supplied plaintext password and generate output in the format
34 * described in {@link SCryptUtil}.
41 * Memory cost parameter.
43 * Parallelization parameter.
44 * @return The hashed password.
46 public static String scrypt(String passwd, int N, int r, int p) {
48 byte[] salt = new byte[16];
49 SecureRandom.getInstance("SHA1PRNG").nextBytes(salt);
51 byte[] derived = SCrypt.scrypt(passwd.getBytes("UTF-8"), salt, N, r, p, 32);
53 String params = Long.toString(log2(N) << 16L | r << 8 | p, 16);
55 StringBuilder sb = new StringBuilder((salt.length + derived.length) * 2);
56 sb.append("$s0$").append(params).append('$');
57 sb.append(Base64.getEncoder().encodeToString(salt)).append('$');
58 sb.append(Base64.getEncoder().encodeToString(derived));
61 } catch (UnsupportedEncodingException e) {
62 throw new IllegalStateException("JVM doesn't support UTF-8?");
63 } catch (GeneralSecurityException e) {
64 throw new IllegalStateException("JVM doesn't support SHA1PRNG or HMAC_SHA256?");
69 * Compare the supplied plaintext password to a hashed password.
74 * scrypt hashed password.
75 * @return true if passwd matches hashed value.
77 public static boolean check(String passwd, String hashed) {
79 String[] parts = hashed.split("\\$");
81 if (parts.length != 5 || !parts[1].equals("s0")) {
82 throw new IllegalArgumentException("Invalid hashed value");
85 long params = Long.parseLong(parts[2], 16);
86 byte[] salt = Base64.getDecoder().decode(parts[3]);
87 byte[] derived0 = Base64.getDecoder().decode(parts[4]);
89 int N = (int) Math.pow(2, params >> 16 & 0xffff);
90 int r = (int) params >> 8 & 0xff;
91 int p = (int) params & 0xff;
92 if (r == 0 || p == 0) {
96 byte[] derived1 = SCrypt.scrypt(passwd.getBytes("UTF-8"), salt, N, r, p, 32);
98 if (derived0.length != derived1.length) {
103 for (int i = 0; i < derived0.length; i++) {
104 result |= derived0[i] ^ derived1[i];
107 } catch (UnsupportedEncodingException e) {
108 throw new IllegalStateException("JVM doesn't support UTF-8?");
109 } catch (GeneralSecurityException e) {
110 throw new IllegalStateException("JVM doesn't support SHA1PRNG or HMAC_SHA256?");
114 private static int log2(int n) {
116 if ((n & 0xffff0000) != 0) {
132 return log + (n >>> 1);