diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.28
+
+* Add hash constant time capability
+* Prevent possible overflow during hashing by hashing in 4GB chunks
+
 ## 0.27
 
 * Optimise AES GCM and CCM
diff --git a/Crypto/Cipher/RC4.hs b/Crypto/Cipher/RC4.hs
--- a/Crypto/Cipher/RC4.hs
+++ b/Crypto/Cipher/RC4.hs
@@ -30,6 +30,11 @@
 import           Crypto.Internal.Imports
 
 -- | The encryption state for RC4
+--
+-- This type is an instance of 'ByteArrayAccess' for debugging purpose. Internal
+-- layout is architecture dependent, may contain uninitialized data fragments,
+-- and change in future versions.  The bytearray should not be used as input to
+-- cryptographic algorithms.
 newtype State = State ScrubbedBytes
     deriving (ByteArrayAccess,NFData)
 
diff --git a/Crypto/Cipher/Types/AEAD.hs b/Crypto/Cipher/Types/AEAD.hs
--- a/Crypto/Cipher/Types/AEAD.hs
+++ b/Crypto/Cipher/Types/AEAD.hs
@@ -27,24 +27,24 @@
 -- | Authenticated Encryption with Associated Data algorithms
 data AEAD cipher = forall st . AEAD
     { aeadModeImpl :: AEADModeImpl st
-    , aeadState    :: st
+    , aeadState    :: !st
     }
 
 -- | Append some header information to an AEAD context
 aeadAppendHeader :: ByteArrayAccess aad => AEAD cipher -> aad -> AEAD cipher
-aeadAppendHeader (AEAD impl st) aad = AEAD impl $ (aeadImplAppendHeader impl) st aad
+aeadAppendHeader (AEAD impl st) aad = AEAD impl $ aeadImplAppendHeader impl st aad
 
 -- | Encrypt some data and update the AEAD context
 aeadEncrypt :: ByteArray ba => AEAD cipher -> ba -> (ba, AEAD cipher)
-aeadEncrypt (AEAD impl st) ba = second (AEAD impl) $ (aeadImplEncrypt impl) st ba
+aeadEncrypt (AEAD impl st) ba = second (AEAD impl) $ aeadImplEncrypt impl st ba
 
 -- | Decrypt some data and update the AEAD context
 aeadDecrypt :: ByteArray ba => AEAD cipher -> ba -> (ba, AEAD cipher)
-aeadDecrypt (AEAD impl st) ba = second (AEAD impl) $ (aeadImplDecrypt impl) st ba
+aeadDecrypt (AEAD impl st) ba = second (AEAD impl) $ aeadImplDecrypt impl st ba
 
 -- | Finalize the AEAD context and return the authentication tag
 aeadFinalize :: AEAD cipher -> Int -> AuthTag
-aeadFinalize (AEAD impl st) n = (aeadImplFinalize impl) st n
+aeadFinalize (AEAD impl st) = aeadImplFinalize impl st
 
 -- | Simple AEAD encryption
 aeadSimpleEncrypt :: (ByteArrayAccess aad, ByteArray ba)
diff --git a/Crypto/Hash.hs b/Crypto/Hash.hs
--- a/Crypto/Hash.hs
+++ b/Crypto/Hash.hs
@@ -28,14 +28,17 @@
     -- * Hash methods parametrized by algorithm
     , hashInitWith
     , hashWith
+    , hashPrefixWith
     -- * Hash methods
     , hashInit
     , hashUpdates
     , hashUpdate
     , hashFinalize
+    , hashFinalizePrefix
     , hashBlockSize
     , hashDigestSize
     , hash
+    , hashPrefix
     , hashlazy
     -- * Hash algorithms
     , module Crypto.Hash.Algorithms
@@ -47,16 +50,20 @@
 import           Crypto.Internal.Compat (unsafeDoIO)
 import           Crypto.Hash.Types
 import           Crypto.Hash.Algorithms
-import           Foreign.Ptr (Ptr)
+import           Foreign.Ptr (Ptr, plusPtr)
 import           Crypto.Internal.ByteArray (ByteArrayAccess)
 import qualified Crypto.Internal.ByteArray as B
 import qualified Data.ByteString.Lazy as L
-import           Data.Word (Word8)
+import           Data.Word (Word8, Word32)
 
 -- | Hash a strict bytestring into a digest.
 hash :: (ByteArrayAccess ba, HashAlgorithm a) => ba -> Digest a
 hash bs = hashFinalize $ hashUpdate hashInit bs
 
+-- | Hash the first N bytes of a bytestring, with code path independent from N.
+hashPrefix :: (ByteArrayAccess ba, HashAlgorithmPrefix a) => ba -> Int -> Digest a
+hashPrefix = hashFinalizePrefix hashInit
+
 -- | Hash a lazy bytestring into a digest.
 hashlazy :: HashAlgorithm a => L.ByteString -> Digest a
 hashlazy lbs = hashFinalize $ hashUpdates hashInit (L.toChunks lbs)
@@ -81,9 +88,17 @@
 hashUpdates c l
     | null ls   = c
     | otherwise = Context $ B.copyAndFreeze c $ \(ctx :: Ptr (Context a)) ->
-        mapM_ (\b -> B.withByteArray b $ \d -> hashInternalUpdate ctx d (fromIntegral $ B.length b)) ls
+        mapM_ (\b -> B.withByteArray b (processBlocks ctx (B.length b))) ls
   where
     ls = filter (not . B.null) l
+    -- process the data in 4GB chunks to fit in uint32_t
+    processBlocks ctx bytesLeft dataPtr
+        | bytesLeft == 0 = return ()
+        | otherwise = do
+            hashInternalUpdate ctx dataPtr (fromIntegral actuallyProcessed)
+            processBlocks ctx (bytesLeft - actuallyProcessed) (dataPtr `plusPtr` actuallyProcessed)
+        where
+            actuallyProcessed = min bytesLeft (fromIntegral (maxBound :: Word32))
 
 -- | Finalize a context and return a digest.
 hashFinalize :: forall a . HashAlgorithm a
@@ -94,6 +109,24 @@
         ((!_) :: B.Bytes) <- B.copy c $ \(ctx :: Ptr (Context a)) -> hashInternalFinalize ctx dig
         return ()
 
+-- | Update the context with the first N bytes of a bytestring and return the
+-- digest.  The code path is independent from N but much slower than a normal
+-- 'hashUpdate'.  The function can be called for the last bytes of a message, in
+-- order to exclude a variable padding, without leaking the padding length.  The
+-- begining of the message, never impacted by the padding, should preferably go
+-- through 'hashUpdate' for better performance.
+hashFinalizePrefix :: forall a ba . (HashAlgorithmPrefix a, ByteArrayAccess ba)
+                   => Context a
+                   -> ba
+                   -> Int
+                   -> Digest a
+hashFinalizePrefix !c b len =
+    Digest $ B.allocAndFreeze (hashDigestSize (undefined :: a)) $ \(dig :: Ptr (Digest a)) -> do
+        ((!_) :: B.Bytes) <- B.copy c $ \(ctx :: Ptr (Context a)) ->
+            B.withByteArray b $ \d ->
+                hashInternalFinalizePrefix ctx d (fromIntegral $ B.length b) (fromIntegral len) dig
+        return ()
+
 -- | Initialize a new context for a specified hash algorithm
 hashInitWith :: HashAlgorithm alg => alg -> Context alg
 hashInitWith _ = hashInit
@@ -101,6 +134,10 @@
 -- | Run the 'hash' function but takes an explicit hash algorithm parameter
 hashWith :: (ByteArrayAccess ba, HashAlgorithm alg) => alg -> ba -> Digest alg
 hashWith _ = hash
+
+-- | Run the 'hashPrefix' function but takes an explicit hash algorithm parameter
+hashPrefixWith :: (ByteArrayAccess ba, HashAlgorithmPrefix alg) => alg -> ba -> Int -> Digest alg
+hashPrefixWith _ = hashPrefix
 
 -- | Try to transform a bytearray into a Digest of specific algorithm.
 --
diff --git a/Crypto/Hash/Algorithms.hs b/Crypto/Hash/Algorithms.hs
--- a/Crypto/Hash/Algorithms.hs
+++ b/Crypto/Hash/Algorithms.hs
@@ -9,6 +9,7 @@
 --
 module Crypto.Hash.Algorithms
     ( HashAlgorithm
+    , HashAlgorithmPrefix
     -- * Hash algorithms
     , Blake2s_160(..)
     , Blake2s_224(..)
@@ -54,7 +55,7 @@
     , Whirlpool(..)
     ) where
 
-import           Crypto.Hash.Types (HashAlgorithm)
+import           Crypto.Hash.Types (HashAlgorithm, HashAlgorithmPrefix)
 import           Crypto.Hash.Blake2s
 import           Crypto.Hash.Blake2sp
 import           Crypto.Hash.Blake2b
diff --git a/Crypto/Hash/IO.hs b/Crypto/Hash/IO.hs
--- a/Crypto/Hash/IO.hs
+++ b/Crypto/Hash/IO.hs
@@ -24,6 +24,11 @@
 import           Foreign.Ptr
 
 -- | A Mutable hash context
+--
+-- This type is an instance of 'B.ByteArrayAccess' for debugging purpose.
+-- Internal layout is architecture dependent, may contain uninitialized data
+-- fragments, and change in future versions.  The bytearray should not be used
+-- as input to cryptographic algorithms.
 newtype MutableContext a = MutableContext B.Bytes
     deriving (B.ByteArrayAccess)
 
diff --git a/Crypto/Hash/MD5.hs b/Crypto/Hash/MD5.hs
--- a/Crypto/Hash/MD5.hs
+++ b/Crypto/Hash/MD5.hs
@@ -34,6 +34,9 @@
     hashInternalUpdate        = c_md5_update
     hashInternalFinalize      = c_md5_finalize
 
+instance HashAlgorithmPrefix MD5 where
+    hashInternalFinalizePrefix = c_md5_finalize_prefix
+
 foreign import ccall unsafe "cryptonite_md5_init"
     c_md5_init :: Ptr (Context a)-> IO ()
 
@@ -42,3 +45,6 @@
 
 foreign import ccall unsafe "cryptonite_md5_finalize"
     c_md5_finalize :: Ptr (Context a) -> Ptr (Digest a) -> IO ()
+
+foreign import ccall "cryptonite_md5_finalize_prefix"
+    c_md5_finalize_prefix :: Ptr (Context a) -> Ptr Word8 -> Word32 -> Word32 -> Ptr (Digest a) -> IO ()
diff --git a/Crypto/Hash/SHA1.hs b/Crypto/Hash/SHA1.hs
--- a/Crypto/Hash/SHA1.hs
+++ b/Crypto/Hash/SHA1.hs
@@ -34,6 +34,9 @@
     hashInternalUpdate        = c_sha1_update
     hashInternalFinalize      = c_sha1_finalize
 
+instance HashAlgorithmPrefix SHA1 where
+    hashInternalFinalizePrefix = c_sha1_finalize_prefix
+
 foreign import ccall unsafe "cryptonite_sha1_init"
     c_sha1_init :: Ptr (Context a)-> IO ()
 
@@ -42,3 +45,6 @@
 
 foreign import ccall unsafe "cryptonite_sha1_finalize"
     c_sha1_finalize :: Ptr (Context a) -> Ptr (Digest a) -> IO ()
+
+foreign import ccall "cryptonite_sha1_finalize_prefix"
+    c_sha1_finalize_prefix :: Ptr (Context a) -> Ptr Word8 -> Word32 -> Word32 -> Ptr (Digest a) -> IO ()
diff --git a/Crypto/Hash/SHA224.hs b/Crypto/Hash/SHA224.hs
--- a/Crypto/Hash/SHA224.hs
+++ b/Crypto/Hash/SHA224.hs
@@ -34,6 +34,9 @@
     hashInternalUpdate        = c_sha224_update
     hashInternalFinalize      = c_sha224_finalize
 
+instance HashAlgorithmPrefix SHA224 where
+    hashInternalFinalizePrefix = c_sha224_finalize_prefix
+
 foreign import ccall unsafe "cryptonite_sha224_init"
     c_sha224_init :: Ptr (Context a)-> IO ()
 
@@ -42,3 +45,6 @@
 
 foreign import ccall unsafe "cryptonite_sha224_finalize"
     c_sha224_finalize :: Ptr (Context a) -> Ptr (Digest a) -> IO ()
+
+foreign import ccall "cryptonite_sha224_finalize_prefix"
+    c_sha224_finalize_prefix :: Ptr (Context a) -> Ptr Word8 -> Word32 -> Word32 -> Ptr (Digest a) -> IO ()
diff --git a/Crypto/Hash/SHA256.hs b/Crypto/Hash/SHA256.hs
--- a/Crypto/Hash/SHA256.hs
+++ b/Crypto/Hash/SHA256.hs
@@ -34,6 +34,9 @@
     hashInternalUpdate        = c_sha256_update
     hashInternalFinalize      = c_sha256_finalize
 
+instance HashAlgorithmPrefix SHA256 where
+    hashInternalFinalizePrefix = c_sha256_finalize_prefix
+
 foreign import ccall unsafe "cryptonite_sha256_init"
     c_sha256_init :: Ptr (Context a)-> IO ()
 
@@ -42,3 +45,6 @@
 
 foreign import ccall unsafe "cryptonite_sha256_finalize"
     c_sha256_finalize :: Ptr (Context a) -> Ptr (Digest a) -> IO ()
+
+foreign import ccall "cryptonite_sha256_finalize_prefix"
+    c_sha256_finalize_prefix :: Ptr (Context a) -> Ptr Word8 -> Word32 -> Word32 -> Ptr (Digest a) -> IO ()
diff --git a/Crypto/Hash/SHA384.hs b/Crypto/Hash/SHA384.hs
--- a/Crypto/Hash/SHA384.hs
+++ b/Crypto/Hash/SHA384.hs
@@ -34,6 +34,9 @@
     hashInternalUpdate        = c_sha384_update
     hashInternalFinalize      = c_sha384_finalize
 
+instance HashAlgorithmPrefix SHA384 where
+    hashInternalFinalizePrefix = c_sha384_finalize_prefix
+
 foreign import ccall unsafe "cryptonite_sha384_init"
     c_sha384_init :: Ptr (Context a)-> IO ()
 
@@ -42,3 +45,6 @@
 
 foreign import ccall unsafe "cryptonite_sha384_finalize"
     c_sha384_finalize :: Ptr (Context a) -> Ptr (Digest a) -> IO ()
+
+foreign import ccall "cryptonite_sha384_finalize_prefix"
+    c_sha384_finalize_prefix :: Ptr (Context a) -> Ptr Word8 -> Word32 -> Word32 -> Ptr (Digest a) -> IO ()
diff --git a/Crypto/Hash/SHA512.hs b/Crypto/Hash/SHA512.hs
--- a/Crypto/Hash/SHA512.hs
+++ b/Crypto/Hash/SHA512.hs
@@ -34,6 +34,9 @@
     hashInternalUpdate        = c_sha512_update
     hashInternalFinalize      = c_sha512_finalize
 
+instance HashAlgorithmPrefix SHA512 where
+    hashInternalFinalizePrefix = c_sha512_finalize_prefix
+
 foreign import ccall unsafe "cryptonite_sha512_init"
     c_sha512_init :: Ptr (Context a)-> IO ()
 
@@ -42,3 +45,6 @@
 
 foreign import ccall unsafe "cryptonite_sha512_finalize"
     c_sha512_finalize :: Ptr (Context a) -> Ptr (Digest a) -> IO ()
