]> WPIA git - gigi.git/blob - lib/scrypt/com/lambdaworks/crypto/SCryptUtil.java
Merge remote-tracking branch 'origin/libs/scrypt/local'
[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 import java.util.Base64;
9
10 /**
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>.
16  * <dl>
17  * <dd>PARAMS</dd>
18  * <dt>32-bit hex integer containing log2(N) (16 bits), r (8 bits), and p (8
19  * bits)</dt>
20  * <dd>SALT</dd>
21  * <dt>base64-encoded salt</dt>
22  * <dd>KEY</dd>
23  * <dt>base64-encoded derived key</dt>
24  * </dl>
25  * <code>s0</code> identifies version 0 of the scrypt format, using a 128-bit
26  * salt and 256-bit derived key.
27  *
28  * @author Will Glozer
29  */
30 public class SCryptUtil {
31
32     /**
33      * Hash the supplied plaintext password and generate output in the format
34      * described in {@link SCryptUtil}.
35      *
36      * @param passwd
37      *            Password.
38      * @param N
39      *            CPU cost parameter.
40      * @param r
41      *            Memory cost parameter.
42      * @param p
43      *            Parallelization parameter.
44      * @return The hashed password.
45      */
46     public static String scrypt(String passwd, int N, int r, int p) {
47         try {
48             byte[] salt = new byte[16];
49             SecureRandom.getInstance("SHA1PRNG").nextBytes(salt);
50
51             byte[] derived = SCrypt.scrypt(passwd.getBytes("UTF-8"), salt, N, r, p, 32);
52
53             String params = Long.toString(log2(N) << 16L | r << 8 | p, 16);
54
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));
59
60             return sb.toString();
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?");
65         }
66     }
67
68     /**
69      * Compare the supplied plaintext password to a hashed password.
70      *
71      * @param passwd
72      *            Plaintext password.
73      * @param hashed
74      *            scrypt hashed password.
75      * @return true if passwd matches hashed value.
76      */
77     public static boolean check(String passwd, String hashed) {
78         try {
79             String[] parts = hashed.split("\\$");
80
81             if (parts.length != 5 || !parts[1].equals("s0")) {
82                 throw new IllegalArgumentException("Invalid hashed value");
83             }
84
85             long params = Long.parseLong(parts[2], 16);
86             byte[] salt = Base64.getDecoder().decode(parts[3]);
87             byte[] derived0 = Base64.getDecoder().decode(parts[4]);
88
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) {
93                 return false;
94             }
95
96             byte[] derived1 = SCrypt.scrypt(passwd.getBytes("UTF-8"), salt, N, r, p, 32);
97
98             if (derived0.length != derived1.length) {
99                 return false;
100             }
101
102             int result = 0;
103             for (int i = 0; i < derived0.length; i++) {
104                 result |= derived0[i] ^ derived1[i];
105             }
106             return result == 0;
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?");
111         }
112     }
113
114     private static int log2(int n) {
115         int log = 0;
116         if ((n & 0xffff0000) != 0) {
117             n >>>= 16;
118             log = 16;
119         }
120         if (n >= 256) {
121             n >>>= 8;
122             log += 8;
123         }
124         if (n >= 16) {
125             n >>>= 4;
126             log += 4;
127         }
128         if (n >= 4) {
129             n >>>= 2;
130             log += 2;
131         }
132         return log + (n >>> 1);
133     }
134 }