diff --git a/configure.ac b/configure.ac
index 0297390..0504dff 100644
--- a/configure.ac
+++ b/configure.ac
@@ -216,6 +216,9 @@ PKG_CHECK_MODULES([GLIB],[glib-2.0 >= $GLIB_REQUIRED])
 PKG_CHECK_MODULES([GOBJECT],[gobject-2.0])
 PKG_CHECK_MODULES([GTK],[gtk+-$GTK_API_VERSION >= $GTK_REQUIRED])
 
+### TODO: why is the 3rd arg needed, is it okay?
+PKG_CHECK_MODULES([LIBGNUTLS],[gnutls >= 3.2.0],[LIBS="$LIBS $LIBGNUTLS_LIBS"])
+
 wcs_funcs_includes="
 #ifdef HAVE_STRING_H
 # if !STDC_HEADERS && HAVE_MEMORY_H
diff --git a/src/vtestream-file.h b/src/vtestream-file.h
index 93ed6ea..ed77ab5 100644
--- a/src/vtestream-file.h
+++ b/src/vtestream-file.h
@@ -82,17 +82,34 @@
 #include <string.h>
 #include <unistd.h>
 
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
 #include "vteutils.h"
 
+/* For encryption design and details, see
+ * https://bugzilla.gnome.org/show_bug.cgi?id=738601
+ * (comment 28 onwards).
+ */
+#define VTE_CIPHER_ALGORITHM GNUTLS_CIPHER_AES_256_GCM
+#define VTE_CIPHER_KEY_SIZE 32
+#define VTE_CIPHER_BLOCK_SIZE 16
+#define VTE_CIPHER_IV_SIZE 12
+#define VTE_CIPHER_TAG_SIZE 16
 
 #ifndef VTESTREAM_MAIN
 #define VTE_FILE_STREAM_BLOCKSIZE 4096
+#define VTE_FILE_STREAM_ENCRYPTION_PAYLOAD_SIZE (VTE_FILE_STREAM_BLOCKSIZE - VTE_CIPHER_TAG_SIZE)
 #else
 #define VTE_FILE_STREAM_BLOCKSIZE 4  /* Small block size for unit testing */
+#define VTE_FILE_STREAM_ENCRYPTION_PAYLOAD_SIZE 3
 #endif
 
-#define ALIGN(x) ((x) & ~(VTE_FILE_STREAM_BLOCKSIZE - 1))
-#define MOD(x)   ((x) &  (VTE_FILE_STREAM_BLOCKSIZE - 1))
+// TODO: it's a stupid name
+#define TO_UNBUFFERED_OFFSET(x) ((x) / VTE_FILE_STREAM_ENCRYPTION_PAYLOAD_SIZE * VTE_FILE_STREAM_BLOCKSIZE)
+
+#define ALIGN(x) ((x) / VTE_FILE_STREAM_ENCRYPTION_PAYLOAD_SIZE * VTE_FILE_STREAM_ENCRYPTION_PAYLOAD_SIZE)
+#define MOD(x)   ((x) % VTE_FILE_STREAM_ENCRYPTION_PAYLOAD_SIZE)
 
 
 #ifndef HAVE_PREAD
@@ -566,6 +583,9 @@ typedef struct _VteFileStream {
 	gsize wbuf_len;
 
 	gsize head, tail;
+
+	gnutls_cipher_hd_t cipher_hd;
+	unsigned char iv[VTE_CIPHER_IV_SIZE];
 } VteFileStream;
 
 typedef VteStreamClass VteFileStreamClass;