+
+foreign import ccall "cryptonite_sha512_finalize_prefix"
+    c_sha512_finalize_prefix :: Ptr (Context a) -> Ptr Word8 -> Word32 -> Word32 -> Ptr (Digest a) -> IO ()
diff --git a/Crypto/Hash/Types.hs b/Crypto/Hash/Types.hs
--- a/Crypto/Hash/Types.hs
+++ b/Crypto/Hash/Types.hs
@@ -14,6 +14,7 @@
 {-# LANGUAGE TypeFamilies #-}
 module Crypto.Hash.Types
     ( HashAlgorithm(..)
+    , HashAlgorithmPrefix(..)
     , Context(..)
     , Digest(..)
     ) where
@@ -59,12 +60,28 @@
     -- | Finalize the context and set the digest raw memory to the right value
     hashInternalFinalize :: Ptr (Context a) -> Ptr (Digest a) -> IO ()
 
+-- | Hashing algorithms with a constant-time implementation.
+class HashAlgorithm a => HashAlgorithmPrefix a where
+    -- | Update the context with the first N bytes of a buffer and finalize this
+    -- context.  The code path executed is independent from N and depends only
+    -- on the complete buffer length.
+    hashInternalFinalizePrefix :: Ptr (Context a)
+                               -> Ptr Word8 -> Word32
+                               -> Word32
+                               -> Ptr (Digest a)
+                               -> IO ()
+
 {-
 hashContextGetAlgorithm :: HashAlgorithm a => Context a -> a
 hashContextGetAlgorithm = undefined
 -}
 
 -- | Represent a context for a given hash algorithm.
+--
+-- This type is an instance of 'ByteArrayAccess' for debugging purpose. Internal
+-- layout is architecture dependent, may contain uninitialized data fragments,
+-- and change in future versions.  The bytearray should not be used as input to
+-- cryptographic algorithms.
 newtype Context a = Context Bytes
     deriving (ByteArrayAccess,NFData)
 
diff --git a/Crypto/MAC/Poly1305.hs b/Crypto/MAC/Poly1305.hs
--- a/Crypto/MAC/Poly1305.hs
+++ b/Crypto/MAC/Poly1305.hs
@@ -33,6 +33,11 @@
 import           Crypto.Error
 
 -- | Poly1305 State
+--
+-- This type is an instance of 'ByteArrayAccess' for debugging purpose. Internal
+-- layout is architecture dependent, may contain uninitialized data fragments,
+-- and change in future versions.  The bytearray should not be used as input to
+-- cryptographic algorithms.
 newtype State = State ScrubbedBytes
     deriving (ByteArrayAccess)
 
diff --git a/Crypto/Random.hs b/Crypto/Random.hs
--- a/Crypto/Random.hs
+++ b/Crypto/Random.hs
@@ -84,6 +84,9 @@
 -- Note that the @Arbitrary@ instance provided by QuickCheck for 'Word64' does
 -- not have a uniform distribution.  It is often better to use instead
 -- @arbitraryBoundedRandom@.
+--
+-- System endianness impacts how the tuple is interpreted and therefore changes
+-- the resulting DRG.
 drgNewTest :: (Word64, Word64, Word64, Word64, Word64) -> ChaChaDRG
 drgNewTest = initializeWords
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -52,6 +52,10 @@
 with the lack of autodetection feature builtin in .cabal file, it is left on
 the user to disable the aesni. See the [Disabling AESNI] section
 
+On CentOS 7 the default C compiler includes intrinsic header files incompatible
+with per-function target options.  Solutions are to use GCC >= 4.9 or disable
+flag *use_target_attributes* (see flag configuration examples below).
+
 Disabling AESNI
 ---------------
 
@@ -71,6 +75,13 @@
 ```
 
 For help with cabal flags, see: [stackoverflow : is there a way to define flags for cabal](http://stackoverflow.com/questions/23523869/is-there-any-way-to-define-flags-for-cabal-dependencies)
+
+Enabling PCLMULDQ
+-----------------
+
+When the C toolchain supports it, enabling flag *support_pclmuldq* can bring
+additional security and performance for AES GCM.  A CPU with the necessary
+instruction set will use an alternate implementation selected at runtime.
 
 Links
 -----
diff --git a/cbits/cryptonite_align.h b/cbits/cryptonite_align.h
--- a/cbits/cryptonite_align.h
+++ b/cbits/cryptonite_align.h
@@ -44,11 +44,21 @@
 	*((uint32_t *) dst) = cpu_to_le32(v);
 }
 
+static inline void xor_le32_aligned(uint8_t *dst, const uint32_t v)
+{
+	*((uint32_t *) dst) ^= cpu_to_le32(v);
+}
+
 static inline void store_be32_aligned(uint8_t *dst, const uint32_t v)
 {
 	*((uint32_t *) dst) = cpu_to_be32(v);
 }
 
+static inline void xor_be32_aligned(uint8_t *dst, const uint32_t v)
+{
+	*((uint32_t *) dst) ^= cpu_to_be32(v);
+}
+
 static inline void store_le64_aligned(uint8_t *dst, const uint64_t v)
 {
 	*((uint64_t *) dst) = cpu_to_le64(v);
@@ -59,6 +69,11 @@
 	*((uint64_t *) dst) = cpu_to_be64(v);
 }
 
+static inline void xor_be64_aligned(uint8_t *dst, const uint64_t v)
+{
+	*((uint64_t *) dst) ^= cpu_to_be64(v);
+}
+
 #ifdef UNALIGNED_ACCESS_OK
 #define load_le32(a) load_le32_aligned(a)
 #else
@@ -70,20 +85,30 @@
 
 #ifdef UNALIGNED_ACCESS_OK
 #define store_le32(a, b) store_le32_aligned(a, b)
+#define xor_le32(a, b) xor_le32_aligned(a, b)
 #else
 static inline void store_le32(uint8_t *dst, const uint32_t v)
 {
 	dst[0] = v; dst[1] = v >> 8; dst[2] = v >> 16; dst[3] = v >> 24;
 }
+static inline void xor_le32(uint8_t *dst, const uint32_t v)
+{
+	dst[0] ^= v; dst[1] ^= v >> 8; dst[2] ^= v >> 16; dst[3] ^= v >> 24;
+}
 #endif
 
 #ifdef UNALIGNED_ACCESS_OK
 #define store_be32(a, b) store_be32_aligned(a, b)
+#define xor_be32(a, b) xor_be32_aligned(a, b)
 #else
 static inline void store_be32(uint8_t *dst, const uint32_t v)
 {
 	dst[3] = v; dst[2] = v >> 8; dst[1] = v >> 16; dst[0] = v >> 24;
 }
+static inline void xor_be32(uint8_t *dst, const uint32_t v)
+{
+	dst[3] ^= v; dst[2] ^= v >> 8; dst[1] ^= v >> 16; dst[0] ^= v >> 24;
+}
 #endif
 
 #ifdef UNALIGNED_ACCESS_OK
@@ -98,11 +123,17 @@
 
 #ifdef UNALIGNED_ACCESS_OK
 #define store_be64(a, b) store_be64_aligned(a, b)
+#define xor_be64(a, b) xor_be64_aligned(a, b)
 #else
 static inline void store_be64(uint8_t *dst, const uint64_t v)
 {
 	dst[7] = v      ; dst[6] = v >> 8 ; dst[5] = v >> 16; dst[4] = v >> 24;
 	dst[3] = v >> 32; dst[2] = v >> 40; dst[1] = v >> 48; dst[0] = v >> 56;
+}
+static inline void xor_be64(uint8_t *dst, const uint64_t v)
+{
+	dst[7] ^= v      ; dst[6] ^= v >> 8 ; dst[5] ^= v >> 16; dst[4] ^= v >> 24;
+	dst[3] ^= v >> 32; dst[2] ^= v >> 40; dst[1] ^= v >> 48; dst[0] ^= v >> 56;
 }
 #endif
 
diff --git a/cbits/cryptonite_hash_prefix.c b/cbits/cryptonite_hash_prefix.c
new file mode 100644
--- /dev/null
+++ b/cbits/cryptonite_hash_prefix.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020 Olivier Chéron <olivier.cheron@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <cryptonite_hash_prefix.h>
+
+void CRYPTONITE_HASHED(finalize_prefix)(struct HASHED_LOWER(ctx) *ctx, const uint8_t *data, uint32_t len, uint32_t n, uint8_t *out)
+{
+	uint64_t bits[HASHED(BITS_ELEMS)];
+	uint8_t *p = (uint8_t *) &bits;
+	uint32_t index, padidx, padlen, pos, out_mask;
+	static const uint32_t cut_off = HASHED(BLOCK_SIZE) - sizeof(bits);
+
+	/* Make sure n <= len */
+	n += (len - n) & constant_time_lt(len, n);
+
+	/* Initial index, based on current context state */
+	index = CRYPTONITE_HASHED(get_index)(ctx);
+
+	/* Final size after n bytes */
+	CRYPTONITE_HASHED(incr_sz)(ctx, bits, n);
+
+	/* Padding index and length */
+	padidx = CRYPTONITE_HASHED(get_index)(ctx);
+	padlen = HASHED(BLOCK_SIZE) + cut_off - padidx;
+	padlen -= HASHED(BLOCK_SIZE) & constant_time_lt(padidx, cut_off);
+
+	/* Initialize buffers because we will XOR into them */
+	memset(ctx->buf + index, 0, HASHED(BLOCK_SIZE) - index);
+	memset(out, 0, HASHED(DIGEST_SIZE));
+	pos = 0;
+
+	/* Iterate based on the full buffer length, regardless of n, and include
+	 * the maximum overhead with padding and size bytes
+	 */
+	while (pos < len + HASHED(BLOCK_SIZE) + sizeof(bits)) {
+		uint8_t b;
+
+		/* Take as many bytes from the input buffer as possible */
+		if (pos < len)
+			b = *(data++) & (uint8_t) constant_time_lt(pos, n);
+		else
+			b = 0;
+
+		/* First padding byte */
+		b |= 0x80 & (uint8_t) constant_time_eq(pos, n);
+
+		/* Size bytes are always at the end of a block */
+		if (index >= cut_off)
+			b |= p[index - cut_off] & (uint8_t) constant_time_ge(pos, n + padlen);
+
+		/* Store this byte into the buffer */
+		ctx->buf[index++] ^= b;
+		pos++;
+
+		/* Process a full block, at a boundary which is independent from n */
+		if (index >= HASHED(BLOCK_SIZE)) {
+			index = 0;
+			HASHED_LOWER(do_chunk)(ctx, (void *) ctx->buf);
+			memset(ctx->buf, 0, HASHED(BLOCK_SIZE));
+
+			/* Try to store the result: this is a no-op except when we reach the
+			 * actual size based on n, more iterations may continue after that
+			 * when len is really larger
+			 */
+			out_mask = constant_time_eq(pos, n + padlen + sizeof(bits));
+			CRYPTONITE_HASHED(select_digest)(ctx, out, out_mask);
+		}
+	}
+}
diff --git a/cbits/cryptonite_hash_prefix.h b/cbits/cryptonite_hash_prefix.h
new file mode 100644
--- /dev/null
+++ b/cbits/cryptonite_hash_prefix.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 Olivier Chéron <olivier.cheron@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *	notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *	notice, this list of conditions and the following disclaimer in the
+ *	documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef CRYPTONITE_HASH_PREFIX_H
+#define CRYPTONITE_HASH_PREFIX_H
+
+#include <stdint.h>
+
+static inline uint32_t constant_time_msb(uint32_t a)
+{
+	return 0 - (a >> 31);
+}
+
+static inline uint32_t constant_time_lt(uint32_t a, uint32_t b)
+{
+	return constant_time_msb(a ^ ((a ^ b) | ((a - b) ^ b)));
+}
+
+static inline uint32_t constant_time_ge(uint32_t a, uint32_t b)
+{
+	return ~constant_time_lt(a, b);
+}
+
+static inline uint32_t constant_time_is_zero(uint32_t a)
+{
+	return constant_time_msb(~a & (a - 1));
+}
+
+static inline uint32_t constant_time_eq(uint32_t a, uint32_t b)
+{
+	return constant_time_is_zero(a ^ b);
+}
+
+static inline uint64_t constant_time_msb_64(uint64_t a)
+{
+	return 0 - (a >> 63);
+}
+
+static inline uint64_t constant_time_lt_64(uint64_t a, uint64_t b)
+{
+	return constant_time_msb_64(a ^ ((a ^ b) | ((a - b) ^ b)));
+}
+
+#endif
diff --git a/cbits/cryptonite_md5.c b/cbits/cryptonite_md5.c
--- a/cbits/cryptonite_md5.c
+++ b/cbits/cryptonite_md5.c
@@ -185,3 +185,30 @@
 	store_le32(out+ 8, ctx->h[2]);
 	store_le32(out+12, ctx->h[3]);
 }
+
+#define HASHED(m) MD5_##m
+#define HASHED_LOWER(m) md5_##m
+#define CRYPTONITE_HASHED(m) cryptonite_md5_##m
+#define MD5_BLOCK_SIZE 64
+#define MD5_BITS_ELEMS 1
+
+static inline uint32_t cryptonite_md5_get_index(const struct md5_ctx *ctx)
+{
+	return (uint32_t) (ctx->sz & 0x3f);
+}
+
+static inline void cryptonite_md5_incr_sz(struct md5_ctx *ctx, uint64_t *bits, uint32_t n)
+{
+	ctx->sz += n;
+	*bits = cpu_to_le64(ctx->sz << 3);
+}
+
+static inline void cryptonite_md5_select_digest(const struct md5_ctx *ctx, uint8_t *out, uint32_t out_mask)
+{
+	xor_le32(out   , ctx->h[0] & out_mask);
+	xor_le32(out+ 4, ctx->h[1] & out_mask);
+	xor_le32(out+ 8, ctx->h[2] & out_mask);
+	xor_le32(out+12, ctx->h[3] & out_mask);
+}
+
+#include <cryptonite_hash_prefix.c>
diff --git a/cbits/cryptonite_md5.h b/cbits/cryptonite_md5.h
--- a/cbits/cryptonite_md5.h
+++ b/cbits/cryptonite_md5.h
@@ -39,5 +39,6 @@
 void cryptonite_md5_init(struct md5_ctx *ctx);
 void cryptonite_md5_update(struct md5_ctx *ctx, const uint8_t *data, uint32_t len);
 void cryptonite_md5_finalize(struct md5_ctx *ctx, uint8_t *out);
+void cryptonite_md5_finalize_prefix(struct md5_ctx *ctx, const uint8_t *data, uint32_t len, uint32_t n, uint8_t *out);
 
 #endif
diff --git a/cbits/cryptonite_sha1.c b/cbits/cryptonite_sha1.c
--- a/cbits/cryptonite_sha1.c
+++ b/cbits/cryptonite_sha1.c
@@ -216,3 +216,31 @@
 	store_be32(out+12, ctx->h[3]);
 	store_be32(out+16, ctx->h[4]);
 }
+
+#define HASHED(m) SHA1_##m
+#define HASHED_LOWER(m) sha1_##m
+#define CRYPTONITE_HASHED(m) cryptonite_sha1_##m
+#define SHA1_BLOCK_SIZE 64
+#define SHA1_BITS_ELEMS 1
+
+static inline uint32_t cryptonite_sha1_get_index(const struct sha1_ctx *ctx)
+{
+	return (uint32_t) (ctx->sz & 0x3f);
+}
+
+static inline void cryptonite_sha1_incr_sz(struct sha1_ctx *ctx, uint64_t *bits, uint32_t n)
+{
+	ctx->sz += n;
+	*bits = cpu_to_be64(ctx->sz << 3);
+}
+
+static inline void cryptonite_sha1_select_digest(const struct sha1_ctx *ctx, uint8_t *out, uint32_t out_mask)
+{
+	xor_be32(out   , ctx->h[0] & out_mask);
+	xor_be32(out+ 4, ctx->h[1] & out_mask);
+	xor_be32(out+ 8, ctx->h[2] & out_mask);
+	xor_be32(out+12, ctx->h[3] & out_mask);
+	xor_be32(out+16, ctx->h[4] & out_mask);
+}
+
+#include <cryptonite_hash_prefix.c>
diff --git a/cbits/cryptonite_sha1.h b/cbits/cryptonite_sha1.h
--- a/cbits/cryptonite_sha1.h
+++ b/cbits/cryptonite_sha1.h
@@ -41,5 +41,6 @@
 void cryptonite_sha1_init(struct sha1_ctx *ctx);
 void cryptonite_sha1_update(struct sha1_ctx *ctx, const uint8_t *data, uint32_t len);
 void cryptonite_sha1_finalize(struct sha1_ctx *ctx, uint8_t *out);
+void cryptonite_sha1_finalize_prefix(struct sha1_ctx *ctx, const uint8_t *data, uint32_t len, uint32_t n, uint8_t *out);
 
 #endif
diff --git a/cbits/cryptonite_sha256.c b/cbits/cryptonite_sha256.c
--- a/cbits/cryptonite_sha256.c
+++ b/cbits/cryptonite_sha256.c
@@ -161,6 +161,14 @@
 	memcpy(out, intermediate, SHA224_DIGEST_SIZE);
 }
 
+void cryptonite_sha224_finalize_prefix(struct sha224_ctx *ctx, const uint8_t *data, uint32_t len, uint32_t n, uint8_t *out)
+{
+	uint8_t intermediate[SHA256_DIGEST_SIZE];
+
+	cryptonite_sha256_finalize_prefix(ctx, data, len, n, intermediate);
+	memcpy(out, intermediate, SHA224_DIGEST_SIZE);
+}
+
 void cryptonite_sha256_finalize(struct sha256_ctx *ctx, uint8_t *out)
 {
 	static uint8_t padding[64] = { 0x80, };
@@ -182,3 +190,29 @@
 	for (i = 0; i < 8; i++)
 		store_be32(out+4*i, ctx->h[i]);
 }
+
+#define HASHED(m) SHA256_##m
+#define HASHED_LOWER(m) sha256_##m
+#define CRYPTONITE_HASHED(m) cryptonite_sha256_##m
+#define SHA256_BLOCK_SIZE 64
+#define SHA256_BITS_ELEMS 1
+
+static inline uint32_t cryptonite_sha256_get_index(const struct sha256_ctx *ctx)
+{
+	return (uint32_t) (ctx->sz & 0x3f);
+}
+
+static inline void cryptonite_sha256_incr_sz(struct sha256_ctx *ctx, uint64_t *bits, uint32_t n)
+{
+	ctx->sz += n;
+	*bits = cpu_to_be64(ctx->sz << 3);
+}
+
+static inline void cryptonite_sha256_select_digest(const struct sha256_ctx *ctx, uint8_t *out, uint32_t out_mask)
+{
+	uint32_t i;
+	for (i = 0; i < 8; i++)
+		xor_be32(out+4*i, ctx->h[i] & out_mask);
+}
+
+#include <cryptonite_hash_prefix.c>
diff --git a/cbits/cryptonite_sha256.h b/cbits/cryptonite_sha256.h
--- a/cbits/cryptonite_sha256.h
+++ b/cbits/cryptonite_sha256.h
@@ -47,9 +47,11 @@
 void cryptonite_sha224_init(struct sha224_ctx *ctx);
 void cryptonite_sha224_update(struct sha224_ctx *ctx, const uint8_t *data, uint32_t len);
 void cryptonite_sha224_finalize(struct sha224_ctx *ctx, uint8_t *out);
+void cryptonite_sha224_finalize_prefix(struct sha224_ctx *ctx, const uint8_t *data, uint32_t len, uint32_t n, uint8_t *out);
 
 void cryptonite_sha256_init(struct sha256_ctx *ctx);
 void cryptonite_sha256_update(struct sha256_ctx *ctx, const uint8_t *data, uint32_t len);
 void cryptonite_sha256_finalize(struct sha256_ctx *ctx, uint8_t *out);
+void cryptonite_sha256_finalize_prefix(struct sha256_ctx *ctx, const uint8_t *data, uint32_t len, uint32_t n, uint8_t *out);
 
 #endif
diff --git a/cbits/cryptonite_sha512.c b/cbits/cryptonite_sha512.c
--- a/cbits/cryptonite_sha512.c
+++ b/cbits/cryptonite_sha512.c
@@ -180,6 +180,14 @@
 	memcpy(out, intermediate, SHA384_DIGEST_SIZE);
 }
 
+void cryptonite_sha384_finalize_prefix(struct sha384_ctx *ctx, const uint8_t *data, uint32_t len, uint32_t n, uint8_t *out)
+{
+	uint8_t intermediate[SHA512_DIGEST_SIZE];
+
+	cryptonite_sha512_finalize_prefix(ctx, data, len, n, intermediate);
+	memcpy(out, intermediate, SHA384_DIGEST_SIZE);
+}
+
 void cryptonite_sha512_finalize(struct sha512_ctx *ctx, uint8_t *out)
 {
 	static uint8_t padding[128] = { 0x80, };
@@ -202,6 +210,38 @@
 	for (i = 0; i < 8; i++)
 		store_be64(out+8*i, ctx->h[i]);
 }
+
+#define HASHED(m) SHA512_##m
+#define HASHED_LOWER(m) sha512_##m
+#define CRYPTONITE_HASHED(m) cryptonite_sha512_##m
+#define SHA512_BLOCK_SIZE 128
+#define SHA512_BITS_ELEMS 2
+
+#include <cryptonite_hash_prefix.h>
+
+static inline uint32_t cryptonite_sha512_get_index(const struct sha512_ctx *ctx)
+{
+	return (uint32_t) (ctx->sz[0] & 0x7f);
+}
+
+static inline void cryptonite_sha512_incr_sz(struct sha512_ctx *ctx, uint64_t *bits, uint32_t n)
+{
+	ctx->sz[0] += n;
+	ctx->sz[1] += 1 & constant_time_lt_64(ctx->sz[0], n);
+	bits[0] = cpu_to_be64((ctx->sz[1] << 3 | ctx->sz[0] >> 61));
+	bits[1] = cpu_to_be64((ctx->sz[0] << 3));
+}
+
+static inline void cryptonite_sha512_select_digest(const struct sha512_ctx *ctx, uint8_t *out, uint32_t out_mask)
+{
+	uint32_t i;
+	uint64_t out_mask_64 = out_mask;
+	out_mask_64 |= out_mask_64 << 32;
+	for (i = 0; i < 8; i++)
+		xor_be64(out+8*i, ctx->h[i] & out_mask_64);
+}
+
+#include <cryptonite_hash_prefix.c>
 
 #include <stdio.h>
 
diff --git a/cbits/cryptonite_sha512.h b/cbits/cryptonite_sha512.h
--- a/cbits/cryptonite_sha512.h
+++ b/cbits/cryptonite_sha512.h
@@ -46,10 +46,12 @@
 void cryptonite_sha384_init(struct sha384_ctx *ctx);
 void cryptonite_sha384_update(struct sha384_ctx *ctx, const uint8_t *data, uint32_t len);
 void cryptonite_sha384_finalize(struct sha384_ctx *ctx, uint8_t *out);
+void cryptonite_sha384_finalize_prefix(struct sha384_ctx *ctx, const uint8_t *data, uint32_t len, uint32_t n, uint8_t *out);
 
 void cryptonite_sha512_init(struct sha512_ctx *ctx);
 void cryptonite_sha512_update(struct sha512_ctx *ctx, const uint8_t *data, uint32_t len);
 void cryptonite_sha512_finalize(struct sha512_ctx *ctx, uint8_t *out);
+void cryptonite_sha512_finalize_prefix(struct sha512_ctx *ctx, const uint8_t *data, uint32_t len, uint32_t n, uint8_t *out);
 
 /* only multiples of 8 are supported as valid t values */
 void cryptonite_sha512t_init(struct sha512_ctx *ctx, uint32_t hashlen);
diff --git a/cryptonite.cabal b/cryptonite.cabal
--- a/cryptonite.cabal
+++ b/cryptonite.cabal
@@ -1,5 +1,5 @@
 Name:                cryptonite
-version:             0.27
+version:             0.28
 Synopsis:            Cryptography Primitives sink
 Description:
     A repository of cryptographic primitives.
@@ -57,6 +57,7 @@
                      cbits/argon2/*.h
                      cbits/argon2/*.c
                      cbits/aes/x86ni_impl.c
+                     cbits/cryptonite_hash_prefix.c
                      tests/*.hs
 
 source-repository head
diff --git a/tests/Hash.hs b/tests/Hash.hs
--- a/tests/Hash.hs
+++ b/tests/Hash.hs
@@ -221,6 +221,24 @@
 runhashinc (HashAlg hashAlg) v = B.convertToBase B.Base16 $ hashinc $ v
   where hashinc = hashFinalize . foldl hashUpdate (hashInitWith hashAlg)
 
+data HashPrefixAlg = forall alg . HashAlgorithmPrefix alg => HashPrefixAlg alg
+
+expectedPrefix :: [ (String, HashPrefixAlg) ]
+expectedPrefix =
+    [ ("MD5", HashPrefixAlg MD5)
+    , ("SHA1", HashPrefixAlg SHA1)
+    , ("SHA224", HashPrefixAlg SHA224)
+    , ("SHA256", HashPrefixAlg SHA256)
+    , ("SHA384", HashPrefixAlg SHA384)
+    , ("SHA512", HashPrefixAlg SHA512)
+    ]
+
+runhashpfx :: HashPrefixAlg -> ByteString -> ByteString
+runhashpfx (HashPrefixAlg hashAlg) v = B.convertToBase B.Base16 $ hashWith hashAlg v
+
+runhashpfxpfx :: HashPrefixAlg -> ByteString -> Int -> ByteString
+runhashpfxpfx (HashPrefixAlg hashAlg) v len = B.convertToBase B.Base16 $ hashPrefixWith hashAlg v len
+
 makeTestAlg (name, hashAlg, results) =
     testGroup name $ concatMap maketest (zip3 is vectors results)
   where
@@ -236,6 +254,19 @@
         runhash hashAlg inp `propertyEq` runhashinc hashAlg (chunkS ckLen inp)
     ]
 
+makeTestPrefix (hashName, hashAlg) =
+    [ testProperty hashName $ \(ArbitraryBS0_2901 inp) (Int0_2901 len) ->
+        runhashpfx hashAlg (B.take len inp) `propertyEq` runhashpfxpfx hashAlg inp len
+    ]
+
+makeTestHybrid (hashName, HashPrefixAlg alg) =
+    [ testProperty hashName $ \(ArbitraryBS0_2901 start) (ArbitraryBS0_2901 end) -> do
+        len <- choose (0, B.length end)
+        let ref = hashWith alg (start `B.append` B.take len end)
+            hyb = hashFinalizePrefix (hashUpdate (hashInitWith alg) start) end len
+        return (ref `propertyEq` hyb)
+    ]
+
 -- SHAKE128 truncation example with expected byte at final position
 -- <https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/ShakeTruncation.pdf>
 shake128TruncationBytes = [0x01, 0x03, 0x07, 0x0f, 0x0f, 0x2f, 0x6f, 0x6f]
@@ -253,6 +284,8 @@
 tests = testGroup "hash"
     [ testGroup "KATs" (map makeTestAlg expected)
     , testGroup "Chunking" (concatMap makeTestChunk expected)
+    , testGroup "Prefix" (concatMap makeTestPrefix expectedPrefix)
+    , testGroup "Hybrid" (concatMap makeTestHybrid expectedPrefix)
     , testGroup "Truncating"
         [ testGroup "SHAKE128"
             (zipWith makeTestSHAKE128Truncation [1..] shake128TruncationBytes)
