diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,28 @@
 # Revision history for cryptostore
 
+## 0.3.0.0 - 2023-01-14
+
+* API change in PKCS5, PKCS8 and PKCS12 modules to handle better password-based
+  encryption derived from an empty password.  All encryption/decryption
+  functions now expect an opaque `ProtectionPassword` data type.  Conversion
+  functions `toProtectionPassword` and `fromProtectionPassword` are provided.
+  Additionnally in the PKCS12 module, the type `OptProtected` is replaced with
+  `OptAuthenticated` when dealing with password integrity.  Similarly at that
+  level, function `recover` is to be replaced with `recoverAuthenticated`.
+
+* Added support for KMAC (Keccak Message Authentication Code) in CMS
+  authenticated data, through constructors `KMAC_SHAKE128` and `KMAC_SHAKE256`.
+
+* CMS key agreement now supports derivation with HKDF along with X9.63.  Data
+  type `KeyAgreementParams` is modified to include a KDF instead of simply the
+  digest algorithm.  HKDF has assigned OIDs only for standard DH and cannot be
+  used with cofactor DH.
+
+* Added CMS utility functions to deal with the `signingTime` attribute.
+
+* Changed `withSignerCertificate` validation callback API to include the
+  `signingTime` value when available.
+
 ## 0.2.3.0 - 2022-11-05
 
 * Fix RC2 on big-endian architectures
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2018-2022, Olivier Chéron
+Copyright (c) 2018-2023, Olivier Chéron
 
 All rights reserved.
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
 # cryptostore
 