@@ -584,11 +604,33 @@ _vte_file_stream_new (void)
 static void
 _vte_file_stream_init (VteFileStream *stream)
 {
+	unsigned char key[VTE_CIPHER_KEY_SIZE];
+	gnutls_datum_t datum_key;
+
 	_vte_unbuffered_file_stream_init ((VteUnbufferedFileStream *) stream);
 
+	/* Use VTE_FILE_STREAM_BLOCKSIZE for both of them, to have room for reading or adding the GCM tag. */
 	stream->rbuf = g_malloc(VTE_FILE_STREAM_BLOCKSIZE);
 	stream->wbuf = g_malloc(VTE_FILE_STREAM_BLOCKSIZE);
 	stream->rbuf_offset = 1;  /* Invalidate the read cache. */
+
+	gnutls_global_init ();
+
+	g_assert (gnutls_cipher_get_block_size(VTE_CIPHER_ALGORITHM) == VTE_CIPHER_BLOCK_SIZE);
+	g_assert (gnutls_cipher_get_iv_size(VTE_CIPHER_ALGORITHM) == VTE_CIPHER_IV_SIZE);
+	g_assert (gnutls_cipher_get_tag_size(VTE_CIPHER_ALGORITHM) == VTE_CIPHER_TAG_SIZE);
+
+	/* Some bytes of IV will contain the offset (gsize), some other bytes will be random. */
+	g_assert (gnutls_cipher_get_iv_size(VTE_CIPHER_ALGORITHM) > (int) sizeof(gsize));
+
+	gnutls_rnd_refresh();
+	gnutls_rnd(GNUTLS_RND_KEY, key, VTE_CIPHER_KEY_SIZE);
+	gnutls_rnd(GNUTLS_RND_NONCE, stream->iv, VTE_CIPHER_IV_SIZE);
+
+	datum_key.data = key;
+	datum_key.size = VTE_CIPHER_KEY_SIZE;
+	gnutls_cipher_init(&stream->cipher_hd, VTE_CIPHER_ALGORITHM, &datum_key, NULL);
+	memset(key, 0, VTE_CIPHER_KEY_SIZE);
 }
 
 static void
@@ -599,6 +641,10 @@ _vte_file_stream_finalize (GObject *object)
 	g_free(stream->rbuf);
 	g_free(stream->wbuf);
 
+	memset(stream->iv, 0, VTE_CIPHER_IV_SIZE);
+	gnutls_cipher_deinit (stream->cipher_hd);
+	gnutls_global_deinit ();
+
 	G_OBJECT_CLASS (_vte_file_stream_parent_class)->finalize(object);
 }
 
@@ -606,9 +652,8 @@ static void
 _vte_file_stream_reset (VteStream *astream, gsize offset)
 {
 	VteFileStream *stream = (VteFileStream *) astream;
-	gsize offset_aligned = ALIGN(offset);
 
-	_vte_unbuffered_file_stream_reset (astream, offset_aligned);
+	_vte_unbuffered_file_stream_reset (astream, TO_UNBUFFERED_OFFSET(offset));
 	stream->tail = stream->head = offset;
 	memset(stream->wbuf, 0, MOD(offset));
 	stream->wbuf_len = MOD(offset);
@@ -616,21 +661,96 @@ _vte_file_stream_reset (VteStream *astream, gsize offset)
 }
 
 static void
+_vte_file_stream_encrypt_and_append (VteStream *astream, gsize offset, char *data)
+{
+	VteFileStream *stream = (VteFileStream *) astream;
+
+	g_assert(MOD(offset) == 0);
+
+#ifndef VTESTREAM_MAIN
+	memcpy (stream->iv, &offset, sizeof(gsize));
+	gnutls_cipher_set_iv (stream->cipher_hd, stream->iv, VTE_CIPHER_IV_SIZE);
+	gnutls_cipher_encrypt (stream->cipher_hd, data, VTE_FILE_STREAM_ENCRYPTION_PAYLOAD_SIZE);
+	gnutls_cipher_tag (stream->cipher_hd, data + VTE_FILE_STREAM_ENCRYPTION_PAYLOAD_SIZE, VTE_CIPHER_TAG_SIZE);
+#else
+	/* Fake encryption (lowercase<->uppercase) for unit testing */
+	data[0] ^= 0x20;
+	data[1] ^= 0x20;
+	data[2] ^= 0x20;
+	data[3] = '@';
+	(void) stream;  /* silence gcc */
+#endif
+
+	_vte_unbuffered_file_stream_append (astream, data, VTE_FILE_STREAM_BLOCKSIZE);
+}
+
+static gboolean
+_vte_file_stream_read_and_decrypt (VteStream *astream, gsize offset, char *data)
+{
+	VteFileStream *stream = (VteFileStream *) astream;
+	unsigned char tag[VTE_CIPHER_TAG_SIZE];
+	guint8 faulty = 0;
+	unsigned int i;
+
+	g_assert(MOD(offset) == 0);
+
+	if (!_vte_unbuffered_file_stream_read (astream, TO_UNBUFFERED_OFFSET(offset), data, VTE_FILE_STREAM_BLOCKSIZE))
+		return FALSE;
+
+#ifndef VTESTREAM_MAIN
+	memcpy (stream->iv, &offset, sizeof(gsize));
+	gnutls_cipher_set_iv (stream->cipher_hd, stream->iv, VTE_CIPHER_IV_SIZE);
+	gnutls_cipher_decrypt (stream->cipher_hd, data, VTE_FILE_STREAM_ENCRYPTION_PAYLOAD_SIZE);
+	gnutls_cipher_tag (stream->cipher_hd, tag, VTE_CIPHER_TAG_SIZE);
+
+	/*
+	 * Allegedly this can be parallelized, and hence is faster than memcmp:
+	 * https://bugzilla.gnome.org/show_bug.cgi?id=738601#c66
+	 */
+	for (i = 0; i < VTE_CIPHER_TAG_SIZE; i++) {
+		faulty |= tag[i] ^ data[VTE_FILE_STREAM_ENCRYPTION_PAYLOAD_SIZE + i];
+	}
+	if (faulty) {
+		/*
+		 * Data integrity failure. What to do???
+		 * One possible reason is a previous disk full, leaving a hole in the file.
+		 * To make it more complicated, we're not sure that we're using the actual filesystem block size,
+		 * so a simple check for a fully zero block wouldn't suffice.
+		 * As of now, just return a normal failure, wiping out the data to be absolutely sure.
+		 */
+		memset(data, 0, VTE_FILE_STREAM_ENCRYPTION_PAYLOAD_SIZE);
+		return FALSE;
+	}
+#else
+	/* Fake decryption (lowercase<->uppercase) for unit testing */
+	data[0] ^= 0x20;
+	data[1] ^= 0x20;
+	data[2] ^= 0x20;
+	g_assert(data[3] == '@');
+	(void) stream;  /* silence gcc */
+	(void) tag;
+	(void) faulty;
+	(void) i;
+#endif
+	return TRUE;
+}
+
+static void
 _vte_file_stream_append (VteStream *astream, const char *data, gsize len)
 {
 	VteFileStream *stream = (VteFileStream *) astream;
 
 	g_assert (len > 0);
 
-	stream->head += len;
 	while (len) {
-		gsize l = MIN(VTE_FILE_STREAM_BLOCKSIZE - stream->wbuf_len, len);
+		gsize l = MIN(VTE_FILE_STREAM_ENCRYPTION_PAYLOAD_SIZE - stream->wbuf_len, len);
 		memcpy(stream->wbuf + stream->wbuf_len, data, l);
 		stream->wbuf_len += l; data += l; len -= l;
-		if (stream->wbuf_len == VTE_FILE_STREAM_BLOCKSIZE) {
-			_vte_unbuffered_file_stream_append (astream, stream->wbuf, VTE_FILE_STREAM_BLOCKSIZE);
+		if (stream->wbuf_len == VTE_FILE_STREAM_ENCRYPTION_PAYLOAD_SIZE) {
+			_vte_file_stream_encrypt_and_append (astream, ALIGN(stream->head), stream->wbuf);
 			stream->wbuf_len = 0;
 		}
+		stream->head += l;
 	}
 }
 
@@ -653,11 +773,11 @@ _vte_file_stream_read (VteStream *astream, gsize offset, char *data, gsize len)
 		g_assert_not_reached();
 	}
 
-	while (len && offset < stream->parent.head) {
-		gsize l = MIN(VTE_FILE_STREAM_BLOCKSIZE - MOD(offset), len);
+	while (len && offset < ALIGN(stream->head)) {
+		gsize l = MIN(VTE_FILE_STREAM_ENCRYPTION_PAYLOAD_SIZE - MOD(offset), len);
 		gsize offset_aligned = ALIGN(offset);
 		if (offset_aligned != stream->rbuf_offset) {
-			if (!_vte_unbuffered_file_stream_read (astream, offset_aligned, stream->rbuf, VTE_FILE_STREAM_BLOCKSIZE))
+			if (!_vte_file_stream_read_and_decrypt (astream, offset_aligned, stream->rbuf))
 				return FALSE;
 			stream->rbuf_offset = offset_aligned;
 		}
@@ -679,7 +799,7 @@ _vte_file_stream_truncate (VteStream *astream, gsize offset)
 	g_assert (offset >= stream->tail);
 	g_assert (offset <= stream->head);
 
-	if (offset < stream->parent.head) {
+	if (offset < ALIGN(stream->head)) {
 		/* Truncating goes back to the part that we've written to the
 		 * file. For simplicity (since this is a very rare, user
 		 * initiated event - the window is made taller) go for the
@@ -687,11 +807,11 @@ _vte_file_stream_truncate (VteStream *astream, gsize offset)
 		 * the code intact, that is, read back the new partial last
 		 * block to the write cache. */
 		gsize offset_aligned = ALIGN(offset);
-		if (!_vte_unbuffered_file_stream_read (astream, offset_aligned, stream->wbuf, VTE_FILE_STREAM_BLOCKSIZE)) {
+		if (!_vte_file_stream_read_and_decrypt (astream, offset_aligned, stream->wbuf)) {
 			/* what now? */
-			memset(stream->wbuf, 0, VTE_FILE_STREAM_BLOCKSIZE);
+			memset(stream->wbuf, 0, VTE_FILE_STREAM_ENCRYPTION_PAYLOAD_SIZE);
 		}
-		_vte_unbuffered_file_stream_truncate (astream, offset_aligned);
+		_vte_unbuffered_file_stream_truncate (astream, TO_UNBUFFERED_OFFSET(offset));
 
 		if (stream->rbuf_offset >= offset_aligned) {
 			stream->rbuf_offset = 1;  /* Invalidate the read cache. */
@@ -709,12 +829,10 @@ _vte_file_stream_advance_tail (VteStream *astream, gsize offset)
 	g_assert (offset >= stream->tail);
 	g_assert (offset <= stream->head);
 
-	stream->tail = offset;
+	if (ALIGN(offset) > ALIGN(stream->tail))
+		_vte_unbuffered_file_stream_advance_tail (astream, TO_UNBUFFERED_OFFSET(offset));
 
-	offset = ALIGN(offset);
-	if (offset < stream->parent.segment[0].st_tail) return;
-
-	_vte_unbuffered_file_stream_advance_tail (astream, offset);
+	stream->tail = offset;
 }
 
 static gsize
@@ -1154,112 +1272,102 @@ test_buffer (void)
 	VteFileStream *s = (VteFileStream *) as;
 
 	/* Append */
-	_vte_stream_append (as, "abc", 3);
-	g_assert (s->head == 3);
+	_vte_stream_append (as, "ab", 2);
+	g_assert (s->head == 2);
 	g_assert (s->parent.head == 0);
 	g_assert (s->parent.file.fd == -1);
 
-	_vte_stream_append (as, "d", 1);
-	g_assert (s->head == 4);
+	_vte_stream_append (as, "c", 1);
+	g_assert (s->head == 3);
 	g_assert (s->parent.head == 4);
-	assert_file_contents (s->parent.file.fd, "abcd");
-	assert_stream_contents (as, "abcd");
+	assert_file_contents (s->parent.file.fd, "ABC@");
+	assert_stream_contents (as, "abc");
 
-	_vte_stream_append (as, "efghij", 6);
-	g_assert (s->head == 10);
+	_vte_stream_append (as, "defg", 4);
+	g_assert (s->head == 7);
 	g_assert (s->parent.head == 8);
-	assert_file_contents (s->parent.file.fd, "abcdefgh");
-	assert_stream_contents (as, "abcdefghij");
+	assert_file_contents (s->parent.file.fd, "ABC@DEF@");
+	assert_stream_contents (as, "abcdefg");
 
-	_vte_stream_append (as, "klm", 3);
-	g_assert (s->head == 13);
+	_vte_stream_append (as, "hijk", 4);
+	g_assert (s->head == 11);
 	g_assert (s->parent.head == 12);
-	assert_file_contents (s->parent.file.fd, "abcdefghijkl");
-	assert_stream_contents (as, "abcdefghijklm");
+	assert_file_contents (s->parent.file.fd, "ABC@DEF@GHI@");
+	assert_stream_contents (as, "abcdefghijk");
 
 	/* Truncate */
-	_vte_stream_truncate (as, 12);
-	g_assert (s->head == 12);
-	g_assert (s->parent.head == 12);
-	assert_file_contents (s->parent.file.fd, "abcdefghijkl");
-	assert_stream_contents (as, "abcdefghijkl");
-
 	_vte_stream_truncate (as, 9);
 	g_assert (s->head == 9);
-	g_assert (s->parent.head == 8);
-	assert_file_contents (s->parent.file.fd, "abcdefgh");
+	g_assert (s->parent.head == 12);
+	assert_file_contents (s->parent.file.fd, "ABC@DEF@GHI@");
 	assert_stream_contents (as, "abcdefghi");
 
+	_vte_stream_truncate (as, 8);
+	g_assert (s->head == 8);
+	g_assert (s->parent.head == 8);
+	assert_file_contents (s->parent.file.fd, "ABC@DEF@");
+	assert_stream_contents (as, "abcdefgh");
+
 	/* Test that the read cache is invalidated on truncate */
-	_vte_stream_read (as, 4, buf, 4);
-	g_assert (s->rbuf_offset == 4);
-	_vte_stream_truncate (as, 7);
+	_vte_stream_read (as, 3, buf, 3);
+	g_assert (s->rbuf_offset == 3);
+	_vte_stream_truncate (as, 5);
 	g_assert (s->rbuf_offset == 1);
 	_vte_stream_append (as, "mn", 2);
-	_vte_stream_read (as, 4, buf, 4);
-	g_assert (s->rbuf_offset == 4);
-	g_assert (strncmp (buf, "efgm", 4) == 0);
+	_vte_stream_read (as, 3, buf, 3);
+	g_assert (s->rbuf_offset == 3);
+	g_assert (strncmp (buf, "dem", 3) == 0);
 
 	/* Advance_tail */
-	_vte_stream_advance_tail (as, 1);
-	g_assert (s->tail == 1);
+	_vte_stream_advance_tail (as, 2);
+	g_assert (s->tail == 2);
 	g_assert (s->parent.tail == 0);
-	assert_file_contents (s->parent.file.fd, "abcdefgm");
-	assert_stream_contents (as, "bcdefgmn");
+	assert_file_contents (s->parent.file.fd, "ABC@DEM@");
+	assert_stream_contents (as, "cdemn");
 
 	_vte_stream_advance_tail (as, 3);
 	g_assert (s->tail == 3);
-	g_assert (s->parent.tail == 0);
-	assert_file_contents (s->parent.file.fd, "abcdefgm");
-	assert_stream_contents (as, "defgmn");
+	g_assert (s->parent.tail == 4);
+	assert_file_contents (s->parent.file.fd, "ABC@DEM@");
+	assert_stream_contents (as, "demn");
 
 	_vte_stream_advance_tail (as, 4);
 	g_assert (s->tail == 4);
 	g_assert (s->parent.tail == 4);
-	assert_file_contents (s->parent.file.fd, "abcdefgm");
-	assert_stream_contents (as, "efgmn");
+	assert_file_contents (s->parent.file.fd, "ABC@DEM@");
+	assert_stream_contents (as, "emn");
 
 	/* Tail and head within the same block (causes a reset in the underlying layer) */
-	_vte_stream_advance_tail (as, 8);
-	g_assert (s->tail == 8);
+	_vte_stream_advance_tail (as, 6);
+	g_assert (s->tail == 6);
 	g_assert (s->parent.tail == 8);
 	assert_stream_contents (as, "n");
 
-	_vte_stream_advance_tail (as, 9);
-	g_assert (s->tail == 9);
-	g_assert (s->parent.tail == 8);
-	assert_stream_contents (as, "");
-
-	_vte_stream_append (as, "op", 2);
-	g_assert (s->head == 11);
+	_vte_stream_append (as, "o", 1);
+	g_assert (s->head == 8);
 	g_assert (s->parent.head == 8);
-	assert_stream_contents (as, "op");
+	assert_stream_contents (as, "no");
 
-	_vte_stream_advance_tail (as, 10);
-	g_assert (s->tail == 10);
+	_vte_stream_advance_tail (as, 7);
+	g_assert (s->tail == 7);
 	g_assert (s->parent.tail == 8);
-	assert_stream_contents (as, "p");
-
-	_vte_stream_append (as, "q", 1);
-	g_assert (s->head == 12);
-	g_assert (s->parent.head == 12);
-	assert_stream_contents (as, "pq");
+	assert_stream_contents (as, "o");
 
-	_vte_stream_append (as, "rs", 2);
-	g_assert (s->head == 14);
+	_vte_stream_append (as, "p", 1);
+	g_assert (s->head == 9);
 	g_assert (s->parent.head == 12);
-	assert_file_contents_starts_with (s->parent.file.fd, "nopq");
-	assert_stream_contents (as, "pqrs");
+	assert_file_contents_starts_with (s->parent.file.fd, "NOP@");
+	assert_stream_contents (as, "op");
 
 	/* Reset */
-	_vte_stream_reset (as, 13);
-	_vte_stream_append (as, "tuvw", 4);
-	g_assert (s->tail == 13);
-	g_assert (s->parent.tail == 12);
-	g_assert (s->head == 17);
-	g_assert (s->parent.head == 16);
-	assert_file_contents_starts_with_n (s->parent.file.fd, "\0tuv", 4);
-	assert_stream_contents (as, "tuvw");
+	_vte_stream_reset (as, 100);
+	_vte_stream_append (as, "qrst", 4);
+	g_assert (s->tail == 100);
+	g_assert (s->parent.tail == 132);
+	g_assert (s->head == 104);
+	g_assert (s->parent.head == 136);
+	assert_file_contents_starts_with (s->parent.file.fd, " QR@");  /* Note: the space is the fake-encrypted '\0' */
+	assert_stream_contents (as, "qrst");
 
 	g_object_unref (as);
 }
