]> WPIA git - gigi.git/blob - lib/scrypt/com/lambdaworks/crypto/SCryptUtil.java
6d794000d44bdb682a7a9fedc7dee918ceaf3632
[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
93             byte[] derived1 = SCrypt.scrypt(passwd.getBytes("UTF-8"), salt, N, r, p, 32);
94
95             if (derived0.length != derived1.length) {
96                 return false;
97             }
98
99             int result = 0;
100             for (int i = 0; i < derived0.length; i++) {
101                 result |= derived0[i] ^ derived1[i];
102             }
103             return result == 0;
104         } catch (UnsupportedEncodingException e) {
105             throw new IllegalStateException("JVM doesn't support UTF-8?");
106         } catch (GeneralSecurityException e) {
107             throw new IllegalStateException("JVM doesn't support SHA1PRNG or HMAC_SHA256?");
108         }
109     }
110
111     private static int log2(int n) {
112         int log = 0;
113         if ((n & 0xffff0000) != 0) {
114             n >>>= 16;
115             log = 16;
116         }
117         if (n >= 256) {
118             n >>>= 8;
119             log += 8;
120         }
121         if (n >= 16) {
122             n >>>= 4;
123             log += 4;
124         }
125         if (n >= 4) {
126             n >>>= 2;
127             log += 2;
128         }
129         return log + (n >>> 1);
130     }
131 }