Online encrypted streaming video files with AES in exoplayer android - encryption

I have video files encrypted with AES stored on server. How to stream them online in exoplayer? I don't want to download the file and decrypt it: waiting for the download to complete and then play decrypted file.

I would suggest taking a look at the UriDataSource or the DataSource interface. You can derive from DataSource and provide an implementation very similar to UriDataSource and pass that into ExoPlayer. That class has access to the read() method which all the bytes pass through. That method allows you to decrypt the files on the fly one buffer at a time.
In ExoPlayer 2.0, you supply your own custom DataSource from your own custom DataSource.Factory which can be passed to an ExtractorMediaSource (or any other MediaSource).
If you're not on ExoPlayer 2.0, you pass the DataSource to the ExtractorSampleSource and then to the VideoRenderer and the AudioRender in the buildRenderers() of a custom RendererBuilder that you implement.
(Also you can Google "custom datasource exoplayer" and that should give more info if what I provided isn't enough - or I can clarify if you can't find anything).
Here's a code snippet of the read() method:
#Override
public int read(byte[] buffer, int offset, int readLength) throws IOException {
if (bytesRemaining == 0) {
return -1;
} else {
int bytesRead = 0;
try {
long filePointer = randomAccessFile.getFilePointer();
bytesRead =
randomAccessFile.read(buffer, offset, (int) Math.min(bytesRemaining, readLength));
// Supply your decrypting logic here
AesEncrypter.decrypt(buffer, offset, bytesRead, filePointer);
} catch (EOFException eof) {
Log.v("Woo", "End of randomAccessFile reached.");
}
if (bytesRead > 0) {
bytesRemaining -= bytesRead;
if (listener != null) {
listener.onBytesTransferred(bytesRead);
}
}
return bytesRead;
}
}
[EDIT] Also just found this SO post which has a similar suggestion.

Related

Microsoft Custom Speech Service issue when using web socket url

so recently for a work project I've been playing around with speech to text models and in particular custom speech to text models. With a bit of mixing and matching examples I've managed to get a test application to talk to the normal Bing speech to text API. But when I attempt to use it with a custom speech instance only the HTTPS URL works. When I use any of the available long form web socket URLS the error An unhandled exception of type 'System.NullReferenceException' occurred in SpeechClient.dll occurs. This is a bit of a problem as that endpoint only supports 2 minutes of transcription, where as the websocket endpoint supports up to 10 minutes.
This https://learn.microsoft.com/en-us/azure/cognitive-services/custom-speech-service/customspeech-how-to-topics/cognitive-services-custom-speech-use-endpoint page here is what I'm going off of. It says that I should use a web socket url when creating the service, but that leads to the error above.
Here my test bed code for trying it out:
using System;
using Microsoft.CognitiveServices.SpeechRecognition;
using System.IO;
namespace ConsoleApp1
{
class Program
{
DataRecognitionClient dataClient;
static void Main(string[] args)
{
Program p = new Program();
p.Run(args);
}
void Run(string[] args)
{
try
{
// Works
//this.dataClient = SpeechRecognitionServiceFactory.CreateDataClient(SpeechRecognitionMode.LongDictation, "en-US", "Key");
// Works
//this.dataClient = SpeechRecognitionServiceFactory.CreateDataClient(SpeechRecognitionMode.LongDictation, "en-US",
// "Key", "Key",
// "https://Id.api.cris.ai/ws/cris/speech/recognize/continuous");
// Doesn't work
this.dataClient = SpeechRecognitionServiceFactory.CreateDataClient(SpeechRecognitionMode.LongDictation, "en-US",
"Key", "Key",
"wss://Id.api.cris.ai/ws/cris/speech/recognize/continuous");
this.dataClient.AuthenticationUri = "https://westus.api.cognitive.microsoft.com/sts/v1.0/issueToken";
this.dataClient.OnResponseReceived += this.ResponseHandler;
this.dataClient.OnConversationError += this.ErrorHandler;
this.dataClient.OnPartialResponseReceived += this.PartialHandler;
Console.WriteLine("Starting Transcription");
this.SendAudioHelper("Audio file path");
(new System.Threading.ManualResetEvent(false)).WaitOne();
} catch(Exception e)
{
Console.WriteLine(e);
}
}
private void SendAudioHelper(string wavFileName)
{
using (FileStream fileStream = new FileStream(wavFileName, FileMode.Open, FileAccess.Read))
{
// Note for wave files, we can just send data from the file right to the server.
// In the case you are not an audio file in wave format, and instead you have just
// raw data (for example audio coming over bluetooth), then before sending up any
// audio data, you must first send up an SpeechAudioFormat descriptor to describe
// the layout and format of your raw audio data via DataRecognitionClient's sendAudioFormat() method.
int bytesRead = 0;
byte[] buffer = new byte[1024];
try
{
do
{
// Get more Audio data to send into byte buffer.
bytesRead = fileStream.Read(buffer, 0, buffer.Length);
// Send of audio data to service.
this.dataClient.SendAudio(buffer, bytesRead);
}
while (bytesRead > 0);
}
finally
{
// We are done sending audio. Final recognition results will arrive in OnResponseReceived event call.
this.dataClient.EndAudio();
}
}
}
void ErrorHandler(object sender, SpeechErrorEventArgs e)
{
Console.WriteLine(e.SpeechErrorText);
}
void ResponseHandler(object sender, SpeechResponseEventArgs e)
{
if(e.PhraseResponse.RecognitionStatus == RecognitionStatus.EndOfDictation || e.PhraseResponse.RecognitionStatus == RecognitionStatus.DictationEndSilenceTimeout)
{
Console.WriteLine("Trnascription Over");
Console.ReadKey();
Environment.Exit(0);
}
for(int i = 0; i < e.PhraseResponse.Results.Length; i++)
{
Console.Write(e.PhraseResponse.Results[i].DisplayText);
}
Console.WriteLine();
}
void PartialHandler(object sender, PartialSpeechResponseEventArgs e)
{
}
}
}
Thanks in advance for any help.
so you are probably ok with using https ...
we are revisiting the SDKs right now (restructuring/reorganizing). I expect updates in the next couple of months.
Wolfgang
The new speech service SDK supports Custom Speech Service out-of-box. Please also check the samples RecognitionUsingCustomizedModelAsync() here for details.

Stream.WriteAsync throws The remote host closed the connection exception

I have an asp.net webforms application and to retrieve video from database that saved in varbinary format and show it as html5 video tag.
after a googled it, i found a way that i should play it asynchronously using ASP.Net WebApi, it works fine
First problem
When video played first time and the user click on play button to replay the video, The remote host closed the connection. The error code is 0x800704CD exception throws at line await outputStream.WriteAsync(buffer, 0, bytesRead);.
Second Problem
When user click on seek bar, the video goes to played from first.
NOTE
Internet Explorer 11 plays the video without any problem, but firefox and chrome have both problems.
how can i solve this problem?
Here is my codes:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.EnableCors();
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "VideoApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
public class VideoController : ApiController
{
public IVideoRepository videoRepository;
public HttpResponseMessage Get(long id)
{
try
{
videoRepository = new VideoRepository();
Video video = videoRepository.load(id);
if (video != null)
{
var videoStream = new VideoStream(video.fileContent);
string ext = video.extension;
var response = Request.CreateResponse();
response.Content = new PushStreamContent((Action<Stream, HttpContent, TransportContext>)videoStream.WriteToStream, new MediaTypeHeaderValue("video/" + ext));
response.Content.Headers.Add("Content-Disposition", "attachment;filename=" + video.fullName.Replace(" ", ""));
response.Content.Headers.Add("Content-Length", videoStream.FileLength.ToString());
return response;
}
else
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
}
catch (Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.ServiceUnavailable, e);
}
}
}
public class VideoStream
{
private readonly byte[] _fileContent;
private long _contentLength;
public long FileLength
{
get { return _contentLength; }
}
public VideoStream(byte[] content)
{
_contentLength = content.Length;
_fileContent = content;
}
public async void WriteToStream(Stream outputStream, HttpContent content, TransportContext context)
{
try
{
var buffer = new byte[65536];
MemoryStream memoryStream = new MemoryStream();
memoryStream.Write(_fileContent, 0, _fileContent.Length);
memoryStream.Position = 0;
using (memoryStream)
{
var length = (int)memoryStream.Length;
var bytesRead = 1;
while (length > 0 && bytesRead > 0)
{
bytesRead = memoryStream.Read(buffer, 0, Math.Min(length, buffer.Length));
await outputStream.WriteAsync(buffer, 0, bytesRead);
length -= bytesRead;
}
}
}
catch (Exception e)
{
throw e;
}
finally
{
outputStream.Close();
}
}
}
UPDATE
after this way didn't worked properly, i had to use this way, but the new way have seekbar problem, when user click on seek bar to seek to time it dosn't work in Chrome and FireFox.
ASP.NET is not very good at video streaming. Third-party video streaming solution is the best option.
There are a few video-streaming servers (like Wowza), but they require installation and you have to buy license.
Cloud streaming service is another option. I personally prefer AWS Cloudfront. They propose distribution in various globally distributed content delivery zones. It costs really cheap and you can be sure that it will survive any traffic amount (even if all your users will watch the same video simultaneously).
You might have got the answer by now. But this might help others-
My best bet is removing the Content-length from the response headers.
Content-Length tells the caller that it needs to receive this fixed length in the response.
When you click on a play button, the complete video stream is not received (i.e., the entire Content-Length is not received.) & therefore, the error.
Another approach could be using response.Headers.TransferEncodingChunked = true, which tells the caller that it will receive a response in chunks. The only catch here is you will get a 200OK even if the stream is not present.

Increase Http Runtime MaxRequestLength from C# code

How can I increase
from my C# code ? I can't do this in Web.config, My application is created to deploy web
application in IIS.
Take a look at http://bytes.com/topic/asp-net/answers/346534-how-i-can-get-httpruntime-section-page
There's how you get access to an instance of HttpRuntimeSection. Then modify the property MaxRequestLength.
An alternative to increasing the max request length is to create an IHttpModule implementation. In the BeginRequest handler, grab the HttpWorkerRequest to process it entirely in your own code, rather than letting the default implementation handle it.
Here is a basic implementation that will handle any request posted to any file called "dropbox.aspx" (in any directory, whether it exists or not):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Example
{
public class FileUploadModule: IHttpModule
{
#region IHttpModule Members
public void Dispose() {}
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
}
#endregion
void context_BeginRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
HttpContext context = application.Context;
string filePath = context.Request.FilePath;
string fileName = VirtualPathUtility.GetFileName( filePath );
string fileExtension = VirtualPathUtility.GetExtension(filePath);
if (fileName == "dropbox.aspx")
{
IServiceProvider provider = (IServiceProvider)context;
HttpWorkerRequest wr = (HttpWorkerRequest)provider.GetService(typeof(HttpWorkerRequest));
//HANDLE REQUEST HERE
//Grab data from HttpWorkerRequest instance, as reflected in HttpRequest.GetEntireRawContent method.
application.CompleteRequest(); //bypasses all other modules and ends request immediately
}
}
}
}
You could use something like that, for example, if you're implementing a file uploader, and you want to process the multi-part content stream as it's received, so you can perform authentication based on posted form fields and, more importantly, cancel the request on the server-side before you even receive any file data. That can save a lot of time if you can determine early on in the stream that the upload is not authorized or the file will be too big or exceed the user's disk quota for the dropbox.
This is impossible to do with the default implementation, because trying to access the Form property of the HttpRequest will cause it to try to receive the entire request stream, complete with MaxRequestLength checks. The HttpRequest object has a method called "GetEntireRawContent" which is called as soon as access to the content is needed. That method starts with the following code:
HttpRuntimeSection httpRuntime = RuntimeConfig.GetConfig(this._context).HttpRuntime;
int maxRequestLengthBytes = httpRuntime.MaxRequestLengthBytes;
if (this.ContentLength > maxRequestLengthBytes)
{
if (!(this._wr is IIS7WorkerRequest))
{
this.Response.CloseConnectionAfterError();
}
throw new HttpException(SR.GetString("Max_request_length_exceeded"), null, 0xbbc);
}
The point is that you'll be skipping that code and implementing your own custom content length check instead. If you use Reflector to look at the rest of "GetEntireRawContent" to use it as a model implementation, you'll see that it basically does the following: calls GetPreloadedEntityBody, checks if there's more to load by calling IsEntireEntityBodyIsPreloaded, and finally loops through calls to ReadEntityBody to get the rest of the data. The data read by GetPreloadedEntityBody and ReadEntityBody are dumped into a specialized stream, which automatically uses a temporary file as a backing store once it crosses a size threshold.
A basic implementation would look like this:
MemoryStream request_content = new MemoryStream();
int bytesRemaining = wr.GetTotalEntityBodyLength() - wr.GetPreloadedEntityBodyLength();
byte[] preloaded_data = wr.GetPreloadedEntityBody();
if (preloaded_data != null)
request_content.Write( preloaded_data, 0, preloaded_data.Length );
if (!wr.IsEntireEntityBodyIsPreloaded()) //not a type-o, they use "Is" redundantly in the
{
int BUFFER_SIZE = 0x2000; //8K buffer or whatever
byte[] buffer = new byte[BUFFER_SIZE];
while (bytesRemaining > 0)
{
bytesRead = wr.ReadEntityBody(buffer, Math.Min( bytesRemaining, BUFFER_SIZE )); //Read another set of bytes
bytesRemaining -= bytesRead; // Update the bytes remaining
request_content.Write( buffer, 0, bytesRead ); // Write the chunks to the backing store (memory stream or whatever you want)
}
if (bytesRead == 0) //failure to read or nothing left to read
break;
}
At that point, you'll have your entire request in a MemoryStream. However, rather than download the entire request like that, what I've done is offload that "bytesRemaining" loop into a class with a "ReadEnough( int max_index )" method that is called on demand from a specialized MemoryStream that "loads enough" into the stream to access the byte being accessed.
Ultimately, that architecture allows me to send the request directly to a parser that reads from the memory stream, and the memory stream automatically loads more data from the worker request as needed. I've also implemented events so that as each element of the multi-part content stream is parsed, it fires events when each new part is identified and when each part is completely received.
You can do that in the web.config
<httpRuntime maxRequestLength="11000" />
11000 == 11 mb

How to get image directly?

Follwing webpage includes light adult contents. Please do not click link if you don't want it.
go to : http://www.hqasians.com/tgp/bigasiantits/MaiNishida/at.htm
you can see several thumb images.
click one of them. you can see large image.
Check current page url. It will be like ~~~~~~~~~~~~~~~~/tgp/bigasiantits/MaiNishida/images/01.jpg
you can know how to access another image by changing last .jpg name of whole url
change 01.jpg to 02.jpg and enter.
But, you will encounter website's main page not 02.jpg.
Is this security way to block direct access by that site ?
Is there any work-around way to get image directly?
Following is my codes.
InputStream bmis;
bmis = new URL(params[0]).openStream();
final Drawable image =
new BitmapDrawable(BitmapFactory.decodeStream(new FlushedInputStream(bmis)));
if(image != null)
{
activity.setContentView(imageSwitcher);
imageSwitcher.setImageDrawable(image);
}
I'm only guessing here, but I think what this site does is to check the "Referer" field from the HTTP request header to check whether the request came from within the site, or from outside.
It isn't a secure way of blocking direct access. In fact, there's an workaround, but I don't think the site rules allow me to write it here, so, you'll have to figure out yourself.
It's because of the Referrer. You have to be referred by that main page to open the picture.
Sorry I'm not sure how to use Android, but C# code should look like this:
static void Main(string[] args)
{
for (int i = 1; i <= 15; i++)
{
HttpWebRequest request =
WebRequest.Create(
string.Format("http://www.hqasians.com/tgp/bigasiantits/MaiNishida/images/{0:00}.jpg", i)
) as HttpWebRequest;
request.Credentials = CredentialCache.DefaultCredentials;
request.Referer = "http://www.hqasians.com/tgp/bigasiantits/MaiNishida/at.htm";
request.Method = "POST";
WebResponse response = request.GetResponse();
string inputFile = string.Format("{0}.jpg", i);
Console.WriteLine(response.ResponseUri.AbsoluteUri);
using (Stream file = File.OpenWrite(inputFile))
{
CopyStream(response.GetResponseStream(), file);
}
}
}
/// <summary>
/// Copies the contents of input to output. Doesn't close either stream.
/// </summary>
public static void CopyStream(Stream input, Stream output)
{
byte[] buffer = new byte[8 * 1024];
int len;
while ((len = input.Read(buffer, 0, buffer.Length)) > 0)
{
output.Write(buffer, 0, len);
}
}
The CopyStream method is got from here: How do I save a stream to a file in C#?

Creating an encrypted log file

