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

com.lithium.flow.streams.CryptStreamer Maven / Gradle / Ivy

/*
 * Copyright 2015 Lithium Technologies, Inc.
 *
 * 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.lithium.flow.streams;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import com.lithium.flow.config.Config;
import com.lithium.flow.key.KeySource;
import com.lithium.flow.replacer.NoOpStringReplacer;
import com.lithium.flow.replacer.RegexStringReplacer;
import com.lithium.flow.replacer.StringReplacer;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Arrays;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.IvParameterSpec;

/**
 * Filter to apply encryption on output and decryption on input.
 *
 * @author Matt Ayres
 */
public class CryptStreamer implements Streamer {
	private final KeySource keySource;
	private final byte[] header;
	private final String cipherName;
	private final StringReplacer replacer;

	public CryptStreamer(@Nonnull Config config, @Nonnull KeySource keySource) {
		checkNotNull(config);
		checkNotNull(keySource);

		header = config.getString("crypt.header", "LiAESv01").getBytes();
		cipherName = config.getString("crypt.cipher", "AES/CBC/PKCS5Padding");
		if (config.containsKey("crypt.regex")) {
			replacer = new RegexStringReplacer(config.getString("crypt.regex"),
					config.getString("crypt.replacement", "$1"));
		} else {
			replacer = new NoOpStringReplacer();
		}

		this.keySource = keySource;
	}

	@Override
	@Nonnull
	public OutputStream filterOut(@Nonnull OutputStream out, String name) throws IOException {
		try {
			for (Key key : keySource.getKeys(replacer.replace(name))) {
				out = encryptOut(out, key);
			}
		} catch (GeneralSecurityException e) {
			throw new IOException("encrypt failed", e);
		}
		return out;
	}

	@Nonnull
	private OutputStream encryptOut(@Nonnull OutputStream out, @Nonnull Key key)
			throws IOException, GeneralSecurityException {
		out.write(header);

		Cipher cipher = Cipher.getInstance(cipherName);
		byte[] iv = new byte[cipher.getBlockSize()];
		new SecureRandom().nextBytes(iv);
		AlgorithmParameterSpec spec = new IvParameterSpec(iv);

		out.write(iv);

		cipher.init(Cipher.ENCRYPT_MODE, key, spec);
		return new CipherOutputStream(out, cipher);
	}

	@Override
	@Nonnull
	public InputStream filterIn(@Nonnull InputStream in, @Nullable String name) throws IOException {
		checkArgument(name != null, "name must be specified for key lookup");

		try {
			for (Key key : keySource.getKeys(replacer.replace(name))) {
				in = decryptIn(in, key);
			}
		} catch (GeneralSecurityException e) {
			throw new IOException("decrypt failed", e);
		}
		return in;
	}

	@Nonnull
	private InputStream decryptIn(@Nonnull InputStream in, @Nonnull Key key)
			throws IOException, GeneralSecurityException {
		byte[] inHeader = new byte[header.length];
		if (in.read(inHeader) != inHeader.length) {
			throw new IOException("read didn't complete for header");
		}

		if (!Arrays.equals(header, inHeader)) {
			throw new IOException("unexpected header: '" + new String(inHeader) + "', expected: '"
					+ new String(header) + "'");
		}

		Cipher cipher = Cipher.getInstance(cipherName);
		byte[] iv = new byte[cipher.getBlockSize()];
		if (in.read(iv) != iv.length) {
			throw new IOException("read didn't complete for iv");
		}
		AlgorithmParameterSpec spec = new IvParameterSpec(iv);

		cipher.init(Cipher.DECRYPT_MODE, key, spec);
		return new CipherInputStream(in, cipher);
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy