/*
* Copyright 2011 Eric F. Savage, code@efsavage.com
*
* 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.ajah.crypto;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Scanner;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import lombok.extern.java.Log;
import com.ajah.util.AjahUtils;
import com.ajah.util.StringUtils;
import com.ajah.util.config.Config;
/**
* Crypto/hash utilities.
*
* @author Eric F. Savage <code@efsavage.com>
*/
@Log
public class Crypto {
/**
* Accepts a hexadecimal encoded version of the encrypted data and decrypts
* it. Uses the crypto.key.aes property as the encryption key.
*
* @param encrypted
* hexadecimal encoded version of the encrypted data
* @return Decrypted version.
* @throws CryptoException
* If there is a cryptographic error.
*/
public static String fromAES(final String encrypted) throws CryptoException {
final String keyString = Config.i.get("crypto.key.aes", null);
return fromAES(encrypted, keyString);
}
/**
* Accepts a hexadecimal encoded version of the encrypted data and decrypts
* it.
*
* @param encrypted
* hexadecimal encoded version of the encrypted data
* @param keyString
* The key to use to decrypt the data.
* @return Decrypted version.
* @throws CryptoException
* If there is a cryptographic error.
*/
public static String fromAES(final String encrypted, final String keyString) throws CryptoException {
final SecretKeySpec skeySpec = new SecretKeySpec(new BigInteger(keyString, 16).toByteArray(), "AES");
try {
final Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
return new String(cipher.doFinal(new BigInteger(encrypted, 16).toByteArray()));
} catch (final InvalidKeyException e) {
throw new CryptoException(e);
} catch (final IllegalBlockSizeException e) {
throw new CryptoException(e);
} catch (final BadPaddingException e) {
throw new CryptoException(e);
} catch (final NoSuchAlgorithmException e) {
throw new CryptoException(e);
} catch (final NoSuchPaddingException e) {
throw new CryptoException(e);
}
}
/**
* Returns HmacSHA1 of input.
*
* @param secret
* String to process.
* @return Digest of hash.
* @throws CryptoException
* If there is a cryptographic error.
*/
public static byte[] getHmacSha1(final byte[] secret) throws CryptoException {
try {
final String keyString = Config.i.get("crypto.key.hmacsha1", null);
if (StringUtils.isBlank(keyString)) {
throw new IllegalArgumentException("crypto.key.hmacsha1 not defined");
}
final SecretKey key = new SecretKeySpec(keyString.getBytes(), "HmacSHA1");
final Mac m = Mac.getInstance("HmacSHA1");
m.init(key);
m.update(secret);
return m.doFinal();
} catch (final NoSuchAlgorithmException e) {
throw new CryptoException(e);
} catch (final InvalidKeyException e) {
throw new CryptoException(e);
}
}
/**
* Returns HmacSHA1 of input.
*
* @param secret
* String to process.
* @return Digest of hash.
* @throws CryptoException
* If there is a cryptographic error.
*/
public static String getHmacSha1Hex(final String secret) throws CryptoException {
AjahUtils.requireParam(secret, "secret");
return new BigInteger(getHmacSha1(secret.getBytes())).toString(16);
}
private static void keyGen() throws CryptoException {
final SecureRandom sr = new SecureRandom();
final byte[] keyBytes = new byte[128];
sr.nextBytes(keyBytes);
try (final Scanner scanner = new Scanner(System.in)) {
System.out.print("Algorithm: [HmacSHA1] ");
final String algorithm = scanner.nextLine();
if (StringUtils.isBlank(algorithm) || algorithm.equals("HmacSHA1")) {
System.out.print("Secret: ");
final String secret = scanner.nextLine();
System.out.print(getHmacSha1Hex(secret));
}
}
}
private static void listProviders() {
log.info("Supported providers:");
for (final Provider provider : Security.getProviders()) {
System.out.println("\t" + provider);
for (final Provider.Service service : provider.getServices()) {
System.out.println("\t\t" + service.getAlgorithm());
}
}
}
/**
* Command line utility for testing passwords and such.
*
* @param args
* No args, program is interactive.
* @throws Exception
*/
public static void main(final String[] args) throws Exception {
if (args.length < 1) {
return;
}
if ("list".equals(args[0])) {
listProviders();
return;
} else if ("keygen".equals(args[0])) {
keyGen();
} else if ("to-aes".equals(args[0])) {
final String secret = "turtle";
final String encrypted = toAES(secret);
System.out.println(encrypted);
final String decrypted = fromAES(encrypted);
System.out.println(decrypted);
}
}
/**
* Encodes a value in aes and returns the hexadecimal encoded version of the
* encrypted data. Uses the crypto.key.aes property as the encryption key.
*
* @param secret
* Data to encrypt.
* @return Hexadecimal encoded version of the encrypted data.
* @throws CryptoException
* If there is a cryptographic error.
*/
public static String toAES(final String secret) throws CryptoException {
final String keyString = Config.i.get("crypto.key.aes", null);
AjahUtils.requireParam(keyString, "crypto.key.aes");
return toAES(secret, keyString);
}
/**
* Encodes a value in aes and returns the hexadecimal encoded version of the
* encrypted data.
*
* @param secret
* Data to encrypt.
* @param key
* The key to use to encrypt the data.
* @return Hexadecimal encoded version of the encrypted data.
* @throws CryptoException
* If there is a cryptographic error.
*/
public static String toAES(final String secret, final byte[] key) throws CryptoException {
AjahUtils.requireParam(secret, "secret");
AjahUtils.requireParam(key, "key");
log.finest("key is " + key.length + " bytes long");
try {
final int maxKeyLen = Cipher.getMaxAllowedKeyLength("AES");
log.finest("maximum key length is " + maxKeyLen + " bytes");
if (key.length > maxKeyLen) {
throw new IllegalArgumentException("Key length of " + key.length + " is longer than maximum allowed " + maxKeyLen);
}
final SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
final Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
final byte[] encrypted = cipher.doFinal(secret.getBytes());
return new BigInteger(encrypted).toString(16);
} catch (final InvalidKeyException e) {
throw new CryptoException(e);
} catch (final IllegalBlockSizeException e) {
throw new CryptoException(e);
} catch (final BadPaddingException e) {
throw new CryptoException(e);
} catch (final NoSuchAlgorithmException e) {
throw new CryptoException(e);
} catch (final NoSuchPaddingException e) {
throw new CryptoException(e);
}
}
/**
* Encodes a value in aes and returns the hexadecimal encoded version of the
* encrypted data.
*
* @param secret
* Data to encrypt.
* @param keyString
* The key to use to encrypt the data.
* @return Hexadecimal encoded version of the encrypted data.
* @throws CryptoException
* If there is a cryptographic error.
*/
public static String toAES(final String secret, final String keyString) throws CryptoException {
return toAES(secret, new BigInteger(keyString, 16).toByteArray());
}
}