diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,6 +1,10 @@
 # Revision history for z-botan
 
-## 0.2.0.0  -- 2020-05-14
+## 0.3.1.0  -- 2020-05-14
+
+* Change `cipherBIO` to buffer an extra chunk so that the last chunk is larger than minimum final chunk size, add a file encryption example.
+
+## 0.3.0.0  -- 2020-05-14
 
 * Change `EMEPadding` to `EncParam`, add `SM2EncParam`.
 * Change `EMSA` to `SignParam`, add `Ed25519Pure`, `Ed25519ph`, `Ed25519Hash`, `SM2SignParam`.
diff --git a/Z-Botan.cabal b/Z-Botan.cabal
--- a/Z-Botan.cabal
+++ b/Z-Botan.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.4
 name:               Z-Botan
-version:            0.3.0.0
+version:            0.3.1.0
 synopsis:           Crypto for Haskell
 description:        Crypto for Haskell, based on <http://botan.randombit.net/ Botan>
 license:            BSD-3-Clause
diff --git a/Z/Botan/FFI.hsc b/Z/Botan/FFI.hsc
--- a/Z/Botan/FFI.hsc
+++ b/Z/Botan/FFI.hsc
@@ -218,6 +218,7 @@
 foreign import ccall unsafe botan_cipher_get_default_nonce_length :: BotanStructT -> MBA## Int -> IO CInt
 foreign import ccall unsafe botan_cipher_get_update_granularity :: BotanStructT -> MBA## Int -> IO CInt
 foreign import ccall unsafe botan_cipher_get_tag_length :: BotanStructT -> MBA## Int -> IO CInt
+foreign import ccall unsafe botan_cipher_get_minimum_final_size :: BotanStructT -> MBA## Int -> IO CInt
 
 --------------------------------------------------------------------------------
 -- PBKDF
diff --git a/Z/Crypto/Cipher.hs b/Z/Crypto/Cipher.hs
--- a/Z/Crypto/Cipher.hs
+++ b/Z/Crypto/Cipher.hs
@@ -387,10 +387,13 @@
         (t, _) <- allocPrimUnsafe $ \ pt ->
             botan_cipher_get_tag_length pci pt
 
+        (f, _) <- allocPrimUnsafe $ \ pt ->
+            botan_cipher_get_minimum_final_size pci pt
+
         (n, _) <- allocPrimUnsafe $ \ pn ->
             botan_cipher_get_default_nonce_length pci pn
 
-        return (Cipher ci name g (KeySpec a b c) t n)
+        return (Cipher ci name g (KeySpec a b c) t f n)
 
 --------------------------------------------------------------------------------
 --
