1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.apache.directory.api.ldap.model.password;
22
23
24 import java.security.Key;
25 import java.security.MessageDigest;
26 import java.security.NoSuchAlgorithmException;
27 import java.security.SecureRandom;
28 import java.security.spec.KeySpec;
29 import java.util.Arrays;
30 import java.util.Date;
31
32 import javax.crypto.SecretKeyFactory;
33 import javax.crypto.spec.PBEKeySpec;
34
35 import org.apache.commons.codec.digest.Crypt;
36 import org.apache.directory.api.ldap.model.constants.LdapSecurityConstants;
37 import org.apache.directory.api.util.Base64;
38 import org.apache.directory.api.util.DateUtils;
39 import org.apache.directory.api.util.Strings;
40
41
42
43
44
45
46 public final class PasswordUtil
47 {
48
49
50 public static final int SHA1_LENGTH = 20;
51
52
53 public static final int SHA256_LENGTH = 32;
54
55
56 public static final int SHA384_LENGTH = 48;
57
58
59 public static final int SHA512_LENGTH = 64;
60
61
62 public static final int MD5_LENGTH = 16;
63
64
65 public static final int PKCS5S2_LENGTH = 32;
66
67
68 public static final int CRYPT_LENGTH = 11;
69
70
71 public static final int CRYPT_MD5_LENGTH = 22;
72
73
74 public static final int CRYPT_SHA256_LENGTH = 43;
75
76
77 public static final int CRYPT_SHA512_LENGTH = 86;
78
79
80 public static final int CRYPT_BCRYPT_LENGTH = 31;
81
82 private static final byte[] CRYPT_SALT_CHARS = Strings
83 .getBytesUtf8( "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" );
84
85
86 private PasswordUtil()
87 {
88 }
89
90
91
92
93
94
95
96
97
98 public static LdapSecurityConstants findAlgorithm( byte[] credentials )
99 {
100 if ( ( credentials == null ) || ( credentials.length == 0 ) )
101 {
102 return null;
103 }
104
105 if ( credentials[0] == '{' )
106 {
107
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
125 return null;
126 }
127
128 String algorithm = Strings.toLowerCaseAscii( Strings.utf8ToString( credentials, 1, pos - 1 ) );
129
130
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
149 return null;
150 }
151 }
152 else
153 {
154
155 return null;
156 }
157 }
158
159
160
161
162
163
164
165
166
167 public static byte[] createStoragePassword( String credentials, LdapSecurityConstants algorithm )
168 {
169 return createStoragePassword( Strings.getBytesUtf8( credentials ), algorithm );
170 }
171
172
173
174
175
176
177
178
179
180
181 public static byte[] createStoragePassword( byte[] credentials, LdapSecurityConstants algorithm )
182 {
183
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
199 salt = new byte[8];
200 new SecureRandom().nextBytes( salt );
201 break;
202
203 case HASH_METHOD_PKCS5S2:
204
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
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317 public static boolean compareCredentials( byte[] receivedCredentials, byte[] storedCredentials )
318 {
319 LdapSecurityConstants algorithm = findAlgorithm( storedCredentials );
320
321 if ( algorithm != null )
322 {
323
324
325
326
327
328 PasswordDetails passwordDetails = PasswordUtil.splitCredentials( storedCredentials );
329
330
331
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
346
347
348
349
350
351
352 private static boolean compareBytes( byte[] provided, byte[] stored )
353 {
354 if ( stored == null )
355 {
356 return provided == null;
357 }
358 else if ( provided == null )
359 {
360 return false;
361 }
362
363
364 if ( stored.length != provided.length )
365 {
366 return false;
367 }
368
369
370 int result = 0;
371
372 for ( int i = 0; i < stored.length; i++ )
373 {
374
375 result |= ( stored[i] ^ provided[i] );
376 }
377
378 return result == 0;
379 }
380
381
382
383
384
385
386
387
388
389
390 private static byte[] encryptPassword( byte[] credentials, LdapSecurityConstants algorithm, byte[] salt )
391 {
392 switch ( algorithm )
393 {
394 case HASH_METHOD_SHA:
395 case HASH_METHOD_SSHA:
396 return digest( LdapSecurityConstants.HASH_METHOD_SHA, credentials, salt );
397
398 case HASH_METHOD_SHA256:
399 case HASH_METHOD_SSHA256:
400 return digest( LdapSecurityConstants.HASH_METHOD_SHA256, credentials, salt );
401
402 case HASH_METHOD_SHA384:
403 case HASH_METHOD_SSHA384:
404 return digest( LdapSecurityConstants.HASH_METHOD_SHA384, credentials, salt );
405
406 case HASH_METHOD_SHA512:
407 case HASH_METHOD_SSHA512:
408 return digest( LdapSecurityConstants.HASH_METHOD_SHA512, credentials, salt );
409
410 case HASH_METHOD_MD5:
411 case HASH_METHOD_SMD5:
412 return digest( LdapSecurityConstants.HASH_METHOD_MD5, credentials, salt );
413
414 case HASH_METHOD_CRYPT:
415 String saltWithCrypted = Crypt.crypt( Strings.utf8ToString( credentials ), Strings
416 .utf8ToString( salt ) );
417 String crypted = saltWithCrypted.substring( 2 );
418 return Strings.getBytesUtf8( crypted );
419
420 case HASH_METHOD_CRYPT_MD5:
421 case HASH_METHOD_CRYPT_SHA256:
422 case HASH_METHOD_CRYPT_SHA512:
423 String saltWithCrypted2 = Crypt.crypt( Strings.utf8ToString( credentials ),
424 algorithm.getSubPrefix() + Strings.utf8ToString( salt ) );
425 String crypted2 = saltWithCrypted2.substring( saltWithCrypted2.lastIndexOf( '$' ) + 1 );
426 return Strings.getBytesUtf8( crypted2 );
427
428 case HASH_METHOD_CRYPT_BCRYPT:
429 String crypted3 = BCrypt.hashPw( Strings.utf8ToString( credentials ), Strings.utf8ToString( salt ) );
430 return Strings.getBytesUtf8( crypted3.substring( crypted3.length() - 31 ) );
431
432 case HASH_METHOD_PKCS5S2:
433 return generatePbkdf2Hash( credentials, algorithm, salt );
434
435 default:
436 return credentials;
437 }
438 }
439
440
441
442
443
444
445
446
447
448
449
450 private static byte[] digest( LdapSecurityConstants algorithm, byte[] password, byte[] salt )
451 {
452 MessageDigest digest;
453
454 try
455 {
456 digest = MessageDigest.getInstance( algorithm.getAlgorithm() );
457 }
458 catch ( NoSuchAlgorithmException e1 )
459 {
460 return null;
461 }
462
463 if ( salt != null )
464 {
465 digest.update( password );
466 digest.update( salt );
467 return digest.digest();
468 }
469 else
470 {
471 return digest.digest( password );
472 }
473 }
474
475
476
477
478
479
480
481
482
483
484
485
486 public static PasswordDetails splitCredentials( byte[] credentials )
487 {
488 LdapSecurityConstants algorithm = findAlgorithm( credentials );
489
490
491 if ( algorithm == null )
492 {
493 return new PasswordDetails( null, null, credentials );
494 }
495
496 int algoLength = algorithm.getPrefix().length() + 2;
497 byte[] password;
498
499 switch ( algorithm )
500 {
501 case HASH_METHOD_MD5:
502 case HASH_METHOD_SMD5:
503 return getCredentials( credentials, algoLength, MD5_LENGTH, algorithm );
504
505 case HASH_METHOD_SHA:
506 case HASH_METHOD_SSHA:
507 return getCredentials( credentials, algoLength, SHA1_LENGTH, algorithm );
508
509 case HASH_METHOD_SHA256:
510 case HASH_METHOD_SSHA256:
511 return getCredentials( credentials, algoLength, SHA256_LENGTH, algorithm );
512
513 case HASH_METHOD_SHA384:
514 case HASH_METHOD_SSHA384:
515 return getCredentials( credentials, algoLength, SHA384_LENGTH, algorithm );
516
517 case HASH_METHOD_SHA512:
518 case HASH_METHOD_SSHA512:
519 return getCredentials( credentials, algoLength, SHA512_LENGTH, algorithm );
520
521 case HASH_METHOD_PKCS5S2:
522 return getPbkdf2Credentials( credentials, algoLength, algorithm );
523
524 case HASH_METHOD_CRYPT:
525
526
527
528
529 byte[] salt = new byte[2];
530 password = new byte[credentials.length - salt.length - algoLength];
531 split( credentials, algoLength, salt, password );
532 return new PasswordDetails( algorithm, salt, password );
533
534 case HASH_METHOD_CRYPT_BCRYPT:
535 salt = Arrays.copyOfRange( credentials, algoLength, credentials.length - 31 );
536 password = Arrays.copyOfRange( credentials, credentials.length - 31, credentials.length );
537
538 return new PasswordDetails( algorithm, salt, password );
539 case HASH_METHOD_CRYPT_MD5:
540 case HASH_METHOD_CRYPT_SHA256:
541 case HASH_METHOD_CRYPT_SHA512:
542
543 algoLength = algoLength + 3;
544 return getCryptCredentials( credentials, algoLength, algorithm );
545
546 default:
547
548 throw new IllegalArgumentException( "Unknown hash algorithm " + algorithm );
549 }
550 }
551
552
553
554
555
556 private static PasswordDetails getCredentials( byte[] credentials, int algoLength, int hashLen,
557 LdapSecurityConstants algorithm )
558 {
559
560
561
562
563 byte[] passwordAndSalt = Base64
564 .decode( Strings.utf8ToString( credentials, algoLength, credentials.length - algoLength ).toCharArray() );
565
566 int saltLength = passwordAndSalt.length - hashLen;
567 byte[] salt = saltLength == 0 ? null : new byte[saltLength];
568 byte[] password = new byte[hashLen];
569 split( passwordAndSalt, 0, password, salt );
570
571 return new PasswordDetails( algorithm, salt, password );
572 }
573
574
575 private static void split( byte[] all, int offset, byte[] left, byte[] right )
576 {
577 System.arraycopy( all, offset, left, 0, left.length );
578 if ( right != null )
579 {
580 System.arraycopy( all, offset + left.length, right, 0, right.length );
581 }
582 }
583
584
585 private static void merge( byte[] all, byte[] left, byte[] right )
586 {
587 System.arraycopy( left, 0, all, 0, left.length );
588 System.arraycopy( right, 0, all, left.length, right.length );
589 }
590
591
592
593
594
595
596
597
598
599 public static boolean isPwdExpired( String pwdChangedZtime, int pwdMaxAgeSec )
600 {
601 Date pwdChangeDate = DateUtils.getDate( pwdChangedZtime );
602
603
604 long time = pwdMaxAgeSec * 1000L;
605 time += pwdChangeDate.getTime();
606
607 Date expiryDate = DateUtils.getDate( DateUtils.getGeneralizedTime( time ) );
608 Date now = DateUtils.getDate( DateUtils.getGeneralizedTime() );
609
610 boolean expired = false;
611
612 if ( expiryDate.equals( now ) || expiryDate.before( now ) )
613 {
614 expired = true;
615 }
616
617 return expired;
618 }
619
620
621
622
623
624
625
626
627
628
629
630
631 private static byte[] generatePbkdf2Hash( byte[] credentials, LdapSecurityConstants algorithm, byte[] salt )
632 {
633 try
634 {
635 SecretKeyFactory sk = SecretKeyFactory.getInstance( algorithm.getAlgorithm() );
636 char[] password = Strings.utf8ToString( credentials ).toCharArray();
637 KeySpec keySpec = new PBEKeySpec( password, salt, 10000, PKCS5S2_LENGTH * 8 );
638 Key key = sk.generateSecret( keySpec );
639 return key.getEncoded();
640 }
641 catch ( Exception e )
642 {
643 throw new RuntimeException( e );
644 }
645 }
646
647
648
649
650
651
652 private static PasswordDetails getPbkdf2Credentials( byte[] credentials, int algoLength, LdapSecurityConstants algorithm )
653 {
654
655
656
657
658 byte[] passwordAndSalt = Base64
659 .decode( Strings.utf8ToString( credentials, algoLength, credentials.length - algoLength ).toCharArray() );
660
661 int saltLength = passwordAndSalt.length - PKCS5S2_LENGTH;
662 byte[] salt = new byte[saltLength];
663 byte[] password = new byte[PKCS5S2_LENGTH];
664
665 split( passwordAndSalt, 0, salt, password );
666
667 return new PasswordDetails( algorithm, salt, password );
668 }
669
670
671 private static byte[] generateCryptSalt( int length )
672 {
673 byte[] salt = new byte[length];
674 SecureRandom sr = new SecureRandom();
675 for ( int i = 0; i < salt.length; i++ )
676 {
677 salt[i] = CRYPT_SALT_CHARS[sr.nextInt( CRYPT_SALT_CHARS.length )];
678 }
679
680 return salt;
681 }
682
683
684 private static PasswordDetails getCryptCredentials( byte[] credentials, int algoLength,
685 LdapSecurityConstants algorithm )
686 {
687
688
689
690
691
692
693 int pos = algoLength;
694 while ( pos < credentials.length )
695 {
696 if ( credentials[pos] == '$' )
697 {
698 break;
699 }
700
701 pos++;
702 }
703
704 byte[] salt = Arrays.copyOfRange( credentials, algoLength, pos );
705 byte[] password = Arrays.copyOfRange( credentials, pos + 1, credentials.length );
706
707 return new PasswordDetails( algorithm, salt, password );
708 }
709
710 }