-[![Build Status](https://travis-ci.org/ocheron/cryptostore.png?branch=master)](https://travis-ci.org/ocheron/cryptostore)
-[![BSD](https://b.repl.ca/v1/license-BSD-blue.png)](https://en.wikipedia.org/wiki/BSD_licenses)
-[![Haskell](https://b.repl.ca/v1/language-haskell-lightgrey.png)](https://haskell.org/)
+[![Build Status](https://github.com/ocheron/cryptostore/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/ocheron/cryptostore/actions/workflows/tests.yml)
+[![BSD](https://img.shields.io/badge/License-BSD-blue)](https://en.wikipedia.org/wiki/BSD_licenses)
+[![Haskell](https://img.shields.io/badge/Language-Haskell-lightgrey)](https://haskell.org/)
 
 This package allows to read and write cryptographic objects to/from ASN.1.
 
@@ -50,8 +50,8 @@
 > :set -XOverloadedStrings
 > :m Crypto.PubKey.RSA Crypto.Store.PKCS8 Data.X509 Crypto.Store.PKCS5
 > privKey <- PrivKeyRSA . snd <$> generate (2048 `div` 8) 0x10001
-> salt <- generateSalt 8
-> let kdf = PBKDF2 salt 2048 Nothing PBKDF2_SHA256
+> salt <- generateSalt 16
+> let kdf = PBKDF2 salt 200000 Nothing PBKDF2_SHA256
 > encParams <- generateEncryptionParams (CBC AES256)
 > let pbes = PBES2 (PBES2Parameter kdf encParams)
 > writeEncryptedKeyFile "/path/to/privkey.pem" pbes "mypassword" privKey
@@ -59,8 +59,8 @@
 ```
 
 Parameters used in this example are AES-256-CBC as cipher, PBKDF2 as
-key-derivation function, with an 8-byte salt, 2048 iterations and SHA-256 as
-pseudorandom function.
+key-derivation function, with a 16-byte salt, 200,000 iterations and SHA-256
+as pseudorandom function.
 
 ## Public Keys and Signed Objects
 
@@ -92,13 +92,28 @@
 API to read PKCS #12 files requires some password at each layer.  This API is
 available in module `Crypto.Store.PKCS12`.
 
+Reading a binary PKCS #12 file using a single password doing both integrity
+and privacy (usual case):
+
+```haskell
+> :set -XOverloadedStrings
+> :m Crypto.Store.PKCS12
+> Right p12 <- readP12File "/path/to/file.p12"
+> let Right (password, pkcs12) = recoverAuthenticated "mypassword" p12
+> let Right contents = recover password (unPKCS12 pkcs12)
+> getAllSafeX509Certs contents
+[SignedExact {getSigned = ...}]
+> recover password (getAllSafeKeys contents)
+Right [PrivKeyRSA ...]
+```
+
 Reading a binary PKCS #12 file using distinct integrity and privacy passwords:
 
 ```haskell
 > :set -XOverloadedStrings
 > :m Crypto.Store.PKCS12
 > Right p12 <- readP12File "/path/to/file.p12"
-> let Right pkcs12 = recover "myintegrityassword" p12
+> let Right (_, pkcs12) = recoverAuthenticated "myintegritypassword" p12
 > let Right contents = recover "myprivacypassword" (unPKCS12 pkcs12)
 > getAllSafeX509Certs contents
 [SignedExact {getSigned = ...}]
@@ -122,15 +137,15 @@
 >     contents = SafeContents [keyBag]
 
 -- Encrypt the contents
-> salt <- generateSalt 8
-> let kdf = PBKDF2 salt 2048 Nothing PBKDF2_SHA256
+> salt <- generateSalt 16
+> let kdf = PBKDF2 salt 200000 Nothing PBKDF2_SHA256
 > encParams <- generateEncryptionParams (CBC AES256)
 > let pbes = PBES2 (PBES2Parameter kdf encParams)
 >     Right pkcs12 = encrypted pbes "mypassword" contents
 
 -- Save to PKCS #12 with integrity protection (same password)
-> salt' <- generateSalt 8
-> let iParams = (DigestAlgorithm SHA256, PBEParameter salt' 2048)
+> salt' <- generateSalt 16
+> let iParams = (DigestAlgorithm SHA256, PBEParameter salt' 200000)
 > writeP12File "/path/to/privkey.p12" iParams "mypassword" pkcs12
 Right ()
 ```
@@ -144,28 +159,28 @@
 
 -- Read PKCS #12 content as credential
 > Right p12 <- readP12File "/path/to/file.p12"
-> let Right pkcs12 = recover "myintegrityassword" p12
+> let Right (_, pkcs12) = recoverAuthenticated "myintegritypassword" p12
 > let Right (Just cred) = recover "myprivacypassword" (toCredential pkcs12)
 > cred
 (CertificateChain [...], PrivKeyRSA (...))
 
 -- Scheme to reencrypt the key
-> saltK <- generateSalt 8
-> let kdfK = PBKDF2 saltK 2048 Nothing PBKDF2_SHA256
+> saltK <- generateSalt 16
+> let kdfK = PBKDF2 saltK 200000 Nothing PBKDF2_SHA256
 > encParamsK <- generateEncryptionParams (CBC AES256)
 > let sKey = PBES2 (PBES2Parameter kdfK encParamsK)
 
 -- Scheme to reencrypt the certificate chain
 > saltC <- generateSalt 8
-> let kdfC = PBKDF2 saltC 1024 Nothing PBKDF2_SHA256
+> let kdfC = PBKDF2 saltC 100000 Nothing PBKDF2_SHA256
 > encParamsC <- generateEncryptionParams (CBC AES128)
 > let sCert = PBES2 (PBES2Parameter kdfC encParamsC)
 
 -- Write the content back to a new file
 > let Right pkcs12' = fromCredential (Just sCert) sKey "myprivacypassword" cred
-> salt <- generateSalt 8
-> let iParams = (DigestAlgorithm SHA256, PBEParameter salt 2048)
-> writeP12File "/path/to/newfile.p12" iParams "myintegrityassword" pkcs12'
+> salt <- generateSalt 16
+> let iParams = (DigestAlgorithm SHA256, PBEParameter salt 200000)
+> writeP12File "/path/to/newfile.p12" iParams "myintegritypassword" pkcs12'
 ```
 
 Variants `toNamedCredential` and `fromNamedCredential` are also available when
@@ -214,8 +229,8 @@
 -- Encrypt the Content Encryption Key with a Password Recipient Info,
 -- i.e. a KDF will derive the Key Encryption Key from a password
 -- that the recipient will need to know
-> salt <- generateSalt 8
-> let kdf = PBKDF2 salt 2048 Nothing PBKDF2_SHA256
+> salt <- generateSalt 16
+> let kdf = PBKDF2 salt 200000 Nothing PBKDF2_SHA256
 > keParams <- generateEncryptionParams (CBC AES128)
 > let pri = forPasswordRecipient "mypassword" kdf (PWRIKEK keParams)
 
@@ -289,7 +304,7 @@
 > :m Crypto.Store.CMS Data.Default.Class
 > [SignedDataCI signedEncapData] <- readCMSFile "/path/to/signed.pem"
 > signedData <- fromAttached signedEncapData
-> let doValidation chain = null <$> validateNoFQHN store def noServiceID chain
+> let doValidation _ chain = null <$> validateNoFQHN store def noServiceID chain
 > verifySignedData (withSignerCertificate doValidation) signedData
 Right (DataCI "Some trustworthy content")
 ```
diff --git a/cryptostore.cabal b/cryptostore.cabal
--- a/cryptostore.cabal
+++ b/cryptostore.cabal
@@ -1,5 +1,5 @@
 name:                cryptostore
-version:             0.2.3.0
+version:             0.3.0.0
 synopsis:            Serialization of cryptographic data types
 description:         Haskell implementation of PKCS \#8, PKCS \#12 and CMS
                      (Cryptographic Message Syntax).
diff --git a/src/Crypto/Store/CMS.hs b/src/Crypto/Store/CMS.hs
--- a/src/Crypto/Store/CMS.hs
+++ b/src/Crypto/Store/CMS.hs
@@ -21,6 +21,7 @@
 -- * <https://tools.ietf.org/html/rfc8103 RFC 8103>: Using ChaCha20-Poly1305 Authenticated Encryption in the Cryptographic Message Syntax (CMS)
 -- * <https://tools.ietf.org/html/rfc8418 RFC 8418>: Use of the Elliptic Curve Diffie-Hellman Key Agreement Algorithm with X25519 and X448 in the Cryptographic Message Syntax (CMS)
 -- * <https://tools.ietf.org/html/rfc8419 RFC 8419>: Use of Edwards-Curve Digital Signature Algorithm (EdDSA) Signatures in the Cryptographic Message Syntax (CMS)
+-- * <https://tools.ietf.org/html/rfc8702 RFC 8702>: Use of the SHAKE One-Way Hash Functions in the Cryptographic Message Syntax (CMS)
 {-# LANGUAGE RecordWildCards #-}
 module Crypto.Store.CMS
     ( ContentType(..)
@@ -60,6 +61,7 @@
     , KeyEncryptionParams(..)
     , KeyTransportParams(..)
     , KeyAgreementParams(..)
+    , KeyAgreementKDF(..)
     , RecipientInfo(..)
     , EnvelopedData(..)
     , ProducerOfRI
@@ -144,6 +146,10 @@
     , findAttribute
     , setAttribute
     , filterAttributes
+    -- * CMS standard attributes
+    , getSigningTimeAttr
+    , setSigningTimeAttr
+    , setSigningTimeAttrCurrent
     -- * Originator information
     , OriginatorInfo(..)
     , CertificateChoice(..)
@@ -158,7 +164,7 @@
 import Data.ASN1.Encoding
 import Data.ByteString (ByteString)
 import Data.Maybe (isJust)
-import Data.List (nub, unzip3)
+import Data.List (nub)
 
 import Crypto.Hash
 
diff --git a/src/Crypto/Store/CMS/Algorithms.hs b/src/Crypto/Store/CMS/Algorithms.hs
--- a/src/Crypto/Store/CMS/Algorithms.hs
+++ b/src/Crypto/Store/CMS/Algorithms.hs
@@ -63,6 +63,7 @@
     , transportEncrypt
     , transportDecrypt
     , KeyAgreementParams(..)
+    , KeyAgreementKDF(..)
     , ECDHPair
     , ecdhGenerate
     , ecdhPublic
@@ -109,9 +110,11 @@
 import           Crypto.ECC (Curve_X25519, Curve_X448, ecdh)
 import           Crypto.Error
 import qualified Crypto.Hash as Hash
+import qualified Crypto.KDF.HKDF as HKDF
 import qualified Crypto.KDF.PBKDF2 as PBKDF2
 import qualified Crypto.KDF.Scrypt as Scrypt
 import qualified Crypto.MAC.HMAC as HMAC
+import qualified Crypto.MAC.KMAC as KMAC
 import qualified Crypto.MAC.Poly1305 as Poly1305
 import           Crypto.Number.Serialize
 import qualified Crypto.PubKey.Curve25519 as X25519
@@ -173,7 +176,27 @@
     -- | SHAKE256 (variable size)
     SHAKE256 :: KnownNat n => Proxy n -> DigestProxy (Hash.SHAKE256 n)
 
-deriving instance Show (DigestProxy hashAlg)
+
+instance Show (DigestProxy hashAlg) where
+    showsPrec _ MD2          = showString "MD2"
+    showsPrec _ MD4          = showString "MD4"
+    showsPrec _ MD5          = showString "MD5"
+    showsPrec _ SHA1         = showString "SHA1"
+    showsPrec _ SHA224       = showString "SHA224"
+    showsPrec _ SHA256       = showString "SHA256"
+    showsPrec _ SHA384       = showString "SHA384"
+    showsPrec _ SHA512       = showString "SHA512"
+    showsPrec _ SHAKE128_256 = showString "SHAKE128_256"
+    showsPrec _ SHAKE256_512 = showString "SHAKE256_512"
+    showsPrec d (SHAKE128 p) =
+        showParen (d > 10) $ showString "SHAKE128 " . showNat 11 p
+    showsPrec d (SHAKE256 p) =
+        showParen (d > 10) $ showString "SHAKE256 " . showNat 11 p
+
+showNat :: KnownNat n => Int -> Proxy n -> ShowS
+showNat d n = showParen (d > 0) $
+    shows n . showString " :: Proxy " . shows (natVal n)
+
 deriving instance Eq (DigestProxy hashAlg)
 
 instance HasStrength (DigestProxy hashAlg) where
@@ -300,9 +323,9 @@
     parseParameter Type_SHAKE128_256 = parseDigestParam (DigestAlgorithm SHAKE128_256)
     parseParameter Type_SHAKE256_512 = parseDigestParam (DigestAlgorithm SHAKE256_512)
     parseParameter Type_SHAKE128_Len = parseBitLen $
-        \(SomeNat p) -> DigestAlgorithm (SHAKE128 p)
+        \(SomeNat p) -> return $ DigestAlgorithm (SHAKE128 p)
     parseParameter Type_SHAKE256_Len = parseBitLen $
-        \(SomeNat p) -> DigestAlgorithm (SHAKE256 p)
+        \(SomeNat p) -> return $ DigestAlgorithm (SHAKE256 p)
 
 -- | Compute the digest of a message.
 digest :: ByteArrayAccess message => DigestAlgorithm -> message -> ByteString
@@ -318,13 +341,16 @@
 parseDigestParam :: Monoid e => DigestAlgorithm -> ParseASN1 e DigestAlgorithm
 parseDigestParam p = getNextMaybe nullOrNothing >> return p
 
-parseBitLen :: Monoid e => (SomeNat -> a) -> ParseASN1 e a
-parseBitLen build = do
+parseBitLen :: Monoid e => (SomeNat -> ParseASN1 e a) -> ParseASN1 e a
+parseBitLen fn = do
     IntVal n <- getNext
     case someNatVal n of
         Nothing -> throwParseError ("Invalid bit length: " ++ show n)
-        Just sn -> return (build sn)
+        Just sn -> fn sn
 
+p256 :: Proxy 256
+p256 = Proxy
+
 p512 :: Proxy 512
 p512 = Proxy
 
@@ -384,52 +410,115 @@
 -- | Message authentication code.  Equality is time constant.
 type MessageAuthenticationCode = AuthTag
 
+data MACType
+    = forall hashAlg . Hash.HashAlgorithm hashAlg
+        => TypeHMAC (DigestProxy hashAlg)
+    | TypeKMAC_SHAKE128
+    | TypeKMAC_SHAKE256
+
+deriving instance Show MACType
+
 -- | Message Authentication Code (MAC) Algorithm.
 data MACAlgorithm
     = forall hashAlg . Hash.HashAlgorithm hashAlg
         => HMAC (DigestProxy hashAlg)
+    | forall n . KnownNat n => KMAC_SHAKE128 (Proxy n) ByteString
+    | forall n . KnownNat n => KMAC_SHAKE256 (Proxy n) ByteString
 
-deriving instance Show MACAlgorithm
+instance Show MACAlgorithm where
+    showsPrec d (HMAC p) = showParen (d > 10) $
+        showString "HMAC " . showsPrec 11 p
+    showsPrec d (KMAC_SHAKE128 p s) = showParen (d > 10) $
+        showString "KMAC_SHAKE128 " . showNat 11 p .
+            showChar ' ' . showsPrec 11 s
+    showsPrec d (KMAC_SHAKE256 p s) = showParen (d > 10) $
+        showString "KMAC_SHAKE256 " . showNat 11 p .
+            showChar ' ' . showsPrec 11 s
 
 instance Eq MACAlgorithm where
-    HMAC a1 == HMAC a2 = DigestAlgorithm a1 == DigestAlgorithm a2
+    HMAC a1             == HMAC a2             =
+        DigestAlgorithm a1 == DigestAlgorithm a2
+    KMAC_SHAKE128 p1 s1 == KMAC_SHAKE128 p2 s2 =
+        natVal p1 == natVal p2 && s1 == s2
+    KMAC_SHAKE256 p1 s1 == KMAC_SHAKE256 p2 s2 =
+        natVal p1 == natVal p2 && s1 == s2
+    _                   == _                   = False
 
 instance HasStrength MACAlgorithm where
     getSecurityBits (HMAC a) = getSecurityBits (DigestAlgorithm a)
+    getSecurityBits (KMAC_SHAKE128 p _) = shakeSecurityBits 128 p
+    getSecurityBits (KMAC_SHAKE256 p _) = shakeSecurityBits 256 p
 
-instance Enumerable MACAlgorithm where
-    values = [ HMAC MD5
-             , HMAC SHA1
-             , HMAC SHA224
-             , HMAC SHA256
-             , HMAC SHA384
-             , HMAC SHA512
+instance Enumerable MACType where
+    values = [ TypeHMAC MD5
+             , TypeHMAC SHA1
+             , TypeHMAC SHA224
+             , TypeHMAC SHA256
+             , TypeHMAC SHA384
+             , TypeHMAC SHA512
+             , TypeKMAC_SHAKE128
+             , TypeKMAC_SHAKE256
              ]
 
-instance OIDable MACAlgorithm where
-    getObjectID (HMAC MD5)    = [1,3,6,1,5,5,8,1,1]
-    getObjectID (HMAC SHA1)   = [1,3,6,1,5,5,8,1,2]
-    getObjectID (HMAC SHA224) = [1,2,840,113549,2,8]
-    getObjectID (HMAC SHA256) = [1,2,840,113549,2,9]
-    getObjectID (HMAC SHA384) = [1,2,840,113549,2,10]
-    getObjectID (HMAC SHA512) = [1,2,840,113549,2,11]
+instance OIDable MACType where
+    getObjectID (TypeHMAC MD5)    = [1,3,6,1,5,5,8,1,1]
+    getObjectID (TypeHMAC SHA1)   = [1,3,6,1,5,5,8,1,2]
+    getObjectID (TypeHMAC SHA224) = [1,2,840,113549,2,8]
+    getObjectID (TypeHMAC SHA256) = [1,2,840,113549,2,9]
+    getObjectID (TypeHMAC SHA384) = [1,2,840,113549,2,10]
+    getObjectID (TypeHMAC SHA512) = [1,2,840,113549,2,11]
+    getObjectID TypeKMAC_SHAKE128 = [2,16,840,1,101,3,4,2,19]
+    getObjectID TypeKMAC_SHAKE256 = [2,16,840,1,101,3,4,2,20]
 
     getObjectID ty = error ("Unsupported MACAlgorithm: " ++ show ty)
 
-instance OIDNameable MACAlgorithm where
+instance OIDNameable MACType where
     fromObjectID oid = unOIDNW <$> fromObjectID oid
 
 instance AlgorithmId MACAlgorithm where
-    type AlgorithmType MACAlgorithm = MACAlgorithm
+    type AlgorithmType MACAlgorithm = MACType
     algorithmName _  = "mac algorithm"
-    algorithmType    = id
-    parameterASN1S _ = id
-    parseParameter p = getNextMaybe nullOrNothing >> return p
 
+    algorithmType (HMAC alg)          = TypeHMAC alg
+    algorithmType (KMAC_SHAKE128 _ _) = TypeKMAC_SHAKE128
+    algorithmType (KMAC_SHAKE256 _ _) = TypeKMAC_SHAKE256
+
+    parameterASN1S (HMAC _)              = id
+    parameterASN1S (KMAC_SHAKE128 p str) = kmacASN1S 256 p str
+    parameterASN1S (KMAC_SHAKE256 p str) = kmacASN1S 512 p str
+
+    parseParameter (TypeHMAC alg)    = getNextMaybe nullOrNothing >> return (HMAC alg)
+    parseParameter TypeKMAC_SHAKE128 = parseKMAC p256 KMAC_SHAKE128
+    parseParameter TypeKMAC_SHAKE256 = parseKMAC p512 KMAC_SHAKE256
+
+kmacASN1S :: (KnownNat n, ASN1Elem e)
+          => Integer -> proxy n -> ByteString -> ASN1Stream e
+kmacASN1S n p str
+    | B.null str && n == i = id
+    | otherwise = asn1Container Sequence (gIntVal i . gOctetString str)
+  where i = natVal p
+
+parseKMAC :: (Monoid e, KnownNat n')
+          => Proxy n'
+          -> (forall n . KnownNat n => Proxy n -> ByteString -> MACAlgorithm)
+          -> ParseASN1 e MACAlgorithm
+parseKMAC p' fn = do
+    b <- hasNext
+    if b then parseParams else return (fn p' B.empty)
+  where
+    parseParams = onNextContainer Sequence $ parseBitLen $ \(SomeNat p) ->
+        getNext >>= \(OctetString str) -> return (fn p str)
+
 instance HasKeySize MACAlgorithm where
     getKeySizeSpecifier (HMAC a) = KeySizeFixed (digestSizeFromProxy a)
-      where digestSizeFromProxy = Hash.hashDigestSize . hashFromProxy
+    getKeySizeSpecifier (KMAC_SHAKE128 p _) =
+        KeySizeFixed (digestSizeFromProxy (SHAKE128 p))
+    getKeySizeSpecifier (KMAC_SHAKE256 p _) =
+        KeySizeFixed (digestSizeFromProxy (SHAKE256 p))
 
+digestSizeFromProxy :: Hash.HashAlgorithm a => proxy a -> Int
+digestSizeFromProxy = Hash.hashDigestSize . hashFromProxy
+
 -- | Invoke the MAC function.
 mac :: (ByteArrayAccess key, ByteArrayAccess message)
      => MACAlgorithm -> key -> message -> MessageAuthenticationCode
@@ -441,7 +530,19 @@
         => proxy a -> k -> m -> HMAC.HMAC a
     runHMAC _ = HMAC.hmac
 
+mac (KMAC_SHAKE128 p str) = \key -> AuthTag . B.convert . run128 p str key
+  where
+    run128 :: (KnownNat n, ByteArrayAccess k, ByteArrayAccess m)
+           => proxy n -> ByteString -> k -> m -> KMAC.KMAC (Hash.SHAKE128 n)
+    run128 _ = KMAC.kmac
 
+mac (KMAC_SHAKE256 p str) = \key -> AuthTag . B.convert . run256 p str key
+  where
+    run256 :: (KnownNat n, ByteArrayAccess k, ByteArrayAccess m)
+           => proxy n -> ByteString -> k -> m -> KMAC.KMAC (Hash.SHAKE256 n)
+    run256 _ = KMAC.kmac
+
+
 -- Content encryption
 
 -- | CMS content encryption cipher.
@@ -1554,46 +1655,70 @@
 
 -- Key agreement
 
-data KeyAgreementType = TypeStdDH DigestAlgorithm
-                      | TypeCofactorDH DigestAlgorithm
+-- | Key derivation function used for key agreement.
+data KeyAgreementKDF
+    = forall hashAlg . Hash.HashAlgorithm hashAlg
+      => KA_X963_KDF (DigestProxy hashAlg)
+      -- ^ ANSI-X9.63-KDF key derivation function
+    | forall hashAlg . Hash.HashAlgorithm hashAlg
+       => KA_HKDF (DigestProxy hashAlg)
+      -- ^ Extract-and-Expand HMAC-based key derivation function
+
+deriving instance Show KeyAgreementKDF
+
+instance Eq KeyAgreementKDF where
+    KA_X963_KDF a == KA_X963_KDF b = DigestAlgorithm a == DigestAlgorithm b
+    KA_HKDF a     == KA_HKDF b     = DigestAlgorithm a == DigestAlgorithm b
+    _             == _             = False
+
+data KeyAgreementType = TypeStdDH KeyAgreementKDF
+                      | TypeCofactorDH KeyAgreementKDF
                       deriving (Show,Eq)
 
 instance Enumerable KeyAgreementType where
-    values = [ TypeStdDH (DigestAlgorithm SHA1)
-             , TypeStdDH (DigestAlgorithm SHA224)
-             , TypeStdDH (DigestAlgorithm SHA256)
-             , TypeStdDH (DigestAlgorithm SHA384)
-             , TypeStdDH (DigestAlgorithm SHA512)
+    values = [ TypeStdDH (KA_X963_KDF SHA1)
+             , TypeStdDH (KA_X963_KDF SHA224)
+             , TypeStdDH (KA_X963_KDF SHA256)
+             , TypeStdDH (KA_X963_KDF SHA384)
+             , TypeStdDH (KA_X963_KDF SHA512)
 
-             , TypeCofactorDH (DigestAlgorithm SHA1)
-             , TypeCofactorDH (DigestAlgorithm SHA224)
-             , TypeCofactorDH (DigestAlgorithm SHA256)
-             , TypeCofactorDH (DigestAlgorithm SHA384)
-             , TypeCofactorDH (DigestAlgorithm SHA512)
+             , TypeCofactorDH (KA_X963_KDF SHA1)
+             , TypeCofactorDH (KA_X963_KDF SHA224)
+             , TypeCofactorDH (KA_X963_KDF SHA256)
+             , TypeCofactorDH (KA_X963_KDF SHA384)
+             , TypeCofactorDH (KA_X963_KDF SHA512)
+
+             , TypeStdDH (KA_HKDF SHA256)
+             , TypeStdDH (KA_HKDF SHA384)
+             , TypeStdDH (KA_HKDF SHA512)
              ]
 
 instance OIDable KeyAgreementType where
-    getObjectID (TypeStdDH (DigestAlgorithm SHA1))        = [1,3,133,16,840,63,0,2]
-    getObjectID (TypeStdDH (DigestAlgorithm SHA224))      = [1,3,132,1,11,0]
-    getObjectID (TypeStdDH (DigestAlgorithm SHA256))      = [1,3,132,1,11,1]
-    getObjectID (TypeStdDH (DigestAlgorithm SHA384))      = [1,3,132,1,11,2]
-    getObjectID (TypeStdDH (DigestAlgorithm SHA512))      = [1,3,132,1,11,3]
+    getObjectID (TypeStdDH (KA_X963_KDF SHA1))        = [1,3,133,16,840,63,0,2]
+    getObjectID (TypeStdDH (KA_X963_KDF SHA224))      = [1,3,132,1,11,0]
+    getObjectID (TypeStdDH (KA_X963_KDF SHA256))      = [1,3,132,1,11,1]
+    getObjectID (TypeStdDH (KA_X963_KDF SHA384))      = [1,3,132,1,11,2]
+    getObjectID (TypeStdDH (KA_X963_KDF SHA512))      = [1,3,132,1,11,3]
 
-    getObjectID (TypeCofactorDH (DigestAlgorithm SHA1))   = [1,3,133,16,840,63,0,3]
-    getObjectID (TypeCofactorDH (DigestAlgorithm SHA224)) = [1,3,132,1,14,0]
-    getObjectID (TypeCofactorDH (DigestAlgorithm SHA256)) = [1,3,132,1,14,1]
-    getObjectID (TypeCofactorDH (DigestAlgorithm SHA384)) = [1,3,132,1,14,2]
-    getObjectID (TypeCofactorDH (DigestAlgorithm SHA512)) = [1,3,132,1,14,3]
+    getObjectID (TypeCofactorDH (KA_X963_KDF SHA1))   = [1,3,133,16,840,63,0,3]
+    getObjectID (TypeCofactorDH (KA_X963_KDF SHA224)) = [1,3,132,1,14,0]
+    getObjectID (TypeCofactorDH (KA_X963_KDF SHA256)) = [1,3,132,1,14,1]
+    getObjectID (TypeCofactorDH (KA_X963_KDF SHA384)) = [1,3,132,1,14,2]
+    getObjectID (TypeCofactorDH (KA_X963_KDF SHA512)) = [1,3,132,1,14,3]
 
+    getObjectID (TypeStdDH (KA_HKDF SHA256))  = [1,2,840,113549,1,9,16,3,19]
+    getObjectID (TypeStdDH (KA_HKDF SHA384))  = [1,2,840,113549,1,9,16,3,20]
+    getObjectID (TypeStdDH (KA_HKDF SHA512))  = [1,2,840,113549,1,9,16,3,21]
+
     getObjectID ty = error ("Unsupported KeyAgreementType: " ++ show ty)
 
 instance OIDNameable KeyAgreementType where
     fromObjectID oid = unOIDNW <$> fromObjectID oid
 
 -- | Key agreement algorithm with associated parameters.
-data KeyAgreementParams = StdDH DigestAlgorithm KeyEncryptionParams
+data KeyAgreementParams = StdDH KeyAgreementKDF KeyEncryptionParams
                           -- ^ 1-Pass D-H with Stardard ECDH
-                        | CofactorDH DigestAlgorithm KeyEncryptionParams
+                        | CofactorDH KeyAgreementKDF KeyEncryptionParams
                           -- ^ 1-Pass D-H with Cofactor ECDH
                         deriving (Show,Eq)
 
@@ -1611,21 +1736,31 @@
     parseParameter (TypeCofactorDH d) = CofactorDH d <$> parseAlgorithm Sequence
 
 ecdhKeyMaterial :: (ByteArrayAccess bin, ByteArray bout)
-                => DigestAlgorithm -> KeyEncryptionParams -> Maybe ByteString -> bin -> bout
-ecdhKeyMaterial (DigestAlgorithm hashAlg) kep ukm zz
-    | r == 0    = B.concat (map chunk [1..d])
-    | otherwise = B.concat (map chunk [1..d]) `B.append` B.take r (chunk $ succ d)
-  where
-    (d, r)   = outLen `divMod` Hash.hashDigestSize prx
+                => KeyAgreementKDF -> KeyEncryptionParams -> Maybe ByteString -> bin -> bout
+ecdhKeyMaterial kdf kep ukm zz =
+    case kdf of
+        KA_HKDF hashAlg ->
+            HKDF.expand (extract hashAlg zz) otherInfo outLen
+        KA_X963_KDF hashAlg
+            | r == 0    -> B.concat (map chunk [1..d])
+            | otherwise -> B.concat (map chunk [1..d]) `B.append` B.take r (chunk $ succ d)
+          where
+            (d, r)   = outLen `divMod` Hash.hashDigestSize prx
+            prx      = hashFromProxy hashAlg
 
-    prx      = hashFromProxy hashAlg
+            chunk     = B.convert . Hash.hashFinalize . hashCtx
+            hashCtx'  = Hash.hashUpdate (Hash.hashInitWith prx) zz
+            hashCtx i = Hash.hashUpdate (Hash.hashUpdate hashCtx' $ toWord32 i) otherInfo
+
+  where
     outLen   = getMaximumKeySize kep
     outBits  = 8 * outLen
     toWord32 = i2ospOf_ 4 . fromIntegral
 
-    chunk     = B.convert . Hash.hashFinalize . hashCtx
-    hashCtx'  = Hash.hashInitWith prx
-    hashCtx i = Hash.hashUpdate (Hash.hashUpdate (Hash.hashUpdate hashCtx' zz) (toWord32 i)) otherInfo
+    extract :: (Hash.HashAlgorithm a, ByteArrayAccess ikm)
+            => DigestProxy a -> ikm -> HKDF.PRK a
+    extract _ = HKDF.extract (fromMaybe B.empty ukm)
+
     otherInfo =
         let ki  = algorithmASN1S Sequence kep
             eui = case ukm of
diff --git a/src/Crypto/Store/CMS/Attribute.hs b/src/Crypto/Store/CMS/Attribute.hs
--- a/src/Crypto/Store/CMS/Attribute.hs
+++ b/src/Crypto/Store/CMS/Attribute.hs
@@ -25,12 +25,20 @@
     , setContentTypeAttr
     , getMessageDigestAttr
     , setMessageDigestAttr
+    , getSigningTimeAttr
+    , setSigningTimeAttr
+    , setSigningTimeAttrCurrent
     ) where
 
+import Control.Monad.IO.Class
+
 import Data.ASN1.Types
 import Data.ByteString (ByteString)
+import Data.Hourglass
 import Data.Maybe (fromMaybe)
 
+import System.Hourglass (dateCurrent)
+
 import Crypto.Store.ASN1.Generate
 import Crypto.Store.ASN1.Parse
 import Crypto.Store.CMS.Type
@@ -124,3 +132,36 @@
 -- | Add or replace the @messageDigest@ attribute in a list of attributes.
 setMessageDigestAttr :: ByteString -> [Attribute] -> [Attribute]
 setMessageDigestAttr d = setAttributeASN1S messageDigest (gOctetString d)
+
+
+-- Signing time
+
+signingTime :: OID
+signingTime = [1,2,840,113549,1,9,5]
+
+-- | Return the value of the @signingTime@ attribute.
+getSigningTimeAttr :: [Attribute] -> Maybe DateTime
+getSigningTimeAttr attrs = runParseAttribute signingTime attrs $ do
+    ASN1Time _ t offset <- getNext
+    let validOffset = maybe True (== TimezoneOffset 0) offset
+    if validOffset
+        then return t
+        else fail "getSigningTimeAttr: invalid timezone"
+
+-- | Add or replace the @signingTime@ attribute in a list of attributes.
+setSigningTimeAttr :: DateTime -> [Attribute] -> [Attribute]
+setSigningTimeAttr t =
+    let normalize val = val { dtTime = (dtTime val) { todNSec = 0 } }
+        offset = Just (TimezoneOffset 0)
+        ty | t >= timeConvert (Date 2050 January 1) = TimeGeneralized
+           | t <  timeConvert (Date 1950 January 1) = TimeGeneralized
+           | otherwise                              = TimeUTC
+     in setAttributeASN1S signingTime (gASN1Time ty (normalize t) offset)
+
+-- | Add or replace the @signingTime@ attribute in a list of attributes with the
+-- current time.  This is equivalent to calling 'setSigningTimeAttr' with the
+-- result of 'dateCurrent'.
+setSigningTimeAttrCurrent :: MonadIO m => [Attribute] -> m [Attribute]
+setSigningTimeAttrCurrent attrs = do
+    t <- liftIO dateCurrent
+    return (setSigningTimeAttr t attrs)
diff --git a/src/Crypto/Store/CMS/Encrypted.hs b/src/Crypto/Store/CMS/Encrypted.hs
--- a/src/Crypto/Store/CMS/Encrypted.hs
+++ b/src/Crypto/Store/CMS/Encrypted.hs
@@ -101,5 +101,3 @@
     parseEncryptedContent = parseWrapped <|> parsePrimitive
     parseWrapped  = onNextContainer (Container Context 0) parseOctetStrings
     parsePrimitive = do Other Context 0 bs <- getNext; return bs
-    parseOctetString = do OctetString bs <- getNext; return bs
-    parseOctetStrings = B.concat <$> getMany parseOctetString
diff --git a/src/Crypto/Store/CMS/Enveloped.hs b/src/Crypto/Store/CMS/Enveloped.hs
--- a/src/Crypto/Store/CMS/Enveloped.hs
+++ b/src/Crypto/Store/CMS/Enveloped.hs
@@ -81,6 +81,10 @@
 --
 -- Some key-derivation functions add restrictions to what characters
 -- are supported.
+--
+-- Beware: 'Data.String.fromString' truncates multi-byte characters.
+-- If the string may contain non-ASCII characters, prefer instead
+-- @'Crypto.Store.PKCS5.fromProtectionPassword' . 'Data.String.fromString'@.
 type Password = ByteString
 
 -- | Union type related to identification of the recipient.
@@ -98,8 +102,7 @@
     parse = parseIASN <|> parseSKI
       where parseIASN = RecipientIASN <$> parse
             parseSKI  = RecipientSKI  <$>
-                onNextContainer (Container Context 0) parseBS
-            parseBS = do { OctetString bs <- getNext; return bs }
+                onNextContainer (Container Context 0) parseOctetStringPrim
 
 getKTVersion :: RecipientIdentifier -> Integer
 getKTVersion (RecipientIASN _) = 0
@@ -173,8 +176,7 @@
     parse = parseIASN <|> parseSKI <|> parsePublic
       where parseIASN = OriginatorIASN <$> parse
             parseSKI  = OriginatorSKI  <$>
-                onNextContainer (Container Context 0) parseBS
-            parseBS = do { OctetString bs <- getNext; return bs }
+                onNextContainer (Container Context 0) parseOctetStringPrim
             parsePublic  = OriginatorPublic <$>
                 parseOriginatorPublicKey (Container Context 1)
 
@@ -371,7 +373,7 @@
                 throwParseError ("RecipientInfo: parsed invalid KT version: " ++ show v)
             rid <- parse
             ktp <- parseAlgorithm Sequence
-            (OctetString ek) <- getNext
+            OctetString ek <- getNext
             return KTRecipientInfo { ktRid = rid
                                    , ktKeyTransportParams = ktp
                                    , ktEncryptedKey = ek
@@ -394,7 +396,7 @@
             IntVal 4 <- getNext
             kid <- parse
             kep <- parseAlgorithm Sequence
-            (OctetString ek) <- getNext
+            OctetString ek <- getNext
             return KEKRecipientInfo { kekId = kid
                                     , kekKeyEncryptionParams = kep
                                     , kekEncryptedKey = ek
@@ -404,7 +406,7 @@
             IntVal 0 <- getNext
             kdf <- parseAlgorithm (Container Context 0)
             kep <- parseAlgorithm Sequence
-            (OctetString ek) <- getNext
+            OctetString ek <- getNext
             return PasswordRecipientInfo { priKeyDerivationFunc = kdf
                                          , priKeyEncryptionParams = kep
                                          , priEncryptedKey = ek
diff --git a/src/Crypto/Store/CMS/Info.hs b/src/Crypto/Store/CMS/Info.hs
--- a/src/Crypto/Store/CMS/Info.hs
+++ b/src/Crypto/Store/CMS/Info.hs
@@ -105,12 +105,7 @@
 dataASN1S = gOctetString
 
 parseData :: Monoid e => ParseASN1 e ByteString
-parseData = do
-    next <- getNext
-    case next of
-        OctetString bs -> return bs
-        _              -> throwParseError "Data: parsed unexpected content"
-
+parseData = parseOctetString
 
 -- Encapsulation
 
diff --git a/src/Crypto/Store/CMS/Signed.hs b/src/Crypto/Store/CMS/Signed.hs
--- a/src/Crypto/Store/CMS/Signed.hs
+++ b/src/Crypto/Store/CMS/Signed.hs
@@ -31,6 +31,7 @@
 import           Data.ASN1.Types
 import           Data.ByteString (ByteString)
 import qualified Data.ByteString as B
+import           Data.Hourglass
 import           Data.List
 import           Data.Maybe
 import           Data.X509
@@ -90,7 +91,7 @@
         dig <- parseAlgorithm Sequence
         sAttrs <- parseAttributes (Container Context 0)
         alg <- parseAlgorithm Sequence
-        (OctetString sig) <- getNext
+        OctetString sig <- getNext
         uAttrs <- parseAttributes (Container Context 1)
         return SignerInfo { siSignerId = sid
                           , siDigestAlgorithm = dig
@@ -123,8 +124,7 @@
     parse = parseIASN <|> parseSKI
       where parseIASN = SignerIASN <$> parse
             parseSKI  = SignerSKI  <$>
-                onNextContainer (Container Context 0) parseBS
-            parseBS = do { OctetString bs <- getNext; return bs }
+                onNextContainer (Container Context 0) parseOctetStringPrim
 
 -- | Try to find a certificate with the specified identifier.
 findSigner :: SignerIdentifier
@@ -226,16 +226,17 @@
 -- valid.  All transmitted certificates are implicitely trusted and all CRLs are
 -- ignored.
 withSignerKey :: Applicative f => ConsumerOfSI f
-withSignerKey = withSignerCertificate (\_ -> pure True)
+withSignerKey = withSignerCertificate (\_ _ -> pure True)
 
 -- | Verify that the signature is valid with one of the X.509 certificates
 -- contained in the signed data, and verify that the signer certificate is valid
 -- using the validation function supplied.  All CRLs are ignored.
 withSignerCertificate :: Applicative f
-                      => (CertificateChain -> f Bool) -> ConsumerOfSI f
+                      => (Maybe DateTime -> CertificateChain -> f Bool)
+                      -> ConsumerOfSI f
 withSignerCertificate validate ct msg SignerInfo{..} certs crls =
     case getCertificateChain of
-        Just chain -> validate chain
+        Just chain -> validate mSigningTime chain
         Nothing    -> pure False
   where
     getCertificateChain = do
@@ -245,6 +246,8 @@
         guard validSignature
         return $ CertificateChain (cert : others)
 
+    mSigningTime = getSigningTimeAttr siSignedAttrs
+
     x509Certificates = mapMaybe asX509 certs
 
     asX509 (CertificateCertificate c) = Just c
@@ -322,16 +325,10 @@
     onNextContainer Sequence $ do
         OID oid <- getNext
         withObjectID "content type" oid $ \ct ->
-            wrap ct <$> onNextContainerMaybe (Container Context 0) parseInner
+            wrap ct <$> onNextContainerMaybe (Container Context 0) parseOctetString
   where
     wrap ct Nothing  = (ct, Detached)
     wrap ct (Just c) = (ct, Attached c)
-
-    parseInner = parseContentSingle <|> parseContentChunks
-
-    parseContentSingle = do { OctetString bs <- getNext; return bs }
-    parseContentChunks = onNextContainer (Container Universal 4) $
-        B.concat <$> getMany parseContentSingle
 
 digestTypesASN1S :: ASN1Elem e => [DigestAlgorithm] -> ASN1Stream e
 digestTypesASN1S list cont = foldr (algorithmASN1S Sequence) cont list
diff --git a/src/Crypto/Store/CMS/Util.hs b/src/Crypto/Store/CMS/Util.hs
--- a/src/Crypto/Store/CMS/Util.hs
+++ b/src/Crypto/Store/CMS/Util.hs
@@ -24,6 +24,10 @@
     , Enumerable(..)
     , OIDNameableWrapper(..)
     , withObjectID
+    -- * Parsing octet strings
+    , parseOctetString
+    , parseOctetStringPrim
+    , parseOctetStrings
     -- * Parsing and encoding ASN.1 objects
     , ASN1Event
     , ASN1ObjectExact(..)
@@ -41,12 +45,15 @@
     , orElse
     ) where
 
+import Control.Applicative
+
 import           Data.ASN1.BinaryEncoding
 import           Data.ASN1.BinaryEncoding.Raw
 import           Data.ASN1.Encoding
 import           Data.ASN1.OID
 import           Data.ASN1.Types
 import           Data.ByteString (ByteString)
+import qualified Data.ByteString as B
 import           Data.List (find)
 import           Data.X509
 
@@ -231,3 +238,22 @@
     case va of
         Nothing -> pb
         _       -> return va
+
+-- | Parse an octet string, in primitive or constructed encodings.
+parseOctetString :: Monoid e => ParseASN1 e ByteString
+parseOctetString = parseOctetStringPrim <|> parseConstructed
+  where
+    parseConstructed = onNextContainer (Container Universal 4) parseOctetStrings
+
+-- | Parse an octet string in primitive encoding.
+parseOctetStringPrim :: Monoid e => ParseASN1 e ByteString
+parseOctetStringPrim = do
+    next <- getNext
+    case next of
+        OctetString bs -> return bs
+        _ -> throwParseError "parseOctetStringPrim: parsed unexpected content"
+
+-- | Parse some octet strings, in primitive or constructed encodings, and
+-- concatenate the result.
+parseOctetStrings :: Monoid e => ParseASN1 e ByteString
+parseOctetStrings = B.concat <$> getMany parseOctetString
diff --git a/src/Crypto/Store/PKCS12.hs b/src/Crypto/Store/PKCS12.hs
--- a/src/Crypto/Store/PKCS12.hs
+++ b/src/Crypto/Store/PKCS12.hs
@@ -13,6 +13,7 @@
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TupleSections #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE UndecidableInstances #-}
 module Crypto.Store.PKCS12
@@ -58,6 +59,12 @@
     , toNamedCredential
     -- * Password-based protection
     , Password
+    , OptAuthenticated(..)
+    , recoverAuthenticated
+    , ProtectionPassword
+    , emptyNotTerminated
+    , fromProtectionPassword
+    , toProtectionPassword
     , OptProtected(..)
     , recover
     , recoverA
@@ -82,6 +89,7 @@
 import Crypto.Store.CMS.Algorithms
 import Crypto.Store.CMS.Attribute
 import Crypto.Store.CMS.Encrypted
+import Crypto.Store.CMS.Enveloped
 import Crypto.Store.CMS.Util
 import Crypto.Store.Error
 import Crypto.Store.PKCS5
@@ -89,30 +97,63 @@
 import Crypto.Store.PKCS8
 
 
+-- Password-based integrity
+
+-- | Data type for objects that are possibly authenticated with a password.
+--
+-- Content is verified and retrieved by providing a 'Password' value.  When
+-- verification is successful, a value of type 'ProtectionPassword' is also
+-- returned and this value can be fed to an inner decryption layer that needs
+-- the same password (usual case for PKCS #12).
+data OptAuthenticated a = Unauthenticated a
+                          -- ^ Value is not authenticated
+                        | Authenticated (Password -> Either StoreError (ProtectionPassword, a))
+                          -- ^ Value is authenticated with a password
+
+instance Functor OptAuthenticated where
+    fmap f (Unauthenticated x) = Unauthenticated (f x)
+    fmap f (Authenticated g)   = Authenticated (fmap (fmap f) . g)
+
+-- | Try to recover an 'OptAuthenticated' content using the specified password.
+--
+-- When successful, the content is returned, as well as the password converted
+-- to type 'ProtectionPassword'.  This password value can then be fed to the
+-- inner decryption layer when both passwords are known to be same (usual case
+-- for PKCS #12).
+recoverAuthenticated :: Password -> OptAuthenticated a -> Either StoreError (ProtectionPassword, a)
+recoverAuthenticated pwd (Unauthenticated x) = Right (toProtectionPassword pwd, x)
+recoverAuthenticated pwd (Authenticated f)   = f pwd
+
+
 -- Decoding and parsing
 
 -- | Read a PKCS #12 file from disk.
-readP12File :: FilePath -> IO (Either StoreError (OptProtected PKCS12))
+readP12File :: FilePath -> IO (Either StoreError (OptAuthenticated PKCS12))
 readP12File path = readP12FileFromMemory <$> BS.readFile path
 
 -- | Read a PKCS #12 file from a bytearray in BER format.
-readP12FileFromMemory :: BS.ByteString -> Either StoreError (OptProtected PKCS12)
+readP12FileFromMemory :: BS.ByteString -> Either StoreError (OptAuthenticated PKCS12)
 readP12FileFromMemory ber = decode ber >>= integrity
   where
     integrity PFX{..} =
         case macData of
-            Nothing -> Unprotected <$> decode authSafeData
-            Just md -> return $ Protected (verify md authSafeData)
+            Nothing -> Unauthenticated <$> decode authSafeData
+            Just md -> return $ Authenticated (verify md authSafeData)
 
     verify MacData{..} content pwdUTF8 =
         case digAlg of
-            DigestAlgorithm d ->
-                let fn key macAlg bs
-                        | not (securityAcceptable macAlg) =
-                            Left (InvalidParameter "Integrity MAC too weak")
-                        | macValue == mac macAlg key bs = decode bs
-                        | otherwise = Left BadContentMAC
-                 in pkcs12mac Left fn d macParams content pwdUTF8
+            DigestAlgorithm d -> loop (toProtectionPasswords pwdUTF8)
+              where
+                -- iterate over all possible representations of a password
+                -- until a successful match is found
+                loop []           = Left BadContentMAC
+                loop (pwd:others) =
+                    let fn key macAlg bs
+                            | not (securityAcceptable macAlg) =
+                                Left (InvalidParameter "Integrity MAC too weak")
+                            | macValue == mac macAlg key bs = (pwd,) <$> decode bs
+                            | otherwise = loop others
+                     in pkcs12mac Left fn d macParams content pwd
 
 
 -- Generating and encoding
@@ -122,7 +163,7 @@
 
 -- | Write a PKCS #12 file to disk.
 writeP12File :: FilePath
-             -> IntegrityParams -> Password
+             -> IntegrityParams -> ProtectionPassword
              -> PKCS12
              -> IO (Either StoreError ())
 writeP12File path intp pw aSafe =
@@ -131,7 +172,7 @@
         Right bs -> Right <$> BS.writeFile path bs
 
 -- | Write a PKCS #12 file to a bytearray in DER format.
-writeP12FileToMemory :: IntegrityParams -> Password
+writeP12FileToMemory :: IntegrityParams -> ProtectionPassword
                      -> PKCS12
                      -> Either StoreError BS.ByteString
 writeP12FileToMemory (alg@(DigestAlgorithm hashAlg), pbeParam) pwdUTF8 aSafe =
@@ -263,7 +304,7 @@
 unencrypted = PKCS12 . (:[]) . Unencrypted
 
 -- | Build a PKCS #12 encrypted with the specified scheme and password.
-encrypted :: EncryptionScheme -> Password -> SafeContents -> Either StoreError PKCS12
+encrypted :: EncryptionScheme -> ProtectionPassword -> SafeContents -> Either StoreError PKCS12
 encrypted alg pwd sc = PKCS12 . (:[]) . Encrypted <$> encrypt alg pwd bs
   where bs = encodeASN1Object sc
 
@@ -603,7 +644,7 @@
 -- values so that the salt is not reused twice in the encryption process.
 fromCredential :: Maybe EncryptionScheme -- for certificates
                -> EncryptionScheme       -- for private key
-               -> Password
+               -> ProtectionPassword
                -> (X509.CertificateChain, X509.PrivKey)
                -> Either StoreError PKCS12
 fromCredential = fromCredential' id
@@ -618,7 +659,7 @@
 fromNamedCredential :: String
                     -> Maybe EncryptionScheme -- for certificates
                     -> EncryptionScheme       -- for private key
-                    -> Password
+                    -> ProtectionPassword
                     -> (X509.CertificateChain, X509.PrivKey)
                     -> Either StoreError PKCS12
 fromNamedCredential name = fromCredential' (setFriendlyName name)
@@ -626,7 +667,7 @@
 fromCredential' :: ([Attribute] -> [Attribute])
                 -> Maybe EncryptionScheme -- for certificates
                 -> EncryptionScheme       -- for private key
-                -> Password
+                -> ProtectionPassword
                 -> (X509.CertificateChain, X509.PrivKey)
                 -> Either StoreError PKCS12
 fromCredential' trans algChain algKey pwd (X509.CertificateChain certs, key)
@@ -743,7 +784,7 @@
 parseOctetStringObject :: (Monoid e, ParseASN1Object [ASN1Event] obj)
                        => String -> ParseASN1 e obj
 parseOctetStringObject name = do
-    OctetString bs <- getNext
+    bs <- parseOctetString
     case decode bs of
         Left e  -> throwParseError (name ++ ": " ++ show e)
         Right c -> return c
diff --git a/src/Crypto/Store/PKCS5.hs b/src/Crypto/Store/PKCS5.hs
--- a/src/Crypto/Store/PKCS5.hs
+++ b/src/Crypto/Store/PKCS5.hs
@@ -11,7 +11,10 @@
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE TypeFamilies #-}
 module Crypto.Store.PKCS5
-    ( Password
+    ( ProtectionPassword
+    , emptyNotTerminated
+    , fromProtectionPassword
+    , toProtectionPassword
     , EncryptedContent
     -- * High-level API
     , PKCS5(..)
@@ -38,7 +41,6 @@
     ) where
 
 import           Data.ASN1.Types
-import           Data.ByteArray (ByteArrayAccess)
 import           Data.ByteString (ByteString)
 import           Data.Maybe (fromMaybe)
 
@@ -46,7 +48,6 @@
 import Crypto.Store.ASN1.Generate
 import Crypto.Store.CMS.Algorithms
 import Crypto.Store.CMS.Encrypted
-import Crypto.Store.CMS.Enveloped
 import Crypto.Store.CMS.Util
 import Crypto.Store.Error
 import Crypto.Store.PKCS5.PBES1
@@ -192,20 +193,20 @@
     fromASN1 = runParseASN1State parse
 
 -- | Encrypt a bytestring with the specified encryption scheme and password.
-encrypt :: EncryptionScheme -> Password -> ByteString -> Either StoreError PKCS5
+encrypt :: EncryptionScheme -> ProtectionPassword -> ByteString -> Either StoreError PKCS5
 encrypt alg pwd bs = build <$> pbEncrypt alg bs pwd
   where
     build ed = ed `seq` PKCS5 { encryptionAlgorithm = alg, encryptedData = ed }
 
 -- | Decrypt the PKCS #5 content with the specified password.
-decrypt :: PKCS5 -> Password -> Either StoreError ByteString
+decrypt :: PKCS5 -> ProtectionPassword -> Either StoreError ByteString
 decrypt obj = pbDecrypt (encryptionAlgorithm obj) (encryptedData obj)
 
 
 -- Encryption Schemes
 
 -- | Encrypt a bytestring with the specified encryption scheme and password.
-pbEncrypt :: EncryptionScheme -> ByteString -> Password
+pbEncrypt :: EncryptionScheme -> ByteString -> ProtectionPassword
           -> Either StoreError EncryptedContent
 pbEncrypt (PBES2 p)                 = pbes2  contentEncrypt p
 pbEncrypt (PBE_MD5_DES_CBC p)       = pkcs5  Left contentEncrypt MD5  DES p
@@ -219,7 +220,7 @@
 
 -- | Decrypt an encrypted bytestring with the specified encryption scheme and
 -- password.
-pbDecrypt :: EncryptionScheme -> EncryptedContent -> Password -> Either StoreError ByteString
+pbDecrypt :: EncryptionScheme -> EncryptedContent -> ProtectionPassword -> Either StoreError ByteString
 pbDecrypt (PBES2 p)                 = pbes2  contentDecrypt p
 pbDecrypt (PBE_MD5_DES_CBC p)       = pkcs5  Left contentDecrypt MD5  DES p
 pbDecrypt (PBE_SHA1_DES_CBC p)      = pkcs5  Left contentDecrypt SHA1 DES p
@@ -230,9 +231,8 @@
 pbDecrypt (PBE_SHA1_RC2_128 p)      = pkcs12rc2 Left contentDecrypt SHA1 128 p
 pbDecrypt (PBE_SHA1_RC2_40 p)       = pkcs12rc2 Left contentDecrypt SHA1 40 p
 
-pbes2 :: ByteArrayAccess password
-      => (Key -> ContentEncryptionParams -> ByteString -> result)
-      -> PBES2Parameter -> ByteString -> password -> result
+pbes2 :: (Key -> ContentEncryptionParams -> ByteString -> result)
+      -> PBES2Parameter -> ByteString -> ProtectionPassword -> result
 pbes2 encdec PBES2Parameter{..} bs pwd = encdec key pbes2EScheme bs
-  where key = kdfDerive pbes2KDF len pwd :: Key
+  where key = kdfDerive pbes2KDF len (fromProtectionPassword pwd) :: Key
         len = fromMaybe (getMaximumKeySize pbes2EScheme) (kdfKeyLength pbes2KDF)
diff --git a/src/Crypto/Store/PKCS5/PBES1.hs b/src/Crypto/Store/PKCS5/PBES1.hs
--- a/src/Crypto/Store/PKCS5/PBES1.hs
+++ b/src/Crypto/Store/PKCS5/PBES1.hs
@@ -14,6 +14,11 @@
 module Crypto.Store.PKCS5.PBES1
     ( PBEParameter(..)
     , Key
+    , ProtectionPassword
+    , emptyNotTerminated
+    , fromProtectionPassword
+    , toProtectionPassword
+    , toProtectionPasswords
     , pkcs5
     , pkcs12
     , pkcs12rc2
@@ -38,6 +43,7 @@
 import           Data.ByteString (ByteString)
 import           Data.Maybe (fromMaybe)
 import           Data.Memory.PtrMethods
+import           Data.String (IsString(..))
 import           Data.Word
 
 import           Foreign.Ptr (plusPtr)
@@ -49,6 +55,64 @@
 import Crypto.Store.CMS.Util
 import Crypto.Store.Error
 
+-- | A password stored as a sequence of UTF-8 bytes.
+--
+-- Some key-derivation functions add restrictions to what characters
+-- are supported.
+--
+-- The data type provides a special value 'emptyNotTerminated' that is used
+-- as alternate representation of empty passwords on some systems and that
+-- produces encryption results different than an empty bytearray.
+--
+-- Conversion to/from a regular sequence of bytes is possible with functions
+-- 'toProtectionPassword' and 'fromProtectionPassword'.
+--
+-- Beware: the 'fromString' implementation correctly handles multi-byte
+-- characters, so here is not equivalent to the 'ByteString' counterpart.
+data ProtectionPassword = NullPassword | PasswordUTF8 ByteString
+    deriving Eq
+
+instance Show ProtectionPassword where
+    showsPrec _ NullPassword     = showString "emptyNotTerminated"
+    showsPrec d (PasswordUTF8 b) = showParen (d > 10) $
+        showString "toProtectionPassword " . showsPrec 11 b
+
+instance IsString ProtectionPassword where
+    fromString = PasswordUTF8 . B.convert . S.toBytes S.UTF8 . fromString
+
+instance ByteArrayAccess ProtectionPassword where
+    length = applyPP 0 B.length
+    withByteArray = B.withByteArray . fromProtectionPassword
+
+applyPP :: a -> (ByteString -> a) -> ProtectionPassword -> a
+applyPP d _ NullPassword     = d
+applyPP _ f (PasswordUTF8 b) = f b
+
+-- | A value denoting an empty password, but having a special encoding when
+-- deriving a symmetric key on some systems, like the certificate export
+-- wizard on Windows.
+--
+-- This value is different from @'toProtectionPassword ""'@ and can be tried
+-- when decrypting content with a password known to be empty.
+emptyNotTerminated :: ProtectionPassword
+emptyNotTerminated = NullPassword
+
+-- | Extract the UTF-8 bytes in a password value.
+fromProtectionPassword :: ProtectionPassword -> ByteString
+fromProtectionPassword = applyPP B.empty id
+
+-- | Build a password value from a sequence of UTF-8 bytes.
+--
+-- When the password is empty, the special value 'emptyNotTerminated' may
+-- be tried as well.
+toProtectionPassword :: ByteString -> ProtectionPassword
+toProtectionPassword = PasswordUTF8
+
+toProtectionPasswords :: ByteString -> [ProtectionPassword]
+toProtectionPasswords bs
+    | B.null bs = [PasswordUTF8 B.empty, NullPassword]
+    | otherwise = [PasswordUTF8 bs]
+
 -- | Secret key.
 type Key = B.ScrubbedBytes
 
@@ -88,8 +152,9 @@
 rc4Combine key = Right . snd . RC4.combine (RC4.initialize key)
 
 -- | Conversion to UCS2 from UTF-8, ignoring non-BMP bits.
-toUCS2 :: (ByteArrayAccess butf8, ByteArray bucs2) => butf8 -> Maybe bucs2
-toUCS2 pwdUTF8
+toUCS2 :: ByteArray bucs2 => ProtectionPassword -> Maybe bucs2
+toUCS2 NullPassword = Just B.empty
+toUCS2 (PasswordUTF8 pwdUTF8)
     | B.null r  = Just pwdUCS2
     | otherwise = Nothing
   where
@@ -105,19 +170,19 @@
 
 -- | Apply PBKDF1 on the specified password and run an encryption or decryption
 -- function on some input using derived key and IV.
-pkcs5 :: (Hash.HashAlgorithm hash, BlockCipher cipher, ByteArrayAccess password)
+pkcs5 :: (Hash.HashAlgorithm hash, BlockCipher cipher)
       => (StoreError -> result)
       -> (Key -> ContentEncryptionParams -> ByteString -> result)
       -> DigestProxy hash
       -> ContentEncryptionCipher cipher
       -> PBEParameter
       -> ByteString
-      -> password
+      -> ProtectionPassword
       -> result
 pkcs5 failure encdec hashAlg cec pbeParam bs pwd
     | proxyBlockSize cec /= 8 = failure (InvalidParameter "Invalid cipher block size")
     | otherwise =
-        case pbkdf1 hashAlg pwd pbeParam 16 of
+        case pbkdf1 hashAlg (fromProtectionPassword pwd) pbeParam 16 of
             Left err -> failure err
             Right dk ->
                 let (key, iv) = B.splitAt 8 (dk :: Key)
@@ -145,14 +210,14 @@
 
 -- | Apply PKCS #12 derivation on the specified password and run an encryption
 -- or decryption function on some input using derived key and IV.
-pkcs12 :: (Hash.HashAlgorithm hash, BlockCipher cipher, ByteArrayAccess password)
+pkcs12 :: (Hash.HashAlgorithm hash, BlockCipher cipher)
        => (StoreError -> result)
        -> (Key -> ContentEncryptionParams -> ByteString -> result)
        -> DigestProxy hash
        -> ContentEncryptionCipher cipher
        -> PBEParameter
        -> ByteString
-       -> password
+       -> ProtectionPassword
        -> result
 pkcs12 failure encdec hashAlg cec pbeParam bs pwdUTF8 =
     case toUCS2 pwdUTF8 of
@@ -168,14 +233,14 @@
 -- | Apply PKCS #12 derivation on the specified password and run an encryption
 -- or decryption function on some input using derived key and IV.  This variant
 -- uses an RC2 cipher with the EKL specified (effective key length).
-pkcs12rc2 :: (Hash.HashAlgorithm hash, ByteArrayAccess password)
+pkcs12rc2 :: Hash.HashAlgorithm hash
           => (StoreError -> result)
           -> (Key -> ContentEncryptionParams -> ByteString -> result)
           -> DigestProxy hash
           -> Int
           -> PBEParameter
           -> ByteString
-          -> password
+          -> ProtectionPassword
           -> result
 pkcs12rc2 failure encdec hashAlg len pbeParam bs pwdUTF8 =
     case toUCS2 pwdUTF8 of
@@ -191,14 +256,14 @@
 -- | Apply PKCS #12 derivation on the specified password and run an encryption
 -- or decryption function on some input using derived key.  This variant does
 -- not derive any IV and is required for RC4.
-pkcs12stream :: (Hash.HashAlgorithm hash, ByteArrayAccess password)
+pkcs12stream :: Hash.HashAlgorithm hash
              => (StoreError -> result)
              -> (Key -> ByteString -> result)
              -> DigestProxy hash
              -> Int
              -> PBEParameter
              -> ByteString
-             -> password
+             -> ProtectionPassword
              -> result
 pkcs12stream failure encdec hashAlg keyLen pbeParam bs pwdUTF8 =
     case toUCS2 pwdUTF8 of
@@ -209,13 +274,13 @@
 
 -- | Apply PKCS #12 derivation on the specified password and run a MAC function
 -- on some input using derived key.
-pkcs12mac :: (Hash.HashAlgorithm hash, ByteArrayAccess password)
+pkcs12mac :: Hash.HashAlgorithm hash
           => (StoreError -> result)
           -> (Key -> MACAlgorithm -> ByteString -> result)
           -> DigestProxy hash
           -> PBEParameter
           -> ByteString
-          -> password
+          -> ProtectionPassword
           -> result
 pkcs12mac failure macFn hashAlg pbeParam bs pwdUTF8 =
     case toUCS2 pwdUTF8 of
diff --git a/src/Crypto/Store/PKCS8.hs b/src/Crypto/Store/PKCS8.hs
--- a/src/Crypto/Store/PKCS8.hs
+++ b/src/Crypto/Store/PKCS8.hs
@@ -35,7 +35,10 @@
     , PrivateKeyFormat(..)
     , FormattedKey(..)
     -- * Password-based protection
-    , Password
+    , ProtectionPassword
+    , emptyNotTerminated
+    , fromProtectionPassword
+    , toProtectionPassword
     , OptProtected(..)
     , recover
     , recoverA
@@ -78,7 +81,7 @@
 -- | Data type for objects that are possibly protected with a password.
 data OptProtected a = Unprotected a
                       -- ^ Value is unprotected
-                    | Protected (Password -> Either StoreError a)
+                    | Protected (ProtectionPassword -> Either StoreError a)
                       -- ^ Value is protected with a password
 
 instance Functor OptProtected where
@@ -86,7 +89,7 @@
     fmap f (Protected g)   = Protected (fmap f . g)
 
 -- | Try to recover an 'OptProtected' content using the specified password.
-recover :: Password -> OptProtected a -> Either StoreError a
+recover :: ProtectionPassword -> OptProtected a -> Either StoreError a
 recover _   (Unprotected x) = Right x
 recover pwd (Protected f)   = f pwd
 
@@ -98,12 +101,14 @@
 -- >
 -- > [encryptedKey] <- readKeyFile "privkey.pem"
 -- > let askForPassword = putStr "Please enter password: " >> B.getLine
--- > result <- recoverA askForPassword encryptedKey
+-- > result <- recoverA (toProtectionPassword <$> askForPassword) encryptedKey
 -- > case result of
 -- >     Left err  -> putStrLn $ "Unable to recover key: " ++ show err
 -- >     Right key -> print key
 recoverA :: Applicative f
-         => f Password -> OptProtected a -> f (Either StoreError a)
+         => f ProtectionPassword
+         -> OptProtected a
+         -> f (Either StoreError a)
 recoverA _   (Unprotected x) = pure (Right x)
 recoverA get (Protected f)   = fmap f get
 
@@ -178,7 +183,7 @@
 -- Fresh 'EncryptionScheme' parameters should be generated for each key to
 -- encrypt.
 writeEncryptedKeyFile :: FilePath
-                      -> EncryptionScheme -> Password-> X509.PrivKey
+                      -> EncryptionScheme -> ProtectionPassword -> X509.PrivKey
                       -> IO (Either StoreError ())
 writeEncryptedKeyFile path alg pwd privKey =
     let pem = encryptKeyToPEM alg pwd privKey
@@ -191,8 +196,8 @@
 --
 -- Fresh 'EncryptionScheme' parameters should be generated for each key to
 -- encrypt.
-writeEncryptedKeyFileToMemory :: EncryptionScheme -> Password -> X509.PrivKey
-                              -> Either StoreError B.ByteString
+writeEncryptedKeyFileToMemory :: EncryptionScheme -> ProtectionPassword
+                              -> X509.PrivKey -> Either StoreError B.ByteString
 writeEncryptedKeyFileToMemory alg pwd privKey =
     pemWriteBS <$> encryptKeyToPEM alg pwd privKey
 
@@ -241,7 +246,7 @@
 --
 -- Fresh 'EncryptionScheme' parameters should be generated for each key to
 -- encrypt.
-encryptKeyToPEM :: EncryptionScheme -> Password -> X509.PrivKey
+encryptKeyToPEM :: EncryptionScheme -> ProtectionPassword -> X509.PrivKey
                 -> Either StoreError PEM
 encryptKeyToPEM alg pwd privKey = toPEM <$> encrypt alg pwd bs
   where bs = pemContent (keyToModernPEM privKey)
diff --git a/tests/CMS/Instances.hs b/tests/CMS/Instances.hs
--- a/tests/CMS/Instances.hs
+++ b/tests/CMS/Instances.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE OverloadedStrings #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 -- | Orphan instances.
 module CMS.Instances
@@ -154,7 +155,12 @@
     ]
 
 instance Arbitrary MACAlgorithm where
-    arbitrary = (\(DigestAlgorithm alg) -> HMAC alg) <$> arbitraryIntegrityDigest
+    arbitrary = oneof
+        [ (\(DigestAlgorithm alg) -> HMAC alg) <$> arbitraryIntegrityDigest
+        , (\(SomeNat p) -> KMAC_SHAKE128 p) <$> arbitraryNat <*> arbitraryCtx
+        , (\(SomeNat p) -> KMAC_SHAKE256 p) <$> arbitraryNat <*> arbitraryCtx
+        ]
+      where arbitraryCtx = elements [ B.empty , "ctx" , "custom string" ]
 
 instance Arbitrary OAEPParams where
     arbitrary = do
@@ -377,19 +383,24 @@
 arbitraryAgreeParams :: Bool -> KeyEncryptionParams -> Gen KeyAgreementParams
 arbitraryAgreeParams allowCofactorDH alg
     | allowCofactorDH = oneof
-        [ flip StdDH alg <$> arbitraryDigest
-        , flip CofactorDH alg <$> arbitraryDigest
+        [ flip StdDH alg <$> elements stdKDFs
+        , flip CofactorDH alg <$> elements cofactorKDFs
         ]
-    | otherwise = flip StdDH alg <$> arbitraryDigest
+    | otherwise = flip StdDH alg <$> elements stdKDFs
   where
-    arbitraryDigest =
-        elements
-            [ DigestAlgorithm SHA1
-            , DigestAlgorithm SHA224
-            , DigestAlgorithm SHA256
-            , DigestAlgorithm SHA384
-            , DigestAlgorithm SHA512
-            ]
+    cofactorKDFs =
+        [ KA_X963_KDF SHA1
+        , KA_X963_KDF SHA224
+        , KA_X963_KDF SHA256
+        , KA_X963_KDF SHA384
+        , KA_X963_KDF SHA512
+        ]
+
+    stdKDFs = cofactorKDFs ++
+        [ KA_HKDF SHA256
+        , KA_HKDF SHA384
+        , KA_HKDF SHA512
+        ]
 
 arbitraryEnvDev :: ContentEncryptionKey
                 -> Gen ([ProducerOfRI Gen], ConsumerOfRI Gen)
diff --git a/tests/PKCS12/Instances.hs b/tests/PKCS12/Instances.hs
--- a/tests/PKCS12/Instances.hs
+++ b/tests/PKCS12/Instances.hs
@@ -29,7 +29,7 @@
 arbitraryIntegrityParams :: Gen IntegrityParams
 arbitraryIntegrityParams = (,) <$> arbitraryIntegrityDigest <*> arbitrary
 
-arbitraryPKCS12 :: Password -> Gen PKCS12
+arbitraryPKCS12 :: ProtectionPassword -> Gen PKCS12
 arbitraryPKCS12 pwd = do
     p <- one
     ps <- listOf one
diff --git a/tests/PKCS12/Tests.hs b/tests/PKCS12/Tests.hs
--- a/tests/PKCS12/Tests.hs
+++ b/tests/PKCS12/Tests.hs
@@ -38,13 +38,12 @@
         let r = readP12FileFromMemory (pemContent pem)
 
         assertRight r $ \integrity ->
-            assertRight (recover pwd integrity) $ \privacy ->
-                assertRight (recover pwd $ unPKCS12 privacy) $ \scs -> do
+            assertRight (recoverAuthenticated pwd integrity) $ \(ppwd, privacy) ->
+                assertRight (recover ppwd $ unPKCS12 privacy) $ \scs -> do
                     step ("Testing " ++ name)
-                    recover pwd (getAllSafeKeys scs) @?= Right [key]
+                    recover ppwd (getAllSafeKeys scs) @?= Right [key]
                     getAllSafeX509Certs scs @?= certs
   where
-    pwd :: Password
     pwd = fromString "dontchangeme"
 
     nameIntegrity n = "integrity with " ++ n
@@ -67,19 +66,47 @@
                      , "PBE-SHA1-RC2-40"
                      ]
 
+testEmptyPassword :: TestTree
+testEmptyPassword = testCaseSteps "empty password" $ \step -> do
+    step "Reading PKCS #12 files"
+    pems <- readPEMs path
+    length pems @?= length infos
+
+    forM_ (zip infos pems) $ \((name, numKeys, numCerts), pem) -> do
+        let r = readP12FileFromMemory (pemContent pem)
+
+        assertRight r $ \integrity ->
+            assertRight (recoverAuthenticated pwd integrity) $ \(ppwd, privacy) ->
+                assertRight (recover ppwd $ unPKCS12 privacy) $ \scs -> do
+                    step ("Testing " ++ name)
+                    assertRight (recover ppwd $ getAllSafeKeys scs) $ \keys ->
+                        length keys @?= numKeys
+                    length (getAllSafeX509Certs scs) @?= numCerts
+  where
+    pwd = fromString ""
+
+    path  = testFile "pkcs12-empty-password.pem"
+    infos = [ ("Windows Certificate Export Wizard", 1, 2)
+            , ("OpenSSL", 1, 1)
+            , ("GnuTLS with --empty-password", 1, 1)
+            , ("GnuTLS with --null-password", 1, 1)
+            ]
+
 propertyTests :: TestTree
 propertyTests = localOption (QuickCheckMaxSize 5) $ testGroup "properties"
     [ testProperty "marshalling" $ do
-        pE <- arbitraryPassword
+        pE <- arbitrary
         c <- arbitraryPKCS12 pE
         let r = readP12FileFromMemory $ writeUnprotectedP12FileToMemory c
-        return $ Right (Right c) === (recover (fromString "not-used") <$> r)
+            unused = fromString "not-used"
+        return $ Right (Right c) === (fmap snd . recoverAuthenticated unused <$> r)
     , testProperty "marshalling with authentication" $ do
         params <- arbitraryIntegrityParams
-        c <- arbitraryPassword >>= arbitraryPKCS12
-        pI <- arbitraryPassword
+        c <- arbitrary >>= arbitraryPKCS12
+        pI <- arbitrary
         let r = readP12FileFromMemory <$> writeP12FileToMemory params pI c
-        return $ Right (Right (Right c)) === (fmap (recover pI) <$> r)
+            p = fromProtectionPassword pI
+        return $ Right (Right (Right (pI, c))) === (fmap (recoverAuthenticated p) <$> r)
     , localOption (QuickCheckTests 20) $ testProperty "converting credentials" $
         \pChain pKey privKey ->
             testCredConv privKey toCredential (fromCredential pChain pKey)
@@ -92,7 +119,7 @@
     ]
   where
     testCredConv privKey to from = do
-        pwd <- arbitraryPassword
+        pwd <- arbitrary
         chain <- arbitrary >>= arbitraryCertificateChain
         chain' <- shuffleCertificateChain chain
         let cred = (chain, privKey)
@@ -104,5 +131,6 @@
     testGroup "PKCS12"
         [ testType "RSA"                        "rsa"
         , testType "Ed25519"                    "ed25519"
+        , testEmptyPassword
         , propertyTests
         ]
diff --git a/tests/PKCS8/Instances.hs b/tests/PKCS8/Instances.hs
--- a/tests/PKCS8/Instances.hs
+++ b/tests/PKCS8/Instances.hs
@@ -15,6 +15,11 @@
 
 import CMS.Instances
 
+instance Arbitrary ProtectionPassword where
+    arbitrary = oneof [ return emptyNotTerminated
+                      , toProtectionPassword <$> arbitraryPassword
+                      ]
+
 instance Arbitrary PBEParameter where
     arbitrary = do
         salt <- generateSalt 8
diff --git a/tests/PKCS8/Tests.hs b/tests/PKCS8/Tests.hs
--- a/tests/PKCS8/Tests.hs
+++ b/tests/PKCS8/Tests.hs
@@ -11,7 +11,7 @@
 import Test.Tasty.QuickCheck
 
 import Util
-import PKCS8.Instances
+import PKCS8.Instances ()
 
 keyTests :: String -> TestTree
 keyTests prefix =
@@ -85,7 +85,7 @@
         let r = readKeyFileFromMemory $ writeKeyFileToMemory fmt l
         in map Right l === map (recover $ fromString "not-used") r
     , testProperty "marshalling with encryption" $ \es k -> do
-        p <- arbitraryPassword
+        p <- arbitrary
         let r = readKeyFileFromMemory <$> writeEncryptedKeyFileToMemory es p k
         return $ Right [Right k] === (map (recover p) <$> r)
     ]
diff --git a/tests/files/pkcs12-empty-password.pem b/tests/files/pkcs12-empty-password.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/pkcs12-empty-password.pem
@@ -0,0 +1,191 @@
+The file from <https://bugzilla.mozilla.org/show_bug.cgi?id=265991>
+encoded in base64:
+
+    openssl base64 -in alice-nopassword.pfx
+
+-----BEGIN PKCS12-----
+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
+B9A=
+-----END PKCS12-----
+
+A file generated with OpenSSL and encoded in base64:
+
+    openssl pkcs12 -legacy -export -in tests/files/rsa-self-signed-cert.pem \
+      -inkey tests/files/rsa-unencrypted-trad.pem -password 'pass:' \
+        | openssl base64
+
+-----BEGIN PKCS12-----
+MIIGCQIBAzCCBc8GCSqGSIb3DQEHAaCCBcAEggW8MIIFuDCCArcGCSqGSIb3DQEH
+BqCCAqgwggKkAgEAMIICnQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQI+CTH
+s6/efloCAggAgIICcGL8NGL8zObJ3DcHTQyYuWq6U1qv8bpeZmb9TXvuwrfOz2qQ
++HUxpZ5wPooaGXhPOryPwv0uQ2KGmGmIcqi3lpf5LakeANoXbuP7SUE6N1G//Q8n
+tYb6ahhqDrlr1p6xafay4QiJkZjGMsM0w3XnFw5kqxbj3yNaARqEsr8UoPrx5RgF
+pHvmNeYqkiOeEt/YLLor2cMekSN5xw5X3neRR2hNEwvU1kZHAAFxZ2+Byeapbd2Q
+m0RepoS1Dto/Q7cw5z+rS+f7fGE7SMEIBl6FnRoQwrlHa16LVed5pMNIb2wY16S9
+Rf3hCwPQfHUaa9wgLIC9FLHdOlVsTY7WCrZ0R8pwahzSrgJE+Y3RYcpsHfDDvzW0
+mYj9tL+DrT/rbIq01JNydigj5sUrpoavblaCOGMGTWF1CJqHoeshnK5O3a3Ng+vq
+hhzLMoCDwLL31XQFWFoeIm1g5wiQULSYqSc6475CYG87zuHvLrYrQRk56GEXFVGd
+bNhGUIGyDouz5+D/jmmB89TwgppEN+EtIIRqX1pbNRboSP9hpqzsMadBwwvxfYzN
+60zMwvlg5X4sxv/W4XwczgY5dkf2JD6ZWjD0uEWcqMl/z+dTZULvqHMD/gMZVWHk
+Po3xKzSG2eVvLM2eOlRKq0vWGtHmKtV5cwWQ98Kx6C+rexlpSZbqDahVASazyYR/
+VmpprDoT1d7bPYj1qsQq0n9ww7UeI/hIg83X5+TZV4xvsEXNv9crqKx0Mjj2TsP9
+AjoNONNFGp1bZgYc38qQXh4jNbgi2Cr4H9l9YVVjrzIS6AiUep/3F6zDMaOVGsmD
+TrANXPy/2VA0dNP2CDCCAvkGCSqGSIb3DQEHAaCCAuoEggLmMIIC4jCCAt4GCyqG
+SIb3DQEMCgECoIICpjCCAqIwHAYKKoZIhvcNAQwBAzAOBAjInPcICW0iUwICCAAE
+ggKA99RQodJGF0d/80E2Q2hHB+RUXigpxFTYoHk+m5cwlPvZHhXCkrlMO2q9WHIh
+33KD2z2tJJ01hEB1P2LrD8TZDDMHNzRXIOAlXR5rhkMDia4uICPresE/j/RuVisJ
+LgUVG35V9R4H0CvGhoUa3AAg/JXob/GBTABPMSGg8bQDXLDMqD0sOJIYOU5g/ddq
+97HK7KHmfI5yigBvzDxM7SnbR/up7qvmwdMgs1gq339BJPBS2PvxFORXtnnW+pBG
+c68EpUQQVpfC+5UrZGf9zYEnsBR+ihE+8EYDHG/WJlLj++PXnMK7R1r1V5q024VD
+8B0FmlP5t19YGd8GcsnMhnFRfvugQ70GMODMNxejrXFxI/yXuN3OCHdkP/rCY36n
+9J9TMB5efcVJWcfU+UcWTQZbvDwlVTF8LOqZkteu16zZufn7Lo3IKYirT8Wq9tcD
+HrjkSiiUDMT3Q5sorYSum9s1n1jXbgGJRONmeTYpaXAEvi4HVbLKRcRL/0iv3YUM
+P+ekurzBomqlPqhU10DyXHg/6wb8+qHk1tAlJFY16hC+jFM87SkKr3CQsD1EdFLd
+eymlwzEVvpsAr8FFznoBo+fw0faZMg8g2IneCAXSqs4hqa9M2mmbDYRmx1CmU0Zt
+Bzwd7k2f9N/yLId7YmI40E9aCgybd2lirejm86ZSReDQ+hj4/03kzm3IjuWhMvce
+nprJZOXhrlQ/Wm9buAojkqfjWbvr3U81aqrKDCCM9gVx9NQx7DhPmqy4Fl6hLnEX
+HsHuoNyiN3/f/b6t98JCSBXWRxdqLGE/cgi2kqljokwsSb16SPe/KodF8lk6gkCn
+FbA90vTgoyWT/0ou+NpppAVCLzElMCMGCSqGSIb3DQEJFTEWBBTAbCJKoqJORdb4
+i56jGIji040OsDAxMCEwCQYFKw4DAhoFAAQUslUy7TCr35RrfHVPzfBYIA5cyMkE
+CBx9AR1csYyJAgIIAA==
+-----END PKCS12-----
+
+A file generated by GnuTLS with --empty-password:
+
+gnutls-certtool --to-p12 --p12-name=demo \
+    --load-privkey tests/files/rsa-unencrypted-trad.pem \
+    --load-certificate tests/files/rsa-self-signed-cert.pem \
+    --empty-password
+
+-----BEGIN PKCS12-----
+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
+PRSjE7CldboCAigA
+-----END PKCS12-----
+
+A file generated by GnuTLS with --null-password:
+
+gnutls-certtool --to-p12 --p12-name=demo \
+    --load-privkey tests/files/rsa-unencrypted-trad.pem \
+    --load-certificate tests/files/rsa-self-signed-cert.pem \
+    --null-password
+
+-----BEGIN PKCS12-----
+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
+nbWw5eZvpVsCAigA
+-----END PKCS12-----
