1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.commons.codec.digest; 18 19 import java.nio.charset.StandardCharsets; 20 import java.security.MessageDigest; 21 import java.security.SecureRandom; 22 import java.util.Arrays; 23 import java.util.Random; 24 import java.util.regex.Matcher; 25 import java.util.regex.Pattern; 26 27 /** 28 * The libc crypt() "$1$" and Apache "$apr1$" MD5-based hash algorithm. 29 * <p> 30 * Based on the public domain ("beer-ware") C implementation from Poul-Henning Kamp which was found at: <a 31 * href="http://www.freebsd.org/cgi/cvsweb.cgi/src/lib/libcrypt/crypt-md5.c?rev=1.1;content-type=text%2Fplain"> 32 * crypt-md5.c @ freebsd.org</a> 33 * </p> 34 * <p> 35 * Source: 36 * </p> 37 * <pre> 38 * $FreeBSD: src/lib/libcrypt/crypt-md5.c,v 1.1 1999/01/21 13:50:09 brandon Exp $ 39 * </pre> 40 * <p> 41 * Conversion to Kotlin and from there to Java in 2012. 42 * </p> 43 * <p> 44 * The C style comments are from the original C code, the ones with "//" from the port. 45 * </p> 46 * <p> 47 * This class is immutable and thread-safe. 48 * </p> 49 * 50 * @since 1.7 51 */ 52 public class Md5Crypt { 53 54 /** The Identifier of the Apache variant. */ 55 static final String APR1_PREFIX = "$apr1$"; 56 57 /** The number of bytes of the final hash. */ 58 private static final int BLOCKSIZE = 16; 59 60 /** The Identifier of this crypt() variant. */ 61 static final String MD5_PREFIX = "$1$"; 62 63 /** The number of rounds of the big loop. */ 64 private static final int ROUNDS = 1000; 65 66 /** 67 * See {@link #apr1Crypt(byte[], String)} for details. 68 * <p> 69 * A salt is generated for you using {@link SecureRandom}; your own {@link Random} in 70 * {@link #apr1Crypt(byte[], Random)}. 71 * </p> 72 * 73 * @param keyBytes plaintext string to hash. Each array element is set to {@code 0} before returning. 74 * @return the hash value 75 * @throws IllegalArgumentException when a {@link java.security.NoSuchAlgorithmException} is caught. * 76 * @see #apr1Crypt(byte[], String) 77 */ 78 public static String apr1Crypt(final byte[] keyBytes) { 79 return apr1Crypt(keyBytes, APR1_PREFIX + B64.getRandomSalt(8)); 80 } 81 82 /** 83 * See {@link #apr1Crypt(byte[], String)} for details. 84 * <p> 85 * A salt is generated for you using the user provided {@link Random}. 86 * </p> 87 * 88 * @param keyBytes plaintext string to hash. Each array element is set to {@code 0} before returning. 89 * @param random the instance of {@link Random} to use for generating the salt. 90 * Consider using {@link SecureRandom} for more secure salts. 91 * @return the hash value 92 * @throws IllegalArgumentException when a {@link java.security.NoSuchAlgorithmException} is caught. * 93 * @see #apr1Crypt(byte[], String) 94 * @since 1.12 95 */ 96 public static String apr1Crypt(final byte[] keyBytes, final Random random) { 97 return apr1Crypt(keyBytes, APR1_PREFIX + B64.getRandomSalt(8, random)); 98 } 99 100 /** 101 * See {@link #apr1Crypt(String, String)} for details. 102 * <p> 103 * A salt is generated for you using {@link SecureRandom} 104 * </p> 105 * 106 * @param keyBytes 107 * plaintext string to hash. Each array element is set to {@code 0} before returning. 108 * @param salt 109 * An APR1 salt. The salt may be null, in which case a salt is generated for you using 110 * {@link SecureRandom} 111 * @return the hash value 112 * @throws IllegalArgumentException 113 * if the salt does not match the allowed pattern 114 * @throws IllegalArgumentException 115 * when a {@link java.security.NoSuchAlgorithmException} is caught. 116 */ 117 public static String apr1Crypt(final byte[] keyBytes, String salt) { 118 // to make the md5Crypt regex happy 119 if (salt != null && !salt.startsWith(APR1_PREFIX)) { 120 salt = APR1_PREFIX + salt; 121 } 122 return Md5Crypt.md5Crypt(keyBytes, salt, APR1_PREFIX); 123 } 124 125 /** 126 * See {@link #apr1Crypt(String, String)} for details. 127 * <p> 128 * A salt is generated for you using {@link SecureRandom}. 129 * </p> 130 * 131 * @param keyBytes 132 * plaintext string to hash. Each array element is set to {@code 0} before returning. 133 * @return the hash value 134 * @throws IllegalArgumentException 135 * when a {@link java.security.NoSuchAlgorithmException} is caught. 136 * @see #apr1Crypt(byte[], String) 137 */ 138 public static String apr1Crypt(final String keyBytes) { 139 return apr1Crypt(keyBytes.getBytes(StandardCharsets.UTF_8)); 140 } 141 142 /** 143 * Generates an Apache htpasswd compatible "$apr1$" MD5 based hash value. 144 * <p> 145 * The algorithm is identical to the crypt(3) "$1$" one but produces different outputs due to the different salt 146 * prefix. 147 * </p> 148 * 149 * @param keyBytes 150 * plaintext string to hash. Each array element is set to {@code 0} before returning. 151 * @param salt 152 * salt string including the prefix and optionally garbage at the end. The salt may be null, in which 153 * case a salt is generated for you using {@link SecureRandom}. 154 * @return the hash value 155 * @throws IllegalArgumentException 156 * if the salt does not match the allowed pattern 157 * @throws IllegalArgumentException 158 * when a {@link java.security.NoSuchAlgorithmException} is caught. 159 */ 160 public static String apr1Crypt(final String keyBytes, final String salt) { 161 return apr1Crypt(keyBytes.getBytes(StandardCharsets.UTF_8), salt); 162 } 163 164 /** 165 * Generates a libc6 crypt() compatible "$1$" hash value. 166 * <p> 167 * See {@link #md5Crypt(byte[], String)} for details. 168 * </p> 169 * <p> 170 * A salt is generated for you using {@link SecureRandom}. 171 * </p> 172 * @param keyBytes 173 * plaintext string to hash. Each array element is set to {@code 0} before returning. 174 * @return the hash value 175 * @throws IllegalArgumentException 176 * when a {@link java.security.NoSuchAlgorithmException} is caught. 177 * @see #md5Crypt(byte[], String) 178 */ 179 public static String md5Crypt(final byte[] keyBytes) { 180 return md5Crypt(keyBytes, MD5_PREFIX + B64.getRandomSalt(8)); 181 } 182 183 /** 184 * Generates a libc6 crypt() compatible "$1$" hash value. 185 * <p> 186 * See {@link #md5Crypt(byte[], String)} for details. 187 * </p> 188 * <p> 189 * A salt is generated for you using the instance of {@link Random} you supply. 190 * </p> 191 * @param keyBytes 192 * plaintext string to hash. Each array element is set to {@code 0} before returning. 193 * @param random 194 * the instance of {@link Random} to use for generating the salt. 195 * Consider using {@link SecureRandom} for more secure salts. 196 * @return the hash value 197 * @throws IllegalArgumentException 198 * when a {@link java.security.NoSuchAlgorithmException} is caught. 199 * @see #md5Crypt(byte[], String) 200 * @since 1.12 201 */ 202 public static String md5Crypt(final byte[] keyBytes, final Random random) { 203 return md5Crypt(keyBytes, MD5_PREFIX + B64.getRandomSalt(8, random)); 204 } 205 206 /** 207 * Generates a libc crypt() compatible "$1$" MD5 based hash value. 208 * <p> 209 * See {@link Crypt#crypt(String, String)} for details. We use {@link SecureRandom} for seed generation by 210 * default. 211 * </p> 212 * 213 * @param keyBytes 214 * plaintext string to hash. Each array element is set to {@code 0} before returning. 215 * @param salt 216 * salt string including the prefix and optionally garbage at the end. The salt may be null, in which 217 * case a salt is generated for you using {@link SecureRandom}. 218 * @return the hash value 219 * @throws IllegalArgumentException 220 * if the salt does not match the allowed pattern 221 * @throws IllegalArgumentException 222 * when a {@link java.security.NoSuchAlgorithmException} is caught. 223 */ 224 public static String md5Crypt(final byte[] keyBytes, final String salt) { 225 return md5Crypt(keyBytes, salt, MD5_PREFIX); 226 } 227 228 /** 229 * Generates a libc6 crypt() "$1$" or Apache htpasswd "$apr1$" hash value. 230 * <p> 231 * See {@link Crypt#crypt(String, String)} or {@link #apr1Crypt(String, String)} for details. We use 232 * {@link SecureRandom by default}. 233 * </p> 234 * 235 * @param keyBytes 236 * plaintext string to hash. Each array element is set to {@code 0} before returning. 237 * @param salt 238 * real salt value without prefix or "rounds=". The salt may be null, in which case a salt 239 * is generated for you using {@link SecureRandom}. 240 * @param prefix 241 * salt prefix 242 * @return the hash value 243 * @throws IllegalArgumentException 244 * if the salt does not match the allowed pattern 245 * @throws IllegalArgumentException 246 * when a {@link java.security.NoSuchAlgorithmException} is caught. 247 */ 248 public static String md5Crypt(final byte[] keyBytes, final String salt, final String prefix) { 249 return md5Crypt(keyBytes, salt, prefix, new SecureRandom()); 250 } 251 252 /** 253 * Generates a libc6 crypt() "$1$" or Apache htpasswd "$apr1$" hash value. 254 * <p> 255 * See {@link Crypt#crypt(String, String)} or {@link #apr1Crypt(String, String)} for details. 256 * </p> 257 * 258 * @param keyBytes 259 * plaintext string to hash. Each array element is set to {@code 0} before returning. 260 * @param salt 261 * real salt value without prefix or "rounds=". The salt may be null, in which case a salt 262 * is generated for you using {@link SecureRandom}. 263 * @param prefix 264 * salt prefix 265 * @param random 266 * the instance of {@link Random} to use for generating the salt. 267 * Consider using {@link SecureRandom} for more secure salts. 268 * @return the hash value 269 * @throws IllegalArgumentException 270 * if the salt does not match the allowed pattern 271 * @throws IllegalArgumentException 272 * when a {@link java.security.NoSuchAlgorithmException} is caught. 273 * @since 1.12 274 */ 275 public static String md5Crypt(final byte[] keyBytes, final String salt, final String prefix, final Random random) { 276 final int keyLen = keyBytes.length; 277 278 // Extract the real salt from the given string which can be a complete hash string. 279 final String saltString; 280 if (salt == null) { 281 saltString = B64.getRandomSalt(8, random); 282 } else { 283 final Pattern p = Pattern.compile("^" + prefix.replace("$", "\\$") + "([\\.\\/a-zA-Z0-9]{1,8}).*"); 284 final Matcher m = p.matcher(salt); 285 if (!m.find()) { 286 throw new IllegalArgumentException("Invalid salt value: " + salt); 287 } 288 saltString = m.group(1); 289 } 290 final byte[] saltBytes = saltString.getBytes(StandardCharsets.UTF_8); 291 292 final MessageDigest ctx = DigestUtils.getMd5Digest(); 293 294 /* 295 * The password first, since that is what is most unknown 296 */ 297 ctx.update(keyBytes); 298 299 /* 300 * Then our magic string 301 */ 302 ctx.update(prefix.getBytes(StandardCharsets.UTF_8)); 303 304 /* 305 * Then the raw salt 306 */ 307 ctx.update(saltBytes); 308 309 /* 310 * Then just as many characters of the MD5(pw,salt,pw) 311 */ 312 MessageDigest ctx1 = DigestUtils.getMd5Digest(); 313 ctx1.update(keyBytes); 314 ctx1.update(saltBytes); 315 ctx1.update(keyBytes); 316 byte[] finalb = ctx1.digest(); 317 int ii = keyLen; 318 while (ii > 0) { 319 ctx.update(finalb, 0, Math.min(ii, 16)); 320 ii -= 16; 321 } 322 323 /* 324 * Don't leave anything around in JVM they could use. 325 */ 326 Arrays.fill(finalb, (byte) 0); 327 328 /* 329 * Then something really weird... 330 */ 331 ii = keyLen; 332 final int j = 0; 333 while (ii > 0) { 334 if ((ii & 1) == 1) { 335 ctx.update(finalb[j]); 336 } else { 337 ctx.update(keyBytes[j]); 338 } 339 ii >>= 1; 340 } 341 342 /* 343 * Now make the output string 344 */ 345 final StringBuilder passwd = new StringBuilder(prefix + saltString + "$"); 346 finalb = ctx.digest(); 347 348 /* 349 * and now, just to make sure things don't run too fast On a 60 Mhz Pentium this takes 34 milliseconds, so you 350 * would need 30 seconds to build a 1000 entry dictionary... 351 */ 352 for (int i = 0; i < ROUNDS; i++) { 353 ctx1 = DigestUtils.getMd5Digest(); 354 if ((i & 1) != 0) { 355 ctx1.update(keyBytes); 356 } else { 357 ctx1.update(finalb, 0, BLOCKSIZE); 358 } 359 360 if (i % 3 != 0) { 361 ctx1.update(saltBytes); 362 } 363 364 if (i % 7 != 0) { 365 ctx1.update(keyBytes); 366 } 367 368 if ((i & 1) != 0) { 369 ctx1.update(finalb, 0, BLOCKSIZE); 370 } else { 371 ctx1.update(keyBytes); 372 } 373 finalb = ctx1.digest(); 374 } 375 376 // The following was nearly identical to the Sha2Crypt code. 377 // Again, the buflen is not really needed. 378 // int buflen = MD5_PREFIX.length() - 1 + salt_string.length() + 1 + BLOCKSIZE + 1; 379 B64.b64from24bit(finalb[0], finalb[6], finalb[12], 4, passwd); 380 B64.b64from24bit(finalb[1], finalb[7], finalb[13], 4, passwd); 381 B64.b64from24bit(finalb[2], finalb[8], finalb[14], 4, passwd); 382 B64.b64from24bit(finalb[3], finalb[9], finalb[15], 4, passwd); 383 B64.b64from24bit(finalb[4], finalb[10], finalb[5], 4, passwd); 384 B64.b64from24bit((byte) 0, (byte) 0, finalb[11], 2, passwd); 385 386 /* 387 * Don't leave anything around in JVM they could use. 388 */ 389 // Is there a better way to do this with the JVM? 390 ctx.reset(); 391 ctx1.reset(); 392 Arrays.fill(keyBytes, (byte) 0); 393 Arrays.fill(saltBytes, (byte) 0); 394 Arrays.fill(finalb, (byte) 0); 395 396 return passwd.toString(); 397 } 398 399 /** 400 * TODO Make private in 2.0. 401 * 402 * @deprecated TODO Make private in 2.0. 403 */ 404 @Deprecated 405 public Md5Crypt() { 406 // empty 407 } 408 }