SoapPasswordEncoder.java
/*
* Copyright 2019 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gringlobal.spring;
import java.security.SecureRandom;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* GRIN-Global uses SHA-1 hashed and Base 64 encoded passwords
*
* @author Matija Obreza
*/
@Slf4j
public class SoapPasswordEncoder implements PasswordEncoder {
private final SecureRandom secureRandom = new SecureRandom();
@Override
public String encode(final CharSequence rawPassword) {
if (StringUtils.isBlank(rawPassword)) {
return null;
}
log.trace("Encoding password {}", rawPassword);
return encodeNew(hashText(rawPassword.toString()));
}
@Override
public boolean matches(final CharSequence rawPassword, final String encodedPassword) {
// Sometimes we get raw password, sometimes we get hashes
// System.err.println("Testing '" + rawPassword + "' against '" +
// encodedPassword + "'");
final String password = rawPassword.toString();
final String encodeOld = hashText(password);
log.trace("Raw password: {}", rawPassword);
log.trace("Stored password: {}", encodedPassword);
log.trace("Encode old: {}", encodeOld);
if (encodedPassword.equals(encodeOld) || rawPassword.equals(encodedPassword)) {
return true;
}
return comparePasswordHash(encodedPassword, password) || comparePasswordHash(encodedPassword, encodeOld);
}
private boolean comparePasswordHash(final String encodedPassword, final String password) {
final String[] hashes = encodedPassword.split(":");
final String[] passField = hashes[0].split("\\$");
String salt;
String storedHash;
String hashedPassword;
if (passField.length == 1) {
// original format of SHA1 hash with no salt
// crypt = "SHA1";
salt = "";
storedHash = passField[0];
hashedPassword = Base64.encodeBase64String(DigestUtils.sha1(salt + password));
} else if (passField.length == 2) {
// two fields means salt and hash
// crypt = "SHA256";
salt = passField[0];
storedHash = passField[1];
hashedPassword = Base64.encodeBase64String(DigestUtils.sha256(salt + password));
// } else if (passField.length == 3) {
// // with three fields the first is the hash type
// crypt = passField[0];
// salt = passField[1];
// storedHash = passField[2];
} else {
// can't figure out what is stored in the hash field
log.warn("Unsupported password crpyo {}", passField[0]);
return false;
}
log.trace("salt={} storedHash={} hashed={}", salt, storedHash, hashedPassword);
return hashedPassword.equals(storedHash);
}
/**
* Original SHA1 as Base64
*
* @param password plain text password
* @return SHA1 of the plain text password as Base64
*/
public String hashText(final String password) {
return Base64.encodeBase64String(DigestUtils.sha1(password));
}
private String encodeNew(final String password) {
return saltAndHash(password);
}
private String saltAndHash(final String password) {
final String salt64 = saltText(6);
final byte[] hash = DigestUtils.sha256(salt64 + password);
return salt64 + "$" + Base64.encodeBase64String(hash);
}
private String saltText(final int length) {
final byte[] salt = new byte[length];
secureRandom.nextBytes(salt);
return Base64.encodeBase64String(salt);
}
}