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)
diff --git a/src/Makefile b/src/Makefile
index 1d20ec1..772b15c 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -1431,6 +1431,8 @@
 	blowfish.c \
 	buffer.c \
 	charset.c \
+	crypt.c \
+	crypt_zip.c \
 	diff.c \
 	digraph.c \
 	edit.c \
@@ -1520,6 +1522,8 @@
 	objects/buffer.o \
 	objects/blowfish.o \
 	objects/charset.o \
+	objects/crypt.o \
+	objects/crypt_zip.o \
 	objects/diff.o \
 	objects/digraph.o \
 	objects/edit.o \
@@ -1589,6 +1593,8 @@
 	blowfish.pro \
 	buffer.pro \
 	charset.pro \
+	crypt.pro \
+	crypt_zip.pro \
 	diff.pro \
 	digraph.pro \
 	edit.pro \
@@ -1753,10 +1759,11 @@
 languages:
 	@if test -n "$(MAKEMO)" -a -f $(PODIR)/Makefile; then \
 		cd $(PODIR); \
-		CC="$(CC)" $(MAKE) prefix=$(DESTDIR)$(prefix); \
+		  CC="$(CC)" $(MAKE) prefix=$(DESTDIR)$(prefix); \
 	fi
 	-@if test -n "$(MAKEMO)" -a -f $(PODIR)/Makefile; then \
-		cd $(PODIR); CC="$(CC)" $(MAKE) prefix=$(DESTDIR)$(prefix) converted; \
+		cd $(PODIR); \
+		  CC="$(CC)" $(MAKE) prefix=$(DESTDIR)$(prefix) converted; \
 	fi
 
 # Update the *.po files for changes in the sources.  Only run manually.
@@ -1883,8 +1890,14 @@
 # Run individual test, assuming that Vim was already compiled.
 test1 test2 test3 test4 test5 test6 test7 test8 test9 \
 	test_autoformat_join \
+	test_breakindent \
+	test_changelist \
 	test_eval \
+	test_insertcount \
+	test_listlbr \
+	test_listlbr_utf8 \
 	test_options \
+	test_qf_title \
 	test10 test11 test12 test13 test14 test15 test16 test17 test18 test19 \
 	test20 test21 test22 test23 test24 test25 test26 test27 test28 test29 \
 	test30 test31 test32 test33 test34 test35 test36 test37 test38 test39 \
@@ -2506,6 +2519,12 @@
 objects/charset.o: charset.c
 	$(CCC) -o $@ charset.c
 
+objects/crypt.o: crypt.c
+	$(CCC) -o $@ crypt.c
+
+objects/crypt_zip.o: crypt_zip.c
+	$(CCC) -o $@ crypt_zip.c
+
 objects/diff.o: diff.c
 	$(CCC) -o $@ diff.c
 
@@ -2855,6 +2874,14 @@
  ascii.h keymap.h term.h macros.h option.h structs.h regexp.h gui.h \
  gui_beval.h proto/gui_beval.pro ex_cmds.h proto.h globals.h farsi.h \
  arabic.h
+objects/crypt.o: crypt.c vim.h auto/config.h feature.h os_unix.h auto/osdef.h \
+ ascii.h keymap.h term.h macros.h option.h structs.h regexp.h gui.h \
+ gui_beval.h proto/gui_beval.pro ex_cmds.h proto.h globals.h farsi.h \
+ arabic.h
+objects/crypt_zip.o: crypt_zip.c vim.h auto/config.h feature.h os_unix.h \
+ auto/osdef.h ascii.h keymap.h term.h macros.h option.h structs.h \
+ regexp.h gui.h gui_beval.h proto/gui_beval.pro ex_cmds.h proto.h \
+ globals.h farsi.h arabic.h
 objects/diff.o: diff.c vim.h auto/config.h feature.h os_unix.h auto/osdef.h \
  ascii.h keymap.h term.h macros.h option.h structs.h regexp.h gui.h \
  gui_beval.h proto/gui_beval.pro ex_cmds.h proto.h globals.h farsi.h \
diff --git a/src/blowfish.c b/src/blowfish.c
index 3d9ba55..6bf2482 100644
--- a/src/blowfish.c
+++ b/src/blowfish.c
@@ -9,17 +9,25 @@
  * Blowfish encryption for Vim; in Blowfish cipher feedback mode.
  * Contributed by Mohsin Ahmed, http://www.cs.albany.edu/~mosh
  * Based on http://www.schneier.com/blowfish.html by Bruce Schneier.
+ *
+ * There are two variants:
+ * - The old one "blowfish" has a flaw which makes it much easier to crack the
+ *   key.  To see this, make a text file with one line of 1000 "x" characters
+ *   and write it encrypted.  Use "xxd" to inspect the bytes in the file.  You
+ *   will see that a block of 8 bytes repeats 8 times.
+ * - The new one "blowfish2" is better.  It uses an 8 byte CFB to avoid the
+ *   repeats.
  */
 
 #include "vim.h"
 
-#if defined(FEAT_CRYPT)
+#if defined(FEAT_CRYPT) || defined(PROTO)
 
 #define ARRAY_LENGTH(A)      (sizeof(A)/sizeof(A[0]))
 
 #define BF_BLOCK    8
 #define BF_BLOCK_MASK 7
-#define BF_CFB_LEN  (8*(BF_BLOCK))
+#define BF_MAX_CFB_LEN  (8 * BF_BLOCK)
 
 typedef union {
     UINT32_T ul[2];
@@ -37,14 +45,26 @@
 # endif
 #endif
 
-static void bf_e_block __ARGS((UINT32_T *p_xl, UINT32_T *p_xr));
-static void bf_e_cblock __ARGS((char_u *block));
-static int bf_check_tables __ARGS((UINT32_T a_ipa[18], UINT32_T a_sbi[4][256], UINT32_T val));
+/* The state of encryption, referenced by cryptstate_T. */
+typedef struct {
+    UINT32_T	pax[18];	    /* P-array */
+    UINT32_T	sbx[4][256];	    /* S-boxes */
+    int		randbyte_offset;
+    int		update_offset;
+    char_u	cfb_buffer[BF_MAX_CFB_LEN]; /* up to 64 bytes used */
+    int		cfb_len;	    /* size of cfb_buffer actually used */
+} bf_state_T;
+
+
+static void bf_e_block __ARGS((bf_state_T *state, UINT32_T *p_xl, UINT32_T *p_xr));
+static void bf_e_cblock __ARGS((bf_state_T *state, char_u *block));
+static int bf_check_tables __ARGS((UINT32_T pax[18], UINT32_T sbx[4][256], UINT32_T val));
 static int bf_self_test __ARGS((void));
+static void bf_key_init __ARGS((bf_state_T *state, char_u *password, char_u *salt, int salt_len));
+static void bf_cfb_init __ARGS((bf_state_T *state, char_u *seed, int seed_len));
 
 /* Blowfish code */
-static UINT32_T pax[18];
-static UINT32_T ipa[18] = {
+static UINT32_T pax_init[18] = {
     0x243f6a88u, 0x85a308d3u, 0x13198a2eu,
     0x03707344u, 0xa4093822u, 0x299f31d0u,
     0x082efa98u, 0xec4e6c89u, 0x452821e6u,
@@ -53,8 +73,7 @@
     0xb5470917u, 0x9216d5d9u, 0x8979fb1bu
 };
 
-static UINT32_T sbx[4][256];
-static UINT32_T sbi[4][256] = {
+static UINT32_T sbx_init[4][256] = {
    {0xd1310ba6u, 0x98dfb5acu, 0x2ffd72dbu, 0xd01adfb7u,
     0xb8e1afedu, 0x6a267e96u, 0xba7c9045u, 0xf12c7f99u,
     0x24a19947u, 0xb3916cf7u, 0x0801f2e2u, 0x858efc16u,
@@ -314,33 +333,40 @@
  }
 };
 
-
 #define F1(i) \
-    xl ^= pax[i]; \
-    xr ^= ((sbx[0][xl >> 24] + \
-    sbx[1][(xl & 0xFF0000) >> 16]) ^ \
-    sbx[2][(xl & 0xFF00) >> 8]) + \
-    sbx[3][xl & 0xFF];
+    xl ^= bfs->pax[i]; \
+    xr ^= ((bfs->sbx[0][xl >> 24] + \
+    bfs->sbx[1][(xl & 0xFF0000) >> 16]) ^ \
+    bfs->sbx[2][(xl & 0xFF00) >> 8]) + \
+    bfs->sbx[3][xl & 0xFF];
 
 #define F2(i) \
-    xr ^= pax[i]; \
-    xl ^= ((sbx[0][xr >> 24] + \
-    sbx[1][(xr & 0xFF0000) >> 16]) ^ \
-    sbx[2][(xr & 0xFF00) >> 8]) + \
-    sbx[3][xr & 0xFF];
-
+    xr ^= bfs->pax[i]; \
+    xl ^= ((bfs->sbx[0][xr >> 24] + \
+    bfs->sbx[1][(xr & 0xFF0000) >> 16]) ^ \
+    bfs->sbx[2][(xr & 0xFF00) >> 8]) + \
+    bfs->sbx[3][xr & 0xFF];
 
     static void
-bf_e_block(p_xl, p_xr)
+bf_e_block(bfs, p_xl, p_xr)
+    bf_state_T *bfs;
     UINT32_T *p_xl;
     UINT32_T *p_xr;
 {
-    UINT32_T temp, xl = *p_xl, xr = *p_xr;
+    UINT32_T temp;
+    UINT32_T xl = *p_xl;
+    UINT32_T xr = *p_xr;
 
-    F1(0) F2(1) F1(2) F2(3) F1(4) F2(5) F1(6) F2(7)
-    F1(8) F2(9) F1(10) F2(11) F1(12) F2(13) F1(14) F2(15)
-    xl ^= pax[16];
-    xr ^= pax[17];
+    F1(0) F2(1)
+    F1(2) F2(3)
+    F1(4) F2(5)
+    F1(6) F2(7)
+    F1(8) F2(9)
+    F1(10) F2(11)
+    F1(12) F2(13)
+    F1(14) F2(15)
+    xl ^= bfs->pax[16];
+    xr ^= bfs->pax[17];
     temp = xl;
     xl = xr;
     xr = temp;
@@ -348,22 +374,6 @@
     *p_xr = xr;
 }
 
-#if 0  /* not used */
-    static void
-bf_d_block(p_xl, p_xr)
-    UINT32_T *p_xl;
-    UINT32_T *p_xr;
-{
-    UINT32_T temp, xl = *p_xl, xr = *p_xr;
-    F1(17) F2(16) F1(15) F2(14) F1(13) F2(12) F1(11) F2(10)
-    F1(9) F2(8) F1(7) F2(6) F1(5) F2(4) F1(3) F2(2)
-    xl ^= pax[1];
-    xr ^= pax[0];
-    temp = xl; xl = xr; xr = temp;
-    *p_xl = xl; *p_xr = xr;
-}
-#endif
-
 
 #ifdef WORDS_BIGENDIAN
 # define htonl2(x) \
@@ -374,7 +384,8 @@
 #endif
 
     static void
-bf_e_cblock(block)
+bf_e_cblock(bfs, block)
+    bf_state_T *bfs;
     char_u *block;
 {
     block8	bk;
@@ -382,35 +393,22 @@
     memcpy(bk.uc, block, 8);
     htonl2(bk.ul[0]);
     htonl2(bk.ul[1]);
-    bf_e_block(&bk.ul[0], &bk.ul[1]);
+    bf_e_block(bfs, &bk.ul[0], &bk.ul[1]);
     htonl2(bk.ul[0]);
     htonl2(bk.ul[1]);
     memcpy(block, bk.uc, 8);
 }
 
-#if 0  /* not used */
-    void
-bf_d_cblock(block)
-    char_u *block;
-{
-    block8 bk;
-    memcpy(bk.uc, block, 8);
-    htonl2(bk.ul[0]); htonl2(bk.ul[1]);
-    bf_d_block(&bk.ul[0], &bk.ul[1]);
-    htonl2(bk.ul[0]); htonl2(bk.ul[1]);
-    memcpy(block, bk.uc, 8);
-}
-#endif
-
 /*
  * Initialize the crypt method using "password" as the encryption key and
  * "salt[salt_len]" as the salt.
  */
-    void
-bf_key_init(password, salt, salt_len)
-    char_u *password;
-    char_u *salt;
-    int    salt_len;
+    static void
+bf_key_init(bfs, password, salt, salt_len)
+    bf_state_T	*bfs;
+    char_u	*password;
+    char_u	*salt;
+    int		salt_len;
 {
     int      i, j, keypos = 0;
     unsigned u;
@@ -418,7 +416,7 @@
     char_u   *key;
     int      keylen;
 
-    /* Process the key 1000 times.
+    /* Process the key 1001 times.
      * See http://en.wikipedia.org/wiki/Key_strengthening. */
     key = sha256_key(password, salt, salt_len);
     for (i = 0; i < 1000; i++)
@@ -437,52 +435,54 @@
 	key[i] = u;
     }
 
-    mch_memmove(sbx, sbi, 4 * 4 * 256);
+    /* Use "key" to initialize the P-array ("pax") and S-boxes ("sbx") of
+     * Blowfish. */
+    mch_memmove(bfs->sbx, sbx_init, 4 * 4 * 256);
 
     for (i = 0; i < 18; ++i)
     {
 	val = 0;
 	for (j = 0; j < 4; ++j)
 	    val = (val << 8) | key[keypos++ % keylen];
-	pax[i] = ipa[i] ^ val;
+	bfs->pax[i] = pax_init[i] ^ val;
     }
 
     data_l = data_r = 0;
     for (i = 0; i < 18; i += 2)
     {
-	bf_e_block(&data_l, &data_r);
-	pax[i + 0] = data_l;
-	pax[i + 1] = data_r;
+	bf_e_block(bfs, &data_l, &data_r);
+	bfs->pax[i + 0] = data_l;
+	bfs->pax[i + 1] = data_r;
     }
 
     for (i = 0; i < 4; ++i)
     {
 	for (j = 0; j < 256; j += 2)
 	{
-	    bf_e_block(&data_l, &data_r);
-	    sbx[i][j + 0] = data_l;
-	    sbx[i][j + 1] = data_r;
+	    bf_e_block(bfs, &data_l, &data_r);
+	    bfs->sbx[i][j + 0] = data_l;
+	    bfs->sbx[i][j + 1] = data_r;
 	}
     }
 }
 
 /*
- * BF Self test for corrupted tables or instructions
+ * Blowfish self-test for corrupted tables or instructions.
  */
     static int
-bf_check_tables(a_ipa, a_sbi, val)
-    UINT32_T a_ipa[18];
-    UINT32_T a_sbi[4][256];
+bf_check_tables(pax, sbx, val)
+    UINT32_T pax[18];
+    UINT32_T sbx[4][256];
     UINT32_T val;
 {
     int i, j;
     UINT32_T c = 0;
 
     for (i = 0; i < 18; i++)
-	c ^= a_ipa[i];
+	c ^= pax[i];
     for (i = 0; i < 4; i++)
 	for (j = 0; j < 256; j++)
-	    c ^= a_sbi[i][j];
+	    c ^= sbx[i][j];
     return c == val;
 }
 
@@ -520,6 +520,10 @@
     int    err = 0;
     block8 bk;
     UINT32_T ui = 0xffffffffUL;
+    bf_state_T state;
+
+    vim_memset(&state, 0, sizeof(bf_state_T));
+    state.cfb_len = BF_MAX_CFB_LEN;
 
     /* We can't simply use sizeof(UINT32_T), it would generate a compiler
      * warning. */
@@ -528,21 +532,21 @@
 	EMSG(_("E820: sizeof(uint32_t) != 4"));
     }
 
-    if (!bf_check_tables(ipa, sbi, 0x6ffa520a))
+    if (!bf_check_tables(pax_init, sbx_init, 0x6ffa520a))
 	err++;
 
     bn = ARRAY_LENGTH(bf_test_data);
     for (i = 0; i < bn; i++)
     {
-	bf_key_init((char_u *)(bf_test_data[i].password),
+	bf_key_init(&state, (char_u *)(bf_test_data[i].password),
 		    bf_test_data[i].salt,
 		    (int)STRLEN(bf_test_data[i].salt));
-	if (!bf_check_tables(pax, sbx, bf_test_data[i].keysum))
+	if (!bf_check_tables(state.pax, state.sbx, bf_test_data[i].keysum))
 	    err++;
 
 	/* Don't modify bf_test_data[i].plaintxt, self test is idempotent. */
 	memcpy(bk.uc, bf_test_data[i].plaintxt, 8);
-	bf_e_cblock(bk.uc);
+	bf_e_cblock(&state, bk.uc);
 	if (memcmp(bk.uc, bf_test_data[i].cryptxt, 8) != 0)
 	{
 	    if (err == 0 && memcmp(bk.uc, bf_test_data[i].badcryptxt, 8) == 0)
@@ -554,43 +558,43 @@
     return err > 0 ? FAIL : OK;
 }
 
-/* Cipher feedback mode. */
-static int randbyte_offset = 0;
-static int update_offset = 0;
-static char_u cfb_buffer[BF_CFB_LEN]; /* 64 bytes */
+/*
+ * CFB: Cipher Feedback Mode.
+ */
 
 /*
- * Initialize with seed "iv[iv_len]".
+ * Initialize with seed "seed[seed_len]".
  */
-    void
-bf_cfb_init(iv, iv_len)
-    char_u *iv;
-    int    iv_len;
+    static void
+bf_cfb_init(bfs, seed, seed_len)
+    bf_state_T	*bfs;
+    char_u	*seed;
+    int		seed_len;
 {
     int i, mi;
 
-    randbyte_offset = update_offset = 0;
-    vim_memset(cfb_buffer, 0, BF_CFB_LEN);
-    if (iv_len > 0)
+    bfs->randbyte_offset = bfs->update_offset = 0;
+    vim_memset(bfs->cfb_buffer, 0, bfs->cfb_len);
+    if (seed_len > 0)
     {
-	mi = iv_len > BF_CFB_LEN ? iv_len : BF_CFB_LEN;
+	mi = seed_len > bfs->cfb_len ? seed_len : bfs->cfb_len;
 	for (i = 0; i < mi; i++)
-	    cfb_buffer[i % BF_CFB_LEN] ^= iv[i % iv_len];
+	    bfs->cfb_buffer[i % bfs->cfb_len] ^= seed[i % seed_len];
     }
 }
 
-#define BF_CFB_UPDATE(c) { \
-    cfb_buffer[update_offset] ^= (char_u)c; \
-    if (++update_offset == BF_CFB_LEN) \
-	update_offset = 0; \
+#define BF_CFB_UPDATE(bfs, c) { \
+    bfs->cfb_buffer[bfs->update_offset] ^= (char_u)c; \
+    if (++bfs->update_offset == bfs->cfb_len) \
+	bfs->update_offset = 0; \
 }
 
-#define BF_RANBYTE(t) { \
-    if ((randbyte_offset & BF_BLOCK_MASK) == 0) \
-	bf_e_cblock(&cfb_buffer[randbyte_offset]); \
-    t = cfb_buffer[randbyte_offset]; \
-    if (++randbyte_offset == BF_CFB_LEN) \
-	randbyte_offset = 0; \
+#define BF_RANBYTE(bfs, t) { \
+    if ((bfs->randbyte_offset & BF_BLOCK_MASK) == 0) \
+	bf_e_cblock(bfs, &(bfs->cfb_buffer[bfs->randbyte_offset])); \
+    t = bfs->cfb_buffer[bfs->randbyte_offset]; \
+    if (++bfs->randbyte_offset == bfs->cfb_len) \
+	bfs->randbyte_offset = 0; \
 }
 
 /*
@@ -598,90 +602,69 @@
  * "from" and "to" can be equal to encrypt in place.
  */
     void
-bf_crypt_encode(from, len, to)
+crypt_blowfish_encode(state, from, len, to)
+    cryptstate_T *state;
     char_u	*from;
     size_t	len;
     char_u	*to;
 {
+    bf_state_T *bfs = state->method_state;
     size_t	i;
     int		ztemp, t;
 
     for (i = 0; i < len; ++i)
     {
 	ztemp = from[i];
-	BF_RANBYTE(t);
-	BF_CFB_UPDATE(ztemp);
+	BF_RANBYTE(bfs, t);
+	BF_CFB_UPDATE(bfs, ztemp);
 	to[i] = t ^ ztemp;
     }
 }
 
 /*
- * Decrypt "ptr[len]" in place.
+ * Decrypt "from[len]" into "to[len]".
  */
     void
-bf_crypt_decode(ptr, len)
-    char_u	*ptr;
-    long	len;
+crypt_blowfish_decode(state, from, len, to)
+    cryptstate_T *state;
+    char_u	*from;
+    size_t	len;
+    char_u	*to;
 {
-    char_u	*p;
+    bf_state_T *bfs = state->method_state;
+    size_t	i;
     int		t;
 
-    for (p = ptr; p < ptr + len; ++p)
+    for (i = 0; i < len; ++i)
     {
-	BF_RANBYTE(t);
-	*p ^= t;
-	BF_CFB_UPDATE(*p);
+	BF_RANBYTE(bfs, t);
+	to[i] = from[i] ^ t;
+	BF_CFB_UPDATE(bfs, to[i]);
     }
 }
 
-/*
- * Initialize the encryption keys and the random header according to
- * the given password.
- */
     void
-bf_crypt_init_keys(passwd)
-    char_u *passwd;		/* password string with which to modify keys */
+crypt_blowfish_init(state, key, salt, salt_len, seed, seed_len)
+    cryptstate_T	*state;
+    char_u*		key;
+    char_u*		salt;
+    int			salt_len;
+    char_u*		seed;
+    int			seed_len;
 {
-    char_u *p;
+    bf_state_T	*bfs = (bf_state_T *)alloc_clear(sizeof(bf_state_T));
 
-    for (p = passwd; *p != NUL; ++p)
-    {
-	BF_CFB_UPDATE(*p);
-    }
-}
+    state->method_state = bfs;
 
-static int save_randbyte_offset;
-static int save_update_offset;
-static char_u save_cfb_buffer[BF_CFB_LEN];
-static UINT32_T save_pax[18];
-static UINT32_T save_sbx[4][256];
+    /* "blowfish" uses a 64 byte buffer, causing it to repeat 8 byte groups 8
+     * times.  "blowfish2" uses a 8 byte buffer to avoid repeating. */
+    bfs->cfb_len = state->method_nr == CRYPT_M_BF ? BF_MAX_CFB_LEN : BF_BLOCK;
 
-/*
- * Save the current crypt state.  Can only be used once before
- * bf_crypt_restore().
- */
-    void
-bf_crypt_save()
-{
-    save_randbyte_offset = randbyte_offset;
-    save_update_offset = update_offset;
-    mch_memmove(save_cfb_buffer, cfb_buffer, BF_CFB_LEN);
-    mch_memmove(save_pax, pax, 4 * 18);
-    mch_memmove(save_sbx, sbx, 4 * 4 * 256);
-}
+    if (blowfish_self_test() == FAIL)
+	return;
 
-/*
- * Restore the current crypt state.  Can only be used after
- * bf_crypt_save().
- */
-    void
-bf_crypt_restore()
-{
-    randbyte_offset = save_randbyte_offset;
-    update_offset = save_update_offset;
-    mch_memmove(cfb_buffer, save_cfb_buffer, BF_CFB_LEN);
-    mch_memmove(pax, save_pax, 4 * 18);
-    mch_memmove(sbx, save_sbx, 4 * 4 * 256);
+    bf_key_init(bfs, key, salt, salt_len);
+    bf_cfb_init(bfs, seed, seed_len);
 }
 
 /*
diff --git a/src/crypt.c b/src/crypt.c
new file mode 100644
index 0000000..758ffb1
--- /dev/null
+++ b/src/crypt.c
@@ -0,0 +1,585 @@
+/* vi:set ts=8 sts=4 sw=4:
+ *
+ * VIM - Vi IMproved	by Bram Moolenaar
+ *
+ * Do ":help uganda"  in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+/*
+ * crypt.c: Generic encryption support.
+ */
+#include "vim.h"
+
+#if defined(FEAT_CRYPT) || defined(PROTO)
+/*
+ * Optional encryption support.
+ * Mohsin Ahmed, mosh@sasi.com, 1998-09-24
+ * Based on zip/crypt sources.
+ * Refactored by David Leadbeater, 2014.
+ *
+ * NOTE FOR USA: Since 2000 exporting this code from the USA is allowed to
+ * most countries.  There are a few exceptions, but that still should not be a
+ * problem since this code was originally created in Europe and India.
+ *
+ * Blowfish addition originally made by Mohsin Ahmed,
+ * http://www.cs.albany.edu/~mosh 2010-03-14
+ * Based on blowfish by Bruce Schneier (http://www.schneier.com/blowfish.html)
+ * and sha256 by Christophe Devine.
+ */
+
+typedef struct {
+    char    *name;	/* encryption name as used in 'cryptmethod' */
+    char    *magic;	/* magic bytes stored in file header */
+    int	    salt_len;	/* length of salt, or 0 when not using salt */
+    int	    seed_len;	/* length of seed, or 0 when not using salt */
+    int	    works_inplace; /* encryption/decryption can be done in-place */
+    int	    whole_undofile; /* whole undo file is encrypted */
+
+    /* Optional function pointer for a self-test. */
+    int (* self_test_fn)();
+
+    /* Function pointer for initializing encryption/decription. */
+    void (* init_fn)(cryptstate_T *state, char_u *key,
+		      char_u *salt, int salt_len, char_u *seed, int seed_len);
+
+    /* Function pointers for encoding/decoding from one buffer into another.
+     * Optional, however, these or the _buffer ones should be configured. */
+    void (*encode_fn)(cryptstate_T *state, char_u *from, size_t len,
+								  char_u *to);
+    void (*decode_fn)(cryptstate_T *state, char_u *from, size_t len,
+								  char_u *to);
+
+    /* Function pointers for encoding and decoding, can buffer data if needed.
+     * Optional (however, these or the above should be configured). */
+    long (*encode_buffer_fn)(cryptstate_T *state, char_u *from, size_t len,
+							     char_u **newptr);
+    long (*decode_buffer_fn)(cryptstate_T *state, char_u *from, size_t len,
+							     char_u **newptr);
+
+    /* Function pointers for in-place encoding and decoding, used for
+     * crypt_*_inplace(). "from" and "to" arguments will be equal.
+     * These may be the same as decode_fn and encode_fn above, however an
+     * algorithm may implement them in a way that is not interchangeable with
+     * the crypt_(en|de)code() interface (for example because it wishes to add
+     * padding to files).
+     * This method is used for swap and undo files which have a rigid format.
+     */
+    void (*encode_inplace_fn)(cryptstate_T *state, char_u *p1, size_t len,
+								  char_u *p2);
+    void (*decode_inplace_fn)(cryptstate_T *state, char_u *p1, size_t len,
+								  char_u *p2);
+} cryptmethod_T;
+
+/* index is method_nr of cryptstate_T, CRYPT_M_* */
+static cryptmethod_T cryptmethods[CRYPT_M_COUNT] = {
+    /* PK_Zip; very weak */
+    {
+	"zip",
+	"VimCrypt~01!",
+	0,
+	0,
+	TRUE,
+	FALSE,
+	NULL,
+	crypt_zip_init,
+	crypt_zip_encode, crypt_zip_decode,
+	NULL, NULL,
+	crypt_zip_encode, crypt_zip_decode,
+    },
+
+    /* Blowfish/CFB + SHA-256 custom key derivation; implementation issues. */
+    {
+	"blowfish",
+	"VimCrypt~02!",
+	8,
+	8,
+	TRUE,
+	FALSE,
+	blowfish_self_test,
+	crypt_blowfish_init,
+	crypt_blowfish_encode, crypt_blowfish_decode,
+	NULL, NULL,
+	crypt_blowfish_encode, crypt_blowfish_decode,
+    },
+
+    /* Blowfish/CFB + SHA-256 custom key derivation; fixed. */
+    {
+	"blowfish2",
+	"VimCrypt~03!",
+	8,
+	8,
+	TRUE,
+	TRUE,
+	blowfish_self_test,
+	crypt_blowfish_init,
+	crypt_blowfish_encode, crypt_blowfish_decode,
+	NULL, NULL,
+	crypt_blowfish_encode, crypt_blowfish_decode,
+    },
+};
+
+#define CRYPT_MAGIC_LEN	12	/* cannot change */
+static char	crypt_magic_head[] = "VimCrypt~";
+
+/*
+ * Return int value for crypt method name.
+ * 0 for "zip", the old method.  Also for any non-valid value.
+ * 1 for "blowfish".
+ * 2 for "blowfish2".
+ */
+    int
+crypt_method_nr_from_name(name)
+    char_u  *name;
+{
+    int i;
+
+    for (i = 0; i < CRYPT_M_COUNT; ++i)
+	if (STRCMP(name, cryptmethods[i].name) == 0)
+	    return i;
+    return 0;
+}
+
+/*
+ * Get the crypt method used for a file from "ptr[len]", the magic text at the
+ * start of the file.
+ * Returns -1 when no encryption used.
+ */
+    int
+crypt_method_nr_from_magic(ptr, len)
+    char  *ptr;
+    int   len;
+{
+    int i;
+
+    if (len < CRYPT_MAGIC_LEN)
+	return -1;
+
+    for (i = 0; i < CRYPT_M_COUNT; i++)
+	if (memcmp(ptr, cryptmethods[i].magic, CRYPT_MAGIC_LEN) == 0)
+	    return i;
+
+    i = (int)STRLEN(crypt_magic_head);
+    if (len >= i && memcmp(ptr, crypt_magic_head, i) == 0)
+	EMSG(_("E821: File is encrypted with unknown method"));
+
+    return -1;
+}
+
+/*
+ * Return TRUE if the crypt method for "method_nr" can be done in-place.
+ */
+    int
+crypt_works_inplace(state)
+    cryptstate_T *state;
+{
+    return cryptmethods[state->method_nr].works_inplace;
+}
+
+/*
+ * Get the crypt method for buffer "buf" as a number.
+ */
+    int
+crypt_get_method_nr(buf)
+    buf_T *buf;
+{
+    return crypt_method_nr_from_name(*buf->b_p_cm == NUL ? p_cm : buf->b_p_cm);
+}
+
+/*
+ * Return TRUE when the buffer uses an encryption method that encrypts the
+ * whole undo file, not only the text.
+ */
+    int
+crypt_whole_undofile(method_nr)
+    int method_nr;
+{
+    return cryptmethods[method_nr].whole_undofile;
+}
+
+/*
+ * Get crypt method specifc length of the file header in bytes.
+ */
+    int
+crypt_get_header_len(method_nr)
+    int method_nr;
+{
+    return CRYPT_MAGIC_LEN
+	+ cryptmethods[method_nr].salt_len
+	+ cryptmethods[method_nr].seed_len;
+}
+
+/*
+ * Set the crypt method for buffer "buf" to "method_nr" using the int value as
+ * returned by crypt_method_nr_from_name().
+ */
+    void
+crypt_set_cm_option(buf, method_nr)
+    buf_T   *buf;
+    int	    method_nr;
+{
+    free_string_option(buf->b_p_cm);
+    buf->b_p_cm = vim_strsave((char_u *)cryptmethods[method_nr].name);
+}
+
+/*
+ * If the crypt method for the current buffer has a self-test, run it and
+ * return OK/FAIL.
+ */
+    int
+crypt_self_test()
+{
+    int method_nr = crypt_get_method_nr(curbuf);
+
+    if (cryptmethods[method_nr].self_test_fn == NULL)
+	return OK;
+    return cryptmethods[method_nr].self_test_fn();
+}
+
+/*
+ * Allocate a crypt state and initialize it.
+ */
+    cryptstate_T *
+crypt_create(method_nr, key, salt, salt_len, seed, seed_len)
+    int		method_nr;
+    char_u	*key;
+    char_u	*salt;
+    int		salt_len;
+    char_u	*seed;
+    int		seed_len;
+{
+    cryptstate_T *state = (cryptstate_T *)alloc((int)sizeof(cryptstate_T));
+
+    state->method_nr = method_nr;
+    cryptmethods[method_nr].init_fn(state, key, salt, salt_len, seed, seed_len);
+    return state;
+}
+
+/*
+ * Allocate a crypt state from a file header and initialize it.
+ * Assumes that header contains at least the number of bytes that
+ * crypt_get_header_len() returns for "method_nr".
+ */
+    cryptstate_T *
+crypt_create_from_header(method_nr, key, header)
+    int		method_nr;
+    char_u	*key;
+    char_u	*header;
+{
+    char_u	*salt = NULL;
+    char_u	*seed = NULL;
+    int		salt_len = cryptmethods[method_nr].salt_len;
+    int		seed_len = cryptmethods[method_nr].seed_len;
+
+    if (salt_len > 0)
+	salt = header + CRYPT_MAGIC_LEN;
+    if (seed_len > 0)
+	seed = header + CRYPT_MAGIC_LEN + salt_len;
+
+    return crypt_create(method_nr, key, salt, salt_len, seed, seed_len);
+}
+
+/*
+ * Read the crypt method specific header data from "fp".
+ * Return an allocated cryptstate_T or NULL on error.
+ */
+    cryptstate_T *
+crypt_create_from_file(fp, key)
+    FILE    *fp;
+    char_u  *key;
+{
+    int		method_nr;
+    int		header_len;
+    char	magic_buffer[CRYPT_MAGIC_LEN];
+    char_u	*buffer;
+    cryptstate_T *state;
+
+    if (fread(magic_buffer, CRYPT_MAGIC_LEN, 1, fp) != 1)
+	return NULL;
+    method_nr = crypt_method_nr_from_magic(magic_buffer, CRYPT_MAGIC_LEN);
+    if (method_nr < 0)
+	return NULL;
+
+    header_len = crypt_get_header_len(method_nr);
+    if ((buffer = alloc(header_len)) == NULL)
+	return NULL;
+    mch_memmove(buffer, magic_buffer, CRYPT_MAGIC_LEN);
+    if (header_len > CRYPT_MAGIC_LEN
+	    && fread(buffer + CRYPT_MAGIC_LEN,
+				    header_len - CRYPT_MAGIC_LEN, 1, fp) != 1)
+    {
+	vim_free(buffer);
+	return NULL;
+    }
+
+    state = crypt_create_from_header(method_nr, key, buffer);
+    vim_free(buffer);
+    return state;
+}
+
+/*
+ * Allocate a cryptstate_T for writing and initialize it with "key".
+ * Allocates and fills in the header and stores it in "header", setting
+ * "header_len".  The header may include salt and seed, depending on
+ * cryptmethod.  Caller must free header.
+ * Returns the state or NULL on failure.
+ */
+    cryptstate_T *
+crypt_create_for_writing(method_nr, key, header, header_len)
+    int	    method_nr;
+    char_u  *key;
+    char_u  **header;
+    int	    *header_len;
+{
+    int	    len = crypt_get_header_len(method_nr);
+    char_u  *salt = NULL;
+    char_u  *seed = NULL;
+    int	    salt_len = cryptmethods[method_nr].salt_len;
+    int	    seed_len = cryptmethods[method_nr].seed_len;
+    cryptstate_T *state;
+
+    *header_len = len;
+    *header = alloc(len);
+    if (*header == NULL)
+	return NULL;
+
+    mch_memmove(*header, cryptmethods[method_nr].magic, CRYPT_MAGIC_LEN);
+    if (salt_len > 0 || seed_len > 0)
+    {
+	if (salt_len > 0)
+	    salt = *header + CRYPT_MAGIC_LEN;
+	if (seed_len > 0)
+	    seed = *header + CRYPT_MAGIC_LEN + salt_len;
+
+	/* TODO: Should this be crypt method specific? (Probably not worth
+	 * it).  sha2_seed is pretty bad for large amounts of entropy, so make
+	 * that into something which is suitable for anything. */
+	sha2_seed(salt, salt_len, seed, seed_len);
+    }
+
+    state = crypt_create(method_nr, key, salt, salt_len, seed, seed_len);
+    if (state == NULL)
+    {
+	vim_free(*header);
+	*header = NULL;
+    }
+    return state;
+}
+
+/*
+ * Free the crypt state.
+ */
+    void
+crypt_free_state(state)
+    cryptstate_T	*state;
+{
+    vim_free(state->method_state);
+    vim_free(state);
+}
+
+/*
+ * Encode "from[len]" and store the result in a newly allocated buffer, which
+ * is stored in "newptr".
+ * Return number of bytes in "newptr", 0 for need more or -1 on error.
+ */
+    long
+crypt_encode_alloc(state, from, len, newptr)
+    cryptstate_T *state;
+    char_u	*from;
+    size_t	len;
+    char_u	**newptr;
+{
+    cryptmethod_T *method = &cryptmethods[state->method_nr];
+
+    if (method->encode_buffer_fn != NULL)
+	/* Has buffer function, pass through. */
+	return method->encode_buffer_fn(state, from, len, newptr);
+    if (len == 0)
+	/* Not buffering, just return EOF. */
+	return len;
+
+    *newptr = alloc(len);
+    if (*newptr == NULL)
+	return -1;
+    method->encode_fn(state, from, len, *newptr);
+    return len;
+}
+
+/*
+ * Decrypt "ptr[len]" and store the result in a newly allocated buffer, which
+ * is stored in "newptr".
+ * Return number of bytes in "newptr", 0 for need more or -1 on error.
+ */
+    long
+crypt_decode_alloc(state, ptr, len, newptr)
+    cryptstate_T *state;
+    char_u	*ptr;
+    long	len;
+    char_u      **newptr;
+{
+    cryptmethod_T *method = &cryptmethods[state->method_nr];
+
+    if (method->decode_buffer_fn != NULL)
+	/* Has buffer function, pass through. */
+	return method->decode_buffer_fn(state, ptr, len, newptr);
+
+    if (len == 0)
+	/* Not buffering, just return EOF. */
+	return len;
+
+    *newptr = alloc(len);
+    if (*newptr == NULL)
+	return -1;
+    method->decode_fn(state, ptr, len, *newptr);
+    return len;
+}
+
+/*
+ * Encrypting "from[len]" into "to[len]".
+ */
+    void
+crypt_encode(state, from, len, to)
+    cryptstate_T *state;
+    char_u	*from;
+    size_t	len;
+    char_u	*to;
+{
+    cryptmethods[state->method_nr].encode_fn(state, from, len, to);
+}
+
+/*
+ * decrypting "from[len]" into "to[len]".
+ */
+    void
+crypt_decode(state, from, len, to)
+    cryptstate_T *state;
+    char_u	*from;
+    size_t	len;
+    char_u	*to;
+{
+    cryptmethods[state->method_nr].decode_fn(state, from, len, to);
+}
+
+/*
+ * Simple inplace encryption, modifies "buf[len]" in place.
+ */
+    void
+crypt_encode_inplace(state, buf, len)
+    cryptstate_T *state;
+    char_u	*buf;
+    size_t	len;
+{
+    cryptmethods[state->method_nr].encode_inplace_fn(state, buf, len, buf);
+}
+
+/*
+ * Simple inplace decryption, modifies "buf[len]" in place.
+ */
+    void
+crypt_decode_inplace(state, buf, len)
+    cryptstate_T *state;
+    char_u	*buf;
+    size_t	len;
+{
+    cryptmethods[state->method_nr].decode_inplace_fn(state, buf, len, buf);
+}
+
+/*
+ * Free an allocated crypt key.  Clear the text to make sure it doesn't stay
+ * in memory anywhere.
+ */
+    void
+crypt_free_key(key)
+    char_u *key;
+{
+    char_u *p;
+
+    if (key != NULL)
+    {
+	for (p = key; *p != NUL; ++p)
+	    *p = 0;
+	vim_free(key);
+    }
+}
+
+/*
+ * Ask the user for a crypt key.
+ * When "store" is TRUE, the new key is stored in the 'key' option, and the
+ * 'key' option value is returned: Don't free it.
+ * When "store" is FALSE, the typed key is returned in allocated memory.
+ * Returns NULL on failure.
+ */
+    char_u *
+crypt_get_key(store, twice)
+    int		store;
+    int		twice;	    /* Ask for the key twice. */
+{
+    char_u	*p1, *p2 = NULL;
+    int		round;
+
+    for (round = 0; ; ++round)
+    {
+	cmdline_star = TRUE;
+	cmdline_row = msg_row;
+	p1 = getcmdline_prompt(NUL, round == 0
+		? (char_u *)_("Enter encryption key: ")
+		: (char_u *)_("Enter same key again: "), 0, EXPAND_NOTHING,
+		NULL);
+	cmdline_star = FALSE;
+
+	if (p1 == NULL)
+	    break;
+
+	if (round == twice)
+	{
+	    if (p2 != NULL && STRCMP(p1, p2) != 0)
+	    {
+		MSG(_("Keys don't match!"));
+		crypt_free_key(p1);
+		crypt_free_key(p2);
+		p2 = NULL;
+		round = -1;		/* do it again */
+		continue;
+	    }
+
+	    if (store)
+	    {
+		set_option_value((char_u *)"key", 0L, p1, OPT_LOCAL);
+		crypt_free_key(p1);
+		p1 = curbuf->b_p_key;
+	    }
+	    break;
+	}
+	p2 = p1;
+    }
+
+    /* since the user typed this, no need to wait for return */
+    if (msg_didout)
+	msg_putchar('\n');
+    need_wait_return = FALSE;
+    msg_didout = FALSE;
+
+    crypt_free_key(p2);
+    return p1;
+}
+
+
+/*
+ * Append a message to IObuff for the encryption/decryption method being used.
+ */
+    void
+crypt_append_msg(buf)
+    buf_T *buf;
+{
+    if (crypt_get_method_nr(buf) == 0)
+	STRCAT(IObuff, _("[crypted]"));
+    else
+    {
+	STRCAT(IObuff, "[");
+	STRCAT(IObuff, *buf->b_p_cm == NUL ? p_cm : buf->b_p_cm);
+	STRCAT(IObuff, "]");
+    }
+}
+
+#endif /* FEAT_CRYPT */
diff --git a/src/crypt_zip.c b/src/crypt_zip.c
new file mode 100644
index 0000000..571f3ec
--- /dev/null
+++ b/src/crypt_zip.c
@@ -0,0 +1,158 @@
+/* vi:set ts=8 sts=4 sw=4:
+ *
+ * VIM - Vi IMproved	by Bram Moolenaar
+ *
+ * Do ":help uganda"  in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+/*
+ * crypt_zip.c: Zip encryption support.
+ */
+#include "vim.h"
+
+#if defined(FEAT_CRYPT) || defined(PROTO)
+/*
+ * Optional encryption support.
+ * Mohsin Ahmed, mosh@sasi.com, 98-09-24
+ * Based on zip/crypt sources.
+ *
+ * NOTE FOR USA: Since 2000 exporting this code from the USA is allowed to
+ * most countries.  There are a few exceptions, but that still should not be a
+ * problem since this code was originally created in Europe and India.
+ */
+
+/* Need a type that should be 32 bits. 64 also works but wastes space. */
+# if VIM_SIZEOF_INT >= 4
+typedef unsigned int u32_T;	/* int is at least 32 bits */
+# else
+typedef unsigned long u32_T;	/* long should be 32 bits or more */
+# endif
+
+/* The state of encryption, referenced by cryptstate_T. */
+typedef struct {
+    u32_T keys[3];
+} zip_state_T;
+
+
+static void make_crc_tab __ARGS((void));
+
+static u32_T crc_32_table[256];
+
+/*
+ * Fill the CRC table, if not done already.
+ */
+    static void
+make_crc_tab()
+{
+    u32_T	s, t, v;
+    static int	done = FALSE;
+
+    if (done)
+	return;
+    for (t = 0; t < 256; t++)
+    {
+	v = t;
+	for (s = 0; s < 8; s++)
+	    v = (v >> 1) ^ ((v & 1) * (u32_T)0xedb88320L);
+	crc_32_table[t] = v;
+    }
+    done = TRUE;
+}
+
+#define CRC32(c, b) (crc_32_table[((int)(c) ^ (b)) & 0xff] ^ ((c) >> 8))
+
+/*
+ * Return the next byte in the pseudo-random sequence.
+ */
+#define DECRYPT_BYTE_ZIP(keys, t) { \
+    short_u temp = (short_u)keys[2] | 2; \
+    t = (int)(((unsigned)(temp * (temp ^ 1U)) >> 8) & 0xff); \
+}
+
+/*
+ * Update the encryption keys with the next byte of plain text.
+ */
+#define UPDATE_KEYS_ZIP(keys, c) { \
+    keys[0] = CRC32(keys[0], (c)); \
+    keys[1] += keys[0] & 0xff; \
+    keys[1] = keys[1] * 134775813L + 1; \
+    keys[2] = CRC32(keys[2], (int)(keys[1] >> 24)); \
+}
+
+/*
+ * Initialize for encryption/decryption.
+ */
+    void
+crypt_zip_init(state, key, salt, salt_len, seed, seed_len)
+    cryptstate_T    *state;
+    char_u	    *key;
+    char_u	    *salt UNUSED;
+    int		    salt_len UNUSED;
+    char_u	    *seed UNUSED;
+    int		    seed_len UNUSED;
+{
+    char_u	*p;
+    zip_state_T	*zs;
+
+    zs = (zip_state_T *)alloc(sizeof(zip_state_T));
+    state->method_state = zs;
+
+    make_crc_tab();
+    zs->keys[0] = 305419896L;
+    zs->keys[1] = 591751049L;
+    zs->keys[2] = 878082192L;
+    for (p = key; *p != NUL; ++p)
+    {
+	UPDATE_KEYS_ZIP(zs->keys, (int)*p);
+    }
+}
+
+/*
+ * Encrypt "from[len]" into "to[len]".
+ * "from" and "to" can be equal to encrypt in place.
+ */
+    void
+crypt_zip_encode(state, from, len, to)
+    cryptstate_T *state;
+    char_u	*from;
+    size_t	len;
+    char_u	*to;
+{
+    zip_state_T *zs = state->method_state;
+    size_t	i;
+    int		ztemp, t;
+
+    for (i = 0; i < len; ++i)
+    {
+	ztemp = from[i];
+	DECRYPT_BYTE_ZIP(zs->keys, t);
+	UPDATE_KEYS_ZIP(zs->keys, ztemp);
+	to[i] = t ^ ztemp;
+    }
+}
+
+/*
+ * Decrypt "from[len]" into "to[len]".
+ */
+    void
+crypt_zip_decode(state, from, len, to)
+    cryptstate_T *state;
+    char_u	*from;
+    size_t	len;
+    char_u	*to;
+{
+    zip_state_T *zs = state->method_state;
+    size_t	i;
+    short_u	temp;
+
+    for (i = 0; i < len; ++i)
+    {
+	temp = (short_u)zs->keys[2] | 2;
+	temp = (int)(((unsigned)(temp * (temp ^ 1U)) >> 8) & 0xff);
+	UPDATE_KEYS_ZIP(zs->keys, to[i] = from[i] ^ temp);
+    }
+}
+
+#endif /* FEAT_CRYPT */
diff --git a/src/ex_docmd.c b/src/ex_docmd.c
index ba764bf..fecb653 100644
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -11506,8 +11506,7 @@
 ex_X(eap)
     exarg_T	*eap UNUSED;
 {
-    if (get_crypt_method(curbuf) == 0 || blowfish_self_test() == OK)
-	(void)get_crypt_key(TRUE, TRUE);
+    (void)crypt_get_key(TRUE, TRUE);
 }
 #endif
 
diff --git a/src/fileio.c b/src/fileio.c
index 38dc259..a028b22 100644
--- a/src/fileio.c
+++ b/src/fileio.c
@@ -24,20 +24,6 @@
 #define BUFSIZE		8192	/* size of normal write buffer */
 #define SMBUFSIZE	256	/* size of emergency write buffer */
 
-#ifdef FEAT_CRYPT
-/* crypt_magic[0] is pkzip crypt, crypt_magic[1] is sha2+blowfish */
-static char	*crypt_magic[] = {"VimCrypt~01!", "VimCrypt~02!"};
-static char	crypt_magic_head[] = "VimCrypt~";
-# define CRYPT_MAGIC_LEN	12		/* must be multiple of 4! */
-
-/* For blowfish, after the magic header, we store 8 bytes of salt and then 8
- * bytes of seed (initialisation vector). */
-static int	crypt_salt_len[] = {0, 8};
-static int	crypt_seed_len[] = {0, 8};
-#define CRYPT_SALT_LEN_MAX 8
-#define CRYPT_SEED_LEN_MAX 8
-#endif
-
 /* Is there any system that doesn't have access()? */
 #define USE_MCH_ACCESS
 
@@ -55,7 +41,6 @@
 static void check_marks_read __ARGS((void));
 #endif
 #ifdef FEAT_CRYPT
-static int crypt_method_from_magic __ARGS((char *ptr, int len));
 static char_u *check_for_cryptkey __ARGS((char_u *cryptkey, char_u *ptr, long *sizep, off_t *filesizep, int newfile, char_u *fname, int *did_ask));
 #endif
 #ifdef UNIX
@@ -116,6 +101,9 @@
 #ifdef HAS_BW_FLAGS
     int		bw_flags;	/* FIO_ flags */
 #endif
+#ifdef FEAT_CRYPT
+    buf_T	*bw_buffer;	/* buffer being written */
+#endif
 #ifdef FEAT_MBYTE
     char_u	bw_rest[CONV_RESTLEN]; /* not converted bytes */
     int		bw_restlen;	/* nr of bytes in bw_rest[] */
@@ -250,7 +238,6 @@
 #ifdef FEAT_CRYPT
     char_u	*cryptkey = NULL;
     int		did_ask_for_key = FALSE;
-    int		crypt_method_used;
 #endif
 #ifdef FEAT_PERSISTENT_UNDO
     context_sha256_T sha_ctx;
@@ -966,13 +953,6 @@
 #endif
     }
 
-#ifdef FEAT_CRYPT
-    if (cryptkey != NULL)
-	/* Need to reset the state, but keep the key, don't want to ask for it
-	 * again. */
-	crypt_pop_state();
-#endif
-
     /*
      * When retrying with another "fenc" and the first time "fileformat"
      * will be reset.
@@ -1175,6 +1155,15 @@
 	if (read_undo_file)
 	    sha256_start(&sha_ctx);
 #endif
+#ifdef FEAT_CRYPT
+	if (curbuf->b_cryptstate != NULL)
+	{
+	    /* Need to free the state, but keep the key, don't want to ask for
+	     * it again. */
+	    crypt_free_state(curbuf->b_cryptstate);
+	    curbuf->b_cryptstate = NULL;
+	}
+#endif
     }
 
     while (!error && !got_int)
@@ -1339,6 +1328,76 @@
 		    size = read_eintr(fd, ptr, size);
 		}
 
+#ifdef FEAT_CRYPT
+		/*
+		 * At start of file: Check for magic number of encryption.
+		 */
+		if (filesize == 0 && size > 0)
+		    cryptkey = check_for_cryptkey(cryptkey, ptr, &size,
+						  &filesize, newfile, sfname,
+						  &did_ask_for_key);
+		/*
+		 * Decrypt the read bytes.  This is done before checking for
+		 * EOF because the crypt layer may be buffering.
+		 */
+		if (cryptkey != NULL && size > 0)
+		{
+		    if (crypt_works_inplace(curbuf->b_cryptstate))
+		    {
+			crypt_decode_inplace(curbuf->b_cryptstate, ptr, size);
+		    }
+		    else
+		    {
+			char_u	*newptr = NULL;
+			int	decrypted_size;
+
+			decrypted_size = crypt_decode_alloc(
+				    curbuf->b_cryptstate, ptr, size, &newptr);
+
+			/* If the crypt layer is buffering, not producing
+			 * anything yet, need to read more. */
+			if (size > 0 && decrypted_size == 0)
+			    continue;
+
+			if (linerest == 0)
+			{
+			    /* Simple case: reuse returned buffer (may be
+			     * NULL, checked later). */
+			    new_buffer = newptr;
+			}
+			else
+			{
+			    long_u	new_size;
+
+			    /* Need new buffer to add bytes carried over. */
+			    new_size = (long_u)(decrypted_size + linerest + 1);
+			    new_buffer = lalloc(new_size, FALSE);
+			    if (new_buffer == NULL)
+			    {
+				do_outofmem_msg(new_size);
+				error = TRUE;
+				break;
+			    }
+
+			    mch_memmove(new_buffer, buffer, linerest);
+			    if (newptr != NULL)
+				mch_memmove(new_buffer + linerest, newptr,
+							      decrypted_size);
+			}
+
+			if (new_buffer != NULL)
+			{
+			    vim_free(buffer);
+			    buffer = new_buffer;
+			    new_buffer = NULL;
+			    line_start = buffer;
+			    ptr = buffer + linerest;
+			}
+			size = decrypted_size;
+		    }
+		}
+#endif
+
 		if (size <= 0)
 		{
 		    if (size < 0)		    /* read error */
@@ -1403,21 +1462,6 @@
 		    }
 #endif
 		}
-
-#ifdef FEAT_CRYPT
-		/*
-		 * At start of file: Check for magic number of encryption.
-		 */
-		if (filesize == 0)
-		    cryptkey = check_for_cryptkey(cryptkey, ptr, &size,
-					&filesize, newfile, sfname,
-					&did_ask_for_key);
-		/*
-		 * Decrypt the read bytes.
-		 */
-		if (cryptkey != NULL && size > 0)
-		    crypt_decode(ptr, size);
-#endif
 	    }
 	    skip_read = FALSE;
 
@@ -1430,10 +1474,9 @@
 	     */
 	    if ((filesize == 0
 # ifdef FEAT_CRYPT
-		   || (filesize == (CRYPT_MAGIC_LEN
-					   + crypt_salt_len[use_crypt_method]
-					   + crypt_seed_len[use_crypt_method])
-							  && cryptkey != NULL)
+		   || (cryptkey != NULL
+			&& filesize == crypt_get_header_len(
+						 crypt_get_method_nr(curbuf)))
 # endif
 		       )
 		    && (fio_flags == FIO_UCSBOM
@@ -2262,15 +2305,15 @@
 	save_file_ff(curbuf);		/* remember the current file format */
 
 #ifdef FEAT_CRYPT
-    crypt_method_used = use_crypt_method;
-    if (cryptkey != NULL)
+    if (curbuf->b_cryptstate != NULL)
     {
-	crypt_pop_state();
-	if (cryptkey != curbuf->b_p_key)
-	    free_crypt_key(cryptkey);
-	/* don't set cryptkey to NULL, it's used below as a flag that
-	 * encryption was used */
+	crypt_free_state(curbuf->b_cryptstate);
+	curbuf->b_cryptstate = NULL;
     }
+    if (cryptkey != NULL && cryptkey != curbuf->b_p_key)
+	crypt_free_key(cryptkey);
+    /* Don't set cryptkey to NULL, it's used below as a flag that
+     * encryption was used. */
 #endif
 
 #ifdef FEAT_MBYTE
@@ -2457,10 +2500,7 @@
 #ifdef FEAT_CRYPT
 	    if (cryptkey != NULL)
 	    {
-		if (crypt_method_used == 1)
-		    STRCAT(IObuff, _("[blowfish]"));
-		else
-		    STRCAT(IObuff, _("[crypted]"));
+		crypt_append_msg(curbuf);
 		c = TRUE;
 	    }
 #endif
@@ -2489,9 +2529,7 @@
 #ifdef FEAT_CRYPT
 	    if (cryptkey != NULL)
 		msg_add_lines(c, (long)linecnt, filesize
-			- CRYPT_MAGIC_LEN
-			- crypt_salt_len[use_crypt_method]
-			- crypt_seed_len[use_crypt_method]);
+			 - crypt_get_header_len(crypt_get_method_nr(curbuf)));
 	    else
 #endif
 		msg_add_lines(c, (long)linecnt, filesize);
@@ -2882,33 +2920,6 @@
 
 #if defined(FEAT_CRYPT) || defined(PROTO)
 /*
- * Get the crypt method used for a file from "ptr[len]", the magic text at the
- * start of the file.
- * Returns -1 when no encryption used.
- */
-    static int
-crypt_method_from_magic(ptr, len)
-    char  *ptr;
-    int   len;
-{
-    int i;
-
-    for (i = 0; i < (int)(sizeof(crypt_magic) / sizeof(crypt_magic[0])); i++)
-    {
-	if (len < (CRYPT_MAGIC_LEN + crypt_salt_len[i] + crypt_seed_len[i]))
-	    continue;
-	if (memcmp(ptr, crypt_magic[i], CRYPT_MAGIC_LEN) == 0)
-	    return i;
-    }
-
-    i = (int)STRLEN(crypt_magic_head);
-    if (len >= i && memcmp(ptr, crypt_magic_head, i) == 0)
-	EMSG(_("E821: File is encrypted with unknown method"));
-
-    return -1;
-}
-
-/*
  * Check for magic number used for encryption.  Applies to the current buffer.
  * If found, the magic number is removed from ptr[*sizep] and *sizep and
  * *filesizep are updated.
@@ -2924,7 +2935,7 @@
     char_u	*fname;		/* file name to display */
     int		*did_ask;	/* flag: whether already asked for key */
 {
-    int method = crypt_method_from_magic((char *)ptr, *sizep);
+    int method = crypt_method_nr_from_magic((char *)ptr, *sizep);
     int b_p_ro = curbuf->b_p_ro;
 
     if (method >= 0)
@@ -2933,9 +2944,7 @@
 	 * Avoids accidentally overwriting the file with garbage. */
 	curbuf->b_p_ro = TRUE;
 
-	set_crypt_method(curbuf, method);
-	if (method > 0)
-	    (void)blowfish_self_test();
+	crypt_set_cm_option(curbuf, method);
 	if (cryptkey == NULL && !*did_ask)
 	{
 	    if (*curbuf->b_p_key)
@@ -2948,7 +2957,7 @@
 		 * Happens when retrying to detect encoding. */
 		smsg((char_u *)_(need_key_msg), fname);
 		msg_scroll = TRUE;
-		cryptkey = get_crypt_key(newfile, FALSE);
+		cryptkey = crypt_get_key(newfile, FALSE);
 		*did_ask = TRUE;
 
 		/* check if empty key entered */
@@ -2963,24 +2972,18 @@
 
 	if (cryptkey != NULL)
 	{
-	    int seed_len = crypt_seed_len[method];
-	    int salt_len = crypt_salt_len[method];
+	    int header_len;
 
-	    crypt_push_state();
-	    use_crypt_method = method;
-	    if (method == 0)
-		crypt_init_keys(cryptkey);
-	    else
-	    {
-		bf_key_init(cryptkey, ptr + CRYPT_MAGIC_LEN, salt_len);
-		bf_cfb_init(ptr + CRYPT_MAGIC_LEN + salt_len, seed_len);
-	    }
+	    curbuf->b_cryptstate = crypt_create_from_header(
+						       method, cryptkey, ptr);
+	    crypt_set_cm_option(curbuf, method);
 
-	    /* Remove magic number from the text */
-	    *filesizep += CRYPT_MAGIC_LEN + salt_len + seed_len;
-	    *sizep -= CRYPT_MAGIC_LEN + salt_len + seed_len;
-	    mch_memmove(ptr, ptr + CRYPT_MAGIC_LEN + salt_len + seed_len,
-							      (size_t)*sizep);
+	    /* Remove cryptmethod specific header from the text. */
+	    header_len = crypt_get_header_len(method);
+	    *filesizep += header_len;
+	    *sizep -= header_len;
+	    mch_memmove(ptr, ptr + header_len, (size_t)*sizep);
+
 	    /* Restore the read-only flag. */
 	    curbuf->b_p_ro = b_p_ro;
 	}
@@ -2992,85 +2995,6 @@
 
     return cryptkey;
 }
-
-/*
- * Check for magic number used for encryption.  Applies to the current buffer.
- * If found and decryption is possible returns OK;
- */
-    int
-prepare_crypt_read(fp)
-    FILE	*fp;
-{
-    int		method;
-    char_u	buffer[CRYPT_MAGIC_LEN + CRYPT_SALT_LEN_MAX
-						    + CRYPT_SEED_LEN_MAX + 2];
-
-    if (fread(buffer, CRYPT_MAGIC_LEN, 1, fp) != 1)
-	return FAIL;
-    method = crypt_method_from_magic((char *)buffer,
-					CRYPT_MAGIC_LEN +
-					CRYPT_SEED_LEN_MAX +
-					CRYPT_SALT_LEN_MAX);
-    if (method < 0 || method != get_crypt_method(curbuf))
-	return FAIL;
-
-    crypt_push_state();
-    if (method == 0)
-	crypt_init_keys(curbuf->b_p_key);
-    else
-    {
-	int salt_len = crypt_salt_len[method];
-	int seed_len = crypt_seed_len[method];
-
-	if (fread(buffer, salt_len + seed_len, 1, fp) != 1)
-	    return FAIL;
-	bf_key_init(curbuf->b_p_key, buffer, salt_len);
-	bf_cfb_init(buffer + salt_len, seed_len);
-    }
-    return OK;
-}
-
-/*
- * Prepare for writing encrypted bytes for buffer "buf".
- * Returns a pointer to an allocated header of length "*lenp".
- * When out of memory returns NULL.
- * Otherwise calls crypt_push_state(), call crypt_pop_state() later.
- */
-    char_u *
-prepare_crypt_write(buf, lenp)
-    buf_T *buf;
-    int   *lenp;
-{
-    char_u  *header;
-    int	    seed_len = crypt_seed_len[get_crypt_method(buf)];
-    int     salt_len = crypt_salt_len[get_crypt_method(buf)];
-    char_u  *salt;
-    char_u  *seed;
-
-    header = alloc_clear(CRYPT_MAGIC_LEN + CRYPT_SALT_LEN_MAX
-						    + CRYPT_SEED_LEN_MAX + 2);
-    if (header != NULL)
-    {
-	crypt_push_state();
-	use_crypt_method = get_crypt_method(buf);  /* select zip or blowfish */
-	vim_strncpy(header, (char_u *)crypt_magic[use_crypt_method],
-							     CRYPT_MAGIC_LEN);
-	if (use_crypt_method == 0)
-	    crypt_init_keys(buf->b_p_key);
-	else
-	{
-	    /* Using blowfish, add salt and seed. */
-	    salt = header + CRYPT_MAGIC_LEN;
-	    seed = salt + salt_len;
-	    sha2_seed(salt, salt_len, seed, seed_len);
-	    bf_key_init(buf->b_p_key, salt, salt_len);
-	    bf_cfb_init(seed, seed_len);
-	}
-    }
-    *lenp = CRYPT_MAGIC_LEN + salt_len + seed_len;
-    return header;
-}
-
 #endif  /* FEAT_CRYPT */
 
 #ifdef UNIX
@@ -3224,9 +3148,6 @@
     int		    write_undo_file = FALSE;
     context_sha256_T sha_ctx;
 #endif
-#ifdef FEAT_CRYPT
-    int		    crypt_method_used;
-#endif
 
     if (fname == NULL || *fname == NUL)	/* safety check */
 	return FAIL;
@@ -3262,6 +3183,9 @@
     write_info.bw_iconv_fd = (iconv_t)-1;
 # endif
 #endif
+#ifdef FEAT_CRYPT
+    write_info.bw_buffer = buf;
+#endif
 
     /* After writing a file changedtick changes but we don't want to display
      * the line. */
@@ -4505,17 +4429,17 @@
 #ifdef FEAT_CRYPT
     if (*buf->b_p_key != NUL && !filtering)
     {
-	char_u *header;
-	int    header_len;
+	char_u		*header;
+	int		header_len;
 
-	header = prepare_crypt_write(buf, &header_len);
-	if (header == NULL)
+	buf->b_cryptstate = crypt_create_for_writing(crypt_get_method_nr(buf),
+					  buf->b_p_key, &header, &header_len);
+	if (buf->b_cryptstate == NULL || header == NULL)
 	    end = 0;
 	else
 	{
-	    /* Write magic number, so that Vim knows that this file is
-	     * encrypted when reading it again.  This also undergoes utf-8 to
-	     * ucs-2/4 conversion when needed. */
+	    /* Write magic number, so that Vim knows how this file is
+	     * encrypted when reading it back. */
 	    write_info.bw_buf = header;
 	    write_info.bw_len = header_len;
 	    write_info.bw_flags = FIO_NOCONVERT;
@@ -4769,12 +4693,13 @@
 	mch_set_acl(wfname, acl);
 #endif
 #ifdef FEAT_CRYPT
-    crypt_method_used = use_crypt_method;
-    if (wb_flags & FIO_ENCRYPTED)
-	crypt_pop_state();
+    if (buf->b_cryptstate != NULL)
+    {
+	crypt_free_state(buf->b_cryptstate);
+	buf->b_cryptstate = NULL;
+    }
 #endif
 
-
 #if defined(FEAT_MBYTE) && defined(FEAT_EVAL)
     if (wfname != fname)
     {
@@ -4924,10 +4849,7 @@
 #ifdef FEAT_CRYPT
 	if (wb_flags & FIO_ENCRYPTED)
 	{
-	    if (crypt_method_used == 1)
-		STRCAT(IObuff, _("[blowfish]"));
-	    else
-		STRCAT(IObuff, _("[crypted]"));
+	    crypt_append_msg(buf);
 	    c = TRUE;
 	}
 #endif
@@ -5740,8 +5662,26 @@
 #endif /* FEAT_MBYTE */
 
 #ifdef FEAT_CRYPT
-    if (flags & FIO_ENCRYPTED)		/* encrypt the data */
-	crypt_encode(buf, len, buf);
+    if (flags & FIO_ENCRYPTED)
+    {
+	/* Encrypt the data. Do it in-place if possible, otherwise use an
+	 * allocated buffer. */
+	if (crypt_works_inplace(ip->bw_buffer->b_cryptstate))
+	{
+	    crypt_encode_inplace(ip->bw_buffer->b_cryptstate, buf, len);
+	}
+	else
+	{
+	    char_u *outbuf;
+
+	    len = crypt_encode_alloc(curbuf->b_cryptstate, buf, len, &outbuf);
+	    if (len == 0)
+		return OK;  /* Crypt layer is buffering, will flush later. */
+	    wlen = write_eintr(ip->bw_fd, outbuf, len);
+	    vim_free(outbuf);
+	    return (wlen < len) ? FAIL : OK;
+	}
+    }
 #endif
 
     wlen = write_eintr(ip->bw_fd, buf, len);
diff --git a/src/globals.h b/src/globals.h
index ce831f3..d1fdc33 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -105,10 +105,6 @@
 
 EXTERN int	screen_cleared INIT(= FALSE);	/* screen has been cleared */
 
-#ifdef FEAT_CRYPT
-EXTERN int      use_crypt_method INIT(= 0);
-#endif
-
 /*
  * When '$' is included in 'cpoptions' option set:
  * When a change command is given that deletes only part of a line, a dollar
diff --git a/src/main.c b/src/main.c
index 9c92f7a..2fb2edc 100644
--- a/src/main.c
+++ b/src/main.c
@@ -846,8 +846,7 @@
 #ifdef FEAT_CRYPT
     if (params.ask_for_key)
     {
-	(void)blowfish_self_test();
-	(void)get_crypt_key(TRUE, TRUE);
+	(void)crypt_get_key(TRUE, TRUE);
 	TIME_MSG("getting crypt key");
     }
 #endif
diff --git a/src/memline.c b/src/memline.c
index fb438d2..a5053f0 100644
--- a/src/memline.c
+++ b/src/memline.c
@@ -63,6 +63,15 @@
 #define BLOCK0_ID1     '0'		    /* block 0 id 1 */
 #define BLOCK0_ID1_C0  'c'		    /* block 0 id 1 'cm' 0 */
 #define BLOCK0_ID1_C1  'C'		    /* block 0 id 1 'cm' 1 */
+#define BLOCK0_ID1_C2  'd'		    /* block 0 id 1 'cm' 2 */
+
+#if defined(FEAT_CRYPT)
+static int id1_codes[] = {
+    BLOCK0_ID1_C0,  /* CRYPT_M_ZIP */
+    BLOCK0_ID1_C1,  /* CRYPT_M_BF */
+    BLOCK0_ID1_C2,  /* CRYPT_M_BF2 */
+};
+#endif
 
 /*
  * pointer to a block, used in a pointer block
@@ -151,7 +160,7 @@
 struct block0
 {
     char_u	b0_id[2];	/* id for block 0: BLOCK0_ID0 and BLOCK0_ID1,
-				 * BLOCK0_ID1_C0, BLOCK0_ID1_C1 */
+				 * BLOCK0_ID1_C0, BLOCK0_ID1_C1, etc. */
     char_u	b0_version[10];	/* Vim version string */
     char_u	b0_page_size[4];/* number of bytes per page */
     char_u	b0_mtime[4];	/* last modification time of file */
@@ -256,7 +265,7 @@
 static char_u *make_percent_swname __ARGS((char_u *dir, char_u *name));
 #endif
 #ifdef FEAT_CRYPT
-static void ml_crypt_prepare __ARGS((memfile_T *mfp, off_t offset, int reading));
+static cryptstate_T *ml_crypt_prepare __ARGS((memfile_T *mfp, off_t offset, int reading));
 #endif
 #ifdef FEAT_BYTEOFF
 static void ml_updatechunk __ARGS((buf_T *buf, long line, long len, int updtype));
@@ -359,8 +368,7 @@
 	b0p->b0_hname[B0_HNAME_SIZE - 1] = NUL;
 	long_to_char(mch_get_pid(), b0p->b0_pid);
 #ifdef FEAT_CRYPT
-	if (*buf->b_p_key != NUL)
-	    ml_set_b0_crypt(buf, b0p);
+	ml_set_b0_crypt(buf, b0p);
 #endif
     }
 
@@ -436,11 +444,11 @@
 	b0p->b0_id[1] = BLOCK0_ID1;
     else
     {
-	if (get_crypt_method(buf) == 0)
-	    b0p->b0_id[1] = BLOCK0_ID1_C0;
-	else
+	int method_nr = crypt_get_method_nr(buf);
+
+	b0p->b0_id[1] = id1_codes[method_nr];
+	if (method_nr > CRYPT_M_ZIP)
 	{
-	    b0p->b0_id[1] = BLOCK0_ID1_C1;
 	    /* Generate a seed and store it in block 0 and in the memfile. */
 	    sha2_seed(&b0p->b0_seed, MF_SEED_LEN, NULL, 0);
 	    mch_memmove(buf->b_ml.ml_mfp->mf_seed, &b0p->b0_seed, MF_SEED_LEN);
@@ -887,7 +895,8 @@
     if (b0p->b0_id[0] != BLOCK0_ID0
 	    || (b0p->b0_id[1] != BLOCK0_ID1
 		&& b0p->b0_id[1] != BLOCK0_ID1_C0
-		&& b0p->b0_id[1] != BLOCK0_ID1_C1)
+		&& b0p->b0_id[1] != BLOCK0_ID1_C1
+		&& b0p->b0_id[1] != BLOCK0_ID1_C2)
 	    )
 	return FAIL;
     return OK;
@@ -1255,14 +1264,12 @@
     }
 
 #ifdef FEAT_CRYPT
-    if (b0p->b0_id[1] == BLOCK0_ID1_C0)
-	b0_cm = 0;
-    else if (b0p->b0_id[1] == BLOCK0_ID1_C1)
-    {
-	b0_cm = 1;
+    for (i = 0; i < (int)(sizeof(id1_codes) / sizeof(int)); ++i)
+	if (id1_codes[i] == b0p->b0_id[1])
+	    b0_cm = i;
+    if (b0_cm > 0)
 	mch_memmove(mfp->mf_seed, &b0p->b0_seed, MF_SEED_LEN);
-    }
-    set_crypt_method(buf, b0_cm);
+    crypt_set_cm_option(buf, b0_cm < 0 ? 0 : b0_cm);
 #else
     if (b0p->b0_id[1] != BLOCK0_ID1)
     {
@@ -1389,7 +1396,7 @@
 	}
 	else
 	    smsg((char_u *)_(need_key_msg), fname_used);
-	buf->b_p_key = get_crypt_key(FALSE, FALSE);
+	buf->b_p_key = crypt_get_key(FALSE, FALSE);
 	if (buf->b_p_key == NULL)
 	    buf->b_p_key = curbuf->b_p_key;
 	else if (*buf->b_p_key == NUL)
@@ -4816,6 +4823,7 @@
     char_u	*text_start;
     char_u	*new_data;
     int		text_len;
+    cryptstate_T *state;
 
     if (dp->db_id != DATA_ID)
 	return data;
@@ -4831,10 +4839,9 @@
     mch_memmove(new_data, dp, head_end - (char_u *)dp);
 
     /* Encrypt the text. */
-    crypt_push_state();
-    ml_crypt_prepare(mfp, offset, FALSE);
-    crypt_encode(text_start, text_len, new_data + dp->db_txt_start);
-    crypt_pop_state();
+    state = ml_crypt_prepare(mfp, offset, FALSE);
+    crypt_encode(state, text_start, text_len, new_data + dp->db_txt_start);
+    crypt_free_state(state);
 
     /* Clear the gap. */
     if (head_end < text_start)
@@ -4857,6 +4864,7 @@
     char_u	*head_end;
     char_u	*text_start;
     int		text_len;
+    cryptstate_T *state;
 
     if (dp->db_id == DATA_ID)
     {
@@ -4869,17 +4877,17 @@
 	    return;  /* data was messed up */
 
 	/* Decrypt the text in place. */
-	crypt_push_state();
-	ml_crypt_prepare(mfp, offset, TRUE);
-	crypt_decode(text_start, text_len);
-	crypt_pop_state();
+	state = ml_crypt_prepare(mfp, offset, TRUE);
+	crypt_decode_inplace(state, text_start, text_len);
+	crypt_free_state(state);
     }
 }
 
 /*
  * Prepare for encryption/decryption, using the key, seed and offset.
+ * Return an allocated cryptstate_T *.
  */
-    static void
+    static cryptstate_T *
 ml_crypt_prepare(mfp, offset, reading)
     memfile_T	*mfp;
     off_t	offset;
@@ -4887,38 +4895,37 @@
 {
     buf_T	*buf = mfp->mf_buffer;
     char_u	salt[50];
-    int		method;
+    int		method_nr;
     char_u	*key;
     char_u	*seed;
 
     if (reading && mfp->mf_old_key != NULL)
     {
 	/* Reading back blocks with the previous key/method/seed. */
-	method = mfp->mf_old_cm;
+	method_nr = mfp->mf_old_cm;
 	key = mfp->mf_old_key;
 	seed = mfp->mf_old_seed;
     }
     else
     {
-	method = get_crypt_method(buf);
+	method_nr = crypt_get_method_nr(buf);
 	key = buf->b_p_key;
 	seed = mfp->mf_seed;
     }
 
-    use_crypt_method = method;  /* select pkzip or blowfish */
-    if (method == 0)
+    if (method_nr == CRYPT_M_ZIP)
     {
+	/* For PKzip: Append the offset to the key, so that we use a different
+	 * key for every block. */
 	vim_snprintf((char *)salt, sizeof(salt), "%s%ld", key, (long)offset);
-	crypt_init_keys(salt);
+	return crypt_create(method_nr, salt, NULL, 0, NULL, 0);
     }
-    else
-    {
-	/* Using blowfish, add salt and seed. We use the byte offset of the
-	 * block for the salt. */
-	vim_snprintf((char *)salt, sizeof(salt), "%ld", (long)offset);
-	bf_key_init(key, salt, (int)STRLEN(salt));
-	bf_cfb_init(seed, MF_SEED_LEN);
-    }
+
+    /* Using blowfish or better: add salt and seed. We use the byte offset
+     * of the block for the salt. */
+    vim_snprintf((char *)salt, sizeof(salt), "%ld", (long)offset);
+    return crypt_create(method_nr, key, salt, (int)STRLEN(salt),
+							   seed, MF_SEED_LEN);
 }
 
 #endif
diff --git a/src/misc2.c b/src/misc2.c
index 1a5b370..1f8878f 100644
--- a/src/misc2.c
+++ b/src/misc2.c
@@ -3803,322 +3803,6 @@
 #endif /* CURSOR_SHAPE */
 
 
-#ifdef FEAT_CRYPT
-/*
- * Optional encryption support.
- * Mohsin Ahmed, mosh@sasi.com, 98-09-24
- * Based on zip/crypt sources.
- *
- * NOTE FOR USA: Since 2000 exporting this code from the USA is allowed to
- * most countries.  There are a few exceptions, but that still should not be a
- * problem since this code was originally created in Europe and India.
- *
- * Blowfish addition originally made by Mohsin Ahmed,
- * http://www.cs.albany.edu/~mosh 2010-03-14
- * Based on blowfish by Bruce Schneier (http://www.schneier.com/blowfish.html)
- * and sha256 by Christophe Devine.
- */
-
-/* from zip.h */
-
-typedef unsigned short ush;	/* unsigned 16-bit value */
-typedef unsigned long  ulg;	/* unsigned 32-bit value */
-
-static void make_crc_tab __ARGS((void));
-
-static ulg crc_32_tab[256];
-
-/*
- * Fill the CRC table.
- */
-    static void
-make_crc_tab()
-{
-    ulg		s,t,v;
-    static int	done = FALSE;
-
-    if (done)
-	return;
-    for (t = 0; t < 256; t++)
-    {
-	v = t;
-	for (s = 0; s < 8; s++)
-	    v = (v >> 1) ^ ((v & 1) * (ulg)0xedb88320L);
-	crc_32_tab[t] = v;
-    }
-    done = TRUE;
-}
-
-#define CRC32(c, b) (crc_32_tab[((int)(c) ^ (b)) & 0xff] ^ ((c) >> 8))
-
-static ulg keys[3]; /* keys defining the pseudo-random sequence */
-
-/*
- * Return the next byte in the pseudo-random sequence.
- */
-#define DECRYPT_BYTE_ZIP(t) { \
-    ush temp; \
- \
-    temp = (ush)keys[2] | 2; \
-    t = (int)(((unsigned)(temp * (temp ^ 1U)) >> 8) & 0xff); \
-}
-
-/*
- * Update the encryption keys with the next byte of plain text.
- */
-#define UPDATE_KEYS_ZIP(c) { \
-    keys[0] = CRC32(keys[0], (c)); \
-    keys[1] += keys[0] & 0xff; \
-    keys[1] = keys[1] * 134775813L + 1; \
-    keys[2] = CRC32(keys[2], (int)(keys[1] >> 24)); \
-}
-
-static int crypt_busy = 0;
-static ulg saved_keys[3];
-static int saved_crypt_method;
-
-/*
- * Return int value for crypt method string:
- * 0 for "zip", the old method.  Also for any non-valid value.
- * 1 for "blowfish".
- */
-    int
-crypt_method_from_string(s)
-    char_u  *s;
-{
-    return *s == 'b' ? 1 : 0;
-}
-
-/*
- * Get the crypt method for buffer "buf" as a number.
- */
-    int
-get_crypt_method(buf)
-    buf_T *buf;
-{
-    return crypt_method_from_string(*buf->b_p_cm == NUL ? p_cm : buf->b_p_cm);
-}
-
-/*
- * Set the crypt method for buffer "buf" to "method" using the int value as
- * returned by crypt_method_from_string().
- */
-    void
-set_crypt_method(buf, method)
-    buf_T   *buf;
-    int	    method;
-{
-    free_string_option(buf->b_p_cm);
-    buf->b_p_cm = vim_strsave((char_u *)(method == 0 ? "zip" : "blowfish"));
-}
-
-/*
- * Prepare for initializing encryption.  If already doing encryption then save
- * the state.
- * Must always be called symmetrically with crypt_pop_state().
- */
-    void
-crypt_push_state()
-{
-    if (crypt_busy == 1)
-    {
-	/* save the state */
-	if (use_crypt_method == 0)
-	{
-	    saved_keys[0] = keys[0];
-	    saved_keys[1] = keys[1];
-	    saved_keys[2] = keys[2];
-	}
-	else
-	    bf_crypt_save();
-	saved_crypt_method = use_crypt_method;
-    }
-    else if (crypt_busy > 1)
-	EMSG2(_(e_intern2), "crypt_push_state()");
-    ++crypt_busy;
-}
-
-/*
- * End encryption.  If doing encryption before crypt_push_state() then restore
- * the saved state.
- * Must always be called symmetrically with crypt_push_state().
- */
-    void
-crypt_pop_state()
-{
-    --crypt_busy;
-    if (crypt_busy == 1)
-    {
-	use_crypt_method = saved_crypt_method;
-	if (use_crypt_method == 0)
-	{
-	    keys[0] = saved_keys[0];
-	    keys[1] = saved_keys[1];
-	    keys[2] = saved_keys[2];
-	}
-	else
-	    bf_crypt_restore();
-    }
-}
-
-/*
- * Encrypt "from[len]" into "to[len]".
- * "from" and "to" can be equal to encrypt in place.
- */
-    void
-crypt_encode(from, len, to)
-    char_u	*from;
-    size_t	len;
-    char_u	*to;
-{
-    size_t	i;
-    int		ztemp, t;
-
-    if (use_crypt_method == 0)
-	for (i = 0; i < len; ++i)
-	{
-	    ztemp = from[i];
-	    DECRYPT_BYTE_ZIP(t);
-	    UPDATE_KEYS_ZIP(ztemp);
-	    to[i] = t ^ ztemp;
-	}
-    else
-	bf_crypt_encode(from, len, to);
-}
-
-/*
- * Decrypt "ptr[len]" in place.
- */
-    void
-crypt_decode(ptr, len)
-    char_u	*ptr;
-    long	len;
-{
-    char_u *p;
-
-    if (use_crypt_method == 0)
-	for (p = ptr; p < ptr + len; ++p)
-	{
-	    ush temp;
-
-	    temp = (ush)keys[2] | 2;
-	    temp = (int)(((unsigned)(temp * (temp ^ 1U)) >> 8) & 0xff);
-	    UPDATE_KEYS_ZIP(*p ^= temp);
-	}
-    else
-	bf_crypt_decode(ptr, len);
-}
-
-/*
- * Initialize the encryption keys and the random header according to
- * the given password.
- * If "passwd" is NULL or empty, don't do anything.
- */
-    void
-crypt_init_keys(passwd)
-    char_u *passwd;		/* password string with which to modify keys */
-{
-    if (passwd != NULL && *passwd != NUL)
-    {
-	if (use_crypt_method == 0)
-	{
-	    char_u *p;
-
-	    make_crc_tab();
-	    keys[0] = 305419896L;
-	    keys[1] = 591751049L;
-	    keys[2] = 878082192L;
-	    for (p = passwd; *p!= NUL; ++p)
-	    {
-		UPDATE_KEYS_ZIP((int)*p);
-	    }
-	}
-	else
-	    bf_crypt_init_keys(passwd);
-    }
-}
-
-/*
- * Free an allocated crypt key.  Clear the text to make sure it doesn't stay
- * in memory anywhere.
- */
-    void
-free_crypt_key(key)
-    char_u *key;
-{
-    char_u *p;
-
-    if (key != NULL)
-    {
-	for (p = key; *p != NUL; ++p)
-	    *p = 0;
-	vim_free(key);
-    }
-}
-
-/*
- * Ask the user for a crypt key.
- * When "store" is TRUE, the new key is stored in the 'key' option, and the
- * 'key' option value is returned: Don't free it.
- * When "store" is FALSE, the typed key is returned in allocated memory.
- * Returns NULL on failure.
- */
-    char_u *
-get_crypt_key(store, twice)
-    int		store;
-    int		twice;	    /* Ask for the key twice. */
-{
-    char_u	*p1, *p2 = NULL;
-    int		round;
-
-    for (round = 0; ; ++round)
-    {
-	cmdline_star = TRUE;
-	cmdline_row = msg_row;
-	p1 = getcmdline_prompt(NUL, round == 0
-		? (char_u *)_("Enter encryption key: ")
-		: (char_u *)_("Enter same key again: "), 0, EXPAND_NOTHING,
-		NULL);
-	cmdline_star = FALSE;
-
-	if (p1 == NULL)
-	    break;
-
-	if (round == twice)
-	{
-	    if (p2 != NULL && STRCMP(p1, p2) != 0)
-	    {
-		MSG(_("Keys don't match!"));
-		free_crypt_key(p1);
-		free_crypt_key(p2);
-		p2 = NULL;
-		round = -1;		/* do it again */
-		continue;
-	    }
-
-	    if (store)
-	    {
-		set_option_value((char_u *)"key", 0L, p1, OPT_LOCAL);
-		free_crypt_key(p1);
-		p1 = curbuf->b_p_key;
-	    }
-	    break;
-	}
-	p2 = p1;
-    }
-
-    /* since the user typed this, no need to wait for return */
-    if (msg_didout)
-	msg_putchar('\n');
-    need_wait_return = FALSE;
-    msg_didout = FALSE;
-
-    free_crypt_key(p2);
-    return p1;
-}
-
-#endif /* FEAT_CRYPT */
-
 /* TODO: make some #ifdef for this */
 /*--------[ file searching ]-------------------------------------------------*/
 /*
@@ -6588,8 +6272,23 @@
     FILE	*fd;
     time_t	the_time;
 {
+    char_u	buf[8];
+
+    time_to_bytes(the_time, buf);
+    fwrite(buf, (size_t)8, (size_t)1, fd);
+}
+
+/*
+ * Write time_t to "buf[8]".
+ */
+    void
+time_to_bytes(the_time, buf)
+    time_t	the_time;
+    char_u	*buf;
+{
     int		c;
     int		i;
+    int		bi = 0;
     time_t	wtime = the_time;
 
     /* time_t can be up to 8 bytes in size, more than long_u, thus we
@@ -6603,7 +6302,7 @@
     {
 	if (i + 1 > (int)sizeof(time_t))
 	    /* ">>" doesn't work well when shifting more bits than avail */
-	    putc(0, fd);
+	    buf[bi++] = 0;
 	else
 	{
 #if defined(SIZEOF_TIME_T) && SIZEOF_TIME_T > 4
@@ -6611,7 +6310,7 @@
 #else
 	    c = (int)((long_u)wtime >> (i * 8));
 #endif
-	    putc(c, fd);
+	    buf[bi++] = c;
 	}
     }
 }
