/* * Copyright 2014-2019 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * 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.hawkular.openshift.auth; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.util.Base64; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.Md5Crypt; import org.apache.commons.codec.digest.Sha2Crypt; public class PasswordManager { private static final String MD5_PREFIX = "$apr1$"; private static final String SHA_PREFIX = "{SHA}"; private static final String SHA256_PREFIX = "$5$"; private static final String SHA512_PREFIX = "$6$"; private static final String PBKDF2_SHA256_PREFIX = "$pbkdf2-sha256$"; private static final String PBKDF2_SHA512_PREFIX = "$pbkdf2-sha512$"; private static final int DEFAULT_ITERATIONS_PBKDF2 = 25000; public boolean isAuthorized(String storedCredential, String passedPassword) { return (storedCredential.startsWith(MD5_PREFIX) && verifyMD5Password(storedCredential, passedPassword)) || (storedCredential.startsWith(SHA_PREFIX) && verifySHA1Password(storedCredential, passedPassword)) || (storedCredential.startsWith(SHA256_PREFIX) && verifySHA256Password(storedCredential, passedPassword)) || (storedCredential.startsWith(SHA512_PREFIX) && verifySHA512Password(storedCredential, passedPassword)) || (storedCredential.startsWith(PBKDF2_SHA256_PREFIX) && verifyPBDKF2Password(storedCredential, passedPassword)) || (storedCredential.startsWith(PBKDF2_SHA512_PREFIX) && verifyPBDKF2Password(storedCredential, passedPassword)); } private boolean verifyMD5Password(String storedCredential, String passedPassword) { // We send in the password presented by the user and use the stored password as the salt // If they match, then the password matches the original non-encrypted stored password return Md5Crypt.apr1Crypt(passedPassword, storedCredential).equals(storedCredential); } private boolean verifySHA1Password(String storedCredential, String passedPassword) { //Remove the SHA_PREFIX from the password string String storedPassword = storedCredential.substring(SHA_PREFIX.length()); //Get the SHA digest and encode it in Base64 byte[] digestedPasswordBytes = DigestUtils.sha1(passedPassword); String digestedPassword = Base64.getEncoder().encodeToString(digestedPasswordBytes); //Check if the stored password matches the passed one return digestedPassword.equals(storedPassword); } private boolean verifySHA256Password(String storedCredential, String passedPassword) { //Obtain the salt from the beginning of the storedPassword String salt = storedCredential.substring(0, storedCredential.lastIndexOf("$") + 1); String digestedPassword = Sha2Crypt.sha256Crypt(passedPassword.getBytes(), salt); //Check if the stored password matches the passed one return digestedPassword.equals(storedCredential); } private boolean verifySHA512Password(String storedCredential, String passedPassword) { //Obtain the salt from the beginning of the storedPassword String salt = storedCredential.substring(0, storedCredential.lastIndexOf("$") + 1); String digestedPassword = Sha2Crypt.sha512Crypt(passedPassword.getBytes(), salt); //Check if the stored password matches the passed one return digestedPassword.equals(storedCredential); } private boolean verifyPBDKF2Password(String storedCredential, String passedPassword) { String[] creds = storedCredential.split("\\$"); if (creds.length != 5) { // TODO verify ok to throw exception here if using other password types throw new RuntimeException("Stored password checksum not valid. Check password store."); } String pbkdf2Algorithm = "PBKDF2WithHmacSHA256"; int keySize = Base64.getDecoder().decode(creds[4]).length * 8; if (keySize == 256) { // default } else if (keySize == 512) { pbkdf2Algorithm = "PBKDF2WithHmacSHA512"; } else { throw new RuntimeException("Stored password is not a valid size. Check password store."); } byte[] salt = Base64.getDecoder().decode(creds[3].getBytes()); String encoded = encodePBDKF2(passedPassword, Integer.parseInt(creds[2]), salt, keySize, pbkdf2Algorithm); return encoded.equals(creds[4]); } private String encodePBDKF2(String rawPassword, int iterations, byte[] salt, int keySize, String pbkdf2Algorithm) { KeySpec spec = new PBEKeySpec(rawPassword.toCharArray(), salt, iterations, keySize); try { byte[] key = SecretKeyFactory.getInstance(pbkdf2Algorithm).generateSecret(spec).getEncoded(); return Base64.getEncoder().encodeToString(key); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("PBKDF2 algorithm not found", e); } catch (InvalidKeySpecException e) { throw new RuntimeException("Password could not be encoded", e); } catch (Exception e) { throw new RuntimeException(e); } } public String createPBDKF2SHA256Password(String password) { return createPBDKF2Password(password, DEFAULT_ITERATIONS_PBKDF2, 256); } public String createPBDKF2SHA512Password(String password) { return createPBDKF2Password(password, DEFAULT_ITERATIONS_PBKDF2, 512); } private String createPBDKF2Password(String password, int iterations, int keySize) { String prefix = PBKDF2_SHA256_PREFIX; String pbkdf2Algorithm = "PBKDF2WithHmacSHA256"; if (keySize == 256) { // default } else if (keySize == 512) { prefix = PBKDF2_SHA512_PREFIX; pbkdf2Algorithm = "PBKDF2WithHmacSHA512"; } else { throw new RuntimeException("Keysize must be 256 or 512"); } if (iterations < 10000) { throw new RuntimeException("Iterations must be above 10000 when specified."); } byte[] salt = getSalt(); String saltEncoded = Base64.getEncoder().encodeToString(salt); String checksum = encodePBDKF2(password, iterations, salt, keySize, pbkdf2Algorithm); return prefix + iterations + "$" + saltEncoded + "$" + checksum; } private byte[] getSalt() { byte[] buffer = new byte[16]; SecureRandom secureRandom = new SecureRandom(); secureRandom.nextBytes(buffer); return buffer; } }