| 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 | } |