001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Portions Copyright 2010-2015 ForgeRock AS 025 * 026 * BSD-compatible md5 password crypt 027 * Ported to Java from C based on crypt-md5.c by Poul-Henning Kamp, 028 * which was distributed with the following notice: 029 * ---------------------------------------------------------------------------- 030 * "THE BEER-WARE LICENSE" (Revision 42): 031 * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you 032 * can do whatever you want with this stuff. If we meet some day, and you think 033 * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp 034 * ---------------------------------------------------------------------------- 035 */ 036package org.opends.server.util; 037 038import org.forgerock.opendj.ldap.ByteSequence; 039import org.forgerock.opendj.ldap.ByteString; 040 041import java.security.MessageDigest; 042import java.security.SecureRandom; 043import java.security.NoSuchAlgorithmException; 044import java.util.Arrays; 045 046/** 047 * BSD MD5 Crypt algorithm, ported from C. 048 * 049 * @author ludo 050 */ 051public final class BSDMD5Crypt { 052 053 private static final String magic = "$1$"; 054 private static final int saltLength = 8; 055 private static final String itoa64 = 056 "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 057 058 private static String intTo64(int value, int length) 059 { 060 StringBuilder output = new StringBuilder(); 061 062 while (--length >= 0) 063 { 064 output.append(itoa64.charAt(value & 0x3f)); 065 value >>= 6; 066 } 067 068 return output.toString(); 069 } 070 071 /** 072 * Encode the supplied password in BSD MD5 crypt form, using 073 * a random salt. 074 * 075 * @param password A password to encode. 076 * 077 * @return An encrypted string. 078 * 079 * @throws NoSuchAlgorithmException If the MD5 algorithm is not supported. 080 * 081 */ 082 public static String crypt(ByteSequence password) 083 throws NoSuchAlgorithmException 084 { 085 SecureRandom randomGenerator = new SecureRandom(); 086 StringBuilder salt = new StringBuilder(); 087 088 /* Generate some random salt */ 089 while (salt.length() < saltLength) 090 { 091 int index = (int) (randomGenerator.nextFloat() * itoa64.length()); 092 salt.append(itoa64.charAt(index)); 093 } 094 095 return BSDMD5Crypt.crypt(password, salt.toString()); 096 } 097 098 /** 099 * Encode the supplied password in BSD MD5 crypt form, using 100 * provided salt. 101 * 102 * @param password A password to encode. 103 * 104 * @param salt A salt string of any size, of which only the first 105 * 8 bytes will be considered. 106 * 107 * @return An encrypted string. 108 * 109 * @throws NoSuchAlgorithmException If the MD5 algorithm is not supported. 110 * 111 */ 112 public static String crypt(ByteSequence password, String salt) 113 throws NoSuchAlgorithmException 114 { 115 MessageDigest ctx, ctx1; 116 byte digest1[], digest[]; 117 byte[] plaintextBytes = password.toByteArray(); 118 119 /* First skip the magic string */ 120 if (salt.startsWith(magic)) 121 { 122 salt = salt.substring(magic.length()); 123 } 124 125 /* Salt stops at the first $, max saltLength chars */ 126 int saltEnd = salt.indexOf('$'); 127 if (saltEnd != -1) 128 { 129 salt = salt.substring(0, saltEnd); 130 } 131 132 if (salt.length() > saltLength) 133 { 134 salt = salt.substring(0, saltLength); 135 } 136 137 ctx = MessageDigest.getInstance("MD5"); 138 139 /* The password first, since that is what is most unknown */ 140 ctx.update(plaintextBytes); 141 142 /* Then our magic string */ 143 ctx.update(magic.getBytes()); 144 145 /* Then the raw salt */ 146 ctx.update(salt.getBytes()); 147 148 /* Then just as many characters of the MD5(password,salt,password) */ 149 ctx1 = MessageDigest.getInstance("MD5"); 150 ctx1.update(plaintextBytes); 151 ctx1.update(salt.getBytes()); 152 ctx1.update(plaintextBytes); 153 digest1 = ctx1.digest(); 154 155 156 for (int pl = password.length(); pl > 0; pl -= 16) 157 { 158 ctx.update(digest1, 0, pl > 16 ? 16 : pl); 159 } 160 161 /* Don't leave anything around in vm they could use. */ 162 Arrays.fill(digest1, (byte) 0); 163 164 /* Then something really weird... */ 165 for (int i = password.length(); i != 0; i >>= 1) 166 { 167 if ((i & 1) != 0) 168 { 169 ctx.update(digest1[0]); 170 } else 171 { 172 ctx.update(plaintextBytes[0]); 173 } 174 } 175 176 /* Now make the output string */ 177 StringBuilder output = new StringBuilder(); 178 output.append(magic); 179 output.append(salt); 180 output.append("$"); 181 182 digest = ctx.digest(); 183 184 /* 185 * and now, just to make sure things don't run too fast 186 * On a 60 MHz Pentium this takes 34 msec, so you would 187 * need 30 seconds to build a 1000 entry dictionary... 188 */ 189 for (int i = 0; i < 1000; i++) 190 { 191 ctx1 = MessageDigest.getInstance("MD5"); 192 193 if ((i & 1) != 0) 194 { 195 ctx1.update(plaintextBytes); 196 } else 197 { 198 ctx1.update(digest); 199 } 200 if (i % 3 != 0) 201 { 202 ctx1.update(salt.getBytes()); 203 } 204 if (i % 7 != 0) 205 { 206 ctx1.update(plaintextBytes); 207 } 208 if ((i & 1) != 0) 209 { 210 ctx1.update(digest); 211 } else 212 { 213 ctx1.update(plaintextBytes); 214 } 215 digest = ctx1.digest(); 216 } 217 218 int l; 219 220 l = ((digest[0] & 0xff) << 16) | ((digest[6] & 0xff) << 8) 221 | (digest[12] & 0xff); 222 output.append(intTo64(l, 4)); 223 l = ((digest[1] & 0xff) << 16) | ((digest[7] & 0xff) << 8) 224 | (digest[13] & 0xff); 225 output.append(intTo64(l, 4)); 226 l = ((digest[2] & 0xff) << 16) | ((digest[8] & 0xff) << 8) 227 | (digest[14] & 0xff); 228 output.append(intTo64(l, 4)); 229 l = ((digest[3] & 0xff) << 16) | ((digest[9] & 0xff) << 8) 230 | (digest[15] & 0xff); 231 output.append(intTo64(l, 4)); 232 l = ((digest[4] & 0xff) << 16) | ((digest[10] & 0xff) << 8) 233 | (digest[5] & 0xff); 234 output.append(intTo64(l, 4)); 235 l = digest[11] & 0xff; 236 output.append(intTo64(l, 2)); 237 238 /* Don't leave anything around in vm they could use. */ 239 Arrays.fill(digest, (byte) 0); 240 Arrays.fill(plaintextBytes, (byte) 0); 241 ctx = null; 242 ctx1 = null; 243 244 return output.toString(); 245 246 } 247 248 /** 249 * Getter to the BSD MD5 magic string. 250 * 251 * @return the magic string for this crypt algorithm 252 */ 253 public static String getMagicString() 254 { 255 return magic; 256 } 257 258 /** 259 * Main test method. 260 * 261 * @param argv The array of test arguments 262 * 263 */ 264 public static void main(String argv[]) 265 { 266 if (argv.length < 1 || argv.length > 2) 267 { 268 System.err.println("Usage: BSDMD5Crypt password salt"); 269 System.exit(1); 270 } 271 try 272 { 273 if (argv.length == 2) 274 { 275 System.out.println(BSDMD5Crypt.crypt(ByteString.valueOfUtf8(argv[0]), 276 argv[1])); 277 } else 278 { 279 System.out.println(BSDMD5Crypt.crypt(ByteString.valueOfUtf8(argv[0]))); 280 } 281 } catch (Exception e) 282 { 283 System.err.println(e.getMessage()); 284 System.exit(1); 285 } 286 System.exit(0); 287 } 288}