I am creating a client side application which needs to create a log of the user activity but for various reasons this log must not be human readable.
Currently for my development I am creating a plain text log which looks something like this:
12/03/2009 08:34:21 -> User 'Bob' logged in
12/03/2009 08:34:28 -> Navigated to config page
12/03/2009 08:34:32 -> Option x changed to y
When I deploy my application, the log must not be in plain text, so all text must be encrypted. This doesn't appear to be straightforward to achieve as I need the log file to dynamically update as each entry is added.
The approach I was thinking about was to create a binary file, encrypt each log entry in isolation and then append it to the binary file with some suitable demarcation between each entry.
Does anyone know of any common approaches to this problem, I'm sure there has to be a better solution!
Don't encrypt individual log entries separately and write them to a file as suggested by other posters, because an attacker would easily be able to identify patterns in the log file. See the block cipher modes Wikipedia entry to learn more about this problem.
Instead, make sure that the encryption of a log entry depends on the previous log entries. Although this has some drawbacks (you cannot decrypt individual log entries as you always need to decrypt the entire file), it makes the encryption a lot stronger. For our own logging library, SmartInspect, we use AES encryption and the CBC mode to avoid the pattern problem. Feel free to give SmartInspect a try if a commercial solution would be suitable.
This is not really my thing, I'll admit that readily, but can't you encrypt each entry individually and then append it to the logfile? If you that refrain from encrypting the timestamp, you can easily find entries your are looking for and decrypt those when needed.
My point being mainly that appending individual encrypted entries to a file does not necessarily need to be binary entries appended to a binary file. Encryption with (for example) gpg will yield ascii garble that can be appended to an ascii file. Would that solve you problem?
FWIW, the one time I needed an encrypted logger I used a symmetric key (for performance reasons) to encrypt the actual log entries.
The symmetric 'log file key' was then encrypted under a public key and stored at the beginning of the log file and a separate log reader used the private key to decrypt the 'log file key' and read the entries.
The whole thing was implemented using log4j and an XML log file format (to make it easier for the reader to parse) and each time the log files were rolled over a new 'log file key' was generated.
Assuming you're using some sort of logging framework, e.g., log4j et al, then you should be able to create a custom implementation of Appender (or similar) that encrypts each entry, as #wzzrd suggested.
It is not clear to me wheter your concern is on the security, or the implement.
A simple implement is to hook up with a stream encryptor. A stream encryptor maintains its own state and can encrypt on the fly.
StreamEncryptor<AES_128> encryptor;
encryptor.connectSink(new std::ofstream("app.log"));
encryptor.write(line);
encryptor.write(line2);
...
Very old question and I'm sure the tech world has made much progress, but FWIW Bruce Schneier and John Kelsey wrote a paper on how to do this: https://www.schneier.com/paper-auditlogs.html
The context is not just security but also preventing the corruption or change of existing log file data if the system that hosts the log/audit files is compromised.
Encrypting each log entry individually would decrease the security of your ciphertext a lot, especially because you're working with very predictable plaintext.
Here's what you can do:
Use symmetric encryption (preferably AES)
Pick a random master key
Pick a security window (5 minutes, 10 minutes, etc.)
Then, pick a random temporary key at the beginning of each window (every 5 minutes, every 10 minutes, etc.)
Encrypt each log item separately using the temporary key and append to a temporary log file.
When the window's closed (the predetermined time is up), decrypt each element using the temporary key, decrypt the master log file using the master key, merge the files, and encrypt using the master key.
Then, pick a new temporary key and continue.
Also, change the master key each time you rotate your master log file (every day, every week, etc.)
This should provide enough security.
I'm wondering what kind of application you write. A virus or a Trojan horse? Anyway ...
Encrypt each entry alone, convert it to some string (Base64, for example) and then log that string as the "message".
This allows you to keep parts of the file readable and only encrypt important parts.
Notice that there is another side to this coin: If you create a fully encrypted file and ask the user for it, she can't know what you will learn from the file. Therefore, you should encrypt as little as possible (passwords, IP addresses, costumer data) to make it possible for the legal department to verify what data is leaving.
A much better approach would be to an obfuscator for the log file. That simply replaces certain patterns with "XXX". You can still see what happened and when you need a specific piece of data, you can ask for that.
[EDIT] This story has more implications that you'd think at first glance. This effectively means that a user can't see what's in the file. "User" doesn't necessarily include "cracker". A cracker will concentrate on encrypted files (since they are probably more important). That's the reason for the old saying: As soon as someone gets access to the machine, there is no way to prevent him to do anything on it. Or to say it another way: Just because you don't know how doesn't mean someone else also doesn't. If you think you have nothing to hide, you haven't thought about yourself.
Also, there is the issue of liability. Say, some data leaks on the Internet after you get a copy of the logs. Since the user has no idea what is in the log files, how can you prove in court that you weren't the leak? Bosses could ask for the log files to monitor their pawns, asking to have it encoded so the peasants can't notice and whine about it (or sue, the scum!).
Or look at it from a completely different angle: If there was no log file, no one could abuse it. How about enabling debugging only in case of an emergency? I've configured log4j to keep the last 200 log messages in a buffer. If an ERROR is logged, I dump the 200 messages to the log. Rationale: I really don't care what happens during the day. I only care for bugs. Using JMX, it's simple to set the debug level to ERROR and lower it remotely at runtime when you need more details.
For .Net see Microsoft Application blocks for log and encrypt functionality:
http://msdn.microsoft.com/en-us/library/dd203099.aspx
I would append encrypted log entries to a flat text file using suitable demarcation between each entry for the decryption to work.
I have the exact same need as you. Some guy called 'maybeWeCouldStealAVa' wrote a good implementation in: How to append to AES encrypted file , however this suffered from not being flushable - you would have to close and reopen the file each time you flush a message, to be sure not to lose anything.
So I've written my own class to do this:
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.security.*;
public class FlushableCipherOutputStream extends OutputStream
{
private static int HEADER_LENGTH = 16;
private SecretKeySpec key;
private RandomAccessFile seekableFile;
private boolean flushGoesStraightToDisk;
private Cipher cipher;
private boolean needToRestoreCipherState;
/** the buffer holding one byte of incoming data */
private byte[] ibuffer = new byte[1];
/** the buffer holding data ready to be written out */
private byte[] obuffer;
/** Each time you call 'flush()', the data will be written to the operating system level, immediately available
* for other processes to read. However this is not the same as writing to disk, which might save you some
* data if there's a sudden loss of power to the computer. To protect against that, set 'flushGoesStraightToDisk=true'.
* Most people set that to 'false'. */
public FlushableCipherOutputStream(String fnm, SecretKeySpec _key, boolean append, boolean _flushGoesStraightToDisk)
throws IOException
{
this(new File(fnm), _key, append,_flushGoesStraightToDisk);
}
public FlushableCipherOutputStream(File file, SecretKeySpec _key, boolean append, boolean _flushGoesStraightToDisk)
throws IOException
{
super();
if (! append)
file.delete();
seekableFile = new RandomAccessFile(file,"rw");
flushGoesStraightToDisk = _flushGoesStraightToDisk;
key = _key;
try {
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[16];
byte[] headerBytes = new byte[HEADER_LENGTH];
long fileLen = seekableFile.length();
if (fileLen % 16L != 0L) {
throw new IllegalArgumentException("Invalid file length (not a multiple of block size)");
} else if (fileLen == 0L) {
// new file
// You can write a 16 byte file header here, including some file format number to represent the
// encryption format, in case you need to change the key or algorithm. E.g. "100" = v1.0.0
headerBytes[0] = 100;
seekableFile.write(headerBytes);
// Now appending the first IV
SecureRandom sr = new SecureRandom();
sr.nextBytes(iv);
seekableFile.write(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
} else if (fileLen <= 16 + HEADER_LENGTH) {
throw new IllegalArgumentException("Invalid file length (need 2 blocks for iv and data)");
} else {
// file length is at least 2 blocks
needToRestoreCipherState = true;
}
} catch (InvalidKeyException e) {
throw new IOException(e.getMessage());
} catch (NoSuchAlgorithmException e) {
throw new IOException(e.getMessage());
} catch (NoSuchPaddingException e) {
throw new IOException(e.getMessage());
} catch (InvalidAlgorithmParameterException e) {
throw new IOException(e.getMessage());
}
}
/**
* Writes one _byte_ to this output stream.
*/
public void write(int b) throws IOException {
if (needToRestoreCipherState)
restoreStateOfCipher();
ibuffer[0] = (byte) b;
obuffer = cipher.update(ibuffer, 0, 1);
if (obuffer != null) {
seekableFile.write(obuffer);
obuffer = null;
}
}
/** Writes a byte array to this output stream. */
public void write(byte data[]) throws IOException {
write(data, 0, data.length);
}
/**
* Writes <code>len</code> bytes from the specified byte array
* starting at offset <code>off</code> to this output stream.
*
* #param data the data.
* #param off the start offset in the data.
* #param len the number of bytes to write.
*/
public void write(byte data[], int off, int len) throws IOException
{
if (needToRestoreCipherState)
restoreStateOfCipher();
obuffer = cipher.update(data, off, len);
if (obuffer != null) {
seekableFile.write(obuffer);
obuffer = null;
}
}
/** The tricky stuff happens here. We finalise the cipher, write it out, but then rewind the
* stream so that we can add more bytes without padding. */
public void flush() throws IOException
{
try {
if (needToRestoreCipherState)
return; // It must have already been flushed.
byte[] obuffer = cipher.doFinal();
if (obuffer != null) {
seekableFile.write(obuffer);
if (flushGoesStraightToDisk)
seekableFile.getFD().sync();
needToRestoreCipherState = true;
}
} catch (IllegalBlockSizeException e) {
throw new IOException("Illegal block");
} catch (BadPaddingException e) {
throw new IOException("Bad padding");
}
}
private void restoreStateOfCipher() throws IOException
{
try {
// I wish there was a more direct way to snapshot a Cipher object, but it seems there's not.
needToRestoreCipherState = false;
byte[] iv = cipher.getIV(); // To help avoid garbage, re-use the old one if present.
if (iv == null)
iv = new byte[16];
seekableFile.seek(seekableFile.length() - 32);
seekableFile.read(iv);
byte[] lastBlockEnc = new byte[16];
seekableFile.read(lastBlockEnc);
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
byte[] lastBlock = cipher.doFinal(lastBlockEnc);
seekableFile.seek(seekableFile.length() - 16);
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
byte[] out = cipher.update(lastBlock);
assert out == null || out.length == 0;
} catch (Exception e) {
throw new IOException("Unable to restore cipher state");
}
}
public void close() throws IOException
{
flush();
seekableFile.close();
}
}
Here's an example of using it:
import org.junit.Test;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.io.BufferedWriter;
public class TestFlushableCipher {
private static byte[] keyBytes = new byte[] {
// Change these numbers, lest other StackOverflow readers can decrypt your files.
-53, 93, 59, 108, -34, 17, -72, -33, 126, 93, -62, -50, 106, -44, 17, 55
};
private static SecretKeySpec key = new SecretKeySpec(keyBytes,"AES");
private static int HEADER_LENGTH = 16;
private static BufferedWriter flushableEncryptedBufferedWriter(File file, boolean append) throws Exception
{
FlushableCipherOutputStream fcos = new FlushableCipherOutputStream(file, key, append, false);
return new BufferedWriter(new OutputStreamWriter(fcos, "UTF-8"));
}
private static InputStream readerEncryptedByteStream(File file) throws Exception
{
FileInputStream fin = new FileInputStream(file);
byte[] iv = new byte[16];
byte[] headerBytes = new byte[HEADER_LENGTH];
if (fin.read(headerBytes) < HEADER_LENGTH)
throw new IllegalArgumentException("Invalid file length (failed to read file header)");
if (headerBytes[0] != 100)
throw new IllegalArgumentException("The file header does not conform to our encrypted format.");
if (fin.read(iv) < 16) {
throw new IllegalArgumentException("Invalid file length (needs a full block for iv)");
}
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
return new CipherInputStream(fin,cipher);
}
private static BufferedReader readerEncrypted(File file) throws Exception
{
InputStream cis = readerEncryptedByteStream(file);
return new BufferedReader(new InputStreamReader(cis));
}
#Test
public void test() throws Exception {
File zfilename = new File("c:\\WebEdvalData\\log.x");
BufferedWriter cos = flushableEncryptedBufferedWriter(zfilename, false);
cos.append("Sunny ");
cos.append("and green. \n");
cos.close();
int spaces=0;
for (int i = 0; i<10; i++) {
cos = flushableEncryptedBufferedWriter(zfilename, true);
for (int j=0; j < 2; j++) {
cos.append("Karelia and Tapiola" + i);
for (int k=0; k < spaces; k++)
cos.append(" ");
spaces++;
cos.append("and other nice things. \n");
cos.flush();
tail(zfilename);
}
cos.close();
}
BufferedReader cis = readerEncrypted(zfilename);
String msg;
while ((msg=cis.readLine()) != null) {
System.out.println(msg);
}
cis.close();
}
private void tail(File filename) throws Exception
{
BufferedReader infile = readerEncrypted(filename);
String last = null, secondLast = null;
do {
String msg = infile.readLine();
if (msg == null)
break;
if (! msg.startsWith("}")) {
secondLast = last;
last = msg;
}
} while (true);
if (secondLast != null)
System.out.println(secondLast);
System.out.println(last);
System.out.println();
}
}

Resources