diff --git a/src/option.c b/src/option.c
index 3e6164e..62556b6 100644
--- a/src/option.c
+++ b/src/option.c
@@ -2989,7 +2989,7 @@
 static char *(p_nf_values[]) = {"octal", "hex", "alpha", NULL};
 static char *(p_ff_values[]) = {FF_UNIX, FF_DOS, FF_MAC, NULL};
 #ifdef FEAT_CRYPT
-static char *(p_cm_values[]) = {"zip", "blowfish", NULL};
+static char *(p_cm_values[]) = {"zip", "blowfish", "blowfish2", NULL};
 #endif
 #ifdef FEAT_CMDL_COMPL
 static char *(p_wop_values[]) = {"tagfile", NULL};
@@ -6140,7 +6140,7 @@
 # endif
 	if (STRCMP(curbuf->b_p_key, oldval) != 0)
 	    /* Need to update the swapfile. */
-	    ml_set_crypt_key(curbuf, oldval, get_crypt_method(curbuf));
+	    ml_set_crypt_key(curbuf, oldval, crypt_get_method_nr(curbuf));
     }
 
     else if (gvarp == &p_cm)
@@ -6151,7 +6151,7 @@
 	    p = p_cm;
 	if (check_opt_strings(p, p_cm_values, TRUE) != OK)
 	    errmsg = e_invarg;
-	else if (get_crypt_method(curbuf) > 0 && blowfish_self_test() == FAIL)
+	else if (crypt_self_test() == FAIL)
 	    errmsg = e_invarg;
 	else
 	{
@@ -6177,7 +6177,7 @@
 		p = curbuf->b_p_cm;
 	    if (STRCMP(s, p) != 0)
 		ml_set_crypt_key(curbuf, curbuf->b_p_key,
-						 crypt_method_from_string(s));
+						crypt_method_nr_from_name(s));
 
 	    /* If the global value changes need to update the swapfile for all
 	     * buffers using that value. */
@@ -6188,7 +6188,7 @@
 		for (buf = firstbuf; buf != NULL; buf = buf->b_next)
 		    if (buf != curbuf && *buf->b_p_cm == NUL)
 			ml_set_crypt_key(buf, buf->b_p_key,
-					    crypt_method_from_string(oldval));
+					   crypt_method_nr_from_name(oldval));
 	    }
 	}
     }
diff --git a/src/proto.h b/src/proto.h
index 191ecd8..2b08eb3 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -70,6 +70,8 @@
 
 # ifdef FEAT_CRYPT
 #  include "blowfish.pro"
+#  include "crypt.pro"
+#  include "crypt_zip.pro"
 # endif
 # include "buffer.pro"
 # include "charset.pro"
diff --git a/src/proto/blowfish.pro b/src/proto/blowfish.pro
index 4d64e10..66d029c 100644
--- a/src/proto/blowfish.pro
+++ b/src/proto/blowfish.pro
@@ -1,10 +1,6 @@
 /* blowfish.c */
-void bf_key_init __ARGS((char_u *password, char_u *salt, int salt_len));
-void bf_cfb_init __ARGS((char_u *iv, int iv_len));
-void bf_crypt_encode __ARGS((char_u *from, size_t len, char_u *to));
-void bf_crypt_decode __ARGS((char_u *ptr, long len));
-void bf_crypt_init_keys __ARGS((char_u *passwd));
-void bf_crypt_save __ARGS((void));
-void bf_crypt_restore __ARGS((void));
+void crypt_blowfish_encode __ARGS((cryptstate_T *state, char_u *from, size_t len, char_u *to));
+void crypt_blowfish_decode __ARGS((cryptstate_T *state, char_u *from, size_t len, char_u *to));
+void crypt_blowfish_init __ARGS((cryptstate_T *state, char_u *key, char_u *salt, int salt_len, char_u *seed, int seed_len));
 int blowfish_self_test __ARGS((void));
 /* vim: set ft=c : */
diff --git a/src/proto/crypt.pro b/src/proto/crypt.pro
new file mode 100644
index 0000000..d61df71
--- /dev/null
+++ b/src/proto/crypt.pro
@@ -0,0 +1,24 @@
+/* crypt.c */
+int crypt_method_nr_from_name __ARGS((char_u *name));
+int crypt_method_nr_from_magic __ARGS((char *ptr, int len));
+int crypt_works_inplace __ARGS((cryptstate_T *state));
+int crypt_get_method_nr __ARGS((buf_T *buf));
+int crypt_whole_undofile __ARGS((int method_nr));
+int crypt_get_header_len __ARGS((int method_nr));
+void crypt_set_cm_option __ARGS((buf_T *buf, int method_nr));
+int crypt_self_test __ARGS((void));
+cryptstate_T *crypt_create __ARGS((int method_nr, char_u *key, char_u *salt, int salt_len, char_u *seed, int seed_len));
+cryptstate_T *crypt_create_from_header __ARGS((int method_nr, char_u *key, char_u *header));
+cryptstate_T *crypt_create_from_file __ARGS((FILE *fp, char_u *key));
+cryptstate_T *crypt_create_for_writing __ARGS((int method_nr, char_u *key, char_u **header, int *header_len));
+void crypt_free_state __ARGS((cryptstate_T *state));
+long crypt_encode_alloc __ARGS((cryptstate_T *state, char_u *from, size_t len, char_u **newptr));
+long crypt_decode_alloc __ARGS((cryptstate_T *state, char_u *ptr, long len, char_u **newptr));
+void crypt_encode __ARGS((cryptstate_T *state, char_u *from, size_t len, char_u *to));
+void crypt_decode __ARGS((cryptstate_T *state, char_u *from, size_t len, char_u *to));
+void crypt_encode_inplace __ARGS((cryptstate_T *state, char_u *buf, size_t len));
+void crypt_decode_inplace __ARGS((cryptstate_T *state, char_u *buf, size_t len));
+void crypt_free_key __ARGS((char_u *key));
+char_u *crypt_get_key __ARGS((int store, int twice));
+void crypt_append_msg __ARGS((buf_T *buf));
+/* vim: set ft=c : */
diff --git a/src/proto/crypt_zip.pro b/src/proto/crypt_zip.pro
new file mode 100644
index 0000000..5f4e137
--- /dev/null
+++ b/src/proto/crypt_zip.pro
@@ -0,0 +1,5 @@
+/* crypt_zip.c */
+void crypt_zip_init __ARGS((cryptstate_T *state, char_u *key, char_u *salt, int salt_len, char_u *seed, int seed_len));
+void crypt_zip_encode __ARGS((cryptstate_T *state, char_u *from, size_t len, char_u *to));
+void crypt_zip_decode __ARGS((cryptstate_T *state, char_u *from, size_t len, char_u *to));
+/* vim: set ft=c : */
diff --git a/src/proto/fileio.pro b/src/proto/fileio.pro
index 7aa99cf..0d532f4 100644
--- a/src/proto/fileio.pro
+++ b/src/proto/fileio.pro
@@ -4,8 +4,6 @@
 int prep_exarg __ARGS((exarg_T *eap, buf_T *buf));
 void set_file_options __ARGS((int set_options, exarg_T *eap));
 void set_forced_fenc __ARGS((exarg_T *eap));
-int prepare_crypt_read __ARGS((FILE *fp));
-char_u *prepare_crypt_write __ARGS((buf_T *buf, int *lenp));
 int check_file_readonly __ARGS((char_u *fname, int perm));
 int buf_write __ARGS((buf_T *buf, char_u *fname, char_u *sfname, linenr_T start, linenr_T end, exarg_T *eap, int append, int forceit, int reset_changed, int filtering));
 void msg_add_fname __ARGS((buf_T *buf, char_u *fname));
diff --git a/src/proto/misc2.pro b/src/proto/misc2.pro
index 4fd4573..b2f72d8 100644
--- a/src/proto/misc2.pro
+++ b/src/proto/misc2.pro
@@ -84,16 +84,6 @@
 char_u *parse_shape_opt __ARGS((int what));
 int get_shape_idx __ARGS((int mouse));
 void update_mouseshape __ARGS((int shape_idx));
-int crypt_method_from_string __ARGS((char_u *s));
-int get_crypt_method __ARGS((buf_T *buf));
-void set_crypt_method __ARGS((buf_T *buf, int method));
-void crypt_push_state __ARGS((void));
-void crypt_pop_state __ARGS((void));
-void crypt_encode __ARGS((char_u *from, size_t len, char_u *to));
-void crypt_decode __ARGS((char_u *ptr, long len));
-void crypt_init_keys __ARGS((char_u *passwd));
-void free_crypt_key __ARGS((char_u *key));
-char_u *get_crypt_key __ARGS((int store, int twice));
 void *vim_findfile_init __ARGS((char_u *path, char_u *filename, char_u *stopdirs, int level, int free_visited, int find_what, void *search_ctx_arg, int tagfile, char_u *rel_fname));
 char_u *vim_findfile_stopdir __ARGS((char_u *buf));
 void vim_findfile_cleanup __ARGS((void *ctx));
@@ -116,5 +106,6 @@
 char_u *read_string __ARGS((FILE *fd, int cnt));
 int put_bytes __ARGS((FILE *fd, long_u nr, int len));
 void put_time __ARGS((FILE *fd, time_t the_time));
+void time_to_bytes __ARGS((time_t the_time, char_u *buf));
 int has_non_ascii __ARGS((char_u *s));
 /* vim: set ft=c : */
diff --git a/src/structs.h b/src/structs.h
index 1382380..441fcaa 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -1251,6 +1251,24 @@
 } syn_time_T;
 #endif
 
+#ifdef FEAT_CRYPT
+/*
+ * Structure to hold the type of encryption and the state of encryption or
+ * decryption.
+ */
+typedef struct {
+    int	    method_nr;
+    void    *method_state;  /* method-specific state information */
+} cryptstate_T;
+
+/* values for method_nr */
+# define CRYPT_M_ZIP	0
+# define CRYPT_M_BF	1
+# define CRYPT_M_BF2	2
+# define CRYPT_M_COUNT	3 /* number of crypt methods */
+#endif
+
+
 /*
  * These are items normally related to a buffer.  But when using ":ownsyntax"
  * a window may have its own instance.
@@ -1778,7 +1796,12 @@
     int		b_was_netbeans_file;/* TRUE if b_netbeans_file was once set */
 #endif
 
-};
+#ifdef FEAT_CRYPT
+    cryptstate_T *b_cryptstate;	/* Encryption state while reading or writing
+				 * the file. NULL when not using encryption. */
+#endif
+
+}; /* file_buffer */
 
 
 #ifdef FEAT_DIFF
diff --git a/src/testdir/test71.in b/src/testdir/test71.in
index 155fd41..944b69d 100644
--- a/src/testdir/test71.in
+++ b/src/testdir/test71.in
@@ -13,6 +13,8 @@
 :let cm0_bytes = getline('.', '.')
 :/^start of cm=blowfish bytes/+1
 :let cm1_bytes = getline('.', '.')
+:/^start of cm=blowfish2 bytes/+1
+:let cm2_bytes = getline('.', '.')
 :bwipe!
 :call append(0, text_lines)
 :$d
@@ -36,6 +38,18 @@
 :e Xtestfile
 barfoo
 :let cm1_read_back = getline('.', '$')
+:set key=
+:set cryptmethod=blowfish2
+:" If the blowfish test fails 'cryptmethod' will be 'zip' now.
+:%s/^/\=&cryptmethod == 'blowfish2' ? "OK " : "blowfish test failed "/
+:X
+bar2foo
+bar2foo
+:w! Xtestfile
+:bwipe!
+:e Xtestfile
+bar2foo
+:let cm2_read_back = getline('.', '$')
 :bwipe!
 :set bin noeol key=
 :call append(0, cm0_bytes)
@@ -57,7 +71,20 @@
 :set nobin
 :e Xtestfile
 barbar
+:let cm1_read_bin = getline('.', '$')
+:bwipe!
+:set bin noeol key=
+:call append(0, cm2_bytes)
+:$d
+:set fenc=latin1
+:w! Xtestfile
+:bwipe!
+:set nobin
+:e Xtestfile
+barburp
+:call append(0, cm1_read_bin)
 :call append(0, cm0_read_bin)
+:call append(0, cm2_read_back)
 :call append(0, cm1_read_back)
 :call append(0, cm0_read_back)
 :set key= fenc=latin1
diff --git a/src/testdir/test71.ok b/src/testdir/test71.ok
index 24652c4..de1b0ab 100644
--- a/src/testdir/test71.ok
+++ b/src/testdir/test71.ok
@@ -4,7 +4,12 @@
 OK 01234567890123456789012345678901234567
 OK line 2  foo bar blah
 OK line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+OK OK 01234567890123456789012345678901234567
+OK OK line 2  foo bar blah
+OK OK line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
 1234567890
 aábbccddeëff
 asdfasdfasdf
 0001112223333
+abcdefghijklmnopqrstuvwxyz
+!@#$%^&*()_+=-`~
diff --git a/src/testdir/test71a.in b/src/testdir/test71a.in
index 85bd22c..e79a398 100644
--- a/src/testdir/test71a.in
+++ b/src/testdir/test71a.in
Binary files differ
diff --git a/src/testdir/test72.in b/src/testdir/test72.in
index 3b3a6a4..031edbf 100644
--- a/src/testdir/test72.in
+++ b/src/testdir/test72.in
@@ -81,6 +81,7 @@
 :"
 :" With encryption, cryptmethod=blowfish
 :e! Xtestfile
+rubbish
 :set undofile cm=blowfish
 ggdGijan
 feb
@@ -104,6 +105,32 @@
 u:.w >>test.out
 u:.w >>test.out
 :"
+:" With encryption, cryptmethod=blowfish2
+:e! Xtestfile
+rubbish
+:set undofile cm=blowfish2
+ggdGijan
+feb
+mar
+apr
+jun:set ul=100
+kk0ifoo :set ul=100
+dd:set ul=100
+ibar :set ul=100
+:X
+foo2bar
+foo2bar
+:w!
+:bwipe!
+:e Xtestfile
+foo2bar
+:set key=
+/bar
+:.w >>test.out
+u:.w >>test.out
+u:.w >>test.out
+u:.w >>test.out
+:"
 :" Rename the undo file so that it gets cleaned up.
 :if has("vms")
 : call rename("_un_Xtestfile", "Xtestundo")
diff --git a/src/testdir/test72.ok b/src/testdir/test72.ok
index bb267d0..8d30ba1 100644
--- a/src/testdir/test72.ok
+++ b/src/testdir/test72.ok
@@ -25,3 +25,7 @@
 apr
 foo mar
 mar
+bar apr
+apr
+foo mar
+mar
diff --git a/src/undo.c b/src/undo.c
index 5db25f0..1661c80 100644
--- 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 @@
 #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 @@
 }
 
 /*
- * 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);
 }
 
 /*
- * Read a string of length "len" from "fd".
- * When 'key' is set decrypt the bytes.
+ * Write a number, MSB first, in "len" bytes.
+ * Must match with undo_read_?c() functions.
+ * Returns OK or FAIL.
  */
-    static char_u *
-read_string_decrypt(buf, fd, len)
-    buf_T   *buf UNUSED;
-    FILE    *fd;
+    static int
+undo_write_bytes(bi, nr, len)
+    bufinfo_T *bi;
+    long_u  nr;
     int	    len;
 {
-    char_u  *ptr;
+    char_u  buf[8];
+    int	    i;
+    int	    bufi = 0;
 
-    ptr = read_string(fd, len);
-#ifdef FEAT_CRYPT
-    if (ptr != NULL && *buf->b_p_key != NUL)
-	crypt_decode(ptr, len);
-#endif
-    return ptr;
+    for (i = len - 1; i >= 0; --i)
+	buf[bufi++] = nr >> (i * 8);
+    return undo_write(bi, buf, (size_t)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
-serialize_header(fp, buf, hash)
-    FILE	*fp;
-    buf_T	*buf;
+undo_read_4c(bi)
+    bufinfo_T	*bi;
+{
+#ifdef FEAT_CRYPT
+    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 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
+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 @@
 	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_header_ptr(bi, buf->b_u_oldhead);
+    put_header_ptr(bi, buf->b_u_newhead);
+    put_header_ptr(bi, 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);
+    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);
+    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);
 
-    putc(0, fp);  /* end marker */
+    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;
+    char_u	time_buf[8];
 
-    if (put_bytes(fp, (long_u)UF_HEADER_MAGIC, 2) == FAIL)
+    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);
+    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);
 
-    putc(0, fp);  /* end marker */
+    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 @@
 #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 @@
 }
 
 /*
- * 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 @@
 #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 @@
 
     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 @@
 }
 
 /*
- * 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 @@
     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_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 @@
 #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 @@
 	    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 @@
     }
 #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 @@
 
 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 @@
     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 @@
 	    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 @@
 								   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 @@
 	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 @@
     }
 
     /* 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 @@
     }
 
     /* 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 @@
 	    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 @@
 	    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 @@
 
 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);
diff --git a/src/version.c b/src/version.c
index 19183fd..30bcb2e 100644
--- a/src/version.c
+++ b/src/version.c
@@ -742,6 +742,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    399,
+/**/
     398,
 /**/
     397,