@@ -538,7 +541,8 @@
     , cipherKeySpec           :: {-# UNPACK #-} !KeySpec       -- ^ cipher keyspec
     , cipherTagLength         :: {-# UNPACK #-} !Int    -- ^ AEAD tag length,
                                                         -- will be zero for non-authenticated ciphers.
-    , defaultNonceLength      :: {-# UNPACK #-} !Int    -- ^ a proper default nonce length
+    , cipherMinFinalSize      :: {-# UNPACK #-} !Int    -- ^ Minimum input size for final chunk.
+    , defaultNonceLength      :: {-# UNPACK #-} !Int    -- ^ a proper default nonce length.
     }
     deriving (Show, Generic)
     deriving anyclass T.Print
@@ -546,7 +550,7 @@
 -- | Pass 'Cipher' to FFI as 'botan_cipher_t'.
 withCipher :: Cipher -> (BotanStructT -> IO r) -> IO r
 {-# INLINABLE withCipher #-}
-withCipher (Cipher c _ _ _ _ _) = withBotanStruct c
+withCipher (Cipher c _ _ _ _ _ _) = withBotanStruct c
 
 -- | Create a new cipher.
 --
@@ -572,17 +576,20 @@
         (t, _) <- allocPrimUnsafe $ \ pt ->
             botan_cipher_get_tag_length pci pt
 
+        (f, _) <- allocPrimUnsafe $ \ pt ->
+            botan_cipher_get_minimum_final_size pci pt
+
         (n, _) <- allocPrimUnsafe $ \ pn ->
             botan_cipher_get_default_nonce_length pci pn
 
-        return (Cipher ci name g (KeySpec a b c) t n)
+        return (Cipher ci name g (KeySpec a b c) t f n)
 
 -- | Clear the internal state (such as keys) of this cipher object.
 --
 clearCipher :: HasCallStack => Cipher -> IO ()
 {-# INLINABLE clearCipher #-}
-clearCipher (Cipher ci _ _ _ _ _) =
-    withBotanStruct ci (throwBotanIfMinus_ . botan_cipher_clear)
+clearCipher ci =
+    withCipher ci (throwBotanIfMinus_ . botan_cipher_clear)
 
 -- | Reset the message specific state for this cipher.
 -- Without resetting the keys, this resets the nonce, and any state
@@ -593,15 +600,15 @@
 --
 resetCipher :: HasCallStack => Cipher -> IO ()
 {-# INLINABLE resetCipher #-}
-resetCipher (Cipher ci _ _ _ _ _) =
-    withBotanStruct ci (throwBotanIfMinus_ . botan_cipher_reset)
+resetCipher ci =
+    withCipher ci (throwBotanIfMinus_ . botan_cipher_reset)
 
 -- | Set the key for this cipher object
 --
 setCipherKey :: HasCallStack => Cipher -> V.Bytes -> IO ()
 {-# INLINABLE setCipherKey #-}
-setCipherKey (Cipher ci _ _ _ _ _) key =
-    withBotanStruct ci $ \ pci -> do
+setCipherKey ci key =
+    withCipher ci $ \ pci -> do
         withPrimVectorUnsafe key $ \ pkey key_off key_len -> do
             throwBotanIfMinus_ (hs_botan_cipher_set_key
                 pci pkey key_off key_len)
@@ -610,8 +617,8 @@
 --
 setAssociatedData :: HasCallStack => Cipher -> V.Bytes -> IO ()
 {-# INLINABLE setAssociatedData #-}
-setAssociatedData (Cipher ci _ _ _ _ _) ad =
-    withBotanStruct ci $ \ pci -> do
+setAssociatedData ci ad =
+    withCipher ci $ \ pci -> do
         withPrimVectorUnsafe ad $ \ pad ad_off ad_len -> do
             throwBotanIfMinus_ (hs_botan_cipher_set_associated_data
                 pci pad ad_off ad_len)
@@ -623,8 +630,8 @@
             -> V.Bytes      -- ^ nonce
             -> IO ()
 {-# INLINABLE startCipher #-}
-startCipher (Cipher ci _ _ _ _ _) nonce =
-    withBotanStruct ci $ \ pci -> do
+startCipher ci nonce =
+    withCipher ci $ \ pci -> do
         withPrimVectorUnsafe nonce $ \ pnonce nonce_off nonce_len -> do
             throwBotanIfMinus_ (hs_botan_cipher_start
                 pci pnonce nonce_off nonce_len)
@@ -638,8 +645,8 @@
              -> V.Bytes
              -> IO (V.Bytes, V.Bytes)   -- ^ trailing input, output
 {-# INLINABLE updateCipher #-}
-updateCipher (Cipher ci _ _ _ _ _) input =
-    withBotanStruct ci $ \ pci -> do
+updateCipher ci input =
+    withCipher ci $ \ pci -> do
         withPrimVectorUnsafe input $ \ in_p in_off in_len -> do
             (out, r) <- allocPrimVectorUnsafe in_len $ \ out_p ->
                 throwBotanIfMinus (hs_botan_cipher_update pci
@@ -655,7 +662,7 @@
              -> V.Bytes
              -> IO V.Bytes
 {-# INLINABLE finishCipher #-}
-finishCipher (Cipher ci _ ug _ tag_len _) input =
+finishCipher (Cipher ci _ ug _ tag_len _ _) input =
     withBotanStruct ci $ \ pci -> do
         withPrimVectorUnsafe input $ \ in_p in_off in_len -> do
             let !out_len = in_len + ug + tag_len
@@ -669,25 +676,44 @@
 
 -- | Wrap a cipher into a 'BIO' node(experimental).
 --
--- The cipher should have already started by setting key, nounce, etc.
+-- The cipher should have already started by setting key, nounce, etc,
+-- for example to encrypt a file in constant memory:
 --
--- Note some cipher modes have a minimal input length requirement for last chunk(CBC_CTS, XTS, etc.),
--- which may not be suitable for arbitrary bytes streams.
+-- @
+-- encryptFile :: CBytes -> CBytes -> IO ()
+-- encryptFile origin target = do
+--     let demoKey = "12345678123456781234567812345678"
+--         nonce = "demo only, use random nonce"
+--     cipher <- newCipher (EAX AES256) CipherEncrypt
+--     setCipherKey cipher demoKey
+--     startCipher cipher nonce
+--     encryptor <- cipherBIO cipher
 --
+--     withResource (initSourceFromFile origin) $ \ src ->
+--         withResource (initSinkToFile target) $ \ sink ->
+--             runBIO_ $ src . encryptor . sink
+-- @
+--
+-- Note that many cipher modes have a maximum length limit on the plaintext under a security context,
+-- i.e. a key nonce combination. If you want to encrypt a large message, please consider divide it into
+-- smaller chunks, and re-key or change the iv.
 cipherBIO :: HasCallStack => Cipher -> IO (BIO V.Bytes V.Bytes)
 {-# INLINABLE cipherBIO #-}
 cipherBIO c = do
     trailingRef <- newIORef V.empty
     return $ \ k mbs -> case mbs of
-        Just bs -> do
+        Just chunk -> do
             trailing <- readIORef trailingRef
-            let chunk =  trailing `V.append` bs
-            (rest, out) <- updateCipher c chunk
-            writeIORef trailingRef rest
-            unless (V.null out) (k (Just out))
+            if (V.length trailing >= cipherUpdateGranularity c)
+                && (V.length chunk >= cipherMinFinalSize c)
+            then do
+                (rest, out) <- updateCipher c trailing
+                k (Just out)
+                writeIORef trailingRef (rest `V.append` chunk)
+            else writeIORef trailingRef (trailing `V.append` chunk)
         _ -> do
             trailing <- readIORef trailingRef
             bs <- finishCipher c trailing
             if V.null bs
-            then k (Just bs) >> k EOF
-            else k EOF
+            then k EOF
+            else k (Just bs) >> k EOF
diff --git a/third_party/botan/botan_all.cpp b/third_party/botan/botan_all.cpp
# file too large to diff: third_party/botan/botan_all.cpp
diff --git a/third_party/botan/src/lib/ffi/ffi.h b/third_party/botan/src/lib/ffi/ffi.h
--- a/third_party/botan/src/lib/ffi/ffi.h
+++ b/third_party/botan/src/lib/ffi/ffi.h
@@ -451,6 +451,11 @@
 BOTAN_PUBLIC_API(2,0) int botan_cipher_get_tag_length(botan_cipher_t cipher, size_t* tag_size);
 
 /**
+* Get the minimum input size required of the cipher.
+*/
+BOTAN_PUBLIC_API(2,19) int botan_cipher_get_minimum_final_size(botan_cipher_t cipher, size_t* fs);
+
+/**
 * Get the default nonce length of this cipher
 */
 BOTAN_PUBLIC_API(2,0) int botan_cipher_get_default_nonce_length(botan_cipher_t cipher, size_t* nl);
diff --git a/third_party/botan/src/lib/ffi/ffi_cipher.cpp b/third_party/botan/src/lib/ffi/ffi_cipher.cpp
--- a/third_party/botan/src/lib/ffi/ffi_cipher.cpp
+++ b/third_party/botan/src/lib/ffi/ffi_cipher.cpp
@@ -224,6 +224,11 @@
    return BOTAN_FFI_DO(Botan::Cipher_Mode, cipher, c, { *tl = c.tag_size(); });
    }
 
+int botan_cipher_get_minimum_final_size(botan_cipher_t cipher, size_t* fs)
+   {
+   return BOTAN_FFI_DO(Botan::Cipher_Mode, cipher, c, { *fs = c.minimum_final_size(); });
+   }
+
 int botan_cipher_name(botan_cipher_t cipher, char* name, size_t* name_len)
    {
    return BOTAN_FFI_DO(Botan::Cipher_Mode, cipher, c, {
