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