bwLehrpool Masterserver
Manages authentication and sharing of virtual machines between participating institutions
Sha512Crypt.java
Go to the documentation of this file.
1 /*
2  Sha512Crypt.java
3 
4  Created: 18 December 2007
5 
6  Java Port By: James Ratcliff, falazar@arlut.utexas.edu
7 
8  This class implements the new generation, scalable, SHA512-based
9  Unix 'crypt' algorithm developed by a group of engineers from Red
10  Hat, Sun, IBM, and HP for common use in the Unix and Linux
11  /etc/shadow files.
12 
13  The Linux glibc library (starting at version 2.7) includes support
14  for validating passwords hashed using this algorithm.
15 
16  The algorithm itself was released into the Public Domain by Ulrich
17  Drepper <drepper@redhat.com>. A discussion of the rationale and
18  development of this algorithm is at
19 
20  http://people.redhat.com/drepper/sha-crypt.html
21 
22  and the specification and a sample C language implementation is at
23 
24  http://people.redhat.com/drepper/SHA-crypt.txt
25 
26  This Java Port is
27 
28  Copyright (c) 2008-2013 The University of Texas at Austin.
29 
30  All rights reserved.
31 
32  Redistribution and use in source and binary form are permitted
33  provided that distributions retain this entire copyright notice
34  and comment. Neither the name of the University nor the names of
35  its contributors may be used to endorse or promote products
36  derived from this software without specific prior written
37  permission. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY
38  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE
39  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
40  PARTICULAR PURPOSE.
41 
42 */
43 
44 package org.openslx.imagemaster.util;
45 
46 import java.security.MessageDigest;
47 import java.security.NoSuchAlgorithmException;
48 
49 /*------------------------------------------------------------------------------
50  class
51  Sha512Crypt
52 
53 ------------------------------------------------------------------------------*/
54 
81 public final class Sha512Crypt
82 {
83  static private final String sha512_salt_prefix = "$6$";
84  static private final String sha512_rounds_prefix = "rounds=";
85  static private final int SALT_LEN_MAX = 16;
86  static private final int ROUNDS_DEFAULT = 5000;
87  static private final int ROUNDS_MIN = 1000;
88  static private final int ROUNDS_MAX = 999999999;
89  static private final String SALTCHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
90  static private final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
91 
95  private static final ThreadLocal<MessageDigest>
98 
103  private static ThreadLocal<MessageDigest> newThreadLocalSha512() {
104  return new ThreadLocal<MessageDigest>() {
105  @Override
106  public MessageDigest initialValue()
107  {
108  try {
109  return MessageDigest.getInstance( "SHA-512" );
110  } catch ( NoSuchAlgorithmException e ) {
111  e.printStackTrace();
112  System.exit(1);
113  return null;
114  }
115  }
116  };
117  }
118 
137  public static final String Sha512_crypt(String keyStr, String saltStr, int roundsCount)
138  {
139  MessageDigest ctx = sha1.get();
140  MessageDigest alt_ctx = sha2.get();
141 
142  byte[] alt_result;
143  byte[] temp_result;
144  byte[] p_bytes = null;
145  byte[] s_bytes = null;
146  int cnt, cnt2;
147  int rounds = ROUNDS_DEFAULT; // Default number of rounds.
148  StringBuilder buffer;
149  boolean include_round_count = false;
150 
151  /* -- */
152 
153  if (saltStr != null)
154  {
155  if (saltStr.startsWith(sha512_salt_prefix))
156  {
157  saltStr = saltStr.substring(sha512_salt_prefix.length());
158  }
159 
160  if (saltStr.startsWith(sha512_rounds_prefix))
161  {
162  String num = saltStr.substring(sha512_rounds_prefix.length(), saltStr.indexOf('$'));
163  int srounds = Integer.valueOf(num).intValue();
164  saltStr = saltStr.substring(saltStr.indexOf('$')+1);
165  rounds = Math.max(ROUNDS_MIN, Math.min(srounds, ROUNDS_MAX));
166  include_round_count = true;
167  }
168 
169  if (saltStr.length() > SALT_LEN_MAX)
170  {
171  saltStr = saltStr.substring(0, SALT_LEN_MAX);
172  }
173 
174  // gnu libc's crypt(3) implementation allows the salt to end
175  // in $ which is then ignored.
176 
177  if (saltStr.endsWith("$"))
178  {
179  saltStr = saltStr.substring(0, saltStr.length() - 1);
180  }
181  else
182  {
183  if (saltStr.indexOf("$") != -1)
184  {
185  saltStr = saltStr.substring(0, saltStr.indexOf("$"));
186  }
187  }
188  }
189  else
190  {
191  StringBuilder saltBuf = new StringBuilder();
192 
193  while (saltBuf.length() < 16)
194  {
195  int index = Util.randomInt(SALTCHARS.length());
196  saltBuf.append(SALTCHARS.substring(index, index+1));
197  }
198 
199  saltStr = saltBuf.toString();
200  }
201 
202  if (roundsCount != 0)
203  {
204  rounds = Math.max(ROUNDS_MIN, Math.min(roundsCount, ROUNDS_MAX));
205  }
206 
207  byte[] key = keyStr.getBytes();
208  byte[] salt = saltStr.getBytes();
209 
210  ctx.reset();
211  ctx.update(key, 0, key.length);
212  ctx.update(salt, 0, salt.length);
213 
214  alt_ctx.reset();
215  alt_ctx.update(key, 0, key.length);
216  alt_ctx.update(salt, 0, salt.length);
217  alt_ctx.update(key, 0, key.length);
218 
219  alt_result = alt_ctx.digest();
220 
221  for (cnt = key.length; cnt > 64; cnt -= 64)
222  {
223  ctx.update(alt_result, 0, 64);
224  }
225 
226  ctx.update(alt_result, 0, cnt);
227 
228  for (cnt = key.length; cnt > 0; cnt >>= 1)
229  {
230  if ((cnt & 1) != 0)
231  {
232  ctx.update(alt_result, 0, 64);
233  }
234  else
235  {
236  ctx.update(key, 0, key.length);
237  }
238  }
239 
240  alt_result = ctx.digest();
241 
242  alt_ctx.reset();
243 
244  for (cnt = 0; cnt < key.length; ++cnt)
245  {
246  alt_ctx.update(key, 0, key.length);
247  }
248 
249  temp_result = alt_ctx.digest();
250 
251  p_bytes = new byte[key.length];
252 
253  for (cnt2 = 0, cnt = p_bytes.length; cnt >= 64; cnt -= 64)
254  {
255  System.arraycopy(temp_result, 0, p_bytes, cnt2, 64);
256  cnt2 += 64;
257  }
258 
259  System.arraycopy(temp_result, 0, p_bytes, cnt2, cnt);
260 
261  alt_ctx.reset();
262 
263  for (cnt = 0; cnt < 16 + (alt_result[0]&0xFF); ++cnt)
264  {
265  alt_ctx.update(salt, 0, salt.length);
266  }
267 
268  temp_result = alt_ctx.digest();
269 
270  s_bytes = new byte[salt.length];
271 
272  for (cnt2 = 0, cnt = s_bytes.length; cnt >= 64; cnt -= 64)
273  {
274  System.arraycopy(temp_result, 0, s_bytes, cnt2, 64);
275  cnt2 += 64;
276  }
277 
278  System.arraycopy(temp_result, 0, s_bytes, cnt2, cnt);
279 
280  /* Repeatedly run the collected hash value through SHA512 to burn
281  CPU cycles. */
282 
283  for (cnt = 0; cnt < rounds; ++cnt)
284  {
285  ctx.reset();
286 
287  if ((cnt & 1) != 0)
288  {
289  ctx.update(p_bytes, 0, key.length);
290  }
291  else
292  {
293  ctx.update (alt_result, 0, 64);
294  }
295 
296  if (cnt % 3 != 0)
297  {
298  ctx.update(s_bytes, 0, salt.length);
299  }
300 
301  if (cnt % 7 != 0)
302  {
303  ctx.update(p_bytes, 0, key.length);
304  }
305 
306  if ((cnt & 1) != 0)
307  {
308  ctx.update(alt_result, 0, 64);
309  }
310  else
311  {
312  ctx.update(p_bytes, 0, key.length);
313  }
314 
315  alt_result = ctx.digest();
316  }
317 
318  buffer = new StringBuilder(sha512_salt_prefix);
319 
320  if (include_round_count || rounds != ROUNDS_DEFAULT)
321  {
322  buffer.append(sha512_rounds_prefix);
323  buffer.append(rounds);
324  buffer.append("$");
325  }
326 
327  buffer.append(saltStr);
328  buffer.append("$");
329 
330  buffer.append(b64_from_24bit (alt_result[0], alt_result[21], alt_result[42], 4));
331  buffer.append(b64_from_24bit (alt_result[22], alt_result[43], alt_result[1], 4));
332  buffer.append(b64_from_24bit (alt_result[44], alt_result[2], alt_result[23], 4));
333  buffer.append(b64_from_24bit (alt_result[3], alt_result[24], alt_result[45], 4));
334  buffer.append(b64_from_24bit (alt_result[25], alt_result[46], alt_result[4], 4));
335  buffer.append(b64_from_24bit (alt_result[47], alt_result[5], alt_result[26], 4));
336  buffer.append(b64_from_24bit (alt_result[6], alt_result[27], alt_result[48], 4));
337  buffer.append(b64_from_24bit (alt_result[28], alt_result[49], alt_result[7], 4));
338  buffer.append(b64_from_24bit (alt_result[50], alt_result[8], alt_result[29], 4));
339  buffer.append(b64_from_24bit (alt_result[9], alt_result[30], alt_result[51], 4));
340  buffer.append(b64_from_24bit (alt_result[31], alt_result[52], alt_result[10], 4));
341  buffer.append(b64_from_24bit (alt_result[53], alt_result[11], alt_result[32], 4));
342  buffer.append(b64_from_24bit (alt_result[12], alt_result[33], alt_result[54], 4));
343  buffer.append(b64_from_24bit (alt_result[34], alt_result[55], alt_result[13], 4));
344  buffer.append(b64_from_24bit (alt_result[56], alt_result[14], alt_result[35], 4));
345  buffer.append(b64_from_24bit (alt_result[15], alt_result[36], alt_result[57], 4));
346  buffer.append(b64_from_24bit (alt_result[37], alt_result[58], alt_result[16], 4));
347  buffer.append(b64_from_24bit (alt_result[59], alt_result[17], alt_result[38], 4));
348  buffer.append(b64_from_24bit (alt_result[18], alt_result[39], alt_result[60], 4));
349  buffer.append(b64_from_24bit (alt_result[40], alt_result[61], alt_result[19], 4));
350  buffer.append(b64_from_24bit (alt_result[62], alt_result[20], alt_result[41], 4));
351  buffer.append(b64_from_24bit ((byte)0x00, (byte)0x00, alt_result[63], 2));
352 
353  /* Clear the buffer for the intermediate result so that people
354  attaching to processes or reading core dumps cannot get any
355  information. */
356 
357  ctx.reset();
358 
359  return buffer.toString();
360  }
361 
362  private static final String b64_from_24bit(byte B2, byte B1, byte B0, int size)
363  {
364  int v = ((((int) B2) & 0xFF) << 16) | ((((int) B1) & 0xFF) << 8) | ((int)B0 & 0xff);
365 
366  StringBuilder result = new StringBuilder();
367 
368  while (--size >= 0)
369  {
370  result.append(itoa64.charAt((int) (v & 0x3f)));
371  v >>>= 6;
372  }
373 
374  return result.toString();
375  }
376 
387  static public final boolean verifyPassword(String plaintextPass, String sha512CryptText)
388  {
389  if (sha512CryptText.startsWith("$6$"))
390  {
391  return sha512CryptText.equals(Sha512_crypt(plaintextPass, sha512CryptText, 0));
392  }
393  else
394  {
395  throw new RuntimeException("Bad sha512CryptText");
396  }
397  }
398 
404  public static final boolean verifyHashTextFormat(String sha512CryptText)
405  {
406  if (!sha512CryptText.startsWith(sha512_salt_prefix))
407  {
408  return false;
409  }
410 
411  sha512CryptText = sha512CryptText.substring(sha512_salt_prefix.length());
412 
413  if (sha512CryptText.startsWith(sha512_rounds_prefix))
414  {
415  String num = sha512CryptText.substring(sha512_rounds_prefix.length(), sha512CryptText.indexOf('$'));
416 
417  try
418  {
419  Integer.valueOf(num).intValue();
420  }
421  catch (NumberFormatException ex)
422  {
423  return false;
424  }
425 
426  sha512CryptText = sha512CryptText.substring(sha512CryptText.indexOf('$')+1);
427  }
428 
429  if (sha512CryptText.indexOf('$') > (SALT_LEN_MAX + 1))
430  {
431  return false;
432  }
433 
434  sha512CryptText = sha512CryptText.substring(sha512CryptText.indexOf('$') + 1);
435 
436  for (int i = 0; i < sha512CryptText.length(); i++)
437  {
438  if (itoa64.indexOf(sha512CryptText.charAt(i)) == -1)
439  {
440  return false;
441  }
442  }
443 
444  return true;
445  }
446 
452  public static void selfTest()
453  {
454  String msgs[] =
455  {
456  "$6$saltstring", "Hello world!", "$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJuesI68u4OTLiBFdcbYEdFCoEOfaS35inz1",
457  "$6$xxxxxxxx", "geheim", "$6$xxxxxxxx$wuSdyeOvQXjj/nNoWnjjo.6OxUWrQFRIj019kh1cDpun6l6cpr3ywSrBprYRYZXcm4Kv9lboCEFI3GzBkdNAz/",
458  "$6$rounds=10000$saltstringsaltstring", "Hello world!", "$6$rounds=10000$saltstringsaltst$OW1/O6BYHV6BcXZu8QVeXbDWra3Oeqh0sbHbbMCVNSnCM/UrjmM0Dp8vOuZeHBy/YTBmSK6H9qs/y3RnOaw5v.",
459  "$6$rounds=5000$toolongsaltstring", "This is just a test", "$6$rounds=5000$toolongsaltstrin$lQ8jolhgVRVhY4b5pZKaysCLi0QBxGoNeKQzQ3glMhwllF7oGDZxUhx1yxdYcz/e1JSbq3y6JMxxl8audkUEm0",
460  "$6$rounds=1400$anotherlongsaltstring", "a very much longer text to encrypt. This one even stretches over morethan one line.", "$6$rounds=1400$anotherlongsalts$POfYwTEok97VWcjxIiSOjiykti.o/pQs.wPvMxQ6Fm7I6IoYN3CmLs66x9t0oSwbtEW7o7UmJEiDwGqd8p4ur1",
461  "$6$rounds=77777$short", "we have a short salt string but not a short password", "$6$rounds=77777$short$WuQyW2YR.hBNpjjRhpYD/ifIw05xdfeEyQoMxIXbkvr0gge1a1x3yRULJ5CCaUeOxFmtlcGZelFl5CxtgfiAc0",
462  "$6$rounds=123456$asaltof16chars..", "a short string", "$6$rounds=123456$asaltof16chars..$BtCwjqMJGx5hrJhZywWvt0RLE8uZ4oPwcelCjmw2kSYu.Ec6ycULevoBK25fs2xXgMNrCzIMVcgEJAstJeonj1",
463  "$6$rounds=10$roundstoolow", "the minimum number is still observed", "$6$rounds=1000$roundstoolow$kUMsbe306n21p9R.FRkW3IGn.S9NPN0x50YhH1xhLsPuWGsUSklZt58jaTfF4ZEQpyUNGc0dqbpBYYBaHHrsX.",
464  };
465 
466  System.out.println("Starting Sha512Crypt tests now...");
467 
468  for (int t=0; t<(msgs.length/3); t++)
469  {
470  String plainText = msgs[t*3+1];
471  String cryptText = msgs[t*3+2];
472 
473  String result = Sha512_crypt(plainText, cryptText, 0);
474 
475  System.out.println("test " + t + " result is:" + result);
476  System.out.println("test " + t + " should be:" + cryptText);
477 
478  if (result.equals(cryptText))
479  {
480  System.out.println("Passed Crypt well");
481  }
482  else
483  {
484  System.out.println("Failed Crypt Badly");
485  throw new RuntimeException();
486  }
487 
488  if (verifyPassword(plainText, cryptText))
489  {
490  System.out.println("Passed verifyPassword well");
491  }
492  else
493  {
494  System.out.println("Failed verifyPassword Badly");
495  throw new RuntimeException();
496  }
497  }
498  }
499 }
static final ThreadLocal< MessageDigest > sha1
Cache of sha512 digesters.
static final ThreadLocal< MessageDigest > sha2
static ThreadLocal< MessageDigest > newThreadLocalSha512()
For instantiating TL sha512, so we don't have to copy this stuff twice.
static final boolean verifyPassword(String plaintextPass, String sha512CryptText)
static final String b64_from_24bit(byte B2, byte B1, byte B0, int size)
static final String Sha512_crypt(String keyStr, String saltStr, int roundsCount)
static int randomInt(int n)
Return a random integer in the range of 0 (inclusive) and n (exclusive).
Definition: Util.java:98
static final boolean verifyHashTextFormat(String sha512CryptText)
Some utilities to make our lives easier.
Definition: Util.java:18