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.io.UnsupportedEncodingException;
25 import java.security.Key;
26 import java.security.MessageDigest;
27 import java.security.NoSuchAlgorithmException;
28 import java.security.SecureRandom;
29 import java.security.spec.KeySpec;
30 import java.util.Arrays;
31 import java.util.Date;
32
33 import javax.crypto.SecretKeyFactory;
34 import javax.crypto.spec.PBEKeySpec;
35
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 import org.apache.directory.api.util.UnixCrypt;
41
42
43
44
45
46
47
48 public class PasswordUtil
49 {
50
51
52 public static final int SHA1_LENGTH = 20;
53
54
55 public static final int SHA256_LENGTH = 32;
56
57
58 public static final int SHA384_LENGTH = 48;
59
60
61 public static final int SHA512_LENGTH = 64;
62
63
64 public static final int MD5_LENGTH = 16;
65
66
67 public static final int PKCS5S2_LENGTH = 32;
68
69
70
71
72
73
74
75
76 public static LdapSecurityConstants findAlgorithm( byte[] credentials )
77 {
78 if ( ( credentials == null ) || ( credentials.length == 0 ) )
79 {
80 return null;
81 }
82
83 if ( credentials[0] == '{' )
84 {
85
86 int pos = 1;
87
88 while ( pos < credentials.length )
89 {
90 if ( credentials[pos] == '}' )
91 {
92 break;
93 }
94
95 pos++;
96 }
97
98 if ( pos < credentials.length )
99 {
100 if ( pos == 1 )
101 {
102
103 return null;
104 }
105
106 String algorithm = Strings.toLowerCase( new String( credentials, 1, pos - 1 ) );
107
108 return LdapSecurityConstants.getAlgorithm( algorithm );
109 }
110 else
111 {
112
113 return null;
114 }
115 }
116 else
117 {
118
119 return null;
120 }
121 }
122
123
124
125
126
127 public static byte[] createStoragePassword( String credentials, LdapSecurityConstants algorithm )
128 {
129 return createStoragePassword( Strings.getBytesUtf8( credentials ), algorithm );
130 }
131
132
133
134
135
136
137
138
139
140
141 public static byte[] createStoragePassword( byte[] credentials, LdapSecurityConstants algorithm )
142 {
143 byte[] salt;
144
145 switch ( algorithm )
146 {
147 case HASH_METHOD_SSHA:
148 case HASH_METHOD_SSHA256:
149 case HASH_METHOD_SSHA384:
150 case HASH_METHOD_SSHA512:
151 case HASH_METHOD_SMD5:
152 salt = new byte[8];
153 new SecureRandom().nextBytes( salt );
154 break;
155
156 case HASH_METHOD_PKCS5S2:
157 salt = new byte[16];
158 new SecureRandom().nextBytes( salt );
159 break;
160
161 case HASH_METHOD_CRYPT:
162 salt = new byte[2];
163 SecureRandom sr = new SecureRandom();
164 int i1 = sr.nextInt( 64 );
165 int i2 = sr.nextInt( 64 );
166
167 salt[0] = ( byte ) ( i1 < 12 ? ( i1 + '.' ) : i1 < 38 ? ( i1 + 'A' - 12 ) : ( i1 + 'a' - 38 ) );
168 salt[1] = ( byte ) ( i2 < 12 ? ( i2 + '.' ) : i2 < 38 ? ( i2 + 'A' - 12 ) : ( i2 + 'a' - 38 ) );
169 break;
170
171 default:
172 salt = null;
173 }
174
175 byte[] hashedPassword = encryptPassword( credentials, algorithm, salt );
176 StringBuffer sb = new StringBuffer();
177
178 if ( algorithm != null )
179 {
180 sb.append( '{' ).append( algorithm.getPrefix().toUpperCase() ).append( '}' );
181
182 if ( algorithm == LdapSecurityConstants.HASH_METHOD_CRYPT )
183 {
184 sb.append( Strings.utf8ToString( salt ) );
185 sb.append( Strings.utf8ToString( hashedPassword ) );
186 }
187 else if ( salt != null )
188 {
189 byte[] hashedPasswordWithSaltBytes = new byte[hashedPassword.length + salt.length];
190
191 if ( algorithm == LdapSecurityConstants.HASH_METHOD_PKCS5S2 )
192 {
193 merge( hashedPasswordWithSaltBytes, salt, hashedPassword );
194 }
195 else
196 {
197 merge( hashedPasswordWithSaltBytes, hashedPassword, salt );
198 }
199
200 sb.append( String.valueOf( Base64.encode( hashedPasswordWithSaltBytes ) ) );
201 }
202 else
203 {
204 sb.append( String.valueOf( Base64.encode( hashedPassword ) ) );
205 }
206 }
207 else
208 {
209 sb.append( Strings.utf8ToString( hashedPassword ) );
210 }
211
212 return Strings.getBytesUtf8( sb.toString() );
213 }
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262 public static boolean compareCredentials( byte[] receivedCredentials, byte[] storedCredentials )
263 {
264 LdapSecurityConstants algorithm = findAlgorithm( storedCredentials );
265
266 if ( algorithm != null )
267 {
268 EncryptionMethod encryptionMethod = new EncryptionMethod( algorithm, null );
269
270
271
272
273
274
275 byte[] encryptedStored = PasswordUtil.splitCredentials( storedCredentials, encryptionMethod );
276
277
278
279 byte[] userPassword = PasswordUtil.encryptPassword( receivedCredentials, encryptionMethod.getAlgorithm(),
280 encryptionMethod.getSalt() );
281
282
283 return Arrays.equals( userPassword, encryptedStored );
284 }
285 else
286 {
287 return Arrays.equals( storedCredentials, receivedCredentials );
288 }
289 }
290
291
292
293
294
295
296
297
298
299
300 public static byte[] encryptPassword( byte[] credentials, LdapSecurityConstants algorithm, byte[] salt )
301 {
302 switch ( algorithm )
303 {
304 case HASH_METHOD_SHA:
305 case HASH_METHOD_SSHA:
306 return digest( LdapSecurityConstants.HASH_METHOD_SHA, credentials, salt );
307
308 case HASH_METHOD_SHA256:
309 case HASH_METHOD_SSHA256:
310 return digest( LdapSecurityConstants.HASH_METHOD_SHA256, credentials, salt );
311
312 case HASH_METHOD_SHA384:
313 case HASH_METHOD_SSHA384:
314 return digest( LdapSecurityConstants.HASH_METHOD_SHA384, credentials, salt );
315
316 case HASH_METHOD_SHA512:
317 case HASH_METHOD_SSHA512:
318 return digest( LdapSecurityConstants.HASH_METHOD_SHA512, credentials, salt );
319
320 case HASH_METHOD_MD5:
321 case HASH_METHOD_SMD5:
322 return digest( LdapSecurityConstants.HASH_METHOD_MD5, credentials, salt );
323
324 case HASH_METHOD_CRYPT:
325 String saltWithCrypted = UnixCrypt.crypt( Strings.utf8ToString( credentials ), Strings
326 .utf8ToString( salt ) );
327 String crypted = saltWithCrypted.substring( 2 );
328
329 return Strings.getBytesUtf8( crypted );
330
331 case HASH_METHOD_PKCS5S2:
332 return generatePbkdf2Hash( credentials, algorithm, salt );
333
334 default:
335 return credentials;
336 }
337 }
338
339
340
341
342
343
344
345
346
347
348
349 private static byte[] digest( LdapSecurityConstants algorithm, byte[] password, byte[] salt )
350 {
351 MessageDigest digest;
352
353 try
354 {
355 digest = MessageDigest.getInstance( algorithm.getAlgorithm() );
356 }
357 catch ( NoSuchAlgorithmException e1 )
358 {
359 return null;
360 }
361
362 if ( salt != null )
363 {
364 digest.update( password );
365 digest.update( salt );
366 return digest.digest();
367 }
368 else
369 {
370 return digest.digest( password );
371 }
372 }
373
374
375
376
377
378
379
380
381
382
383
384
385
386 public static byte[] splitCredentials( byte[] credentials, EncryptionMethod encryptionMethod )
387 {
388 int algoLength = encryptionMethod.getAlgorithm().getPrefix().length() + 2;
389
390 switch ( encryptionMethod.getAlgorithm() )
391 {
392 case HASH_METHOD_MD5:
393 case HASH_METHOD_SHA:
394 try
395 {
396
397
398 return Base64
399 .decode( new String( credentials, algoLength, credentials.length - algoLength, "UTF-8" )
400 .toCharArray() );
401 }
402 catch ( UnsupportedEncodingException uee )
403 {
404
405 return credentials;
406 }
407
408 case HASH_METHOD_SMD5:
409 try
410 {
411
412
413
414
415 byte[] passwordAndSalt = Base64.decode( new String( credentials, algoLength, credentials.length
416 - algoLength, "UTF-8" ).toCharArray() );
417
418 int saltLength = passwordAndSalt.length - MD5_LENGTH;
419 encryptionMethod.setSalt( new byte[saltLength] );
420 byte[] password = new byte[MD5_LENGTH];
421 split( passwordAndSalt, 0, password, encryptionMethod.getSalt() );
422
423 return password;
424 }
425 catch ( UnsupportedEncodingException uee )
426 {
427
428 return credentials;
429 }
430
431 case HASH_METHOD_SSHA:
432 return getCredentials( credentials, algoLength, SHA1_LENGTH, encryptionMethod );
433
434 case HASH_METHOD_SHA256:
435 case HASH_METHOD_SSHA256:
436 return getCredentials( credentials, algoLength, SHA256_LENGTH, encryptionMethod );
437
438 case HASH_METHOD_SHA384:
439 case HASH_METHOD_SSHA384:
440 return getCredentials( credentials, algoLength, SHA384_LENGTH, encryptionMethod );
441
442 case HASH_METHOD_SHA512:
443 case HASH_METHOD_SSHA512:
444 return getCredentials( credentials, algoLength, SHA512_LENGTH, encryptionMethod );
445
446 case HASH_METHOD_PKCS5S2:
447 return getPbkdf2Credentials( credentials, algoLength, encryptionMethod );
448
449 case HASH_METHOD_CRYPT:
450
451
452
453 encryptionMethod.setSalt( new byte[2] );
454 byte[] password = new byte[credentials.length - encryptionMethod.getSalt().length - algoLength];
455 split( credentials, algoLength, encryptionMethod.getSalt(), password );
456
457 return password;
458
459 default:
460
461 return credentials;
462
463 }
464 }
465
466
467
468
469
470 private static byte[] getCredentials( byte[] credentials, int algoLength, int hashLen,
471 EncryptionMethod encryptionMethod )
472 {
473 try
474 {
475
476
477
478
479 byte[] passwordAndSalt = Base64.decode( new String( credentials, algoLength, credentials.length
480 - algoLength, "UTF-8" ).toCharArray() );
481
482 int saltLength = passwordAndSalt.length - hashLen;
483 encryptionMethod.setSalt( new byte[saltLength] );
484 byte[] password = new byte[hashLen];
485 split( passwordAndSalt, 0, password, encryptionMethod.getSalt() );
486
487 return password;
488 }
489 catch ( UnsupportedEncodingException uee )
490 {
491
492 return credentials;
493 }
494 }
495
496
497 private static void split( byte[] all, int offset, byte[] left, byte[] right )
498 {
499 System.arraycopy( all, offset, left, 0, left.length );
500 System.arraycopy( all, offset + left.length, right, 0, right.length );
501 }
502
503
504 private static void merge( byte[] all, byte[] left, byte[] right )
505 {
506 System.arraycopy( left, 0, all, 0, left.length );
507 System.arraycopy( right, 0, all, left.length, right.length );
508 }
509
510
511
512
513
514
515
516
517
518 public static boolean isPwdExpired( String pwdChangedZtime, int pwdMaxAgeSec )
519 {
520 Date pwdChangeDate = DateUtils.getDate( pwdChangedZtime );
521
522 long time = pwdMaxAgeSec * 1000L;
523 time += pwdChangeDate.getTime();
524
525 Date expiryDate = DateUtils.getDate( DateUtils.getGeneralizedTime( time ) );
526 Date now = DateUtils.getDate( DateUtils.getGeneralizedTime() );
527
528 boolean expired = false;
529
530 if ( expiryDate.equals( now ) || expiryDate.before( now ) )
531 {
532 expired = true;
533 }
534
535 return expired;
536 }
537
538
539
540
541
542
543
544
545
546
547
548
549 private static byte[] generatePbkdf2Hash( byte[] credentials, LdapSecurityConstants algorithm, byte[] salt )
550 {
551 try
552 {
553 SecretKeyFactory sk = SecretKeyFactory.getInstance( algorithm.getAlgorithm() );
554 char[] password = Strings.utf8ToString( credentials ).toCharArray();
555 KeySpec keySpec = new PBEKeySpec( password, salt, 10000, PKCS5S2_LENGTH * 8 );
556 Key key = sk.generateSecret( keySpec );
557 return key.getEncoded();
558 }
559 catch( Exception e )
560 {
561 throw new RuntimeException( e );
562 }
563 }
564
565
566
567
568
569
570 private static byte[] getPbkdf2Credentials( byte[] credentials, int algoLength, EncryptionMethod encryptionMethod )
571 {
572 try
573 {
574
575
576
577
578 byte[] passwordAndSalt = Base64.decode( new String( credentials, algoLength, credentials.length
579 - algoLength, "UTF-8" ).toCharArray() );
580
581 int saltLength = passwordAndSalt.length - PKCS5S2_LENGTH;
582 encryptionMethod.setSalt( new byte[saltLength] );
583 byte[] password = new byte[PKCS5S2_LENGTH];
584
585 split( passwordAndSalt, 0, encryptionMethod.getSalt(), password );
586
587 return password;
588 }
589 catch ( UnsupportedEncodingException uee )
590 {
591
592 return credentials;
593 }
594 }
595
596 }