package io.mangoo.crypto; import java.util.Base64; import java.util.Objects; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.CryptoException; import org.bouncycastle.crypto.engines.AESLightEngine; import org.bouncycastle.crypto.modes.CBCBlockCipher; import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher; import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.params.ParametersWithRandom; import com.google.common.base.Charsets; import com.google.common.base.Preconditions; import com.google.inject.Inject; import io.mangoo.configuration.Config; import io.mangoo.enums.Required; /** * Convenient class for encryption and decryption * * @author svenkubiak * */ public class Crypto { private static final Logger LOG = LogManager.getLogger(Crypto.class); private static final int KEYINDEX_START = 0; private static final int KEYLENGTH_32 = 32; private static final Base64.Encoder base64Encoder = Base64.getEncoder(); private static final Base64.Decoder base64Decoder = Base64.getDecoder(); private final PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESLightEngine())); private Config config; @Inject public Crypto(Config config) { this.config = Objects.requireNonNull(config, Required.CONFIG.toString()); } /** * Decrypts an given encrypted text using the application secret property (application.secret) as key * * @param encrytedText The encrypted text * @return The clear text or null if decryption fails */ public String decrypt(String encrytedText) { Objects.requireNonNull(encrytedText, Required.ENCRYPTED_TEXT.toString()); return decrypt(encrytedText, getSizedKey(this.config.getApplicationSecret())); } /** * Decrypts an given encrypted text using the given key * * @param encrytedText The encrypted text * @param key The encryption key * @return The clear text or null if decryption fails */ public String decrypt(String encrytedText, String key) { Objects.requireNonNull(encrytedText, Required.ENCRYPTED_TEXT.toString()); Objects.requireNonNull(key, Required.KEY.toString()); CipherParameters cipherParameters = new ParametersWithRandom(new KeyParameter(getSizedKey(key).getBytes(Charsets.UTF_8))); this.cipher.init(false, cipherParameters); return new String(cipherData(base64Decoder.decode(encrytedText)), Charsets.UTF_8); } /** * Encrypts a given plain text using the application secret property (application.secret) as key * * Encryption is done by using AES and CBC Cipher and a key length of 256 bit * * @param plainText The plain text to encrypt * @return The encrypted text or null if encryption fails */ public String encrypt(String plainText) { Objects.requireNonNull(plainText, Required.PLAIN_TEXT.toString()); return encrypt(plainText, getSizedKey(this.config.getApplicationSecret())); } /** * Encrypts a given plain text using the given key * * Encryption is done by using AES and CBC Cipher and a key length of 256 bit * * @param plainText The plain text to encrypt * @param key The key to use for encryption * @return The encrypted text or null if encryption fails */ public String encrypt(String plainText, String key) { Objects.requireNonNull(plainText, Required.PLAIN_TEXT.toString()); Objects.requireNonNull(key, Required.KEY.toString()); CipherParameters cipherParameters = new ParametersWithRandom(new KeyParameter(getSizedKey(key).getBytes(Charsets.UTF_8))); this.cipher.init(true, cipherParameters); return new String(base64Encoder.encode(cipherData(plainText.getBytes(Charsets.UTF_8))), Charsets.UTF_8); } /** * Encrypts or decrypts a given byte array of data * * @param data The data to encrypt or decrypt * @return A clear text or encrypted byte array */ private byte[] cipherData(byte[] data) { byte[] result = null; try { final byte[] buffer = new byte[this.cipher.getOutputSize(data.length)]; final int processedBytes = this.cipher.processBytes(data, 0, data.length, buffer, 0); final int finalBytes = this.cipher.doFinal(buffer, processedBytes); result = new byte[processedBytes + finalBytes]; System.arraycopy(buffer, 0, result, 0, result.length); } catch (final CryptoException e) { LOG.error("Failed to encrypt/decrypt", e); } return result; } /** * Creates a secret for encrypt or decryption which has a length * of 32 characters, corresponding to 256 Bits * * If the provided secret has more than 32 characters it will be trimmed * to 32 characters * * @param secret A given secret to trim * @return A secret with at least 32 characters */ private String getSizedKey(String secret) { Objects.requireNonNull(secret, Required.SECRET.toString()); String key = secret.replaceAll("[^\\x00-\\x7F]", ""); Preconditions.checkArgument(key.length() >= KEYLENGTH_32, "encryption key must be at least 32 characters"); return key.substring(KEYINDEX_START, KEYLENGTH_32); } }