Next: , Previous: Writing Encrypted Files, Up: Top



6 Reading and Seeking Encrypted Files

This short sample program should give you an idea what is and how to use libencio.

#include <stdio.h>
#include "encio.h"

int main(int argc, char **argv)
{
  ENCFILE *ef;
  char buf[100];

  char *passphrase = "acomplicatedpassphrase";

  /* open an file encrypted with passphrase */
  ef = enc_fopen("test.txt.nc", "rb", passphrase);
  enc_add_index(ef, "test.txt.ix", passphrase, 
    INDEX_LOAD | INDEX_CREATE | INDEX_SAVE);

  /* read first 100 bytes of the encrypted data*/
  enc_fread(ef, 1, buf, 100);
  printf("First 100 bytes of the file: %s", ef);

  /* read last 100 bytes of the encrypted data */
  enc_fseek(ef, -100, SEEK_SET);
  enc_fread(ef, 1, buf, 100);
  printf("Last 100 bytes of the file: %s", ef);

  enc_fclose(ef);
}

In the sample, we assume that we have a file named test.txt.nc which has been symmetrically encrypted with passphrase "acomplicatedpassphrase".

enc_fopen() opens the file for reading, just as fopen() would open a non-encrypted counterpart. The difference, compared to stdio fopen() call, is a third parameter through which you supply the passphrase for decryption. All other libencio functions in the example function as their stdio counterparts.

7 Random access (seeking)

The other difference from stdio is that a call to enc_add_index() is required in order to support random seeking of the file. This is needed because in most encryption modes considered secure (eg, feedback modes - CFB), in order to decrypt n-th byte of a file, one must decrypt all preceeding bytes as well. In the worst case scenario – a seek to the last byte in the file – the complete file must be decrypted. Such an implementation of seeking would be unacceptably timeconsuming for large files.

libencio handles this by creating an "encryption state index" (ENCINDEX) on the file - a snapshot of decryption engine state (usually, the feedback register, FR), at predefined locations ("bookmarks") in the file (usually, every blocksize bytes, where by default blocksize = 1024). This allows libencio to quickly resume decryption starting from the bookmark nearest to the requested seek location. The worst case scenario in seeking with an index is a required decryption of blocksize-1 bytes, which is usually acceptable.

Memory required for an index is proportional to (fsize/blocksize)*FR_size where fsize is the encrypted file size and FR_size is the size of the feedback register for the cipher mode. For example, using 256 bit AES cipher requires 32 bytes per bookmark, which for a 500MB file and one bookmark per kb (blocksize = 1024), will generate a 16MB encryption state index. Increasing the blocksize will reduce the ENCINDEX footprint, but will degrade the seek performance.

ENCINDEX can (and should, for frequently accessed files) be saved to speed up construction on subsequent openings of the file. When and ENCINDEX is saved, it is encrypted with the same passphrase as the file which it indexes. The index is saved by adding an INDEX_SAVE flag as the fourth parameter of the enc_add_index() call. It can be subsequently loaded by specifying the INDEX_LOAD flag.