Sorcerer's IsleCode cfPassphrase / files

     1// Copyright (C) 2011 - Will Glozer.  All rights reserved.
     2
     3package com.lambdaworks.crypto;
     4
     5import java.io.UnsupportedEncodingException;
     6import java.security.GeneralSecurityException;
     7import java.security.SecureRandom;
     8
     9import static com.lambdaworks.codec.Base64.*;
    10
    11/**
    12 * Simple {@link SCrypt} interface for hashing passwords using the
    13 * <a href="http://www.tarsnap.com/scrypt.html">scrypt</a> key derivation function
    14 * and comparing a plain text password to a hashed one. The hashed output is an
    15 * extended implementation of the Modular Crypt Format that also includes the scrypt
    16 * algorithm parameters.
    17 *
    18 * Format: <code>$s0$PARAMS$SALT$KEY</code>.
    19 *
    20 * <dl>
    21 * <dd>PARAMS</dd><dt>32-bit hex integer containing log2(N) (16 bits), r (8 bits), and p (8 bits)</dt>
    22 * <dd>SALT</dd><dt>base64-encoded salt</dt>
    23 * <dd>KEY</dd><dt>base64-encoded derived key</dt>
    24 * </dl>
    25 *
    26 * <code>s0</code> identifies version 0 of the scrypt format, using a 128-bit salt and 256-bit derived key.
    27 *
    28 * @author  Will Glozer
    29 */
    30public class SCryptUtil {
    31    /**
    32     * Hash the supplied plaintext password and generate output in the format described
    33     * in {@link SCryptUtil}.
    34     *
    35     * @param passwd    Password.
    36     * @param N         CPU cost parameter.
    37     * @param r         Memory cost parameter.
    38     * @param p         Parallelization parameter.
    39     *
    40     * @return The hashed password.
    41     */
    42    public static String scrypt(String passwd, int N, int r, int p) {
    43        try {
    44            byte[] salt = new byte[16];
    45            SecureRandom.getInstance("SHA1PRNG").nextBytes(salt);
    46
    47            byte[] derived = SCrypt.scrypt(passwd.getBytes("UTF-8"), salt, N, r, p, 32);
    48
    49            String params = Long.toString(log2(N) << 16L | r << 8 | p, 16);
    50
    51            StringBuilder sb = new StringBuilder((salt.length + derived.length) * 2);
    52            sb.append("$s0$").append(params).append('$');
    53            sb.append(encode(salt)).append('$');
    54            sb.append(encode(derived));
    55
    56            return sb.toString();
    57        } catch (UnsupportedEncodingException e) {
    58            throw new IllegalStateException("JVM doesn't support UTF-8?");
    59        } catch (GeneralSecurityException e) {
    60            throw new IllegalStateException("JVM doesn't support SHA1PRNG or HMAC_SHA256?");
    61        }
    62    }
    63
    64    /**
    65     * Compare the supplied plaintext password to a hashed password.
    66     *
    67     * @param   passwd  Plaintext password.
    68     * @param   hashed  scrypt hashed password.
    69     *
    70     * @return true if passwd matches hashed value.
    71     */
    72    public static boolean check(String passwd, String hashed) {
    73        try {
    74            String[] parts = hashed.split("\\$");
    75
    76            if (parts.length != 5 || !parts[1].equals("s0")) {
    77                throw new IllegalArgumentException("Invalid hashed value");
    78            }
    79
    80            long params = Long.parseLong(parts[2], 16);
    81            byte[] salt = decode(parts[3].toCharArray());
    82            byte[] derived0 = decode(parts[4].toCharArray());
    83
    84            int N = (int) Math.pow(2, params >> 16 & 0xffff);
    85            int r = (int) params >> 8 & 0xff;
    86            int p = (int) params      & 0xff;
    87
    88            byte[] derived1 = SCrypt.scrypt(passwd.getBytes("UTF-8"), salt, N, r, p, 32);
    89
    90            if (derived0.length != derived1.length) return false;
    91
    92            int result = 0;
    93            for (int i = 0; i < derived0.length; i++) {
    94                result |= derived0[i] ^ derived1[i];
    95            }
    96            return result == 0;
    97        } catch (UnsupportedEncodingException e) {
    98            throw new IllegalStateException("JVM doesn't support UTF-8?");
    99        } catch (GeneralSecurityException e) {
   100            throw new IllegalStateException("JVM doesn't support SHA1PRNG or HMAC_SHA256?");
   101        }
   102    }
   103
   104    private static int log2(int n) {
   105        int log = 0;
   106        if ((n & 0xffff0000 ) != 0) { n >>>= 16; log = 16; }
   107        if (n >= 256) { n >>>= 8; log += 8; }
   108        if (n >= 16 ) { n >>>= 4; log += 4; }
   109        if (n >= 4  ) { n >>>= 2; log += 2; }
   110        return log + (n >>> 1);
   111    }
   112}