All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.rapidoid.crypto.Crypto Maven / Gradle / Ivy

/*-
 * #%L
 * rapidoid-commons
 * %%
 * Copyright (C) 2014 - 2017 Nikolche Mihajlovski and contributors
 * %%
 * 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.
 * #L%
 */

package org.rapidoid.crypto;

import org.rapidoid.RapidoidThing;
import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;
import org.rapidoid.commons.Str;
import org.rapidoid.config.Conf;
import org.rapidoid.log.Log;
import org.rapidoid.u.U;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;


@Authors("Nikolche Mihajlovski")
@Since("4.0.0")
public class Crypto extends RapidoidThing {

	public static final SecureRandom RANDOM = new SecureRandom();

	private static final AESCypherTool AES = new AESCypherTool();

	private static final String HMAC_SHA_256 = "HmacSHA256";

	static final int HMAC_KEY_LENGTH = 256; // bits

	private static volatile CryptoKey secretKey;

	static final byte[] DEFAULT_PBKDF2_SALT = {
		0, -3, -76, 48, 23, 1, 43, -41, -120, 45, -92, -113, -100, 70, -68, -46, 96, -93, 15, 99
	};

	public static void reset() {
		secretKey = null;
	}

	public static MessageDigest digest(String algorithm) {
		try {
			return MessageDigest.getInstance(algorithm);
		} catch (NoSuchAlgorithmException e) {
			throw U.rte("Cannot find crypto algorithm: " + algorithm);
		}
	}

	public static Cipher cipher(String transformation) {
		try {
			return Cipher.getInstance(transformation);
		} catch (NoSuchAlgorithmException e) {
			throw U.rte("Cannot find crypto algorithm: " + transformation);
		} catch (NoSuchPaddingException e) {
			throw U.rte("No such padding: " + transformation);
		}
	}

	public static String bytesAsText(byte[] bytes) {
		StringBuilder sb = new StringBuilder();

		for (int i = 0; i < bytes.length; i++) {
			sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
		}

		return sb.toString();
	}

	public static byte[] md5Bytes(byte[] bytes) {
		MessageDigest md5 = digest("MD5");
		md5.update(bytes);
		return md5.digest();
	}

	public static String md5(byte[] bytes) {
		return bytesAsText(md5Bytes(bytes));
	}

	public static String md5(String data) {
		return md5(data.getBytes());
	}

	public static byte[] sha1Bytes(byte[] bytes) {
		MessageDigest sha1 = digest("SHA-1");
		sha1.update(bytes);
		return sha1.digest();
	}

	public static String sha1(byte[] bytes) {
		return bytesAsText(sha1Bytes(bytes));
	}

	public static String sha1(String data) {
		return sha1(data.getBytes());
	}

	public static byte[] sha512Bytes(byte[] bytes) {
		MessageDigest sha1 = digest("SHA-512");
		sha1.update(bytes);
		return sha1.digest();
	}

	public static String sha512(byte[] bytes) {
		return bytesAsText(sha512Bytes(bytes));
	}

	public static String sha512(String data) {
		return sha512(data.getBytes());
	}

	public static synchronized CryptoKey getSecretKey() {
		if (secretKey == null) {
			initSecret();
		}

		U.notNull(secretKey, "secret key");
		return secretKey;
	}

	private static synchronized void initSecret() {
		String secret = Conf.ROOT.entry("secret").str().getOrNull();

		char[] src;
		if (secret == null) {
			Log.warn("!Application secret was not specified, generating random secret!");

			src = Str.toHex(randomBytes(128)).toCharArray();

		} else {
			src = secret.toCharArray();
		}

		secretKey = CryptoKey.from(src);
	}

	public static String randomStr(int byteCount) {
		return DatatypeConverter.printHexBinary(randomBytes(byteCount));
	}

	public static byte[] encrypt(byte[] data, CryptoKey key) {
		try {
			return AES.encrypt(data, key);
		} catch (Exception e) {
			throw U.rte(e);
		}
	}

	public static byte[] decrypt(byte[] data, CryptoKey key) {
		try {
			return AES.decrypt(data, key);
		} catch (Exception e) {
			throw U.rte(e);
		}
	}

	public static byte[] encrypt(byte[] data) {
		return encrypt(data, getSecretKey());
	}

	public static byte[] decrypt(byte[] data) {
		return decrypt(data, getSecretKey());
	}

	public static byte[] pbkdf2(char[] password, byte[] salt, int iterations, int length) {
		try {
			PBEKeySpec keySpec = new PBEKeySpec(password, salt, iterations, length);

			return getPBKDFInstance().generateSecret(keySpec).getEncoded();

		} catch (Exception e) {
			throw U.rte(e);
		}
	}

	public static String passwordHash(char[] password) {
		return passwordHash(password, 100000);
	}

	public static String passwordHash(char[] password, int iterations) {
		return passwordHash(password, iterations, randomBytes(20));
	}

	public static String passwordHash(char[] password, int iterations, byte[] salt) {
		byte[] hash = pbkdf2(password, salt, iterations, 256);
		return Str.toBase64(hash) + "$" + Str.toBase64(salt) + "$" + iterations;
	}

	public static boolean passwordMatches(char[] password, String saltedHash) {
		String[] parts = saltedHash.split("\\$");

		if (parts.length != 3) {
			return false;
		}

		byte[] expectedHash = Str.fromBase64(parts[0]);
		byte[] salt = Str.fromBase64(parts[1]);
		int iterations;

		try {
			iterations = U.num(parts[2]);
		} catch (Exception e) {
			return false;
		}

		byte[] realHash = pbkdf2(password, salt, iterations, 256);
		return Arrays.equals(expectedHash, realHash);
	}

	public static boolean passwordMatches(String password, String saltedHash) {
		return passwordMatches(password.toCharArray(), saltedHash);
	}

	public static byte[] randomBytes(int byteCount) {
		byte[] bytes = new byte[byteCount];
		RANDOM.nextBytes(bytes);
		return bytes;
	}

	public static byte[] hmac(byte[] data, byte[] secret, byte[] salt) throws Exception {
		Mac m = Mac.getInstance(HMAC_SHA_256);
		m.init(new SecretKeySpec(secret, HMAC_SHA_256));
		return m.doFinal(data);
	}

	public static boolean hmacMatches(byte[] hmac, byte[] data, byte[] secret, byte[] salt) throws Exception {
		return MessageDigest.isEqual(hmac, Crypto.hmac(data, secret, salt));
	}

	private static SecretKeyFactory getPBKDFInstance() throws NoSuchAlgorithmException {
		try {
			return SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
		} catch (NoSuchAlgorithmException e) {
			return SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy