1 | /* |
2 | Sha512Crypt.java |
3 | |
4 | Created: 18 December 2007 |
5 | Last Changed By: $Author: broccol $ |
6 | Version: $Revision: 7692 $ |
7 | Last Mod Date: $Date: 2007-12-30 01:55:31 -0600 (Sun, 30 Dec 2007) $ |
8 | |
9 | Java Port By: James Ratcliff, falazar@arlut.utexas.edu |
10 | |
11 | This class implements the new generation, scalable, SHA512-based |
12 | Unix 'crypt' algorithm developed by a group of engineers from Red |
13 | Hat, Sun, IBM, and HP for common use in the Unix and Linux |
14 | /etc/shadow files. |
15 | |
16 | The Linux glibc library (starting at version 2.7) includes support |
17 | for validating passwords hashed using this algorithm. |
18 | |
19 | The algorithm itself was released into the Public Domain by Ulrich |
20 | Drepper <drepper@redhat.com>. A discussion of the rationale and |
21 | development of this algorithm is at |
22 | |
23 | http://people.redhat.com/drepper/sha-crypt.html |
24 | |
25 | and the specification and a sample C language implementation is at |
26 | |
27 | http://people.redhat.com/drepper/SHA-crypt.txt |
28 | |
29 | This Java Port is |
30 | |
31 | Copyright (c) 2008 The University of Texas at Austin. |
32 | |
33 | All rights reserved. |
34 | |
35 | Redistribution and use in source and binary form are permitted |
36 | provided that distributions retain this entire copyright notice |
37 | and comment. Neither the name of the University nor the names of |
38 | its contributors may be used to endorse or promote products |
39 | derived from this software without specific prior written |
40 | permission. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY |
41 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE |
42 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A |
43 | PARTICULAR PURPOSE. |
44 | |
45 | */ |
46 | |
47 | package org.jclouds.crypto; |
48 | |
49 | import java.security.MessageDigest; |
50 | |
51 | import javax.annotation.Nullable; |
52 | |
53 | import org.jclouds.encryption.internal.JCECrypto; |
54 | |
55 | import com.google.common.base.Throwables; |
56 | |
57 | /** |
58 | * This class defines a method, |
59 | * {@link Sha512Crypt#Sha512_crypt(java.lang.String, java.lang.String, int) |
60 | * Sha512_crypt()}, which takes a password and a salt string and generates a |
61 | * Sha512 encrypted password entry. |
62 | * |
63 | * This class implements the new generation, scalable, SHA512-based Unix 'crypt' |
64 | * algorithm developed by a group of engineers from Red Hat, Sun, IBM, and HP |
65 | * for common use in the Unix and Linux /etc/shadow files. |
66 | * |
67 | * The Linux glibc library (starting at version 2.7) includes support for |
68 | * validating passwords hashed using this algorithm. |
69 | * |
70 | * The algorithm itself was released into the Public Domain by Ulrich Drepper |
71 | * <drepper@redhat.com>. A discussion of the rationale and development of |
72 | * this algorithm is at |
73 | * |
74 | * http://people.redhat.com/drepper/sha-crypt.html |
75 | * |
76 | * and the specification and a sample C language implementation is at |
77 | * |
78 | * http://people.redhat.com/drepper/SHA-crypt.txt |
79 | */ |
80 | public class Sha512Crypt { |
81 | public static com.google.common.base.Function<String, String> function() { |
82 | return Function.INSTANCE; |
83 | } |
84 | |
85 | public static enum Function implements com.google.common.base.Function<String, String> { |
86 | INSTANCE; |
87 | private Crypto crypto; |
88 | |
89 | Function() { |
90 | try { |
91 | this.crypto = new JCECrypto(); |
92 | } catch (Exception e) { |
93 | Throwables.propagate(e); |
94 | } |
95 | } |
96 | |
97 | @Override |
98 | public String apply(String input) { |
99 | return Sha512Crypt.makeShadowLine(input, null, crypto); |
100 | } |
101 | |
102 | @Override |
103 | public String toString() { |
104 | return "sha512Crypt()"; |
105 | } |
106 | |
107 | } |
108 | |
109 | static private final String sha512_salt_prefix = "$6$"; |
110 | static private final String sha512_rounds_prefix = "rounds="; |
111 | static private final int SALT_LEN_MAX = 16; |
112 | static private final int ROUNDS_DEFAULT = 5000; |
113 | static private final int ROUNDS_MIN = 1000; |
114 | static private final int ROUNDS_MAX = 999999999; |
115 | static private final String SALTCHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; |
116 | static private final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; |
117 | |
118 | /** |
119 | * This method actually generates an Sha512 crypted password hash from a |
120 | * plaintext password and a salt. |
121 | * |
122 | * <p> |
123 | * The resulting string will be in the form |
124 | * '$6$<rounds=n>$<salt>$<hashed mess> |
125 | * </p> |
126 | * |
127 | * @param password |
128 | * Plaintext password |
129 | * |
130 | * @param shadowPrefix |
131 | * An encoded salt/rounds which will be consulted to determine the |
132 | * salt and round count, if not null |
133 | * |
134 | * @return The Sha512 Unix Crypt hash text for the password |
135 | */ |
136 | public static String makeShadowLine(String password, @Nullable String shadowPrefix, Crypto crypto) { |
137 | MessageDigest ctx = crypto.sha512(); |
138 | MessageDigest alt_ctx = crypto.sha512(); |
139 | |
140 | byte[] alt_result; |
141 | byte[] temp_result; |
142 | byte[] p_bytes = null; |
143 | byte[] s_bytes = null; |
144 | int cnt, cnt2; |
145 | int rounds = ROUNDS_DEFAULT; // Default number of rounds. |
146 | StringBuffer buffer; |
147 | |
148 | /* -- */ |
149 | |
150 | if (shadowPrefix != null) { |
151 | if (shadowPrefix.startsWith(sha512_salt_prefix)) { |
152 | shadowPrefix = shadowPrefix.substring(sha512_salt_prefix.length()); |
153 | } |
154 | |
155 | if (shadowPrefix.startsWith(sha512_rounds_prefix)) { |
156 | String num = shadowPrefix.substring(sha512_rounds_prefix.length(), shadowPrefix.indexOf('$')); |
157 | int srounds = Integer.valueOf(num).intValue(); |
158 | shadowPrefix = shadowPrefix.substring(shadowPrefix.indexOf('$') + 1); |
159 | rounds = Math.max(ROUNDS_MIN, Math.min(srounds, ROUNDS_MAX)); |
160 | } |
161 | |
162 | if (shadowPrefix.length() > SALT_LEN_MAX) { |
163 | shadowPrefix = shadowPrefix.substring(0, SALT_LEN_MAX); |
164 | } |
165 | } else { |
166 | java.util.Random randgen = new java.util.Random(); |
167 | StringBuffer saltBuf = new StringBuffer(); |
168 | |
169 | while (saltBuf.length() < 16) { |
170 | int index = (int) (randgen.nextFloat() * SALTCHARS.length()); |
171 | saltBuf.append(SALTCHARS.substring(index, index + 1)); |
172 | } |
173 | |
174 | shadowPrefix = saltBuf.toString(); |
175 | } |
176 | |
177 | byte[] key = password.getBytes(); |
178 | byte[] salts = shadowPrefix.getBytes(); |
179 | |
180 | ctx.reset(); |
181 | ctx.update(key, 0, key.length); |
182 | ctx.update(salts, 0, salts.length); |
183 | |
184 | alt_ctx.reset(); |
185 | alt_ctx.update(key, 0, key.length); |
186 | alt_ctx.update(salts, 0, salts.length); |
187 | alt_ctx.update(key, 0, key.length); |
188 | |
189 | alt_result = alt_ctx.digest(); |
190 | |
191 | for (cnt = key.length; cnt > 64; cnt -= 64) { |
192 | ctx.update(alt_result, 0, 64); |
193 | } |
194 | |
195 | ctx.update(alt_result, 0, cnt); |
196 | |
197 | for (cnt = key.length; cnt > 0; cnt >>= 1) { |
198 | if ((cnt & 1) != 0) { |
199 | ctx.update(alt_result, 0, 64); |
200 | } else { |
201 | ctx.update(key, 0, key.length); |
202 | } |
203 | } |
204 | |
205 | alt_result = ctx.digest(); |
206 | |
207 | alt_ctx.reset(); |
208 | |
209 | for (cnt = 0; cnt < key.length; ++cnt) { |
210 | alt_ctx.update(key, 0, key.length); |
211 | } |
212 | |
213 | temp_result = alt_ctx.digest(); |
214 | |
215 | p_bytes = new byte[key.length]; |
216 | |
217 | for (cnt2 = 0, cnt = p_bytes.length; cnt >= 64; cnt -= 64) { |
218 | System.arraycopy(temp_result, 0, p_bytes, cnt2, 64); |
219 | cnt2 += 64; |
220 | } |
221 | |
222 | System.arraycopy(temp_result, 0, p_bytes, cnt2, cnt); |
223 | |
224 | alt_ctx.reset(); |
225 | |
226 | for (cnt = 0; cnt < 16 + (alt_result[0] & 0xFF); ++cnt) { |
227 | alt_ctx.update(salts, 0, salts.length); |
228 | } |
229 | |
230 | temp_result = alt_ctx.digest(); |
231 | |
232 | s_bytes = new byte[salts.length]; |
233 | |
234 | for (cnt2 = 0, cnt = s_bytes.length; cnt >= 64; cnt -= 64) { |
235 | System.arraycopy(temp_result, 0, s_bytes, cnt2, 64); |
236 | cnt2 += 64; |
237 | } |
238 | |
239 | System.arraycopy(temp_result, 0, s_bytes, cnt2, cnt); |
240 | |
241 | /* |
242 | * Repeatedly run the collected hash value through SHA512 to burn CPU |
243 | * cycles. |
244 | */ |
245 | |
246 | for (cnt = 0; cnt < rounds; ++cnt) { |
247 | ctx.reset(); |
248 | |
249 | if ((cnt & 1) != 0) { |
250 | ctx.update(p_bytes, 0, key.length); |
251 | } else { |
252 | ctx.update(alt_result, 0, 64); |
253 | } |
254 | |
255 | if (cnt % 3 != 0) { |
256 | ctx.update(s_bytes, 0, salts.length); |
257 | } |
258 | |
259 | if (cnt % 7 != 0) { |
260 | ctx.update(p_bytes, 0, key.length); |
261 | } |
262 | |
263 | if ((cnt & 1) != 0) { |
264 | ctx.update(alt_result, 0, 64); |
265 | } else { |
266 | ctx.update(p_bytes, 0, key.length); |
267 | } |
268 | |
269 | alt_result = ctx.digest(); |
270 | } |
271 | |
272 | buffer = new StringBuffer(sha512_salt_prefix); |
273 | |
274 | if (rounds != 5000) { |
275 | buffer.append(sha512_rounds_prefix); |
276 | buffer.append(rounds); |
277 | buffer.append("$"); |
278 | } |
279 | |
280 | buffer.append(shadowPrefix); |
281 | buffer.append("$"); |
282 | |
283 | buffer.append(b64_from_24bit(alt_result[0], alt_result[21], alt_result[42], 4)); |
284 | buffer.append(b64_from_24bit(alt_result[22], alt_result[43], alt_result[1], 4)); |
285 | buffer.append(b64_from_24bit(alt_result[44], alt_result[2], alt_result[23], 4)); |
286 | buffer.append(b64_from_24bit(alt_result[3], alt_result[24], alt_result[45], 4)); |
287 | buffer.append(b64_from_24bit(alt_result[25], alt_result[46], alt_result[4], 4)); |
288 | buffer.append(b64_from_24bit(alt_result[47], alt_result[5], alt_result[26], 4)); |
289 | buffer.append(b64_from_24bit(alt_result[6], alt_result[27], alt_result[48], 4)); |
290 | buffer.append(b64_from_24bit(alt_result[28], alt_result[49], alt_result[7], 4)); |
291 | buffer.append(b64_from_24bit(alt_result[50], alt_result[8], alt_result[29], 4)); |
292 | buffer.append(b64_from_24bit(alt_result[9], alt_result[30], alt_result[51], 4)); |
293 | buffer.append(b64_from_24bit(alt_result[31], alt_result[52], alt_result[10], 4)); |
294 | buffer.append(b64_from_24bit(alt_result[53], alt_result[11], alt_result[32], 4)); |
295 | buffer.append(b64_from_24bit(alt_result[12], alt_result[33], alt_result[54], 4)); |
296 | buffer.append(b64_from_24bit(alt_result[34], alt_result[55], alt_result[13], 4)); |
297 | buffer.append(b64_from_24bit(alt_result[56], alt_result[14], alt_result[35], 4)); |
298 | buffer.append(b64_from_24bit(alt_result[15], alt_result[36], alt_result[57], 4)); |
299 | buffer.append(b64_from_24bit(alt_result[37], alt_result[58], alt_result[16], 4)); |
300 | buffer.append(b64_from_24bit(alt_result[59], alt_result[17], alt_result[38], 4)); |
301 | buffer.append(b64_from_24bit(alt_result[18], alt_result[39], alt_result[60], 4)); |
302 | buffer.append(b64_from_24bit(alt_result[40], alt_result[61], alt_result[19], 4)); |
303 | buffer.append(b64_from_24bit(alt_result[62], alt_result[20], alt_result[41], 4)); |
304 | buffer.append(b64_from_24bit((byte) 0x00, (byte) 0x00, alt_result[63], 2)); |
305 | |
306 | /* |
307 | * Clear the buffer for the intermediate result so that people attaching |
308 | * to processes or reading core dumps cannot get any information. |
309 | */ |
310 | |
311 | ctx.reset(); |
312 | |
313 | return buffer.toString(); |
314 | } |
315 | |
316 | private static final String b64_from_24bit(byte B2, byte B1, byte B0, int size) { |
317 | int v = ((((int) B2) & 0xFF) << 16) | ((((int) B1) & 0xFF) << 8) | ((int) B0 & 0xff); |
318 | |
319 | StringBuffer result = new StringBuffer(); |
320 | |
321 | while (--size >= 0) { |
322 | result.append(itoa64.charAt((int) (v & 0x3f))); |
323 | v >>>= 6; |
324 | } |
325 | |
326 | return result.toString(); |
327 | } |
328 | |
329 | } |