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