phkdf 0.0.0.0 → 0.1.0.0
raw patch · 16 files changed
+1533/−610 lines, 16 filesdep +quickcheck-instancesdep +sha256dep +tasty-quickcheckdep −cryptohash-sha256dep ~base16dep ~bytestringdep ~tuplehash-utilsPVP ok
version bump matches the API change (PVP)
Dependencies added: quickcheck-instances, sha256, tasty-quickcheck
Dependencies removed: cryptohash-sha256
Dependency ranges changed: base16, bytestring, tuplehash-utils
API changes (from Hackage documentation)
- Crypto.Encoding.PHKDF: credentialsPadding :: Foldable f => f ByteString -> ByteString -> ByteString -> ByteString
- Crypto.Encoding.PHKDF: cycleByteString :: ByteString -> Int -> ByteString
- Crypto.Encoding.PHKDF: cycleByteStringToList :: ByteString -> Int -> [ByteString]
- Crypto.Encoding.PHKDF: cycleByteStringWithNull :: ByteString -> Int -> ByteString
- Crypto.Encoding.PHKDF: cycleByteStringWithNullToList :: ByteString -> Int -> [ByteString]
- Crypto.Encoding.PHKDF: passwordPadding :: Foldable f => f ByteString -> f ByteString -> ByteString -> ByteString -> ByteString -> ByteString
- Crypto.Encoding.PHKDF: passwordPaddingBytes :: Foldable f => Int -> f ByteString -> f ByteString -> ByteString -> ByteString -> ByteString -> ByteString
- Crypto.Encoding.PHKDF: trimExtTag :: ByteString -> Maybe ByteString
- Crypto.Encoding.PHKDF: usernamePadding :: Foldable f => f ByteString -> ByteString -> ByteString -> ByteString
- Crypto.PHKDF: PhkdfInputArgs :: !ByteString -> !ByteString -> !Vector ByteString -> PhkdfInputArgs
- Crypto.PHKDF: PhkdfInputBlock :: !ByteString -> !ByteString -> !ByteString -> !Vector ByteString -> !Word32 -> PhkdfInputBlock
- Crypto.PHKDF: PhkdfInputTweak :: !Vector ByteString -> !ByteString -> PhkdfInputTweak
- Crypto.PHKDF: PhkdfSeed :: !ByteString -> !HmacKey -> !ByteString -> !ByteString -> PhkdfSeed
- Crypto.PHKDF: [phkdfInputArgs_credentials] :: PhkdfInputArgs -> !Vector ByteString
- Crypto.PHKDF: [phkdfInputArgs_password] :: PhkdfInputArgs -> !ByteString
- Crypto.PHKDF: [phkdfInputArgs_username] :: PhkdfInputArgs -> !ByteString
- Crypto.PHKDF: [phkdfInputBlock_domainTag] :: PhkdfInputBlock -> !ByteString
- Crypto.PHKDF: [phkdfInputBlock_longTag] :: PhkdfInputBlock -> !ByteString
- Crypto.PHKDF: [phkdfInputBlock_rounds] :: PhkdfInputBlock -> !Word32
- Crypto.PHKDF: [phkdfInputBlock_seguid] :: PhkdfInputBlock -> !ByteString
- Crypto.PHKDF: [phkdfInputBlock_tags] :: PhkdfInputBlock -> !Vector ByteString
- Crypto.PHKDF: [phkdfInputTweak_echoTag] :: PhkdfInputTweak -> !ByteString
- Crypto.PHKDF: [phkdfInputTweak_role] :: PhkdfInputTweak -> !Vector ByteString
- Crypto.PHKDF: [phkdfSeed_domainTag] :: PhkdfSeed -> !ByteString
- Crypto.PHKDF: [phkdfSeed_secret] :: PhkdfSeed -> !ByteString
- Crypto.PHKDF: [phkdfSeed_seguidKey] :: PhkdfSeed -> !HmacKey
- Crypto.PHKDF: [phkdfSeed_seguid] :: PhkdfSeed -> !ByteString
- Crypto.PHKDF: data PhkdfInputArgs
- Crypto.PHKDF: data PhkdfInputBlock
- Crypto.PHKDF: data PhkdfInputTweak
- Crypto.PHKDF: data PhkdfSeed
- Crypto.PHKDF: instance GHC.Classes.Eq Crypto.PHKDF.PhkdfInputArgs
- Crypto.PHKDF: instance GHC.Classes.Eq Crypto.PHKDF.PhkdfInputBlock
- Crypto.PHKDF: instance GHC.Classes.Eq Crypto.PHKDF.PhkdfInputTweak
- Crypto.PHKDF: instance GHC.Classes.Eq Crypto.PHKDF.PhkdfSeed
- Crypto.PHKDF: instance GHC.Classes.Ord Crypto.PHKDF.PhkdfInputArgs
- Crypto.PHKDF: instance GHC.Classes.Ord Crypto.PHKDF.PhkdfInputBlock
- Crypto.PHKDF: instance GHC.Classes.Ord Crypto.PHKDF.PhkdfInputTweak
- Crypto.PHKDF: instance GHC.Show.Show Crypto.PHKDF.PhkdfInputArgs
- Crypto.PHKDF: instance GHC.Show.Show Crypto.PHKDF.PhkdfInputBlock
- Crypto.PHKDF: instance GHC.Show.Show Crypto.PHKDF.PhkdfInputTweak
- Crypto.PHKDF: phkdfPass :: PhkdfInputBlock -> PhkdfInputArgs -> PhkdfInputTweak -> Stream ByteString
- Crypto.PHKDF: phkdfPass_seedFinalize :: PhkdfSeed -> PhkdfInputTweak -> Stream ByteString
- Crypto.PHKDF: phkdfPass_seedInit :: PhkdfInputBlock -> PhkdfInputArgs -> PhkdfSeed
- Crypto.PHKDF: phkdfSimple :: PhkdfInputBlock -> PhkdfInputArgs -> Stream ByteString
- Crypto.PHKDF.HMAC: data HmacCtx
- Crypto.PHKDF.HMAC: data HmacKey
- Crypto.PHKDF.HMAC: hmacCtx_finalize :: HmacCtx -> ByteString
- Crypto.PHKDF.HMAC: hmacCtx_init :: ByteString -> HmacCtx
- Crypto.PHKDF.HMAC: hmacCtx_initFromHmacKey :: HmacKey -> HmacCtx
- Crypto.PHKDF.HMAC: hmacCtx_update :: ByteString -> HmacCtx -> HmacCtx
- Crypto.PHKDF.HMAC: hmacCtx_updates :: [ByteString] -> HmacCtx -> HmacCtx
- Crypto.PHKDF.HMAC: hmacKey_init :: ByteString -> HmacKey
- Crypto.PHKDF.HMAC: hmacKey_run :: HmacKey -> HmacCtx
- Crypto.PHKDF.HMAC.Subtle: HmacCtx :: !Ctx -> !Ctx -> HmacCtx
- Crypto.PHKDF.HMAC.Subtle: HmacKey :: HmacCtx -> HmacKey
- Crypto.PHKDF.HMAC.Subtle: [hmacCtx_ipad] :: HmacCtx -> !Ctx
- Crypto.PHKDF.HMAC.Subtle: [hmacCtx_opad] :: HmacCtx -> !Ctx
- Crypto.PHKDF.HMAC.Subtle: [hmacKey_run] :: HmacKey -> HmacCtx
- Crypto.PHKDF.HMAC.Subtle: data HmacCtx
- Crypto.PHKDF.HMAC.Subtle: hmacKey_ipad :: HmacKey -> Ctx
- Crypto.PHKDF.HMAC.Subtle: hmacKey_opad :: HmacKey -> Ctx
- Crypto.PHKDF.HMAC.Subtle: instance GHC.Classes.Eq Crypto.PHKDF.HMAC.Subtle.HmacCtx
- Crypto.PHKDF.HMAC.Subtle: instance GHC.Classes.Eq Crypto.PHKDF.HMAC.Subtle.HmacKey
- Crypto.PHKDF.HMAC.Subtle: newtype HmacKey
- Crypto.PHKDF.Primitives: hmacKey_init :: ByteString -> HmacKey
- Crypto.PHKDF.Primitives: phkdfCtx_addArg :: ByteString -> PhkdfCtx -> PhkdfCtx
- Crypto.PHKDF.Primitives: phkdfCtx_addArgs :: Foldable f => f ByteString -> PhkdfCtx -> PhkdfCtx
- Crypto.PHKDF.Primitives: phkdfCtx_addArgsBy :: Foldable f => (a -> ByteString) -> f a -> PhkdfCtx -> PhkdfCtx
- Crypto.PHKDF.Primitives: phkdfCtx_finalizeGen :: (Int -> ByteString) -> Word32 -> ByteString -> PhkdfCtx -> PhkdfGen
- Crypto.PHKDF.Primitives: phkdfCtx_finalizeHmacCtx :: PhkdfCtx -> HmacCtx
- Crypto.PHKDF.Primitives: phkdfCtx_finalizeStream :: (Int -> ByteString) -> Word32 -> ByteString -> PhkdfCtx -> Stream ByteString
- Crypto.PHKDF.Primitives: phkdfCtx_initFromHmacKey :: HmacKey -> PhkdfCtx
- Crypto.PHKDF.Primitives: phkdfCtx_resetCtx :: PhkdfCtx -> HmacCtx
- Crypto.PHKDF.Primitives: phkdfGen_finalizeStream :: PhkdfGen -> Stream ByteString
- Crypto.PHKDF.Primitives: phkdfGen_initFromHmacKey :: ByteString -> Word32 -> ByteString -> HmacKey -> PhkdfGen
- Crypto.PHKDF.Primitives: phkdfSlowCtx_addArg :: ByteString -> PhkdfSlowCtx -> PhkdfSlowCtx
- Crypto.PHKDF.Primitives: phkdfSlowCtx_addArgs :: Foldable f => f ByteString -> PhkdfSlowCtx -> PhkdfSlowCtx
- Crypto.PHKDF.Primitives: phkdfSlowCtx_finalizeStream :: (Int -> ByteString) -> PhkdfSlowCtx -> Stream ByteString
- Crypto.PHKDF.Primitives.Subtle: [phkdfCtx_byteLen] :: PhkdfCtx -> !Word64
- Crypto.PHKDF.Primitives.Subtle: [phkdfCtx_hmacKey] :: PhkdfCtx -> !HmacKey
- Crypto.PHKDF.Primitives.Subtle: [phkdfGen_hmacKey] :: PhkdfGen -> !HmacKey
+ Crypto.Encoding.PHKDF: assertTakeB' :: Int64 -> ByteString -> ByteString
+ Crypto.Encoding.PHKDF: chunkify :: Int -> ByteString -> [ByteString]
+ Crypto.Encoding.PHKDF: chunkifyCycle :: Int64 -> ByteString -> Int64 -> [ByteString]
+ Crypto.Encoding.PHKDF: dropBs :: Int64 -> [ByteString] -> [ByteString]
+ Crypto.Encoding.PHKDF: nullBuffer :: ByteString
+ Crypto.Encoding.PHKDF: takeB' :: Int64 -> ByteString -> Maybe ByteString
+ Crypto.Encoding.PHKDF: takeBs :: Int64 -> [ByteString] -> [ByteString]
+ Crypto.Encoding.PHKDF: takeBs' :: Int64 -> [ByteString] -> [ByteString]
+ Crypto.Encoding.PHKDF: trimExtendedTag :: ByteString -> Maybe ByteString
+ Crypto.Encoding.PHKDF.V1: credentialsPadding :: Foldable f => f ByteString -> ByteString -> ByteString -> ByteString
+ Crypto.Encoding.PHKDF.V1: cycleByteString :: ByteString -> Int -> ByteString
+ Crypto.Encoding.PHKDF.V1: cycleByteStringToList :: ByteString -> Int -> [ByteString]
+ Crypto.Encoding.PHKDF.V1: cycleByteStringWithNull :: ByteString -> Int -> ByteString
+ Crypto.Encoding.PHKDF.V1: cycleByteStringWithNullToList :: ByteString -> Int -> [ByteString]
+ Crypto.Encoding.PHKDF.V1: extendTag :: ByteString -> ByteString
+ Crypto.Encoding.PHKDF.V1: extendTagToList :: ByteString -> [ByteString]
+ Crypto.Encoding.PHKDF.V1: passwordPadding :: Foldable f => f ByteString -> f ByteString -> ByteString -> ByteString -> ByteString -> ByteString
+ Crypto.Encoding.PHKDF.V1: passwordPaddingBytes :: Foldable f => Int -> f ByteString -> f ByteString -> ByteString -> ByteString -> ByteString -> ByteString
+ Crypto.Encoding.PHKDF.V1: trimExtTag :: ByteString -> Maybe ByteString
+ Crypto.Encoding.PHKDF.V1: usernamePadding :: Foldable f => f ByteString -> ByteString -> ByteString -> ByteString
+ Crypto.PHKDF: data () => HmacKey
+ Crypto.PHKDF: data PhkdfCtx
+ Crypto.PHKDF: data PhkdfGen
+ Crypto.PHKDF: hmacKey :: HmacKeyPlain -> HmacKey
+ Crypto.PHKDF: phkdfCtx :: ByteString -> PhkdfCtx
+ Crypto.PHKDF: phkdfCtx_blockPaddingLength :: PhkdfCtx -> Int
+ Crypto.PHKDF: phkdfCtx_byteCount :: PhkdfCtx -> Word64
+ Crypto.PHKDF: phkdfCtx_endPaddingLength :: PhkdfCtx -> Int
+ Crypto.PHKDF: phkdfCtx_feedArg :: ByteString -> PhkdfCtx -> PhkdfCtx
+ Crypto.PHKDF: phkdfCtx_feedArgConcat :: Foldable f => f ByteString -> PhkdfCtx -> PhkdfCtx
+ Crypto.PHKDF: phkdfCtx_feedArgs :: Foldable f => f ByteString -> PhkdfCtx -> PhkdfCtx
+ Crypto.PHKDF: phkdfCtx_feedArgsBy :: Foldable f => (a -> ByteString) -> f a -> PhkdfCtx -> PhkdfCtx
+ Crypto.PHKDF: phkdfCtx_finalize :: (Int -> ByteString) -> Word32 -> ByteString -> PhkdfCtx -> ByteString
+ Crypto.PHKDF: phkdfCtx_finalizeHmac :: PhkdfCtx -> ByteString
+ Crypto.PHKDF: phkdfCtx_hmacKey :: PhkdfCtx -> Maybe HmacKey
+ Crypto.PHKDF: phkdfCtx_hmacKeyHashed :: PhkdfCtx -> Maybe HmacKeyHashed
+ Crypto.PHKDF: phkdfCtx_hmacKeyLike :: PhkdfCtx -> HmacKeyLike
+ Crypto.PHKDF: phkdfCtx_hmacKeyPlain :: PhkdfCtx -> Maybe HmacKeyPlain
+ Crypto.PHKDF: phkdfCtx_hmacKeyPrefixed :: PhkdfCtx -> HmacKeyPrefixed
+ Crypto.PHKDF: phkdfCtx_init :: HmacKey -> PhkdfCtx
+ Crypto.PHKDF: phkdfCtx_initHashed :: HmacKeyHashed -> PhkdfCtx
+ Crypto.PHKDF: phkdfCtx_initLike :: HmacKeyLike -> PhkdfCtx
+ Crypto.PHKDF: phkdfCtx_initPrefixed :: ByteString -> HmacKeyPrefixed -> PhkdfCtx
+ Crypto.PHKDF: phkdfCtx_reset :: PhkdfCtx -> PhkdfCtx
+ Crypto.PHKDF: phkdfCtx_toGen :: (Int -> ByteString) -> Word32 -> ByteString -> PhkdfCtx -> PhkdfGen
+ Crypto.PHKDF: phkdfCtx_toHmacCtx :: PhkdfCtx -> HmacCtx
+ Crypto.PHKDF: phkdfCtx_toHmacKeyPrefixed :: (Int -> ByteString) -> PhkdfCtx -> HmacKeyPrefixed
+ Crypto.PHKDF: phkdfCtx_toResetHmacCtx :: PhkdfCtx -> HmacCtx
+ Crypto.PHKDF: phkdfCtx_toStream :: (Int -> ByteString) -> Word32 -> ByteString -> PhkdfCtx -> Stream ByteString
+ Crypto.PHKDF: phkdfGen :: ByteString -> ByteString -> Word32 -> ByteString -> PhkdfGen
+ Crypto.PHKDF: phkdfGen_head :: PhkdfGen -> ByteString
+ Crypto.PHKDF: phkdfGen_hmacKey :: PhkdfGen -> Maybe HmacKey
+ Crypto.PHKDF: phkdfGen_hmacKeyHashed :: PhkdfGen -> Maybe HmacKeyHashed
+ Crypto.PHKDF: phkdfGen_hmacKeyLike :: PhkdfGen -> HmacKeyLike
+ Crypto.PHKDF: phkdfGen_hmacKeyPlain :: PhkdfGen -> Maybe HmacKeyPlain
+ Crypto.PHKDF: phkdfGen_hmacKeyPrefixed :: PhkdfGen -> HmacKeyPrefixed
+ Crypto.PHKDF: phkdfGen_init :: HmacKey -> ByteString -> Word32 -> ByteString -> PhkdfGen
+ Crypto.PHKDF: phkdfGen_initHashed :: HmacKeyHashed -> ByteString -> Word32 -> ByteString -> PhkdfGen
+ Crypto.PHKDF: phkdfGen_initLike :: HmacKeyLike -> ByteString -> Word32 -> ByteString -> PhkdfGen
+ Crypto.PHKDF: phkdfGen_initPrefixed :: HmacKeyPrefixed -> ByteString -> Word32 -> ByteString -> PhkdfGen
+ Crypto.PHKDF: phkdfGen_peek :: PhkdfGen -> Maybe ByteString
+ Crypto.PHKDF: phkdfGen_read :: PhkdfGen -> (ByteString, PhkdfGen)
+ Crypto.PHKDF: phkdfGen_toStream :: PhkdfGen -> Stream ByteString
+ Crypto.PHKDF.Assert: phkdfCtx_assertBufferPosition :: Word64 -> PhkdfCtx -> PhkdfCtx
+ Crypto.PHKDF.Assert: phkdfCtx_assertBufferPosition' :: Word64 -> PhkdfCtx -> PhkdfCtx
+ Crypto.PHKDF.Primitives: hmacKey :: HmacKeyPlain -> HmacKey
+ Crypto.PHKDF.Primitives: phkdfCtx :: ByteString -> PhkdfCtx
+ Crypto.PHKDF.Primitives: phkdfCtx_feedArg :: ByteString -> PhkdfCtx -> PhkdfCtx
+ Crypto.PHKDF.Primitives: phkdfCtx_feedArgConcat :: Foldable f => f ByteString -> PhkdfCtx -> PhkdfCtx
+ Crypto.PHKDF.Primitives: phkdfCtx_feedArgs :: Foldable f => f ByteString -> PhkdfCtx -> PhkdfCtx
+ Crypto.PHKDF.Primitives: phkdfCtx_feedArgsBy :: Foldable f => (a -> ByteString) -> f a -> PhkdfCtx -> PhkdfCtx
+ Crypto.PHKDF.Primitives: phkdfCtx_hmacKeyHashed :: PhkdfCtx -> Maybe HmacKeyHashed
+ Crypto.PHKDF.Primitives: phkdfCtx_hmacKeyLike :: PhkdfCtx -> HmacKeyLike
+ Crypto.PHKDF.Primitives: phkdfCtx_hmacKeyPlain :: PhkdfCtx -> Maybe HmacKeyPlain
+ Crypto.PHKDF.Primitives: phkdfCtx_hmacKeyPrefixed :: PhkdfCtx -> HmacKeyPrefixed
+ Crypto.PHKDF.Primitives: phkdfCtx_initHashed :: HmacKeyHashed -> PhkdfCtx
+ Crypto.PHKDF.Primitives: phkdfCtx_initLike :: HmacKeyLike -> PhkdfCtx
+ Crypto.PHKDF.Primitives: phkdfCtx_initPrefixed :: ByteString -> HmacKeyPrefixed -> PhkdfCtx
+ Crypto.PHKDF.Primitives: phkdfCtx_toGen :: (Int -> ByteString) -> Word32 -> ByteString -> PhkdfCtx -> PhkdfGen
+ Crypto.PHKDF.Primitives: phkdfCtx_toHmacCtx :: PhkdfCtx -> HmacCtx
+ Crypto.PHKDF.Primitives: phkdfCtx_toResetHmacCtx :: PhkdfCtx -> HmacCtx
+ Crypto.PHKDF.Primitives: phkdfCtx_toStream :: (Int -> ByteString) -> Word32 -> ByteString -> PhkdfCtx -> Stream ByteString
+ Crypto.PHKDF.Primitives: phkdfGen :: ByteString -> ByteString -> Word32 -> ByteString -> PhkdfGen
+ Crypto.PHKDF.Primitives: phkdfGen_hmacKey :: PhkdfGen -> Maybe HmacKey
+ Crypto.PHKDF.Primitives: phkdfGen_hmacKeyHashed :: PhkdfGen -> Maybe HmacKeyHashed
+ Crypto.PHKDF.Primitives: phkdfGen_hmacKeyLike :: PhkdfGen -> HmacKeyLike
+ Crypto.PHKDF.Primitives: phkdfGen_hmacKeyPlain :: PhkdfGen -> Maybe HmacKeyPlain
+ Crypto.PHKDF.Primitives: phkdfGen_hmacKeyPrefixed :: PhkdfGen -> HmacKeyPrefixed
+ Crypto.PHKDF.Primitives: phkdfGen_init :: HmacKey -> ByteString -> Word32 -> ByteString -> PhkdfGen
+ Crypto.PHKDF.Primitives: phkdfGen_initHashed :: HmacKeyHashed -> ByteString -> Word32 -> ByteString -> PhkdfGen
+ Crypto.PHKDF.Primitives: phkdfGen_initLike :: HmacKeyLike -> ByteString -> Word32 -> ByteString -> PhkdfGen
+ Crypto.PHKDF.Primitives: phkdfGen_initPrefixed :: HmacKeyPrefixed -> ByteString -> Word32 -> ByteString -> PhkdfGen
+ Crypto.PHKDF.Primitives: phkdfGen_toStream :: PhkdfGen -> Stream ByteString
+ Crypto.PHKDF.Primitives: phkdfSlowCtx_feedArg :: ByteString -> PhkdfSlowCtx -> PhkdfSlowCtx
+ Crypto.PHKDF.Primitives: phkdfSlowCtx_feedArgs :: Foldable f => f ByteString -> PhkdfSlowCtx -> PhkdfSlowCtx
+ Crypto.PHKDF.Primitives: phkdfSlowCtx_toStream :: (Int -> ByteString) -> PhkdfSlowCtx -> Stream ByteString
+ Crypto.PHKDF.Primitives.Subtle: [phkdfCtx_hmacKeyLike] :: PhkdfCtx -> !HmacKeyLike
+ Crypto.PHKDF.Primitives.Subtle: [phkdfGen_hmacKeyLike] :: PhkdfGen -> !HmacKeyLike
+ Crypto.PHKDF.Primitives.Subtle: phkdfCtx_byteLen :: PhkdfCtx -> Word64
+ Crypto.PHKDF.Subtle: PhkdfCtx :: !Word64 -> !Sha256Ctx -> !HmacKeyLike -> PhkdfCtx
+ Crypto.PHKDF.Subtle: PhkdfGen :: !HmacKeyLike -> !ByteString -> !Word32 -> !ByteString -> !Maybe Sha256Ctx -> PhkdfGen
+ Crypto.PHKDF.Subtle: [phkdfCtx_byteCount] :: PhkdfCtx -> !Word64
+ Crypto.PHKDF.Subtle: [phkdfCtx_hmacKeyLike] :: PhkdfCtx -> !HmacKeyLike
+ Crypto.PHKDF.Subtle: [phkdfCtx_state] :: PhkdfCtx -> !Sha256Ctx
+ Crypto.PHKDF.Subtle: [phkdfGen_counter] :: PhkdfGen -> !Word32
+ Crypto.PHKDF.Subtle: [phkdfGen_extTag] :: PhkdfGen -> !ByteString
+ Crypto.PHKDF.Subtle: [phkdfGen_hmacKeyLike] :: PhkdfGen -> !HmacKeyLike
+ Crypto.PHKDF.Subtle: [phkdfGen_initCtx] :: PhkdfGen -> !Maybe Sha256Ctx
+ Crypto.PHKDF.Subtle: [phkdfGen_state] :: PhkdfGen -> !ByteString
+ Crypto.PHKDF.Subtle: data PhkdfCtx
+ Crypto.PHKDF.Subtle: data PhkdfGen
+ Crypto.PHKDF.Subtle: phkdfCtx_unsafeFeed :: Foldable f => f ByteString -> PhkdfCtx -> PhkdfCtx
+ Crypto.PHKDF.V1.Cookbook: PhkdfInputArgs :: !ByteString -> !ByteString -> !Vector ByteString -> PhkdfInputArgs
+ Crypto.PHKDF.V1.Cookbook: PhkdfInputBlock :: !ByteString -> !ByteString -> !ByteString -> !Vector ByteString -> !Word32 -> PhkdfInputBlock
+ Crypto.PHKDF.V1.Cookbook: PhkdfInputTweak :: !Vector ByteString -> !ByteString -> PhkdfInputTweak
+ Crypto.PHKDF.V1.Cookbook: PhkdfSeed :: !ByteString -> !HmacKey -> !ByteString -> !ByteString -> PhkdfSeed
+ Crypto.PHKDF.V1.Cookbook: [phkdfInputArgs_credentials] :: PhkdfInputArgs -> !Vector ByteString
+ Crypto.PHKDF.V1.Cookbook: [phkdfInputArgs_password] :: PhkdfInputArgs -> !ByteString
+ Crypto.PHKDF.V1.Cookbook: [phkdfInputArgs_username] :: PhkdfInputArgs -> !ByteString
+ Crypto.PHKDF.V1.Cookbook: [phkdfInputBlock_domainTag] :: PhkdfInputBlock -> !ByteString
+ Crypto.PHKDF.V1.Cookbook: [phkdfInputBlock_longTag] :: PhkdfInputBlock -> !ByteString
+ Crypto.PHKDF.V1.Cookbook: [phkdfInputBlock_rounds] :: PhkdfInputBlock -> !Word32
+ Crypto.PHKDF.V1.Cookbook: [phkdfInputBlock_seguid] :: PhkdfInputBlock -> !ByteString
+ Crypto.PHKDF.V1.Cookbook: [phkdfInputBlock_tags] :: PhkdfInputBlock -> !Vector ByteString
+ Crypto.PHKDF.V1.Cookbook: [phkdfInputTweak_echoTag] :: PhkdfInputTweak -> !ByteString
+ Crypto.PHKDF.V1.Cookbook: [phkdfInputTweak_role] :: PhkdfInputTweak -> !Vector ByteString
+ Crypto.PHKDF.V1.Cookbook: [phkdfSeed_domainTag] :: PhkdfSeed -> !ByteString
+ Crypto.PHKDF.V1.Cookbook: [phkdfSeed_secret] :: PhkdfSeed -> !ByteString
+ Crypto.PHKDF.V1.Cookbook: [phkdfSeed_seguidKey] :: PhkdfSeed -> !HmacKey
+ Crypto.PHKDF.V1.Cookbook: [phkdfSeed_seguid] :: PhkdfSeed -> !ByteString
+ Crypto.PHKDF.V1.Cookbook: data PhkdfInputArgs
+ Crypto.PHKDF.V1.Cookbook: data PhkdfInputBlock
+ Crypto.PHKDF.V1.Cookbook: data PhkdfInputTweak
+ Crypto.PHKDF.V1.Cookbook: data PhkdfSeed
+ Crypto.PHKDF.V1.Cookbook: phkdfPass :: PhkdfInputBlock -> PhkdfInputArgs -> PhkdfInputTweak -> Stream ByteString
+ Crypto.PHKDF.V1.Cookbook: phkdfPass_seedFinalize :: PhkdfSeed -> PhkdfInputTweak -> Stream ByteString
+ Crypto.PHKDF.V1.Cookbook: phkdfPass_seedInit :: PhkdfInputBlock -> PhkdfInputArgs -> PhkdfSeed
+ Crypto.PHKDF.V1.Cookbook: phkdfSimple :: PhkdfInputBlock -> PhkdfInputArgs -> Stream ByteString
- Crypto.PHKDF.Primitives: data HmacKey
+ Crypto.PHKDF.Primitives: data () => HmacKey
- Crypto.PHKDF.Primitives: phkdfCtx_hmacKey :: PhkdfCtx -> HmacKey
+ Crypto.PHKDF.Primitives: phkdfCtx_hmacKey :: PhkdfCtx -> Maybe HmacKey
- Crypto.PHKDF.Primitives: phkdfCtx_init :: ByteString -> PhkdfCtx
+ Crypto.PHKDF.Primitives: phkdfCtx_init :: HmacKey -> PhkdfCtx
- Crypto.PHKDF.Primitives.Subtle: PhkdfCtx :: !Word64 -> !Ctx -> !HmacKey -> PhkdfCtx
+ Crypto.PHKDF.Primitives.Subtle: PhkdfCtx :: !Sha256Ctx -> !HmacKeyLike -> PhkdfCtx
- Crypto.PHKDF.Primitives.Subtle: PhkdfGen :: !HmacKey -> !ByteString -> !Word32 -> !ByteString -> !Maybe Ctx -> PhkdfGen
+ Crypto.PHKDF.Primitives.Subtle: PhkdfGen :: !HmacKeyLike -> !ByteString -> !Word32 -> !ByteString -> !Maybe Sha256Ctx -> PhkdfGen
- Crypto.PHKDF.Primitives.Subtle: [phkdfCtx_state] :: PhkdfCtx -> !Ctx
+ Crypto.PHKDF.Primitives.Subtle: [phkdfCtx_state] :: PhkdfCtx -> !Sha256Ctx
- Crypto.PHKDF.Primitives.Subtle: [phkdfGen_initCtx] :: PhkdfGen -> !Maybe Ctx
+ Crypto.PHKDF.Primitives.Subtle: [phkdfGen_initCtx] :: PhkdfGen -> !Maybe Sha256Ctx
Files
- ChangeLog.md +15/−0
- lib/Crypto/Encoding/PHKDF.hs +141/−61
- lib/Crypto/Encoding/PHKDF/V1.hs +112/−0
- lib/Crypto/PHKDF.hs +528/−336
- lib/Crypto/PHKDF/Assert.hs +36/−0
- lib/Crypto/PHKDF/HMAC.hs +6/−58
- lib/Crypto/PHKDF/HMAC/Subtle.hs +3/−39
- lib/Crypto/PHKDF/Primitives.hs +175/−84
- lib/Crypto/PHKDF/Primitives/Assert.hs +7/−0
- lib/Crypto/PHKDF/Primitives/Subtle.hs +19/−16
- lib/Crypto/PHKDF/Subtle.hs +55/−0
- lib/Crypto/PHKDF/V1/Cookbook.hs +395/−0
- phkdf.cabal +17/−6
- test/HMAC.hs +0/−2
- test/Main.hs +16/−3
- test/PHKDF.hs +8/−5
ChangeLog.md view
@@ -1,5 +1,20 @@ # Revision history for phkdf +## Version 0.1.0.0 (2025-01-20)++* This includes the Version 2 of PHKDF, which is mostly compatible with+ Version 1 except that the slow extract function has been removed and the+ end-of-message padding has been tweaked to save a byte when the domain tag+ is 20 bytes or longer. A simpler slow-extract function much closer to PBKDF2+ will eventually be factored out of g3p-hash version 2.++* Saving this byte requires bitstring SHA256 inputs, thus the binding has been+ moved from cryptohash-sha256 to the new sha256 package. This is a new FFI+ binding around the core cryptohash implementation that better and more+ robustly supports the needs of this library.++* The old code in Crypto.PHKDF has been moved to Crypto.PHKDF.V1.Cookbook+ ## Version 0.0.0.0 (2024-03-21) * The definition of Crypto.PHKDF.Primitives is quite stable.
lib/Crypto/Encoding/PHKDF.hs view
@@ -1,60 +1,98 @@ {-# LANGUAGE OverloadedStrings, ViewPatterns #-} +-------------------------------------------------------------------------------+-- |+-- Module: Crypto.Encoding.PHKDF+-- Copyright: (c) 2024 Auth Global+-- License: Apache2+--+-------------------------------------------------------------------------------+ module Crypto.Encoding.PHKDF where -import Data.Monoid((<>))-import Data.Bits(Bits, (.&.))+import Data.Bits(Bits, (.&.), shift) import Data.ByteString(ByteString)-import Data.Foldable(Foldable)+import Data.Int(Int64)+import Data.List(scanl') import qualified Data.ByteString as B-import Crypto.Encoding.SHA3.TupleHash import Debug.Trace --- FIXME: several functions in here have opportunites for optimization--cycleByteStringToList :: ByteString -> Int -> [ByteString]-cycleByteStringToList str outBytes =- if outBytes <= 0- then []- else if n == 0- then [ B.replicate outBytes 0 ]- else replicate q str ++ [B.take r str]- where- n = B.length str- (q,r) = outBytes `quotRem` n--cycleByteStringWithNullToList :: ByteString -> Int -> [ByteString]-cycleByteStringWithNullToList str outBytes = out- where- out = cycleByteStringToList (str <> "\x00") outBytes--cycleByteString :: ByteString -> Int -> ByteString-cycleByteString str outBytes = B.concat (cycleByteStringToList str outBytes)--cycleByteStringWithNull :: ByteString -> Int -> ByteString-cycleByteStringWithNull str outBytes =- B.concat (cycleByteStringWithNullToList str outBytes)- extendTagToList :: ByteString -> [ByteString] extendTagToList tag = if n <= 19 then [tag] else tag' where n = B.length tag- x = (18 - n) `mod` 64- tag' = cycleByteStringWithNullToList tag (n+x)- ++ [B.singleton (fromIntegral x)]+ x = (19 - n) `mod` 64+ tag' = takeBs (fromIntegral (n+x)) (cycle [tag, "\x00"])+ ++ [B.singleton (fromIntegral x `shift` 2)] +-- | Extends a PHKDF end-of-message tag in order to ensure the last SHA-256+-- block contains something interesting.+--+-- Tags less than 160 bits (20 bytes) long are appended directly, without+-- extension, as the final portion of the message. Thus this function+-- is the identity on short inputs.+--+-- After extension, tags that are at least 20 bytes long should be thought+-- of as a bitstring with a single null bit appended at the end to make it+-- a full bytestring.+--+-- Tags 160 bits or longer are first extended, iff necessary, to a full+-- bytestring by adding a single "1" bit followed by zero to six "0" bits.+--+-- The bytestring is then extended by 0-63 bytes as needed to make the+-- overall length equivalent to 19 (mod 64). The first byte of the extension+-- is a null byte, then followed by the bytestring, then starting again+-- at the null byte as needed.+--+-- The length of this extension takes up the first 6 bits of the last byte,+-- followed by a "0" bit denoting the tag is a bytestring, or a "1" denoting+-- that the tag is a proper bitstring whose length is not an exact multiple+-- of 8.+--+-- The final bit is reserved for SHA-256's end-of-message padding, which+-- will set it to 1.+ extendTag :: ByteString -> ByteString extendTag = B.concat <$> extendTagToList -trimExtTag :: ByteString -> Maybe ByteString-trimExtTag extTag+-- | This function robustly undoes 'extendTag', thus "proving" that all+-- collisions on PHKDF's tag are cryptographically non-trivial, even after+-- extension.+--+-- This is a "proof" in the sense that if+-- @trimExtendedTag (extendTag x) == Just x@ is true for all bytestrings+-- @x@, then all collisions are non-trivial, but we haven't presented a+-- full deductive proof of this property. It is part of the test suite,+-- tested by quickcheck fuzzing.+--+-- The rest of PHKDF and the G3P's syntax follows this as an iron rule+-- of syntax design. I've not literally written a program to parse out+-- the original arguments, but I've ensured that it is straightforward+-- to do so in principle.+--+-- In the case of variable-length PHKDF, starting from some known buffer+-- position (usually either 0 or 32), first there are zero or more+-- bitstring arguments encoded via TupleHash syntax. Since TupleHash's+-- length encoding cannot start with a null byte, a single null byte+-- is used to signal the end of these input arguments. Then 0-63 end+-- padding bytes are generated in order to bring the buffer position+-- equivalent to 32 (mod 64), then 4 bytes of counter, then the extended+-- version of PHKDF's end-of-message tag, then finally SHA256's end padding.+--+-- This is easy to robustly undo, as I've started to demonstrate in this+-- subroutine. This leads to a simple categorical/combinatorial style proof+-- that all collisions over PHKDF's input arguments and domain tag are+-- cryptographically non-trivial.++trimExtendedTag :: ByteString -> Maybe ByteString+trimExtendedTag extTag | n <= 19 = Just extTag | extTag /= extendTag tag = Nothing | otherwise = Just tag where n = B.length extTag- x = B.last extTag+ x = B.last extTag `shift` (-2) tag = B.take (n - fromIntegral x - 1) extTag {--@@ -77,39 +115,81 @@ | b >= c = b | otherwise = c + ((b - c) .&. 63) +-- | Equivalent to 'add64WhileLt', except with trace debugging. This should+-- never be used in production.+ add64WhileLt' :: (Ord a, Num a, Bits a, Show a) => a -> a -> a add64WhileLt' b c | b >= c = b- | otherwise = let d = c + ((b - c) .&. 63)- in trace (show b ++ " -> " ++ show d) d+ | otherwise = trace msg d+ where+ d = c + ((b - c) .&. 63)+ msg = show b ++ " + " ++ show ((d - b) `shift` (-6)) ++ " * 64 == "+ ++ show d ++ " == " ++ show c ++ " + " ++ show (d - c) -usernamePadding :: Foldable f => f ByteString -> ByteString -> ByteString -> ByteString-usernamePadding headerExtract fillerTag domainTag- = cycleByteStringWithNull fillerTag (a-32)- <> cycleByteStringWithNull domainTag 32+dropBs :: Int64 -> [ ByteString ] -> [ ByteString ]+dropBs = go where- al = encodedVectorByteLength headerExtract- a = add64WhileLt (157 - al) 32+ len = fromIntegral . B.length+ go _ [] = []+ go 0 bs = bs+ go n (b:bs)+ | n >= len b = go (n - len b) bs+ | otherwise = B.drop (fromIntegral n) b : bs -passwordPaddingBytes :: Foldable f => Int -> f ByteString -> f ByteString -> ByteString -> ByteString -> ByteString -> ByteString-passwordPaddingBytes bytes headerUsername headerLongTag fillerTag domainTag password- = cycleByteStringWithNull fillerTag (c-32)- <> cycleByteStringWithNull domainTag 32+takeBs :: Int64 -> [ ByteString ] -> [ ByteString ]+takeBs = go where- al = encodedVectorByteLength headerLongTag- a = add64WhileLt (bytes - al) 3240- bl = encodedVectorByteLength headerUsername- b = add64WhileLt (a - bl) 136- cl = encodedByteLength password- c = add64WhileLt (b - cl) 32+ len = fromIntegral . B.length+ go _ [] = []+ go n (b:bs)+ | n <= 0 = []+ | len b < n = b : go (n - len b) bs+ | otherwise = [B.take (fromIntegral n) b] -passwordPadding :: Foldable f => f ByteString -> f ByteString -> ByteString -> ByteString -> ByteString -> ByteString-passwordPadding = passwordPaddingBytes 8413+takeBs' :: Int64 -> [ ByteString ] -> [ ByteString ]+takeBs' n bs = if haveEnough then takeBs n bs else []+ where+ len = fromIntegral . B.length+ haveEnough = any (>= n) (scanl' (+) 0 (map len bs)) -credentialsPadding :: Foldable f => f ByteString -> ByteString -> ByteString -> ByteString-credentialsPadding credentials fillerTag domainTag- = cycleByteStringWithNull fillerTag (a-29)- <> cycleByteStringWithNull domainTag 29+takeB' :: Int64 -> ByteString -> Maybe ByteString+takeB' n bs =+ -- this fromIntegral is inherently safe+ if fromIntegral (B.length bs) < n+ then Nothing+ -- this fromIntegral is safe because of the check above+ else Just (B.take (fromIntegral n) bs)++assertTakeB' :: Int64 -> ByteString -> ByteString+assertTakeB' = (maybe (error "not enough bytes") id <$>) . takeB'++nullBuffer :: ByteString+nullBuffer = B.replicate 64 0++-- | Partition a bytestring into chunks of up to a given size++chunkify :: Int -> ByteString -> [ ByteString ]+chunkify n = go where- al = encodedVectorByteLength credentials- a = add64WhileLt (122 - al) 32+ go bs+ | B.null bs = []+ | otherwise = bs0 : go bs1+ where (bs0, bs1) = B.splitAt n bs++-- | Partition a cyclically extended bytestring into chunks of+-- a given size, starting at a given offset.+--+-- Note that repetitions of the original string get a single+-- null byte placed between them.++chunkifyCycle+ :: Int64 -- ^ Desired chunk size+ -> ByteString -- ^ String to be cyclically extended.+ -> Int64 -- ^ Starting offset+ -> [ ByteString ] -- ^ Infinite stream of chunks+chunkifyCycle len bs = go+ where+ modN pos = pos `mod` (fromIntegral (B.length bs) + 1)+ ext = B.concat (bs:takeBs len (cycle ["\x00", bs]))+ go (modN -> pos) = assertTakeB' len (B.drop (fromIntegral pos) ext) : go (pos + len)
+ lib/Crypto/Encoding/PHKDF/V1.hs view
@@ -0,0 +1,112 @@+{-# LANGUAGE OverloadedStrings, ViewPatterns #-}++-------------------------------------------------------------------------------+-- |+-- Module: Crypto.Encoding.PHKDF.V1+-- Copyright: (c) 2024 Auth Global+-- License: Apache2+--+-------------------------------------------------------------------------------++module Crypto.Encoding.PHKDF.V1 where++import Data.Monoid((<>))+import Data.ByteString(ByteString)+import Data.Foldable(Foldable)+import qualified Data.ByteString as B++import Crypto.Encoding.PHKDF(add64WhileLt)+import Crypto.Encoding.SHA3.TupleHash++-- FIXME: most of the older parts of this module should be deleted, but+-- need to move to something better first.++cycleByteStringToList :: ByteString -> Int -> [ByteString]+cycleByteStringToList str outBytes =+ if outBytes <= 0+ then []+ else if n == 0+ then [ B.replicate outBytes 0 ]+ else replicate q str ++ [B.take r str]+ where+ n = B.length str+ (q,r) = outBytes `quotRem` n++cycleByteStringWithNullToList :: ByteString -> Int -> [ByteString]+cycleByteStringWithNullToList str outBytes = out+ where+ out = cycleByteStringToList (str <> "\x00") outBytes++cycleByteString :: ByteString -> Int -> ByteString+cycleByteString str outBytes = B.concat (cycleByteStringToList str outBytes)++cycleByteStringWithNull :: ByteString -> Int -> ByteString+cycleByteStringWithNull str outBytes =+ B.concat (cycleByteStringWithNullToList str outBytes)++extendTagToList :: ByteString -> [ByteString]+extendTagToList tag = if n <= 19 then [tag] else tag'+ where+ n = B.length tag+ x = (18 - n) `mod` 64+ tag' = cycleByteStringWithNullToList tag (n+x)+ ++ [B.singleton (fromIntegral x)]++extendTag :: ByteString -> ByteString+extendTag = B.concat <$> extendTagToList++trimExtTag :: ByteString -> Maybe ByteString+trimExtTag extTag+ | n <= 19 = Just extTag+ | extTag /= extendTag tag = Nothing+ | otherwise = Just tag+ where+ n = B.length extTag+ x = B.last extTag+ tag = B.take (n - fromIntegral x - 1) extTag++{--++FIXME: as written, this only works on signed arithmetic, unless the modulus @a@+is a power of 2, such as 64++-- | @addWhileLt a b c@ is equivalent to @while (b < c) { b += a }; return b@+addWhileLt :: Integral a => a -> a -> a -> a+addWhileLt a b c+ | b >= c = b+ | otherwise = c + ((b - c) `mod` a)++--}++-- | @add64WhileLt b c@ is equivalent to @while (b < c) { b += 64 }; return b@++usernamePadding :: Foldable f => f ByteString -> ByteString -> ByteString -> ByteString+usernamePadding headerExtract fillerTag domainTag+ = cycleByteStringWithNull fillerTag (a-32)+ <> cycleByteStringWithNull domainTag 32+ where+ al = encodedVectorByteLength headerExtract+ a = add64WhileLt (157 - al) 32++passwordPaddingBytes :: Foldable f => Int -> f ByteString -> f ByteString -> ByteString -> ByteString -> ByteString -> ByteString+passwordPaddingBytes bytes headerUsername headerLongTag fillerTag domainTag password+ = cycleByteStringWithNull fillerTag (c-32)+ <> cycleByteStringWithNull domainTag 32+ where+ al = encodedVectorByteLength headerLongTag+ a = add64WhileLt (bytes - al) 3240+ bl = encodedVectorByteLength headerUsername+ b = add64WhileLt (a - bl) 136+ cl = encodedByteLength password+ c = add64WhileLt (b - cl) 32++passwordPadding :: Foldable f => f ByteString -> f ByteString -> ByteString -> ByteString -> ByteString -> ByteString+passwordPadding = passwordPaddingBytes 8413++credentialsPadding :: Foldable f => f ByteString -> ByteString -> ByteString -> ByteString+credentialsPadding credentials fillerTag domainTag+ = cycleByteStringWithNull fillerTag (a-29)+ <> cycleByteStringWithNull domainTag 29+ where+ al = encodedVectorByteLength credentials+ a = add64WhileLt (122 - al) 32
lib/Crypto/PHKDF.hs view
@@ -1,384 +1,576 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedStrings, BangPatterns, ScopedTypeVariables #-} --- | The Password Hash Key Derivation Function (PHKDF) is a unification,--- synthesis, and distillation of PBKDF2, HKDF, and TupleHash. It was--- designed as a building block for implementing a variety of--- self-documenting cryptographic constructions.------ This module is intended more as a demonstration of and cookbook for--- what can be done with the PHKDF primitives. For actual deployments,--- consider if the Global Password Prehash Protocol (G3P) is more--- appropriate for your needs. The G3P is a variant of 'phkdfPass' that--- additionally integrates bcrypt as the primary key-stretching component.------ These examples also serve as design studies that help informally justify--- the G3P. Within my design framework, I've tried to maximize the benefits--- while managing implementation costs.------ 1. Every bit of every parameter matters. Every boundary between--- parameters matter. There aren't supposed to be any trivial collisions,--- the only exception being null-extension collisions on the seguid.------ 2. Except for the tweaks, any change to any parameter requires restarting--- the PHKDF key-stretching computation from somewhere in the very first--- call to HMAC.------ 3. All input arguments are hardened against length-related timing side--- channels in various different ways.------ At one extreme, the username, password, and long tag have the most--- aggressive length hardening in the conventional sense, exhibiting no--- timing side channels except on multi-kilobyte inputs, after which--- the timing impacts are minimzed.------ At another extreme, the domain tag exhibits severe yet predictable--- timing side channels transitioning from 19 to 20 bytes and every 64--- bytes thereafter. However, the domain tag is otherwise free of--- timing-based side channels, so it too is hardened in its own way.+-------------------------------------------------------------------------------+-- |+-- Module: Crypto.PHKDF+-- Copyright: (c) 2024 Auth Global+-- License: Apache2 ----- The design I converged upon employs fairly complicated data encoding--- procedures. Unfortunately, this provides a fair bit of surface area--- for subtly wrong implementations that work most of the time, but will--- return garbage on certain lengths of inputs. I hope that this will--- eventually be remediated with a more comprehensive suite of test vectors.+------------------------------------------------------------------------------- -module Crypto.PHKDF where+{- | +This module provides an interface to Version 2 of PHKDF, especially the+following function:++@+phkdfStream :: BitString -> [BitString] -> Word32 -> BitString -> Stream ByteString+phkdfStream key args counter tag = [output0, output1 ..]+ where+ output0 = hmac key (encode args ++ encode counter ++ tag)+ output1 = hmac key (output0 ++ encode (counter + 1) ++ tag)+ output2 = hmac key (output1 ++ encode (counter + 2) ++ tag)+ ...+@++This hash function exhibits a misleading resemblance to HKDF, with the @key@+corresponding to HKDF's @salt@, the @msgs@ parameter corresponding to HKDF's+@ikm@ (initial keying material), and the @counter@ and @tag@ parameters+corresponding to HKDF's info parameter.++@+hkdf :: BitString -> BitString -> ByteString -> [ByteString]+hkdf salt ikm info = [output1, output2 .. output255]+ where+ key = hmac salt ikm+ output1 = hmac key (info ++ encodeWord8 1)+ output2 = hmac key (output1 ++ info ++ encodeWord8 2)+ output3 = hmac key (output2 ++ info ++ encodeWord8 3)+ ...+@++However this is a false cognate. The first thing to notice about @phkdfStream@+is that it doesn't matter how secure the @args@ parameter is, if you use a+publicly known key, counter, and tag, then revealing a full output block reveals+the remainder of the output stream.++This is in contrast to @hkdf@, which allows secret initial keying material and+publicly-known salt and info parameters to be expanded into a large number of+output blocks. These blocks can be divvied up into non-overlapping pieces that+may be revealed independently of each other.++Thus @phkdfStream@ is actually a much lower-level hash function than @hkdf@. As+such has it's own /modes of operation/, which provide various different answers+for this issue of output stream predictability. Building a proper replacement+for @hkdf@ requires combining two or more calls to @phkdfStream@ in different+modes of operation.++The first and simplest mode of operation for @phkdfStream@ is to simply discard+all but the first output block. In this case, @phkdfStream@ simplifies to a call+to HMAC with the addition of TupleHash style encoding, and custom end-of-message+padding determined by the counter and tag. Thus we can use this mode to+implement the key extraction portion of an HKDF-like hash function.++In this mode of operation, we can safely use @phkdfStream@ with secret initial+keying materials and optionally non-secret salt, counter, and tag, and possibly+even reveal the output. After all it doesn't matter if anybody can predict the+remainder of the stream if it's never been granted any meaning.++The second mode of operation is to use @phkdfStream@ with a secret key,+non-secret arguments, and optionally secret counter and tag. In this mode, we+can reveal arbitrary non-overlapping portions of the output stream to third+parties, without worry that one portion can be derived from another.++Thus we can implement a variant of the HKDF construction using these two modes+of operation in conjunction with each other:++@+hkdfSimple :: BitString -> [BitString] -> BitString -> Stream ByteString+hkdfSimple salt ikms tag = out+ where+ key = head $ phkdfStream salt ikms inCtr tag+ out = phkdfStream key echoArgs outCtr tag++ echoArgs = ["hkdf-simple"]+ inCtr = word32 "IN\x00\x00"+ outCtr = word32 "OUT\x00"+@++If the recommendations of NIST SP 800-108 are to be followed strictly, one+shouldn't examine more than 2^32 output blocks which is about 137.4 GB of+output from @hkdfSimple@. I don't think this will be a problem in practice,+as this particular CSPRNG is not overly well suited to generating large amounts+of pseudorandom data.++However, we must be aware of the /echo args gotcha/: for reasons intimately+related to the predictability of @phkdfStream@ with a non-secret key, counter,+and tag, the @echoArgs@ parameter must not include any important new secrets.++This time we are deriving a secret key using initial keying material. However,+if that material is potentially guessable, then introducing a high-entropy+secret in the @echoArgs@ parameter will secure the first output block, but+revealing two output blocks would re-reveal the ability to guess the original+keying material.++Thus all secrets should be included in the derivation of the key, or possibly+included in the tag parameter. A secret counter can also help, but cannot+provide a sufficient level of entropy tmo secure the output all by itself.++One of HKDF's design principles was to obtain a clean seperation between the+extraction and expansion phases. This seperation allows HKDF's design to avoid+the /echo args gotcha/ by specifying that the echo args is the empty string.++In a literal, low-level sense, @phkdfStream@ intentionally violates this+seperation. In a metaphorical, higher-level sense, @phkdf@ affirms this design+principle, rather @phkdf@'s' goal is to allow a single primitive to serve both+roles. This unification makes it easy to create cryptographic hash protocols+where every call to HMAC is covered by a directly self-documenting plaintext tag.++Moreover, the alternative to PBKDF2 is phkdf's slow extraction function, which+makes crucial use of the /echo args gotcha/. This brings us to the third mode+of operation, which keeps the output stream secret, except possibly for the very+last output block examined.++Each mode of operation provides an answer to the predictability of @phkdfStream@.+Our first answer is to make it irrelevant that the output stream is predictable.+Our second answer achieves unpredictability by using a key, counter, and/or tag+that is secret. The third answer achieves unpredictability by keeping the output+stream secret, allowing a publicly-known key, counter, and tag to be used as+self-documenting domain seperation constants.++Thus phkdf's slow extraction function calls @phkdfStream@ to generate a stream+that is allowed to be predictable, but at an unpredictable starting point. This+predictable stream remains secret, and is immediately consumed by a second call+to @phkdfStream@. After @rounds + 1@ blocks have been produced and consumed, the+second call to @phkdfStream@ has an opportunity to add some additional+post-key-stretching tweaks before the output stream is finalized.++Conceptually, the slow extraction function looks like this:++@+phkdfSlowExtract ::+ BitString -> [BitString] -> Word32 -> BitString ->+ ByteString -> Word32 -> [BitString] -> Stream ByteString+phkdfSlowExtract key args counter tag fnName rounds tweaks = out+ where+ blocks = take (rounds + 1) $ phkdfStream key args counter tag+ header = [makePadding fnName rounds, makeLongString tag blocks]+ out = phkdfStream key (header ++ tweaks) (counter + rounds + 1) tag+@++Compared to PBKDF2, @phkdfSlowExtract@ uses essentially the same stream+generator, but enhanced with counters and contextual parameters. PBKDF2 proper+then condenses that stream by xor-ing all the output blocks together.+@phkdfSlowExtract@ condenses it's internal stream by feeding it to another call+to HMAC. So @phkdfSlowExtract@ is very likely at least as strong as PBKDF2.++Again, assuming key, counter, rounds, and tag are all publicly known, which is+the primary intended use case of this function, then the output stream is+predictable. Thus the output of @phkdfSlowExtract@ must itself be subjected to+the first or third mode of operation.++If more than 32 bytes ever need to be revealed, then another call to+@phkdfStream@ with a secret key in the second mode of operation is required+for final output expansion. We do just this in our next example.++@phkdfVerySimple@ uses our flavor of not-quite-PBKDF2 to produce a pseudorandom+key to use with our flavor of not-quite-HKDF for final output expansion. Thus+the algorithm behind this construction is a portmanteau of the algorithms behind+PBKDF2 and HKDF. Thus the name.++@+phkdfVerySimple ::+ BitString -> BitString -> BitString -> BitString ->+ Word32 -> Stream ByteString+phkdfVerySimple seguid tag username password rounds = out+ where+ inArgs = [myLabel, username, password, encode rounds]++ key = head $ phkdfSlowExtract seguid inArgs inCtr tag myLabel rounds []++ out = phkdfStream key [myLabel] outCtr tag++ myLabel = "phkdf-very-simple"+ inCtr = word32 "IN\x00\x00"+ outCtr = word32 "OUT\x00"+@++@phkdfVerySimple@ is a distillation of the core features of the @phkdfSimple@+function exported from the @Crypto.PHKDF@ module, containing the most salient+features of that more fully worked construction.++Not only does @phkdfVerySimple@ provide key stretching very similar in flavor+to PBKDF2, but it also infuses the entire key-stretching process with+cryptoacoustic repetitions of the plaintext of the tag. This amplifies the+minimum obfuscation overhead associated with any tag obscuration attack that is+truly secure against the best reverse engineers. This in turns reduces the+minimum obfuscation overhead associated with a single application of SHA256+in order for the overall construction to be cryptoacoustically viable.++@phkdfVerySimple@ encodes the number of rounds to be performed in the+key-stretching phase in order to ensure that changing the number of rounds+requires a full key-stretching recomputation. This is necessary because it is+possible to share portions of @phkdfSlowExtract@'s key-stretching computation+when the @rounds@ parameter is varied while holding the input arguments+constant. Including an encoding of the @rounds@ parameter in the input arguments+forces both to be varied, thus forcing a full recomputation.+-}++module Crypto.PHKDF+ ( HmacKey()+ , hmacKey+ , PhkdfCtx()+ , phkdfCtx+ , phkdfCtx_init+ , phkdfCtx_initHashed+ , phkdfCtx_initPrefixed+ , phkdfCtx_initLike+ , phkdfCtx_hmacKeyPlain+ , phkdfCtx_hmacKeyHashed+ , phkdfCtx_hmacKeyPrefixed+ , phkdfCtx_hmacKey+ , phkdfCtx_hmacKeyLike+ , phkdfCtx_toResetHmacCtx+ , phkdfCtx_reset+ , phkdfCtx_feedArg+ , phkdfCtx_feedArgs+ , phkdfCtx_feedArgsBy+ , phkdfCtx_feedArgConcat+ , phkdfCtx_finalize+ , phkdfCtx_finalizeHmac+ , phkdfCtx_toHmacCtx+ , phkdfCtx_toHmacKeyPrefixed+ , phkdfCtx_toStream+ , phkdfCtx_toGen+ , phkdfCtx_byteCount+ , phkdfCtx_endPaddingLength+ , phkdfCtx_blockPaddingLength+{--+--- FIXME: add an updated xor-based SlowCtx, closer to PBKDF2+ , PhkdfSlowCtx()+ , phkdfSlowCtx_extract+ , phkdfSlowCtx_feedArg+ , phkdfSlowCtx_feedArgs+ , phkdfSlowCtx_finalize+ , phkdfSlowCtx_toStream+--}+ , PhkdfGen()+ , phkdfGen+ , phkdfGen_init+ , phkdfGen_initHashed+ , phkdfGen_initPrefixed+ , phkdfGen_initLike+ , phkdfGen_hmacKeyPlain+ , phkdfGen_hmacKeyHashed+ , phkdfGen_hmacKeyPrefixed+ , phkdfGen_hmacKey+ , phkdfGen_hmacKeyLike+ , phkdfGen_head+ , phkdfGen_read+ , phkdfGen_peek+ , phkdfGen_toStream+ ) where++import Control.Arrow((>>>))+import Data.Bits((.&.), complement) import Data.ByteString (ByteString) import qualified Data.ByteString as B import Data.Function((&))+import Data.Foldable(Foldable, foldl') import Data.Word-import Data.Stream (Stream)-import Data.Vector (Vector)-import qualified Data.Vector as V-import Network.ByteOrder (word32)+import Data.Stream (Stream(..))+import qualified Data.Stream as Stream+import Network.ByteOrder (bytestring32) +import Crypto.Sha256 as Sha256+import Crypto.PHKDF.HMAC+import Crypto.PHKDF.HMAC.Subtle+import Crypto.PHKDF.Subtle import Crypto.Encoding.PHKDF import Crypto.Encoding.SHA3.TupleHash-import Crypto.PHKDF.Primitives-import Crypto.PHKDF.Primitives.Assert --- | These input parameters are grouped together because the envisioned use--- for them is that they are constants (or near-constants) specified by--- a deployment. User-supplied inputs would typically not go here.------ The seguid parameter acts as a deployment-wide salt. Cryptographically--- speaking, the most important thing a deployment can do is specify a--- constant seguid. It is highly recommended that the seguid input be a--- genuine Self-Documenting Globally Unique Identifier attesting to the--- parameters, purposes, and public playbook of the protocol for y'all--- to follow.------ In more concrete cryptographic terms, the seguid parameter is the constant--- HMAC key used by the protocol right up until the final output exansion.--- This design is closely modelled on the HKDF construction. As such, adding--- null bytes onto the ends of seguids that are less than 64 bytes long--- should be the only source of trivial collisions in the entire protocol.------ The remaining parameter strings are all directly-documenting plaintext--- tags. A deployment can use these tags to encode a message into the password--- hash function so that it must be known to whomever is hashing a password--- of their choice.------ Finally, the rounds parameter determines the latency of the function.--- At least 250,000 rounds are recommended if PHKDF is used as the sole key--- stretching component of a password hash database.------ Unfortunately PHKDF is inexpensively parallelized, so large investments--- here aren't a good expenditure of a user's latency budget. This is why--- the G3P integrates bcrypt, and cuts the suggested rounds down to 20,000------ For comparison, @n@ rounds of PHKDF is approximately equivalent to--- @(1.5 + dtl)*n + c@ rounds of PBKDF2, where @dtl@ is related to the domain--- tag length, and c is a bit larger than 130 or so.------ Here, @dtl@ is 0 when the domain tag is between 0 and 19 bytes long, 0.5--- when the domain tag is between 20 and 83 bytes long, and an additional 0.5--- for every 64 bytes thereafter. Thus these functions exhibit extreme--- timing side channels on the length of the domain tag.------ By contrast, the long tag is hardened against timing side channels up to--- a bit less than 5 kilobytes in length. However, an extremely long tag--- does reduce the headroom provided to masking the length of the username--- and password fields, however the minimum headroom allocated to the--- username and password fields is a bit less than 3 kilobytes.------ As an alternate tagging location, consider the 'phkdfInputArgs_credentials'--- vector, which can be used as an inexpensive, pay-as-you-go plaintext--- tagging location.------ If the total encoded byte length of 'phkdfInputBlock_tags' is between 0-63--- bytes, then these hash protocols operate in a constant number of SHA256--- blocks. Every additional 64 bytes incurs the computation of two or three--- additional SHA256 blocks, because these tags are hashed into the result--- two times in the case of 'phkdfPass', and three times in the case of--- 'phkdfSimple' (and @g3pHash@).+import Control.Exception(assert) -data PhkdfInputBlock = PhkdfInputBlock- { phkdfInputBlock_seguid :: !ByteString- -- ^ HMAC-SHA256 key, usable as a high-repetition indirect tag via- -- self-documenting globally unique identifiers (seguids).- , phkdfInputBlock_domainTag :: !ByteString- -- ^ plaintext tag with one repetition per round. 0-19 bytes are free,- -- 20-83 bytes cost a additional sha256 block per round, with every- -- 64 bytes thereafter incurring a similar cost.- , phkdfInputBlock_longTag :: !ByteString- -- ^ plaintext tag with 1x repetition, then cycled for roughly- -- 8 kilobytes. Constant time on inputs up to nearly 5 kilobytes.- , phkdfInputBlock_tags :: !(Vector ByteString)- -- ^ plaintext tag with 2x repetition ('phkdfPass') or 3x repetition- -- ('phkdfSimple'). Constant-time on 0-63 encoded bytes, which includes- -- the length encoding of each string. Thus 60 of those bytes are usable- -- if the tags vector is a single string, or less if it contains two or- -- more strings.- , phkdfInputBlock_rounds :: !Word32- -- ^ how expensive will this hash function be? An optimal implementation- -- computes exactly three SHA256 blocks per round if the domain tag is- -- 19 bytes or less. It is not recommended that phkdf be used as the- -- primary key-stretching component of a deployment, but if it is used- -- this way, we recommend at least 250,000 rounds. This can be adjusted- -- downward in the case of domain tags longer than 19 bytes.- } deriving (Eq, Ord, Show)+-- | initialize an empty @phkdfStream@ context from a plaintext HMAC key. --- | The username and password are grouped together because they are normally--- expected to be supplied by users or other observers of a deployment.------ Furthermore, the credentials vector is here because it is an ideal--- location to include other user input. For example, one could implement--- a Two-Secret Key Derivation (2SKD) scheme analogous to 1Password's.------ A deployment can also specify additional constant tags as part of the--- credentials vector. As the plaintext of these tags is only ever hashed--- into the output a single time, this is the least expensive--- pay-as-you-go option for plaintext tagging.------ The credentials vector is constant time on 0-63 encoded bytes, incurring--- one additional SHA256 block every 64 bytes thereafter. This includes--- a variable-length field that encodes the bit length of each string; this--- field itself requires 2 or more bytes.------ The username and password are constant time as long as their encoded--- lengths add up to less than roughly 3 kilobytes, or the username,--- password, and domain tag add up to less than roughly 8 kilobytes.--- The actual numbers are somewhat less in both cases, but this is a--- good approximation.+phkdfCtx :: ByteString -> PhkdfCtx+phkdfCtx = phkdfCtx_init . hmacKey -data PhkdfInputArgs = PhkdfInputArgs- { phkdfInputArgs_username :: !ByteString- -- ^ The name of this parameter is suggestive, but this parameter is- -- functionally identical to a second password. The only difference- -- is the fact that a password can be cracked without knowledge of the- -- plaintext username. By contrast, the password acts as a plaintext tag- -- if one provides the username: guessing the username implies plaintext- -- knowledge of the password.- , phkdfInputArgs_password :: !ByteString- , phkdfInputArgs_credentials :: !(Vector ByteString)- } deriving (Eq, Ord, Show)+-- | initialize an empty @phkdfStream@ context from a plaintext or precomputed HMAC key. --- | These parameters are used to tweak the final output, without redoing any--- expensive key stretching. A possible use case is including a high entropy--- secret in the role itself that isn't available until after a successful--- stage of authentication.------ Since these parameters are processed in a context that could conceivably be--- performance sensitive, we don't apply any length padding or side-channel--- hardening. Instead we opt for maximizing free tagging space. Thus we--- want to avoid incurring additional SHA256 block computations, one of the--- favorite techniques employed by the key-stretching phase of 'phkdfPass'--- to harden against timing side-channels.------ A deployment could conceivably harden this expansion phase against timing--- side channels themselves, if the were sufficiently inclined. There are--- several techniques. For starters, a deployment could specify an additional--- variable-length string in the role vector, used to control its relative--- ending position inside the SHA256 buffer.+phkdfCtx_init :: HmacKey -> PhkdfCtx+phkdfCtx_init = phkdfCtx_initLike . hmacKeyLike_init -data PhkdfInputTweak = PhkdfInputTweak- { phkdfInputTweak_role :: !(Vector ByteString)- , phkdfInputTweak_echoTag :: !ByteString- } deriving (Eq, Ord, Show)+-- | initialize an empty @phkdfStream@ context from a plaintext, precomputed, or buffer-prefixed HMAC key. --- | A plain-old-data explicit representation of the intermediate 'phkdfPass'--- computation after the 'PhkdfInputBlock' and 'PhkdfInputArgs' have been--- processed and key stretching has been completed, but before the tweaks--- have been applied and the final output generated.------ If you ever need to serialize or persist a seed, you probably want this.------ Intended to be generated by 'phkdfPass_seedInit' and then consumed--- without modification by 'phkdfPass_seedFinalize'.+phkdfCtx_initLike :: HmacKeyLike -> PhkdfCtx+phkdfCtx_initLike key =+ PhkdfCtx {+ phkdfCtx_byteCount = hmacKeyLike_byteCount key,+ phkdfCtx_state = hmacKeyLike_ipadCtx key,+ phkdfCtx_hmacKeyLike = key+ } -data PhkdfSeed = PhkdfSeed- { phkdfSeed_seguid :: !ByteString- , phkdfSeed_seguidKey :: !HmacKey- , phkdfSeed_domainTag :: !ByteString- , phkdfSeed_secret :: !ByteString- } deriving (Eq)+-- | initialize an empty @phkdfStream@ context from a precomputed HMAC key. --- | A non-tweakable, complete password prehash protocol+phkdfCtx_initHashed :: HmacKeyHashed -> PhkdfCtx+phkdfCtx_initHashed = phkdfCtx_init . hmacKeyHashed_toKey -phkdfSimple :: PhkdfInputBlock -> PhkdfInputArgs -> Stream ByteString-phkdfSimple block args = echo+-- | initialize an empty @phkdfStream@ context from a buffer-prefixed HMAC key.++phkdfCtx_initPrefixed :: ByteString -> HmacKeyPrefixed -> PhkdfCtx+phkdfCtx_initPrefixed str key = PhkdfCtx+ { phkdfCtx_byteCount = hmacKeyPrefixed_byteCount key+ + fromIntegral (B.length str)+ , phkdfCtx_state = sha256_update (hmacKeyPrefixed_ipadCtx key) str+ , phkdfCtx_hmacKeyLike = hmacKeyLike_initPrefixed key+ }++-- | Retrieve the HmacKeyPlain that the phkdfCtx was originally+-- initialized with, if possible++phkdfCtx_hmacKeyPlain :: PhkdfCtx -> Maybe HmacKeyPlain+phkdfCtx_hmacKeyPlain = hmacKeyLike_toPlain . phkdfCtx_hmacKeyLike++-- | Retrieve the HmacKeyHashed that the phkdfCtx was originally+-- initialized with, if possible++phkdfCtx_hmacKeyHashed :: PhkdfCtx -> Maybe HmacKeyHashed+phkdfCtx_hmacKeyHashed = hmacKeyLike_toHashed . phkdfCtx_hmacKeyLike++-- | Retrieve the HmacKeyPrefixed that the phkdfCtx was originally+-- initialized with.++phkdfCtx_hmacKeyPrefixed :: PhkdfCtx -> HmacKeyPrefixed+phkdfCtx_hmacKeyPrefixed = hmacKeyLike_toPrefixed . phkdfCtx_hmacKeyLike++-- | Retrieve the HmacKey that the phkdfCtx was originally+-- initialized with, if possible.++phkdfCtx_hmacKey :: PhkdfCtx -> Maybe HmacKey+phkdfCtx_hmacKey = hmacKeyLike_toKey . phkdfCtx_hmacKeyLike++-- | initialize a new empty @phkdfStream@ context from the HMAC key+-- originally supplied to the context, discarding all arguments already added.++phkdfCtx_reset :: PhkdfCtx -> PhkdfCtx+phkdfCtx_reset = phkdfCtx_initLike . phkdfCtx_hmacKeyLike++-- | initialize a new empty HMAC context from the key originally supplied to+-- the PHKDF context, discarding all arguments already added.++phkdfCtx_toResetHmacCtx :: PhkdfCtx -> HmacCtx+phkdfCtx_toResetHmacCtx = hmacKeyLike_run . phkdfCtx_hmacKeyLike++-- FIXME? what should happen when the SHA256 counters overflow?+-- (As SHA-256 can handle 2.3e6 TB, this isn't a pressing issue.)++-- | append a single string onto the end of @phkdfStream@'s list of+-- arguments.++phkdfCtx_feedArg :: ByteString -> PhkdfCtx -> PhkdfCtx+phkdfCtx_feedArg str = phkdfCtx_unsafeFeed [len, str] where- -- Explicitly unpack everything for the unused variable warnings.- -- i.e. It's relatively easy to check that we've unpacked every- -- field, then we can rely on unused variable warnings to ensure- -- we have in fact made use of everything.- domainTag = phkdfInputBlock_domainTag block- seguid = phkdfInputBlock_seguid block- longTag = phkdfInputBlock_longTag block- tags = phkdfInputBlock_tags block- rounds = phkdfInputBlock_rounds block+ len = leftEncodeFromBytes (B.length str)+-- | append zero or more strings onto the end of @phkdfStream@'s list of+-- arguments. - username = phkdfInputArgs_username args- password = phkdfInputArgs_password args- credentials = phkdfInputArgs_credentials args+phkdfCtx_feedArgs :: Foldable f => f ByteString -> PhkdfCtx -> PhkdfCtx+phkdfCtx_feedArgs params ctx = foldl' (flip phkdfCtx_feedArg) ctx params - headerExtract = [ "phkdf-simple0 username", username ]+phkdfCtx_feedArgsBy :: Foldable f => (a -> ByteString) -> f a -> PhkdfCtx -> PhkdfCtx+phkdfCtx_feedArgsBy f params ctx0 = foldl' delta ctx0 params+ where delta ctx a = phkdfCtx_feedArg (f a) ctx - headerUsername = headerExtract ++ [ usernamePadding headerExtract domainTag domainTag ]- -- password field goes here+phkdfCtx_feedArgConcat :: Foldable f => f ByteString -> PhkdfCtx -> PhkdfCtx+phkdfCtx_feedArgConcat strs =+ phkdfCtx_unsafeFeed [len] >>>+ phkdfCtx_unsafeFeed strs+ where+ len = leftEncodeFromBytes (foldl' delta 0 strs)+ delta tot str = tot + B.length str - headerLongTag =- [ longTag- , B.concat- [ "password-hash-key-derivation-function phkdf-simple0\x00"- , leftEncodeFromBytes (B.length domainTag)- , bareEncode rounds- ]- ]+-- | close out a @phkdfStream@ context using the first mode of operation,+-- examining only the first output block and discarding the rest of the+-- stream. - secretKey =- phkdfCtx_init seguid &- phkdfCtx_addArgs headerUsername &- phkdfCtx_assertBufferPosition 32 &- phkdfCtx_addArg password &- phkdfCtx_addArgs headerLongTag &- -- FIXME: fusing addArg and passwordPadding can save ~ 8 KiB RAM- phkdfCtx_addArg (passwordPadding headerUsername headerLongTag longTag domainTag password) &- phkdfCtx_assertBufferPosition 32 &- phkdfCtx_addArgs credentials &- phkdfCtx_addArg (credentialsPadding credentials longTag domainTag) &- phkdfCtx_assertBufferPosition 29 &- phkdfCtx_addArgs tags &- phkdfCtx_addArg (bareEncode (V.length tags)) &- phkdfSlowCtx_extract- (cycleByteStringWithNull domainTag)- (word32 "go\x00\x00" + 2023) domainTag- "phkdf-simple0 compact" rounds &- phkdfSlowCtx_assertBufferPosition 32 &- phkdfSlowCtx_addArgs tags &- phkdfSlowCtx_finalize (cycleByteStringWithNull domainTag)+phkdfCtx_finalize+ :: (Int -> ByteString) -- ^ end-of-message padding, output length must be equal to the number provided+ -> Word32 -- ^ counter+ -> ByteString -- ^ tag+ -> PhkdfCtx+ -> ByteString+phkdfCtx_finalize genFillerPad counter tag ctx =+ phkdfCtx_toGen genFillerPad counter tag ctx &+ phkdfGen_head - -- Harden the tags vector against length-based timing side-channels- echoHeader = cycleByteStringWithNull "phkdf-simple0 expand echo" 30+-- | Turn a 'PhkdfCtx' into a incomplete call to @hmac@, with the option of+-- adding additional data to the end of the message that need not be+-- TupleHash encoded. - echo = phkdfCtx_init secretKey &- phkdfCtx_addArg echoHeader &- phkdfCtx_assertBufferPosition 32 &- phkdfCtx_addArgs tags &- phkdfCtx_finalizeStream (cycleByteStringWithNull domainTag) (word32 "OUT\x00") domainTag+phkdfCtx_toHmacCtx :: PhkdfCtx -> HmacCtx+phkdfCtx_toHmacCtx ctx =+ (phkdfCtx_toResetHmacCtx ctx) {+ hmacCtx_ipadCtx = phkdfCtx_state ctx+ } --- | A tweakable, complete prehash protocol. Note that this function is very--- intentionally implemented in such a way that the following idiom is--- efficient, and only performs the expensive key stretching phase once:------ @--- let mySeed = phkdfPass block args--- in [ mySeed tweak1, mySeed tweak2, mySeed tweak3 ]--- @------ However in the case that you want or need to persist or serialize the--- intermediate seed, then the plain-old-datatype 'PhkdfSeed' and its--- companion functions 'phkdfPass_seedInit' and 'phkdfPass_seedFinalize'--- are likely to be more appropriate.+-- | Turn a 'PhkdfCtx' into a 'HmacKeyPrefixed' by adding a null byte followed+-- by 0-63 bytes as needed to get to a SHA256 block boundary -phkdfPass :: PhkdfInputBlock -> PhkdfInputArgs -> PhkdfInputTweak -> Stream ByteString-phkdfPass block args = phkdfPass_seedInit block args & phkdfPass_seedFinalize+phkdfCtx_toHmacKeyPrefixed+ :: (Int -> ByteString) -- ^ block synchronization padding, ouput length must be equal to the number provided+ -> PhkdfCtx+ -> HmacKeyPrefixed+phkdfCtx_toHmacKeyPrefixed genFillerPad ctx =+ HmacKeyPrefixed+ { hmacKeyPrefixed_ipadCtx = ipadCtx'+ , hmacKeyPrefixed_opad = hmacKeyLike_opad (phkdfCtx_hmacKeyLike ctx)+ }+ where+ blockPadLen = phkdfCtx_blockPaddingLength ctx --- | This generates a seed, which encapsulates the expensive key-stretching component of 'phkdfPass' into a reusable, tweakable cryptographic value. This function is way slower than it's companion, 'phkdfPass_seedFinalize'. Broadly comparable to HKDF-Extract, though with key stretching built-in.+ blockPadding = genFillerPad blockPadLen -phkdfPass_seedInit :: PhkdfInputBlock -> PhkdfInputArgs -> PhkdfSeed-phkdfPass_seedInit block args =- PhkdfSeed {- phkdfSeed_seguid = seguid,- phkdfSeed_seguidKey = seguidKey,- phkdfSeed_domainTag = domainTag,- phkdfSeed_secret = secret- }+ ctx' = phkdfCtx_unsafeFeed ["\x00",blockPadding] ctx++ paddingIsValid = phkdfCtx_byteCount ctx' `mod` 64 == 0+ && B.length blockPadding == blockPadLen++ ipadCtx' = assert paddingIsValid $ phkdfCtx_state ctx'++-- | "improperly" close out a 'PhkdfCtx' as if it were a call to @hmac@ instead+-- of @phkdfStream@, though with a TupleHash message encoding.++phkdfCtx_finalizeHmac :: PhkdfCtx -> ByteString+phkdfCtx_finalizeHmac = hmacCtx_finalize_toByteString . phkdfCtx_toHmacCtx++-- | close out a @phkdfStream@ context with a given counter and tag++phkdfCtx_toStream :: (Int -> ByteString) -> Word32 -> ByteString -> PhkdfCtx -> Stream ByteString+phkdfCtx_toStream genFillerPad counter0 tag ctx =+ phkdfCtx_toGen genFillerPad counter0 tag ctx &+ phkdfGen_toStream++-- | How long would the end padding be if the PhkdfCtx was finalized?++phkdfCtx_endPaddingLength :: PhkdfCtx -> Int+phkdfCtx_endPaddingLength ctx =+ fromIntegral ((31 - phkdfCtx_byteCount ctx) .&. 63)++-- | How long would the block padding be if 'phkdfCtx_toHmacKeyPrefixed' is+-- called?++phkdfCtx_blockPaddingLength :: PhkdfCtx -> Int+phkdfCtx_blockPaddingLength ctx =+ fromIntegral ((63 - phkdfCtx_byteCount ctx) .&. 63)++-- actually I should probably offer a version of this function with permuted+-- arguments, as there is at least one potentially useful partial application+-- here, namely the block computations involved in processing the+-- end-of-message padding. This partial application requires that PhkdfCtx+-- and genFillerPad must come first.++phkdfCtx_toGen+ :: (Int -> ByteString) -- ^ end-of-message padding, output length must be equal to the number provided+ -> Word32 -- ^ counter+ -> ByteString -- ^ tag+ -> PhkdfCtx+ -> PhkdfGen+phkdfCtx_toGen genFillerPad counter0 tag ctx =+ PhkdfGen+ { phkdfGen_hmacKeyLike = phkdfCtx_hmacKeyLike ctx+ , phkdfGen_extTag = extendTag tag+ , phkdfGen_counter = counter0+ , phkdfGen_state = ""+ , phkdfGen_initCtx = Just context0+ } where- domainTag = phkdfInputBlock_domainTag block- seguid = phkdfInputBlock_seguid block- longTag = phkdfInputBlock_longTag block- seedTags = phkdfInputBlock_tags block- rounds = phkdfInputBlock_rounds block+ endPadLen = phkdfCtx_endPaddingLength ctx - username = phkdfInputArgs_username args- password = phkdfInputArgs_password args- credentials = phkdfInputArgs_credentials args+ endPadding = genFillerPad endPadLen - headerExtract = [ "phkdf-pass-v0 username", username ]+ ctx' = phkdfCtx_unsafeFeed ["\x00",endPadding] ctx - headerUsername = headerExtract ++ [ usernamePadding headerExtract domainTag domainTag ]+ endPaddingIsValid = phkdfCtx_byteCount ctx' `mod` 64 == 32+ && B.length endPadding == endPadLen - -- password field goes here+ context0 = assert endPaddingIsValid $ phkdfCtx_state ctx' - headerLongTag =- [ longTag- , B.concat- [ "password hash & key derivation function: phkdf-pass-v0"- , bareEncode rounds- ]- ]+phkdfGen :: ByteString -> ByteString -> Word32 -> ByteString -> PhkdfGen+phkdfGen = phkdfGen_init . hmacKey - seguidKey = hmacKey_init seguid+phkdfGen_init :: HmacKey -> ByteString -> Word32 -> ByteString -> PhkdfGen+phkdfGen_init = phkdfGen_initLike . hmacKeyLike_init - secret =- phkdfCtx_initFromHmacKey seguidKey &- phkdfCtx_addArgs headerUsername &- phkdfCtx_assertBufferPosition 32 &- phkdfCtx_addArg password &- -- FIXME: fusing addArg and longPadding can save ~ 8 KiB RAM- phkdfCtx_addArgs headerLongTag &- phkdfCtx_addArg (passwordPadding headerUsername headerLongTag longTag domainTag password) &- phkdfCtx_assertBufferPosition 32 &- phkdfCtx_addArgs credentials &- phkdfCtx_addArg (credentialsPadding credentials longTag domainTag) &- phkdfCtx_assertBufferPosition 29 &- phkdfCtx_addArgs seedTags &- phkdfCtx_addArg (bareEncode (V.length seedTags)) &- phkdfSlowCtx_extract- (cycleByteStringWithNull domainTag)- (word32 "go\x00\x00" + 2023) domainTag- "phkdf-pass-v0 compact" rounds &- phkdfSlowCtx_assertBufferPosition 32 &- phkdfSlowCtx_addArgs seedTags &- phkdfSlowCtx_finalize (cycleByteStringWithNull domainTag)+phkdfGen_initLike :: HmacKeyLike -> ByteString -> Word32 -> ByteString -> PhkdfGen+phkdfGen_initLike key initBytes = initGen+ where+ -- Round down to the previous buffer boundary+ n = B.length initBytes .&. complement 63+ (blocks, state0) = B.splitAt n initBytes+ ipad0 = sha256_update (hmacKeyLike_ipadCtx key) blocks --- | This consumes a seed and tweaks to produce the final output stream.--- This function is the output expansion phase of 'phkdfPass'. This function--- is way faster than it's companion 'phkdfPass_seedInit'. Broadly comparable to--- HKDF-Expand.+ initGen counter0 tag = PhkdfGen+ { phkdfGen_hmacKeyLike = key+ , phkdfGen_extTag = extendTag tag+ , phkdfGen_counter = counter0+ , phkdfGen_state = state0+ , phkdfGen_initCtx = Just ipad0+ } -phkdfPass_seedFinalize :: PhkdfSeed -> PhkdfInputTweak -> Stream ByteString-phkdfPass_seedFinalize seed tweak = echo+phkdfGen_initHashed :: HmacKeyHashed -> ByteString -> Word32 -> ByteString -> PhkdfGen+phkdfGen_initHashed = phkdfGen_initLike . hmacKeyLike_initHashed++phkdfGen_initPrefixed :: HmacKeyPrefixed -> ByteString -> Word32 -> ByteString -> PhkdfGen+phkdfGen_initPrefixed = phkdfGen_initLike . hmacKeyLike_initPrefixed++phkdfGen_hmacKeyPlain :: PhkdfGen -> Maybe HmacKeyPlain+phkdfGen_hmacKeyPlain = hmacKeyLike_toPlain . phkdfGen_hmacKeyLike++phkdfGen_hmacKeyHashed :: PhkdfGen -> Maybe HmacKeyHashed+phkdfGen_hmacKeyHashed = hmacKeyLike_toHashed . phkdfGen_hmacKeyLike++phkdfGen_hmacKeyPrefixed :: PhkdfGen -> HmacKeyPrefixed+phkdfGen_hmacKeyPrefixed = hmacKeyLike_toPrefixed . phkdfGen_hmacKeyLike++phkdfGen_hmacKey :: PhkdfGen -> Maybe HmacKey+phkdfGen_hmacKey = hmacKeyLike_toKey . phkdfGen_hmacKeyLike++phkdfGen_peek :: PhkdfGen -> Maybe ByteString+phkdfGen_peek gen =+ case phkdfGen_initCtx gen of+ Nothing -> Just $ phkdfGen_state gen+ Just _ -> Nothing++phkdfGen_toHmacCtx :: PhkdfGen -> HmacCtx+phkdfGen_toHmacCtx gen =+ (hmacKeyLike_run (phkdfGen_hmacKeyLike gen)) {+ hmacCtx_ipadCtx = sha256_update ipad (phkdfGen_state gen)+ } where- seguidKey = phkdfSeed_seguidKey seed- domainTag = phkdfSeed_domainTag seed- secret = phkdfSeed_secret seed+ ipad =+ case phkdfGen_initCtx gen of+ Nothing -> hmacCtx_ipadCtx . hmacKeyLike_run $ phkdfGen_hmacKeyLike gen+ Just x -> x - role = phkdfInputTweak_role tweak- echoTag = phkdfInputTweak_echoTag tweak+phkdfGen_head :: PhkdfGen -> ByteString+phkdfGen_head gen =+ if B.length (phkdfGen_extTag gen) <= 19 then+ phkdfGen_toHmacCtx gen &+ hmacCtx_feeds [ bytestring32 (phkdfGen_counter gen)+ , phkdfGen_extTag gen+ ] &+ hmacCtx_finalize_toByteString+ else+ phkdfGen_toHmacCtx gen &+ hmacCtx_feeds [ bytestring32 (phkdfGen_counter gen)+ , B.init (phkdfGen_extTag gen)+ ] &+ hmacCtx_finalizeBits_toByteString (B.singleton (B.last (phkdfGen_extTag gen))) 7 - headerCombine = B.concat ["phkdf-pass-v0 combine", secret]- secretKey =- phkdfCtx_initFromHmacKey seguidKey &- phkdfCtx_addArg headerCombine &- phkdfCtx_addArgs role &- phkdfCtx_finalize (cycleByteStringWithNull domainTag) (word32 "KEY\x00") domainTag - headerEcho = cycleByteString (domainTag <> "\x00phkdf-pass-v0 echo\x00") 32+phkdfGen_read :: PhkdfGen -> (ByteString, PhkdfGen)+phkdfGen_read gen = (state', gen')+ where+ state' = phkdfGen_head gen - echo = hmacKey_init secretKey &- phkdfGen_initFromHmacKey headerEcho (word32 "OUT\x00") echoTag &- phkdfGen_finalizeStream+ gen' = PhkdfGen+ { phkdfGen_hmacKeyLike = phkdfGen_hmacKeyLike gen+ , phkdfGen_initCtx = Nothing+ , phkdfGen_state = state'+ , phkdfGen_counter = phkdfGen_counter gen + 1+ , phkdfGen_extTag = phkdfGen_extTag gen+ }++phkdfGen_toStream :: PhkdfGen -> Stream ByteString+phkdfGen_toStream = Stream.unfold phkdfGen_read
+ lib/Crypto/PHKDF/Assert.hs view
@@ -0,0 +1,36 @@+-------------------------------------------------------------------------------+-- |+-- Module: Crypto.PHKDF.Assert+-- Copyright: (c) 2024 Auth Global+-- License: Apache2+--+-------------------------------------------------------------------------------++module Crypto.PHKDF.Assert where++import Data.Bits+import Data.Word+import Crypto.PHKDF.Subtle++phkdfCtx_assertBufferPosition' :: Word64 -> PhkdfCtx -> PhkdfCtx+phkdfCtx_assertBufferPosition' n ctx+ | len .&. 63 /= n .&. 63+ = error ("phkdf buffer position mismatch: " ++ show len ++ " /= " ++ show n ++ " (mod 64)")+ | otherwise = ctx+ where len = phkdfCtx_byteCount ctx++-- TODO: set up a cabal flag and CPP to select between assertions enabled/not++{--}++phkdfCtx_assertBufferPosition :: Word64 -> PhkdfCtx -> PhkdfCtx+phkdfCtx_assertBufferPosition = phkdfCtx_assertBufferPosition'++--}++{--++phkdfCtx_assertBufferPosition :: Word64 -> PhkdfCtx -> PhkdfCtx+phkdfCtx_assertBufferPosition _ = id++--}
lib/Crypto/PHKDF/HMAC.hs view
@@ -1,66 +1,14 @@+{-# LANGUAGE ViewPatterns, LambdaCase, BangPatterns #-}+ {- | -An alternate implementation of HMAC in terms of cryptohash-sha256, because-the HMAC implementation provided there doesn't support precomputed keys or-streaming inputs. TODO: prepare a patch for cryptohash-sha256.+An implementation of HMAC-SHA256 that supports precomputed keys, streaming inputs,+backtracking, and bitstring message inputs. -} - module Crypto.PHKDF.HMAC- ( HmacCtx- , HmacKey- , hmacKey_init- , hmacKey_run- , hmacCtx_init- , hmacCtx_initFromHmacKey- , hmacCtx_update- , hmacCtx_updates- , hmacCtx_finalize+ ( module Crypto.Sha256.Hmac ) where -import qualified Crypto.Hash.SHA256 as SHA256-import Data.Bits(xor)-import Data.ByteString (ByteString)-import qualified Data.ByteString as B--import Crypto.PHKDF.HMAC.Subtle---- | Precompute an HMAC key for some literal HMAC key.--hmacKey_init :: ByteString -> HmacKey-hmacKey_init = HmacKey . hmacCtx_init---- | Initialize a new empty HMAC context from a literal HMAC key.--hmacCtx_init :: ByteString -> HmacCtx-hmacCtx_init key =- HmacCtx { hmacCtx_ipad = tweak 0x36, hmacCtx_opad = tweak 0x5c }- where- tweak c = SHA256.update SHA256.init $ B.map (xor c) k2- k1 = if B.length key > 64 then SHA256.hash key else key- k2 = B.append k1 (B.replicate (64 - B.length k1) 0)---- | Initialize a new empty HMAC context from a precomputed HMAC key.--hmacCtx_initFromHmacKey :: HmacKey -> HmacCtx-hmacCtx_initFromHmacKey = hmacKey_run---- | Append a bytestring onto the end of the message argument to HMAC.--hmacCtx_update :: ByteString -> HmacCtx -> HmacCtx-hmacCtx_update b (HmacCtx ic oc) = HmacCtx (SHA256.update ic b) oc---- | Append zero or more bytestrings onto the end of the message argument to--- HMAC.--hmacCtx_updates :: [ByteString] -> HmacCtx -> HmacCtx-hmacCtx_updates bs (HmacCtx ic oc) = HmacCtx (SHA256.updates ic bs) oc---- | Finish computing the final 32-byte hash for an HMAC context.--hmacCtx_finalize :: HmacCtx -> ByteString-hmacCtx_finalize (HmacCtx ic oc) = outer- where- inner = SHA256.finalize ic- outer = SHA256.finalize (SHA256.update oc inner)+import Crypto.Sha256.Hmac
lib/Crypto/PHKDF/HMAC/Subtle.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE ViewPatterns, LambdaCase #-} {- | "Internal" data structures representing precomputed HMAC keys and partial HMAC@@ -6,44 +7,7 @@ -} module Crypto.PHKDF.HMAC.Subtle- ( HmacCtx(..)- , HmacKey(..)- , hmacKey_ipad- , hmacKey_opad+ ( module Crypto.Sha256.Hmac.Subtle ) where -import qualified Crypto.Hash.SHA256 as SHA256---- | Fixed-size context representing the state of a partial HMAC computation--- with a complete HMAC key and a partial message parameter.--data HmacCtx = HmacCtx- { hmacCtx_ipad :: !SHA256.Ctx- , hmacCtx_opad :: !SHA256.Ctx- } deriving (Eq)---- | A precomputed HMAC key. Computing an HMAC key costs two SHA256 blocks.------ No additional blocks are incurred for keys that are 64 bytes or less in--- length. Keys that are longer than 64 bytes long must be first hashed--- with SHA256 before the key can be derived, incurring extra blocks.------ It is not uncommon that implementations of PBKDF2, HKDF, etc unnecessarily--- redo this computation even though a single HMAC key is used repeatedly.------ TODO: FIXME: this data structure is way larger than it should be. We can--- pack this into a single 64-byte bytestring, but right now it's 208 bytes--- of data plus extra overhead.------ On the other hand, this approach may actually be more efficient for the--- core PHKDF algorithm as currently implemented. Reducing the size of this--- data structure while maintaining tight code involves some additional work--- on cryptohash-sha256--newtype HmacKey = HmacKey { hmacKey_run :: HmacCtx } deriving (Eq)--hmacKey_ipad :: HmacKey -> SHA256.Ctx-hmacKey_ipad (HmacKey ctx) = hmacCtx_ipad ctx--hmacKey_opad :: HmacKey -> SHA256.Ctx-hmacKey_opad (HmacKey ctx) = hmacCtx_opad ctx+import Crypto.Sha256.Hmac.Subtle
lib/Crypto/PHKDF/Primitives.hs view
@@ -1,10 +1,16 @@ {-# LANGUAGE OverloadedStrings, BangPatterns, ScopedTypeVariables #-}+-------------------------------------------------------------------------------+-- |+-- Module: Crypto.PHKDF.Primitives+-- Copyright: (c) 2024 Auth Global+-- License: Apache2+--+------------------------------------------------------------------------------- {- | -This module provides an interface to the following function. This simplified-presentation elides the fact that the variable-length padding between the-@args@ parameter and the initial counter depends on the tag itself.+This module provides an interface to Version 1 of PHKDF, especially the+following function: @ phkdfStream :: BitString -> [BitString] -> Word32 -> BitString -> Stream ByteString@@ -201,35 +207,53 @@ module Crypto.PHKDF.Primitives ( HmacKey()- , hmacKey_init+ , hmacKey , PhkdfCtx()+ , phkdfCtx , phkdfCtx_init- , phkdfCtx_initFromHmacKey+ , phkdfCtx_initHashed+ , phkdfCtx_initPrefixed+ , phkdfCtx_initLike+ , phkdfCtx_hmacKeyPlain+ , phkdfCtx_hmacKeyHashed+ , phkdfCtx_hmacKeyPrefixed , phkdfCtx_hmacKey- , phkdfCtx_resetCtx+ , phkdfCtx_hmacKeyLike+ , phkdfCtx_toResetHmacCtx , phkdfCtx_reset- , phkdfCtx_addArg- , phkdfCtx_addArgs- , phkdfCtx_addArgsBy+ , phkdfCtx_feedArg+ , phkdfCtx_feedArgs+ , phkdfCtx_feedArgsBy+ , phkdfCtx_feedArgConcat , phkdfCtx_finalize , phkdfCtx_finalizeHmac- , phkdfCtx_finalizeHmacCtx- , phkdfCtx_finalizeStream- , phkdfCtx_finalizeGen+ , phkdfCtx_toHmacCtx+ , phkdfCtx_toStream+ , phkdfCtx_toGen , PhkdfSlowCtx() , phkdfSlowCtx_extract- , phkdfSlowCtx_addArg- , phkdfSlowCtx_addArgs+ , phkdfSlowCtx_feedArg+ , phkdfSlowCtx_feedArgs , phkdfSlowCtx_finalize- , phkdfSlowCtx_finalizeStream+ , phkdfSlowCtx_toStream , PhkdfGen()- , phkdfGen_initFromHmacKey+ , phkdfGen+ , phkdfGen_init+ , phkdfGen_initHashed+ , phkdfGen_initPrefixed+ , phkdfGen_initLike+ , phkdfGen_hmacKeyPlain+ , phkdfGen_hmacKeyHashed+ , phkdfGen_hmacKeyPrefixed+ , phkdfGen_hmacKey+ , phkdfGen_hmacKeyLike , phkdfGen_read , phkdfGen_peek- , phkdfGen_finalizeStream+ , phkdfGen_toStream ) where -import Data.Bits((.&.))+import Control.Arrow((>>>))+import Data.Bits((.&.), complement) import Data.ByteString (ByteString) import qualified Data.ByteString as B import Data.Function((&))@@ -240,61 +264,97 @@ import qualified Data.Stream as Stream import Network.ByteOrder (bytestring32) -import qualified Crypto.Hash.SHA256 as SHA256+import Crypto.Sha256 as Sha256 import Crypto.PHKDF.HMAC import Crypto.PHKDF.HMAC.Subtle import Crypto.PHKDF.Primitives.Subtle-import Crypto.Encoding.PHKDF+import Crypto.Encoding.PHKDF.V1 import Crypto.Encoding.SHA3.TupleHash import Control.Exception(assert) -- | initialize an empty @phkdfStream@ context from a plaintext HMAC key. -phkdfCtx_init :: ByteString -> PhkdfCtx-phkdfCtx_init = phkdfCtx_initFromHmacKey . hmacKey_init+phkdfCtx :: ByteString -> PhkdfCtx+phkdfCtx = phkdfCtx_init . hmacKey --- | initialize an empty @phkdfStream@ context from a precomputed HMAC key.+-- | initialize an empty @phkdfStream@ context from a plaintext or precomputed HMAC key. -phkdfCtx_initFromHmacKey :: HmacKey -> PhkdfCtx-phkdfCtx_initFromHmacKey key =+phkdfCtx_init :: HmacKey -> PhkdfCtx+phkdfCtx_init = phkdfCtx_initLike . hmacKeyLike_init++-- | initialize an empty @phkdfStream@ context from a plaintext, precomputed, or buffer-prefixed HMAC key.++phkdfCtx_initLike :: HmacKeyLike -> PhkdfCtx+phkdfCtx_initLike key = PhkdfCtx {- phkdfCtx_byteLen = 0,- phkdfCtx_state = hmacKey_ipad key,- phkdfCtx_hmacKey = key+ phkdfCtx_state = hmacKeyLike_ipadCtx key,+ phkdfCtx_hmacKeyLike = key } +-- | initialize an empty @phkdfStream@ context from a precomputed HMAC key.++phkdfCtx_initHashed :: HmacKeyHashed -> PhkdfCtx+phkdfCtx_initHashed = phkdfCtx_init . hmacKeyHashed_toKey++-- | initialize an empty @phkdfStream@ context from a buffer-prefixed HMAC key.++phkdfCtx_initPrefixed :: ByteString -> HmacKeyPrefixed -> PhkdfCtx+phkdfCtx_initPrefixed str key = PhkdfCtx+ { phkdfCtx_state = sha256_update (hmacKeyPrefixed_ipadCtx key) str+ , phkdfCtx_hmacKeyLike = hmacKeyLike_initPrefixed key+ }++phkdfCtx_hmacKeyPlain :: PhkdfCtx -> Maybe HmacKeyPlain+phkdfCtx_hmacKeyPlain = hmacKeyLike_toPlain . phkdfCtx_hmacKeyLike++phkdfCtx_hmacKeyHashed :: PhkdfCtx -> Maybe HmacKeyHashed+phkdfCtx_hmacKeyHashed = hmacKeyLike_toHashed . phkdfCtx_hmacKeyLike++phkdfCtx_hmacKeyPrefixed :: PhkdfCtx -> HmacKeyPrefixed+phkdfCtx_hmacKeyPrefixed = hmacKeyLike_toPrefixed . phkdfCtx_hmacKeyLike++phkdfCtx_hmacKey :: PhkdfCtx -> Maybe HmacKey+phkdfCtx_hmacKey = hmacKeyLike_toKey . phkdfCtx_hmacKeyLike+ -- | initialize a new empty @phkdfStream@ context from the HMAC key -- originally supplied to the context, discarding all arguments already added. phkdfCtx_reset :: PhkdfCtx -> PhkdfCtx-phkdfCtx_reset = phkdfCtx_initFromHmacKey . phkdfCtx_hmacKey-+phkdfCtx_reset = phkdfCtx_initLike . phkdfCtx_hmacKeyLike -- | initialize a new empty HMAC context from the key originally supplied to -- the PHKDF context, discarding all arguments already added. -phkdfCtx_resetCtx :: PhkdfCtx -> HmacCtx-phkdfCtx_resetCtx = hmacKey_run . phkdfCtx_hmacKey+phkdfCtx_toResetHmacCtx :: PhkdfCtx -> HmacCtx+phkdfCtx_toResetHmacCtx = hmacKeyLike_run . phkdfCtx_hmacKeyLike -- FIXME? what should happen when the SHA256 counters overflow? -- | append a single string onto the end of @phkdfStream@'s list of -- arguments. -phkdfCtx_addArg :: ByteString -> PhkdfCtx -> PhkdfCtx-phkdfCtx_addArg b ctx = phkdfCtx_unsafeFeed [ leftEncodeFromBytes (B.length b), b ] ctx-+phkdfCtx_feedArg :: ByteString -> PhkdfCtx -> PhkdfCtx+phkdfCtx_feedArg str = phkdfCtx_unsafeFeed [len, str]+ where+ len = leftEncodeFromBytes (B.length str) -- | append zero or more strings onto the end of @phkdfStream@'s list of -- arguments. -phkdfCtx_addArgs :: Foldable f => f ByteString -> PhkdfCtx -> PhkdfCtx-phkdfCtx_addArgs params ctx = foldl' (flip phkdfCtx_addArg) ctx params+phkdfCtx_feedArgs :: Foldable f => f ByteString -> PhkdfCtx -> PhkdfCtx+phkdfCtx_feedArgs params ctx = foldl' (flip phkdfCtx_feedArg) ctx params -phkdfCtx_addArgsBy :: Foldable f => (a -> ByteString) -> f a -> PhkdfCtx -> PhkdfCtx-phkdfCtx_addArgsBy f params ctx0 = foldl' delta ctx0 params- where delta ctx a = phkdfCtx_addArg (f a) ctx+phkdfCtx_feedArgsBy :: Foldable f => (a -> ByteString) -> f a -> PhkdfCtx -> PhkdfCtx+phkdfCtx_feedArgsBy f params ctx0 = foldl' delta ctx0 params+ where delta ctx a = phkdfCtx_feedArg (f a) ctx +phkdfCtx_feedArgConcat :: Foldable f => f ByteString -> PhkdfCtx -> PhkdfCtx+phkdfCtx_feedArgConcat strs =+ phkdfCtx_unsafeFeed [len] >>>+ phkdfCtx_unsafeFeed strs+ where+ len = leftEncodeFromBytes (foldl' delta 0 strs)+ delta tot str = tot + B.length str -- | close out a @phkdfStream@ context using the first mode of operation, -- examining only the first output block and discarding the rest of the@@ -302,7 +362,7 @@ phkdfCtx_finalize :: (Int -> ByteString) -> Word32 -> ByteString -> PhkdfCtx -> ByteString phkdfCtx_finalize genFillerPad counter tag ctx =- phkdfCtx_finalizeGen genFillerPad counter tag ctx &+ phkdfCtx_toGen genFillerPad counter tag ctx & phkdfGen_read & fst @@ -310,29 +370,29 @@ -- adding additional data to the end of the message that need not be -- TupleHash encoded. -phkdfCtx_finalizeHmacCtx :: PhkdfCtx -> HmacCtx-phkdfCtx_finalizeHmacCtx ctx =- (phkdfCtx_resetCtx ctx) {- hmacCtx_ipad = phkdfCtx_state ctx+phkdfCtx_toHmacCtx :: PhkdfCtx -> HmacCtx+phkdfCtx_toHmacCtx ctx =+ (phkdfCtx_toResetHmacCtx ctx) {+ hmacCtx_ipadCtx = phkdfCtx_state ctx } -- | "improperly" close out a 'PhkdfCtx' as if it were a call to @hmac@ instead -- of @phkdfStream@, though with a TupleHash message encoding. phkdfCtx_finalizeHmac :: PhkdfCtx -> ByteString-phkdfCtx_finalizeHmac = hmacCtx_finalize . phkdfCtx_finalizeHmacCtx+phkdfCtx_finalizeHmac = hmacCtx_finalize_toByteString . phkdfCtx_toHmacCtx -- | close out a @phkdfStream@ context with a given counter and tag -phkdfCtx_finalizeStream :: (Int -> ByteString) -> Word32 -> ByteString -> PhkdfCtx -> Stream ByteString-phkdfCtx_finalizeStream genFillerPad counter0 tag ctx =- phkdfCtx_finalizeGen genFillerPad counter0 tag ctx &- phkdfGen_finalizeStream+phkdfCtx_toStream :: (Int -> ByteString) -> Word32 -> ByteString -> PhkdfCtx -> Stream ByteString+phkdfCtx_toStream genFillerPad counter0 tag ctx =+ phkdfCtx_toGen genFillerPad counter0 tag ctx &+ phkdfGen_toStream -phkdfCtx_finalizeGen :: (Int -> ByteString) -> Word32 -> ByteString -> PhkdfCtx -> PhkdfGen-phkdfCtx_finalizeGen genFillerPad counter0 tag ctx =+phkdfCtx_toGen :: (Int -> ByteString) -> Word32 -> ByteString -> PhkdfCtx -> PhkdfGen+phkdfCtx_toGen genFillerPad counter0 tag ctx = PhkdfGen- { phkdfGen_hmacKey = phkdfCtx_hmacKey ctx+ { phkdfGen_hmacKeyLike = phkdfCtx_hmacKeyLike ctx , phkdfGen_extTag = extendTag tag , phkdfGen_counter = counter0 , phkdfGen_state = ""@@ -351,54 +411,85 @@ context0 = assert endPaddingIsValid $ phkdfCtx_state ctx' -phkdfGen_initFromHmacKey :: ByteString -> Word32 -> ByteString -> HmacKey -> PhkdfGen-phkdfGen_initFromHmacKey state0 counter0 tag hmacKey = PhkdfGen- { phkdfGen_hmacKey = hmacKey- , phkdfGen_extTag = extendTag tag- , phkdfGen_counter = counter0- , phkdfGen_state = state0- , phkdfGen_initCtx = Just $ hmacKey_ipad hmacKey- }+phkdfGen :: ByteString -> ByteString -> Word32 -> ByteString -> PhkdfGen+phkdfGen = phkdfGen_init . hmacKey +phkdfGen_init :: HmacKey -> ByteString -> Word32 -> ByteString -> PhkdfGen+phkdfGen_init = phkdfGen_initLike . hmacKeyLike_init++phkdfGen_initLike :: HmacKeyLike -> ByteString -> Word32 -> ByteString -> PhkdfGen+phkdfGen_initLike key initBytes = initGen+ where+ -- Round down to the previous buffer boundary+ n = B.length initBytes .&. complement 63+ (blocks, state0) = B.splitAt n initBytes+ ipad0 = sha256_update (hmacKeyLike_ipadCtx key) blocks++ initGen counter0 tag = PhkdfGen+ { phkdfGen_hmacKeyLike = key+ , phkdfGen_extTag = extendTag tag+ , phkdfGen_counter = counter0+ , phkdfGen_state = state0+ , phkdfGen_initCtx = Just ipad0+ }++phkdfGen_initHashed :: HmacKeyHashed -> ByteString -> Word32 -> ByteString -> PhkdfGen+phkdfGen_initHashed = phkdfGen_initLike . hmacKeyLike_initHashed++phkdfGen_initPrefixed :: HmacKeyPrefixed -> ByteString -> Word32 -> ByteString -> PhkdfGen+phkdfGen_initPrefixed = phkdfGen_initLike . hmacKeyLike_initPrefixed++phkdfGen_hmacKeyPlain :: PhkdfGen -> Maybe HmacKeyPlain+phkdfGen_hmacKeyPlain = hmacKeyLike_toPlain . phkdfGen_hmacKeyLike++phkdfGen_hmacKeyHashed :: PhkdfGen -> Maybe HmacKeyHashed+phkdfGen_hmacKeyHashed = hmacKeyLike_toHashed . phkdfGen_hmacKeyLike++phkdfGen_hmacKeyPrefixed :: PhkdfGen -> HmacKeyPrefixed+phkdfGen_hmacKeyPrefixed = hmacKeyLike_toPrefixed . phkdfGen_hmacKeyLike++phkdfGen_hmacKey :: PhkdfGen -> Maybe HmacKey+phkdfGen_hmacKey = hmacKeyLike_toKey . phkdfGen_hmacKeyLike+ phkdfGen_peek :: PhkdfGen -> Maybe ByteString phkdfGen_peek gen = case phkdfGen_initCtx gen of Nothing -> Just $ phkdfGen_state gen Just _ -> Nothing -phkdfGen_finalizeHmacCtx :: PhkdfGen -> HmacCtx-phkdfGen_finalizeHmacCtx gen =- (hmacKey_run (phkdfGen_hmacKey gen)) {- hmacCtx_ipad = SHA256.update ipad (phkdfGen_state gen)+phkdfGen_toHmacCtx :: PhkdfGen -> HmacCtx+phkdfGen_toHmacCtx gen =+ (hmacKeyLike_run (phkdfGen_hmacKeyLike gen)) {+ hmacCtx_ipadCtx = sha256_update ipad (phkdfGen_state gen) } where ipad = case phkdfGen_initCtx gen of- Nothing -> hmacCtx_ipad . hmacKey_run $ phkdfGen_hmacKey gen+ Nothing -> hmacCtx_ipadCtx . hmacKeyLike_run $ phkdfGen_hmacKeyLike gen Just x -> x phkdfGen_read :: PhkdfGen -> (ByteString, PhkdfGen) phkdfGen_read gen = (state', gen') where state' =- phkdfGen_finalizeHmacCtx gen &- hmacCtx_updates [ bytestring32 (phkdfGen_counter gen)- , phkdfGen_extTag gen- ] &- hmacCtx_finalize+ phkdfGen_toHmacCtx gen &+ hmacCtx_feeds [ bytestring32 (phkdfGen_counter gen)+ , phkdfGen_extTag gen+ ] &+ hmacCtx_finalize_toByteString - hmacKey = phkdfGen_hmacKey gen+ key = phkdfGen_hmacKeyLike gen gen' = PhkdfGen- { phkdfGen_hmacKey = hmacKey+ { phkdfGen_hmacKeyLike = key , phkdfGen_initCtx = Nothing , phkdfGen_state = state' , phkdfGen_counter = phkdfGen_counter gen + 1 , phkdfGen_extTag = phkdfGen_extTag gen } -phkdfGen_finalizeStream :: PhkdfGen -> Stream ByteString-phkdfGen_finalizeStream = Stream.unfold phkdfGen_read+phkdfGen_toStream :: PhkdfGen -> Stream ByteString+phkdfGen_toStream = Stream.unfold phkdfGen_read -- | close out a @phkdfStream@ context with a call to @phkdfSlowExtract@, -- providing the counter, tag, @fnName@, and number of rounds to compute.@@ -409,7 +500,7 @@ phkdfSlowCtx_extract :: (Int -> ByteString) -> Word32 -> ByteString -> ByteString -> Word32 -> PhkdfCtx -> PhkdfSlowCtx phkdfSlowCtx_extract genFillerPad counter tag fnName rounds ctx0 = out where- (Cons block0 innerStream) = phkdfCtx_finalizeStream genFillerPad counter tag ctx0+ (Cons block0 innerStream) = phkdfCtx_toStream genFillerPad counter tag ctx0 approxByteLen = ((fromIntegral rounds :: Int64) + 1) * 64 + 32 encodedLengthByteLen = lengthOfLeftEncodeFromBytes approxByteLen@@ -455,25 +546,25 @@ -- | Add a tweak to a call to @phkdfSlowExtract@. -phkdfSlowCtx_addArg :: ByteString -> PhkdfSlowCtx -> PhkdfSlowCtx-phkdfSlowCtx_addArg = phkdfSlowCtx_lift . phkdfCtx_addArg+phkdfSlowCtx_feedArg :: ByteString -> PhkdfSlowCtx -> PhkdfSlowCtx+phkdfSlowCtx_feedArg = phkdfSlowCtx_lift . phkdfCtx_feedArg -- | Add zero or more tweaks to a call to @phkdfSlowExtract@. -phkdfSlowCtx_addArgs :: Foldable f => f ByteString -> PhkdfSlowCtx -> PhkdfSlowCtx-phkdfSlowCtx_addArgs = phkdfSlowCtx_lift . phkdfCtx_addArgs+phkdfSlowCtx_feedArgs :: Foldable f => f ByteString -> PhkdfSlowCtx -> PhkdfSlowCtx+phkdfSlowCtx_feedArgs = phkdfSlowCtx_lift . phkdfCtx_feedArgs -- | finalize a call to @phkdfSlowExtract@, discarding all but the first block -- of the output stream phkdfSlowCtx_finalize :: (Int -> ByteString) -> PhkdfSlowCtx -> ByteString-phkdfSlowCtx_finalize genFillerPad = Stream.head . phkdfSlowCtx_finalizeStream genFillerPad+phkdfSlowCtx_finalize genFillerPad = Stream.head . phkdfSlowCtx_toStream genFillerPad -- | finalize a call to @phkdfSlowExtract@ -phkdfSlowCtx_finalizeStream :: (Int -> ByteString) -> PhkdfSlowCtx -> Stream ByteString-phkdfSlowCtx_finalizeStream genFillerPad ctx =- phkdfCtx_finalizeStream genFillerPad+phkdfSlowCtx_toStream :: (Int -> ByteString) -> PhkdfSlowCtx -> Stream ByteString+phkdfSlowCtx_toStream genFillerPad ctx =+ phkdfCtx_toStream genFillerPad (phkdfSlowCtx_counter ctx) (phkdfSlowCtx_tag ctx) (phkdfSlowCtx_phkdfCtx ctx)
lib/Crypto/PHKDF/Primitives/Assert.hs view
@@ -1,3 +1,10 @@+-------------------------------------------------------------------------------+-- |+-- Module: Crypto.PHKDF.Primitives.Assert+-- Copyright: (c) 2024 Auth Global+-- License: Apache2+--+------------------------------------------------------------------------------- module Crypto.PHKDF.Primitives.Assert where import Data.Bits
lib/Crypto/PHKDF/Primitives/Subtle.hs view
@@ -1,5 +1,13 @@+-------------------------------------------------------------------------------+-- |+-- Module: Crypto.PHKDF.Primitives.Subtle+-- Copyright: (c) 2024 Auth Global+-- License: Apache2+--+------------------------------------------------------------------------------- module Crypto.PHKDF.Primitives.Subtle ( PhkdfCtx(..)+ , phkdfCtx_byteLen , phkdfCtx_unsafeFeed , PhkdfSlowCtx(..) , phkdfSlowCtx_lift@@ -7,8 +15,8 @@ ) where import Prelude hiding (null)-import qualified Crypto.Hash.SHA256 as SHA256-import Crypto.PHKDF.HMAC (HmacKey)+import Crypto.Sha256 as Sha256+import Crypto.PHKDF.HMAC (HmacKeyLike) import Data.ByteString (ByteString) import qualified Data.ByteString as B import Data.Foldable(foldl', null)@@ -28,27 +36,22 @@ -- modulo 64, this doesn't matter. However we should probably export the SHA256 counter itself data PhkdfCtx = PhkdfCtx- { phkdfCtx_byteLen :: !Word64- , phkdfCtx_state :: !SHA256.Ctx- , phkdfCtx_hmacKey :: !HmacKey+ { phkdfCtx_state :: !Sha256Ctx+ , phkdfCtx_hmacKeyLike :: !HmacKeyLike } -data P = P !Word64 !SHA256.Ctx+phkdfCtx_byteLen :: PhkdfCtx -> Word64+phkdfCtx_byteLen = sha256_byteCount . phkdfCtx_state +data P = P !Word64 !Sha256Ctx+ phkdfCtx_unsafeFeed :: Foldable f => f ByteString -> PhkdfCtx -> PhkdfCtx phkdfCtx_unsafeFeed strs ctx0 = if null strs then ctx0 else ctx0 {- phkdfCtx_byteLen = byteLen',- phkdfCtx_state = state'+ phkdfCtx_state = sha256_feeds strs (phkdfCtx_state ctx0) }- where- delta (P len ctx) str = P (len + (fromIntegral (B.length str))) (SHA256.update ctx str) - p0 = P (phkdfCtx_byteLen ctx0) (phkdfCtx_state ctx0)-- P byteLen' state' = foldl' delta p0 strs- data PhkdfSlowCtx = PhkdfSlowCtx { phkdfSlowCtx_phkdfCtx :: !PhkdfCtx , phkdfSlowCtx_counter :: !Word32@@ -61,9 +64,9 @@ } data PhkdfGen = PhkdfGen- { phkdfGen_hmacKey :: !HmacKey+ { phkdfGen_hmacKeyLike :: !HmacKeyLike , phkdfGen_extTag :: !ByteString , phkdfGen_counter :: !Word32 , phkdfGen_state :: !ByteString- , phkdfGen_initCtx :: !(Maybe SHA256.Ctx)+ , phkdfGen_initCtx :: !(Maybe Sha256Ctx) }
+ lib/Crypto/PHKDF/Subtle.hs view
@@ -0,0 +1,55 @@+-------------------------------------------------------------------------------+-- |+-- Module: Crypto.PHKDF.Subtle+-- Copyright: (c) 2024 Auth Global+-- License: Apache2+--+-------------------------------------------------------------------------------++module Crypto.PHKDF.Subtle+ ( PhkdfCtx(..)+ , phkdfCtx_unsafeFeed+ , PhkdfGen(..)+ ) where++import Prelude hiding (null)+import Crypto.Sha256 as Sha256+import Crypto.PHKDF.HMAC (HmacKeyLike)+import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import Data.Foldable(foldl', null)+import Data.Word++-- I should be using the counter inside the sha256 ctx.+-- While this project is rapidly approaching maturity, it's still somewhat+-- proof of concept. See the new SHA256 bindings WIP.++data PhkdfCtx = PhkdfCtx+ { phkdfCtx_byteCount :: !Word64+ , phkdfCtx_state :: !Sha256Ctx+ , phkdfCtx_hmacKeyLike :: !HmacKeyLike+ }++data P = P !Word64 !Sha256Ctx++phkdfCtx_unsafeFeed :: Foldable f => f ByteString -> PhkdfCtx -> PhkdfCtx+phkdfCtx_unsafeFeed strs ctx0 =+ if null strs then ctx0+ else ctx0 {+ phkdfCtx_byteCount = byteCount',+ phkdfCtx_state = state'+ }+ where+ delta (P len ctx) str = P (len + (fromIntegral (B.length str))) (sha256_update ctx str)++ p0 = P (phkdfCtx_byteCount ctx0) (phkdfCtx_state ctx0)++ P byteCount' state' = foldl' delta p0 strs++data PhkdfGen = PhkdfGen+ { phkdfGen_hmacKeyLike :: !HmacKeyLike+ , phkdfGen_extTag :: !ByteString+ , phkdfGen_counter :: !Word32+ , phkdfGen_state :: !ByteString+ , phkdfGen_initCtx :: !(Maybe Sha256Ctx)+ }
+ lib/Crypto/PHKDF/V1/Cookbook.hs view
@@ -0,0 +1,395 @@+{-# Language OverloadedStrings #-}+-------------------------------------------------------------------------------+-- |+-- Module: Crypto.PHKDF.V1.Cookbook+-- Copyright: (c) 2024 Auth Global+-- License: Apache2+--+-------------------------------------------------------------------------------++-- | The following module is a worked example of how one might apply PHKDF.+-- It is somewhat out of date, but continues to be depended upon for+-- part of the test suite, at least for the time being.+-- +-- The Password Hash Key Derivation Function (PHKDF) is a unification,+-- synthesis, and distillation of PBKDF2, HKDF, and TupleHash. It was+-- designed as a building block for implementing a variety of+-- self-documenting cryptographic constructions.+--+-- This module is intended more as a demonstration of and cookbook for+-- what can be done with the PHKDF primitives. For actual deployments,+-- consider if the Global Password Prehash Protocol (G3P) is more+-- appropriate for your needs. The G3P is a variant of 'phkdfPass' that+-- additionally integrates bcrypt as the primary key-stretching component.+--+-- These examples also serve as design studies that help informally justify+-- the G3P. Within my design framework, I've tried to maximize the benefits+-- while managing implementation costs.+--+-- 1. Every bit of every parameter matters. Every boundary between+-- parameters matter. There aren't supposed to be any trivial collisions,+-- the only exception being null-extension collisions on the seguid.+--+-- 2. Except for the tweaks, any change to any parameter requires restarting+-- the PHKDF key-stretching computation from somewhere in the very first+-- call to HMAC.+--+-- 3. All input arguments are hardened against length-related timing side+-- channels in various different ways.+--+-- At one extreme, the username, password, and long tag have the most+-- aggressive length hardening in the conventional sense, exhibiting no+-- timing side channels except on multi-kilobyte inputs, after which+-- the timing impacts are minimzed.+--+-- At another extreme, the domain tag exhibits severe yet predictable+-- timing side channels transitioning from 19 to 20 bytes and every 64+-- bytes thereafter. However, the domain tag is otherwise free of+-- timing-based side channels, so it too is hardened in its own way.+--+-- The design I converged upon employs fairly complicated data encoding+-- procedures. Unfortunately, this provides a fair bit of surface area+-- for subtly wrong implementations that work most of the time, but will+-- return garbage on certain lengths of inputs. I hope that this will+-- eventually be remediated with a more comprehensive suite of test vectors.++module Crypto.PHKDF.V1.Cookbook where++import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import Data.Function((&))+import Data.Word+import Data.Stream (Stream)+import Data.Vector (Vector)+import qualified Data.Vector as V+import Network.ByteOrder (word32)++--import Crypto.Encoding.PHKDF+import Crypto.Encoding.PHKDF.V1+import Crypto.Encoding.SHA3.TupleHash+import Crypto.PHKDF.Primitives+import Crypto.PHKDF.Primitives.Assert++-- | These input parameters are grouped together because the envisioned use+-- for them is that they are constants (or near-constants) specified by+-- a deployment. User-supplied inputs would typically not go here.+--+-- The seguid parameter acts as a deployment-wide salt. Cryptographically+-- speaking, the most important thing a deployment can do is specify a+-- constant seguid. It is highly recommended that the seguid input be a+-- genuine Self-Documenting Globally Unique Identifier attesting to the+-- parameters, purposes, and public playbook of the protocol for y'all+-- to follow.+--+-- In more concrete cryptographic terms, the seguid parameter is the constant+-- HMAC key used by the protocol right up until the final output exansion.+-- This design is closely modelled on the HKDF construction. As such, adding+-- null bytes onto the ends of seguids that are less than 64 bytes long+-- should be the only source of trivial collisions in the entire protocol.+--+-- The remaining parameter strings are all directly-documenting plaintext+-- tags. A deployment can use these tags to encode a message into the password+-- hash function so that it must be known to whomever is hashing a password+-- of their choice.+--+-- Finally, the rounds parameter determines the latency of the function.+-- At least 250,000 rounds are recommended if PHKDF is used as the sole key+-- stretching component of a password hash database.+--+-- Unfortunately PHKDF is inexpensively parallelized, so large investments+-- here aren't a good expenditure of a user's latency budget. This is why+-- the G3P integrates bcrypt, and cuts the suggested rounds down to 20,000+--+-- For comparison, @n@ rounds of PHKDF is approximately equivalent to+-- @(1.5 + dtl)*n + c@ rounds of PBKDF2, where @dtl@ is related to the domain+-- tag length, and c is a bit larger than 130 or so.+--+-- Here, @dtl@ is 0 when the domain tag is between 0 and 19 bytes long, 0.5+-- when the domain tag is between 20 and 83 bytes long, and an additional 0.5+-- for every 64 bytes thereafter. Thus these functions exhibit extreme+-- timing side channels on the length of the domain tag.+--+-- By contrast, the long tag is hardened against timing side channels up to+-- a bit less than 5 kilobytes in length. However, an extremely long tag+-- does reduce the headroom provided to masking the length of the username+-- and password fields, however the minimum headroom allocated to the+-- username and password fields is a bit less than 3 kilobytes.+--+-- As an alternate tagging location, consider the 'phkdfInputArgs_credentials'+-- vector, which can be used as an inexpensive, pay-as-you-go plaintext+-- tagging location.+--+-- If the total encoded byte length of 'phkdfInputBlock_tags' is between 0-63+-- bytes, then these hash protocols operate in a constant number of SHA256+-- blocks. Every additional 64 bytes incurs the computation of two or three+-- additional SHA256 blocks, because these tags are hashed into the result+-- two times in the case of 'phkdfPass', and three times in the case of+-- 'phkdfSimple' (and @g3pHash@).++data PhkdfInputBlock = PhkdfInputBlock+ { phkdfInputBlock_seguid :: !ByteString+ -- ^ HMAC-SHA256 key, usable as a high-repetition indirect tag via+ -- self-documenting globally unique identifiers (seguids).+ , phkdfInputBlock_domainTag :: !ByteString+ -- ^ plaintext tag with one repetition per round. 0-19 bytes are free,+ -- 20-83 bytes cost a additional sha256 block per round, with every+ -- 64 bytes thereafter incurring a similar cost.+ , phkdfInputBlock_longTag :: !ByteString+ -- ^ plaintext tag with 1x repetition, then cycled for roughly+ -- 8 kilobytes. Constant time on inputs up to nearly 5 kilobytes.+ , phkdfInputBlock_tags :: !(Vector ByteString)+ -- ^ plaintext tag with 2x repetition ('phkdfPass') or 3x repetition+ -- ('phkdfSimple'). Constant-time on 0-63 encoded bytes, which includes+ -- the length encoding of each string. Thus 60 of those bytes are usable+ -- if the tags vector is a single string, or less if it contains two or+ -- more strings.+ , phkdfInputBlock_rounds :: !Word32+ -- ^ how expensive will this hash function be? An optimal implementation+ -- computes exactly three SHA256 blocks per round if the domain tag is+ -- 19 bytes or less. It is not recommended that phkdf be used as the+ -- primary key-stretching component of a deployment, but if it is used+ -- this way, we recommend at least 250,000 rounds. This can be adjusted+ -- downward in the case of domain tags longer than 19 bytes.+ } -- deriving (Eq, Ord, Show)++-- | The username and password are grouped together because they are normally+-- expected to be supplied by users or other observers of a deployment.+--+-- Furthermore, the credentials vector is here because it is an ideal+-- location to include other user input. For example, one could implement+-- a Two-Secret Key Derivation (2SKD) scheme analogous to 1Password's.+--+-- A deployment can also specify additional constant tags as part of the+-- credentials vector. As the plaintext of these tags is only ever hashed+-- into the output a single time, this is the least expensive+-- pay-as-you-go option for plaintext tagging.+--+-- The credentials vector is constant time on 0-63 encoded bytes, incurring+-- one additional SHA256 block every 64 bytes thereafter. This includes+-- a variable-length field that encodes the bit length of each string; this+-- field itself requires 2 or more bytes.+--+-- The username and password are constant time as long as their encoded+-- lengths add up to less than roughly 3 kilobytes, or the username,+-- password, and domain tag add up to less than roughly 8 kilobytes.+-- The actual numbers are somewhat less in both cases, but this is a+-- good approximation.++data PhkdfInputArgs = PhkdfInputArgs+ { phkdfInputArgs_username :: !ByteString+ -- ^ The name of this parameter is suggestive, but this parameter is+ -- functionally identical to a second password. The only difference+ -- is the fact that a password can be cracked without knowledge of the+ -- plaintext username. By contrast, the password acts as a plaintext tag+ -- if one provides the username: guessing the username implies plaintext+ -- knowledge of the password.+ , phkdfInputArgs_password :: !ByteString+ , phkdfInputArgs_credentials :: !(Vector ByteString)+ } -- deriving (Eq, Ord, Show)++-- | These parameters are used to tweak the final output, without redoing any+-- expensive key stretching. A possible use case is including a high entropy+-- secret in the role itself that isn't available until after a successful+-- stage of authentication.+--+-- Since these parameters are processed in a context that could conceivably be+-- performance sensitive, we don't apply any length padding or side-channel+-- hardening. Instead we opt for maximizing free tagging space. Thus we+-- want to avoid incurring additional SHA256 block computations, one of the+-- favorite techniques employed by the key-stretching phase of 'phkdfPass'+-- to harden against timing side-channels.+--+-- A deployment could conceivably harden this expansion phase against timing+-- side channels themselves, if the were sufficiently inclined. There are+-- several techniques. For starters, a deployment could specify an additional+-- variable-length string in the role vector, used to control its relative+-- ending position inside the SHA256 buffer.++data PhkdfInputTweak = PhkdfInputTweak+ { phkdfInputTweak_role :: !(Vector ByteString)+ , phkdfInputTweak_echoTag :: !ByteString+ } -- deriving (Eq, Ord, Show)++-- | A plain-old-data explicit representation of the intermediate 'phkdfPass'+-- computation after the 'PhkdfInputBlock' and 'PhkdfInputArgs' have been+-- processed and key stretching has been completed, but before the tweaks+-- have been applied and the final output generated.+--+-- If you ever need to serialize or persist a seed, you probably want this.+--+-- Intended to be generated by 'phkdfPass_seedInit' and then consumed+-- without modification by 'phkdfPass_seedFinalize'.++data PhkdfSeed = PhkdfSeed+ { phkdfSeed_seguid :: !ByteString+ , phkdfSeed_seguidKey :: !HmacKey+ , phkdfSeed_domainTag :: !ByteString+ , phkdfSeed_secret :: !ByteString+ } -- deriving (Eq)++-- | A non-tweakable, complete password prehash protocol++phkdfSimple :: PhkdfInputBlock -> PhkdfInputArgs -> Stream ByteString+phkdfSimple block args = echo+ where+ -- Explicitly unpack everything for the unused variable warnings.+ -- i.e. It's relatively easy to check that we've unpacked every+ -- field, then we can rely on unused variable warnings to ensure+ -- we have in fact made use of everything.+ domainTag = phkdfInputBlock_domainTag block+ seguid = phkdfInputBlock_seguid block+ longTag = phkdfInputBlock_longTag block+ tags = phkdfInputBlock_tags block+ rounds = phkdfInputBlock_rounds block++ username = phkdfInputArgs_username args+ password = phkdfInputArgs_password args+ credentials = phkdfInputArgs_credentials args++ headerExtract = [ "phkdf-simple0 username", username ]++ headerUsername = headerExtract ++ [ usernamePadding headerExtract domainTag domainTag ]+ -- password field goes here++ headerLongTag =+ [ longTag+ , B.concat+ [ "password-hash-key-derivation-function phkdf-simple0\x00"+ , leftEncodeFromBytes (B.length domainTag)+ , bareEncode rounds+ ]+ ]++ secretKey =+ phkdfCtx seguid &+ phkdfCtx_feedArgs headerUsername &+ phkdfCtx_assertBufferPosition 32 &+ phkdfCtx_feedArg password &+ phkdfCtx_feedArgs headerLongTag &+ -- FIXME: fusing feedArg and passwordPadding can save ~ 8 KiB RAM+ phkdfCtx_feedArg (passwordPadding headerUsername headerLongTag longTag domainTag password) &+ phkdfCtx_assertBufferPosition 32 &+ phkdfCtx_feedArgs credentials &+ phkdfCtx_feedArg (credentialsPadding credentials longTag domainTag) &+ phkdfCtx_assertBufferPosition 29 &+ phkdfCtx_feedArgs tags &+ phkdfCtx_feedArg (bareEncode (V.length tags)) &+ phkdfSlowCtx_extract+ (cycleByteStringWithNull domainTag)+ (word32 "go\x00\x00" + 2023) domainTag+ "phkdf-simple0 compact" rounds &+ phkdfSlowCtx_assertBufferPosition 32 &+ phkdfSlowCtx_feedArgs tags &+ phkdfSlowCtx_finalize (cycleByteStringWithNull domainTag)++ -- Harden the tags vector against length-based timing side-channels+ echoHeader = cycleByteStringWithNull "phkdf-simple0 expand echo" 30++ echo = phkdfCtx secretKey &+ phkdfCtx_feedArg echoHeader &+ phkdfCtx_assertBufferPosition 32 &+ phkdfCtx_feedArgs tags &+ phkdfCtx_toStream (cycleByteStringWithNull domainTag) (word32 "OUT\x00") domainTag++-- | A tweakable, complete prehash protocol. Note that this function is very+-- intentionally implemented in such a way that the following idiom is+-- efficient, and only performs the expensive key stretching phase once:+--+-- @+-- let mySeed = phkdfPass block args+-- in [ mySeed tweak1, mySeed tweak2, mySeed tweak3 ]+-- @+--+-- However in the case that you want or need to persist or serialize the+-- intermediate seed, then the plain-old-datatype 'PhkdfSeed' and its+-- companion functions 'phkdfPass_seedInit' and 'phkdfPass_seedFinalize'+-- are likely to be more appropriate.++phkdfPass :: PhkdfInputBlock -> PhkdfInputArgs -> PhkdfInputTweak -> Stream ByteString+phkdfPass block args = phkdfPass_seedInit block args & phkdfPass_seedFinalize++-- | This generates a seed, which encapsulates the expensive key-stretching component of 'phkdfPass' into a reusable, tweakable cryptographic value. This function is way slower than it's companion, 'phkdfPass_seedFinalize'. Broadly comparable to HKDF-Extract, though with key stretching built-in.++phkdfPass_seedInit :: PhkdfInputBlock -> PhkdfInputArgs -> PhkdfSeed+phkdfPass_seedInit block args =+ PhkdfSeed {+ phkdfSeed_seguid = seguid,+ phkdfSeed_seguidKey = seguidKey,+ phkdfSeed_domainTag = domainTag,+ phkdfSeed_secret = secret+ }+ where+ domainTag = phkdfInputBlock_domainTag block+ seguid = phkdfInputBlock_seguid block+ longTag = phkdfInputBlock_longTag block+ seedTags = phkdfInputBlock_tags block+ rounds = phkdfInputBlock_rounds block++ username = phkdfInputArgs_username args+ password = phkdfInputArgs_password args+ credentials = phkdfInputArgs_credentials args++ headerExtract = [ "phkdf-pass-v0 username", username ]++ headerUsername = headerExtract ++ [ usernamePadding headerExtract domainTag domainTag ]++ -- password field goes here++ headerLongTag =+ [ longTag+ , B.concat+ [ "password hash & key derivation function: phkdf-pass-v0"+ , bareEncode rounds+ ]+ ]++ seguidKey = hmacKey seguid++ secret =+ phkdfCtx_init seguidKey &+ phkdfCtx_feedArgs headerUsername &+ phkdfCtx_assertBufferPosition 32 &+ phkdfCtx_feedArg password &+ -- FIXME: fusing feedArg and longPadding can save ~ 8 KiB RAM+ phkdfCtx_feedArgs headerLongTag &+ phkdfCtx_feedArg (passwordPadding headerUsername headerLongTag longTag domainTag password) &+ phkdfCtx_assertBufferPosition 32 &+ phkdfCtx_feedArgs credentials &+ phkdfCtx_feedArg (credentialsPadding credentials longTag domainTag) &+ phkdfCtx_assertBufferPosition 29 &+ phkdfCtx_feedArgs seedTags &+ phkdfCtx_feedArg (bareEncode (V.length seedTags)) &+ phkdfSlowCtx_extract+ (cycleByteStringWithNull domainTag)+ (word32 "go\x00\x00" + 2023) domainTag+ "phkdf-pass-v0 compact" rounds &+ phkdfSlowCtx_assertBufferPosition 32 &+ phkdfSlowCtx_feedArgs seedTags &+ phkdfSlowCtx_finalize (cycleByteStringWithNull domainTag)++-- | This consumes a seed and tweaks to produce the final output stream.+-- This function is the output expansion phase of 'phkdfPass'. This function+-- is way faster than it's companion 'phkdfPass_seedInit'. Broadly comparable to+-- HKDF-Expand.++phkdfPass_seedFinalize :: PhkdfSeed -> PhkdfInputTweak -> Stream ByteString+phkdfPass_seedFinalize seed tweak = echo+ where+ seguidKey = phkdfSeed_seguidKey seed+ domainTag = phkdfSeed_domainTag seed+ secret = phkdfSeed_secret seed++ role = phkdfInputTweak_role tweak+ echoTag = phkdfInputTweak_echoTag tweak++ headerCombine = B.concat ["phkdf-pass-v0 combine", secret]+ secretKey =+ phkdfCtx_init seguidKey &+ phkdfCtx_feedArg headerCombine &+ phkdfCtx_feedArgs role &+ phkdfCtx_finalize (cycleByteStringWithNull domainTag) (word32 "KEY\x00") domainTag++ headerEcho = cycleByteString (domainTag <> "\x00phkdf-pass-v0 echo\x00") 32++ echo = phkdfGen secretKey headerEcho (word32 "OUT\x00") echoTag &+ phkdfGen_toStream
phkdf.cabal view
@@ -1,11 +1,11 @@ name: phkdf-version: 0.0.0.0+version: 0.1.0.0 synopsis: Toolkit for self-documenting password hash and key derivation functions. description: Inspired by PBKDF2, HKDF, and TupleHash. Uses HMAC-SHA256 as a primitive.- + This is primarily intended to be a highly reliable reference implementation for the underlying PHKDF primitives. It also aspires to be production ready-ish. The main limitation is that it implemented without mutation,@@ -26,19 +26,23 @@ library exposed-modules: Crypto.PHKDF+ Crypto.PHKDF.Assert+ Crypto.PHKDF.Subtle Crypto.Encoding.PHKDF+ Crypto.Encoding.PHKDF.V1 Crypto.PHKDF.Primitives Crypto.PHKDF.Primitives.Assert Crypto.PHKDF.Primitives.Subtle+ Crypto.PHKDF.V1.Cookbook Crypto.PHKDF.HMAC Crypto.PHKDF.HMAC.Subtle build-depends: base < 5- , bytestring- , cryptohash-sha256+ , bytestring >= 0.11.1.0+ , sha256 , network-byte-order , Stream- , tuplehash-utils+ , tuplehash-utils >= 0.1 , vector ghc-options: -Wall@@ -46,6 +50,11 @@ hs-source-dirs: lib default-language: Haskell2010 +source-repository head+ type: git+ location: https://github.com/auth-global/self-documenting-cryptography+ subdir: phkdf+ test-suite test type: exitcode-stdio-1.0 hs-source-dirs: test@@ -56,13 +65,15 @@ build-depends: base < 5 , aeson >= 2- , base16+ , base16 >= 1 , bytestring , containers , phkdf+ , quickcheck-instances , Stream , tasty , tasty-hunit+ , tasty-quickcheck , text , vector
test/HMAC.hs view
@@ -24,8 +24,6 @@ ] ] where- hmac :: ByteString -> ByteString -> ByteString- hmac k m = hmacCtx_init k & hmacCtx_update m & hmacCtx_finalize run x = B.encodeBase16 (hmac (key x) (msg x)) @?= B.encodeBase16 (out x) testVectors :: [TestVector]
test/Main.hs view
@@ -1,7 +1,13 @@-import Test.Tasty-import Data.Monoid+import Data.ByteString(ByteString)+import qualified Data.ByteString as B+import Data.Monoid+import Test.Tasty+import Test.Tasty.QuickCheck+import Test.QuickCheck.Instances.ByteString()+ import qualified HMAC import qualified PHKDF+import Crypto.Encoding.PHKDF main = do let fileName = PHKDF.testVectorDefaultFileName@@ -11,5 +17,12 @@ tests :: (String, Either String PHKDF.TestVectors) -> TestTree tests phkdfTvs = testGroup "Test" [ testGroup "hmac" HMAC.tests,- testGroup "phkdf" [PHKDF.testFile phkdfTvs]+ testGroup "phkdf" [PHKDF.testFile phkdfTvs],+ testProperty "prop_extendTag" prop_extendTag ]++prop_extendTag :: ByteString -> Bool+prop_extendTag x = trimExtendedTag x' == Just x && validLength+ where+ x' = extendTag x+ validLength = B.length x' <= 19 || B.length x' `mod` 64 == 20
test/PHKDF.hs view
@@ -11,6 +11,7 @@ import qualified Data.Aeson.Key as K import Data.Aeson.KeyMap(KeyMap) import qualified Data.Aeson.KeyMap as KM+import Data.Base16.Types import Data.ByteString(ByteString) import qualified Data.ByteString as B import qualified Data.ByteString.Base16 as B@@ -29,12 +30,14 @@ import Debug.Trace -import Crypto.PHKDF+import Crypto.PHKDF.V1.Cookbook import Test.Tasty import Test.Tasty.HUnit type Args = KeyMap Val +encodeBase16 = extractBase16 . B.encodeBase16+ data Val = Int !Int | Str !ByteString@@ -193,7 +196,7 @@ String txt -> pure (T.encodeUtf8 txt) Object obj | KM.size obj == 1 -> do txt <- obj .: "hex"- case B.decodeBase16 (T.encodeUtf8 txt) of+ case B.decodeBase16Untyped (T.encodeUtf8 txt) of Left _ -> empty Right x -> pure x _ -> empty@@ -208,7 +211,7 @@ parseJSONHash :: Value -> Parser ByteString parseJSONHash = \case String txt ->- case B.decodeBase16 (T.encodeUtf8 txt) of+ case B.decodeBase16Untyped (T.encodeUtf8 txt) of Left _ -> empty Right x -> pure x _ -> empty@@ -253,9 +256,9 @@ compareAu :: String -> ByteString -> Stream ByteString -> Assertion compareAu name bs outStream | B.null bs = assertFailure ("\"" ++ name ++ "\":\"" ++ concatMap toHex (S.take 2 outStream) ++ "\"")- | otherwise = B.encodeBase16 (takeBytes (B.length bs) outStream) @?= B.encodeBase16 bs+ | otherwise = encodeBase16 (takeBytes (B.length bs) outStream) @?= encodeBase16 bs where- toHex = T.unpack . B.encodeBase16+ toHex = T.unpack . encodeBase16 takeBytes :: Int -> Stream ByteString -> ByteString takeBytes n stream = B.concat (go n stream)