diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,9 @@
 # Changelog
 
+- 0.2.0 (2025-06-21)
+  * The 'derive' function is now total, returning 'Nothing' when supplied
+    with bad inputs.
+
 - 0.1.1 (2025-02-24)
   * Minor Haddock fixes.
 
diff --git a/lib/Crypto/KDF/PBKDF.hs b/lib/Crypto/KDF/PBKDF.hs
--- a/lib/Crypto/KDF/PBKDF.hs
+++ b/lib/Crypto/KDF/PBKDF.hs
@@ -21,10 +21,12 @@
   , derive
   )where
 
+import Control.Monad (guard)
 import Data.Bits ((.>>.), (.&.))
 import qualified Data.Bits as B
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Builder as BSB
+import qualified Data.ByteString.Builder.Extra as BE
 import Data.Word (Word32, Word64)
 
 -- NB following synonym really only exists to make haddocks more
@@ -69,7 +71,8 @@
 --   >>> import qualified Crypto.Hash.SHA256 as SHA256
 --   >>> import qualified Data.ByteString as BS
 --   >>> import qualified Data.ByteString.Base16 as B16
---   >>> BS.take 16 (B16.encode (derive SHA256.hmac "passwd" "salt" 1 64))
+--   >>> let Just key = derive SHA256.hmac "passwd" "salt" 1 64
+--   >>> BS.take 16 (B16.encode key)
 --   "55ac046e56e3089f"
 derive
   :: HMAC          -- ^ pseudo-random function (HMAC)
@@ -77,12 +80,10 @@
   -> BS.ByteString -- ^ salt
   -> Word64        -- ^ iteration count
   -> Word32        -- ^ bytelength of derived key (max 0xffff_ffff * hlen)
-  -> BS.ByteString -- ^ derived key
-derive prf p s c dklen
-    | dklen > 0xffff_ffff * fi hlen =      -- 2 ^ 32 - 1
-        error "ppad-pbkdf (derive): derived key too long"
-    | otherwise =
-        loop mempty 1
+  -> Maybe BS.ByteString -- ^ derived key
+derive prf p s c dklen = do
+    guard (dklen <= 0xffff_ffff * fi hlen)
+    pure (loop mempty 1)
   where
     !hlen = BS.length (prf mempty mempty)
     !l = ceiling (fi dklen / fi hlen :: Double) :: Word32
@@ -105,8 +106,13 @@
       | i == l =
           let t = f i
               fin = BS.take r t
-          in  BS.toStrict . BSB.toLazyByteString $
-                acc <> BSB.byteString fin
+          in  BS.toStrict $
+                if   dklen <= 128
+                then BE.toLazyByteStringWith
+                       (BE.safeStrategy 128 BE.smallChunkSize) mempty $
+                       acc <> BSB.byteString fin
+                else BSB.toLazyByteString $
+                       acc <> BSB.byteString fin
       | otherwise =
           let t = f i
               nacc = acc <> BSB.byteString t
diff --git a/ppad-pbkdf.cabal b/ppad-pbkdf.cabal
--- a/ppad-pbkdf.cabal
+++ b/ppad-pbkdf.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               ppad-pbkdf
-version:            0.1.1
+version:            0.2.0
 synopsis:           A password-based key derivation function
 license:            MIT
 license-file:       LICENSE
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -3,11 +3,9 @@
 
 module Main where
 
-import Control.Exception
 import qualified Crypto.Hash.SHA256 as SHA256
 import qualified Crypto.Hash.SHA512 as SHA512
 import qualified Crypto.KDF.PBKDF as KDF
-import qualified Data.ByteString as BS
 import qualified Data.Aeson as A
 import qualified Data.Text.IO as TIO
 import Test.Tasty
@@ -48,16 +46,13 @@
         cow = pt_iterationCount
         siz = pt_dkLen
         pec = pt_dk
-    if   pt_result == "invalid"
-    then do
-      out <- try (pure $! KDF.derive hmac pas sal cow siz)
-               :: IO (Either ErrorCall BS.ByteString)
-      case out of
-        Left _  -> assertBool "invalid" True
-        Right o -> assertBool "invalid" (pec /= o)
-    else do
-      let out = KDF.derive hmac pas sal cow siz
-      assertEqual mempty pec out
+    case KDF.derive hmac pas sal cow siz of
+      Nothing
+        | pt_result == "invalid" -> assertBool "invalid" True
+        | otherwise -> assertFailure mempty
+      Just out
+        | pt_result == "invalid" -> assertBool "invalid" (pec /= out)
+        | otherwise -> assertEqual mempty pec out
   where
     hmac = case h of
       SHA256 -> SHA256.hmac
