1 /*
2 * Licensed to The Apereo Foundation under one or more contributor license
3 * agreements. See the NOTICE file distributed with this work for additional
4 * information regarding copyright ownership.
5 *
6 *
7 * The Apereo Foundation licenses this file to you under the Educational
8 * Community License, Version 2.0 (the "License"); you may not use this file
9 * except in compliance with the License. You may obtain a copy of the License
10 * at:
11 *
12 * http://opensource.org/licenses/ecl2.txt
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17 * License for the specific language governing permissions and limitations under
18 * the License.
19 *
20 */
21
22 package org.opencastproject.kernel.security;
23
24 import org.apache.commons.codec.digest.DigestUtils;
25 import org.apache.commons.lang3.StringUtils;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28 import org.springframework.security.authentication.encoding.PasswordEncoder;
29 import org.springframework.security.crypto.bcrypt.BCrypt;
30
31 /**
32 * Password encoder using bcrypt for password hashing while still supporting the verification of olf md5 based
33 * passwords.
34 */
35 public class CustomPasswordEncoder implements PasswordEncoder {
36 private Logger logger = LoggerFactory.getLogger(CustomPasswordEncoder.class);
37
38 /**
39 * Encode the raw password for storage using bcrypt.
40 * @param rawPassword raw password to encrypt/hash
41 * @return hashed password
42 */
43 public String encodePassword(final String rawPassword) {
44 return encodePassword(rawPassword, null);
45 }
46
47 /**
48 * Encode the raw password for storage using bcrypt.
49 * @param rawPassword raw password to encrypt/hash
50 * @param ignored This parameter will not be used. A random salt is generated by bcrypt.
51 * @return hashed password
52 */
53 @Override
54 public String encodePassword(final String rawPassword, final Object ignored) {
55 return BCrypt.hashpw(rawPassword, BCrypt.gensalt());
56 }
57
58 /**
59 * Verify the encoded password obtained from storage matches the submitted raw
60 * password after it too is encoded. Returns true if the passwords match, false if
61 * they do not. The stored password itself is never decoded.
62 *
63 * @param rawPassword the raw password to encode and match
64 * @param encodedPassword the encoded password from storage to compare with
65 * @return true if the raw password, after encoding, matches the encoded password from storage
66 */
67 @Override
68 public boolean isPasswordValid(String encodedPassword, String rawPassword, Object salt) {
69 // Test MD5 encoded hash
70 if (encodedPassword.length() == 32) {
71 final String hash = md5Encode(rawPassword, salt);
72 logger.debug("Checking md5 hashed password '{}' against encoded password '{}'", hash, encodedPassword);
73 return hash.equals(encodedPassword);
74 }
75
76 // Test BCrypt encoded hash
77 logger.debug("Verifying bcrypt hash {}", encodedPassword);
78 try {
79 return StringUtils.startsWith(encodedPassword, "$") && BCrypt.checkpw(rawPassword, encodedPassword);
80 } catch (IllegalArgumentException e) {
81 logger.debug("bcrypt hash verification failed", e);
82 }
83 return false;
84 }
85
86 /**
87 * Encode a clear text password using Opencast's legacy MD5 based hashing with salt.
88 * The username was used as salt for this.
89 *
90 * @param clearText
91 * the password
92 * @param salt
93 * the salt
94 * @return the hashed password
95 * @throws IllegalArgumentException
96 * if clearText or salt are null
97 */
98 public static String md5Encode(String clearText, Object salt) throws IllegalArgumentException {
99 if (clearText == null || salt == null)
100 throw new IllegalArgumentException("clearText and salt must not be null");
101 return DigestUtils.md5Hex(clearText + "{" + salt.toString() + "}");
102 }
103 }