]> WPIA git - gigi.git/blob - lib/scrypt/com/lambdaworks/crypto/SCryptUtil.java
add: import scrypt 1.4.0
[gigi.git] / lib / scrypt / com / lambdaworks / crypto / SCryptUtil.java
1 // Copyright (C) 2011 - Will Glozer.  All rights reserved.
2
3 package com.lambdaworks.crypto;
4
5 import java.io.UnsupportedEncodingException;
6 import java.security.GeneralSecurityException;
7 import java.security.SecureRandom;
8
9 import 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  */
30 public 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 }