com.metaeffekt.artifact.analysis.flow.ng.crypt.EncryptedEntryOutputStream Maven / Gradle / Ivy
/*
* Copyright 2021-2024 the original author or authors.
*
* 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.metaeffekt.artifact.analysis.flow.ng.crypt;
import com.metaeffekt.artifact.analysis.flow.ng.ContentAlgorithmParam;
import org.apache.commons.io.output.CloseShieldOutputStream;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.security.*;
import java.util.Objects;
import java.util.zip.ZipOutputStream;
/**
* Used for creating new entries in a ZipOutputStream and encrypt them.
* See its counterpart {@link DecryptedEntryInputStream}.
*/
public class EncryptedEntryOutputStream extends FilterOutputStream implements AutoCloseable {
private boolean closed = false;
/**
* Stored only so we can run closeEntry at the end
*/
private final ZipOutputStream underlyingZipStream;
private EncryptedEntryOutputStream(ZipOutputStream underlyingZipStream, Cipher cipher) {
// wrap the underlying stream to encrypt this entry
super(new CipherOutputStream(CloseShieldOutputStream.wrap(underlyingZipStream), cipher));
this.underlyingZipStream = underlyingZipStream;
}
private static byte[] getIv() {
// 16-byte iv (and may rely on this length)
byte[] rawContentEncryptionIv = new byte[16];
SecureRandom secureRandom = new SecureRandom();
// generate a new IV each time we write
secureRandom.nextBytes(rawContentEncryptionIv);
return rawContentEncryptionIv;
}
/**
* Creates a Cipher object for use in encryption.
* Generates a new IV each time it's called.
*
* @param algoParam specified algorithms for use in content encryption
* @param contentEncryptionKey the symmetric key for encryption of content
* @return returns the cipher for use in encryption of one data stream
* @throws NoSuchAlgorithmException throws exception in the event of failure
* @throws InvalidKeyException throws exception in the event of failure
* @throws NoSuchProviderException throws exception in the event of failure
* @throws NoSuchPaddingException throws exception in the event of failure
* @throws InvalidAlgorithmParameterException throws exception in the event of failure
*/
private static Cipher createEncryptionCipher(ContentAlgorithmParam algoParam,
ContentEncryptionKey contentEncryptionKey)
throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException,
InvalidAlgorithmParameterException, InvalidKeyException {
Objects.requireNonNull(algoParam);
Objects.requireNonNull(contentEncryptionKey);
final Key key = new SecretKeySpec(contentEncryptionKey.getRaw(),
algoParam.getAlgorithm() + algoParam.getMode()
);
Cipher cipher = Cipher.getInstance(algoParam.getAlgorithm() + algoParam.getMode(), algoParam.getProvider());
byte[] iv = getIv();
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
return cipher;
}
/**
* Creates a new entry with encryption. Uses the provided arguments and generates a new random IV.
*
* @param zipOutputStream where the new entry will be written
* @param contentEncryptionKey the content key to use
* @param algorithmParam parametrization for what encryption algorithm to use. should be set to default
* @return returns a new object, directly usable as a sink for data that will be written to the file
* @throws InvalidAlgorithmParameterException throws on error
* @throws NoSuchPaddingException throws on error
* @throws NoSuchAlgorithmException throws on error
* @throws NoSuchProviderException throws on error
* @throws InvalidKeyException throws on error
* @throws IOException throws on error
*/
public static EncryptedEntryOutputStream createEncryptionOutputStream(ZipOutputStream zipOutputStream,
ContentAlgorithmParam algorithmParam,
ContentEncryptionKey contentEncryptionKey)
throws InvalidAlgorithmParameterException, NoSuchPaddingException, NoSuchAlgorithmException,
NoSuchProviderException, InvalidKeyException, IOException {
Cipher cipher = createEncryptionCipher(algorithmParam, contentEncryptionKey);
// the nonce needs separate transmission but NOT in AAD thanks to EAX's guarantees
zipOutputStream.write(cipher.getIV());
zipOutputStream.flush();
return new EncryptedEntryOutputStream(zipOutputStream, cipher);
}
/**
* Closes the current entry in the underlying ZipOutputStream but DOES NOT close the stream.
* @throws IOException throws on failure to close the encapsulated cipher stream
*/
@Override
public void close() throws IOException {
// make it possible to use these in try-with-resources to avoid unclosed entries
if (!this.closed) {
this.closed = true;
// closes the cipher stream
super.close();
// closing this helper object should just close the zip entry, not the zip file
try {
underlyingZipStream.closeEntry();
} catch (IOException e) {
// closing an entry should not fail
throw new RuntimeException(e);
}
}
}
public boolean isClosed() {
return closed;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy