tag 694091 + patch tag 693460 + patch thanks Hi,
attached you can find a patch against bcrypt which makes it support large files (> 2 GiB) and files which exceed the amount of available RAM. I tested this with a 128 MiB RAM VM: root@squeezevm:/tmp# uname -a Linux squeezevm 2.6.32-5-amd64 #1 SMP Sun Sep 23 10:07:46 UTC 2012 x86_64 GNU/Linux root@squeezevm:/tmp# free -m total used free shared buffers cached Mem: 118 62 56 0 25 17 -/+ buffers/cache: 19 99 Swap: 382 0 382 root@squeezevm:/tmp# dd if=/dev/zero of=bigfile bs=10M count=20 20+0 records in 20+0 records out 209715200 bytes (210 MB) copied, 0.558276 s, 376 MB/s root@squeezevm:/tmp# ./bcrypt -c bigfile Encryption key: Again: root@squeezevm:/tmp# ls -hltr total 201M -rwxr-xr-x 1 root root 28K Nov 25 06:03 bcrypt -rw-r--r-- 1 root root 201M Nov 25 06:04 bigfile.bfe root@squeezevm:/tmp# free -m total used free shared buffers cached Mem: 118 75 43 0 21 34 -/+ buffers/cache: 20 98 Swap: 382 0 382 Also, here is a large file on an i686 (32-bit) machine: root@stability /raid $ ls -l bigfile -rw-r--r-- 1 root root 2621440000 2012-11-25 12:19 bigfile root@stability /raid $ /home/michael/bcrypt-1.1/bcrypt -c bigfile Encryption key: Again: /home/michael/bcrypt-1.1/bcrypt -c bigfile 188,96s user 1286,90s system 93% cpu 26:11,79 total root@stability /raid $ ls -l bigfile.bfe -rw-r--r-- 1 root root 2621440066 2012-11-25 12:45 bigfile.bfe root@stability /raid $ uname -a Linux stability 2.6.30.1 #1 SMP Fri Jul 17 23:19:30 CEST 2009 i686 GNU/Linux I have verified that the tool still works correctly by using a script. The script runs 1000 iterations of each test, that is, it tests that the new version can still read files created by the old version, and it tests that the old version can still read files created by the new version. You can find the script in the pull request linked below. I also tested that files created on big-endian machines can be decrypted on little-endian machines and vice-versa. I have also submitted this patch to the new upstream location at https://github.com/casta/bcrypt/pull/1 -- Best regards, Michael
diff -ur bcrypt-1.1.O/functions.h bcrypt-1.1/functions.h --- bcrypt-1.1.O/functions.h 2002-09-12 11:09:38.000000000 +0200 +++ bcrypt-1.1/functions.h 2012-11-25 12:12:29.451956678 +0100 @@ -11,10 +11,8 @@ #include "blowfish.h" /* from wrapbf.c */ -uLong BFEncrypt(char **input, char *key, uLong sz, - BCoptions *options); -uLong BFDecrypt(char **input, char *key, char *key2, - uLong sz, BCoptions *options); +off_t BFEncrypt(int infd, int outfd, char *key, BCoptions *options); +off_t BFDecrypt(int infd, int outfd, char *key, char *key2, BCoptions *options); /* from keys.c */ char * getkey(int type); diff -ur bcrypt-1.1.O/main.c bcrypt-1.1/main.c --- bcrypt-1.1.O/main.c 2002-09-12 11:50:12.000000000 +0200 +++ bcrypt-1.1/main.c 2012-11-25 12:12:41.835771398 +0100 @@ -6,6 +6,10 @@ * for details * ==================================================================== */ +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + #include "includes.h" #include "defines.h" @@ -156,8 +160,8 @@ } int main(int argc, char *argv[]) { - uLong sz = 0; - char *input = NULL, *key = NULL, *key2 = NULL; + off_t sz = 0; + char *key = NULL, *key2 = NULL; char *infile = NULL, *outfile = NULL; struct stat statbuf; BCoptions options; @@ -174,42 +178,35 @@ mutateKey(&key, &key2); } - sz = readfile(infile, &input, options.type, key, statbuf); - - if ((options.type == DECRYPT) && (testEndian(input))) - swapCompressed(&input, sz); + int infd = open(infile, O_RDONLY); + if (infd == -1) { + perror("open(infile)"); + continue; + } - if ((options.compression == 1) && (options.type == ENCRYPT)) { - options.origsize = sz; - sz = docompress(&input, sz); + int outfd = open(outfile, O_CREAT | O_TRUNC | O_RDWR, 0644); + if (outfd == -1) { + perror("open(infile)"); + continue; } if (options.type == ENCRYPT) { - sz = attachKey(&input, key, sz); - sz = padInput(&input, sz); - } + options.origsize = statbuf.st_size; - if (options.type == ENCRYPT) - sz = BFEncrypt(&input, key, sz, &options); - else if (options.type == DECRYPT) - if ((sz = BFDecrypt(&input, key, key2, sz, &options)) == 0) { + sz = BFEncrypt(infd, outfd, key, &options); + } else { + if ((sz = BFDecrypt(infd, outfd, key, key2, &options)) == 0) { fprintf(stderr, "Invalid encryption key for file: %s\n", infile); exit(1); } + ftruncate(outfd, sz); + } - if ((options.compression == 1) && (options.type == DECRYPT)) - sz = douncompress(&input, sz, options); - - writefile(outfile, input, sz, options, statbuf); - - if ((input = realloc(input, sizeof(char *))) == NULL) - memerror(); + close(infd); + close(outfd); if (options.remove == 1) deletefile(infile, options, key, statbuf); - - if (input != NULL) - free(input); } if(!sz) diff -ur bcrypt-1.1.O/Makefile bcrypt-1.1/Makefile --- bcrypt-1.1.O/Makefile 2012-11-25 12:31:43.000000000 +0100 +++ bcrypt-1.1/Makefile 2012-11-25 12:07:31.035957620 +0100 @@ -1,6 +1,6 @@ DEFAULTS = Makefile includes.h blowfish.h functions.h config.h CC = gcc -CFLAGS = -O2 -Wall +CFLAGS = -O2 -Wall -D_FILE_OFFSET_BITS=64 COMPILE = ${CC} ${CFLAGS} OBJS = main.o blowfish.o rwfile.o keys.o wrapbf.o endian.o wrapzl.o LDFLAGS = -L/usr/local/lib -lz diff -ur bcrypt-1.1.O/wrapbf.c bcrypt-1.1/wrapbf.c --- bcrypt-1.1.O/wrapbf.c 2002-09-13 08:55:04.000000000 +0200 +++ bcrypt-1.1/wrapbf.c 2012-11-25 12:12:27.531985410 +0100 @@ -11,107 +11,244 @@ #include "defines.h" #include "functions.h" -uLong BFEncrypt(char **input, char *key, uLong sz, BCoptions *options) { - uInt32 L, R; - uLong i; +#include <stdint.h> +#include <sys/types.h> +#include <unistd.h> + +/* 256 KiB, as recommended by http://www.zlib.net/zlib_how.html */ +#define ZCHUNK (256 * 1024) + +off_t BFEncrypt(int infd, int outfd, char *key, BCoptions *options) { BLOWFISH_CTX ctx; - int j; + const int block_size = (sizeof(uint32_t) * 2); unsigned char *myEndian = NULL; - j = sizeof(uInt32); + uLong sz = 0; + + /* zlib variables */ + int ret, flush; + unsigned have; + z_stream strm; + uint8_t in[ZCHUNK]; + uint8_t out[ZCHUNK]; + + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + ret = deflateInit(&strm, Z_DEFAULT_COMPRESSION); + if (ret != Z_OK) + exit(2); + getEndian(&myEndian); - memmove(*input+2, *input, sz); + write(outfd, myEndian, sizeof(uint8_t)); + write(outfd, &options->compression, sizeof(uint8_t)); + + Blowfish_Init (&ctx, (unsigned char*)key, MAXKEYBYTES); - memcpy(*input, myEndian, 1); - memcpy(*input+1, &options->compression, 1); + int n; + /* 2 * ZCHUNK for the overhead when compressing */ + uint8_t rest[2 * ZCHUNK]; + int restidx = 0; + uint32_t *rwalk = (uint32_t*)rest; + do { + if ((n = read(infd, in, ZCHUNK)) < 0) { + exit(3); + } + strm.avail_in = n; + strm.next_in = in; + flush = ((n == 0) ? Z_FINISH : Z_NO_FLUSH); + if (options->compression) { + do { + strm.avail_out = ZCHUNK; + strm.next_out = out; + ret = deflate(&strm, flush); + + have = ZCHUNK - strm.avail_out; + memcpy(rest + restidx, out, have); + restidx += have; + sz += have; + } while (strm.avail_out == 0); + } else { + memcpy(rest + restidx, in, n); + restidx += n; + sz += n; + } - sz += 2; /* add room for endian and compress flags */ + /* Encrypt as many blocks (each block_size bytes big) as we can */ + int i; + /* i adresses sizeof(uint32_t) = 4 byte blocks, + * but we use (restidx / block_size) loop up to full blocks */ + for (i = 0; i < 2 * (restidx / block_size); i += 2) { + Blowfish_Encrypt(&ctx, &rwalk[i], &rwalk[i + 1]); + write(outfd, &rwalk[i], block_size); + } + if (i > 0) { + int remaining = restidx - (i * sizeof(uint32_t)); + memmove(rest, &rwalk[i], remaining); + restidx = remaining; + } + } while (flush != Z_FINISH); - Blowfish_Init (&ctx, key, MAXKEYBYTES); + /* Now append the key (MAXKEYBYTES bytes) */ + memcpy(rest + restidx, key, MAXKEYBYTES); + restidx += MAXKEYBYTES; + sz += MAXKEYBYTES; + + /* Pad the rest with 0-bytes */ + int r; + if (sz >= block_size) + r = getremain(sz, block_size); + else + r = block_size - sz; - for (i = 2; i < sz; i+=(j*2)) { /* start just after tags */ - memcpy(&L, *input+i, j); - memcpy(&R, *input+i+j, j); - Blowfish_Encrypt(&ctx, &L, &R); - memcpy(*input+i, &L, j); - memcpy(*input+i+j, &R, j); + memset(rest + restidx, '\0', r); + restidx += r; + sz += r; + + /* Encrypt the rest */ + int i; + for (i = 0; i < (restidx / sizeof(uint32_t)); i += 2) { + Blowfish_Encrypt(&ctx, &rwalk[i], &rwalk[i+1]); + write(outfd, rwalk + i, block_size); } - if (options->compression == 1) { - if ((*input = realloc(*input, sz + j + 1)) == NULL) - memerror(); - - memset(*input+sz, 0, j + 1); - memcpy(*input+sz, &options->origsize, j); - sz += j; /* make room for the original size */ - } + if (options->compression) + write(outfd, &options->origsize, sizeof(uint32_t)); free(myEndian); return(sz); + } -uLong BFDecrypt(char **input, char *key, char *key2, uLong sz, - BCoptions *options) { - uInt32 L, R; - uLong i; +off_t BFDecrypt(int infd, int outfd, char *key, char *key2, BCoptions *options) { BLOWFISH_CTX ctx; - int j, swap = 0; - unsigned char *myEndian = NULL; + const int block_size = (sizeof(uint32_t) * 2); char *mykey = NULL; + int swap = 0; + + /* zlib variables */ + int ret; + unsigned have; + z_stream strm; + uint8_t in[ZCHUNK]; + uint8_t out[ZCHUNK]; + + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit(&strm); + if (ret != Z_OK) + exit(2); - j = sizeof(uInt32); - if ((mykey = malloc(MAXKEYBYTES + 1)) == NULL) + if ((mykey = calloc(MAXKEYBYTES + 1, 1)) == NULL) memerror(); - memset(mykey, 0, MAXKEYBYTES + 1); + uint8_t endianness, compressed; + read(infd, &endianness, sizeof(uint8_t)); + read(infd, &compressed, sizeof(uint8_t)); - if ((swap = testEndian(*input)) == 1) + if ((swap = testEndian(&endianness)) == 1) memcpy(mykey, key2, MAXKEYBYTES); else memcpy(mykey, key, MAXKEYBYTES); - memcpy(&options->compression, *input+1, 1); - - if (options->compression == 1) { - memcpy(&options->origsize, *input+(sz - j), j); - sz -= j; /* dump the size tag */ - } - - sz -= 2; /* now dump endian and compress flags */ - - Blowfish_Init (&ctx, mykey, MAXKEYBYTES); + Blowfish_Init (&ctx, (unsigned char*)mykey, MAXKEYBYTES); - for (i = 0; i < sz; i+=(j*2)) { - memcpy(&L, *input+i+2, j); - memcpy(&R, *input+i+j+2, j); - - if (swap == 1) { - L = swapEndian(L); - R = swapEndian(R); + int n; + /* 2 * ZCHUNK for the overhead when compressing */ + uint8_t rest[2 * ZCHUNK]; + int restidx = 0; + uint32_t *inwalk = (uint32_t*)in; + do { + if ((n = read(infd, in, ZCHUNK)) < 0) { + exit(3); } - Blowfish_Decrypt(&ctx, &L, &R); + /* Decrypt as many blocks (each block_size bytes big) as we can */ + int i; + /* i adresses sizeof(uint32_t) = 4 byte blocks, + * but we use (restidx / block_size) loop up to full blocks */ + for (i = 0; i < 2 * (n / block_size); i += 2) { + if (swap) { + inwalk[i] = swapEndian(inwalk[i]); + inwalk[i + 1] = swapEndian(inwalk[i + 1]); + } + Blowfish_Decrypt(&ctx, &inwalk[i], &inwalk[i + 1]); + if (swap) { + inwalk[i] = swapEndian(inwalk[i]); + inwalk[i + 1] = swapEndian(inwalk[i + 1]); + } + } - if (swap == 1) { - L = swapEndian(L); - R = swapEndian(R); + strm.avail_in = n; + strm.next_in = in; + if (compressed) { + do { + strm.avail_out = ZCHUNK; + strm.next_out = out; + ret = inflate(&strm, Z_NO_FLUSH); + switch (ret) { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; + /* fall-through */ + case Z_DATA_ERROR: + case Z_MEM_ERROR: + exit(10); + } + + have = ZCHUNK - strm.avail_out; + memcpy(rest + restidx, out, have); + write(outfd, out, have); + } while (strm.avail_out == 0); + + /* If zlib is done and we still have data in our buffer, that extra data + * needs to be written out to file. It contains the key + padding + + * original filesize. */ + if (strm.avail_in > sizeof(uint32_t)) { + /* We don't write the original file size since it is not stored for + * uncompressed files either. Actually, we don't need it at all and + * just support it for compatibility with earlier versions. */ + write(outfd, strm.next_in, strm.avail_in - sizeof(uint32_t)); + } + } else { + /* Abort the loop on EOF when the file is not compressed */ + if (n == 0) + ret = Z_STREAM_END; + + /* XXX: If we would buffer blocks as large as the maximum amount of + * padding + key can be, we could almost reliably detect the end of file + * and handle stdin to stdout decryptions correctly. */ + write(outfd, in, n); } + } while (ret != Z_STREAM_END); - memcpy(*input+i, &L, j); - memcpy(*input+i+j, &R, j); + /* Strip off the padding */ + uint8_t lastbyte; + off_t seek_offset = -1, cur_offset; + do { + cur_offset = lseek(outfd, seek_offset, SEEK_CUR); + if (cur_offset == (off_t)-1) { + exit(8); + } + read(outfd, &lastbyte, sizeof(uint8_t)); + seek_offset = -2; + } while (lastbyte == '\0'); + + cur_offset = lseek(outfd, -MAXKEYBYTES, SEEK_CUR); + if (cur_offset == (off_t)-1) { + exit(9); } - while (memcmp(*input+(sz-1), "\0", 1) == 0) /* strip excess nulls */ - sz--; /* from decrypted files */ + /* Verify that the stored key matches our key */ + uint8_t savedkey[MAXKEYBYTES]; + read(outfd, savedkey, MAXKEYBYTES); - sz -= MAXKEYBYTES; + if (memcmp(savedkey, mykey, MAXKEYBYTES) != 0) + return 0; - if (memcmp(*input+sz, mykey, MAXKEYBYTES) != 0) - return(0); - - free(mykey); - free(myEndian); - return(sz); + return cur_offset; }