001/* 002 * or more contributor license agreements. See the NOTICE file 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020 021package org.apache.directory.api.ldap.model.password; 022 023 024import java.security.Key; 025import java.security.MessageDigest; 026import java.security.NoSuchAlgorithmException; 027import java.security.SecureRandom; 028import java.security.spec.KeySpec; 029import java.util.Arrays; 030import java.util.Date; 031 032import javax.crypto.SecretKeyFactory; 033import javax.crypto.spec.PBEKeySpec; 034 035import org.apache.commons.codec.digest.Crypt; 036import org.apache.directory.api.i18n.I18n; 037import org.apache.directory.api.ldap.model.constants.LdapSecurityConstants; 038import org.apache.directory.api.util.Base64; 039import org.apache.directory.api.util.DateUtils; 040import org.apache.directory.api.util.Strings; 041 042/** 043 * A utility class containing methods related to processing passwords. 044 * 045 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 046 */ 047public final class PasswordUtil 048{ 049 050 /** The SHA1 hash length */ 051 public static final int SHA1_LENGTH = 20; 052 053 /** The SHA256 hash length */ 054 public static final int SHA256_LENGTH = 32; 055 056 /** The SHA384 hash length */ 057 public static final int SHA384_LENGTH = 48; 058 059 /** The SHA512 hash length */ 060 public static final int SHA512_LENGTH = 64; 061 062 /** The MD5 hash length */ 063 public static final int MD5_LENGTH = 16; 064 065 /** The PKCS5S2 hash length */ 066 public static final int PKCS5S2_LENGTH = 32; 067 068 /** The CRYPT (DES) hash length */ 069 public static final int CRYPT_LENGTH = 11; 070 071 /** The CRYPT (MD5) hash length */ 072 public static final int CRYPT_MD5_LENGTH = 22; 073 074 /** The CRYPT (SHA-256) hash length */ 075 public static final int CRYPT_SHA256_LENGTH = 43; 076 077 /** The CRYPT (SHA-512) hash length */ 078 public static final int CRYPT_SHA512_LENGTH = 86; 079 080 /** The CRYPT (BCrypt) hash length */ 081 public static final int CRYPT_BCRYPT_LENGTH = 31; 082 083 private static final byte[] CRYPT_SALT_CHARS = Strings 084 .getBytesUtf8( "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" ); 085 086 private PasswordUtil() 087 { 088 } 089 090 091 /** 092 * Get the algorithm from the stored password. 093 * It can be found on the beginning of the stored password, between 094 * curly brackets. 095 * @param credentials the credentials of the user 096 * @return the name of the algorithm to use 097 */ 098 public static LdapSecurityConstants findAlgorithm( byte[] credentials ) 099 { 100 if ( ( credentials == null ) || ( credentials.length == 0 ) ) 101 { 102 return null; 103 } 104 105 if ( credentials[0] == '{' ) 106 { 107 // get the algorithm 108 int pos = 1; 109 110 while ( pos < credentials.length ) 111 { 112 if ( credentials[pos] == '}' ) 113 { 114 break; 115 } 116 117 pos++; 118 } 119 120 if ( pos < credentials.length ) 121 { 122 if ( pos == 1 ) 123 { 124 // We don't have an algorithm : return the credentials as is 125 return null; 126 } 127 128 String algorithm = Strings.toLowerCaseAscii( Strings.utf8ToString( credentials, 1, pos - 1 ) ); 129 130 // support for crypt additional encryption algorithms (e.g. {crypt}$1$salt$ez2vlPGdaLYkJam5pWs/Y1) 131 if ( credentials.length > pos + 3 && credentials[pos + 1] == '$' 132 && Character.isDigit( credentials[pos + 2] ) ) 133 { 134 if ( credentials[pos + 3] == '$' ) 135 { 136 algorithm += Strings.utf8ToString( credentials, pos + 1, 3 ); 137 } 138 else if ( credentials.length > pos + 4 && credentials[pos + 4] == '$' ) 139 { 140 algorithm += Strings.utf8ToString( credentials, pos + 1, 4 ); 141 } 142 } 143 144 return LdapSecurityConstants.getAlgorithm( algorithm ); 145 } 146 else 147 { 148 // We don't have an algorithm 149 return null; 150 } 151 } 152 else 153 { 154 // No '{algo}' part 155 return null; 156 } 157 } 158 159 160 /** 161 * @see #createStoragePassword(byte[], LdapSecurityConstants) 162 * 163 * @param credentials The password 164 * @param algorithm The algorithm to use 165 * @return The resulting byte[] containing the paswword 166 */ 167 public static byte[] createStoragePassword( String credentials, LdapSecurityConstants algorithm ) 168 { 169 return createStoragePassword( Strings.getBytesUtf8( credentials ), algorithm ); 170 } 171 172 173 /** 174 * create a hashed password in a format that can be stored in the server. 175 * If the specified algorithm requires a salt then a random salt of 8 byte size is used 176 * 177 * @param credentials the plain text password 178 * @param algorithm the hashing algorithm to be applied 179 * @return the password after hashing with the given algorithm 180 */ 181 public static byte[] createStoragePassword( byte[] credentials, LdapSecurityConstants algorithm ) 182 { 183 // check plain text password 184 if ( algorithm == null ) 185 { 186 return credentials; 187 } 188 189 byte[] salt; 190 191 switch ( algorithm ) 192 { 193 case HASH_METHOD_SSHA: 194 case HASH_METHOD_SSHA256: 195 case HASH_METHOD_SSHA384: 196 case HASH_METHOD_SSHA512: 197 case HASH_METHOD_SMD5: 198 // we use 8 byte salt always except for "crypt" which needs 2 byte salt 199 salt = new byte[8]; 200 new SecureRandom().nextBytes( salt ); 201 break; 202 203 case HASH_METHOD_PKCS5S2: 204 // we use 16 byte salt for PKCS5S2 205 salt = new byte[16]; 206 new SecureRandom().nextBytes( salt ); 207 break; 208 209 case HASH_METHOD_CRYPT: 210 salt = generateCryptSalt( 2 ); 211 break; 212 213 case HASH_METHOD_CRYPT_MD5: 214 case HASH_METHOD_CRYPT_SHA256: 215 case HASH_METHOD_CRYPT_SHA512: 216 salt = generateCryptSalt( 8 ); 217 break; 218 219 case HASH_METHOD_CRYPT_BCRYPT: 220 salt = Strings.getBytesUtf8( BCrypt.genSalt() ); 221 break; 222 223 default: 224 salt = null; 225 } 226 227 byte[] hashedPassword = encryptPassword( credentials, algorithm, salt ); 228 StringBuilder sb = new StringBuilder(); 229 230 sb.append( '{' ).append( Strings.upperCase( algorithm.getPrefix() ) ).append( '}' ); 231 232 if ( algorithm == LdapSecurityConstants.HASH_METHOD_CRYPT 233 || algorithm == LdapSecurityConstants.HASH_METHOD_CRYPT_BCRYPT ) 234 { 235 sb.append( Strings.utf8ToString( salt ) ); 236 sb.append( Strings.utf8ToString( hashedPassword ) ); 237 } 238 else if ( algorithm == LdapSecurityConstants.HASH_METHOD_CRYPT_MD5 239 || algorithm == LdapSecurityConstants.HASH_METHOD_CRYPT_SHA256 240 || algorithm == LdapSecurityConstants.HASH_METHOD_CRYPT_SHA512 ) 241 { 242 sb.append( algorithm.getSubPrefix() ); 243 sb.append( Strings.utf8ToString( salt ) ); 244 sb.append( '$' ); 245 sb.append( Strings.utf8ToString( hashedPassword ) ); 246 } 247 else if ( salt != null ) 248 { 249 byte[] hashedPasswordWithSaltBytes = new byte[hashedPassword.length + salt.length]; 250 251 if ( algorithm == LdapSecurityConstants.HASH_METHOD_PKCS5S2 ) 252 { 253 merge( hashedPasswordWithSaltBytes, salt, hashedPassword ); 254 } 255 else 256 { 257 merge( hashedPasswordWithSaltBytes, hashedPassword, salt ); 258 } 259 260 sb.append( String.valueOf( Base64.encode( hashedPasswordWithSaltBytes ) ) ); 261 } 262 else 263 { 264 sb.append( String.valueOf( Base64.encode( hashedPassword ) ) ); 265 } 266 267 return Strings.getBytesUtf8( sb.toString() ); 268 } 269 270 271 /** 272 * 273 * Compare the credentials. 274 * We have at least 6 algorithms to encrypt the password : 275 * <ul> 276 * <li>- SHA</li> 277 * <li>- SSHA (salted SHA)</li> 278 * <li>- SHA-2(256, 384 and 512 and their salted versions)</li> 279 * <li>- MD5</li> 280 * <li>- SMD5 (slated MD5)</li> 281 * <li>- PKCS5S2 (PBKDF2)</li> 282 * <li>- crypt (unix crypt)</li> 283 * <li>- plain text, ie no encryption.</li> 284 * </ul> 285 * <p> 286 * If we get an encrypted password, it is prefixed by the used algorithm, between 287 * brackets : {SSHA}password ... 288 * </p> 289 * If the password is using SSHA, SMD5 or crypt, some 'salt' is added to the password : 290 * <ul> 291 * <li>- length(password) - 20, starting at 21st position for SSHA</li> 292 * <li>- length(password) - 16, starting at 16th position for SMD5</li> 293 * <li>- length(password) - 2, starting at 3rd position for crypt</li> 294 * </ul> 295 * <p> 296 * For (S)SHA, SHA-256 and (S)MD5, we have to transform the password from Base64 encoded text 297 * to a byte[] before comparing the password with the stored one. 298 * </p> 299 * <p> 300 * For PKCS5S2 the salt is stored in the beginning of the password 301 * </p> 302 * <p> 303 * For crypt, we only have to remove the salt. 304 * </p> 305 * <p> 306 * At the end, we use the digest() method for (S)SHA and (S)MD5, the crypt() method for 307 * the CRYPT algorithm and a straight comparison for PLAIN TEXT passwords. 308 * </p> 309 * <p> 310 * The stored password is always using the unsalted form, and is stored as a bytes array. 311 * </p> 312 * 313 * @param receivedCredentials the credentials provided by user 314 * @param storedCredentials the credentials stored in the server 315 * @return true if they are equal, false otherwise 316 */ 317 public static boolean compareCredentials( byte[] receivedCredentials, byte[] storedCredentials ) 318 { 319 LdapSecurityConstants algorithm = findAlgorithm( storedCredentials ); 320 321 if ( algorithm != null ) 322 { 323 // Let's get the encrypted part of the stored password 324 // We should just keep the password, excluding the algorithm 325 // and the salt, if any. 326 // But we should also get the algorithm and salt to 327 // be able to encrypt the submitted user password in the next step 328 PasswordDetails passwordDetails = PasswordUtil.splitCredentials( storedCredentials ); 329 330 // Reuse the saltedPassword information to construct the encrypted 331 // password given by the user. 332 byte[] userPassword = PasswordUtil.encryptPassword( receivedCredentials, passwordDetails.getAlgorithm(), 333 passwordDetails.getSalt() ); 334 335 return compareBytes( userPassword, passwordDetails.getPassword() ); 336 } 337 else 338 { 339 return compareBytes( receivedCredentials, storedCredentials ); 340 } 341 } 342 343 344 /** 345 * Compare two byte[] in a constant time. This is necessary because using an Array.equals() is 346 * not Timing attack safe ([1], [2] and [3]), a breach that can be exploited to break some hashes. 347 * 348 * [1] https://en.wikipedia.org/wiki/Timing_attack 349 * [2] http://rdist.root.org/2009/05/28/timing-attack-in-google-keyczar-library/ 350 * [3] https://cryptocoding.net/index.php/Coding_rules 351 * 352 * @param provided The provided password 353 * @param stored The stored password 354 * @return <tt>true</tt> if the compared passwords are equal 355 */ 356 private static boolean compareBytes( byte[] provided, byte[] stored ) 357 { 358 if ( stored == null ) 359 { 360 return provided == null; 361 } 362 else if ( provided == null ) 363 { 364 return false; 365 } 366 367 // Now, compare the two passwords, using a constant time method 368 if ( stored.length != provided.length ) 369 { 370 return false; 371 } 372 373 // loop on *every* byte in both passwords, and at the end, if one char at least is different, return false. 374 int result = 0; 375 376 for ( int i = 0; i < stored.length; i++ ) 377 { 378 // If both bytes are equal, xor will be == 0, otherwise it will be != 0 and so will result. 379 result |= ( stored[i] ^ provided[i] ); 380 } 381 382 return result == 0; 383 } 384 385 386 /** 387 * encrypts the given credentials based on the algorithm name and optional salt 388 * 389 * @param credentials the credentials to be encrypted 390 * @param algorithm the algorithm to be used for encrypting the credentials 391 * @param salt value to be used as salt (optional) 392 * @return the encrypted credentials 393 */ 394 private static byte[] encryptPassword( byte[] credentials, LdapSecurityConstants algorithm, byte[] salt ) 395 { 396 switch ( algorithm ) 397 { 398 case HASH_METHOD_SHA: 399 case HASH_METHOD_SSHA: 400 return digest( LdapSecurityConstants.HASH_METHOD_SHA, credentials, salt ); 401 402 case HASH_METHOD_SHA256: 403 case HASH_METHOD_SSHA256: 404 return digest( LdapSecurityConstants.HASH_METHOD_SHA256, credentials, salt ); 405 406 case HASH_METHOD_SHA384: 407 case HASH_METHOD_SSHA384: 408 return digest( LdapSecurityConstants.HASH_METHOD_SHA384, credentials, salt ); 409 410 case HASH_METHOD_SHA512: 411 case HASH_METHOD_SSHA512: 412 return digest( LdapSecurityConstants.HASH_METHOD_SHA512, credentials, salt ); 413 414 case HASH_METHOD_MD5: 415 case HASH_METHOD_SMD5: 416 return digest( LdapSecurityConstants.HASH_METHOD_MD5, credentials, salt ); 417 418 case HASH_METHOD_CRYPT: 419 String saltWithCrypted = Crypt.crypt( Strings.utf8ToString( credentials ), Strings 420 .utf8ToString( salt ) ); 421 String crypted = saltWithCrypted.substring( 2 ); 422 return Strings.getBytesUtf8( crypted ); 423 424 case HASH_METHOD_CRYPT_MD5: 425 case HASH_METHOD_CRYPT_SHA256: 426 case HASH_METHOD_CRYPT_SHA512: 427 String saltWithCrypted2 = Crypt.crypt( Strings.utf8ToString( credentials ), 428 algorithm.getSubPrefix() + Strings.utf8ToString( salt ) ); 429 String crypted2 = saltWithCrypted2.substring( saltWithCrypted2.lastIndexOf( '$' ) + 1 ); 430 return Strings.getBytesUtf8( crypted2 ); 431 432 case HASH_METHOD_CRYPT_BCRYPT: 433 String crypted3 = BCrypt.hashPw( Strings.utf8ToString( credentials ), Strings.utf8ToString( salt ) ); 434 return Strings.getBytesUtf8( crypted3.substring( crypted3.length() - 31 ) ); 435 436 case HASH_METHOD_PKCS5S2: 437 return generatePbkdf2Hash( credentials, algorithm, salt ); 438 439 default: 440 return credentials; 441 } 442 } 443 444 445 /** 446 * Compute the hashed password given an algorithm, the credentials and 447 * an optional salt. 448 * 449 * @param algorithm the algorithm to use 450 * @param password the credentials 451 * @param salt the optional salt 452 * @return the digested credentials 453 */ 454 private static byte[] digest( LdapSecurityConstants algorithm, byte[] password, byte[] salt ) 455 { 456 MessageDigest digest; 457 458 try 459 { 460 digest = MessageDigest.getInstance( algorithm.getAlgorithm() ); 461 } 462 catch ( NoSuchAlgorithmException e1 ) 463 { 464 return null; 465 } 466 467 if ( salt != null ) 468 { 469 digest.update( password ); 470 digest.update( salt ); 471 return digest.digest(); 472 } 473 else 474 { 475 return digest.digest( password ); 476 } 477 } 478 479 480 /** 481 * Decompose the stored password in an algorithm, an eventual salt 482 * and the password itself. 483 * 484 * If the algorithm is SHA, SSHA, MD5 or SMD5, the part following the algorithm 485 * is base64 encoded 486 * 487 * @param credentials The byte[] containing the credentials to split 488 * @return The password 489 */ 490 public static PasswordDetails splitCredentials( byte[] credentials ) 491 { 492 LdapSecurityConstants algorithm = findAlgorithm( credentials ); 493 494 // check plain text password 495 if ( algorithm == null ) 496 { 497 return new PasswordDetails( null, null, credentials ); 498 } 499 500 int algoLength = algorithm.getPrefix().length() + 2; 501 byte[] password; 502 503 switch ( algorithm ) 504 { 505 case HASH_METHOD_MD5: 506 case HASH_METHOD_SMD5: 507 return getCredentials( credentials, algoLength, MD5_LENGTH, algorithm ); 508 509 case HASH_METHOD_SHA: 510 case HASH_METHOD_SSHA: 511 return getCredentials( credentials, algoLength, SHA1_LENGTH, algorithm ); 512 513 case HASH_METHOD_SHA256: 514 case HASH_METHOD_SSHA256: 515 return getCredentials( credentials, algoLength, SHA256_LENGTH, algorithm ); 516 517 case HASH_METHOD_SHA384: 518 case HASH_METHOD_SSHA384: 519 return getCredentials( credentials, algoLength, SHA384_LENGTH, algorithm ); 520 521 case HASH_METHOD_SHA512: 522 case HASH_METHOD_SSHA512: 523 return getCredentials( credentials, algoLength, SHA512_LENGTH, algorithm ); 524 525 case HASH_METHOD_PKCS5S2: 526 return getPbkdf2Credentials( credentials, algoLength, algorithm ); 527 528 case HASH_METHOD_CRYPT: 529 // The password is associated with a salt. Decompose it 530 // in two parts, no decoding required. 531 // The salt comes first, not like for SSHA and SMD5, and is 2 bytes long 532 // The algorithm, salt, and password will be stored into the PasswordDetails structure. 533 byte[] salt = new byte[2]; 534 password = new byte[credentials.length - salt.length - algoLength]; 535 split( credentials, algoLength, salt, password ); 536 return new PasswordDetails( algorithm, salt, password ); 537 538 case HASH_METHOD_CRYPT_BCRYPT: 539 salt = Arrays.copyOfRange( credentials, algoLength, credentials.length - 31 ); 540 password = Arrays.copyOfRange( credentials, credentials.length - 31, credentials.length ); 541 542 return new PasswordDetails( algorithm, salt, password ); 543 544 case HASH_METHOD_CRYPT_MD5: 545 case HASH_METHOD_CRYPT_SHA256: 546 case HASH_METHOD_CRYPT_SHA512: 547 // skip $x$ 548 algoLength = algoLength + 3; 549 return getCryptCredentials( credentials, algoLength, algorithm ); 550 551 default: 552 // unknown method 553 throw new IllegalArgumentException( I18n.err( I18n.ERR_13010_UNKNOWN_HASH_ALGO, algorithm ) ); 554 } 555 } 556 557 558 /** 559 * Compute the credentials 560 * 561 * @param credentials the credentials 562 * @param algoLength The algorithm length 563 * @param hashLen The hash length 564 * @param algorithm the algorithm to use 565 * @return The split password string, containing the credentials, the salt and the password 566 */ 567 private static PasswordDetails getCredentials( byte[] credentials, int algoLength, int hashLen, 568 LdapSecurityConstants algorithm ) 569 { 570 // The password is associated with a salt. Decompose it 571 // in two parts, after having decoded the password. 572 // The salt is at the end of the credentials. 573 // The algorithm, salt, and password will be stored into the PasswordDetails structure. 574 byte[] passwordAndSalt = Base64 575 .decode( Strings.utf8ToString( credentials, algoLength, credentials.length - algoLength ).toCharArray() ); 576 577 int saltLength = passwordAndSalt.length - hashLen; 578 byte[] salt = saltLength == 0 ? null : new byte[saltLength]; 579 byte[] password = new byte[hashLen]; 580 split( passwordAndSalt, 0, password, salt ); 581 582 return new PasswordDetails( algorithm, salt, password ); 583 } 584 585 586 private static void split( byte[] all, int offset, byte[] left, byte[] right ) 587 { 588 System.arraycopy( all, offset, left, 0, left.length ); 589 if ( right != null ) 590 { 591 System.arraycopy( all, offset + left.length, right, 0, right.length ); 592 } 593 } 594 595 596 private static void merge( byte[] all, byte[] left, byte[] right ) 597 { 598 System.arraycopy( left, 0, all, 0, left.length ); 599 System.arraycopy( right, 0, all, left.length, right.length ); 600 } 601 602 603 /** 604 * checks if the given password's change time is older than the max age 605 * 606 * @param pwdChangedZtime time when the password was last changed 607 * @param pwdMaxAgeSec the max age value in seconds 608 * @return true if expired, false otherwise 609 */ 610 public static boolean isPwdExpired( String pwdChangedZtime, int pwdMaxAgeSec ) 611 { 612 Date pwdChangeDate = DateUtils.getDate( pwdChangedZtime ); 613 614 //DIRSERVER-1735 615 long time = pwdMaxAgeSec * 1000L; 616 time += pwdChangeDate.getTime(); 617 618 Date expiryDate = DateUtils.getDate( DateUtils.getGeneralizedTime( time ) ); 619 Date now = DateUtils.getDate( DateUtils.getGeneralizedTime() ); 620 621 boolean expired = false; 622 623 if ( expiryDate.equals( now ) || expiryDate.before( now ) ) 624 { 625 expired = true; 626 } 627 628 return expired; 629 } 630 631 632 /** 633 * generates a hash based on the <a href="http://en.wikipedia.org/wiki/PBKDF2">PKCS5S2 spec</a> 634 * 635 * Note: this has been implemented to generate hashes compatible with what JIRA generates. 636 * See the <a href="http://pythonhosted.org/passlib/lib/passlib.hash.atlassian_pbkdf2_sha1.html">JIRA's passlib</a> 637 * 638 * @param credentials the credentials 639 * @param algorithm the algorithm to use 640 * @param salt the optional salt 641 * @return the digested credentials 642 */ 643 private static byte[] generatePbkdf2Hash( byte[] credentials, LdapSecurityConstants algorithm, byte[] salt ) 644 { 645 try 646 { 647 SecretKeyFactory sk = SecretKeyFactory.getInstance( algorithm.getAlgorithm() ); 648 char[] password = Strings.utf8ToString( credentials ).toCharArray(); 649 KeySpec keySpec = new PBEKeySpec( password, salt, 10000, PKCS5S2_LENGTH * 8 ); 650 Key key = sk.generateSecret( keySpec ); 651 return key.getEncoded(); 652 } 653 catch ( Exception e ) 654 { 655 throw new RuntimeException( e ); 656 } 657 } 658 659 660 /** 661 * Gets the credentials from a PKCS5S2 hash. 662 * The salt for PKCS5S2 hash is prepended to the password 663 * 664 * @param credentials The password 665 * @param algoLength The length of the algorithm part 666 * @param algorithm The algorithm in use 667 * @return The split credentials, containing the algorithm, the salt and the password 668 */ 669 private static PasswordDetails getPbkdf2Credentials( byte[] credentials, int algoLength, LdapSecurityConstants algorithm ) 670 { 671 // The password is associated with a salt. Decompose it 672 // in two parts, after having decoded the password. 673 // The salt is at the *beginning* of the credentials, and is 16 bytes long 674 // The algorithm, salt, and password will be stored into the PasswordDetails structure. 675 byte[] passwordAndSalt = Base64 676 .decode( Strings.utf8ToString( credentials, algoLength, credentials.length - algoLength ).toCharArray() ); 677 678 int saltLength = passwordAndSalt.length - PKCS5S2_LENGTH; 679 byte[] salt = new byte[saltLength]; 680 byte[] password = new byte[PKCS5S2_LENGTH]; 681 682 split( passwordAndSalt, 0, salt, password ); 683 684 return new PasswordDetails( algorithm, salt, password ); 685 } 686 687 688 private static byte[] generateCryptSalt( int length ) 689 { 690 byte[] salt = new byte[length]; 691 SecureRandom sr = new SecureRandom(); 692 for ( int i = 0; i < salt.length; i++ ) 693 { 694 salt[i] = CRYPT_SALT_CHARS[sr.nextInt( CRYPT_SALT_CHARS.length )]; 695 } 696 697 return salt; 698 } 699 700 701 private static PasswordDetails getCryptCredentials( byte[] credentials, int algoLength, 702 LdapSecurityConstants algorithm ) 703 { 704 // The password is associated with a salt. Decompose it 705 // in two parts, no decoding required. 706 // The salt length is dynamic, between the 2nd and 3rd '$'. 707 // The algorithm, salt, and password will be stored into the PasswordDetails structure. 708 709 // skip {crypt}$x$ 710 int pos = algoLength; 711 while ( pos < credentials.length ) 712 { 713 if ( credentials[pos] == '$' ) 714 { 715 break; 716 } 717 718 pos++; 719 } 720 721 byte[] salt = Arrays.copyOfRange( credentials, algoLength, pos ); 722 byte[] password = Arrays.copyOfRange( credentials, pos + 1, credentials.length ); 723 724 return new PasswordDetails( algorithm, salt, password ); 725 } 726 727}