package org.basex.query.util.crypto; import static org.basex.query.util.Err.*; import static org.basex.util.Token.*; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.Mac; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.basex.query.QueryException; import org.basex.query.item.Item; import org.basex.query.item.Str; import org.basex.util.Base64; import org.basex.util.InputInfo; import org.basex.util.hash.TokenMap; /** * This class encrypts and decrypts textual inputs. * * @author BaseX Team 2005-12, BSD License * @author Lukas Kircher */ public final class Encryption { /** Input info. */ private final InputInfo input; /** Token. */ private static final byte[] SYM = token("symmetric"); /** Token. */ private static final byte[] BASE64 = token("base64"); /** Token. */ private static final byte[] HEX = token("hex"); /** Supported encryption algorithms, mapped to correct IV lengths. */ private static final TokenMap ALGE = new TokenMap(); /** Exact encryption algorithm JAVA names. */ private static final TokenMap ALGN = new TokenMap(); /** Supported HMAC algorithms. */ private static final TokenMap ALGHMAC = new TokenMap(); /** Default hash algorithm. */ private static final byte[] DEFA = token("md5"); /** DES encryption token. */ private static final byte[] DES = token("des"); /** AES encryption token. */ private static final byte[] AES = token("aes"); static { ALGE.add(DES, token("8")); ALGE.add(AES, token("16")); ALGN.add(DES, token("DES/CBC/PKCS5Padding")); ALGN.add(AES, token("AES/CBC/PKCS5Padding")); ALGHMAC.add(DEFA, token("hmacmd5")); ALGHMAC.add(token("sha1"), token("hmacsha1")); ALGHMAC.add(token("sha256"), token("hmacsha256")); ALGHMAC.add(token("sha384"), token("hmacsha1")); ALGHMAC.add(token("sha512"), token("hmacsha512")); /* */ } /** * Constructor. * * @param ii input info */ public Encryption(final InputInfo ii) { input = ii; } /** * Encrypts or decrypts the given input. * @param in input * @param s encryption type * @param k secret key * @param a encryption algorithm * @param ec encrypt or decrypt * @return encrypted or decrypted input * @throws QueryException query exception */ public Str encryption(final byte[] in, final byte[] s, final byte[] k, final byte[] a, final boolean ec) throws QueryException { final boolean symmetric = eq(lc(s), SYM) || s.length == 0; final byte[] aa = a.length == 0 ? DES : a; final byte[] tivl = ALGE.get(lc(aa)); if(!symmetric) CRYPTOENCTYP.thrw(input, ec); if(tivl == null) CRYPTOINVALGO.thrw(input, s); // initialization vector length final int ivl = toInt(tivl); byte[] t = null; try { if(ec) t = encrypt(in, k, aa, ivl); else t = decrypt(in, k, aa, ivl); } catch(final NoSuchPaddingException e) { CRYPTONOPAD.thrw(input, e); } catch(final BadPaddingException e) { CRYPTOBADPAD.thrw(input, e); } catch(final NoSuchAlgorithmException e) { CRYPTOINVALGO.thrw(input, e); } catch(final InvalidKeyException e) { CRYPTOKEYINV.thrw(input, e); } catch(final IllegalBlockSizeException e) { CRYPTOILLBLO.thrw(input, e); } catch(final InvalidAlgorithmParameterException e) { CRYPTOINVALGO.thrw(input, e); } return Str.get(t); } /** * Encrypts the given input data. * * @param in input data to encrypt * @param k key * @param a encryption algorithm * @param ivl initialization vector length * @return encrypted input data * @throws InvalidKeyException ex * @throws InvalidAlgorithmParameterException ex * @throws NoSuchAlgorithmException ex * @throws NoSuchPaddingException ex * @throws IllegalBlockSizeException ex * @throws BadPaddingException ex */ static byte[] encrypt(final byte[] in, final byte[] k, final byte[] a, final int ivl) throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException { final Cipher cipher = Cipher.getInstance(string(ALGN.get(lc(a)))); final SecretKeySpec kspec = new SecretKeySpec(k, string(a)); // generate random iv. random iv is necessary to make the encryption of a // string look different every time it is encrypted. final byte[] iv = new byte[ivl]; // create new random iv if encrypting final SecureRandom rand = SecureRandom.getInstance("SHA1PRNG"); rand.nextBytes(iv); final IvParameterSpec ivspec = new IvParameterSpec(iv); // encrypt/decrypt cipher.init(Cipher.ENCRYPT_MODE, kspec, ivspec); final byte[] t = cipher.doFinal(in); // initialization vector is appended to the message for later decryption return concat(iv, t); } /** * Decrypts the given input data. * * @param in data to decrypt * @param k secret key * @param a encryption algorithm * @param ivl initialization vector length * @return decrypted data * @throws NoSuchAlgorithmException ex * @throws NoSuchPaddingException ex * @throws InvalidKeyException ex * @throws InvalidAlgorithmParameterException ex * @throws IllegalBlockSizeException ex * @throws BadPaddingException ex */ static byte[] decrypt(final byte[] in, final byte[] k, final byte[] a, final int ivl) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { final SecretKeySpec keySpec = new SecretKeySpec(k, string(a)); final Cipher cipher = Cipher.getInstance(string(ALGN.get(lc(a)))); // extract iv from message beginning final byte[] iv = substring(in, 0, ivl); final IvParameterSpec ivspec = new IvParameterSpec(iv); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivspec); return cipher.doFinal(substring(in, ivl, in.length)); } /** * Creates a message authentication code (MAC) for the given input. * @param msg input * @param k secret key * @param a encryption algorithm * @param enc encoding * @return MAC * @throws QueryException query exception */ public Item hmac(final byte[] msg, final byte[] k, final byte[] a, final byte[] enc) throws QueryException { // create hash value from input message final Key key = new SecretKeySpec(k, string(a)); byte[] hash = null; final byte[] aa = a.length == 0 ? DEFA : a; if(ALGHMAC.id(lc(aa)) == 0) CRYPTOINVHASH.thrw(input, aa); final boolean b64 = eq(lc(enc), BASE64) || enc.length == 0; if(!b64 && !eq(lc(enc), HEX)) CRYPTOENC.thrw(input, enc); try { final Mac mac = Mac.getInstance(string(ALGHMAC.get(lc(aa)))); mac.init(key); hash = mac.doFinal(msg); } catch(final NoSuchAlgorithmException e) { CRYPTOINVHASH.thrw(input, e); } catch(final InvalidKeyException e) { CRYPTOKEYINV.thrw(input, e); } // convert to specified encoding, base64 as a standard, else use hex return Str.get(b64 ? Base64.encode(hash) : hex(hash, true)); } }