Commit 42048ee9 authored by rwm's avatar rwm

New crypto classes for extending InputStream and OutputStream

parent 5470a5fd
package de.sekmi.histream.crypto;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
public class DecryptingInputStream extends InputStream {
private Cipher cipher;
private InputStream in;
private ByteBuffer buffer;
private ByteBuffer outputBuffer;
private boolean endOfStream;
public DecryptingInputStream(InputStream in, Key asymmetricKey) throws GeneralSecurityException, IOException{
this(in, "AES",128,"RSA", asymmetricKey);
}
public DecryptingInputStream(InputStream in, String symmetricAlgorithm, int symmetricKeysize, String asymmetricCipher, Key asymmetricKey) throws GeneralSecurityException, IOException{
// use buffer
endOfStream = false;
byte[] buf = new byte[1024*8];
outputBuffer = ByteBuffer.allocate(1024*8).compact();
int read = in.read(buf);
buffer = ByteBuffer.wrap(buf, 0, read);
int version = buffer.getInt();
int ks = buffer.getShort();
if( version != EncryptingByteChannel.Version )throw new IOException("Unsupported MDAT stream version "+version);
byte[] wrapped = new byte[ks];
buffer.get(wrapped);
buffer.compact();
Cipher unwrap;
try {
unwrap = Cipher.getInstance(asymmetricCipher);
unwrap.init(Cipher.UNWRAP_MODE, asymmetricKey);
Key temp = unwrap.unwrap(wrapped, symmetricAlgorithm, Cipher.SECRET_KEY);
cipher = Cipher.getInstance(symmetricAlgorithm);
cipher.init(Cipher.DECRYPT_MODE, temp);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
throw new IOException("Unable to unwrap symmetric key",e);
}
this.in = in;
}
@Override
public void close() throws IOException {
// XXX close underlying stream?
in.close();
}
private int readAndDecrypt() throws IOException{
int bytesRead=0;
if( buffer.hasRemaining() ){
bytesRead = in.read(buffer.array(),buffer.position(),buffer.remaining());
}
outputBuffer.compact();
buffer.flip();
try {
if( bytesRead == -1 ){
endOfStream = true;
bytesRead = cipher.doFinal(buffer, outputBuffer);
}else{
bytesRead = cipher.update(buffer, outputBuffer);
}
} catch (ShortBufferException | IllegalBlockSizeException | BadPaddingException e) {
throw new IOException(e);
} finally {
buffer.compact();
outputBuffer.flip();
}
return bytesRead;
}
@Override
public int read() throws IOException {
if( outputBuffer.hasRemaining() ){
// remove from output buffer
return outputBuffer.get();
}else if( endOfStream )
return -1; // nothing to do
// if empty, fill output buffer
readAndDecrypt();
return read();
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int remaining = outputBuffer.remaining();
if( remaining > 0 ){
remaining = Math.min(remaining, len);
outputBuffer.get(b, off, remaining);
return remaining;
}else if( endOfStream )
return -1; // nothing to do
// if empty, fill output buffer
readAndDecrypt();
return read(b);
}
@Override
public int available() throws IOException {
return outputBuffer.remaining();
}
}
package de.sekmi.histream.crypto;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.Key;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
/**
* Wraps a WritableByteChannel with encryption.
* Closing the EncryptionOutputStream will close the underlying WritableByteChannel
* @author Raphael
*
*/
public class EncryptingOutputStream extends OutputStream{
public static final int Version = 1;
private Cipher cipher;
private OutputStream out;
public EncryptingOutputStream(OutputStream out, Key asymmetricKey) throws GeneralSecurityException, IOException{
this(out, "AES",128,"RSA", asymmetricKey);
}
public EncryptingOutputStream(OutputStream out, String symmetricAlgorithm, int symmetricKeysize, String asymmetricCipher, Key asymmetricKey) throws GeneralSecurityException, IOException{
KeyGenerator kg = KeyGenerator.getInstance(symmetricAlgorithm);
//log.fine("Generating symmetric key "+symmetricAlgorithm+" with size "+symmetricKeysize);
kg.init( symmetricKeysize );
SecretKey sk = kg.generateKey();
// wrap with asymmetric algorithm and write to output
Cipher wrapper = Cipher.getInstance(asymmetricCipher);
wrapper.init(Cipher.WRAP_MODE, asymmetricKey);
byte[] wrapped = wrapper.wrap(sk);
this.out = out;
// use buffer
ByteBuffer buffer = ByteBuffer.allocate(1024*10);
// prefix output with version and key length
buffer.putInt(Version).flip();
out.write(buffer.array(),buffer.position(),buffer.remaining());
buffer.clear();
// write wrapped length
buffer.putShort((short)wrapped.length).flip();
out.write(buffer.array(),buffer.position(),buffer.remaining());
buffer.clear();
out.write(wrapped);
// initialize symmetric cipher
cipher = Cipher.getInstance(symmetricAlgorithm);
cipher.init(Cipher.ENCRYPT_MODE, sk);
}
@Override
public void close() throws IOException {
try {
out.write(cipher.doFinal());
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new IOException(e);
}
out.close();
}
@Override
public void write(byte[] src, int off, int len) throws IOException {
byte[] enc = cipher.update(src, off, len);
out.write(enc);
}
@Override
public void write(int b) throws IOException {
write(new byte[]{(byte)b});
}
@Override
public void write(byte[] b) throws IOException {
byte[] enc = cipher.update(b);
out.write(enc);
}
}
package de.sekmi.histream.crypto;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
......@@ -62,7 +64,7 @@ public class TestEncryptDecrypt {
}
@Test
public void testEncryptDecrypt() throws GeneralSecurityException, IOException{
public void testEncryptDecryptChannels() throws GeneralSecurityException, IOException{
Path source = Paths.get("examples/dwh-jaxb.xml");
Path temp = Files.createTempFile("encrypted", ".enc");
......@@ -70,7 +72,7 @@ public class TestEncryptDecrypt {
// encrypt file
FileChannel out = FileChannel.open(temp, StandardOpenOption.WRITE);
WritableByteChannel enc = new EncryptingOutputStream(out, keyPair.getPublic());
WritableByteChannel enc = new EncryptingByteChannel(out, keyPair.getPublic());
FileChannel in = FileChannel.open(source);
in.transferTo(0, Long.MAX_VALUE, enc);
......@@ -81,7 +83,7 @@ public class TestEncryptDecrypt {
// decrypt file
in = FileChannel.open(temp, StandardOpenOption.READ);
ReadableByteChannel decrypted = new DecryptingInputStream(in, keyPair.getPrivate());
ReadableByteChannel decrypted = new DecryptingByteChannel(in, keyPair.getPrivate());
out = FileChannel.open(dec, StandardOpenOption.WRITE);
out.transferFrom(decrypted, 0, Long.MAX_VALUE);
in.close();
......@@ -93,4 +95,45 @@ public class TestEncryptDecrypt {
assertEqualFiles(dec, source);
Files.delete(dec);
}
public static void transfer(InputStream in, OutputStream out) throws IOException{
byte[] buffer = new byte[1024]; // Adjust if you want
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
@Test
public void testEncryptDecryptStreams() throws GeneralSecurityException, IOException{
Path source = Paths.get("examples/dwh-jaxb.xml");
Path temp = Files.createTempFile("encrypted", ".enc");
Path dec = Files.createTempFile("decrypted", ".xml");
// encrypt file
OutputStream out = Files.newOutputStream(temp, StandardOpenOption.WRITE);
OutputStream enc = new EncryptingOutputStream(out, keyPair.getPublic());
InputStream in = Files.newInputStream(source);
transfer(in, enc);
in.close();
enc.close();
out.close();
// decrypt file
in = Files.newInputStream(temp, StandardOpenOption.READ);
InputStream decrypted = new DecryptingInputStream(in, keyPair.getPrivate());
out = Files.newOutputStream(dec, StandardOpenOption.WRITE);
transfer(decrypted, out);
in.close();
out.close();
decrypted.close();
Files.delete(temp);
// compare files
assertEqualFiles(dec, source);
Files.delete(dec);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment