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