diff src/undo.c @ 6122:18ac55444b37 v7.4.399

updated for version 7.4.399 Problem: Encryption implementation is messy. Blowfish encryption has a weakness. Solution: Refactor the encryption, store the state in an allocated struct instead of using a save/restore mechanism. Introduce the "blowfish2" method, which does not have the weakness and encrypts the whole undo file. (largely by David Leadbeater)
author Bram Moolenaar <bram@vim.org>
date Sun, 10 Aug 2014 13:38:34 +0200
parents 410ef4f1a3d2
children 3aa37ad2c4ed
line wrap: on
line diff
--- a/src/undo.c
+++ b/src/undo.c
@@ -81,8 +81,25 @@
 #define UH_MAGIC 0x18dade	/* value for uh_magic when in use */
 #define UE_MAGIC 0xabc123	/* value for ue_magic when in use */
 
+/* Size of buffer used for encryption. */
+#define CRYPT_BUF_SIZE 8192
+
 #include "vim.h"
 
+/* Structure passed around between functions.
+ * Avoids passing cryptstate_T when encryption not available. */
+typedef struct {
+    buf_T	*bi_buf;
+    FILE	*bi_fp;
+#ifdef FEAT_CRYPT
+    cryptstate_T *bi_state;
+    char_u	*bi_buffer; /* CRYPT_BUF_SIZE, NULL when not buffering */
+    size_t	bi_used;    /* bytes written to/read from bi_buffer */
+    size_t	bi_avail;   /* bytes available in bi_buffer */
+#endif
+} bufinfo_T;
+
+
 static long get_undolevel __ARGS((void));
 static void u_unch_branch __ARGS((u_header_T *uhp));
 static u_entry_T *u_get_headentry __ARGS((void));
@@ -98,18 +115,26 @@ static void u_freeentry __ARGS((u_entry_
 #ifdef FEAT_PERSISTENT_UNDO
 static void corruption_error __ARGS((char *mesg, char_u *file_name));
 static void u_free_uhp __ARGS((u_header_T *uhp));
-static size_t fwrite_crypt __ARGS((buf_T *buf UNUSED, char_u *ptr, size_t len, FILE *fp));
-static char_u *read_string_decrypt __ARGS((buf_T *buf UNUSED, FILE *fd, int len));
-static int serialize_header __ARGS((FILE *fp, buf_T *buf, char_u *hash));
-static int serialize_uhp __ARGS((FILE *fp, buf_T *buf, u_header_T *uhp));
-static u_header_T *unserialize_uhp __ARGS((FILE *fp, char_u *file_name));
-static int serialize_uep __ARGS((FILE *fp, buf_T *buf, u_entry_T *uep));
-static u_entry_T *unserialize_uep __ARGS((FILE *fp, int *error, char_u *file_name));
-static void serialize_pos __ARGS((pos_T pos, FILE *fp));
-static void unserialize_pos __ARGS((pos_T *pos, FILE *fp));
-static void serialize_visualinfo __ARGS((visualinfo_T *info, FILE *fp));
-static void unserialize_visualinfo __ARGS((visualinfo_T *info, FILE *fp));
-static void put_header_ptr __ARGS((FILE	*fp, u_header_T *uhp));
+static int undo_write __ARGS((bufinfo_T *bi, char_u *ptr, size_t len));
+static int undo_flush __ARGS((bufinfo_T *bi));
+static int fwrite_crypt __ARGS((bufinfo_T *bi, char_u *ptr, size_t len));
+static int undo_write_bytes __ARGS((bufinfo_T *bi, long_u nr, int len));
+static void put_header_ptr __ARGS((bufinfo_T *bi, u_header_T *uhp));
+static int undo_read_4c __ARGS((bufinfo_T *bi));
+static int undo_read_2c __ARGS((bufinfo_T *bi));
+static int undo_read_byte __ARGS((bufinfo_T *bi));
+static time_t undo_read_time __ARGS((bufinfo_T *bi));
+static int undo_read __ARGS((bufinfo_T *bi, char_u *buffer, size_t size));
+static char_u *read_string_decrypt __ARGS((bufinfo_T *bi, int len));
+static int serialize_header __ARGS((bufinfo_T *bi, char_u *hash));
+static int serialize_uhp __ARGS((bufinfo_T *bi, u_header_T *uhp));
+static u_header_T *unserialize_uhp __ARGS((bufinfo_T *bi, char_u *file_name));
+static int serialize_uep __ARGS((bufinfo_T *bi, u_entry_T *uep));
+static u_entry_T *unserialize_uep __ARGS((bufinfo_T *bi, int *error, char_u *file_name));
+static void serialize_pos __ARGS((bufinfo_T *bi, pos_T pos));
+static void unserialize_pos __ARGS((bufinfo_T *bi, pos_T *pos));
+static void serialize_visualinfo __ARGS((bufinfo_T *bi, visualinfo_T *info));
+static void unserialize_visualinfo __ARGS((bufinfo_T *bi, visualinfo_T *info));
 #endif
 
 #define U_ALLOC_LINE(size) lalloc((long_u)(size), FALSE)
@@ -859,68 +884,294 @@ u_free_uhp(uhp)
 }
 
 /*
- * Like fwrite() but crypt the bytes when 'key' is set.
- * Returns 1 if successful.
+ * Write a sequence of bytes to the undo file.
+ * Buffers and encrypts as needed.
+ * Returns OK or FAIL.
  */
-    static size_t
-fwrite_crypt(buf, ptr, len, fp)
-    buf_T	*buf UNUSED;
+    static int
+undo_write(bi, ptr, len)
+    bufinfo_T	*bi;
     char_u	*ptr;
     size_t	len;
-    FILE	*fp;
+{
+#ifdef FEAT_CRYPT
+    if (bi->bi_buffer != NULL)
+    {
+	size_t	len_todo = len;
+	char_u  *p = ptr;
+
+	while (bi->bi_used + len_todo >= CRYPT_BUF_SIZE)
+	{
+	    size_t	n = CRYPT_BUF_SIZE - bi->bi_used;
+
+	    mch_memmove(bi->bi_buffer + bi->bi_used, p, n);
+	    len_todo -= n;
+	    p += n;
+	    bi->bi_used = CRYPT_BUF_SIZE;
+	    if (undo_flush(bi) == FAIL)
+		return FAIL;
+	}
+	if (len_todo > 0)
+	{
+	    mch_memmove(bi->bi_buffer + bi->bi_used, p, len_todo);
+	    bi->bi_used += len_todo;
+	}
+	return OK;
+    }
+#endif
+    if (fwrite(ptr, len, (size_t)1, bi->bi_fp) != 1)
+	return FAIL;
+    return OK;
+}
+
+#ifdef FEAT_CRYPT
+    static int
+undo_flush(bi)
+    bufinfo_T	*bi;
+{
+    if (bi->bi_used > 0)
+    {
+	crypt_encode_inplace(bi->bi_state, bi->bi_buffer, bi->bi_used);
+	if (fwrite(bi->bi_buffer, bi->bi_used, (size_t)1, bi->bi_fp) != 1)
+	    return FAIL;
+	bi->bi_used = 0;
+    }
+    return OK;
+}
+#endif
+
+/*
+ * Write "ptr[len]" and crypt the bytes when needed.
+ * Returns OK or FAIL.
+ */
+    static int
+fwrite_crypt(bi, ptr, len)
+    bufinfo_T	*bi;
+    char_u	*ptr;
+    size_t	len;
 {
 #ifdef FEAT_CRYPT
     char_u  *copy;
     char_u  small_buf[100];
     size_t  i;
 
-    if (*buf->b_p_key == NUL)
-	return fwrite(ptr, len, (size_t)1, fp);
-    if (len < 100)
-	copy = small_buf;  /* no malloc()/free() for short strings */
-    else
+    if (bi->bi_state != NULL && bi->bi_buffer == NULL)
     {
-	copy = lalloc(len, FALSE);
-	if (copy == NULL)
-	    return 0;
+	/* crypting every piece of text separately */
+	if (len < 100)
+	    copy = small_buf;  /* no malloc()/free() for short strings */
+	else
+	{
+	    copy = lalloc(len, FALSE);
+	    if (copy == NULL)
+		return 0;
+	}
+	crypt_encode(bi->bi_state, ptr, len, copy);
+	i = fwrite(copy, len, (size_t)1, bi->bi_fp);
+	if (copy != small_buf)
+	    vim_free(copy);
+	return i == 1 ? OK : FAIL;
     }
-    crypt_encode(ptr, len, copy);
-    i = fwrite(copy, len, (size_t)1, fp);
-    if (copy != small_buf)
-	vim_free(copy);
-    return i;
-#else
-    return fwrite(ptr, len, (size_t)1, fp);
 #endif
+    return undo_write(bi, ptr, len);
+}
+
+/*
+ * Write a number, MSB first, in "len" bytes.
+ * Must match with undo_read_?c() functions.
+ * Returns OK or FAIL.
+ */
+    static int
+undo_write_bytes(bi, nr, len)
+    bufinfo_T *bi;
+    long_u  nr;
+    int	    len;
+{
+    char_u  buf[8];
+    int	    i;
+    int	    bufi = 0;
+
+    for (i = len - 1; i >= 0; --i)
+	buf[bufi++] = nr >> (i * 8);
+    return undo_write(bi, buf, (size_t)len);
 }
 
 /*
- * Read a string of length "len" from "fd".
- * When 'key' is set decrypt the bytes.
- */
-    static char_u *
-read_string_decrypt(buf, fd, len)
-    buf_T   *buf UNUSED;
-    FILE    *fd;
-    int	    len;
+ * Write the pointer to an undo header.  Instead of writing the pointer itself
+ * we use the sequence number of the header.  This is converted back to
+ * pointers when reading. */
+    static void
+put_header_ptr(bi, uhp)
+    bufinfo_T	*bi;
+    u_header_T	*uhp;
+{
+    undo_write_bytes(bi, (long_u)(uhp != NULL ? uhp->uh_seq : 0), 4);
+}
+
+    static int
+undo_read_4c(bi)
+    bufinfo_T	*bi;
 {
-    char_u  *ptr;
-
-    ptr = read_string(fd, len);
 #ifdef FEAT_CRYPT
-    if (ptr != NULL && *buf->b_p_key != NUL)
-	crypt_decode(ptr, len);
+    if (bi->bi_buffer != NULL)
+    {
+	char_u  buf[4];
+	int	n;
+
+	undo_read(bi, buf, (size_t)4);
+	n = (buf[0] << 24) + (buf[1] << 16) + (buf[2] << 8) + buf[3];
+	return n;
+    }
 #endif
-    return ptr;
+    return get4c(bi->bi_fp);
+}
+
+    static int
+undo_read_2c(bi)
+    bufinfo_T	*bi;
+{
+#ifdef FEAT_CRYPT
+    if (bi->bi_buffer != NULL)
+    {
+	char_u  buf[2];
+	int	n;
+
+	undo_read(bi, buf, (size_t)2);
+	n = (buf[0] << 8) + buf[1];
+	return n;
+    }
+#endif
+    return get2c(bi->bi_fp);
 }
 
     static int
-serialize_header(fp, buf, hash)
-    FILE	*fp;
-    buf_T	*buf;
+undo_read_byte(bi)
+    bufinfo_T	*bi;
+{
+#ifdef FEAT_CRYPT
+    if (bi->bi_buffer != NULL)
+    {
+	char_u  buf[1];
+
+	undo_read(bi, buf, (size_t)1);
+	return buf[0];
+    }
+#endif
+    return getc(bi->bi_fp);
+}
+
+    static time_t
+undo_read_time(bi)
+    bufinfo_T	*bi;
+{
+#ifdef FEAT_CRYPT
+    if (bi->bi_buffer != NULL)
+    {
+	char_u  buf[8];
+	time_t	n = 0;
+	int	i;
+
+	undo_read(bi, buf, (size_t)8);
+	for (i = 0; i < 8; ++i)
+	    n = (n << 8) + buf[i];
+	return n;
+    }
+#endif
+    return get8ctime(bi->bi_fp);
+}
+
+/*
+ * Read "buffer[size]" from the undo file.
+ * Return OK or FAIL.
+ */
+    static int
+undo_read(bi, buffer, size)
+    bufinfo_T   *bi;
+    char_u	*buffer;
+    size_t	size;
+{
+#ifdef FEAT_CRYPT
+    if (bi->bi_buffer != NULL)
+    {
+	int	size_todo = size;
+	char_u	*p = buffer;
+
+	while (size_todo > 0)
+	{
+	    size_t n;
+
+	    if (bi->bi_used >= bi->bi_avail)
+	    {
+		n = fread(bi->bi_buffer, 1, (size_t)CRYPT_BUF_SIZE, bi->bi_fp);
+		if (n <= 0)
+		{
+		    /* Error may be checked for only later.  Fill with zeros,
+		     * so that the reader won't use garbage. */
+		    vim_memset(p, 0, size_todo);
+		    return FAIL;
+		}
+		bi->bi_avail = n;
+		bi->bi_used = 0;
+		crypt_decode_inplace(bi->bi_state, bi->bi_buffer, bi->bi_avail);
+	    }
+	    n = size_todo;
+	    if (n > bi->bi_avail - bi->bi_used)
+		n = bi->bi_avail - bi->bi_used;
+	    mch_memmove(p, bi->bi_buffer + bi->bi_used, n);
+	    bi->bi_used += n;
+	    size_todo -= n;
+	    p += n;
+	}
+	return OK;
+    }
+#endif
+    if (fread(buffer, (size_t)size, 1, bi->bi_fp) != 1)
+	return FAIL;
+    return OK;
+}
+
+/*
+ * Read a string of length "len" from "bi->bi_fd".
+ * "len" can be zero to allocate an empty line.
+ * Decrypt the bytes if needed.
+ * Append a NUL.
+ * Returns a pointer to allocated memory or NULL for failure.
+ */
+    static char_u *
+read_string_decrypt(bi, len)
+    bufinfo_T	*bi;
+    int		len;
+{
+    char_u  *ptr = alloc((unsigned)len + 1);
+
+    if (ptr != NULL)
+    {
+	if (len > 0 && undo_read(bi, ptr, len) == FAIL)
+	{
+	    vim_free(ptr);
+	    return NULL;
+	}
+	ptr[len] = NUL;
+#ifdef FEAT_CRYPT
+	if (bi->bi_state != NULL && bi->bi_buffer == NULL)
+	    crypt_decode_inplace(bi->bi_state, ptr, len);
+#endif
+    }
+    return ptr;
+}
+
+/*
+ * Writes the (not encrypted) header and initializes encryption if needed.
+ */
+    static int
+serialize_header(bi, hash)
+    bufinfo_T	*bi;
     char_u	*hash;
 {
-    int len;
+    int		len;
+    buf_T	*buf = bi->bi_buf;
+    FILE	*fp = bi->bi_fp;
+    char_u	time_buf[8];
 
     /* Start writing, first the magic marker and undo info version. */
     if (fwrite(UF_START_MAGIC, (size_t)UF_START_MAGIC_LEN, (size_t)1, fp) != 1)
@@ -934,108 +1185,124 @@ serialize_header(fp, buf, hash)
 	char_u *header;
 	int    header_len;
 
-	put_bytes(fp, (long_u)UF_VERSION_CRYPT, 2);
-	header = prepare_crypt_write(buf, &header_len);
-	if (header == NULL)
+	undo_write_bytes(bi, (long_u)UF_VERSION_CRYPT, 2);
+	bi->bi_state = crypt_create_for_writing(crypt_get_method_nr(buf),
+					  buf->b_p_key, &header, &header_len);
+	if (bi->bi_state == NULL)
 	    return FAIL;
 	len = (int)fwrite(header, (size_t)header_len, (size_t)1, fp);
 	vim_free(header);
 	if (len != 1)
 	{
-	    crypt_pop_state();
+	    crypt_free_state(bi->bi_state);
+	    bi->bi_state = NULL;
 	    return FAIL;
 	}
+
+	if (crypt_whole_undofile(crypt_get_method_nr(buf)))
+	{
+	    bi->bi_buffer = alloc(CRYPT_BUF_SIZE);
+	    if (bi->bi_buffer == NULL)
+	    {
+		crypt_free_state(bi->bi_state);
+		bi->bi_state = NULL;
+		return FAIL;
+	    }
+	    bi->bi_used = 0;
+	}
     }
     else
 #endif
-	put_bytes(fp, (long_u)UF_VERSION, 2);
+	undo_write_bytes(bi, (long_u)UF_VERSION, 2);
 
 
     /* Write a hash of the buffer text, so that we can verify it is still the
      * same when reading the buffer text. */
-    if (fwrite(hash, (size_t)UNDO_HASH_SIZE, (size_t)1, fp) != 1)
+    if (undo_write(bi, hash, (size_t)UNDO_HASH_SIZE) == FAIL)
 	return FAIL;
 
     /* buffer-specific data */
-    put_bytes(fp, (long_u)buf->b_ml.ml_line_count, 4);
+    undo_write_bytes(bi, (long_u)buf->b_ml.ml_line_count, 4);
     len = buf->b_u_line_ptr != NULL ? (int)STRLEN(buf->b_u_line_ptr) : 0;
-    put_bytes(fp, (long_u)len, 4);
-    if (len > 0 && fwrite_crypt(buf, buf->b_u_line_ptr, (size_t)len, fp) != 1)
+    undo_write_bytes(bi, (long_u)len, 4);
+    if (len > 0 && fwrite_crypt(bi, buf->b_u_line_ptr, (size_t)len) == FAIL)
 	return FAIL;
-    put_bytes(fp, (long_u)buf->b_u_line_lnum, 4);
-    put_bytes(fp, (long_u)buf->b_u_line_colnr, 4);
+    undo_write_bytes(bi, (long_u)buf->b_u_line_lnum, 4);
+    undo_write_bytes(bi, (long_u)buf->b_u_line_colnr, 4);
 
     /* Undo structures header data */
-    put_header_ptr(fp, buf->b_u_oldhead);
-    put_header_ptr(fp, buf->b_u_newhead);
-    put_header_ptr(fp, buf->b_u_curhead);
-
-    put_bytes(fp, (long_u)buf->b_u_numhead, 4);
-    put_bytes(fp, (long_u)buf->b_u_seq_last, 4);
-    put_bytes(fp, (long_u)buf->b_u_seq_cur, 4);
-    put_time(fp, buf->b_u_time_cur);
+    put_header_ptr(bi, buf->b_u_oldhead);
+    put_header_ptr(bi, buf->b_u_newhead);
+    put_header_ptr(bi, buf->b_u_curhead);
+
+    undo_write_bytes(bi, (long_u)buf->b_u_numhead, 4);
+    undo_write_bytes(bi, (long_u)buf->b_u_seq_last, 4);
+    undo_write_bytes(bi, (long_u)buf->b_u_seq_cur, 4);
+    time_to_bytes(buf->b_u_time_cur, time_buf);
+    undo_write(bi, time_buf, 8);
 
     /* Optional fields. */
-    putc(4, fp);
-    putc(UF_LAST_SAVE_NR, fp);
-    put_bytes(fp, (long_u)buf->b_u_save_nr_last, 4);
-
-    putc(0, fp);  /* end marker */
+    undo_write_bytes(bi, 4, 1);
+    undo_write_bytes(bi, UF_LAST_SAVE_NR, 1);
+    undo_write_bytes(bi, (long_u)buf->b_u_save_nr_last, 4);
+
+    undo_write_bytes(bi, 0, 1);  /* end marker */
 
     return OK;
 }
 
     static int
-serialize_uhp(fp, buf, uhp)
-    FILE	*fp;
-    buf_T	*buf;
+serialize_uhp(bi, uhp)
+    bufinfo_T	*bi;
     u_header_T	*uhp;
 {
     int		i;
     u_entry_T	*uep;
-
-    if (put_bytes(fp, (long_u)UF_HEADER_MAGIC, 2) == FAIL)
+    char_u	time_buf[8];
+
+    if (undo_write_bytes(bi, (long_u)UF_HEADER_MAGIC, 2) == FAIL)
 	return FAIL;
 
-    put_header_ptr(fp, uhp->uh_next.ptr);
-    put_header_ptr(fp, uhp->uh_prev.ptr);
-    put_header_ptr(fp, uhp->uh_alt_next.ptr);
-    put_header_ptr(fp, uhp->uh_alt_prev.ptr);
-    put_bytes(fp, uhp->uh_seq, 4);
-    serialize_pos(uhp->uh_cursor, fp);
+    put_header_ptr(bi, uhp->uh_next.ptr);
+    put_header_ptr(bi, uhp->uh_prev.ptr);
+    put_header_ptr(bi, uhp->uh_alt_next.ptr);
+    put_header_ptr(bi, uhp->uh_alt_prev.ptr);
+    undo_write_bytes(bi, uhp->uh_seq, 4);
+    serialize_pos(bi, uhp->uh_cursor);
 #ifdef FEAT_VIRTUALEDIT
-    put_bytes(fp, (long_u)uhp->uh_cursor_vcol, 4);
+    undo_write_bytes(bi, (long_u)uhp->uh_cursor_vcol, 4);
 #else
-    put_bytes(fp, (long_u)0, 4);
+    undo_write_bytes(bi, (long_u)0, 4);
 #endif
-    put_bytes(fp, (long_u)uhp->uh_flags, 2);
+    undo_write_bytes(bi, (long_u)uhp->uh_flags, 2);
     /* Assume NMARKS will stay the same. */
     for (i = 0; i < NMARKS; ++i)
-	serialize_pos(uhp->uh_namedm[i], fp);
-    serialize_visualinfo(&uhp->uh_visual, fp);
-    put_time(fp, uhp->uh_time);
+	serialize_pos(bi, uhp->uh_namedm[i]);
+    serialize_visualinfo(bi, &uhp->uh_visual);
+    time_to_bytes(uhp->uh_time, time_buf);
+    undo_write(bi, time_buf, 8);
 
     /* Optional fields. */
-    putc(4, fp);
-    putc(UHP_SAVE_NR, fp);
-    put_bytes(fp, (long_u)uhp->uh_save_nr, 4);
-
-    putc(0, fp);  /* end marker */
+    undo_write_bytes(bi, 4, 1);
+    undo_write_bytes(bi, UHP_SAVE_NR, 1);
+    undo_write_bytes(bi, (long_u)uhp->uh_save_nr, 4);
+
+    undo_write_bytes(bi, 0, 1);  /* end marker */
 
     /* Write all the entries. */
     for (uep = uhp->uh_entry; uep != NULL; uep = uep->ue_next)
     {
-	put_bytes(fp, (long_u)UF_ENTRY_MAGIC, 2);
-	if (serialize_uep(fp, buf, uep) == FAIL)
+	undo_write_bytes(bi, (long_u)UF_ENTRY_MAGIC, 2);
+	if (serialize_uep(bi, uep) == FAIL)
 	    return FAIL;
     }
-    put_bytes(fp, (long_u)UF_ENTRY_END_MAGIC, 2);
+    undo_write_bytes(bi, (long_u)UF_ENTRY_END_MAGIC, 2);
     return OK;
 }
 
     static u_header_T *
-unserialize_uhp(fp, file_name)
-    FILE	*fp;
+unserialize_uhp(bi, file_name)
+    bufinfo_T	*bi;
     char_u	*file_name;
 {
     u_header_T	*uhp;
@@ -1051,56 +1318,56 @@ unserialize_uhp(fp, file_name)
 #ifdef U_DEBUG
     uhp->uh_magic = UH_MAGIC;
 #endif
-    uhp->uh_next.seq = get4c(fp);
-    uhp->uh_prev.seq = get4c(fp);
-    uhp->uh_alt_next.seq = get4c(fp);
-    uhp->uh_alt_prev.seq = get4c(fp);
-    uhp->uh_seq = get4c(fp);
+    uhp->uh_next.seq = undo_read_4c(bi);
+    uhp->uh_prev.seq = undo_read_4c(bi);
+    uhp->uh_alt_next.seq = undo_read_4c(bi);
+    uhp->uh_alt_prev.seq = undo_read_4c(bi);
+    uhp->uh_seq = undo_read_4c(bi);
     if (uhp->uh_seq <= 0)
     {
 	corruption_error("uh_seq", file_name);
 	vim_free(uhp);
 	return NULL;
     }
-    unserialize_pos(&uhp->uh_cursor, fp);
+    unserialize_pos(bi, &uhp->uh_cursor);
 #ifdef FEAT_VIRTUALEDIT
-    uhp->uh_cursor_vcol = get4c(fp);
+    uhp->uh_cursor_vcol = undo_read_4c(bi);
 #else
-    (void)get4c(fp);
+    (void)undo_read_4c(bi);
 #endif
-    uhp->uh_flags = get2c(fp);
+    uhp->uh_flags = undo_read_2c(bi);
     for (i = 0; i < NMARKS; ++i)
-	unserialize_pos(&uhp->uh_namedm[i], fp);
-    unserialize_visualinfo(&uhp->uh_visual, fp);
-    uhp->uh_time = get8ctime(fp);
+	unserialize_pos(bi, &uhp->uh_namedm[i]);
+    unserialize_visualinfo(bi, &uhp->uh_visual);
+    uhp->uh_time = undo_read_time(bi);
 
     /* Optional fields. */
     for (;;)
     {
-	int len = getc(fp);
+	int len = undo_read_byte(bi);
 	int what;
 
 	if (len == 0)
 	    break;
-	what = getc(fp);
+	what = undo_read_byte(bi);
 	switch (what)
 	{
 	    case UHP_SAVE_NR:
-		uhp->uh_save_nr = get4c(fp);
+		uhp->uh_save_nr = undo_read_4c(bi);
 		break;
 	    default:
 		/* field not supported, skip */
 		while (--len >= 0)
-		    (void)getc(fp);
+		    (void)undo_read_byte(bi);
 	}
     }
 
     /* Unserialize the uep list. */
     last_uep = NULL;
-    while ((c = get2c(fp)) == UF_ENTRY_MAGIC)
+    while ((c = undo_read_2c(bi)) == UF_ENTRY_MAGIC)
     {
 	error = FALSE;
-	uep = unserialize_uep(fp, &error, file_name);
+	uep = unserialize_uep(bi, &error, file_name);
 	if (last_uep == NULL)
 	    uhp->uh_entry = uep;
 	else
@@ -1123,35 +1390,34 @@ unserialize_uhp(fp, file_name)
 }
 
 /*
- * Serialize "uep" to "fp".
+ * Serialize "uep".
  */
     static int
-serialize_uep(fp, buf, uep)
-    FILE	*fp;
-    buf_T	*buf;
+serialize_uep(bi, uep)
+    bufinfo_T	*bi;
     u_entry_T	*uep;
 {
     int		i;
     size_t	len;
 
-    put_bytes(fp, (long_u)uep->ue_top, 4);
-    put_bytes(fp, (long_u)uep->ue_bot, 4);
-    put_bytes(fp, (long_u)uep->ue_lcount, 4);
-    put_bytes(fp, (long_u)uep->ue_size, 4);
+    undo_write_bytes(bi, (long_u)uep->ue_top, 4);
+    undo_write_bytes(bi, (long_u)uep->ue_bot, 4);
+    undo_write_bytes(bi, (long_u)uep->ue_lcount, 4);
+    undo_write_bytes(bi, (long_u)uep->ue_size, 4);
     for (i = 0; i < uep->ue_size; ++i)
     {
 	len = STRLEN(uep->ue_array[i]);
-	if (put_bytes(fp, (long_u)len, 4) == FAIL)
+	if (undo_write_bytes(bi, (long_u)len, 4) == FAIL)
 	    return FAIL;
-	if (len > 0 && fwrite_crypt(buf, uep->ue_array[i], len, fp) != 1)
+	if (len > 0 && fwrite_crypt(bi, uep->ue_array[i], len) == FAIL)
 	    return FAIL;
     }
     return OK;
 }
 
     static u_entry_T *
-unserialize_uep(fp, error, file_name)
-    FILE	*fp;
+unserialize_uep(bi, error, file_name)
+    bufinfo_T	*bi;
     int		*error;
     char_u	*file_name;
 {
@@ -1168,10 +1434,10 @@ unserialize_uep(fp, error, file_name)
 #ifdef U_DEBUG
     uep->ue_magic = UE_MAGIC;
 #endif
-    uep->ue_top = get4c(fp);
-    uep->ue_bot = get4c(fp);
-    uep->ue_lcount = get4c(fp);
-    uep->ue_size = get4c(fp);
+    uep->ue_top = undo_read_4c(bi);
+    uep->ue_bot = undo_read_4c(bi);
+    uep->ue_lcount = undo_read_4c(bi);
+    uep->ue_size = undo_read_4c(bi);
     if (uep->ue_size > 0)
     {
 	array = (char_u **)U_ALLOC_LINE(sizeof(char_u *) * uep->ue_size);
@@ -1188,9 +1454,9 @@ unserialize_uep(fp, error, file_name)
 
     for (i = 0; i < uep->ue_size; ++i)
     {
-	line_len = get4c(fp);
+	line_len = undo_read_4c(bi);
 	if (line_len >= 0)
-	    line = read_string_decrypt(curbuf, fp, line_len);
+	    line = read_string_decrypt(bi, line_len);
 	else
 	{
 	    line = NULL;
@@ -1207,83 +1473,71 @@ unserialize_uep(fp, error, file_name)
 }
 
 /*
- * Serialize "pos" to "fp".
+ * Serialize "pos".
  */
     static void
-serialize_pos(pos, fp)
+serialize_pos(bi, pos)
+    bufinfo_T *bi;
     pos_T pos;
-    FILE  *fp;
 {
-    put_bytes(fp, (long_u)pos.lnum, 4);
-    put_bytes(fp, (long_u)pos.col, 4);
+    undo_write_bytes(bi, (long_u)pos.lnum, 4);
+    undo_write_bytes(bi, (long_u)pos.col, 4);
 #ifdef FEAT_VIRTUALEDIT
-    put_bytes(fp, (long_u)pos.coladd, 4);
+    undo_write_bytes(bi, (long_u)pos.coladd, 4);
 #else
-    put_bytes(fp, (long_u)0, 4);
+    undo_write_bytes(bi, (long_u)0, 4);
 #endif
 }
 
 /*
- * Unserialize the pos_T at the current position in fp.
+ * Unserialize the pos_T at the current position.
  */
     static void
-unserialize_pos(pos, fp)
+unserialize_pos(bi, pos)
+    bufinfo_T *bi;
     pos_T *pos;
-    FILE  *fp;
 {
-    pos->lnum = get4c(fp);
+    pos->lnum = undo_read_4c(bi);
     if (pos->lnum < 0)
 	pos->lnum = 0;
-    pos->col = get4c(fp);
+    pos->col = undo_read_4c(bi);
     if (pos->col < 0)
 	pos->col = 0;
 #ifdef FEAT_VIRTUALEDIT
-    pos->coladd = get4c(fp);
+    pos->coladd = undo_read_4c(bi);
     if (pos->coladd < 0)
 	pos->coladd = 0;
 #else
-    (void)get4c(fp);
+    (void)undo_read_4c(bi);
 #endif
 }
 
 /*
- * Serialize "info" to "fp".
+ * Serialize "info".
  */
     static void
-serialize_visualinfo(info, fp)
+serialize_visualinfo(bi, info)
+    bufinfo_T	    *bi;
     visualinfo_T    *info;
-    FILE	    *fp;
 {
-    serialize_pos(info->vi_start, fp);
-    serialize_pos(info->vi_end, fp);
-    put_bytes(fp, (long_u)info->vi_mode, 4);
-    put_bytes(fp, (long_u)info->vi_curswant, 4);
+    serialize_pos(bi, info->vi_start);
+    serialize_pos(bi, info->vi_end);
+    undo_write_bytes(bi, (long_u)info->vi_mode, 4);
+    undo_write_bytes(bi, (long_u)info->vi_curswant, 4);
 }
 
 /*
- * Unserialize the visualinfo_T at the current position in fp.
+ * Unserialize the visualinfo_T at the current position.
  */
     static void
-unserialize_visualinfo(info, fp)
+unserialize_visualinfo(bi, info)
+    bufinfo_T	    *bi;
     visualinfo_T    *info;
-    FILE	    *fp;
 {
-    unserialize_pos(&info->vi_start, fp);
-    unserialize_pos(&info->vi_end, fp);
-    info->vi_mode = get4c(fp);
-    info->vi_curswant = get4c(fp);
-}
-
-/*
- * Write the pointer to an undo header.  Instead of writing the pointer itself
- * we use the sequence number of the header.  This is converted back to
- * pointers when reading. */
-    static void
-put_header_ptr(fp, uhp)
-    FILE	*fp;
-    u_header_T	*uhp;
-{
-    put_bytes(fp, (long_u)(uhp != NULL ? uhp->uh_seq : 0), 4);
+    unserialize_pos(bi, &info->vi_start);
+    unserialize_pos(bi, &info->vi_end);
+    info->vi_mode = undo_read_4c(bi);
+    info->vi_curswant = undo_read_4c(bi);
 }
 
 /*
@@ -1317,8 +1571,11 @@ u_write_undo(name, forceit, buf, hash)
     struct stat	st_old;
     struct stat	st_new;
 #endif
+    bufinfo_T	bi;
+
 #ifdef FEAT_CRYPT
-    int		do_crypt = FALSE;
+    bi.bi_state = NULL;
+    bi.bi_buffer = NULL;
 #endif
 
     if (name == NULL)
@@ -1474,14 +1731,12 @@ u_write_undo(name, forceit, buf, hash)
     u_sync(TRUE);
 
     /*
-     * Write the header.
+     * Write the header.  Initializes encryption, if enabled.
      */
-    if (serialize_header(fp, buf, hash) == FAIL)
+    bi.bi_buf = buf;
+    bi.bi_fp = fp;
+    if (serialize_header(&bi, hash) == FAIL)
 	goto write_error;
-#ifdef FEAT_CRYPT
-    if (*buf->b_p_key != NUL)
-	do_crypt = TRUE;
-#endif
 
     /*
      * Iteratively serialize UHPs and their UEPs from the top down.
@@ -1497,7 +1752,7 @@ u_write_undo(name, forceit, buf, hash)
 #ifdef U_DEBUG
 	    ++headers_written;
 #endif
-	    if (serialize_uhp(fp, buf, uhp) == FAIL)
+	    if (serialize_uhp(&bi, uhp) == FAIL)
 		goto write_error;
 	}
 
@@ -1516,7 +1771,7 @@ u_write_undo(name, forceit, buf, hash)
 	    uhp = uhp->uh_next.ptr;
     }
 
-    if (put_bytes(fp, (long_u)UF_HEADER_END_MAGIC, 2) == OK)
+    if (undo_write_bytes(&bi, (long_u)UF_HEADER_END_MAGIC, 2) == OK)
 	write_ok = TRUE;
 #ifdef U_DEBUG
     if (headers_written != buf->b_u_numhead)
@@ -1526,6 +1781,11 @@ u_write_undo(name, forceit, buf, hash)
     }
 #endif
 
+#ifdef FEAT_CRYPT
+    if (bi.bi_state != NULL && undo_flush(&bi) == FAIL)
+	write_ok = FALSE;
+#endif
+
 write_error:
     fclose(fp);
     if (!write_ok)
@@ -1551,8 +1811,9 @@ write_error:
 
 theend:
 #ifdef FEAT_CRYPT
-    if (do_crypt)
-	crypt_pop_state();
+    if (bi.bi_state != NULL)
+	crypt_free_state(bi.bi_state);
+    vim_free(bi.bi_buffer);
 #endif
     if (file_name != name)
 	vim_free(file_name);
@@ -1598,9 +1859,7 @@ u_read_undo(name, hash, orig_name)
     struct stat	st_orig;
     struct stat	st_undo;
 #endif
-#ifdef FEAT_CRYPT
-    int		do_decrypt = FALSE;
-#endif
+    bufinfo_T	bi;
 
     if (name == NULL)
     {
@@ -1644,6 +1903,12 @@ u_read_undo(name, hash, orig_name)
 	    EMSG2(_("E822: Cannot open undo file for reading: %s"), file_name);
 	goto error;
     }
+    bi.bi_buf = curbuf;
+    bi.bi_fp = fp;
+#ifdef FEAT_CRYPT
+    bi.bi_state = NULL;
+    bi.bi_buffer = NULL;
+#endif
 
     /*
      * Read the undo file header.
@@ -1664,12 +1929,24 @@ u_read_undo(name, hash, orig_name)
 								   file_name);
 	    goto error;
 	}
-	if (prepare_crypt_read(fp) == FAIL)
+	bi.bi_state = crypt_create_from_file(fp, curbuf->b_p_key);
+	if (bi.bi_state == NULL)
 	{
 	    EMSG2(_("E826: Undo file decryption failed: %s"), file_name);
 	    goto error;
 	}
-	do_decrypt = TRUE;
+	if (crypt_whole_undofile(bi.bi_state->method_nr))
+	{
+	    bi.bi_buffer = alloc(CRYPT_BUF_SIZE);
+	    if (bi.bi_buffer == NULL)
+	    {
+		crypt_free_state(bi.bi_state);
+		bi.bi_state = NULL;
+		goto error;
+	    }
+	    bi.bi_avail = 0;
+	    bi.bi_used = 0;
+	}
 #else
 	EMSG2(_("E827: Undo file is encrypted: %s"), file_name);
 	goto error;
@@ -1681,12 +1958,12 @@ u_read_undo(name, hash, orig_name)
 	goto error;
     }
 
-    if (fread(read_hash, UNDO_HASH_SIZE, 1, fp) != 1)
+    if (undo_read(&bi, read_hash, (size_t)UNDO_HASH_SIZE) == FAIL)
     {
 	corruption_error("hash", file_name);
 	goto error;
     }
-    line_count = (linenr_T)get4c(fp);
+    line_count = (linenr_T)undo_read_4c(&bi);
     if (memcmp(hash, read_hash, UNDO_HASH_SIZE) != 0
 				  || line_count != curbuf->b_ml.ml_line_count)
     {
@@ -1703,13 +1980,13 @@ u_read_undo(name, hash, orig_name)
     }
 
     /* Read undo data for "U" command. */
-    str_len = get4c(fp);
+    str_len = undo_read_4c(&bi);
     if (str_len < 0)
 	goto error;
     if (str_len > 0)
-	line_ptr = read_string_decrypt(curbuf, fp, str_len);
-    line_lnum = (linenr_T)get4c(fp);
-    line_colnr = (colnr_T)get4c(fp);
+	line_ptr = read_string_decrypt(&bi, str_len);
+    line_lnum = (linenr_T)undo_read_4c(&bi);
+    line_colnr = (colnr_T)undo_read_4c(&bi);
     if (line_lnum < 0 || line_colnr < 0)
     {
 	corruption_error("line lnum/col", file_name);
@@ -1717,32 +1994,32 @@ u_read_undo(name, hash, orig_name)
     }
 
     /* Begin general undo data */
-    old_header_seq = get4c(fp);
-    new_header_seq = get4c(fp);
-    cur_header_seq = get4c(fp);
-    num_head = get4c(fp);
-    seq_last = get4c(fp);
-    seq_cur = get4c(fp);
-    seq_time = get8ctime(fp);
+    old_header_seq = undo_read_4c(&bi);
+    new_header_seq = undo_read_4c(&bi);
+    cur_header_seq = undo_read_4c(&bi);
+    num_head = undo_read_4c(&bi);
+    seq_last = undo_read_4c(&bi);
+    seq_cur = undo_read_4c(&bi);
+    seq_time = undo_read_time(&bi);
 
     /* Optional header fields. */
     for (;;)
     {
-	int len = getc(fp);
+	int len = undo_read_byte(&bi);
 	int what;
 
 	if (len == 0 || len == EOF)
 	    break;
-	what = getc(fp);
+	what = undo_read_byte(&bi);
 	switch (what)
 	{
 	    case UF_LAST_SAVE_NR:
-		last_save_nr = get4c(fp);
+		last_save_nr = undo_read_4c(&bi);
 		break;
 	    default:
 		/* field not supported, skip */
 		while (--len >= 0)
-		    (void)getc(fp);
+		    (void)undo_read_byte(&bi);
 	}
     }
 
@@ -1758,7 +2035,7 @@ u_read_undo(name, hash, orig_name)
 	    goto error;
     }
 
-    while ((c = get2c(fp)) == UF_HEADER_MAGIC)
+    while ((c = undo_read_2c(&bi)) == UF_HEADER_MAGIC)
     {
 	if (num_read_uhps >= num_head)
 	{
@@ -1766,7 +2043,7 @@ u_read_undo(name, hash, orig_name)
 	    goto error;
 	}
 
-	uhp = unserialize_uhp(fp, file_name);
+	uhp = unserialize_uhp(&bi, file_name);
 	if (uhp == NULL)
 	    goto error;
 	uhp_table[num_read_uhps++] = uhp;
@@ -1898,8 +2175,9 @@ error:
 
 theend:
 #ifdef FEAT_CRYPT
-    if (do_decrypt)
-	crypt_pop_state();
+    if (bi.bi_state != NULL)
+	crypt_free_state(bi.bi_state);
+    vim_free(bi.bi_buffer);
 #endif
     if (fp != NULL)
 	fclose(fp);