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