Java tutorial
/* * Copyright (c) 2009 - 2017 CaspersBox Web Services * * 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 com.cws.esolutions.security.utils; /* * Project: eSolutionsSecurity * Package: com.cws.esolutions.security.utils * File: PasswordUtils.java * * History * * Author Date Comments * ---------------------------------------------------------------------------- * cws-khuntly 11/23/2008 22:39:20 Created. */ import javax.crypto.Mac; import org.slf4j.Logger; import java.util.Base64; import javax.crypto.Cipher; import javax.crypto.SecretKey; import org.slf4j.LoggerFactory; import java.security.MessageDigest; import javax.crypto.spec.PBEKeySpec; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.SecretKeySpec; import javax.crypto.BadPaddingException; import javax.crypto.spec.IvParameterSpec; import java.security.InvalidKeyException; import java.security.AlgorithmParameters; import javax.crypto.NoSuchPaddingException; import java.io.UnsupportedEncodingException; import javax.crypto.IllegalBlockSizeException; import org.apache.commons.codec.binary.Base32; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidParameterSpecException; import java.security.InvalidAlgorithmParameterException; import com.cws.esolutions.security.SecurityServiceConstants; /** * Performs password related functions, such as string encryption * and (where necessary) decryption, base64 decode/encode. * * @author cws-khuntly * @version 1.0 */ public final class PasswordUtils { private static final String CNAME = PasswordUtils.class.getName(); static final Logger DEBUGGER = LoggerFactory.getLogger(SecurityServiceConstants.DEBUGGER); static final boolean DEBUG = DEBUGGER.isDebugEnabled(); /** * Provides two-way (reversible) encryption of a provided string. Can be used where reversibility * is required but encryption (obfuscation, technically) is required. * * @param value - The plain text data to encrypt * @param salt - The salt value to utilize for the request * @param secretInstance - The cryptographic instance to use for the SecretKeyFactory * @param iterations - The number of times to loop through the keyspec * @param keyBits - The size of the key, in bits * @param algorithm - The algorithm to encrypt the data with * @param cipherInstance - The cipher instance to utilize * @param encoding - The text encoding * @return The encrypted string in a reversible format * @throws SecurityException {@link java.lang.SecurityException} if an exception occurs during processing */ public static final String encryptText(final String value, final String salt, final String secretInstance, final int iterations, final int keyBits, final String algorithm, final String cipherInstance, final String encoding) throws SecurityException { final String methodName = PasswordUtils.CNAME + "#encryptText(final String value, final String salt, final String secretInstance, final int iterations, final int keyBits, final String algorithm, final String cipherInstance, final String encoding) throws SecurityException"; if (DEBUG) { DEBUGGER.debug(methodName); DEBUGGER.debug("Value: {}", secretInstance); DEBUGGER.debug("Value: {}", iterations); DEBUGGER.debug("Value: {}", keyBits); DEBUGGER.debug("Value: {}", algorithm); DEBUGGER.debug("Value: {}", cipherInstance); DEBUGGER.debug("Value: {}", encoding); } String encPass = null; try { SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(secretInstance); PBEKeySpec keySpec = new PBEKeySpec(salt.toCharArray(), salt.getBytes(), iterations, keyBits); SecretKey keyTmp = keyFactory.generateSecret(keySpec); SecretKeySpec sks = new SecretKeySpec(keyTmp.getEncoded(), algorithm); Cipher pbeCipher = Cipher.getInstance(cipherInstance); pbeCipher.init(Cipher.ENCRYPT_MODE, sks); AlgorithmParameters parameters = pbeCipher.getParameters(); IvParameterSpec ivParameterSpec = parameters.getParameterSpec(IvParameterSpec.class); byte[] cryptoText = pbeCipher.doFinal(value.getBytes(encoding)); byte[] iv = ivParameterSpec.getIV(); String combined = Base64.getEncoder().encodeToString(iv) + ":" + Base64.getEncoder().encodeToString(cryptoText); encPass = Base64.getEncoder().encodeToString(combined.getBytes()); } catch (InvalidKeyException ikx) { throw new SecurityException(ikx.getMessage(), ikx); } catch (NoSuchAlgorithmException nsx) { throw new SecurityException(nsx.getMessage(), nsx); } catch (NoSuchPaddingException npx) { throw new SecurityException(npx.getMessage(), npx); } catch (IllegalBlockSizeException ibx) { throw new SecurityException(ibx.getMessage(), ibx); } catch (BadPaddingException bpx) { throw new SecurityException(bpx.getMessage(), bpx); } catch (UnsupportedEncodingException uex) { throw new SecurityException(uex.getMessage(), uex); } catch (InvalidKeySpecException iksx) { throw new SecurityException(iksx.getMessage(), iksx); } catch (InvalidParameterSpecException ipsx) { throw new SecurityException(ipsx.getMessage(), ipsx); } return encPass; } /** * Provides one-way (irreversible) encryption of a provided string. * * @param plainText - The plain text data to encrypt * @param salt - The salt value to utilize for the request * @param instance - The security instance to utilize * @param iterations - The number of times the value should be re-encrypted * @param encoding - The text encoding * @return The encrypted string * @throws SecurityException {@link java.lang.SecurityException} if an exception occurs during processing */ public static final String encryptText(final String plainText, final String salt, final String instance, final int iterations, final String encoding) throws SecurityException { final String methodName = PasswordUtils.CNAME + "#encryptText(final String plainText, final String salt, final String algorithm, final String instance, final int iterations, final String encoding) throws SecurityException"; if (DEBUG) { DEBUGGER.debug(methodName); DEBUGGER.debug("Value: {}", plainText); DEBUGGER.debug("Value: {}", salt); DEBUGGER.debug("Value: {}", instance); DEBUGGER.debug("Value: {}", iterations); DEBUGGER.debug("Value: {}", encoding); } String response = null; try { MessageDigest md = MessageDigest.getInstance(instance); md.reset(); md.update(salt.getBytes(encoding)); byte[] input = md.digest(plainText.getBytes(encoding)); for (int x = 0; x < iterations; x++) { md.reset(); input = md.digest(input); } response = Base64.getEncoder().encodeToString(input); } catch (NoSuchAlgorithmException nsx) { throw new SecurityException(nsx.getMessage(), nsx); } catch (UnsupportedEncodingException uex) { throw new SecurityException(uex.getMessage(), uex); } return response; } /** * Provides two-way (reversible) encryption of a provided string. Can be used where reversibility * is required but encryption (obfuscation, technically) is required. * * @param value - The plain text data to encrypt * @param salt - The salt value to utilize for the request * @param secretInstance - The cryptographic instance to use for the SecretKeyFactory * @param iterations - The number of times to loop through the keyspec * @param keyBits - The size of the key, in bits * @param algorithm - The algorithm to encrypt the data with * @param cipherInstance - The cipher instance to utilize * @param encoding - The text encoding * @return The encrypted string in a reversible format * @throws SecurityException {@link java.lang.SecurityException} if an exception occurs during processing */ public static final String decryptText(final String value, final String salt, final String secretInstance, final int iterations, final int keyBits, final String algorithm, final String cipherInstance, final String encoding) throws SecurityException { final String methodName = PasswordUtils.CNAME + "#encryptText(final String value, final String salt, final String secretInstance, final int iterations, final int keyBits, final String algorithm, final String cipherInstance, final String encoding) throws SecurityException"; if (DEBUG) { DEBUGGER.debug(methodName); DEBUGGER.debug("Value: {}", secretInstance); DEBUGGER.debug("Value: {}", iterations); DEBUGGER.debug("Value: {}", keyBits); DEBUGGER.debug("Value: {}", algorithm); DEBUGGER.debug("Value: {}", cipherInstance); DEBUGGER.debug("Value: {}", encoding); } String decPass = null; try { String decoded = new String(Base64.getDecoder().decode(value)); String iv = decoded.split(":")[0]; String property = decoded.split(":")[1]; SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(secretInstance); PBEKeySpec keySpec = new PBEKeySpec(salt.toCharArray(), salt.getBytes(), iterations, keyBits); SecretKey keyTmp = keyFactory.generateSecret(keySpec); SecretKeySpec sks = new SecretKeySpec(keyTmp.getEncoded(), algorithm); Cipher pbeCipher = Cipher.getInstance(cipherInstance); pbeCipher.init(Cipher.DECRYPT_MODE, sks, new IvParameterSpec(Base64.getDecoder().decode(iv))); decPass = new String(pbeCipher.doFinal(Base64.getDecoder().decode(property)), encoding); } catch (InvalidKeyException ikx) { throw new SecurityException(ikx.getMessage(), ikx); } catch (NoSuchAlgorithmException nsx) { throw new SecurityException(nsx.getMessage(), nsx); } catch (NoSuchPaddingException npx) { throw new SecurityException(npx.getMessage(), npx); } catch (IllegalBlockSizeException ibx) { throw new SecurityException(ibx.getMessage(), ibx); } catch (BadPaddingException bpx) { throw new SecurityException(bpx.getMessage(), bpx); } catch (UnsupportedEncodingException uex) { throw new SecurityException(uex.getMessage(), uex); } catch (InvalidAlgorithmParameterException iapx) { throw new SecurityException(iapx.getMessage(), iapx); } catch (InvalidKeySpecException iksx) { throw new SecurityException(iksx.getMessage(), iksx); } return decPass; } /** * Base64 encodes a given string * * @param text - The text to base64 encode * @return The base64-encoded string * @throws SecurityException {@link java.lang.SecurityException} if an exception occurs during processing */ public static final String base64Encode(final String text) throws SecurityException { final String methodName = PasswordUtils.CNAME + "#base64Encode(final String text) throws SecurityException"; if (DEBUG) { DEBUGGER.debug(methodName); DEBUGGER.debug("Value: {}", text); } return Base64.getEncoder().encodeToString(text.getBytes()); } /** * Base64 decodes a given string * * @param text - The text to base64 decode * @return The base64-decoded string * @throws SecurityException {@link java.lang.SecurityException} if an exception occurs during processing */ public static final String base64Decode(final String text) throws SecurityException { final String methodName = PasswordUtils.CNAME + "#base64Decode(final String text) throws SecurityException"; if (DEBUG) { DEBUGGER.debug(methodName); DEBUGGER.debug("Value: {}", text); } return new String(Base64.getDecoder().decode(text.getBytes())); } /** * Base64 decodes a given string * * @param variance - The allowed differences in OTP values * @param algorithm - The algorithm to encrypt the data with * @param instance - The security instance to utilize * @param secret - The OTP secret * @param code - The OTP code * @return <code>true</code> if successful, <code>false</code> otherwise * @throws SecurityException {@link java.lang.SecurityException} if an exception occurs during processing */ public static final boolean validateOtpValue(final int variance, final String algorithm, final String instance, final String secret, final int code) throws SecurityException { final String methodName = PasswordUtils.CNAME + "#validateOtpValue(final int variance, final String algorithm, final String instance, final String secret, final int code) throws SecurityException"; if (DEBUG) { DEBUGGER.debug(methodName); DEBUGGER.debug("Value: {}", variance); DEBUGGER.debug("Value: {}", algorithm); DEBUGGER.debug("Value: {}", instance); DEBUGGER.debug("Value: {}", secret); DEBUGGER.debug("Value: {}", code); } long truncatedHash = 0; byte[] data = new byte[8]; long timeIndex = System.currentTimeMillis() / 1000 / 30; final Base32 codec = new Base32(); final byte[] decoded = codec.decode(secret); SecretKeySpec signKey = new SecretKeySpec(decoded, algorithm); if (DEBUG) { DEBUGGER.debug("long: {}", timeIndex); } try { for (int i = 8; i-- > 0; timeIndex >>>= 8) { data[i] = (byte) timeIndex; } Mac mac = Mac.getInstance(instance); mac.init(signKey); byte[] hash = mac.doFinal(data); int offset = hash[20 - 1] & 0xF; for (int i = 0; i < 4; i++) { truncatedHash <<= 8; truncatedHash |= (hash[offset + i] & 0xFF); } truncatedHash &= 0x7FFFFFFF; truncatedHash %= 1000000; if (DEBUG) { DEBUGGER.debug("truncatedHash: {}", truncatedHash); } return (truncatedHash == code); } catch (InvalidKeyException ikx) { throw new SecurityException(ikx.getMessage(), ikx); } catch (NoSuchAlgorithmException nsx) { throw new SecurityException(nsx.getMessage(), nsx); } } }