diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Changelog
 
+## 0.2.0.1 -- 2026-05-08
+
+* PATCH: fix `base64Decode` failing against `botan` 3.12.0, which now rejects a
+  `NULL` output pointer in `botan_base64_decode`. The size-query call now uses
+  a non-null zero-byte sentinel buffer. See PR
+  [#126](https://github.com/haskell-cryptography/botan/pull/126).
+
 ## 0.2.0.0 -- 2026-03-23
 
 * BREAKING: change more flags from numbers into datatypes. See PR
diff --git a/botan-low.cabal b/botan-low.cabal
--- a/botan-low.cabal
+++ b/botan-low.cabal
@@ -1,6 +1,6 @@
 cabal-version: 3.0
 name: botan-low
-version: 0.2.0.0
+version: 0.2.0.1
 synopsis: Low-level Botan bindings
 description:
   Welcome to botan-low
@@ -50,7 +50,7 @@
   type: git
   location: https://github.com/haskell-cryptography/botan
   subdir: botan-low
-  tag: botan-low-0.2.0.0
+  tag: botan-low-0.2.0.1
 
 common warnings
   ghc-options:
diff --git a/src/Botan/Low/Utility.hs b/src/Botan/Low/Utility.hs
--- a/src/Botan/Low/Utility.hs
+++ b/src/Botan/Low/Utility.hs
@@ -24,15 +24,19 @@
   ) where
 
 import           Botan.Bindings.ConstPtr (ConstPtr (..))
+import           Botan.Bindings.Error
 import           Botan.Bindings.Utility
 import           Botan.Low.Error.Internal
 import           Botan.Low.Internal.ByteString
 import           Botan.Low.Make
 import           Data.ByteString (ByteString)
-import           Data.Text
+import qualified Data.ByteString as BS
+import           Data.Text (Text)
 import qualified Data.Text.Encoding as Text
 import           Data.Word
+import           Foreign.Marshal.Alloc
 import           Foreign.Ptr
+import           Foreign.Storable
 
 -- | Returns 0 if x[0..len] == y[0..len], -1 otherwise.
 constantTimeCompare ::
@@ -109,9 +113,18 @@
   -> IO ByteString    -- ^ __out__
 base64Decode txt =
     asBytesLen (Text.encodeUtf8 txt) $ \ base64Ptr base64Len ->
-    allocBytesQueryingCString $ \ bytesPtr szPtr ->
-    botan_base64_decode
-      (ConstPtr base64Ptr)
-      base64Len
-      bytesPtr
-      szPtr
+    alloca $ \ szPtr -> do
+        poke szPtr 0
+        -- Botan 3.12.0 added an any_null_pointers guard on botan_base64_decode that
+        -- rejects a NULL out, so use a non-null zero-byte sentinel for the size-query call.
+        code <- allocaBytes 0 $ \ sentinelPtr ->
+            botan_base64_decode (ConstPtr base64Ptr) base64Len sentinelPtr szPtr
+        case code of
+            BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE -> do
+                sz <- fromIntegral <$> peek szPtr
+                bs <- allocBytes sz $ \ outPtr ->
+                    throwBotanIfNegative_ $ botan_base64_decode
+                      (ConstPtr base64Ptr) base64Len outPtr szPtr
+                actual <- fromIntegral <$> peek szPtr
+                return $! BS.take actual bs
+            _ -> throwBotanError code
