g3p-hash 1.0.0.2 → 2.0.0.0
raw patch · 26 files changed
+6513/−1965 lines, 26 filesdep +bcryptdep +hash-stringdep ~Streamdep ~base16dep ~phkdfPVP ok
version bump matches the API change (PVP)
Dependencies added: bcrypt, hash-string
Dependency ranges changed: Stream, base16, phkdf, tuplehash-utils
API changes (from Hackage documentation)
- Crypto.G3P: G3PGen :: !ByteString -> !PhkdfGen -> G3PGen
- Crypto.G3P: G3PInputArgs :: !ByteString -> !ByteString -> !Vector ByteString -> G3PInputArgs
- Crypto.G3P: G3PInputBlock :: !ByteString -> !ByteString -> !ByteString -> !Vector ByteString -> !Word32 -> !Word32 -> !ByteString -> G3PInputBlock
- Crypto.G3P: G3PInputEcho :: ByteString -> G3PInputEcho
- Crypto.G3P: G3PInputRole :: Vector ByteString -> G3PInputRole
- Crypto.G3P: G3PKey :: !ByteString -> HmacKey -> !ByteString -> G3PKey
- Crypto.G3P: G3PSeed :: !ByteString -> !HmacKey -> !ByteString -> !ByteString -> G3PSeed
- Crypto.G3P: [g3pGen_phkdfGen] :: G3PGen -> !PhkdfGen
- Crypto.G3P: [g3pGen_secret] :: G3PGen -> !ByteString
- Crypto.G3P: [g3pInputArgs_credentials] :: G3PInputArgs -> !Vector ByteString
- Crypto.G3P: [g3pInputArgs_password] :: G3PInputArgs -> !ByteString
- Crypto.G3P: [g3pInputArgs_username] :: G3PInputArgs -> !ByteString
- Crypto.G3P: [g3pInputBlock_bcryptRounds] :: G3PInputBlock -> !Word32
- Crypto.G3P: [g3pInputBlock_bcryptTag] :: G3PInputBlock -> !ByteString
- Crypto.G3P: [g3pInputBlock_domainTag] :: G3PInputBlock -> !ByteString
- Crypto.G3P: [g3pInputBlock_longTag] :: G3PInputBlock -> !ByteString
- Crypto.G3P: [g3pInputBlock_phkdfRounds] :: G3PInputBlock -> !Word32
- Crypto.G3P: [g3pInputBlock_seguid] :: G3PInputBlock -> !ByteString
- Crypto.G3P: [g3pInputBlock_tags] :: G3PInputBlock -> !Vector ByteString
- Crypto.G3P: [g3pInputEcho_echoTag] :: G3PInputEcho -> ByteString
- Crypto.G3P: [g3pInputRole_roleTags] :: G3PInputRole -> Vector ByteString
- Crypto.G3P: [g3pKey_domainTag] :: G3PKey -> !ByteString
- Crypto.G3P: [g3pKey_secretKey] :: G3PKey -> HmacKey
- Crypto.G3P: [g3pKey_secret] :: G3PKey -> !ByteString
- Crypto.G3P: [g3pSeed_domainTag] :: G3PSeed -> !ByteString
- Crypto.G3P: [g3pSeed_secret] :: G3PSeed -> !ByteString
- Crypto.G3P: [g3pSeed_seguidKey] :: G3PSeed -> !HmacKey
- Crypto.G3P: [g3pSeed_seguid] :: G3PSeed -> !ByteString
- Crypto.G3P: data G3PGen
- Crypto.G3P: data G3PInputArgs
- Crypto.G3P: data G3PInputBlock
- Crypto.G3P: data G3PKey
- Crypto.G3P: data G3PSeed
- Crypto.G3P: g3pGen_finalizeStream :: G3PGen -> Stream ByteString
- Crypto.G3P: g3pGen_read :: G3PGen -> (ByteString, G3PGen)
- Crypto.G3P: g3pHash :: G3PInputBlock -> G3PInputArgs -> G3PInputRole -> G3PInputEcho -> Stream ByteString
- Crypto.G3P: g3pHash_finalizeGen :: G3PInputEcho -> G3PKey -> G3PGen
- Crypto.G3P: g3pHash_keyInit :: G3PInputRole -> G3PSeed -> G3PKey
- Crypto.G3P: g3pHash_seedInit :: G3PInputBlock -> G3PInputArgs -> G3PSeed
- Crypto.G3P: instance GHC.Classes.Eq Crypto.G3P.G3PInputArgs
- Crypto.G3P: instance GHC.Classes.Eq Crypto.G3P.G3PInputBlock
- Crypto.G3P: instance GHC.Classes.Eq Crypto.G3P.G3PInputEcho
- Crypto.G3P: instance GHC.Classes.Eq Crypto.G3P.G3PInputRole
- Crypto.G3P: instance GHC.Classes.Eq Crypto.G3P.G3PKey
- Crypto.G3P: instance GHC.Classes.Eq Crypto.G3P.G3PSeed
- Crypto.G3P: instance GHC.Classes.Ord Crypto.G3P.G3PInputArgs
- Crypto.G3P: instance GHC.Classes.Ord Crypto.G3P.G3PInputBlock
- Crypto.G3P: instance GHC.Classes.Ord Crypto.G3P.G3PInputEcho
- Crypto.G3P: instance GHC.Classes.Ord Crypto.G3P.G3PInputRole
- Crypto.G3P: instance GHC.Show.Show Crypto.G3P.G3PInputArgs
- Crypto.G3P: instance GHC.Show.Show Crypto.G3P.G3PInputBlock
- Crypto.G3P: instance GHC.Show.Show Crypto.G3P.G3PInputEcho
- Crypto.G3P: instance GHC.Show.Show Crypto.G3P.G3PInputRole
- Crypto.G3P: newtype G3PInputEcho
- Crypto.G3P: newtype G3PInputRole
+ Crypto.G3P.BCrypt: bcrypt :: ByteString -> ByteString -> Maybe ByteString
+ Crypto.G3P.BCrypt: bcryptXsFree :: forall f a. Foldable f => (a -> ByteString) -> ByteString -> f a -> ByteString -> f a -> ByteString -> Word32 -> HmacKeyPrefixed -> (Int, HmacKeyPrefixed)
+ Crypto.G3P.BCrypt: bcrypt_formatSaltString :: Char -> Word8 -> ByteString -> ByteString -> Maybe ByteString
+ Crypto.G3P.BCrypt: bcrypt_maxPasswordLength :: Int
+ Crypto.G3P.BCrypt: bcrypt_outputLength :: Int
+ Crypto.G3P.BCrypt: bcrypt_parseSaltString :: ByteString -> Maybe (Char, Word8, ByteString, ByteString)
+ Crypto.G3P.BCrypt: bcrypt_saltLength :: Int
+ Crypto.G3P.BCrypt.Subtle: BCryptState :: ByteString -> BCryptState
+ Crypto.G3P.BCrypt.Subtle: BCryptXs :: !ByteString -> !ByteString -> !ByteString -> !ByteString -> !ByteString -> !ByteString -> !ByteString -> !Word32 -> !Bool -> BCryptXs
+ Crypto.G3P.BCrypt.Subtle: BCryptXsCtr :: !ByteString -> !ByteString -> !ByteString -> !ByteString -> BCryptXsCtr
+ Crypto.G3P.BCrypt.Subtle: [bcryptState_toByteString] :: BCryptState -> ByteString
+ Crypto.G3P.BCrypt.Subtle: [bcryptXsCtr_key0] :: BCryptXsCtr -> !ByteString
+ Crypto.G3P.BCrypt.Subtle: [bcryptXsCtr_key1] :: BCryptXsCtr -> !ByteString
+ Crypto.G3P.BCrypt.Subtle: [bcryptXsCtr_name] :: BCryptXsCtr -> !ByteString
+ Crypto.G3P.BCrypt.Subtle: [bcryptXsCtr_tag] :: BCryptXsCtr -> !ByteString
+ Crypto.G3P.BCrypt.Subtle: [bcryptXs_implicitNull] :: BCryptXs -> !Bool
+ Crypto.G3P.BCrypt.Subtle: [bcryptXs_key0] :: BCryptXs -> !ByteString
+ Crypto.G3P.BCrypt.Subtle: [bcryptXs_keyL] :: BCryptXs -> !ByteString
+ Crypto.G3P.BCrypt.Subtle: [bcryptXs_keyR] :: BCryptXs -> !ByteString
+ Crypto.G3P.BCrypt.Subtle: [bcryptXs_rounds] :: BCryptXs -> !Word32
+ Crypto.G3P.BCrypt.Subtle: [bcryptXs_salt0] :: BCryptXs -> !ByteString
+ Crypto.G3P.BCrypt.Subtle: [bcryptXs_saltL] :: BCryptXs -> !ByteString
+ Crypto.G3P.BCrypt.Subtle: [bcryptXs_saltR] :: BCryptXs -> !ByteString
+ Crypto.G3P.BCrypt.Subtle: [bcryptXs_saltZ] :: BCryptXs -> !ByteString
+ Crypto.G3P.BCrypt.Subtle: base64Decode :: ByteString -> Maybe ByteString
+ Crypto.G3P.BCrypt.Subtle: base64Encode :: ByteString -> ByteString
+ Crypto.G3P.BCrypt.Subtle: bcryptRaw_genInputs :: ByteString -> ByteString -> Word32 -> BCryptXs
+ Crypto.G3P.BCrypt.Subtle: bcryptRaw_outputSalt :: ByteString
+ Crypto.G3P.BCrypt.Subtle: bcryptXs :: BCryptXs -> ByteString
+ Crypto.G3P.BCrypt.Subtle: bcryptXsCtrSuperRound :: BCryptXsCtr -> Word32 -> Word32 -> Word32 -> Maybe BCryptState -> (Word32, BCryptState)
+ Crypto.G3P.BCrypt.Subtle: bcryptXsCtr_outputLength :: Int
+ Crypto.G3P.BCrypt.Subtle: bcryptXs_maxKeyLength :: Int
+ Crypto.G3P.BCrypt.Subtle: bcryptXs_maxSaltLength :: Int
+ Crypto.G3P.BCrypt.Subtle: bcrypt_outputSalt :: ByteString
+ Crypto.G3P.BCrypt.Subtle: data BCryptXs
+ Crypto.G3P.BCrypt.Subtle: data BCryptXsCtr
+ Crypto.G3P.BCrypt.Subtle: instance GHC.Classes.Eq Crypto.G3P.BCrypt.Subtle.BCryptState
+ Crypto.G3P.BCrypt.Subtle: instance GHC.Classes.Ord Crypto.G3P.BCrypt.Subtle.BCryptState
+ Crypto.G3P.BCrypt.Subtle: instance GHC.Show.Show Crypto.G3P.BCrypt.Subtle.BCryptState
+ Crypto.G3P.BCrypt.Subtle: newtype BCryptState
+ Crypto.G3P.BCrypt.Subtle: orpheanBeholderScryDoubt :: ByteString
+ Crypto.G3P.V1: G3PGen :: !ByteString -> !PhkdfGen -> G3PGen
+ Crypto.G3P.V1: G3PInputArgs :: !ByteString -> !ByteString -> !Vector ByteString -> G3PInputArgs
+ Crypto.G3P.V1: G3PInputBlock :: !ByteString -> !ByteString -> !ByteString -> !Vector ByteString -> !Word32 -> !Word32 -> !ByteString -> G3PInputBlock
+ Crypto.G3P.V1: G3PInputEcho :: ByteString -> G3PInputEcho
+ Crypto.G3P.V1: G3PInputRole :: Vector ByteString -> G3PInputRole
+ Crypto.G3P.V1: G3PKey :: !ByteString -> HmacKey -> !ByteString -> G3PKey
+ Crypto.G3P.V1: G3PSeed :: !ByteString -> !HmacKey -> !ByteString -> !ByteString -> G3PSeed
+ Crypto.G3P.V1: [g3pGen_phkdfGen] :: G3PGen -> !PhkdfGen
+ Crypto.G3P.V1: [g3pGen_secret] :: G3PGen -> !ByteString
+ Crypto.G3P.V1: [g3pInputArgs_credentials] :: G3PInputArgs -> !Vector ByteString
+ Crypto.G3P.V1: [g3pInputArgs_password] :: G3PInputArgs -> !ByteString
+ Crypto.G3P.V1: [g3pInputArgs_username] :: G3PInputArgs -> !ByteString
+ Crypto.G3P.V1: [g3pInputBlock_bcryptRounds] :: G3PInputBlock -> !Word32
+ Crypto.G3P.V1: [g3pInputBlock_bcryptTag] :: G3PInputBlock -> !ByteString
+ Crypto.G3P.V1: [g3pInputBlock_domainTag] :: G3PInputBlock -> !ByteString
+ Crypto.G3P.V1: [g3pInputBlock_longTag] :: G3PInputBlock -> !ByteString
+ Crypto.G3P.V1: [g3pInputBlock_phkdfRounds] :: G3PInputBlock -> !Word32
+ Crypto.G3P.V1: [g3pInputBlock_seguid] :: G3PInputBlock -> !ByteString
+ Crypto.G3P.V1: [g3pInputBlock_tags] :: G3PInputBlock -> !Vector ByteString
+ Crypto.G3P.V1: [g3pInputEcho_echoTag] :: G3PInputEcho -> ByteString
+ Crypto.G3P.V1: [g3pInputRole_roleTags] :: G3PInputRole -> Vector ByteString
+ Crypto.G3P.V1: [g3pKey_domainTag] :: G3PKey -> !ByteString
+ Crypto.G3P.V1: [g3pKey_secretKey] :: G3PKey -> HmacKey
+ Crypto.G3P.V1: [g3pKey_secret] :: G3PKey -> !ByteString
+ Crypto.G3P.V1: [g3pSeed_domainTag] :: G3PSeed -> !ByteString
+ Crypto.G3P.V1: [g3pSeed_secret] :: G3PSeed -> !ByteString
+ Crypto.G3P.V1: [g3pSeed_seguidKey] :: G3PSeed -> !HmacKey
+ Crypto.G3P.V1: [g3pSeed_seguid] :: G3PSeed -> !ByteString
+ Crypto.G3P.V1: data G3PGen
+ Crypto.G3P.V1: data G3PInputArgs
+ Crypto.G3P.V1: data G3PInputBlock
+ Crypto.G3P.V1: data G3PKey
+ Crypto.G3P.V1: data G3PSeed
+ Crypto.G3P.V1: g3pGen_finalizeStream :: G3PGen -> Stream ByteString
+ Crypto.G3P.V1: g3pGen_read :: G3PGen -> (ByteString, G3PGen)
+ Crypto.G3P.V1: g3pHash :: G3PInputBlock -> G3PInputArgs -> G3PInputRole -> G3PInputEcho -> Stream ByteString
+ Crypto.G3P.V1: g3pHash_finalizeGen :: G3PInputEcho -> G3PKey -> G3PGen
+ Crypto.G3P.V1: g3pHash_keyInit :: G3PInputRole -> G3PSeed -> G3PKey
+ Crypto.G3P.V1: g3pHash_seedInit :: G3PInputBlock -> G3PInputArgs -> G3PSeed
+ Crypto.G3P.V1: instance GHC.Classes.Eq Crypto.G3P.V1.G3PInputArgs
+ Crypto.G3P.V1: instance GHC.Classes.Eq Crypto.G3P.V1.G3PInputBlock
+ Crypto.G3P.V1: instance GHC.Classes.Eq Crypto.G3P.V1.G3PInputEcho
+ Crypto.G3P.V1: instance GHC.Classes.Eq Crypto.G3P.V1.G3PInputRole
+ Crypto.G3P.V1: instance GHC.Classes.Ord Crypto.G3P.V1.G3PInputArgs
+ Crypto.G3P.V1: instance GHC.Classes.Ord Crypto.G3P.V1.G3PInputBlock
+ Crypto.G3P.V1: instance GHC.Classes.Ord Crypto.G3P.V1.G3PInputEcho
+ Crypto.G3P.V1: instance GHC.Classes.Ord Crypto.G3P.V1.G3PInputRole
+ Crypto.G3P.V1: instance GHC.Show.Show Crypto.G3P.V1.G3PInputArgs
+ Crypto.G3P.V1: instance GHC.Show.Show Crypto.G3P.V1.G3PInputBlock
+ Crypto.G3P.V1: instance GHC.Show.Show Crypto.G3P.V1.G3PInputEcho
+ Crypto.G3P.V1: instance GHC.Show.Show Crypto.G3P.V1.G3PInputRole
+ Crypto.G3P.V1: newtype G3PInputEcho
+ Crypto.G3P.V1: newtype G3PInputRole
+ Crypto.G3P.V2: Cons :: a -> Stream a -> Stream a
+ Crypto.G3P.V2: G3PInputs :: !ByteString -> !ByteString -> !Vector ByteString -> G3PInputs
+ Crypto.G3P.V2: G3PSalt :: !HmacKey -> !ByteString -> !Vector ByteString -> !ByteString -> !Word32 -> G3PSalt
+ Crypto.G3P.V2: G3PSeedInputs :: !HmacKey -> !Vector ByteString -> !ByteString -> !Vector ByteString -> !ByteString -> !Word32 -> G3PSeedInputs
+ Crypto.G3P.V2: [g3pInputs_credentials] :: G3PInputs -> !Vector ByteString
+ Crypto.G3P.V2: [g3pInputs_password] :: G3PInputs -> !ByteString
+ Crypto.G3P.V2: [g3pInputs_username] :: G3PInputs -> !ByteString
+ Crypto.G3P.V2: [g3pSalt_contextTags] :: G3PSalt -> !Vector ByteString
+ Crypto.G3P.V2: [g3pSalt_domainTag] :: G3PSalt -> !ByteString
+ Crypto.G3P.V2: [g3pSalt_longTag] :: G3PSalt -> !ByteString
+ Crypto.G3P.V2: [g3pSalt_phkdfRounds] :: G3PSalt -> !Word32
+ Crypto.G3P.V2: [g3pSalt_seguid] :: G3PSalt -> !HmacKey
+ Crypto.G3P.V2: [g3pSeedInputs_bcryptContextTags] :: G3PSeedInputs -> !Vector ByteString
+ Crypto.G3P.V2: [g3pSeedInputs_bcryptCredentials] :: G3PSeedInputs -> !Vector ByteString
+ Crypto.G3P.V2: [g3pSeedInputs_bcryptDomainTag] :: G3PSeedInputs -> !ByteString
+ Crypto.G3P.V2: [g3pSeedInputs_bcryptLongTag] :: G3PSeedInputs -> !ByteString
+ Crypto.G3P.V2: [g3pSeedInputs_bcryptRounds] :: G3PSeedInputs -> !Word32
+ Crypto.G3P.V2: [g3pSeedInputs_bcryptSeguid] :: G3PSeedInputs -> !HmacKey
+ Crypto.G3P.V2: data G3PInputs
+ Crypto.G3P.V2: data G3PKey
+ Crypto.G3P.V2: data G3PSalt
+ Crypto.G3P.V2: data G3PSeed
+ Crypto.G3P.V2: data G3PSeedInputs
+ Crypto.G3P.V2: data G3PSpark
+ Crypto.G3P.V2: data G3PSprout
+ Crypto.G3P.V2: data G3PTree
+ Crypto.G3P.V2: data () => Stream a
+ Crypto.G3P.V2: g3pHash :: Foldable f => G3PSalt -> G3PInputs -> G3PSeedInputs -> HmacKey -> f ByteString -> ByteString -> ByteString -> ByteString -> Word32 -> ByteString -> ByteString
+ Crypto.G3P.V2: g3pKey :: Foldable f => G3PSalt -> G3PInputs -> G3PSeedInputs -> HmacKey -> f ByteString -> ByteString -> ByteString -> G3PKey
+ Crypto.G3P.V2: g3pKey_fromSeed :: Foldable f => HmacKey -> f ByteString -> ByteString -> ByteString -> G3PSeed -> G3PKey
+ Crypto.G3P.V2: g3pKey_fromSpark :: Foldable f => G3PSeedInputs -> HmacKey -> f ByteString -> ByteString -> ByteString -> G3PSpark -> G3PKey
+ Crypto.G3P.V2: g3pKey_fromSprout :: Foldable f => f ByteString -> ByteString -> ByteString -> G3PSprout -> G3PKey
+ Crypto.G3P.V2: g3pKey_fromTree :: ByteString -> G3PTree -> G3PKey
+ Crypto.G3P.V2: g3pKey_toSource :: G3PKey -> ByteString -> Word32 -> ByteString -> G3PSource
+ Crypto.G3P.V2: g3pKey_toStream :: G3PKey -> ByteString -> Word32 -> ByteString -> Stream ByteString
+ Crypto.G3P.V2: g3pSeed :: G3PSalt -> G3PInputs -> G3PSeedInputs -> G3PSeed
+ Crypto.G3P.V2: g3pSeed_fromSpark :: G3PSeedInputs -> G3PSpark -> G3PSeed
+ Crypto.G3P.V2: g3pSeed_toKey :: Foldable f => G3PSeed -> HmacKey -> f ByteString -> ByteString -> ByteString -> G3PKey
+ Crypto.G3P.V2: g3pSeed_toSource :: Foldable f => G3PSeed -> HmacKey -> f ByteString -> ByteString -> ByteString -> ByteString -> Word32 -> ByteString -> G3PSource
+ Crypto.G3P.V2: g3pSeed_toSprout :: G3PSeed -> HmacKey -> G3PSprout
+ Crypto.G3P.V2: g3pSeed_toStream :: Foldable f => G3PSeed -> HmacKey -> f ByteString -> ByteString -> ByteString -> ByteString -> Word32 -> ByteString -> Stream ByteString
+ Crypto.G3P.V2: g3pSeed_toTree :: Foldable f => G3PSeed -> HmacKey -> f ByteString -> ByteString -> G3PTree
+ Crypto.G3P.V2: g3pSource :: Foldable f => G3PSalt -> G3PInputs -> G3PSeedInputs -> HmacKey -> f ByteString -> ByteString -> ByteString -> ByteString -> Word32 -> ByteString -> G3PSource
+ Crypto.G3P.V2: g3pSource_fromKey :: ByteString -> Word32 -> ByteString -> G3PKey -> G3PSource
+ Crypto.G3P.V2: g3pSource_fromSeed :: Foldable f => HmacKey -> f ByteString -> ByteString -> ByteString -> ByteString -> Word32 -> ByteString -> G3PSeed -> G3PSource
+ Crypto.G3P.V2: g3pSource_fromSpark :: Foldable f => G3PSeedInputs -> HmacKey -> f ByteString -> ByteString -> ByteString -> ByteString -> Word32 -> ByteString -> G3PSpark -> G3PSource
+ Crypto.G3P.V2: g3pSource_fromSprout :: Foldable f => f ByteString -> ByteString -> ByteString -> ByteString -> Word32 -> ByteString -> G3PSprout -> G3PSource
+ Crypto.G3P.V2: g3pSource_fromTree :: ByteString -> ByteString -> Word32 -> ByteString -> G3PTree -> G3PSource
+ Crypto.G3P.V2: g3pSource_peek :: G3PSource -> Maybe ByteString
+ Crypto.G3P.V2: g3pSource_read :: G3PSource -> (ByteString, G3PSource)
+ Crypto.G3P.V2: g3pSource_toStream :: G3PSource -> Stream ByteString
+ Crypto.G3P.V2: g3pSpark :: G3PSalt -> G3PInputs -> G3PSpark
+ Crypto.G3P.V2: g3pSpark_toKey :: Foldable f => G3PSpark -> G3PSeedInputs -> HmacKey -> f ByteString -> ByteString -> ByteString -> G3PKey
+ Crypto.G3P.V2: g3pSpark_toSeed :: G3PSpark -> G3PSeedInputs -> G3PSeed
+ Crypto.G3P.V2: g3pSpark_toSource :: Foldable f => G3PSpark -> G3PSeedInputs -> HmacKey -> f ByteString -> ByteString -> ByteString -> ByteString -> Word32 -> ByteString -> G3PSource
+ Crypto.G3P.V2: g3pSpark_toSprout :: G3PSpark -> G3PSeedInputs -> HmacKey -> G3PSprout
+ Crypto.G3P.V2: g3pSpark_toStream :: Foldable f => G3PSpark -> G3PSeedInputs -> HmacKey -> f ByteString -> ByteString -> ByteString -> ByteString -> Word32 -> ByteString -> Stream ByteString
+ Crypto.G3P.V2: g3pSpark_toTree :: Foldable f => G3PSpark -> G3PSeedInputs -> HmacKey -> f ByteString -> ByteString -> G3PTree
+ Crypto.G3P.V2: g3pSprout :: G3PSalt -> G3PInputs -> G3PSeedInputs -> HmacKey -> G3PSprout
+ Crypto.G3P.V2: g3pSprout_arg :: G3PSprout -> ByteString -> G3PSprout
+ Crypto.G3P.V2: g3pSprout_args :: Foldable f => G3PSprout -> f ByteString -> G3PSprout
+ Crypto.G3P.V2: g3pSprout_feedArg :: ByteString -> G3PSprout -> G3PSprout
+ Crypto.G3P.V2: g3pSprout_feedArgs :: Foldable f => f ByteString -> G3PSprout -> G3PSprout
+ Crypto.G3P.V2: g3pSprout_fromSeed :: HmacKey -> G3PSeed -> G3PSprout
+ Crypto.G3P.V2: g3pSprout_fromSpark :: G3PSeedInputs -> HmacKey -> G3PSpark -> G3PSprout
+ Crypto.G3P.V2: g3pSprout_toKey :: Foldable f => G3PSprout -> f ByteString -> ByteString -> ByteString -> G3PKey
+ Crypto.G3P.V2: g3pSprout_toSource :: Foldable f => G3PSprout -> f ByteString -> ByteString -> ByteString -> ByteString -> Word32 -> ByteString -> G3PSource
+ Crypto.G3P.V2: g3pSprout_toStream :: Foldable f => G3PSprout -> f ByteString -> ByteString -> ByteString -> ByteString -> Word32 -> ByteString -> Stream ByteString
+ Crypto.G3P.V2: g3pSprout_toTree :: G3PSprout -> ByteString -> G3PTree
+ Crypto.G3P.V2: g3pStream :: Foldable f => G3PSalt -> G3PInputs -> G3PSeedInputs -> HmacKey -> f ByteString -> ByteString -> ByteString -> ByteString -> Word32 -> ByteString -> Stream ByteString
+ Crypto.G3P.V2: g3pStream_fromKey :: ByteString -> Word32 -> ByteString -> G3PKey -> Stream ByteString
+ Crypto.G3P.V2: g3pStream_fromSeed :: Foldable f => HmacKey -> f ByteString -> ByteString -> ByteString -> ByteString -> Word32 -> ByteString -> G3PSeed -> Stream ByteString
+ Crypto.G3P.V2: g3pStream_fromSource :: G3PSource -> Stream ByteString
+ Crypto.G3P.V2: g3pStream_fromSpark :: Foldable f => G3PSeedInputs -> HmacKey -> f ByteString -> ByteString -> ByteString -> ByteString -> Word32 -> ByteString -> G3PSpark -> Stream ByteString
+ Crypto.G3P.V2: g3pStream_fromSprout :: Foldable f => f ByteString -> ByteString -> ByteString -> ByteString -> Word32 -> ByteString -> G3PSprout -> Stream ByteString
+ Crypto.G3P.V2: g3pStream_fromTree :: ByteString -> ByteString -> Word32 -> ByteString -> G3PTree -> Stream ByteString
+ Crypto.G3P.V2: g3pTree :: Foldable f => G3PSalt -> G3PInputs -> G3PSeedInputs -> HmacKey -> f ByteString -> ByteString -> G3PTree
+ Crypto.G3P.V2: g3pTree_fromSeed :: Foldable f => HmacKey -> f ByteString -> ByteString -> G3PSeed -> G3PTree
+ Crypto.G3P.V2: g3pTree_fromSpark :: Foldable f => G3PSeedInputs -> HmacKey -> f ByteString -> ByteString -> G3PSpark -> G3PTree
+ Crypto.G3P.V2: g3pTree_fromSprout :: ByteString -> G3PSprout -> G3PTree
+ Crypto.G3P.V2: g3pTree_toKey :: G3PTree -> ByteString -> G3PKey
+ Crypto.G3P.V2: g3pTree_toSource :: G3PTree -> ByteString -> ByteString -> Word32 -> ByteString -> G3PSource
+ Crypto.G3P.V2: g3pTree_toStream :: G3PTree -> ByteString -> ByteString -> Word32 -> ByteString -> Stream ByteString
+ Crypto.G3P.V2: infixr 5 `Cons`
+ Crypto.G3P.V2: instance GHC.Classes.Eq Crypto.G3P.V2.G3PInputs
+ Crypto.G3P.V2: instance GHC.Classes.Eq Crypto.G3P.V2.G3PSalt
+ Crypto.G3P.V2: type G3PSource = PhkdfGen
+ Crypto.G3P.V2: word32 :: ByteString -> Word32
+ Crypto.G3P.V2.Foxtrot: G3PFoxtrotSalt :: !HmacKey -> !ByteString -> !Vector ByteString -> !ByteString -> !Word32 -> G3PFoxtrotSalt
+ Crypto.G3P.V2.Foxtrot: [g3pFoxtrotSalt_bcryptRounds] :: G3PFoxtrotSalt -> !Word32
+ Crypto.G3P.V2.Foxtrot: [g3pFoxtrotSalt_contextTags] :: G3PFoxtrotSalt -> !Vector ByteString
+ Crypto.G3P.V2.Foxtrot: [g3pFoxtrotSalt_domainTag] :: G3PFoxtrotSalt -> !ByteString
+ Crypto.G3P.V2.Foxtrot: [g3pFoxtrotSalt_key] :: G3PFoxtrotSalt -> !HmacKey
+ Crypto.G3P.V2.Foxtrot: [g3pFoxtrotSalt_longTag] :: G3PFoxtrotSalt -> !ByteString
+ Crypto.G3P.V2.Foxtrot: data G3PFoxtrotSalt
+ Crypto.G3P.V2.Foxtrot: g3pFoxtrot :: (Foldable f, Foldable g) => G3PFoxtrotSalt -> f ByteString -> g ByteString -> Word32 -> ByteString
+ Crypto.G3P.V2.Foxtrot: g3pTango :: Foldable f => HmacKey -> f ByteString -> Word32 -> ByteString -> ByteString
+ Crypto.G3P.V2.Subtle: G3PKey :: HmacKeyHashed -> G3PKey
+ Crypto.G3P.V2.Subtle: G3PSeed :: ByteString -> G3PSeed
+ Crypto.G3P.V2.Subtle: G3PSpark :: !ByteString -> !ByteString -> !Vector ByteString -> !ByteString -> G3PSpark
+ Crypto.G3P.V2.Subtle: G3PSprout :: PhkdfCtx -> G3PSprout
+ Crypto.G3P.V2.Subtle: G3PTree :: ByteString -> G3PTree
+ Crypto.G3P.V2.Subtle: [g3pKey_streamKey] :: G3PKey -> HmacKeyHashed
+ Crypto.G3P.V2.Subtle: [g3pSeed_seedKey] :: G3PSeed -> ByteString
+ Crypto.G3P.V2.Subtle: [g3pSpark_beginKey] :: G3PSpark -> !ByteString
+ Crypto.G3P.V2.Subtle: [g3pSpark_contKey] :: G3PSpark -> !ByteString
+ Crypto.G3P.V2.Subtle: [g3pSpark_contextTags] :: G3PSpark -> !Vector ByteString
+ Crypto.G3P.V2.Subtle: [g3pSpark_domainTag] :: G3PSpark -> !ByteString
+ Crypto.G3P.V2.Subtle: [g3pSprout_phkdfCtx] :: G3PSprout -> PhkdfCtx
+ Crypto.G3P.V2.Subtle: [g3pTree_echoKeyL] :: G3PTree -> ByteString
+ Crypto.G3P.V2.Subtle: data G3PSpark
+ Crypto.G3P.V2.Subtle: instance GHC.Classes.Eq Crypto.G3P.V2.Subtle.G3PSeed
+ Crypto.G3P.V2.Subtle: instance GHC.Classes.Eq Crypto.G3P.V2.Subtle.G3PSpark
+ Crypto.G3P.V2.Subtle: instance GHC.Classes.Eq Crypto.G3P.V2.Subtle.G3PTree
+ Crypto.G3P.V2.Subtle: newtype G3PKey
+ Crypto.G3P.V2.Subtle: newtype G3PSeed
+ Crypto.G3P.V2.Subtle: newtype G3PSprout
+ Crypto.G3P.V2.Subtle: newtype G3PTree
Files
- ChangeLog.md +44/−9
- csrc/bcrypt_raw.c +0/−54
- csrc/bcrypt_raw.h +0/−12
- csrc/blowfish.c +0/−627
- csrc/g3p_bcrypt.c +845/−0
- csrc/g3p_bcrypt.h +124/−0
- csrc/g3p_bcrypt_base64.c +155/−0
- csrc/g3p_bcrypt_base64.h +27/−0
- csrc/g3p_blf.h +0/−77
- g3p-hash.cabal +30/−9
- g3p-test-vectors.json +364/−182
- g3pb1-test-vectors.json +522/−0
- lib/Crypto/G3P.hs +8/−548
- lib/Crypto/G3P/BCrypt.hs +330/−0
- lib/Crypto/G3P/BCrypt.hsc +0/−88
- lib/Crypto/G3P/BCrypt/Subtle.hsc +365/−0
- lib/Crypto/G3P/V1.hs +575/−0
- lib/Crypto/G3P/V2.hs +1589/−0
- lib/Crypto/G3P/V2/Foxtrot.hs +148/−0
- lib/Crypto/G3P/V2/Subtle.hs +65/−0
- test/Bcrypt.hs +203/−0
- test/G3P.hs +0/−352
- test/G3Pb1.hs +353/−0
- test/G3Pb2.hs +539/−0
- test/Main.hs +15/−7
- test/MyCorpExample.hs +212/−0
ChangeLog.md view
@@ -1,5 +1,39 @@ # Revision history for g3p-hash +## Version 2.0.0.0 release (2025-01-20)++* The prerelease depended on a local fork of cryptohash-sha256 that supported+ bitstring inputs. This release rewrote the FFI bindings surrounding+ cryptohash-sha256. This is coordinated with the release of the new sha256+ binding which also supports precomputed HMAC keys, streaming, backtracking,+ (de)serialization of intermediate states, HKDF, PBKDF2, and more.++* an OpenBSD-compatible bcrypt binding is now exported from Crypto.G3P.BCrypt++## Version 2.0.0.0 prelease "The War Tuba" (2024-04-20)++I was incredibly over the moon happy with Version 1. Then it provided me with+the spark of insight that lead to Version 2, which is 100x better. I am still+amazed by Version 1, although I'm no longer interested in deploying it myself.++At that point in time, I had put on hold a full cryptoacoustic reconstruction+of bcrypt, as I figured it was long past overdue to get Version 1 into this+fight. But then a last minute low-risk, plausibly high-reward guess to hedge+my bets lead to a sequence of revelations that gave me the understanding of+bcrypt that I needed to confidently make the changes I originally wanted.+(See commit a0d89af)++Now I wish I was lofting something bigger than bcrypt, but to paraphrase+Donald Rumsfeld, you go to war with the password hash functions you have,+not the password hash functions you want. And on that count, bcrypt has+become a mighty cryptoacoustic horn indeed! I have great confidence these+modifications will keep those fully homomorphic cancel-culture crackers+away for a long time to come.++(Not actually released on 4-20, but I did start having the mental breakthroughs+a few days earlier, spent 4-20 hacking away, and then spent the next two weeks+slowly making it a reality. It isn't even released yet, but hopefully soon.)+ ## Version 1.0.0.1 "Fight like a Pacifist" (2024-03-21) Developing this project has been a long, strange trip. I've taken clues and@@ -9,9 +43,10 @@ First of all, I'd like to thank Lois T Clark for teaching me how to fight like a pacifist. I'd like to thank Joe Taylor (K1JT) for WSPR and the WSJT suite of-amateur radio protocols. I'd like to thank the State of Indiana for providing me-with a world-class public education. These lessons directly inspired the goals-and methodologies of this project.+amateur radio protocols. I'd like to thank Troy Hunt for "Have I Been Pwned?".+I'd like to thank the State of Indiana for providing me with a world-class+public education. These lessons directly inspired the goals and methodologies+of this project. I'd like to thank Mike Dunn and Katalin Bimbo for trying to teach me relevance logic. While I still have no formal understanding of this topic, thinking about@@ -19,8 +54,8 @@ It helped me see through my own dubious ideas and justifications, it helped me modulate the goals and methodologies of this project, and it helped me make real progress on identifying plausibly-desirable design properties. In short,-it took me to a design that I am so much happier with than I imagined at the-outset of this project.+it took me to a design with which I am so much happier than I could have+imagined at the outset of this project. I'd like to thank David Doiron for introducing me to signals and the theory of communication via optics, and Yuri Goldfeld and the Indiana Academy, especially@@ -33,10 +68,10 @@ David Singer for introducing me to number theory, RSA cryptography, and digital identity. -Finally, I'd like to thank the developers of HMAC, SHA256, PBKDF2, HKDF, and-bcrypt for paving the way, Steve "sc00bz" Thomas and Soatok for sharing their-valuable insights into cryptography with me, and Obsidian Systems for giving me-opportunities to develop my skills in cryptography.+Finally, I'd like to thank the developers of HMAC, SHA256, PBKDF2, HKDF,+blowfish, and bcrypt for paving the way, Steve "sc00bz" Thomas and Soatok for+sharing their valuable insights into cryptography with me, and Obsidian Systems+for giving me opportunities to develop my skills in cryptography. ## Version 1.0.0.0 (2024-03-21)
− csrc/bcrypt_raw.c
@@ -1,54 +0,0 @@-/* lightly modified and aggressively stripped down version of OpenBSD's implementation of BCrypt */--#include <string.h>-#include "g3p_blf.h"-#include "bcrypt_raw.h"--#define BCRYPT_WORDS 6--void-bcrypt_raw ( const char *key, uint32_t keybytes,- const char *salt, uint32_t saltbytes,- char output[BCRYPT_RAW_OUTPUT_LENGTH],- uint32_t rounds) {- G3P_blf_ctx state;- uint8_t ciphertext[BCRYPT_RAW_OUTPUT_LENGTH] = "OrpheanBeholderScryDoubt";- uint32_t cdata[BCRYPT_WORDS];-- /* Setting up S-Boxes and Subkeys */- G3P_Blowfish_initstate(&state);- G3P_Blowfish_expandstate(&state,- (const uint8_t *) salt, saltbytes,- (const uint8_t *) key, keybytes);-- /* Written so that things work when rounds == UINT32_MAX */- rounds++;- do {- G3P_Blowfish_expand0state(&state, (const uint8_t *) key, keybytes);- G3P_Blowfish_expand0state(&state, (const uint8_t *) salt, saltbytes);- rounds--;- } while (rounds != 0);-- uint16_t j = 0;- for (uint32_t i = 0; i < BCRYPT_WORDS; i++)- cdata[i] = G3P_Blowfish_stream2word(ciphertext, 4 * BCRYPT_WORDS, &j);-- /* Now do the encryption */- for (uint32_t k = 0; k < 64; k++)- G3P_blf_enc(&state, cdata, BCRYPT_WORDS / 2);-- for (uint32_t i = 0; i < BCRYPT_WORDS; i++) {- ciphertext[4 * i + 3] = cdata[i] & 0xff;- cdata[i] = cdata[i] >> 8;- ciphertext[4 * i + 2] = cdata[i] & 0xff;- cdata[i] = cdata[i] >> 8;- ciphertext[4 * i + 1] = cdata[i] & 0xff;- cdata[i] = cdata[i] >> 8;- ciphertext[4 * i + 0] = cdata[i] & 0xff;- }-- memcpy(output, ciphertext, BCRYPT_RAW_OUTPUT_LENGTH);- explicit_bzero(&state, sizeof(state));- explicit_bzero(ciphertext, sizeof(ciphertext));- explicit_bzero(cdata, sizeof(cdata));-}
− csrc/bcrypt_raw.h
@@ -1,12 +0,0 @@-#pragma once--#include <stdint.h>--#define BCRYPT_RAW_MAX_INPUT_LENGTH 72-#define BCRYPT_RAW_OUTPUT_LENGTH 24--void-bcrypt_raw ( const char *key, uint32_t keybytes,- const char *salt, uint32_t saltbytes,- char output[BCRYPT_RAW_OUTPUT_LENGTH],- uint32_t rounds);
− csrc/blowfish.c
@@ -1,627 +0,0 @@-/* g3p-hash global password prehash protocol, code adapted from: */--/* $OpenBSD: blowfish.c,v 1.21 2022/08/28 11:11:25 jsg Exp $ */-/*- * Blowfish block cipher for OpenBSD- * Copyright 1997 Niels Provos <provos@physnet.uni-hamburg.de>- * All rights reserved.- *- * Implementation advice by David Mazieres <dm@lcs.mit.edu>.- *- * Redistribution and use in source and binary forms, with or without- * modification, are permitted provided that the following conditions- * are met:- * 1. Redistributions of source code must retain the above copyright- * notice, this list of conditions and the following disclaimer.- * 2. Redistributions in binary form must reproduce the above copyright- * notice, this list of conditions and the following disclaimer in the- * documentation and/or other materials provided with the distribution.- * 3. The name of the author may not be used to endorse or promote products- * derived from this software without specific prior written permission.- *- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.- */--/*- * This code is derived from section 14.3 and the given source- * in section V of Applied Cryptography, second edition.- * Blowfish is an unpatented fast block cipher designed by- * Bruce Schneier.- */--#include "g3p_blf.h"--/* Function for Feistel Networks */--#define F(s, x) ((((s)[ (((x)>>24)&0xFF)] \- + (s)[0x100 + (((x)>>16)&0xFF)]) \- ^ (s)[0x200 + (((x)>> 8)&0xFF)]) \- + (s)[0x300 + ( (x) &0xFF)])--#define BLFRND(s,p,i,j,n) (i ^= F(s,j) ^ (p)[n])--void-G3P_Blowfish_encipher(G3P_blf_ctx *c, uint32_t *xl, uint32_t *xr)-{- uint32_t Xl;- uint32_t Xr;- uint32_t *s = c->S[0];- uint32_t *p = c->P;-- Xl = *xl;- Xr = *xr;-- Xl ^= p[0];- BLFRND(s, p, Xr, Xl, 1); BLFRND(s, p, Xl, Xr, 2);- BLFRND(s, p, Xr, Xl, 3); BLFRND(s, p, Xl, Xr, 4);- BLFRND(s, p, Xr, Xl, 5); BLFRND(s, p, Xl, Xr, 6);- BLFRND(s, p, Xr, Xl, 7); BLFRND(s, p, Xl, Xr, 8);- BLFRND(s, p, Xr, Xl, 9); BLFRND(s, p, Xl, Xr, 10);- BLFRND(s, p, Xr, Xl, 11); BLFRND(s, p, Xl, Xr, 12);- BLFRND(s, p, Xr, Xl, 13); BLFRND(s, p, Xl, Xr, 14);- BLFRND(s, p, Xr, Xl, 15); BLFRND(s, p, Xl, Xr, 16);-- *xl = Xr ^ p[17];- *xr = Xl;-};--void-G3P_Blowfish_decipher(G3P_blf_ctx *c, uint32_t *xl, uint32_t *xr)-{- uint32_t Xl;- uint32_t Xr;- uint32_t *s = c->S[0];- uint32_t *p = c->P;-- Xl = *xl;- Xr = *xr;-- Xl ^= p[17];- BLFRND(s, p, Xr, Xl, 16); BLFRND(s, p, Xl, Xr, 15);- BLFRND(s, p, Xr, Xl, 14); BLFRND(s, p, Xl, Xr, 13);- BLFRND(s, p, Xr, Xl, 12); BLFRND(s, p, Xl, Xr, 11);- BLFRND(s, p, Xr, Xl, 10); BLFRND(s, p, Xl, Xr, 9);- BLFRND(s, p, Xr, Xl, 8); BLFRND(s, p, Xl, Xr, 7);- BLFRND(s, p, Xr, Xl, 6); BLFRND(s, p, Xl, Xr, 5);- BLFRND(s, p, Xr, Xl, 4); BLFRND(s, p, Xl, Xr, 3);- BLFRND(s, p, Xr, Xl, 2); BLFRND(s, p, Xl, Xr, 1);-- *xl = Xr ^ p[0];- *xr = Xl;-};--void-G3P_Blowfish_initstate(G3P_blf_ctx *c)-{- /* P-box and S-box tables initialized with digits of Pi */-- static const G3P_blf_ctx initstate =- { {- {- 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,- 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,- 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,- 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,- 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,- 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,- 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,- 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,- 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,- 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,- 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,- 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,- 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,- 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,- 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,- 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,- 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,- 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,- 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,- 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,- 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,- 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,- 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,- 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,- 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,- 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,- 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,- 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,- 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,- 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,- 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,- 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,- 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,- 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,- 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,- 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,- 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,- 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,- 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,- 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,- 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,- 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,- 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,- 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,- 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,- 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,- 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,- 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,- 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,- 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,- 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,- 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,- 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,- 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,- 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,- 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,- 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,- 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,- 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,- 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,- 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,- 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,- 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,- 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a},- {- 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,- 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,- 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,- 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,- 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,- 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,- 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,- 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,- 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,- 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,- 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,- 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,- 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,- 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,- 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,- 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,- 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,- 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,- 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,- 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,- 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,- 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,- 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,- 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,- 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,- 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,- 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,- 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,- 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,- 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,- 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,- 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,- 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,- 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,- 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,- 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,- 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,- 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,- 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,- 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,- 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,- 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,- 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,- 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,- 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,- 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,- 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,- 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,- 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,- 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,- 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,- 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,- 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,- 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,- 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,- 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,- 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,- 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,- 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,- 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,- 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,- 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,- 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,- 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7},- {- 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,- 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,- 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,- 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,- 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,- 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,- 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,- 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,- 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,- 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,- 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,- 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,- 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,- 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,- 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,- 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,- 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,- 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,- 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,- 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,- 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,- 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,- 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,- 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,- 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,- 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,- 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,- 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,- 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,- 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,- 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,- 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,- 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,- 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,- 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,- 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,- 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,- 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,- 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,- 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,- 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,- 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,- 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,- 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,- 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,- 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,- 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,- 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,- 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,- 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,- 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,- 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,- 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,- 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,- 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,- 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,- 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,- 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,- 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,- 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,- 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,- 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,- 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,- 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0},- {- 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,- 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,- 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,- 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,- 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,- 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,- 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,- 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,- 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,- 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,- 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,- 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,- 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,- 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,- 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,- 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,- 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,- 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,- 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,- 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,- 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,- 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,- 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,- 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,- 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,- 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,- 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,- 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,- 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,- 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,- 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,- 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,- 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,- 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,- 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,- 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,- 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,- 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,- 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,- 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,- 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,- 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,- 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,- 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,- 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,- 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,- 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,- 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,- 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,- 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,- 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,- 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,- 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,- 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,- 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,- 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,- 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,- 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,- 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,- 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,- 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,- 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,- 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,- 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6}- },- {- 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,- 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,- 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,- 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,- 0x9216d5d9, 0x8979fb1b- } };-- *c = initstate;-};--uint32_t-G3P_Blowfish_stream2word(const uint8_t *data, uint16_t databytes,- uint16_t *current)-{- uint8_t i;- uint16_t j;- uint32_t temp;-- temp = 0x00000000;- j = *current;-- for (i = 0; i < 4; i++, j++) {- if (j >= databytes)- j = 0;- temp = (temp << 8) | data[j];- }-- *current = j;- return temp;-};--void-G3P_Blowfish_expand0state(G3P_blf_ctx *c, const uint8_t *key, uint16_t keybytes)-{- uint16_t i;- uint16_t j;- uint16_t k;- uint32_t temp;- uint32_t datal;- uint32_t datar;-- j = 0;- for (i = 0; i < G3P_BLF_N + 2; i++) {- /* Extract 4 int8 to 1 int32 from keystream */- temp = G3P_Blowfish_stream2word(key, keybytes, &j);- c->P[i] = c->P[i] ^ temp;- }-- j = 0;- datal = 0x00000000;- datar = 0x00000000;- for (i = 0; i < G3P_BLF_N + 2; i += 2) {- G3P_Blowfish_encipher(c, &datal, &datar);-- c->P[i] = datal;- c->P[i + 1] = datar;- }-- for (i = 0; i < 4; i++) {- for (k = 0; k < 256; k += 2) {- G3P_Blowfish_encipher(c, &datal, &datar);-- c->S[i][k] = datal;- c->S[i][k + 1] = datar;- }- }-};---void-G3P_Blowfish_expandstate(G3P_blf_ctx *c, const uint8_t *data, uint16_t databytes,- const uint8_t *key, uint16_t keybytes)-{- uint16_t i;- uint16_t j;- uint16_t k;- uint32_t temp;- uint32_t datal;- uint32_t datar;-- j = 0;- for (i = 0; i < G3P_BLF_N + 2; i++) {- /* Extract 4 int8 to 1 int32 from keystream */- temp = G3P_Blowfish_stream2word(key, keybytes, &j);- c->P[i] = c->P[i] ^ temp;- }-- j = 0;- datal = 0x00000000;- datar = 0x00000000;- for (i = 0; i < G3P_BLF_N + 2; i += 2) {- datal ^= G3P_Blowfish_stream2word(data, databytes, &j);- datar ^= G3P_Blowfish_stream2word(data, databytes, &j);- G3P_Blowfish_encipher(c, &datal, &datar);-- c->P[i] = datal;- c->P[i + 1] = datar;- }-- for (i = 0; i < 4; i++) {- for (k = 0; k < 256; k += 2) {- datal ^= G3P_Blowfish_stream2word(data, databytes, &j);- datar ^= G3P_Blowfish_stream2word(data, databytes, &j);- G3P_Blowfish_encipher(c, &datal, &datar);-- c->S[i][k] = datal;- c->S[i][k + 1] = datar;- }- }--};--void-G3P_blf_key(G3P_blf_ctx *c, const uint8_t *k, uint16_t len)-{- /* Initialize S-boxes and subkeys with Pi */- G3P_Blowfish_initstate(c);-- /* Transform S-boxes and subkeys with key */- G3P_Blowfish_expand0state(c, k, len);-};--void-G3P_blf_enc(G3P_blf_ctx *c, uint32_t *data, uint16_t blocks)-{- uint32_t *d;- uint16_t i;-- d = data;- for (i = 0; i < blocks; i++) {- G3P_Blowfish_encipher(c, d, d + 1);- d += 2;- }-};--void-G3P_blf_dec(G3P_blf_ctx *c, uint32_t *data, uint16_t blocks)-{- uint32_t *d;- uint16_t i;-- d = data;- for (i = 0; i < blocks; i++) {- G3P_Blowfish_decipher(c, d, d + 1);- d += 2;- }-};--void-G3P_blf_ecb_encrypt(G3P_blf_ctx *c, uint8_t *data, uint32_t len)-{- uint32_t l, r;- uint32_t i;-- for (i = 0; i < len; i += 8) {- l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];- r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7];- G3P_Blowfish_encipher(c, &l, &r);- data[0] = l >> 24 & 0xff;- data[1] = l >> 16 & 0xff;- data[2] = l >> 8 & 0xff;- data[3] = l & 0xff;- data[4] = r >> 24 & 0xff;- data[5] = r >> 16 & 0xff;- data[6] = r >> 8 & 0xff;- data[7] = r & 0xff;- data += 8;- }-};--void-G3P_blf_ecb_decrypt(G3P_blf_ctx *c, uint8_t *data, uint32_t len)-{- uint32_t l, r;- uint32_t i;-- for (i = 0; i < len; i += 8) {- l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];- r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7];- G3P_Blowfish_decipher(c, &l, &r);- data[0] = l >> 24 & 0xff;- data[1] = l >> 16 & 0xff;- data[2] = l >> 8 & 0xff;- data[3] = l & 0xff;- data[4] = r >> 24 & 0xff;- data[5] = r >> 16 & 0xff;- data[6] = r >> 8 & 0xff;- data[7] = r & 0xff;- data += 8;- }-};--void-G3P_blf_cbc_encrypt(G3P_blf_ctx *c, uint8_t *iv, uint8_t *data, uint32_t len)-{- uint32_t l, r;- uint32_t i, j;-- for (i = 0; i < len; i += 8) {- for (j = 0; j < 8; j++)- data[j] ^= iv[j];- l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];- r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7];- G3P_Blowfish_encipher(c, &l, &r);- data[0] = l >> 24 & 0xff;- data[1] = l >> 16 & 0xff;- data[2] = l >> 8 & 0xff;- data[3] = l & 0xff;- data[4] = r >> 24 & 0xff;- data[5] = r >> 16 & 0xff;- data[6] = r >> 8 & 0xff;- data[7] = r & 0xff;- iv = data;- data += 8;- }-};--void-G3P_blf_cbc_decrypt(G3P_blf_ctx *c, uint8_t *iva, uint8_t *data, uint32_t len)-{- uint32_t l, r;- uint8_t *iv;- uint32_t i, j;-- iv = data + len - 16;- data = data + len - 8;- for (i = len - 8; i >= 8; i -= 8) {- l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];- r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7];- G3P_Blowfish_decipher(c, &l, &r);- data[0] = l >> 24 & 0xff;- data[1] = l >> 16 & 0xff;- data[2] = l >> 8 & 0xff;- data[3] = l & 0xff;- data[4] = r >> 24 & 0xff;- data[5] = r >> 16 & 0xff;- data[6] = r >> 8 & 0xff;- data[7] = r & 0xff;- for (j = 0; j < 8; j++)- data[j] ^= iv[j];- iv -= 8;- data -= 8;- }- l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];- r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7];- G3P_Blowfish_decipher(c, &l, &r);- data[0] = l >> 24 & 0xff;- data[1] = l >> 16 & 0xff;- data[2] = l >> 8 & 0xff;- data[3] = l & 0xff;- data[4] = r >> 24 & 0xff;- data[5] = r >> 16 & 0xff;- data[6] = r >> 8 & 0xff;- data[7] = r & 0xff;- for (j = 0; j < 8; j++)- data[j] ^= iva[j];-};
+ csrc/g3p_bcrypt.c view
@@ -0,0 +1,845 @@+/* aggressively generalized and stripped down version of OpenBSD's implementation of BCrypt */+/* g3p-hash global password prehash protocol, code adapted from: */++/* $OpenBSD: blowfish.c,v 1.21 2022/08/28 11:11:25 jsg Exp $ */+/*+ * Blowfish block cipher for OpenBSD+ * Copyright 1997 Niels Provos <provos@physnet.uni-hamburg.de>+ * All rights reserved.+ *+ * Implementation advice by David Mazieres <dm@lcs.mit.edu>.+ *+ * Redistribution and use in source and binary forms, with or without+ * modification, are permitted provided that the following conditions+ * are met:+ * 1. Redistributions of source code must retain the above copyright+ * notice, this list of conditions and the following disclaimer.+ * 2. Redistributions in binary form must reproduce the above copyright+ * notice, this list of conditions and the following disclaimer in the+ * documentation and/or other materials provided with the distribution.+ * 3. The name of the author may not be used to endorse or promote products+ * derived from this software without specific prior written permission.+ *+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+ */++/*+ * This code is derived from section 14.3 and the given source+ * in section V of Applied Cryptography, second edition.+ * Blowfish is an unpatented fast block cipher designed by+ * Bruce Schneier.+ */++#include <assert.h>+#include <stdbool.h>+#include <stddef.h>+#include <string.h>+#include "g3p_bcrypt.h"++/* Function for Feistel Networks */++#define F(s, x) ((((s)[ (((x)>>24)&0xFF)] \+ + (s)[0x100 + (((x)>>16)&0xFF)]) \+ ^ (s)[0x200 + (((x)>> 8)&0xFF)]) \+ + (s)[0x300 + ( (x) &0xFF)])++#define BLFRND(s,p,i,j,n) (i ^= F(s,j) ^ (p)[n])++void+G3P_Blowfish_encipher(const G3P_blf_ctx *c, uint32_t *xl, uint32_t *xr)+{+ uint32_t Xl;+ uint32_t Xr;+ const uint32_t *s = c->S[0];+ const uint32_t *p = c->P;++ Xl = *xl;+ Xr = *xr;++ Xl ^= p[0];+ BLFRND(s, p, Xr, Xl, 1); BLFRND(s, p, Xl, Xr, 2);+ BLFRND(s, p, Xr, Xl, 3); BLFRND(s, p, Xl, Xr, 4);+ BLFRND(s, p, Xr, Xl, 5); BLFRND(s, p, Xl, Xr, 6);+ BLFRND(s, p, Xr, Xl, 7); BLFRND(s, p, Xl, Xr, 8);+ BLFRND(s, p, Xr, Xl, 9); BLFRND(s, p, Xl, Xr, 10);+ BLFRND(s, p, Xr, Xl, 11); BLFRND(s, p, Xl, Xr, 12);+ BLFRND(s, p, Xr, Xl, 13); BLFRND(s, p, Xl, Xr, 14);+ BLFRND(s, p, Xr, Xl, 15); BLFRND(s, p, Xl, Xr, 16);++ *xl = Xr ^ p[17];+ *xr = Xl;+};++void+G3P_Blowfish_decipher(const G3P_blf_ctx *c, uint32_t *xl, uint32_t *xr)+{+ uint32_t Xl;+ uint32_t Xr;+ const uint32_t *s = c->S[0];+ const uint32_t *p = c->P;++ Xl = *xl;+ Xr = *xr;++ Xl ^= p[17];+ BLFRND(s, p, Xr, Xl, 16); BLFRND(s, p, Xl, Xr, 15);+ BLFRND(s, p, Xr, Xl, 14); BLFRND(s, p, Xl, Xr, 13);+ BLFRND(s, p, Xr, Xl, 12); BLFRND(s, p, Xl, Xr, 11);+ BLFRND(s, p, Xr, Xl, 10); BLFRND(s, p, Xl, Xr, 9);+ BLFRND(s, p, Xr, Xl, 8); BLFRND(s, p, Xl, Xr, 7);+ BLFRND(s, p, Xr, Xl, 6); BLFRND(s, p, Xl, Xr, 5);+ BLFRND(s, p, Xr, Xl, 4); BLFRND(s, p, Xl, Xr, 3);+ BLFRND(s, p, Xr, Xl, 2); BLFRND(s, p, Xl, Xr, 1);++ *xl = Xr ^ p[0];+ *xr = Xl;+};++/* P-box and S-box tables initialized with digits of Pi */+const G3P_blf_ctx g3p_blf_init =+ { {+ {+ 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,+ 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,+ 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,+ 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,+ 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,+ 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,+ 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,+ 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,+ 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,+ 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,+ 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,+ 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,+ 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,+ 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,+ 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,+ 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,+ 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,+ 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,+ 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,+ 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,+ 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,+ 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,+ 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,+ 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,+ 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,+ 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,+ 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,+ 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,+ 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,+ 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,+ 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,+ 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,+ 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,+ 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,+ 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,+ 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,+ 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,+ 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,+ 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,+ 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,+ 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,+ 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,+ 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,+ 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,+ 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,+ 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,+ 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,+ 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,+ 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,+ 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,+ 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,+ 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,+ 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,+ 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,+ 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,+ 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,+ 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,+ 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,+ 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,+ 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,+ 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,+ 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,+ 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,+ 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a},+ {+ 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,+ 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,+ 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,+ 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,+ 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,+ 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,+ 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,+ 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,+ 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,+ 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,+ 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,+ 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,+ 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,+ 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,+ 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,+ 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,+ 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,+ 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,+ 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,+ 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,+ 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,+ 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,+ 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,+ 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,+ 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,+ 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,+ 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,+ 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,+ 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,+ 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,+ 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,+ 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,+ 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,+ 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,+ 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,+ 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,+ 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,+ 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,+ 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,+ 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,+ 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,+ 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,+ 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,+ 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,+ 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,+ 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,+ 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,+ 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,+ 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,+ 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,+ 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,+ 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,+ 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,+ 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,+ 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,+ 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,+ 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,+ 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,+ 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,+ 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,+ 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,+ 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,+ 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,+ 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7},+ {+ 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,+ 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,+ 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,+ 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,+ 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,+ 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,+ 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,+ 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,+ 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,+ 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,+ 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,+ 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,+ 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,+ 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,+ 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,+ 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,+ 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,+ 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,+ 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,+ 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,+ 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,+ 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,+ 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,+ 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,+ 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,+ 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,+ 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,+ 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,+ 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,+ 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,+ 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,+ 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,+ 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,+ 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,+ 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,+ 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,+ 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,+ 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,+ 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,+ 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,+ 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,+ 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,+ 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,+ 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,+ 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,+ 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,+ 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,+ 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,+ 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,+ 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,+ 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,+ 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,+ 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,+ 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,+ 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,+ 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,+ 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,+ 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,+ 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,+ 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,+ 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,+ 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,+ 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,+ 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0},+ {+ 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,+ 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,+ 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,+ 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,+ 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,+ 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,+ 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,+ 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,+ 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,+ 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,+ 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,+ 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,+ 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,+ 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,+ 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,+ 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,+ 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,+ 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,+ 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,+ 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,+ 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,+ 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,+ 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,+ 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,+ 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,+ 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,+ 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,+ 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,+ 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,+ 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,+ 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,+ 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,+ 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,+ 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,+ 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,+ 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,+ 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,+ 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,+ 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,+ 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,+ 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,+ 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,+ 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,+ 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,+ 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,+ 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,+ 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,+ 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,+ 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,+ 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,+ 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,+ 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,+ 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,+ 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,+ 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,+ 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,+ 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,+ 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,+ 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,+ 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,+ 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,+ 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,+ 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,+ 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6}+ },+ {+ 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,+ 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,+ 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,+ 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,+ 0x9216d5d9, 0x8979fb1b+ } };++static inline+uint32_t+G3P_cycle(const uint8_t *const data, const uint32_t len, uint32_t *const pos)+{+ if (data == NULL || len == 0 || pos == NULL) return 0;+ uint32_t x, j;++ x = 0;+ j = *pos;++ for (int i = 0; i < 4; i++) {+ x = (x << 8) | data[j];+ if (++j >= len) j = 0;+ }++ *pos = j;+ return x;+};++static inline+uint32_t+G3P_once(const uint8_t *const data, const uint32_t len, uint32_t *const pos)+{+ if (data == NULL || len == 0 || pos == NULL) return 0;+ uint32_t x, j;++ x = 0;+ j = *pos;++ for (int i = 0; i < 4; i++) {+ if (j < len) {+ x = (x << 8) | data[j++];+ } else {+ x <<= 8;+ }+ }++ *pos = j;+ return x;+};++static inline+uint32_t+G3P_cycleWx00(const uint8_t *data, uint32_t len, uint32_t *current)+{+ if (data == NULL || len == 0 || current == NULL) return 0;++ uint32_t x,j;+ x = 0x00000000;+ j = *current;++ for (int i = 0; i < 4; i++, j++) {+ if (j >= len) {+ if (j == len) {+ x <<= 8;+ continue;+ } else {+ j = 0;+ }+ }+ x = (x << 8) | data[j];+ }++ *current = j;+ return x;+};++static inline+uint32_t+G3P_onceThenCycleWx00+(const uint8_t *const a, const uint32_t al, uint32_t *const restrict ap,+ const uint8_t *const b, const uint32_t bl, uint32_t *const restrict bp) {+ const uint32_t apos = *ap;+ if (apos >= al)+ return G3P_cycleWx00(b,bl,bp);+ return G3P_once(a,al,ap);+}++static inline+uint32_t+G3P_cycleWx00ThenOnce+(uint32_t *const restrict np,+ const uint8_t *const a, const uint32_t al, uint32_t *const restrict ap,+ const uint8_t *const b, const uint32_t bl, uint32_t *const restrict bp) {+ const uint32_t n = *np;+ if (n == 0)+ return G3P_once(b,bl,bp);+ assert(n >= 4);+ *np = n - 4;+ return G3P_cycleWx00(a,al,ap);+}++void+G3P_bcrypt_xs+( const uint8_t *key0, uint16_t key0bytes, const uint8_t *salt0, uint16_t salt0bytes,+ const uint8_t *keyL, uint16_t keyLbytes, const uint8_t *saltL, uint16_t saltLbytes,+ const uint8_t *keyR, uint16_t keyRbytes, const uint8_t *saltR, uint16_t saltRbytes,+ const uint8_t *saltZ, uint32_t saltZbytes, uint32_t rounds, bool implicitNull,+ uint8_t *output )+{+ G3P_blf_ctx state;++ memcpy(&state, &g3p_blf_init, sizeof(state));++ G3P_bcrypt_xs_expand+ (&state,+ key0, key0bytes, salt0, salt0bytes,+ keyL, keyLbytes, saltL, saltLbytes,+ keyR, keyRbytes, saltR, saltRbytes,+ rounds, implicitNull );++ G3P_bcrypt_xs_output (&state, saltZ, saltZbytes, output);++ explicit_bzero(&state, sizeof(state));+}++void+G3P_bcrypt_xs_expand+( G3P_blf_ctx *state,+ const uint8_t *key0, uint16_t key0bytes, const uint8_t *salt0, uint16_t salt0bytes,+ const uint8_t *keyL, uint16_t keyLbytes, const uint8_t *saltL, uint16_t saltLbytes,+ const uint8_t *keyR, uint16_t keyRbytes, const uint8_t *saltR, uint16_t saltRbytes,+ uint32_t rounds, bool implicitNull )+{+ G3P_Blowfish_expand+ (state,+ (const uint8_t *) key0, key0bytes,+ (const uint8_t *) salt0, salt0bytes, implicitNull);++ /* Written so that things work when rounds == UINT32_MAX */+ rounds++;+ do {+ rounds--;+ G3P_Blowfish_expand+ (state,+ (const uint8_t *) keyL, keyLbytes,+ (const uint8_t *) saltL, saltLbytes, implicitNull);+ G3P_Blowfish_expand+ (state,+ (const uint8_t *) keyR, keyRbytes,+ (const uint8_t *) saltR, saltRbytes, false );+ } while (rounds != 0);+}++void+G3P_Blowfish_expand+(G3P_blf_ctx *c,+ const uint8_t *key, uint16_t keybytes,+ const uint8_t *salt, uint16_t saltbytes,+ bool implicitNull )+{+ uint32_t pos;+ uint32_t datal;+ uint32_t datar;++ pos = 0;+ for (int i = 0; i < G3P_BLF_N + 2; i++) {+ if (implicitNull) {+ c->P[i] ^= G3P_cycleWx00(key, keybytes, &pos);+ } else {+ c->P[i] ^= G3P_cycle(key, keybytes, &pos);+ }+ }++ pos = 0;+ datal = G3P_cycle(salt, saltbytes, &pos);+ datar = G3P_cycle(salt, saltbytes, &pos);+ G3P_Blowfish_encipher(c, &datal, &datar);+ c->P[0] = datal;+ c->P[1] = datar;++ for (int i = 2; i < G3P_BLF_N + 2; i += 2) {+ datal ^= G3P_cycle(salt, saltbytes, &pos);+ datar ^= G3P_cycle(salt, saltbytes, &pos);+ G3P_Blowfish_encipher(c, &datal, &datar);++ c->P[i] = datal;+ c->P[i + 1] = datar;+ }++ for (int i = 0; i < 4; i++) {+ for (int k = 0; k < 256; k += 2) {+ datal ^= G3P_cycle(salt, saltbytes, &pos);+ datar ^= G3P_cycle(salt, saltbytes, &pos);+ G3P_Blowfish_encipher(c, &datal, &datar);++ c->S[i][k] = datal;+ c->S[i][k + 1] = datar;+ }+ }+};++// Re-implementation of ECB-mode * 64 encryption+void+G3P_bcrypt_xs_output+( const G3P_blf_ctx *state,+ const uint8_t *saltZ, uint32_t saltZbytes,+ uint8_t *output )+{+ uint32_t blocks = saltZbytes >> 3;+ uint32_t datal, datar;+ for(uint32_t i = 0; i < blocks; i++) {+ datal+ = (uint32_t)saltZ[8*i ] << 24+ | (uint32_t)saltZ[8*i + 1] << 16+ | (uint32_t)saltZ[8*i + 2] << 8+ | (uint32_t)saltZ[8*i + 3];+ datar+ = (uint32_t)saltZ[8*i + 4] << 24+ | (uint32_t)saltZ[8*i + 5] << 16+ | (uint32_t)saltZ[8*i + 6] << 8+ | (uint32_t)saltZ[8*i + 7];+ for(int j = 0; j < 64; j++) {+ G3P_Blowfish_encipher(state, &datal, &datar);+ }+ output[8*i ] = (uint8_t)((datal >> 24) & 0xff);+ output[8*i + 1] = (uint8_t)((datal >> 16) & 0xff);+ output[8*i + 2] = (uint8_t)((datal >> 8) & 0xff);+ output[8*i + 3] = (uint8_t)( datal & 0xff);+ output[8*i + 4] = (uint8_t)((datar >> 24) & 0xff);+ output[8*i + 5] = (uint8_t)((datar >> 16) & 0xff);+ output[8*i + 6] = (uint8_t)((datar >> 8) & 0xff);+ output[8*i + 7] = (uint8_t)( datar & 0xff);+ }+ int bytes = saltZbytes & 7;+ if (bytes > 0) {+ datal = 0;+ datar = 0;++ for(int i = 0; i < 4 && i < bytes; i++)+ datal |= (uint32_t)saltZ[8*blocks + i] << (24 - 8*i);+ for(int i = 4; i < bytes; i++)+ datar |= (uint32_t)saltZ[8*blocks + i] << (56 - 8*i);++ for(int i = 0; i < 64; i++)+ G3P_Blowfish_encipher(state, &datal, &datar);++ for(int i = 0; i < 4 && i < bytes; i++)+ output[8*blocks + i] = (uint8_t)((datal >> (24 - 8*i)) && 0xff);+ for(int i = 4; i < bytes; i++)+ output[8*blocks + i] = (uint8_t)((datar >> (56 - 8*i)) && 0xff);+ }+ explicit_bzero(&datal, sizeof(datal));+ explicit_bzero(&datar, sizeof(datar));+}++uint32_t+G3P_bcrypt_xs_ctr_superround+( const uint8_t input[G3P_BLF_CTX_LENGTH],+ const uint8_t *key0, uint32_t len0, const uint8_t *key1, uint32_t len1,+ const uint8_t *name, uint32_t nameLen, const uint8_t *tag, uint32_t tagLen,+ uint32_t tagPos, uint32_t rounds, uint32_t ctr, uint8_t output[G3P_BLF_CTX_LENGTH] )+{+ G3P_blf_ctx state;++ if (input == NULL)+ memcpy(&state, &g3p_blf_init, sizeof(state));+ else+ G3P_Blowfish_decodestate(input, &state);++ tagPos = G3P_bcrypt_xs_ctr_expand+ (&state,+ key0, len0, key1, len1,+ name, nameLen, tag, tagLen,+ tagPos, rounds, ctr);++ G3P_Blowfish_encodestate(&state, output);++ explicit_bzero(&state, sizeof(state));+ return tagPos;+}+++/* bcrypt-xs-ctr (the idealized function, not this implementation) makes the+ tacit assumptions that:+ 1. len0 <= 72+ 2. len1 <= 72+ 3. len0 == len1 == nameLen+ 4. The first four bytes of "name" are \x00+ The behavior of this implementation should be considered to be undefined if+ any of these assumptions are violated. These conditions imply that:++ 4 <= len0 == len1 == nameLen <= 72++ Honestly, I would recommend a much bigger minimum length for any serious+ deployment. The G3P uses length == 32.++ A more traditional set of names for these parameters would be+ "salt" instead of "key", and "password" instead of "tag".+ */++uint32_t+G3P_bcrypt_xs_ctr_expand+( G3P_blf_ctx *state,+ const uint8_t *key0, uint32_t len0, const uint8_t *key1, uint32_t len1,+ const uint8_t *name, uint32_t nameLen, const uint8_t *tag, uint32_t tagLen,+ uint32_t tagPos, uint32_t rounds, uint32_t ctr)+{+ G3P_Blowfish_expandCtr+ (state, key0, len0, key1, len1, tag, tagLen, tagPos, 0, false);++ while(rounds > 0) {+ G3P_Blowfish_expandCtr+ (state, key0, len0, name, nameLen, tag, tagLen, tagPos, ctr, true);+ tagPos = G3P_Blowfish_expandCtr+ (state, key1, len1, name, nameLen, tag, tagLen, tagPos, ~ctr, true);+ rounds--;+ ctr--;+ }++ if (len1 > 72) len1 = 72;+ // divide by 4, rounding up+ uint32_t words = (len1 + 3) >> 2;+ uint32_t pos = 0;+ for (uint32_t i = 0; i < words; i++) {+ state->P[i] ^= G3P_once (key1, len1, &pos);+ }++ return tagPos;+}++uint32_t+G3P_Blowfish_expandCtr+( G3P_blf_ctx *const c,+ const uint8_t *key, const uint32_t keyLen,+ const uint8_t *name, const uint32_t nameLen,+ const uint8_t *tag, const uint32_t tagLen, const uint32_t tagPos0,+ const uint32_t ctr, const bool keyIsFirst )+{+ if (c == NULL) return tagPos0;++ assert(keyLen <= 72);+ assert(keyLen == nameLen); // FIXME: rework interface+ assert(keyLen % 4 == 0); // FIXME? remove this assumption++ uint32_t pos = 0;+ uint32_t tagPos;+ if (tagLen < 4096) {+ tagPos = 0;+ if (keyIsFirst) {+ int i;+ for (i = 0; i < keyLen; i += 4) {+ c->P[i >> 2] ^= G3P_once(key, keyLen, &pos);+ }+ for (; i < 72; i += 4) {+ c->P[i >> 2] ^= G3P_once(tag, tagLen, &tagPos);+ }+ } else {+ int i;+ for (i = 0; i < (72 - keyLen); i += 4) {+ c->P[i >> 2] ^= G3P_once(tag, tagLen, &tagPos);+ }+ for (; i < 72; i += 4) {+ c->P[i >> 2] ^= G3P_once(key, keyLen, &pos);+ }+ }++ for (int i = 0; i < 4; i++) {+ for (int k = 0; k < 256; k++) {+ if (tagPos >= tagLen) goto end;+ c->S[i][k] ^= G3P_once(tag, tagLen, &tagPos);+ }+ }++ end: (void)0;+ } else {+ tagPos = tagPos0;+ if (keyIsFirst) {+ for (int i = 0; i < 18; i++) {+ c->P[i] ^= G3P_onceThenCycleWx00(key, keyLen, &pos, tag, tagLen, &tagPos);+ }+ } else {+ uint32_t n = 72 - keyLen;+ for (int i = 0; i < 18; i++) {+ c->P[i] ^= G3P_cycleWx00ThenOnce(&n, tag, tagLen, &tagPos, key, keyLen, &pos);+ }+ }++ for (int i = 0; i < 4; i++) {+ for (int k = 0; k < 256; k++) {+ c->S[i][k] ^= G3P_cycleWx00(tag, tagLen, &tagPos);+ }+ }+ }++ tagPos = tagPos0;+ pos = 0;++ uint32_t datal = G3P_onceThenCycleWx00(name, nameLen, &pos, tag, tagLen, &tagPos) ^ ctr;+ uint32_t datar = G3P_onceThenCycleWx00(name, nameLen, &pos, tag, tagLen, &tagPos);+ G3P_Blowfish_encipher(c, &datal, &datar);+ c->P[0] = datal;+ c->P[1] = datar;++ for (int i = 2; i < 18; i += 2) {+ datal ^= G3P_onceThenCycleWx00(name, nameLen, &pos, tag, tagLen, &tagPos);+ datar ^= G3P_onceThenCycleWx00(name, nameLen, &pos, tag, tagLen, &tagPos);+ G3P_Blowfish_encipher(c, &datal, &datar);++ c->P[i] = datal;+ c->P[i + 1] = datar;+ }++ for (int i = 0; i < 4; i++) {+ for (int k = 0; k < 256; k += 2) {+ datal ^= G3P_cycleWx00(tag, tagLen, &tagPos);+ datar ^= G3P_cycleWx00(tag, tagLen, &tagPos);+ G3P_Blowfish_encipher(c, &datal, &datar);++ c->S[i][k] = datal;+ c->S[i][k + 1] = datar;+ }+ }+ return tagPos;+};++void+G3P_Blowfish_encodestate+(const G3P_blf_ctx *c,+ uint8_t out[G3P_BLF_CTX_LENGTH])+{+ uint32_t p = 0;+ for (int i = 0; i < G3P_BLF_N + 2; i++) {+ uint32_t x = c->P[i];+ out[p++] = (x >> 24) & 0xff;+ out[p++] = (x >> 16) & 0xff;+ out[p++] = (x >> 8) & 0xff;+ out[p++] = x & 0xff;+ }+ for (int i = 0; i < 4; i++) {+ for (int j = 0; j < 256; j++) {+ uint32_t x = c->S[i][j];+ out[p++] = (x >> 24) & 0xff;+ out[p++] = (x >> 16) & 0xff;+ out[p++] = (x >> 8) & 0xff;+ out[p++] = x & 0xff;+ }+ }+ assert (p == G3P_BLF_CTX_LENGTH);+}++void+G3P_Blowfish_decodestate+(const uint8_t in[G3P_BLF_CTX_LENGTH],+ G3P_blf_ctx *c)+{+ uint32_t p = 0;+ for (int i = 0; i < G3P_BLF_N + 2; i++) {+ uint32_t x = 0;+ x |= ((uint32_t)in[p++]) << 24;+ x |= ((uint32_t)in[p++]) << 16;+ x |= ((uint32_t)in[p++]) << 8;+ x |= ((uint32_t)in[p++]);+ c->P[i] = x;+ }+ for (int i = 0; i < 4; i++) {+ for (int j = 0; j < 256; j++) {+ uint32_t x = 0;+ x |= ((uint32_t)in[p++]) << 24;+ x |= ((uint32_t)in[p++]) << 16;+ x |= ((uint32_t)in[p++]) << 8;+ x |= ((uint32_t)in[p++]);+ c->S[i][j] = x;+ }+ }+ assert (p == G3P_BLF_CTX_LENGTH);+}
+ csrc/g3p_bcrypt.h view
@@ -0,0 +1,124 @@+#pragma once+/* $OpenBSD: blf.h,v 1.8 2021/11/29 01:04:45 djm Exp $ */+/*+ * Blowfish - a fast block cipher designed by Bruce Schneier+ *+ * Copyright 1997 Niels Provos <provos@physnet.uni-hamburg.de>+ * All rights reserved.+ *+ * Redistribution and use in source and binary forms, with or without+ * modification, are permitted provided that the following conditions+ * are met:+ * 1. Redistributions of source code must retain the above copyright+ * notice, this list of conditions and the following disclaimer.+ * 2. Redistributions in binary form must reproduce the above copyright+ * notice, this list of conditions and the following disclaimer in the+ * documentation and/or other materials provided with the distribution.+ * 3. The name of the author may not be used to endorse or promote products+ * derived from this software without specific prior written permission.+ *+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+ */++/* Schneier specifies a maximum key length of 56 bytes.+ * This ensures that every key bit affects every cipher+ * bit. However, the subkeys can hold up to 72 bytes.+ * Warning: For normal blowfish encryption only 56 bytes+ * of the key affect all cipherbits.+ */++#include <stdbool.h>+#include <stdint.h>++#define G3P_BLF_N 16 /* Number of Subkeys */+#define G3P_BLF_MAXKEYLEN ((G3P_BLF_N-2)*4) /* 448 bits */+#define G3P_BLF_MAXUTILIZED ((G3P_BLF_N+2)*4) /* 576 bits */+#define G3P_BLF_CTX_LENGTH 4168+#define G3P_SALT_BYTES_PER_ROUND (G3P_BLF_CTX_LENGTH - 32)++/* Blowfish context */+typedef struct BlowfishContext {+ uint32_t S[4][256]; /* S-Boxes */+ uint32_t P[G3P_BLF_N + 2]; /* Subkeys */+} G3P_blf_ctx;++/* Raw access to customized Blowfish+ * G3P_blf_key is just:+ * G3P_Blowfish_initstate( state )+ * G3P_Blowfish_expand0state( state, key, keylen )+ */++void G3P_Blowfish_encipher(const G3P_blf_ctx *, uint32_t *, uint32_t *);+void G3P_Blowfish_decipher(const G3P_blf_ctx *, uint32_t *, uint32_t *);+void G3P_Blowfish_initstate(G3P_blf_ctx *);+void G3P_Blowfish_expand(G3P_blf_ctx *c,+ const uint8_t *key, uint16_t keybytes,+ const uint8_t *salt, uint16_t saltbytes,+ bool implicitNull);+uint32_t+G3P_Blowfish_expandCtr+( G3P_blf_ctx *const c,+ const uint8_t *const key, const uint32_t keyLen,+ const uint8_t *const name, const uint32_t nameLen,+ const uint8_t *const tag, const uint32_t tagLen, const uint32_t tagPos,+ const uint32_t ctr, const bool keyIsFirst );++void G3P_Blowfish_encodestate(const G3P_blf_ctx *c, uint8_t out[G3P_BLF_CTX_LENGTH]);+void G3P_Blowfish_decodestate(const uint8_t in[G3P_BLF_CTX_LENGTH], G3P_blf_ctx *c);++/*+uint32_t+G3P_thenCycle+(const uint8_t *const a, const uint32_t al, uint32_t *const restrict ap,+ const uint8_t *const b, const uint32_t bl, uint32_t *const restrict bp);+*/++extern const G3P_blf_ctx g3p_blf_init;++#define BCRYPT_XS_MAX_KEY_LENGTH 72+#define BCRYPT_XS_MAX_SALT_LENGTH G3P_BLF_CTX_LENGTH // i.e. 4168++void+G3P_bcrypt_xs+( const uint8_t *key0, uint16_t key0bytes, const uint8_t *salt0, uint16_t salt0bytes,+ const uint8_t *keyL, uint16_t keyLbytes, const uint8_t *saltL, uint16_t saltLbytes,+ const uint8_t *keyR, uint16_t keyRbytes, const uint8_t *saltR, uint16_t saltRbytes,+ const uint8_t *saltZ, uint32_t saltZbytes, uint32_t rounds, bool implicitNull,+ uint8_t *output );++void+G3P_bcrypt_xs_expand+( G3P_blf_ctx *state,+ const uint8_t *key0, uint16_t key0bytes, const uint8_t *salt0, uint16_t salt0bytes,+ const uint8_t *keyL, uint16_t keyLbytes, const uint8_t *saltL, uint16_t saltLbytes,+ const uint8_t *keyR, uint16_t keyRbytes, const uint8_t *saltR, uint16_t saltRbytes,+ uint32_t rounds, bool implicitNull );++uint32_t+G3P_bcrypt_xs_ctr_superround+( const uint8_t input[G3P_BLF_CTX_LENGTH],+ const uint8_t *key0, uint32_t len0, const uint8_t *key1, uint32_t len1,+ const uint8_t *name, uint32_t nameLen, const uint8_t *tag, uint32_t tagLen,+ uint32_t tagPos, uint32_t rounds, uint32_t ctr, uint8_t output[G3P_BLF_CTX_LENGTH] );++uint32_t+G3P_bcrypt_xs_ctr_expand+( G3P_blf_ctx *state,+ const uint8_t *key0, uint32_t len0, const uint8_t *key1, uint32_t len1,+ const uint8_t *name, uint32_t nameLen, const uint8_t *tag, uint32_t tagLen,+ uint32_t tagPos, uint32_t rounds, uint32_t ctr);++void+G3P_bcrypt_xs_output+( const G3P_blf_ctx * state,+ const uint8_t *saltZ, uint32_t saltZbytes,+ uint8_t *output );
+ csrc/g3p_bcrypt_base64.c view
@@ -0,0 +1,155 @@+/*+ Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)++ Permission is hereby granted, free of charge, to any person obtaining a copy+ of this software and associated documentation files (the "Software"), to deal+ in the Software without restriction, including without limitation the rights+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+ copies of the Software, and to permit persons to whom the Software is+ furnished to do so, subject to the following conditions:++ The above copyright notice and this permission notice shall be included in all+ copies or substantial portions of the Software.++ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+ SOFTWARE.+*/++#include "g3p_bcrypt_base64.h"+#include <stdint.h>++// ************************+// *** Helper Functions ***+// ************************++// Base64 character set:+// ./ [A-Z] [a-z] [0-9]+// 0x2e-0x2f, 0x41-0x5a, 0x61-0x7a, 0x30-0x39++static inline int base64Decode6BitsDotSlash(uint8_t src)+{+ int ch = (unsigned char) src;+ int ret = -1;++ // if (ch > 0x2d && ch < 0x30) ret += ch - 0x2e + 1; // -45+ ret += (((0x2d - ch) & (ch - 0x30)) >> 8) & (ch - 45);++ // if (ch > 0x40 && ch < 0x5b) ret += ch - 0x41 + 2 + 1; // -62+ ret += (((0x40 - ch) & (ch - 0x5b)) >> 8) & (ch - 62);++ // if (ch > 0x60 && ch < 0x7b) ret += ch - 0x61 + 28 + 1; // -68+ ret += (((0x60 - ch) & (ch - 0x7b)) >> 8) & (ch - 68);++ // if (ch > 0x2f && ch < 0x3a) ret += ch - 0x30 + 54 + 1; // 7+ ret += (((0x2f - ch) & (ch - 0x3a)) >> 8) & (ch + 7);++ return ret;+}++static inline int base64Decode3BytesDotSlash(uint8_t dest[3], const uint8_t src[4])+{+ int c0 = base64Decode6BitsDotSlash(src[0]);+ int c1 = base64Decode6BitsDotSlash(src[1]);+ int c2 = base64Decode6BitsDotSlash(src[2]);+ int c3 = base64Decode6BitsDotSlash(src[3]);++ dest[0] = (uint8_t) ((c0 << 2) | (c1 >> 4));+ dest[1] = (uint8_t) ((c1 << 4) | (c2 >> 2));+ dest[2] = (uint8_t) ((c2 << 6) | c3 );+ return ((c0 | c1 | c2 | c3) >> 8) & 1;+}++static inline uint8_t base64Encode6BitsDotSlash(unsigned int src)+{+ src += 0x2e;++ // if (in > 0x2f) in += 0x41 - 0x30; // 17+ src += ((0x2f - src) >> 8) & 17;++ // if (in > 0x5a) in += 0x61 - 0x5b; // 6+ src += ((0x5a - src) >> 8) & 6;++ // if (in > 0x7a) in += 0x30 - 0x7b; // -75+ src -= ((0x7a - src) >> 8) & 75;++ return (char) src;+}++static inline void base64Encode3BytesDotSlash(uint8_t dest[4], const uint8_t src[3])+{+ unsigned int b0 = src[0];+ unsigned int b1 = src[1];+ unsigned int b2 = src[2];++ dest[0] = base64Encode6BitsDotSlash( b0 >> 2 );+ dest[1] = base64Encode6BitsDotSlash(((b0 << 4) | (b1 >> 4)) & 63);+ dest[2] = base64Encode6BitsDotSlash(((b1 << 2) | (b2 >> 6)) & 63);+ dest[3] = base64Encode6BitsDotSlash( b2 & 63);+}++++// **********************+// *** Main Functions ***+// **********************++// Base64 character set "./[A-Z][a-z][0-9]"+void G3P_bcrypt_base64Encode(char *dest, const void *src, size_t srcLen)+{+ if (dest == NULL || src == NULL || srcLen == 0) return;+ for (; srcLen >= 3; srcLen -= 3) {+ base64Encode3BytesDotSlash((uint8_t *)dest, (const uint8_t*) src);+ dest += 4;+ src = (const uint8_t*) src + 3;+ }+ if (srcLen > 0) {+ uint8_t tmp[3] = {0, 0, 0};+ uint8_t tmpdest[4];++ for (size_t i = 0; i < srcLen; i++) {+ tmp[i] = ((const uint8_t*) src)[i];+ }+ base64Encode3BytesDotSlash(tmpdest, tmp);+ dest[0] = tmpdest[0];+ dest[1] = tmpdest[1];+ if (srcLen == 2) {+ dest[2] = tmpdest[2];+ }+ }+}++int G3P_bcrypt_base64Decode(void *dest, const char *src, size_t srcLen)+{+ if (src == NULL || srcLen == 0) return 0;+ if (dest == NULL) return 1;+ int err = 0;++ for (; srcLen > 4; srcLen -= 4) {+ err |= base64Decode3BytesDotSlash((uint8_t*) dest, (uint8_t*)src);+ dest = (uint8_t*) dest + 3;+ src += 4;+ }+ if (srcLen > 0) {+ size_t i;+ uint8_t tmpOut[3];+ uint8_t tmpIn[4] = {'A', 'A', 'A', 'A'};++ for (i = 0; i < srcLen; i++) {+ tmpIn[i] = src[i];+ }+ if (i < 2) {+ err = 1;+ }+ srcLen = i - 1;+ err |= base64Decode3BytesDotSlash(tmpOut, tmpIn);+ for (i = 0; i < srcLen; i++) {+ ((uint8_t*) dest)[i] = tmpOut[i];+ }+ }+ return err;+}
+ csrc/g3p_bcrypt_base64.h view
@@ -0,0 +1,27 @@+#pragma once+/*+ Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)++ Permission is hereby granted, free of charge, to any person obtaining a copy+ of this software and associated documentation files (the "Software"), to deal+ in the Software without restriction, including without limitation the rights+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+ copies of the Software, and to permit persons to whom the Software is+ furnished to do so, subject to the following conditions:++ The above copyright notice and this permission notice shall be included in all+ copies or substantial portions of the Software.++ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+ SOFTWARE.+*/++#include <stddef.h>++void G3P_bcrypt_base64Encode(char *dest, const void *src, size_t srcLen);+int G3P_bcrypt_base64Decode(void *dest, const char *src, size_t srcLen);
− csrc/g3p_blf.h
@@ -1,77 +0,0 @@-#pragma once-/* $OpenBSD: blf.h,v 1.8 2021/11/29 01:04:45 djm Exp $ */-/*- * Blowfish - a fast block cipher designed by Bruce Schneier- *- * Copyright 1997 Niels Provos <provos@physnet.uni-hamburg.de>- * All rights reserved.- *- * Redistribution and use in source and binary forms, with or without- * modification, are permitted provided that the following conditions- * are met:- * 1. Redistributions of source code must retain the above copyright- * notice, this list of conditions and the following disclaimer.- * 2. Redistributions in binary form must reproduce the above copyright- * notice, this list of conditions and the following disclaimer in the- * documentation and/or other materials provided with the distribution.- * 3. The name of the author may not be used to endorse or promote products- * derived from this software without specific prior written permission.- *- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.- */--/* Schneier specifies a maximum key length of 56 bytes.- * This ensures that every key bit affects every cipher- * bit. However, the subkeys can hold up to 72 bytes.- * Warning: For normal blowfish encryption only 56 bytes- * of the key affect all cipherbits.- */--#include <stdint.h>--#define G3P_BLF_N 16 /* Number of Subkeys */-#define G3P_BLF_MAXKEYLEN ((G3P_BLF_N-2)*4) /* 448 bits */-#define G3P_BLF_MAXUTILIZED ((G3P_BLF_N+2)*4) /* 576 bits */--/* Blowfish context */-typedef struct BlowfishContext {- uint32_t S[4][256]; /* S-Boxes */- uint32_t P[G3P_BLF_N + 2]; /* Subkeys */-} G3P_blf_ctx;--/* Raw access to customized Blowfish- * G3P_blf_key is just:- * G3P_Blowfish_initstate( state )- * G3P_Blowfish_expand0state( state, key, keylen )- */--void G3P_Blowfish_encipher(G3P_blf_ctx *, uint32_t *, uint32_t *);-void G3P_Blowfish_decipher(G3P_blf_ctx *, uint32_t *, uint32_t *);-void G3P_Blowfish_initstate(G3P_blf_ctx *);-void G3P_Blowfish_expand0state(G3P_blf_ctx *, const uint8_t *, uint16_t);-void G3P_Blowfish_expandstate-(G3P_blf_ctx *, const uint8_t *, uint16_t, const uint8_t *, uint16_t);--/* Standard Blowfish */--void G3P_blf_key(G3P_blf_ctx *, const uint8_t *, uint16_t);-void G3P_blf_enc(G3P_blf_ctx *, uint32_t *, uint16_t);-void G3P_blf_dec(G3P_blf_ctx *, uint32_t *, uint16_t);--void G3P_blf_ecb_encrypt(G3P_blf_ctx *, uint8_t *, uint32_t);-void G3P_blf_ecb_decrypt(G3P_blf_ctx *, uint8_t *, uint32_t);--void G3P_blf_cbc_encrypt(G3P_blf_ctx *, uint8_t *, uint8_t *, uint32_t);-void G3P_blf_cbc_decrypt(G3P_blf_ctx *, uint8_t *, uint8_t *, uint32_t);--/* Converts uint8_t to uint32_t */-uint32_t G3P_Blowfish_stream2word(const uint8_t *, uint16_t , uint16_t *);
g3p-hash.cabal view
@@ -1,5 +1,5 @@ name: g3p-hash-version: 1.0.0.2+version: 2.0.0.0 synopsis: Global Password Prehash Protocol description: A password hash and key derivation function that provides embedded attributions in order to support self-documenting@@ -13,41 +13,62 @@ build-type: Simple extra-source-files: ChangeLog.md g3p-test-vectors.json+ g3pb1-test-vectors.json+ test/MyCorpExample.hs cabal-version: >=1.10 library exposed-modules: Crypto.G3P+ Crypto.G3P.V1+ Crypto.G3P.V2+ Crypto.G3P.V2.Foxtrot+ Crypto.G3P.V2.Subtle Crypto.G3P.BCrypt+ Crypto.G3P.BCrypt.Subtle build-depends: base < 5 , bytestring , network-byte-order- , phkdf+ , phkdf >= 0.1 , Stream- , tuplehash-utils+ , tuplehash-utils >= 0.1 , vector hs-source-dirs: lib include-dirs: csrc- c-sources: csrc/bcrypt_raw.c- csrc/blowfish.c- install-includes: csrc/bcrypt_raw.h- csrc/g3p_blf.h+ c-sources: csrc/g3p_bcrypt.c+ csrc/g3p_bcrypt_base64.c+ install-includes: csrc/g3p_bcrypt.h+ csrc/g3p_bcrypt_base64.h ghc-options: -Wall+ cc-options: -Wall default-language: Haskell2010 +source-repository head+ type: git+ location: http://github.com/auth-global/self-documenting-cryptography+ subdir: g3p-hash+ test-suite test type: exitcode-stdio-1.0 hs-source-dirs: test main-is: Main.hs - other-modules: G3P+ other-modules: G3Pb1+ G3Pb2+-- MyCorpExample+ Bcrypt build-depends: base , aeson >= 2- , base16+-- , argon2+ , bcrypt+ , base16 >= 1 , bytestring , containers , g3p-hash+ , hash-string+ , network-byte-order+ , phkdf , Stream , tasty , tasty-hunit
g3p-test-vectors.json view
@@ -9,9 +9,11 @@ "bcrypt-rounds":7 }, "results":[- {"G3Pb1":"d6ad3dd2b82b8f279b39a1c667ed247701a2a93702a37f00e867cc3bb7a3c21123b5545d90ee2e4f196fcc81b74dd8c8ad8ae867c8140b3a90ca34dd7eca01f1"},- {"args":{"seguid":{"hex":"60473b8010e16d464314a11c2620a8ad99af49ae25474f877e57f6c27c58ca7a3538b58385eabbbbb540a350491291c870a4f12c8569485100da96f4202c3630"}},- "G3Pb1":"c721fd9ec639fdb104afbd371d80e5253e330c4a66583dff11aef19d02fb84ec6386e5ea2a5899c9155a5dbe59cf21eda2db8a29d79a2199a287436d3c458f64"}+ {"G3Pb2":"73f26cbf3ad8233b795bf6e4d5d32e7ef6d2be4a34ca07d556c6d97c264526c2f846bb33b22597051be7c0870deb702a89e24d5df4b84048087ac3c32d813bbd"},+ {"args":{+ "echo-tag":"Shaka, when the walls fell.",+ "seguid":{"hex":"6aefea0f165a93ba619f5376980cf11156e42e531776c907b69dca3dca35125d02f2a42d2b10b60b08874e4ec8cbecef21f01b84af0d7cf8d08a113bd7bad5b4"}},+ "G3Pb2":"c2dc7c25398c7c97a9b70dd1578bd32aa9b38e59a955ada46c521f84d177205f674ac9bb910448faf171b8a780f5e10dc06f06edc31efb48b60ccf55c80c0503"} ] },{ "name":"prehash first light, elaborated",@@ -21,33 +23,38 @@ "credentials":["The Indiana Academy for Science, Mathematics, and Humanities"], "long-tag":"Please leave the location of America's nuclear wessels after the beep.", "domain-tag":"1-800-CALL-SPY",- "seed-tags":["United States Army Counterintelligence Tip Line"],+ "tags":["United States Army Counterintelligence Tip Line"], "role":["prankster"], "echo-tag":"Star Trek IV: The Voyage Home https://www.youtube.com/watch?v=MdSJFrhb-HM", "phkdf-rounds":1998, "bcrypt-rounds":7 }, "results":[- {"G3Pb1":"60473b8010e16d464314a11c2620a8ad99af49ae25474f877e57f6c27c58ca7a3538b58385eabbbbb540a350491291c870a4f12c8569485100da96f4202c3630"}+ {"args":{"echo-counter":{"hex":"7fffffc0"}},+ "G3Pb2":"6aefea0f165a93ba619f5376980cf11156e42e531776c907b69dca3dca35125d02f2a42d2b10b60b08874e4ec8cbecef21f01b84af0d7cf8d08a113bd7bad5b4"},+ {"args":{"echo-counter":{"hex":"ffffffc0"}},+ "G3Pb2":"08ab9b89eefdf6eb6e5b45d593de005b1d22d5b07782c605f9db703c0de7066b76b28769e7225f7a4fe08dee5820ef25e1384663c00c376ad72f9ea8cb0899fa"} ] },{ "name":"prehash unicode test, inset naval story with a reference", "args": { "username":"Р-360 «Нептун»", "password":"Русский военный корабль, иди нахуй",- "seed-tags":["Державне Київське конструкторське бюро «Луч»"],+ "context-tags":["Державне Київське конструкторське бюро «Луч»"], "echo-tag":"Збройні Cили України «ЗСУ»", "domain-tag":"Україна", "phkdf-rounds":2022, "bcrypt-rounds":8 }, "results":[- {"G3Pb1":"0c06f683f093cb899b4a1e9836fc72812d22941716c05368097b912a328deec579fa21edf7aef04ec808a50bd8757cb734734cc96b00c224abe8642ce94088b3"},- {"args":{"bcrypt-tag":"Державне Київське конструкторське бюро «Луч»"},- "G3Pb1":"8660937862a0644410b7dd1a495ddd3dbdd05b3b43669ae2ec8dfe863f8e99d673f61c89486b7855a62936ea8684bb82e429f681abaaf571193dd1275e21481e"+ {"G3Pb2":"131cce4f62c41713e7bbfd9478a99ed1e990c7387a8283d94dd55fce26a64314563128c479a054f6b07e8dee56647d2b3b5d1147c28dfdeffb5e38c183b326eb"},+ {"args":{"bcrypt-domain-tag":"Державне Київське конструкторське бюро «Луч»"},+ "G3Pb2":"01a1b595c19eada304e37f4242b6a4600da1340ebf330ee284ad2a4fcb8900a0b8859ba365bc452daf0716b002930dc6a6a94430481511e0c9a2f97423cdb216" },- {"args":{"bcrypt-tag":"«ЗСУ» Збройні Cили України «Луч» Державне Київське конструкторське бюро"},- "G3Pb1":"d59452447147d268a81249304430d7859e8b5c135b260f9b366db37c38c10aa9c6ab83f05bc529607023a3cae5248e7e6a825e69fa4a62f4a1fede386195cb33"}+ {"args":{+ "bcrypt-seguid":{"hex":"08ab9b89eefdf6eb6e5b45d593de005b1d22d5b07782c605f9db703c0de7066b76b28769e7225f7a4fe08dee5820ef25e1384663c00c376ad72f9ea8cb0899fa"},+ "bcrypt-context-tags":["«ЗСУ» Збройні Cили України","«Луч» Державне Київське конструкторське бюро"]},+ "G3Pb2":"83991bd4a3e57461d76e94f391a4956e45acf094ffc2006583204b792dab7f1da5c2daa5e2d46c7dbdf3747423536f077f4072cb3e6671b8932e33874a5d431f"} ] },{ "name":"prehash unicode test, lightly extended on top",@@ -61,11 +68,11 @@ "bcrypt-rounds":66 }, "results":[- {"G3Pb1":"9c08053b7e507a78b571b5b93e1326674540d7106da6408fcafeddcfcdf1ed76177ae099e711fd570d484af8e9fc548ba03d8bb545f449fdf6e86a9243d8c0f7"},+ {"G3Pb2":"afad94b24b65b37a8c16ba0bca3e5d37a299c7cc542b59563e5cc498efba907b18ff1902d560191bda103ea4c25a12202a7762685aa4a544248ae2c0cf770148"}, {"args":{"seguid":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40"}},- "G3Pb1":"7db250698fe555f6832f33189f97e14ef3c1c2dcada5807119aa7676c24f3fac8d37ecf118059f51614f4d2c7ceecb3224047abec84e0d009f43fbfe984abd28"},+ "G3Pb2":"c0a140a78438b7e7414ce8dbb9006619c18dd080031e157f339910947a785306bc740a9dfa7eb73db8f4c50e2cc7c29f38f7a43586bbef030f84dc88c071dfed"}, {"args":{"seguid":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f4041"}},- "G3Pb1":"4efe93dc422d0b67e9ad5d6d4743bc3f475d805e17f2657d57d57505969d3de56622054aedf3b8fec79e2aa48a2228530b7a11c23a520cc67d70d90721f4fc83"}+ "G3Pb2":"523ad596656f594ad08f87e58e2ca9f681955cb3641ed884f54eab6a482a49a7f07e2a8e5f780797645d1821fe50f6d7dcfe4f2a771f256c28bf9748364bf2cd"} ] },{ "name":"xkcd account problems",@@ -77,9 +84,18 @@ "phkdf-rounds":2022, "bcrypt-rounds":16 },- "results":{- "G3Pb1":"c09df48d21af7c83fe124e518ec40e84abada6b176b3ee99b35701fa2c0af99053ea538a8498d30e19837e7f5b4e200d08e11995d58f1c3d5a003b4bf1bc3089"- }+ "results":[{+ "G3Pb2":"0730f4298825a7d7f874023d8c90c8040a6de43806834db14fb624906700869c153720bcb0dc0f13d4c685af29f839a9001218c3539beb48e1447aabb3f7f3b5"+ },{+ "args":{"password":{"hex":"7061737300776f726400"}},+ "G3Pb2":"a068f02d6955aaa245440920d326dfff5b32e9472491ed561d1ee22d7d087013d1ab06d56e54fc3ebebdca102ef049b7ce2bb15f1ea66205cb0f9aa48e6aaad9"+ },{+ "args":{"password":{"hex":"7061737300574f5244"}},+ "G3Pb2":"1c371a534c3b402291cc17672eed4ea6f17e640fb5de77a0d0723d48f7c52dcf481e05cf1bf4948a1d7ef2623e9fad18fb1b487a586da78f47382bee9401298b"+ },{+ "args":{"password":{"hex":"7061737300574f524400"}},+ "G3Pb2":"3d2fb8731f2a21cad70caeb80eab27efbc5bd74b15e3784cbfecc086bbd518fa23c925861c0831d2241ae3f1e498eeb824bd28fe66bd7879a91e9776da43d476"+ }] },{ "name":"xkcd password strength #1", "args":{@@ -90,13 +106,14 @@ "bcrypt-rounds":5 }, "results":{- "G3Pb1":"fb404c0874e90da9c9bd56e77000c6717482869c942fbac8feee4cb46c3d7e65371bf00071fd0f4a5707d3e3690acd534614a34176fcff704093a350975fa9ed"+ "G3Pb2":"dccb510e6b1ceaa3b9d81a05403d39ec26652a9c0df05313333b3206619d729a1a8982dbf01590b474797bf5a41c42eb333d736c6ff9403378c1fa2f6e4fed3b" } },{ "name":"xkcd password strength #2", "args":{ "username":"Randall Munroe", "password":"correct horse battery staple",+ "long-tag":"Please prehash passwords before they reach your servers. Please include plaintext salts in your prehash deployments. Please encourage your users to use random passphrases. See https://xkcd.com/936", "tags":[ "Diceware","https://theworld.com/~reinhold/diceware.html","Arnold Reinhold", "https://www.eff.org/dice","Electronic Frontier Foundation"@@ -106,25 +123,22 @@ "bcrypt-rounds":5 }, "results":[- {"G3Pb1":"290cf026e6860f78b558cd7deb3d01442f58d3fd90742bc4759407d975b0220fb9ab36dd7f8ec48705c3f55017d5ea2ad69dbf018e25243f7787ee8ba5578c55"},+ {"G3Pb2":"292534d884103b342053a1be6a8b74e63cf322f04a3302cb96ec92ec618506f6e5454d0f1ce212ae7c23422fdec89e4783ee3a52531b7a949c0825f5759d54a3"}, {"args":{"phkdf-rounds":65536, "bcrypt-rounds":256},- "G3Pb1":"200210b6ad928c550b842adb12b2b621dba60f7465e456ac0440da9d8fb539e39811ad3a84d010a08cdb3d8d640da5f67f82fb78715cc6beacc149963df5ead3"},+ "G3Pb2":"6828adfe400be94e5f307b27549c8a1e04772cc76e907e13a07d1eb30ad759c0a7968f193125c7e9cce1b278b2bf33add6c7f237baa961ce660c3507453bc5a0"}, {"args":{"phkdf-rounds":65535, "bcrypt-rounds":255},- "G3Pb1":"872420a152eaedbd035e5aa488fa089c5ad59de5d5013dbf045dc72d9c3668e6e354b1dcbcbd6a9521fd9cf071724fabafbf914ed170c9e04becab9b823fd9a7"},+ "G3Pb2":"d35e281cc1d4c4237b898f409fd10eeb911d467594e6c1a86ab6e935755098053dcee539226d97dac91a7eb604e8c90eba56487559aa56230524ae7299f993f7"}, {"args":{"phkdf-rounds":20240,- "bcrypt-rounds":4095},- "G3Pb1":"650c06d2baa0a6a0e326b02328cf2ef0183cc5ad9211c56f4726e8a43d6f7c692fb1814ca57bf29bcbabc46a4c11d5b1b2af15b8631bfcd2a9bda6b71b540c8d"},- {"args":{"phkdf-rounds":42024, "bcrypt-rounds":4202},- "G3Pb1":"7014dad47f0e7f7157d99b39a06553ce392884314bac76929f3f055bb0350b8f1920972aa1b5a2ea200d19f5d08b594cc9d891dd6913764f0bc080e332ac05e7"},+ "G3Pb2":"9bf154d726d3f7cbcb13847c1f11f660d9ede578149d3b124c287baff56b9ad4ec3873e28cef681f4ce1e00102658e255f23ab7832d42eeaf687be1ab376301b"}, {"args":{"phkdf-rounds":0, "bcrypt-rounds":4202},- "G3Pb1":"fdb25ce5a9a782ce6dbdf3c6e00f02c4710b1b11388eff350a4ad032a66e38c64e80fa038cb6f9db3e29f49b169f846a3ea3a9bed3f7ca4ef264403a3f4b0f35"},- {"args":{"phkdf-rounds":42024,+ "G3Pb2":"b2dd2b87c1ab8fb609f3819500a4f817b8c03f42e15a54411872ca19bd219525eba62cdd4e629f97da70b0c04a15561f07c22f5add266352c06bc3298a4c63ca"},+ {"args":{"phkdf-rounds":20240, "bcrypt-rounds":0},- "G3Pb1":"34c1b5a3009161dabb4ef2a526dab2ed9154866b46251cae266e77c8125373af88cbb4afd03a11b526859feff12d3a4d43a375a7962011cc136d9c709c8db3fa"}+ "G3Pb2":"e5a202f7a12be208bcc0ab63b904d59e96caed4b7b43cda0250d671f4a1d8f61d777cf846613cda21f6a59a74cfed1310638149e43a51f209443b7a1a89fd1b2"} ] },{ "name":"xkcd password reuse #1",@@ -136,7 +150,7 @@ "bcrypt-rounds":4 }, "results":{- "G3Pb1":"f2efe2dd90f30d7509f427cbb06759e0ceaf08e51e0a33c325263a861b81c803f59374fa151c0af5904700adecf392c3bffb921d0a2b7d7c1052ef27ba9fcacc"+ "G3Pb2":"31d0390da676dc38fe19830c774d01f018c63cb09eeffc7c8ea6a8fb641ceb4fc8acbdb13ef43adf3283ce24935587fc51252b70d1728f37c2635b667292587e" } },{ "name":"xkcd password reuse #2",@@ -148,7 +162,7 @@ "bcrypt-rounds":4 }, "results":{- "G3Pb1":"19367746312da7c3c938fc4e5264368e206f89812b84d4fa479cc7e7f66f07882d5788c1d309af46b95aa2c433186808b21fee501aa14b35ab2a9c9f5ccb0ed7"+ "G3Pb2":"5e850c44c346800a7e7bbc2044e912fc0639895f6a40271b154098a5f83aeef9ee9b92d503b5d6fa0008bd7cda7f1ff5d2b8337f09c039cfe14087e1da7332d5" } },{ "name":"trivial inputs",@@ -157,107 +171,222 @@ "password":"", "credentials":[], "seguid":"",+ "context-tags":null, "domain-tag":"",- "long-tag":"",- "echo-tag":"",+ "long-tag":null, "phkdf-rounds":0,+ "bcrypt-seguid":null,+ "bcrypt-credentials":null,+ "bcrypt-context-tags":null,+ "bcrypt-domain-tag":null,+ "bcrypt-long-tag":null, "bcrypt-rounds":0,- "bcrypt-tag":"",- "bcrypt-salt-tag":"",- "role":[],- "seed-tags":[]+ "sprout-seguid":null,+ "role":null,+ "sprout-tag":null,+ "echo-key":null,+ "echo-header":null,+ "echo-counter":0,+ "echo-tag":null },- "results":{- "G3Pb1":"2d0fb502c5e036112ca922e6bb5bd3e41acbd79a3223a42c3a1f08fd92398dfb"- }+ "results":[+ {"G3Pb2":"68b04421029eb6bb52a651953248a177246f71be02d633b0eb9c85357b6229a4",+ "G3PSpark":"b3211bf8e5aee9e18a412f56d32261e32170507998f552007f439e29a2239e2840179c258f47e9fa26d21ebf531717a288af208192366f978614b2f8bb87647c",+ "G3PSeed":"27e5bab1e5fbb2af9ade4d57dcf337eed565390e93f5be95865e5c17ae827a58"+ },+ {"args":{"tags":[""]},+ "G3Pb2":"a35e9a894fb1ab12d7fa6ce0af08067cebc5f5dd772e4e14b7ae62960cbed6d1"+ }+ ] },{+ "name":"self-referential inputs",+ "args":{+ "username":"username",+ "password":"password",+ "credentials":["credentials","vector"],+ "seguid":"seguid",+ "context-tags":["context-tags","vector"],+ "domain-tag":"domain-tag",+ "long-tag":"long-tag",+ "phkdf-rounds":12,+ "bcrypt-seguid":"bcrypt-seguid",+ "bcrypt-credentials":["bcrypt-credentials","vector"],+ "bcrypt-context-tags":["bcrypt-context-tags","vector"],+ "bcrypt-domain-tag":"bcrypt-domain-tag",+ "bcrypt-long-tag":"bcrypt-long-tag",+ "bcrypt-rounds":13,+ "sprout-seguid":"sprout-seguid",+ "role":["role","vector"],+ "sprout-tag":"sprout-tag",+ "echo-key":"echo-key",+ "echo-header":"echo-header",+ "echo-counter":"ECHO",+ "echo-tag":"echo-tag"+ },+ "results":[+ {"G3Pb2":"9bb97245f2d938b469c283564c4ee4cf22ce64a6ec25c865abda13f837d6eb4d"+ }+ ]+},{ "name":"password padding", "args":{ "username":"password padding (tests with very long inputs)", "password":"back references",- "domain-tag":"your-domain-name-here.example",- "long-tag":{"ref":"prehash first light, elaborated","len":5112},- "phkdf-rounds":255,- "bcrypt-rounds":15,- "bcrypt-tag":""+ "domain-tag":"domain tag",+ "long-tag":"",+ "phkdf-rounds":0,+ "bcrypt-rounds":1 }, "results":[- {"G3Pb1":"1e55f1cb8f9ef582beaa176608ad33e65d56fe56898efe648a0b6993a50f4281"+ {"G3Pb2":"87807e2b754a4e5a9a3ddaa2f58f7a41d091d0fcb98a10d7311d23fc2a0e0090" },- {"args":{"long-tag":{"ref":"prehash first light, elaborated","len":5113}},- "G3Pb1":"1ed3ddaea18428b04ac9b8028e6cae82ef08bdd1fa7c71337aa67e7bcd0a1c5d"+ {"args":{+ "bcrypt-rounds":368,+ "long-tag":{"ref":"prehash first light, elaborated","len":4095,"index":0}},+ "G3Pb2":"d100692bed0f8a298dc0519a45507a7465002edc0e5ea55f2ea0bf3eeed45362" }, {"args":{- "username":{+ "bcrypt-rounds":382,+ "long-tag":{"ref":"prehash first light, elaborated","len":4096,"index":1}},+ "G3Pb2":"ba32b204b25b2d612db8fec06cb3e41df1a75f62be5a83827c5d739413770289"+ },+ {"args":{+ "password":"username padding check #1",+ "username":+ { "ref":"prehash unicode test, lightly extended on top",- "len":3045+ "len":293,+ "index":1 }},- "G3Pb1":"f79d27d104ce609e18609fffd72ca3b15d4b4a354dc6d2c686e6a92079654d59"+ "G3Pb2":"87495c1bb525034a49c57e7e731993a27200904eeffcd6e0c2b8709afb32d67f" }, {"args":{- "username":{+ "password":"username padding check #2",+ "username":+ { "ref":"prehash unicode test, lightly extended on top",- "len":3046+ "len":294,+ "index":2 }},- "G3Pb1":"92dcd256761116151048601e171a3f9ae9da41d59fd754ee05043f139e0916b0"+ "G3Pb2":"8cdd0d35f38dcfd6f0926e6acda7196c61385c13d723cf1ddbda747685eed2ed" }, {"args":{+ "username":"5: password padding check",+ "password":{+ "ref":"prehash unicode test, inset naval story with a reference",+ "len":8158,+ "index":0+ }},+ "G3Pb2":"e548fd708947f8fa3d74fa71c3d17f59be5ebd5656124d13c4d65253304402f1"+ },+ {"args":{+ "username":"6. password padding check",+ "password":{+ "ref":"prehash unicode test, inset naval story with a reference",+ "len":8159,+ "index":1+ }},+ "G3Pb2":"e3f82ff3963be9f75d67f72ff06a53a09f8ff9eb87d900f6f4e593bf774c7623"+ },+ {"args":{+ "bcrypt-rounds":358,+ "tags":["7. long tag and username padding check"], "username":{ "ref":"prehash unicode test, lightly extended on top",- "len":3045,- "index":1+ "len":4133,+ "index":0 },+ "long-tag":{+ "ref":"prehash first light, elaborated",+ "len":4024,+ "index":0+ }, "password":{ "ref":"prehash unicode test, inset naval story with a reference",- "len":101,- "index":1- }},- "G3Pb1":"2b71c3780105cc1379642f6ceb3e2d788ca96d7d514d8befc79c1bc403b14991"+ "len":293,+ "index":0+ } },+ "G3Pb2":"01ef9e39dfb482d9097f107185641f2683dbd3423e4275078672a6578fec717f"+ }, {"args":{+ "bcrypt-rounds":356,+ "tags":["8. long tag and username padding check"], "username":{ "ref":"prehash unicode test, lightly extended on top",- "len":3045,+ "len":4133, "index":1 },+ "long-tag":{+ "ref":"prehash first light, elaborated",+ "len":4024,+ "index":1+ }, "password":{ "ref":"prehash unicode test, inset naval story with a reference",- "len":102,+ "len":294, "index":1- }},- "G3Pb1":"a8b6f029b25f1f57e3dac00b1f20bac6f1078230dbf0dfc59c4ae2c480965c73"+ } },+ "G3Pb2":"9a8de0638fdf16af847d4b21884f6f77e82b66cc746eb79aad79acd4abdde933"+ }, {"args":{- "long-tag":{"ref":"prehash first light","len":5049,"index":1},+ "bcrypt-rounds":352,+ "tags":["9. long tag and username padding check"], "username":{ "ref":"prehash unicode test, lightly extended on top",- "len":3109,- "index":2+ "len":4133,+ "index":1 },+ "long-tag":{+ "ref":"prehash first light, elaborated",+ "len":4025,+ "index":1+ }, "password":{ "ref":"prehash unicode test, inset naval story with a reference",- "len":164- }},- "G3Pb1":"12a5118df13e45434b61d168c3c46947bb00c8f97f14675691eb97c25630c7b5"+ "len":293,+ "index":1+ } },+ "G3Pb2":"f35e7aa85421872100d8387ac09bc3748f16df7862ab136da93b0b7adb573608"+ }, {"args":{- "long-tag":{"ref":"prehash first light","len":5049,"index":1},+ "bcrypt-rounds":338,+ "tags":["10. long tag and username padding check"], "username":{ "ref":"prehash unicode test, lightly extended on top",- "len":3109,- "index":2+ "len":4134,+ "index":1 },+ "long-tag":{+ "ref":"prehash first light, elaborated",+ "len":4024,+ "index":1+ }, "password":{ "ref":"prehash unicode test, inset naval story with a reference",- "len":165- }},- "G3Pb1":"d833526e21357efc9cdb0fc9a650d7289ae946b5290381aecdda9bd1bdf140ea"+ "len":293,+ "index":1+ } },- {"args":{"long-tag":"","bcrypt-tag":{"ref":"xkcd password strength #2","len":112}},- "G3Pb1":"d31fa18b6bd099775b21efd7ae0f8c9d4090195be2d932ec1c462e996dfc1812"+ "G3Pb2":"65d892ee88bf0b1d92925e16429646d224f2198d2475145fd333e19866d0ae95" },- {"args":{"long-tag":"","bcrypt-tag":{"ref":"xkcd password strength #2","len":113}},- "G3Pb1":"7a2e399582edaad660124e2513e3b21a9096e6ec81d920e75a7f40d796fa1c5e"+ {"args":{+ "username":"11. misc parameters padding check",+ "credentials":{"ref":"xkcd password strength #2","len":279,"index":0},+ "tags":{"ref":"prehash first light, elaborated","len":60,"index":0},+ "bcrypt-credentials":"this string is of length 27"+ },+ "G3Pb2":"ed0649cecfe4bd109d0b772cb6c3c1cf91c9f9642d53eec8b9f4a2fbf5f820b8"+ },+ {"args":{+ "username":"12. misc parameters padding check",+ "credentials":{"ref":"xkcd password strength #2","len":280,"index":0},+ "tags":{"ref":"prehash first light, elaborated","len":61,"index":0},+ "bcrypt-credentials":"this string is of length 28."+ },+ "G3Pb2":"e035d469fbfbab610989d47532c8fcb09de08b9ae219777eb1e67070b353160f" } ] },{@@ -270,253 +399,306 @@ }, "results":[ {"args":{"domain-tag":""},- "G3Pb1":"76488206562b5b42828ab34e4300701a1c9719dbb886b494f940de50dd9008fc"+ "G3Pb2":"1cf6e3ab0e6e8c2396ac457ba4622183c342c0df2a0265b8d9bd94d346f6c55f" }, {"args":{"domain-tag":{"hex":"01"}},- "G3Pb1":"2eed4085cf87decd6dcec271bab1252a3a1688f5e4f1e01a5f51c3a527176745"+ "G3Pb2":"2d86069f73354a52cef62896967db140748d6fe42796019e9e07d21365b30737" }, {"args":{"domain-tag":{"hex":"0102"}},- "G3Pb1":"9ad2af4b7e5ae0d0b672927069fc03384da7c8982f17ec6fdfeccdf11df96f69"+ "G3Pb2":"df7a2ddb87f8b51a2b7826a042d71a77ab25eb3cd6b89f7470df7380033127a1" }, {"args":{"domain-tag":{"hex":"010203"}},- "G3Pb1":"c62a1ef5be515385bb7d87cbf6da08685857827e1e4133dd31892d7c987e8c7e"+ "G3Pb2":"0a4b2f8b6dd07c339c60f5c83bc1f66d4917bba6b4b91847e034156204452a21" }, {"args":{"domain-tag":{"hex":"01020304"}},- "G3Pb1":"c5d87a518feb9ae1c30bf39317e98c4ccc9f8c07d3cc78f4328648ec08a2afc3"+ "G3Pb2":"bed6827fc6bb655e37255c18ab1ae9813bccb349b06f3b05d55788af5bc123c2" }, {"args":{"domain-tag":{"hex":"0102030405"}},- "G3Pb1":"e4b59846812f693d4692e5d28f70fbe2e5b16bc5b44565904f377a8b8b3e1b3c"+ "G3Pb2":"4b500671345beb22820efea5bc8a14889519b646217dc2350b45367a3c117fa2" }, {"args":{"domain-tag":{"hex":"010203040506"}},- "G3Pb1":"153642e8c40383c92b534e9bf8c2c81da55aef5c22781b97d4ffeb7c90ac8574"+ "G3Pb2":"6f98d1fd621c51da97c4e65d41d1cb677f7e3a8ed52f614a5c980763d4a270c6" }, {"args":{"domain-tag":{"hex":"01020304050607"}},- "G3Pb1":"1e3f7a8203531082c0b993c957d0c989f457775f8d38007bafbcc2b2607fb49b"+ "G3Pb2":"c1e18bb244193c595867af56e9e3f5379efb36c2a9f53242a6fba50ce0992a44" }, {"args":{"domain-tag":{"hex":"0102030405060708"}},- "G3Pb1":"9e7d22be88fdc4c6d6b19668bbb4640f1b2ad783e8f2b2ab8bcc8686dec097ab"+ "G3Pb2":"3354eecb62eb81e6207f4c50be6f8e0a64763bc1886cfd13c78d85fbed10d1e8" }, {"args":{"domain-tag":{"hex":"010203040506070809"}},- "G3Pb1":"926ea057bf81916ce3b9094df12c76b6e51770c2969f5af15775474c05f674e1"+ "G3Pb2":"e2feb1695b20f62835262c0c1a5708cf89a967a787efe6a420618a6bb0d8512f" }, {"args":{"domain-tag":{"hex":"0102030405060708090a"}},- "G3Pb1":"58aa2ca337800412b99ecd98e4ebbb465a7f28b648ceb06ccca62b37d695833d"+ "G3Pb2":"f76227e64b895f5be1cb21aa9e83dd40f7409c5f399cabee3dbee5d86f70eedc" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b"}},- "G3Pb1":"da7a85e33611e860defd768666f4f3798b5fb32ccbcf76847cd9bd0765c74a61"+ "G3Pb2":"3319df1db25909688d053605f907fdfc90fc0428971a48d5385b6cbe22992a6a" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c"}},- "G3Pb1":"c23f47fedefdedb0dd68773d4ea08eb12ef1b569c9c115e135a817deb0be67b1"+ "G3Pb2":"5c18ed7a426ae49a8eecc40c6cd681082ac95ee65a44a54f337828e70fbfab94" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d"}},- "G3Pb1":"35a1c4f934d7d74c7747f42212d3905a6e762e8793caef26bf850815a778c224"+ "G3Pb2":"277d1eae4ecdf0f8ed61e7633f25b1a062d9d666dda2a1d6be891483d099b2ca" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e"}},- "G3Pb1":"7c2a21846426f71eecf4ef86c9dcf7cd4abadb3ba8dd507e271b8936b9946e3a"+ "G3Pb2":"542b2d2c2d6009f65c3eb304b0600acc98dce10a020c5b6b197e3790409c509e" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f"}},- "G3Pb1":"fbd455257919f6ce7f375318b2a1acc2cd0277765e79095aefa8752ade1c6fe8"+ "G3Pb2":"f437c948c4b2eaf19736ba60c9ff7b97b50db6777d474b679707ae20872f1b4f" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f10"}},- "G3Pb1":"d178a9407df73a182091e48d3454d3bba28a923b19f1811f977fb488cca7306e"+ "G3Pb2":"3eb51d91c25c0267325b27e75f94af2ff73c8d99b232a20ed670e4674fc18f19" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f1011"}},- "G3Pb1":"7ebb194e3b2503c5af7348cdc91209a977cb0fe1082a4a6840af192d5b4a85a9"+ "G3Pb2":"462012f5d1f39c9c7ca188f2e4c0273563050737a165d311731754d81807e8b9" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112"}},- "G3Pb1":"8e9926ed8b0220303bba8fa76434477322602de88076349bf903295e27a83eda"+ "G3Pb2":"340a15be0ad6f436def9953103d3a530c31db772d3e8a90a4c37243d8c040347" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f10111213"}},- "G3Pb1":"6f7dead0b80281ad271c56f36cde0c16030d46b9c66ab5426a7b4e304f177787"+ "G3Pb2":"a6d53a3b194caad3a3de813dbc7755ca490c1040c7658832455c0fee0cf52f15" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f1011121314"}},- "G3Pb1":"95819a7a4aafa7ac45c701edceb7748059d044c069ed165bd5afd4b5287f3f30"+ "G3Pb2":"7e623c6581b008968f7223664f7c5dff74b6e0a2b8e44819b2378f52d20160ac" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415"}},- "G3Pb1":"ac6a51331ec00dfb7d33a5e5c28642204b2e752bb26f67b67c6c733c344b1f52"+ "G3Pb2":"e9445e14d3c5f7cd99ba5f052956de51d6a3c2bc43a79bfd57ad74d0cd4bb2a4" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f10111213141516"}},- "G3Pb1":"e58c9f5032941e77d843adf3eacc1efcefd9ee02aea0389d4bc06e15c9d0357a"+ "G3Pb2":"6c141a129cf394b9f03e89309a240a21420f4870f49953421357c09911aa1cb0" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f1011121314151617"}},- "G3Pb1":"4a1c4bbcc544a0304d0a160624c2c22498cc249792f398221cf8eb1683f8c616"+ "G3Pb2":"af6ea862d7c83c9144231275173498ac192252339968b5723ca9d3dafca76024" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718"}},- "G3Pb1":"99bab93d19ca43963061bf9a90eb13a64728cbd69053ce3d2f3725ff4fc7305a"+ "G3Pb2":"86e98f404cc3234e148269dbd863f4b7736a5d67ef983f8fe5dd58b0a31616c3" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f10111213141516171819"}},- "G3Pb1":"ebc5e55955d0ca977ba081c868316f5dfe6c7659cc352a05024d1911995d248f"+ "G3Pb2":"f54ab3dd00005a47ae7b6131288f1284f735d1c4443ee3d0bfab4807b92f0346" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a"}},- "G3Pb1":"7be0875e6acb133a2a4ff27aacee8ffb8bd57c21c48e711d2ee8e97c35c754e3"+ "G3Pb2":"2c8afde8d002bc58ce2b3ff8649c621060c00949dc3d102f13eee5a9b6720a7c" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b"}},- "G3Pb1":"b155e942c56449ae076110008c244b9f6a10deddd70bfa265af7a6e12284da61"+ "G3Pb2":"b63f27753ffb517830eba3f6aef4ce49d5d9b669b119fdacfb0436efc4556a0e" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c"}},- "G3Pb1":"27f3ae6a201d5dbc2a0d9335e424d611b360de377e6a421822f7eae381b0e6ab"+ "G3Pb2":"f068ba5a45504e4e2f6d69fdf384f8a36a980cdb1922f2d222287c11796d8e33" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d"}},- "G3Pb1":"7a5a0c50c494ba373c449c023b6a543e42dfed6473ec65c1d070e89e78f85583"+ "G3Pb2":"7e8462aa5f37c0dede692e8b15348d9565eb12d829b903a1d747af02ba19b12d" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e"}},- "G3Pb1":"7200e3f36082f5389e919bf8148ba0e98cdecd1b4eb14a71a9067e583b037bdf"+ "G3Pb2":"b6bb48e2a7a988dc6a9207c1e97441c85df7b5fb8b245c415211b55835607a2b" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}},- "G3Pb1":"6341748a06146f47c6bd759b615ba84d14b93de448bd773ade4e8f20ebdf6c03"+ "G3Pb2":"a6da18af24bd704985fc8c24c9e74d67ad1144caafe1721e2548b177f9e9eb92" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"}},- "G3Pb1":"e8ffcab67ede795d547ffa2356daae472d6b266b520531d8d78cacff197e6db4"+ "G3Pb2":"854e78e3316ecc31a6623e533b527cc6f27d5425efec33f8418b2a6f878813ca" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021"}},- "G3Pb1":"3c8268530283984b858331b9ecc3b54985f4760ab2b369ffa1c27924937e168e"+ "G3Pb2":"eb13c1637a2b2590153fbc6dd7954207afe022520c0d59f954528126977e8f90" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122"}},- "G3Pb1":"2d0d893ddd1903dc7373206e1475d293d997dd38a9ba946283a4f206ea36d7f8"+ "G3Pb2":"e6dbb922cf0218b40eaa936229a9b5ea6dff9370408ea2a56b8323490f538aa1" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223"}},- "G3Pb1":"651da440e3425f1f2d69f91d5b150c572800b6613734311ea695116209455d1b"+ "G3Pb2":"5d3a1e03849fc2dea617db5a7b20587774461e80a8d5656d6af27ab619ddfab8" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021222324"}},- "G3Pb1":"a951bb1592568a0c05bd41d4d0a9712e9f83829d3f800f1ed6bf5d567cc6ce15"+ "G3Pb2":"c3bc5fe105fb4b8f21e2f84279a51f2f647c43d550b75caa2a1590d20793c5e2" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425"}},- "G3Pb1":"0b4e8f76159bedf3b67541c08c3e11fedaed796d2e6ec3f97b1fc339e727edff"+ "G3Pb2":"65939e5e5cc7f1f171348c873e8e129f8bf382211f1c0db97b6033345c7cc957" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223242526"}},- "G3Pb1":"ff9b9a96cefcb04d42af036984daf11b0bb1d519027b32fd92c58a6a8fda2744"+ "G3Pb2":"53d235fadd46f8bef67a283761847a89a73984ca60c99b188e9f8cd38ac27540" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021222324252627"}},- "G3Pb1":"21112353bce77f3f1781b1ecde88cd205d2871ecbe86a80d8706ed327c0f7c67"+ "G3Pb2":"561b732b3525e07963e522c5f87e326069593bcefc44332b70f9165efa6693b1" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728"}},- "G3Pb1":"17a536829a3c623697c003701cdf0ae16c1faaeec27c095a5edf93f576e37d2a"+ "G3Pb2":"5c5276d4338768a671806e330d4995aba90edf7a4a46de238443f4cac13bcd89" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223242526272829"}},- "G3Pb1":"85c5fd1dfecb6e40f81c8066f984135dfd1def147e37a895ee585fff582a479f"+ "G3Pb2":"502173f2576254a61874da797648129de8e81c98eccb69ef8046e3ab2418edab" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a"}},- "G3Pb1":"6213f75b424b446a3b367c3400e1dcbd2050e2cdf8eac33745266737a1bae9ef"+ "G3Pb2":"3efb7134af3ea4b46b2340c6c59624d3dc87f3fc03350c7a41348c2f65ac3304" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b"}},- "G3Pb1":"8998e460a5c834f5dd76cdcc18e7862d4ba7c06df9602f3066fe75c39e087fe2"+ "G3Pb2":"4187dda53ccf779c89e6cfe769bc83af9d1d3e101f0bb788503c19aaa9472106" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c"}},- "G3Pb1":"4e0b9607bbdc9c82b1df64c8842d412e82bf4da73e53530d1f8bcadb402d3f8c"+ "G3Pb2":"9ba2b8ceed9502d324bd1f0f1101ef275a8f53672c63666506abde2e5de81fa5" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d"}},- "G3Pb1":"3111d270403f023c0342bcdc77c18eb430f27ccf6fbb3edc34845659534a6296"+ "G3Pb2":"d8f3454892335577ac1ff5819c5822fcefd37d2a73bfa5a840f8b8c606356c6b" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e"}},- "G3Pb1":"0c3270549af4cc19b5ee6ac4f669893c9a7b90f12dde29ba1e696813eedb8d98"+ "G3Pb2":"191fbf29badafbe7f03cc0fecb0edcd07d2a42b6a9a0df02c1324375156e334e" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f"}},- "G3Pb1":"45eceeae98b934fc01e064b8829b153951b53e3c094d3ece3a7e61c91df3e5f3"+ "G3Pb2":"10065dd95c6617ccc824ebc793dfddcb12235ce0954033fb0aac984e5ca15361" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30"}},- "G3Pb1":"e65fea4a66668a6ee7a62f0a589615893eb4d6b202b27259bb107be1c31a7e1a"+ "G3Pb2":"dae719636a3ba2488987e37c159713824724b8bc725fa4d4f59a67fa41a7d615" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031"}},- "G3Pb1":"5045dd5daacd156ce916394f92b275039bc5ee084a495c006c783030bd860bd4"+ "G3Pb2":"300109a01c8e5723d14c41bb2f9c716347bba79076b22a1da66c1cb6ef904af6" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132"}},- "G3Pb1":"944e6ca6dbe90636f57612709d620bcea4f6670c25f329cf859a78a2a3403d63"+ "G3Pb2":"f1d45cbb266b09892c68fdf4530465debd93a2ee00ebe49b11da9481dab550cd" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233"}},- "G3Pb1":"27238f189fb2af713a17f85c8db9af5bbc650e19331cfa5851bcbe8a2dc6f94a"+ "G3Pb2":"b818e7257edc2e55cda89dde64f70e0d56e9bb3ff06320c3b2a993032500b249" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334"}},- "G3Pb1":"16661a1a66c625310478edea975716c1788a84861d5c3da4b7e5fd5f26b99fbf"+ "G3Pb2":"5d31a9340026825da3b05050ed6fd082708c1c15ed90d47744906d76c572bfee" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435"}},- "G3Pb1":"d64e32c42b6bd819ba785e223a9ec2fa7fa564cbcd5831fc462cfe55849a8665"+ "G3Pb2":"8ab0d8f3dc3e3c701ac033670e06e71c74a638826bb17cbc089b9093baf2caee" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343536"}},- "G3Pb1":"22279ed0604d0bb24584f26b255e50aad4e0fd3d4a9a62943d756fbef07f95ed"+ "G3Pb2":"7027daf4e4bed0a80ce8dda6a27035e5146110bece4a2908bd5dfdb23bc3b53b" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637"}},- "G3Pb1":"eb92f4bd207342ff2d2b277889e60bd45403ce158abe3c1c15d76c774c8fb267"+ "G3Pb2":"f6675fdca1b4205f3d73126fe4ead4e6bf54903023012f231baa46ed8ce191d8" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738"}},- "G3Pb1":"aeb1db443177577b9e5b0e32824b21c84b848a21b77821f987b51d49a771a243"+ "G3Pb2":"23a1f061dabf4d0caf3e8e0a257d1cc5c260edbedcc7b8fb08957ad8d89e2881" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343536373839"}},- "G3Pb1":"24d09f57f7d8e4186bd00a7fcd6b7c89ac631997175edc0289f641c2c84507ea"+ "G3Pb2":"9e76417a8bf05db366f4742fd03e73aa6037e7e073e2c2b7d737afa71edbae31" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a"}},- "G3Pb1":"dc9ae1c446c1a196ca7829f635f3fa9537268f379cbd5eebbc2b723e4793c032"+ "G3Pb2":"0b57fcff2032b1c6090b377b266925fca04fd2e60e45ca7752af541d411ab060" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b"}},- "G3Pb1":"e37175ff13749abcc954da3a3001c2f93bee48da55e871e93fbc2bdab9de033a"+ "G3Pb2":"3361a54e4ef6ea77c8080d47866ca87eb77f5a9e9f5cfe58ffe178f253387d3a" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c"}},- "G3Pb1":"ee37dcaf0be9e105569b328d6d0f55ba47a169c484be16c3583ca7a0cfc974b7"+ "G3Pb2":"1b054dd92cc7157983382d1891ef765dcdfc1bc7829a866595ed4f32af9f6ad2" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d"}},- "G3Pb1":"55babd9ed724cb1d5587aa7ec0414be1daa10b02625f07cc432d4285954a11b3"+ "G3Pb2":"2f6f21f4f7233574511a88272e8eddc3fddfb55c953bd31dd50c8e25d140d230" }, {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e"}},- "G3Pb1":"27095bf252c4692d99ee7e6057c2b0f8486c1b6a49eed72ece33477b18b04b63"+ "G3Pb2":"35ee3443689537994e38c664eff94d507c317d964f23466e83071de003794f5c" },- {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e40"}},- "G3Pb1":"6645b3d1781caebbe51d472f56bc2269d04a234c51f191ffab03df09b57516a5"+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"}},+ "G3Pb2":"ec2d877962abc757f6d8d9463a95635c38f09d0af7c33d27cb17dbb6d4591f0a" },- {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e4041"}},- "G3Pb1":"eb4d51310ba5e1eb684515f6a2b1478517b2ce96f3964dc4dc2c7ed905730135"+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40"}},+ "G3Pb2":"26ddb6d1e9956ad578214654dc58f811a885f6aff574b92f9618c901e8c645e3" },- {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142"}},- "G3Pb1":"e276e59c52481296a0c94bac8a278d216b21795066d2e196cd211c17497df7c3"+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f4041"}},+ "G3Pb2":"de302491a3c86530d00d3c703d49e891a148de1a7944bd6d2d831658a13d297d" },- {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e40414243"}},- "G3Pb1":"4604f2bbbb246fba73a5a59b7241cb55ed96375c59be780b1cb8b28fb2c92751"+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142"}},+ "G3Pb2":"090bf820d12ef8e4093ac9899701ae40781c0a5c6213052f23f26da272382e42" },- {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e4041424344"}},- "G3Pb1":"fbb15f395fdb8bcc9c041c56240b5ff4c309976ef40bb5561c8d7ab00b8072ef"+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40414243"}},+ "G3Pb2":"e1642a91e7e320a7b57db075b2f647dfbb56ebcdeb9c6eff7ad2eb8936e4651b" },- {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445"}},- "G3Pb1":"e5fd309b5ab3e5549e8ca27a78746d5961f549afa2881e23bbf9eb5a6ad1089c"+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f4041424344"}},+ "G3Pb2":"a27e0bd4001f8afcf50f323aa230b9875e0013b53c6bee93fb8c1e8fdab68d86" },- {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e40414243444546"}},- "G3Pb1":"1d82491a515e2b48272dac77434d01c645b769955e3756f0ad20e630317e2b03"+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445"}},+ "G3Pb2":"d6df95aee6452a3d37e075de17411fd07fa1791c7d78bc270dd647495f587f20" },- {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e4041424344454647"}},- "G3Pb1":"14eb800f06fa890aa3bbd73a7a90c5cc6f04b7e49390ffafa1267982c9cf7f01"+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40414243444546"}},+ "G3Pb2":"35435682a2b21103279247291575e58b15b152f5d70c47c36b6705f5cecef13d" },- {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445464748"}},- "G3Pb1":"a90437e272efb9e725e6bee828fbcf549f5cb3e6721b23fae273d1a82721fda9"+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f4041424344454647"}},+ "G3Pb2":"97433948346165daa4c3aab5301e173cee08c00154aa00efd79bcd3bd5a91a12" },- {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e40414243444546474849"}},- "G3Pb1":"e3d4058aee112881d4d421fdc7ea61228431ddf8b81470903fb6d60833c78078"+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748"}},+ "G3Pb2":"05dfd8397aa39f55105554748b259b868b9d94bed3dc8f26ff0d06c2eba3b7e4" },- {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445464748494a"}},- "G3Pb1":"bb7db09b2fe46d9ba61fb0cca6bec2a99130e24dfaddf04c2af7142c86b080e3"+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40414243444546474849"}},+ "G3Pb2":"9d5ae60d5cb4e02485098b30d9394d66699266923d3e26e944a58cbab99f73bd" },- {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445464748494a4b"}},- "G3Pb1":"92ce36869af780f1540bad4b878d4ee3274172431e18a7a8e0a154673e7c132d"+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a"}},+ "G3Pb2":"9b882fb01d5d8a6d31b4d0a5255c3146ef55042a4f161401cf2a0128a16ad7bf" },- {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445464748494a4b4c"}},- "G3Pb1":"fb96277684531ea48fa7fd04ca1a1a5dcb16b93afd9b96b12665cbb60373d4ac"+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b"}},+ "G3Pb2":"8660972bd5799216909b535bbc97c3a958bc0c5b1ab41236800fa838744d20b0" },- {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445464748494a4b4c4d"}},- "G3Pb1":"7854170c740d1d6878c81284104c40249ea9559403aaea3e33ba4a72c0ee1994"+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c"}},+ "G3Pb2":"f3b95ee7f25d04ebe50ab6d048f8d5cb2bab715b7c93e950b47440e5769b2f8f" },- {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445464748494a4b4c4d4e"}},- "G3Pb1":"748f50f1d7834f9d6d604c8c155c5530b019b66f532039788cc06383fa5ae743"+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d"}},+ "G3Pb2":"5fb7801163d0d9262f4de46effe0228e8b5fba63f2b5bc5657bf48e0a261eec4" },- {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445464748494a4b4c4d4e4f"}},- "G3Pb1":"19eaf5992eb7d6a40cc7d1a9e7599257b65606570f43f83a3ea7ecffb53f371c"+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e"}},+ "G3Pb2":"70285b22387faf0f2ae5e85e4e9fd548631d9cb8be4ec2d705abde3b8ff4647a" },- {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445464748494a4b4c4d4e4f50"}},- "G3Pb1":"8af1badbb6cfe8b76f9ab6fcfd66705cd0f8d0c4d59d4af30968b6c51d7b469a"+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f"}},+ "G3Pb2":"cc549480df68a1fac6e5ca6ebc7266d7d3956b735e7a56062b6339cc249e7576" },- {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445464748494a4b4c4d4e4f5051"}},- "G3Pb1":"7bfc6f034ba1ce335c4ac37646d03ded6af5c3959df59de0aed58777341783d9"+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f50"}},+ "G3Pb2":"74e5699a44a151c093cab5decf9a86ccb81a8729ce6092d185fec6957ecaa85a" },- {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445464748494a4b4c4d4e4f505152"}},- "G3Pb1":"db9b7aebe2a690bd64bbd14324f344918d5f6749f2bebac60d07a4540c320fda"+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f5051"}},+ "G3Pb2":"f3c79c2f45802730314b37b6fea4d6522db82559cca540eb623f60f162be9d9a" },- {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445464748494a4b4c4d4e4f50515253"}},- "G3Pb1":"568fb7b27fea31538e5a77d95adc58fe085e6e21f878958e040fe11358805fd2"+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152"}},+ "G3Pb2":"e123c7d181146e2c4921c2e4dd6754a298ecd4455c774d56fe93b4e543d7ae0b"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f50515253"}},+ "G3Pb2":"f9e706b360627bac1da22bd8083d56bfb29e0774b362ef92a28ad796a253e2d7"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f5051525354"}},+ "G3Pb2":"5db0d353035f842e6792cd5a7687e9c4c29b6a9db58afcd212daf28082b1d63b" }+ ]+},{+ "name":"foxtrot trivial inputs",+ "args": {+ "domain-tag":"",+ "bcrypt-rounds":0,+ "counter":0+ },+ "results":[+ {"G3PFoxtrot":"effb225ccab938512a5d7e050857bdaba8e9dd090621c971608312e649f68f93"}+ ]+},{+ "name":"foxtrot self-referential inputs",+ "args": {+ "key":"key",+ "inputs":["inputs","vector"],+ "domain-tag":"domain-tag",+ "long-tag":"long-tag",+ "context-tags":["context-tags","vector"],+ "bcrypt-rounds":42,+ "tweaks":["tweaks","vector"],+ "counter":"CNTR"+ },+ "results":[+ {"G3PFoxtrot":"5a4997aeca24df3b934020f94ee1f70b35d3468c792737d81182df22f70b7047"}+ ]+},{+ "name":"tango trivial inputs",+ "args": {+ "key":"",+ "inputs":[],+ "counter":0,+ "domain-tag":""+ },+ "results":[+ {"G3PTango":"9b4032ed4312a0ca4c1701f239e03e2770298ca7b790ebd550dd7b16db078f67"}+ ]+},{+ "name":"tango self-referential inputs",+ "args": {+ "key":"key",+ "inputs":["inputs","vector"],+ "counter":"CNTR",+ "domain-tag":"domain-tag"+ },+ "results":[+ {"G3PTango":"d6b55df3e07b59153829818808b4bdd997d7a89b5ead33e353b4bd5ec781a9bc"} ] }]
+ g3pb1-test-vectors.json view
@@ -0,0 +1,522 @@+[{+ "name":"prehash first light",+ "args": {+ "username":"Yuri",+ "password":"default remote access code",+ "long-tag":"Please leave the location of America's nuclear wessels after the beep.",+ "domain-tag":"1-800-CALL-SPY",+ "phkdf-rounds":1998,+ "bcrypt-rounds":7+ },+ "results":[+ {"G3Pb1":"d6ad3dd2b82b8f279b39a1c667ed247701a2a93702a37f00e867cc3bb7a3c21123b5545d90ee2e4f196fcc81b74dd8c8ad8ae867c8140b3a90ca34dd7eca01f1"},+ {"args":{"seguid":{"hex":"60473b8010e16d464314a11c2620a8ad99af49ae25474f877e57f6c27c58ca7a3538b58385eabbbbb540a350491291c870a4f12c8569485100da96f4202c3630"}},+ "G3Pb1":"c721fd9ec639fdb104afbd371d80e5253e330c4a66583dff11aef19d02fb84ec6386e5ea2a5899c9155a5dbe59cf21eda2db8a29d79a2199a287436d3c458f64"}+ ]+},{+ "name":"prehash first light, elaborated",+ "args": {+ "username":"Yuri",+ "password":"default remote access code",+ "credentials":["The Indiana Academy for Science, Mathematics, and Humanities"],+ "long-tag":"Please leave the location of America's nuclear wessels after the beep.",+ "domain-tag":"1-800-CALL-SPY",+ "seed-tags":["United States Army Counterintelligence Tip Line"],+ "role":["prankster"],+ "echo-tag":"Star Trek IV: The Voyage Home https://www.youtube.com/watch?v=MdSJFrhb-HM",+ "phkdf-rounds":1998,+ "bcrypt-rounds":7+ },+ "results":[+ {"G3Pb1":"60473b8010e16d464314a11c2620a8ad99af49ae25474f877e57f6c27c58ca7a3538b58385eabbbbb540a350491291c870a4f12c8569485100da96f4202c3630"}+ ]+},{+ "name":"prehash unicode test, inset naval story with a reference",+ "args": {+ "username":"Р-360 «Нептун»",+ "password":"Русский военный корабль, иди нахуй",+ "seed-tags":["Державне Київське конструкторське бюро «Луч»"],+ "echo-tag":"Збройні Cили України «ЗСУ»",+ "domain-tag":"Україна",+ "phkdf-rounds":2022,+ "bcrypt-rounds":8+ },+ "results":[+ {"G3Pb1":"0c06f683f093cb899b4a1e9836fc72812d22941716c05368097b912a328deec579fa21edf7aef04ec808a50bd8757cb734734cc96b00c224abe8642ce94088b3"},+ {"args":{"bcrypt-tag":"Державне Київське конструкторське бюро «Луч»"},+ "G3Pb1":"8660937862a0644410b7dd1a495ddd3dbdd05b3b43669ae2ec8dfe863f8e99d673f61c89486b7855a62936ea8684bb82e429f681abaaf571193dd1275e21481e"+ },+ {"args":{"bcrypt-tag":"«ЗСУ» Збройні Cили України «Луч» Державне Київське конструкторське бюро"},+ "G3Pb1":"d59452447147d268a81249304430d7859e8b5c135b260f9b366db37c38c10aa9c6ab83f05bc529607023a3cae5248e7e6a825e69fa4a62f4a1fede386195cb33"}+ ]+},{+ "name":"prehash unicode test, lightly extended on top",+ "args": {+ "username":"Лев Николаевич Толстой",+ "password":"Книга моя, как я и ожидал, была задержана русской цензурой, но отчасти вследствие моей репутации как писателя, отчасти потому, что она заинтересовала людей, книга эта распространилась в рукописях и литографиях в России и в переводах за границей и вызвала, с одной стороны, от людей, разделяющих мои мысли, ряд сведений о сочинениях, писанных об этом же предмете, с другой стороны, ряд критик на мысли, высказанные в самой книге.",+ "domain-tag":"Царство Божие внутри вас",+ "long-tag":"В другой брошюре, под заглавием: «Сколько нужно людей, чтобы преобразить злодейство в праведность», Адина Баллу говорит: «Один человек не должен убивать. Если он убил, он преступник, он убийца. Два, десять, сто человек, если они делают это, — они убийцы. Но государство или народ может убивать, сколько он хочет, и это не будет убийство, а хорошее, доброе дело. Только собрать побольше народа, и бойня десятков тысяч людей становится невинным делом. Но сколько именно нужно людей для этого? Вот в чем вопрос. Один не может красть, грабить, но целый народ может. Но сколько именно нужно для этого? Почему 1, 10, 100 человек не должны нарушать закона бога, а очень много могут?»",+ "tags":["Мы будем стараться распространять свои взгляды среди всех людей, к каким бы народам, исповеданиям и слоям общества они ни принадлежали. Для этой цели мы будем устраивать публичные чтения, распространять печатные объявления и брошюры, составлять общества и подавать прошения во всякие правительственные учреждения. Вообще будем стремиться всеми доступными для нас средствами к достижению коренного переворота во взглядах, чувствах и действиях нашего общества относительно греховности насилия по отношению к внешним и внутренним врагам. Принимаясь за это великое дело, мы вполне сознаем, что наша искренность может быть подвергнута жестоким испытаниям. Наша задача может навлечь на нас оскорбления, обиды, страдания и даже смерть. Нас ожидает непонимание, ложное толкование и клевета. Против нас должна подняться буря. Гордость и фарисейство, честолюбие и жестокость, правители и власти, — всё это может соединиться, чтоб уничтожить нас. Таким образом поступали с мессией, которому мы стремимся подражать по мере сил своих. Но нас не пугают эти ужасы. Мы надеемся не на людей, а на всемогущего господа. Если мы отказались от человеческого заступничества, что же может поддержать нас, как не одна вера, побеждающая мир? Мы не будем удивляться тем испытаниям, которым мы подвергнемся, а будем радоваться тому, что удостоимся разделить страдания Христа.","Вильям Ллойд Гаррисон"],+ "phkdf-rounds":1894,+ "bcrypt-rounds":66+ },+ "results":[+ {"G3Pb1":"9c08053b7e507a78b571b5b93e1326674540d7106da6408fcafeddcfcdf1ed76177ae099e711fd570d484af8e9fc548ba03d8bb545f449fdf6e86a9243d8c0f7"},+ {"args":{"seguid":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40"}},+ "G3Pb1":"7db250698fe555f6832f33189f97e14ef3c1c2dcada5807119aa7676c24f3fac8d37ecf118059f51614f4d2c7ceecb3224047abec84e0d009f43fbfe984abd28"},+ {"args":{"seguid":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f4041"}},+ "G3Pb1":"4efe93dc422d0b67e9ad5d6d4743bc3f475d805e17f2657d57d57505969d3de56622054aedf3b8fec79e2aa48a2228530b7a11c23a520cc67d70d90721f4fc83"}+ ]+},{+ "name":"xkcd account problems",+ "args":{+ "username":"Cueball",+ "password":{"hex":"7061737300776f7264"},+ "credentials":["input method not included"],+ "domain-tag":"https://xkcd.com/2700/",+ "phkdf-rounds":2022,+ "bcrypt-rounds":16+ },+ "results":{+ "G3Pb1":"c09df48d21af7c83fe124e518ec40e84abada6b176b3ee99b35701fa2c0af99053ea538a8498d30e19837e7f5b4e200d08e11995d58f1c3d5a003b4bf1bc3089"+ }+},{+ "name":"xkcd password strength #1",+ "args":{+ "username":"Anonymous Coward",+ "password":"Tr0ub4dor&3",+ "domain-tag":"https://xkcd.com/936/",+ "phkdf-rounds":2011,+ "bcrypt-rounds":5+ },+ "results":{+ "G3Pb1":"fb404c0874e90da9c9bd56e77000c6717482869c942fbac8feee4cb46c3d7e65371bf00071fd0f4a5707d3e3690acd534614a34176fcff704093a350975fa9ed"+ }+},{+ "name":"xkcd password strength #2",+ "args":{+ "username":"Randall Munroe",+ "password":"correct horse battery staple",+ "tags":[+ "Diceware","https://theworld.com/~reinhold/diceware.html","Arnold Reinhold",+ "https://www.eff.org/dice","Electronic Frontier Foundation"+ ],+ "domain-tag":"xkcd.com/936/",+ "phkdf-rounds":2011,+ "bcrypt-rounds":5+ },+ "results":[+ {"G3Pb1":"290cf026e6860f78b558cd7deb3d01442f58d3fd90742bc4759407d975b0220fb9ab36dd7f8ec48705c3f55017d5ea2ad69dbf018e25243f7787ee8ba5578c55"},+ {"args":{"phkdf-rounds":65536,+ "bcrypt-rounds":256},+ "G3Pb1":"200210b6ad928c550b842adb12b2b621dba60f7465e456ac0440da9d8fb539e39811ad3a84d010a08cdb3d8d640da5f67f82fb78715cc6beacc149963df5ead3"},+ {"args":{"phkdf-rounds":65535,+ "bcrypt-rounds":255},+ "G3Pb1":"872420a152eaedbd035e5aa488fa089c5ad59de5d5013dbf045dc72d9c3668e6e354b1dcbcbd6a9521fd9cf071724fabafbf914ed170c9e04becab9b823fd9a7"},+ {"args":{"phkdf-rounds":20240,+ "bcrypt-rounds":4095},+ "G3Pb1":"650c06d2baa0a6a0e326b02328cf2ef0183cc5ad9211c56f4726e8a43d6f7c692fb1814ca57bf29bcbabc46a4c11d5b1b2af15b8631bfcd2a9bda6b71b540c8d"},+ {"args":{"phkdf-rounds":42024,+ "bcrypt-rounds":4202},+ "G3Pb1":"7014dad47f0e7f7157d99b39a06553ce392884314bac76929f3f055bb0350b8f1920972aa1b5a2ea200d19f5d08b594cc9d891dd6913764f0bc080e332ac05e7"},+ {"args":{"phkdf-rounds":0,+ "bcrypt-rounds":4202},+ "G3Pb1":"fdb25ce5a9a782ce6dbdf3c6e00f02c4710b1b11388eff350a4ad032a66e38c64e80fa038cb6f9db3e29f49b169f846a3ea3a9bed3f7ca4ef264403a3f4b0f35"},+ {"args":{"phkdf-rounds":42024,+ "bcrypt-rounds":0},+ "G3Pb1":"34c1b5a3009161dabb4ef2a526dab2ed9154866b46251cae266e77c8125373af88cbb4afd03a11b526859feff12d3a4d43a375a7962011cc136d9c709c8db3fa"}+ ]+},{+ "name":"xkcd password reuse #1",+ "args":{+ "username":"Imperfect User",+ "password":"Password reuse between multiple deployments of the G3P can be safe(-ish), but adds risk and fragility, often meaningfully so. See https://xkcd.com/792/",+ "domain-tag":"Every authentication service provider inherently has offline cracking attacks against its own password database, even if PAKE is used. When you set a password, you are inherently providing that service with the means to verify your password. If a password is reused and its entropy is low enough to be cracked, access to the right authentication database can be used to bootstrap attacks on other accounts.",+ "phkdf-rounds":2010,+ "bcrypt-rounds":4+ },+ "results":{+ "G3Pb1":"f2efe2dd90f30d7509f427cbb06759e0ceaf08e51e0a33c325263a861b81c803f59374fa151c0af5904700adecf392c3bffb921d0a2b7d7c1052ef27ba9fcacc"+ }+},{+ "name":"xkcd password reuse #2",+ "args":{+ "username":"Imperfect User",+ "password":"Password reuse between multiple deployments of the G3P can be safe(-ish), but adds risk and fragility, often meaningfully so. See https://xkcd.com/792/",+ "domain-tag":"How do you know that your password is really being prehashed on your own device? Even if this does indeed happen, how do you know that it happens every single time? In the context of a webapp, it's relatively easy to covertly inject arbitrary code into selected page loads. Thus it is possible that the first 99 times you log into a webpage, you get a completely legitimate login page that properly prehashes your password in your browser. But on that 100th login, maybe you are instead presented with a webpage that steals your password in addition to its normal login functionality. It would be relatively easy to make such an attack covertly in such a way that is likely to turn surreptitious, leaving no trace that the attack ever happened. This is easiest to accomplish if the attacker is providing the webapp, or has the cooperation of those who do. However this isn't always necessary. At the start of the Arab Spring, Facebook's login page was not delivered over a TLS-authenticated channel. This oversight allowed state actors to steal user's passwords by injecting their own malicious code into Facebook's login page as it traversed Internet Service Providers inside their respective countries. Installed applications can be somewhat more resistant to selective-pageload style attacks, but more generally, malware on endpoints is a significant attack vector to be concerned about. Shoulder-surfing attacks upon password entry are another. Consider these things carefully when making decisions with security implications, such as whether or not to reuse a password and in what context(s).",+ "phkdf-rounds":2010,+ "bcrypt-rounds":4+ },+ "results":{+ "G3Pb1":"19367746312da7c3c938fc4e5264368e206f89812b84d4fa479cc7e7f66f07882d5788c1d309af46b95aa2c433186808b21fee501aa14b35ab2a9c9f5ccb0ed7"+ }+},{+ "name":"trivial inputs",+ "args":{+ "username":"",+ "password":"",+ "credentials":[],+ "seguid":"",+ "domain-tag":"",+ "long-tag":"",+ "echo-tag":"",+ "phkdf-rounds":0,+ "bcrypt-rounds":0,+ "bcrypt-tag":"",+ "bcrypt-salt-tag":"",+ "role":[],+ "seed-tags":[]+ },+ "results":{+ "G3Pb1":"2d0fb502c5e036112ca922e6bb5bd3e41acbd79a3223a42c3a1f08fd92398dfb"+ }+},{+ "name":"password padding",+ "args":{+ "username":"password padding (tests with very long inputs)",+ "password":"back references",+ "domain-tag":"your-domain-name-here.example",+ "long-tag":{"ref":"prehash first light, elaborated","len":5112},+ "phkdf-rounds":255,+ "bcrypt-rounds":15,+ "bcrypt-tag":""+ },+ "results":[+ {"G3Pb1":"1e55f1cb8f9ef582beaa176608ad33e65d56fe56898efe648a0b6993a50f4281"+ },+ {"args":{"long-tag":{"ref":"prehash first light, elaborated","len":5113}},+ "G3Pb1":"1ed3ddaea18428b04ac9b8028e6cae82ef08bdd1fa7c71337aa67e7bcd0a1c5d"+ },+ {"args":{+ "username":{+ "ref":"prehash unicode test, lightly extended on top",+ "len":3045+ }},+ "G3Pb1":"f79d27d104ce609e18609fffd72ca3b15d4b4a354dc6d2c686e6a92079654d59"+ },+ {"args":{+ "username":{+ "ref":"prehash unicode test, lightly extended on top",+ "len":3046+ }},+ "G3Pb1":"92dcd256761116151048601e171a3f9ae9da41d59fd754ee05043f139e0916b0"+ },+ {"args":{+ "username":{+ "ref":"prehash unicode test, lightly extended on top",+ "len":3045,+ "index":1+ },+ "password":{+ "ref":"prehash unicode test, inset naval story with a reference",+ "len":101,+ "index":1+ }},+ "G3Pb1":"2b71c3780105cc1379642f6ceb3e2d788ca96d7d514d8befc79c1bc403b14991"+ },+ {"args":{+ "username":{+ "ref":"prehash unicode test, lightly extended on top",+ "len":3045,+ "index":1+ },+ "password":{+ "ref":"prehash unicode test, inset naval story with a reference",+ "len":102,+ "index":1+ }},+ "G3Pb1":"a8b6f029b25f1f57e3dac00b1f20bac6f1078230dbf0dfc59c4ae2c480965c73"+ },+ {"args":{+ "long-tag":{"ref":"prehash first light","len":5049,"index":1},+ "username":{+ "ref":"prehash unicode test, lightly extended on top",+ "len":3109,+ "index":2+ },+ "password":{+ "ref":"prehash unicode test, inset naval story with a reference",+ "len":164+ }},+ "G3Pb1":"12a5118df13e45434b61d168c3c46947bb00c8f97f14675691eb97c25630c7b5"+ },+ {"args":{+ "long-tag":{"ref":"prehash first light","len":5049,"index":1},+ "username":{+ "ref":"prehash unicode test, lightly extended on top",+ "len":3109,+ "index":2+ },+ "password":{+ "ref":"prehash unicode test, inset naval story with a reference",+ "len":165+ }},+ "G3Pb1":"d833526e21357efc9cdb0fc9a650d7289ae946b5290381aecdda9bd1bdf140ea"+ },+ {"args":{"long-tag":"","bcrypt-tag":{"ref":"xkcd password strength #2","len":112}},+ "G3Pb1":"d31fa18b6bd099775b21efd7ae0f8c9d4090195be2d932ec1c462e996dfc1812"+ },+ {"args":{"long-tag":"","bcrypt-tag":{"ref":"xkcd password strength #2","len":113}},+ "G3Pb1":"7a2e399582edaad660124e2513e3b21a9096e6ec81d920e75a7f40d796fa1c5e"+ }+ ]+},{+ "name":"domain tag tests",+ "args":{+ "username":"domain tag tests",+ "password":"",+ "phkdf-rounds":0,+ "bcrypt-rounds":0+ },+ "results":[+ {"args":{"domain-tag":""},+ "G3Pb1":"76488206562b5b42828ab34e4300701a1c9719dbb886b494f940de50dd9008fc"+ },+ {"args":{"domain-tag":{"hex":"01"}},+ "G3Pb1":"2eed4085cf87decd6dcec271bab1252a3a1688f5e4f1e01a5f51c3a527176745"+ },+ {"args":{"domain-tag":{"hex":"0102"}},+ "G3Pb1":"9ad2af4b7e5ae0d0b672927069fc03384da7c8982f17ec6fdfeccdf11df96f69"+ },+ {"args":{"domain-tag":{"hex":"010203"}},+ "G3Pb1":"c62a1ef5be515385bb7d87cbf6da08685857827e1e4133dd31892d7c987e8c7e"+ },+ {"args":{"domain-tag":{"hex":"01020304"}},+ "G3Pb1":"c5d87a518feb9ae1c30bf39317e98c4ccc9f8c07d3cc78f4328648ec08a2afc3"+ },+ {"args":{"domain-tag":{"hex":"0102030405"}},+ "G3Pb1":"e4b59846812f693d4692e5d28f70fbe2e5b16bc5b44565904f377a8b8b3e1b3c"+ },+ {"args":{"domain-tag":{"hex":"010203040506"}},+ "G3Pb1":"153642e8c40383c92b534e9bf8c2c81da55aef5c22781b97d4ffeb7c90ac8574"+ },+ {"args":{"domain-tag":{"hex":"01020304050607"}},+ "G3Pb1":"1e3f7a8203531082c0b993c957d0c989f457775f8d38007bafbcc2b2607fb49b"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708"}},+ "G3Pb1":"9e7d22be88fdc4c6d6b19668bbb4640f1b2ad783e8f2b2ab8bcc8686dec097ab"+ },+ {"args":{"domain-tag":{"hex":"010203040506070809"}},+ "G3Pb1":"926ea057bf81916ce3b9094df12c76b6e51770c2969f5af15775474c05f674e1"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a"}},+ "G3Pb1":"58aa2ca337800412b99ecd98e4ebbb465a7f28b648ceb06ccca62b37d695833d"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b"}},+ "G3Pb1":"da7a85e33611e860defd768666f4f3798b5fb32ccbcf76847cd9bd0765c74a61"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c"}},+ "G3Pb1":"c23f47fedefdedb0dd68773d4ea08eb12ef1b569c9c115e135a817deb0be67b1"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d"}},+ "G3Pb1":"35a1c4f934d7d74c7747f42212d3905a6e762e8793caef26bf850815a778c224"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e"}},+ "G3Pb1":"7c2a21846426f71eecf4ef86c9dcf7cd4abadb3ba8dd507e271b8936b9946e3a"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f"}},+ "G3Pb1":"fbd455257919f6ce7f375318b2a1acc2cd0277765e79095aefa8752ade1c6fe8"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f10"}},+ "G3Pb1":"d178a9407df73a182091e48d3454d3bba28a923b19f1811f977fb488cca7306e"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f1011"}},+ "G3Pb1":"7ebb194e3b2503c5af7348cdc91209a977cb0fe1082a4a6840af192d5b4a85a9"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112"}},+ "G3Pb1":"8e9926ed8b0220303bba8fa76434477322602de88076349bf903295e27a83eda"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f10111213"}},+ "G3Pb1":"6f7dead0b80281ad271c56f36cde0c16030d46b9c66ab5426a7b4e304f177787"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f1011121314"}},+ "G3Pb1":"95819a7a4aafa7ac45c701edceb7748059d044c069ed165bd5afd4b5287f3f30"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415"}},+ "G3Pb1":"ac6a51331ec00dfb7d33a5e5c28642204b2e752bb26f67b67c6c733c344b1f52"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f10111213141516"}},+ "G3Pb1":"e58c9f5032941e77d843adf3eacc1efcefd9ee02aea0389d4bc06e15c9d0357a"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f1011121314151617"}},+ "G3Pb1":"4a1c4bbcc544a0304d0a160624c2c22498cc249792f398221cf8eb1683f8c616"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718"}},+ "G3Pb1":"99bab93d19ca43963061bf9a90eb13a64728cbd69053ce3d2f3725ff4fc7305a"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f10111213141516171819"}},+ "G3Pb1":"ebc5e55955d0ca977ba081c868316f5dfe6c7659cc352a05024d1911995d248f"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a"}},+ "G3Pb1":"7be0875e6acb133a2a4ff27aacee8ffb8bd57c21c48e711d2ee8e97c35c754e3"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b"}},+ "G3Pb1":"b155e942c56449ae076110008c244b9f6a10deddd70bfa265af7a6e12284da61"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c"}},+ "G3Pb1":"27f3ae6a201d5dbc2a0d9335e424d611b360de377e6a421822f7eae381b0e6ab"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d"}},+ "G3Pb1":"7a5a0c50c494ba373c449c023b6a543e42dfed6473ec65c1d070e89e78f85583"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e"}},+ "G3Pb1":"7200e3f36082f5389e919bf8148ba0e98cdecd1b4eb14a71a9067e583b037bdf"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}},+ "G3Pb1":"6341748a06146f47c6bd759b615ba84d14b93de448bd773ade4e8f20ebdf6c03"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"}},+ "G3Pb1":"e8ffcab67ede795d547ffa2356daae472d6b266b520531d8d78cacff197e6db4"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021"}},+ "G3Pb1":"3c8268530283984b858331b9ecc3b54985f4760ab2b369ffa1c27924937e168e"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122"}},+ "G3Pb1":"2d0d893ddd1903dc7373206e1475d293d997dd38a9ba946283a4f206ea36d7f8"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223"}},+ "G3Pb1":"651da440e3425f1f2d69f91d5b150c572800b6613734311ea695116209455d1b"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021222324"}},+ "G3Pb1":"a951bb1592568a0c05bd41d4d0a9712e9f83829d3f800f1ed6bf5d567cc6ce15"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425"}},+ "G3Pb1":"0b4e8f76159bedf3b67541c08c3e11fedaed796d2e6ec3f97b1fc339e727edff"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223242526"}},+ "G3Pb1":"ff9b9a96cefcb04d42af036984daf11b0bb1d519027b32fd92c58a6a8fda2744"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021222324252627"}},+ "G3Pb1":"21112353bce77f3f1781b1ecde88cd205d2871ecbe86a80d8706ed327c0f7c67"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728"}},+ "G3Pb1":"17a536829a3c623697c003701cdf0ae16c1faaeec27c095a5edf93f576e37d2a"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223242526272829"}},+ "G3Pb1":"85c5fd1dfecb6e40f81c8066f984135dfd1def147e37a895ee585fff582a479f"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a"}},+ "G3Pb1":"6213f75b424b446a3b367c3400e1dcbd2050e2cdf8eac33745266737a1bae9ef"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b"}},+ "G3Pb1":"8998e460a5c834f5dd76cdcc18e7862d4ba7c06df9602f3066fe75c39e087fe2"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c"}},+ "G3Pb1":"4e0b9607bbdc9c82b1df64c8842d412e82bf4da73e53530d1f8bcadb402d3f8c"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d"}},+ "G3Pb1":"3111d270403f023c0342bcdc77c18eb430f27ccf6fbb3edc34845659534a6296"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e"}},+ "G3Pb1":"0c3270549af4cc19b5ee6ac4f669893c9a7b90f12dde29ba1e696813eedb8d98"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f"}},+ "G3Pb1":"45eceeae98b934fc01e064b8829b153951b53e3c094d3ece3a7e61c91df3e5f3"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30"}},+ "G3Pb1":"e65fea4a66668a6ee7a62f0a589615893eb4d6b202b27259bb107be1c31a7e1a"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031"}},+ "G3Pb1":"5045dd5daacd156ce916394f92b275039bc5ee084a495c006c783030bd860bd4"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132"}},+ "G3Pb1":"944e6ca6dbe90636f57612709d620bcea4f6670c25f329cf859a78a2a3403d63"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233"}},+ "G3Pb1":"27238f189fb2af713a17f85c8db9af5bbc650e19331cfa5851bcbe8a2dc6f94a"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334"}},+ "G3Pb1":"16661a1a66c625310478edea975716c1788a84861d5c3da4b7e5fd5f26b99fbf"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435"}},+ "G3Pb1":"d64e32c42b6bd819ba785e223a9ec2fa7fa564cbcd5831fc462cfe55849a8665"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343536"}},+ "G3Pb1":"22279ed0604d0bb24584f26b255e50aad4e0fd3d4a9a62943d756fbef07f95ed"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637"}},+ "G3Pb1":"eb92f4bd207342ff2d2b277889e60bd45403ce158abe3c1c15d76c774c8fb267"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738"}},+ "G3Pb1":"aeb1db443177577b9e5b0e32824b21c84b848a21b77821f987b51d49a771a243"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343536373839"}},+ "G3Pb1":"24d09f57f7d8e4186bd00a7fcd6b7c89ac631997175edc0289f641c2c84507ea"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a"}},+ "G3Pb1":"dc9ae1c446c1a196ca7829f635f3fa9537268f379cbd5eebbc2b723e4793c032"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b"}},+ "G3Pb1":"e37175ff13749abcc954da3a3001c2f93bee48da55e871e93fbc2bdab9de033a"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c"}},+ "G3Pb1":"ee37dcaf0be9e105569b328d6d0f55ba47a169c484be16c3583ca7a0cfc974b7"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d"}},+ "G3Pb1":"55babd9ed724cb1d5587aa7ec0414be1daa10b02625f07cc432d4285954a11b3"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e"}},+ "G3Pb1":"27095bf252c4692d99ee7e6057c2b0f8486c1b6a49eed72ece33477b18b04b63"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e40"}},+ "G3Pb1":"6645b3d1781caebbe51d472f56bc2269d04a234c51f191ffab03df09b57516a5"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e4041"}},+ "G3Pb1":"eb4d51310ba5e1eb684515f6a2b1478517b2ce96f3964dc4dc2c7ed905730135"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142"}},+ "G3Pb1":"e276e59c52481296a0c94bac8a278d216b21795066d2e196cd211c17497df7c3"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e40414243"}},+ "G3Pb1":"4604f2bbbb246fba73a5a59b7241cb55ed96375c59be780b1cb8b28fb2c92751"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e4041424344"}},+ "G3Pb1":"fbb15f395fdb8bcc9c041c56240b5ff4c309976ef40bb5561c8d7ab00b8072ef"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445"}},+ "G3Pb1":"e5fd309b5ab3e5549e8ca27a78746d5961f549afa2881e23bbf9eb5a6ad1089c"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e40414243444546"}},+ "G3Pb1":"1d82491a515e2b48272dac77434d01c645b769955e3756f0ad20e630317e2b03"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e4041424344454647"}},+ "G3Pb1":"14eb800f06fa890aa3bbd73a7a90c5cc6f04b7e49390ffafa1267982c9cf7f01"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445464748"}},+ "G3Pb1":"a90437e272efb9e725e6bee828fbcf549f5cb3e6721b23fae273d1a82721fda9"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e40414243444546474849"}},+ "G3Pb1":"e3d4058aee112881d4d421fdc7ea61228431ddf8b81470903fb6d60833c78078"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445464748494a"}},+ "G3Pb1":"bb7db09b2fe46d9ba61fb0cca6bec2a99130e24dfaddf04c2af7142c86b080e3"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445464748494a4b"}},+ "G3Pb1":"92ce36869af780f1540bad4b878d4ee3274172431e18a7a8e0a154673e7c132d"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445464748494a4b4c"}},+ "G3Pb1":"fb96277684531ea48fa7fd04ca1a1a5dcb16b93afd9b96b12665cbb60373d4ac"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445464748494a4b4c4d"}},+ "G3Pb1":"7854170c740d1d6878c81284104c40249ea9559403aaea3e33ba4a72c0ee1994"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445464748494a4b4c4d4e"}},+ "G3Pb1":"748f50f1d7834f9d6d604c8c155c5530b019b66f532039788cc06383fa5ae743"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445464748494a4b4c4d4e4f"}},+ "G3Pb1":"19eaf5992eb7d6a40cc7d1a9e7599257b65606570f43f83a3ea7ecffb53f371c"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445464748494a4b4c4d4e4f50"}},+ "G3Pb1":"8af1badbb6cfe8b76f9ab6fcfd66705cd0f8d0c4d59d4af30968b6c51d7b469a"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445464748494a4b4c4d4e4f5051"}},+ "G3Pb1":"7bfc6f034ba1ce335c4ac37646d03ded6af5c3959df59de0aed58777341783d9"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445464748494a4b4c4d4e4f505152"}},+ "G3Pb1":"db9b7aebe2a690bd64bbd14324f344918d5f6749f2bebac60d07a4540c320fda"+ },+ {"args":{"domain-tag":{"hex":"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e404142434445464748494a4b4c4d4e4f50515253"}},+ "G3Pb1":"568fb7b27fea31538e5a77d95adc58fe085e6e21f878958e040fe11358805fd2"+ }+ ]+}]
lib/Crypto/G3P.hs view
@@ -1,551 +1,11 @@-{-# LANGUAGE OverloadedStrings #-}--{- |--The [Global Password Prehash Protocol (G3P)](https://github.com/auth-global/self-documenting-cryptography/blob/prerelease/design-documents/g3p.md)-is a slow, attribution-armored password hash function and cryptographic key-derivation function. It supports [self-documenting deployments](https://www.cut-the-knot.org/Curriculum/Algebra/SelfDescriptive.shtml)-whose password hashes are /traceable/ or /useless/ after they have been /stolen/.-This secondary security goal seeks to use [/cryptoacoustics/](https://github.com/auth-global/self-documenting-cryptography/)-to provide [/embedded attributions/](https://joeyh.name/blog/entry/attribution_armored_code/)-that are as difficult as possible for an adversarial implementation to remove.--The G3P revisits the role of cryptographic salt, splitting the salt into the-cartesian product of the /seguid/, /username/, and /tag/ parameters. Any-parameter with "tag" as part of the name is an embedded attribution to anybody-providing the inputs to the /username/ or /password/ parameters. Tags are-themselves directly self-documenting embedded attributions, in the sense that-one cannot easily or efficiently replace the tag with anything else without-losing the ability to compute the correct hash function.--The /seguid/ corresponds to the key used for every call to HMAC-SHA256, right up-until final output expansion. In this way the G3P mimicks the construction of-HKDF, with the seguid corresponding to HKDF's /salt/ parameter. The G3P also-mimicks PBKDF2 used in an alternate mode of operation.--The seguid can be trivially replaced with a /precomputed HMAC key/, thus the-seguid is not a direct tag. However this precomputed key is a cryptographic-hash of the seguid, and for this reason the seguid is capable of serving as-an /indirect/ tag, which the Seguid Protocol is designed to utilize via-Self-Documenting Globally Unique Identifiers (seguids).--It is strongly recommend a deployment identify itself with a single 64-byte-(512-bit) seguid, and the deployment's choice of plaintext messages to be-delivered via tags. These salts can be constants across the entire deployment,-as the username is intended to be used as the final bit of salt within a-deployment.--In a traditional password hash function, the salt is a random bytestring-typically between 8 and 32 bytes long. One of its primary purposes is to-identifiy a unique hash function so that one cannot attempt to crack multiple-password hashes with a single key-stretching computation. Oftentimes this-is implemented by storing a salt per user.--However, in the context of a client-side prehash, storing a salt per user has-the potential to leak whether or not an account exists, or if a password has-changed. The G3P has the option to eliminate these complications, because-it is safe to use a plain username as the salt, in addition to the-deployment-identifying seguid and tags.--On the other hand, if one is aware of the potential issues surrounding the-implementation of a random per-user salt in a client-side hashing context, and-is willing to mitigate or live with them, then there are potential advantages-to using a random salt as the input to the G3P's @username@ parameter instead.--All parameter names are suggestive, not prescriptive. Usage is ultimately-defined by the deployment.--When somebody is guessing a username, they must also know (or guess) the-password. However, the username need not be revealed to somebody who is guessing-the password, as the raw username can always be replaced by a precomputed hash.-If this intentional feature is not desired, a deployment might choose to swap-the username and password, as these inputs are otherwise functionally identical.--The usage and interpretation(s) of any given parameter is always defined by the-deployment, and is never defined by offical G3P documentation or specifications.--The G3P always has room for more salt. It doesn't really make sense to inject-more than 256 bits of entropy into the username parameter, because when the G3P-is partially applied to a constant username, the raw input can be replaced with-a SHA256 state. This is not true of any of the tags: it doesn't matter how long-it is, the whole tag must be present for the hash computation to be correct.--Every parameter with the word _tag_ in its name exhibits this property.-Theoretically, one could specify a G3P-based hash function that requires-terabytes of salt to be hashed billions of times over. However it is unclear-what purpose such an impractical specification might serve.--This initial variant of the G3P employs a combination of PHKDF and bcrypt.-PHKDF serves as the primary cryptoacoustic component, and bcrypt serves as the-primary key-stretching component of the G3P. Both are secondarily used in the-alternate role as well, with the PHKDF adding a tiny bit of key stretching and-bcrypt providing significant additional cryptoacoustic plaintext repetitions.--1. Every bit of every parameter matters. Every boundary between parameters- matters. The presence and position of every null byte and every empty- string matters. There aren't supposed to be any trivial collisions, the- only exception being null-byte extension collisions on the seguid, which- serves as an HMAC-SHA256 key.--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 minimized.-- 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.--Note that the username, password, long-tag, and credentials vector are all-/horn-loaded inputs/ in the sense that they are consumed a constant number of-times near the beginning of the hashing protocol, and after each PHKDF-round, the hash with the least key-stretching applied is discarded.--This implies that particularly paranoid password-handling implementations can-eliminate the password from memory even before key-stretching is complete.-Additionally, assuming all the sensitive secrets are contained in horn-loaded-parameters, this implies the key-stretching computation can be relocated at-nearly any time with full credit for any key-stretching already performed.--One of the associated costs is that collisions on horn-loaded inputs can be-found over the entire G3P by "only" colliding the first call to HMAC-SHA256,-/G3Pb1 alfa/. If it were trivial to produce collisions on HMAC-SHA256, this-would very likely make collisions on the horn-loaded inputs trivial. However-such an attack would be unlikely to be able to immediately produce collisions-that vary any of the other inputs. This is because all the other inputs are-repeated elsewhere in the protocol, thus colliding /G3Pb1 alfa/ isn't enough-to collide the final output of the G3P.--This "cost" seems acceptable in the context of password-based authentication-flows, where collision resistance and second preimage resistance are not-directly relevant. What is crucially important is preimage resistance and-maximizing the cost of parallelizing multiple key-stretching computations while-minimizing the latency of a single key-stretching computation.---}--module Crypto.G3P where--import Control.Exception(assert)-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- ( add64WhileLt- , cycleByteString- , cycleByteStringWithNull- , usernamePadding- , passwordPaddingBytes- , credentialsPadding- )-import Crypto.Encoding.SHA3.TupleHash-import Crypto.PHKDF.Primitives-import Crypto.PHKDF.Primitives.Assert-import Crypto.G3P.BCrypt----- | 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. In this--- role, all these parameters function as salt.------ 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 to use the deployment to spec.------ The remaining string parameters are all directly-documenting, embedded--- attributions. A deployment can use these tags to encode a message into the--- password hash function so that it must be known to whomever can compute it.--- There are a variety of different parameters because there are different--- lengths of messages that can be expressed for free, and there are different--- incremental costs for exceeding that limit.------ It is particularly important to include some kind of actionable message--- in the @domainTag@, @longTag@, @bcryptTag@, and @bcryptSaltTag@--- parameters. Specifying an empty string in any of these parameters--- means that a significant quantity of cryptoacoustic messaging space will--- be filled with silence.------ Especially useful messages include URIs, legal names, and domain names.--data G3PInputBlock = G3PInputBlock- { g3pInputBlock_seguid :: !ByteString- -- ^ HMAC-SHA256 key, usable as a high-repetition indirect tag via- -- self-documenting globally unique identifiers (seguids).- , g3pInputBlock_domainTag :: !ByteString- -- ^ plaintext tag with one repetition per PHKDF round. 0-19 bytes are- -- free, 20-83 bytes cost a additional sha256 block /per PHKDF round/,- -- with every 64 bytes thereafter incurring a similar cost.- --- -- Tags up to 83 or maybe even 147 bytes long might be reasonable.- -- In the case of longer domain tags, it is strategically advantageous- -- to ensure that the first 32 bytes are highly actionable.- --- -- This parameter provides [domain separation](https://csrc.nist.gov/glossary/term/domain_separation).- -- A suggested value is a ICANN domain name controlled by the deployment.- -- The name is also a bit of an homage to the "realm" parameter of HTTP- -- basic authentication, which in part inspired it.- , g3pInputBlock_longTag :: !ByteString- -- ^ plaintext tag with 1x repetition, then cycled for roughly- -- 8 kilobytes. Constant time on inputs up to nearly 5 kilobytes.- --- -- Overages incur one sha256 block per 64 bytes.- , g3pInputBlock_tags :: !(Vector ByteString)- -- ^ plaintext tags with 3x repetition. Constant-time on 0-63 encoded bytes,- -- which includes the length encoding of each string. Thus 60 of those- -- free bytes are usable if the tags vector is a single string, or less if- -- it contains two or more strings.- --- -- Overages incur three sha256 blocks per 64 bytes.- --- -- This parameter is notable because it is the least expensive purely- -- auxiliary input that is not horn-loaded. Thus if you want a very long- -- salt input that provides a bit of extra collision resistance, (because- -- the collision resistance of HMAC-SHA256 isn't enough?!) this would- -- be a logical candidate input location to consider.- , g3pInputBlock_phkdfRounds :: !Word32- -- ^ How expensive will the PHKDF component be? An optimal implementation- -- computes exactly three SHA256 blocks per round if the domain tag is- -- 19 bytes or less, plus a reasonably large but constant number of- -- additional blocks. I recommend at least 20,000 rounds, if not 40,000.- -- You might consider adjusting that recommendation downward in the- -- case of domain tags that exceed 19 bytes in length: 15,000 rounds- -- of PHKDF with a domain tag that is 83 bytes long should cost about- -- the same number of SHA256 blocks as 20,000 rounds of PHKDF with a- -- domain tag that is 19 bytes long.- , g3pInputBlock_bcryptRounds :: !Word32- -- ^ How expensive will the bcrypt component be? 4000 rounds recommended,- -- give or take a factor of 2 or so. Each bcrypt round is approximately- -- as time consuming as 60 PHKDF rounds. Using the recommendation, the- -- cost should be dominated by bcrypt.- , g3pInputBlock_bcryptTag :: !ByteString- -- ^ Repeated once or twice per bcrypt round, plus once in PHKDF.- -- This tag has exactly two full repetitions per bcrypt round- -- when the tag is up to 56 bytes long. Above 56 bytes,- -- this tag is cyclically extended to 112 bytes and then split- -- into two strings of 56 bytes, each repeated once. Hashed- -- once in PHKDF to avoid any truncation gotchas, and to force- -- recomputation of PHKDF if this tag is varied.- --- -- 0-112 bytes can be handled in a constant number of cryptographic- -- operations. Overages incur a cost of one SHA-256 block per- -- 64 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 alongside the bcrypt tags are the--- least expensive pay-as-you-go options for plaintext tagging.------ Note that the username and password are subjected to additional length--- hardening. The G3P operates in a constant number of SHA256 blocks so long--- as the combined length of the username and password is less than about--- 3 KiB, or the combined length of the username, password, and long tag is--- less than about 8 KiB. The actual numbers are somewhat less in both cases,--- but this is a reasonable approximation. Note that the bcrypt tags can--- subtract up to 114 bytes from the 8 KiB total, and don't effect the 3 KiB--- total.------ In the case of all of the inputs in this record, longer values incur one--- SHA256 block per 64 bytes.--data G3PInputArgs = G3PInputArgs- { g3pInputArgs_username :: !ByteString- -- ^ constant time on 0-101 bytes, or if any of the other conditions are met.- , g3pInputArgs_password :: !ByteString- -- ^ constant time on 0-101 bytes, or if any of the other conditions are met.- , g3pInputArgs_credentials :: !(Vector ByteString)- -- ^ constant time on 0-90 encoded bytes. This includes a variable-length- -- field that encodes the bit length of each string; this field itself- -- requires two or more bytes per string.- } 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 the G3P--- 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 ensure that these--- parameters themselves are constant-length. Alternatively, a deployment--- could specify an additional variable-length string in the role vector,--- used to control the ending position relative to the SHA256 buffer.--newtype G3PInputRole = G3PInputRole- { g3pInputRole_roleTags :: Vector ByteString- -- ^ This is the least expensive parameter that will vary the secret HMAC- -- key used to generate the final output. Very much analogous to HKDF's- -- initial keying material (ikm) parameter. This is the recommended last- -- call for mixing additional secrets into the output.- } deriving (Eq, Ord, Show)--newtype G3PInputEcho = G3PInputEcho- { g3pInputEcho_echoTag :: ByteString- -- ^ the absolute least expensive parameter to vary, if your implementation- -- supports it. Very much analogous to HKDF's info parameter. 0-19 bytes- -- are free. This incurs a cost of one SHA-256 block per output block at- -- 20 bytes and every 64 bytes thereafter.- } deriving (Eq, Ord, Show)---- | A plain-old-data explicit representation of the intermediate 'g3pHash'--- computation after the 'G3PInputBlock' and 'G3PInputArgs' 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 'g3pHash_seedInit' and consumed--- by 'g3pHash_seedFinalize'.--data G3PSeed = G3PSeed- { g3pSeed_seguid :: !ByteString -- ^ filled in for convenience by 'g3pHash_seedInit', but ignored by 'g3p_seedFinalize'- , g3pSeed_seguidKey :: !HmacKey- , g3pSeed_domainTag :: !ByteString- , g3pSeed_secret :: !ByteString- } deriving (Eq)--data G3PKey = G3PKey- { g3pKey_secret :: !ByteString- , g3pKey_secretKey :: HmacKey- , g3pKey_domainTag :: !ByteString- } deriving (Eq)--data G3PGen = G3PGen- { g3pGen_secret :: !ByteString- , g3pGen_phkdfGen :: !PhkdfGen- }---- | The Global Password Prehash Protocol (G3P). Note that this function is very--- intentionally implemented in such a way that the following idiom is--- efficient. It performs the expensive key stretching phase only once.------ @--- let mySeed = g3pHash block args--- myKey0 = mySeed myRole0--- myKey1 = mySeed myRole1--- in [ myKey0 myEcho , myKey0 altEcho, myKey1 myEcho, myKey1 altEcho ]--- @------ This expression also only performs 2 output key computations, though this--- is very fast compared to the stretching applied to the seed. It's still--- slower than varying only the echo tag. Thus we end up with four--- cryptographically independent bytestreams.+-------------------------------------------------------------------------------+-- |+-- Module: Crypto.G3P+-- Copyright: (c) 2024 Auth Global+-- License: Apache2 ----- In the case that you want or need to persist or serialize the intermediate--- seed, or change the seguid or domain tag before final output expansion,--- then the plain-old-datatype 'G3PSeed' and its companion functions--- 'g3pHash_seedInit' and 'g3pHash_seedFinalize' are needed.--g3pHash :: G3PInputBlock -> G3PInputArgs -> G3PInputRole -> G3PInputEcho -> Stream ByteString-g3pHash block args role echoTag =- g3pHash_seedInit block args &- g3pHash_keyInit role &- g3pHash_finalizeGen echoTag &- g3pGen_finalizeStream---- | This generates a seed, which encapsulates the expensive key-stretching--- component of 'g3pHash' into a reusable, tweakable cryptographic value.--- This function is way slower than it's companion, 'g3pHash_seedFinalize'.--- Broadly comparable to @HKDF-Extract@, though with key stretching built-in.--g3pHash_seedInit :: G3PInputBlock -> G3PInputArgs -> G3PSeed-g3pHash_seedInit block args =- G3PSeed {- g3pSeed_seguid = seguid,- g3pSeed_seguidKey = seguidKey,- g3pSeed_domainTag = domainTag,- g3pSeed_secret = secret- }- 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 = g3pInputBlock_domainTag block- seguid = g3pInputBlock_seguid block- longTag = g3pInputBlock_longTag block- seedTags = g3pInputBlock_tags block- phkdfRounds = g3pInputBlock_phkdfRounds block- bcryptRounds = g3pInputBlock_bcryptRounds block- bcryptTag = g3pInputBlock_bcryptTag block-- username = g3pInputArgs_username args- password = g3pInputArgs_password args- credentials = g3pInputArgs_credentials args-- headerAlfa = [ "G3Pb1 alfa username", username ]-- headerUsername = headerAlfa ++ [- usernamePadding headerAlfa bcryptTag- (domainTag <> "\x00password G3Pb1\x00")- ]-- -- password will go here-- headerLongTag =- [ longTag- , B.concat- [ "Global Password Prehash Protocol bcrypt (v1) G3Pb1"- , leftEncode phkdfRounds- , bareEncode bcryptRounds- ]- ]-- longPadding = passwordPaddingBytes bytes headerUsername headerLongTag- longTag (domainTag <> "\x00creds G3Pb1\x00") password- where- bl = encodedByteLength bcryptTag- bytes = add64WhileLt (8413 - bl) 8298-- credsPadding = credentialsPadding credentials bcryptTag- (domainTag <> "\x00tags G3Pb1\x00")-- seguidKey = hmacKey_init seguid-- secretStream =- phkdfCtx_initFromHmacKey seguidKey &- phkdfCtx_addArgs headerUsername &- phkdfCtx_assertBufferPosition' 32 &- phkdfCtx_addArg password &- phkdfCtx_addArg bcryptTag &- phkdfCtx_addArgs headerLongTag &- -- FIXME: fusing addArg and longPadding can save ~ 8 KiB RAM- phkdfCtx_addArg longPadding &- phkdfCtx_assertBufferPosition' 32 &- phkdfCtx_addArgs credentials &- phkdfCtx_addArg credsPadding &- phkdfCtx_assertBufferPosition' 29 &- phkdfCtx_addArgs seedTags &- phkdfCtx_addArg (bareEncode (V.length seedTags)) &- phkdfSlowCtx_extract- (cycleByteStringWithNull bcryptTag)- (word32 "go\x00\x00" + 2024) domainTag- "G3Pb1 bravo" phkdfRounds &- phkdfSlowCtx_assertBufferPosition' 32 &- phkdfSlowCtx_addArgs seedTags &- phkdfSlowCtx_finalizeStream (cycleByteStringWithNull bcryptTag)-- (Cons phkdfHash (Cons bcryptInput _)) = secretStream-- dup a = (a,a)-- (bKeyInput, bSaltInput) = B.splitAt 16 bcryptInput-- (bKeyTag, bSaltTag) =- if B.length bcryptTag <= 56- then dup $ cycleByteString (bcryptTag <> "\x00G3Pb1 bcrypt\00") 56- else B.splitAt 56 $ cycleByteStringWithNull bcryptTag 112-- bKey = bKeyTag <> bKeyInput- bSalt = bSaltInput <> bSaltTag-- bcryptHash = assert (B.length bKey == 72 && B.length bSalt == 72) $- bcryptRaw bKey bSalt bcryptRounds-- headerCharlie = B.concat [- "G3Pb1 charlie",- phkdfHash,- cycleByteStringWithNull bcryptTag 56,- bcryptHash,- cycleByteStringWithNull (domainTag <> "\x00G3Pb1 charlie\x00") 32- ]-- secret =- phkdfCtx_initFromHmacKey seguidKey &- phkdfCtx_addArg headerCharlie &- phkdfCtx_assertBufferPosition' 32 &- phkdfCtx_addArgs seedTags &- phkdfCtx_finalize (cycleByteStringWithNull bcryptTag) (word32 "SEED") domainTag---- | This consumes a seed and tweaks to produce the final output stream.--- This function is the output expansion phase of 'g3pHash'. This function--- is way faster than it's companion 'g3pHash_seedInit'. Broadly comparable to--- HKDF. Note that this function ignores 'g3pSeed_seguid' in favor of--- 'g3pSeed_seguidKey'.--g3pHash_keyInit :: G3PInputRole -> G3PSeed -> G3PKey-g3pHash_keyInit roleInput seed = G3PKey- { g3pKey_secret = secretKey- , g3pKey_secretKey = hmacKey_init secretKey- , g3pKey_domainTag = g3pSeed_domainTag seed- }- where- -- seguid = g3pSeed_seguid seed- seguidKey = g3pSeed_seguidKey seed- domainTag = g3pSeed_domainTag seed- secret = g3pSeed_secret seed-- role = g3pInputRole_roleTags roleInput-- headerDelta = B.concat [- "G3Pb1 delta",- secret- ]-- secretKey =- phkdfCtx_initFromHmacKey seguidKey &- phkdfCtx_addArg headerDelta &- phkdfCtx_addArgs role &- phkdfCtx_finalize (cycleByteStringWithNull domainTag) (word32 "KEY\x00") domainTag--g3pHash_finalizeGen :: G3PInputEcho -> G3PKey -> G3PGen-g3pHash_finalizeGen inputEcho gKey = G3PGen- { g3pGen_secret = g3pKey_secret gKey- , g3pGen_phkdfGen = echo- }- where- secretKey = g3pKey_secretKey gKey- domainTag = g3pKey_domainTag gKey- echoTag = g3pInputEcho_echoTag inputEcho-- echoHeader = cycleByteString (domainTag <> "\x00G3Pb1 echo\x00") 32-- echoCtr = word32 "OUT\x00"-- echo = phkdfGen_initFromHmacKey echoHeader echoCtr echoTag secretKey+------------------------------------------------------------------------------- -g3pGen_read :: G3PGen -> (ByteString, G3PGen)-g3pGen_read gen = let (out, next) = phkdfGen_read (g3pGen_phkdfGen gen)- in (out, gen { g3pGen_phkdfGen = next })+module Crypto.G3P ( module Crypto.G3P.V2 ) where -g3pGen_finalizeStream :: G3PGen -> Stream ByteString-g3pGen_finalizeStream = phkdfGen_finalizeStream . g3pGen_phkdfGen+import Crypto.G3P.V2
+ lib/Crypto/G3P/BCrypt.hs view
@@ -0,0 +1,330 @@+{-# LANGUAGE ViewPatterns, OverloadedStrings, BangPatterns, ScopedTypeVariables #-}++-------------------------------------------------------------------------------+-- |+-- Module: Crypto.G3P.BCrypt+-- Copyright: (c) 2024 Auth Global+-- License: Apache2+--+-- A very minimal binding to the core of the bcrypt algorithm, adapted from+-- OpenBSD's implementation. The Global Password Prehash Protocol version+-- G3Pb1 cannot be implemented in terms of standard bcrypt interfaces for+-- several reasons:+--+-- 1. Standard bcrypt hashes are truncated to 23 bytes. The G3P depends+-- on all 24 output bytes.+--+-- 2. Standard bcrypt must specify a number of rounds that is a power of+-- two. The G3P allows any number of rounds between 1 and 2^32 inclusive.+--+-- 3. the G3P needs unimpeded access to the full 72 byte password input.+-- This is not doable with all bcrypt variants.+--+-- 4. Standard bcrypt limits salt length to 16 bytes. Version 1 of the G3P+-- depends on 72 byte salt parameters, and Version 2 depends on 4168 byte+-- salts.+--+-- 5. In addition to the standard salt parameter, Version 2 of the G3P+-- depends on two additional 4168 byte salt parameters which are+-- assumed to be filled with null bytes by standard bcrypt.+--+-- 6. G3Pb2 also implements a counter at the start of the excess salt+--+-- For this reason, this binding completely removes the code for handling+-- unix-style bcrypt hashes, which has repeatedly proven problematic. One+-- of the major design motifs of the G3P is to replace this cruft with PHKDF,+-- which is intended to be bulletproof.+--+-- Note that this binding doesn't (currently?) support the @2a@ and @2x@+-- variants. On the other hand, at least the 2a variant depends on+-- overflow, which as undefined behavior in C is allowed to compile to+-- whatever it wants... so there might be multiple variants of the @2a@+-- "variant" of bcrypt floating around out there, depending on particular+-- C implementations and possibly even specific to architectures, compiler+-- flags, and versions+--+-------------------------------------------------------------------------------++module Crypto.G3P.BCrypt+ ( bcrypt+ , bcrypt_saltLength+ , bcrypt_maxPasswordLength+ , bcrypt_formatSaltString+ , bcrypt_parseSaltString+ , bcrypt_outputLength+ , bcryptRaw+ , bcryptRaw_maxInputLength+ , bcryptRaw_outputLength+ , bcryptXsFree+ ) where++import Control.Exception(assert)++import Data.Bits((.&.))+import Data.ByteString(ByteString)+import qualified Data.ByteString as B+import Data.ByteString.Internal(c2w, w2c)+import qualified Data.Char as Char+import Data.Function((&))+import Data.Int+import Data.Word++import Network.ByteOrder(word32, bytestring32)++import Crypto.PHKDF.HMAC (HmacKeyPrefixed, hmacKeyPrefixed_feeds)+import Crypto.PHKDF (PhkdfCtx, phkdfCtx_initPrefixed, phkdfCtx_feedArgsBy, phkdfCtx_feedArg, phkdfCtx_finalize, phkdfCtx_byteCount, phkdfCtx_endPaddingLength)+import Crypto.PHKDF.Assert++import Crypto.Encoding.PHKDF (chunkify, chunkifyCycle, takeBs, nullBuffer)+import Crypto.G3P.BCrypt.Subtle++-- | OpenBSD-compatible bcrypt++bcrypt :: ByteString -- ^ password+ -> ByteString -- ^ unix-style salt string+ -> Maybe ByteString -- ^ unix-style password hash string+bcrypt key saltString =+ case bcrypt_parseSaltString saltString of+ Just (_, cost, salt, _) ->+ let hash = bcryptRaw key' salt (2 ^ cost - 1)+ in Just ( B.take 29 saltString <> base64Encode (B.take 23 hash))+ Nothing -> Nothing+ where+ key' =+ case (B.elemIndex 0 (B.take bcrypt_maxPasswordLength key)) of+ Nothing -> key+ Just n -> B.take n key++-- | produce a standard salt string for bcrypt, with or without a password+-- hash.++bcrypt_formatSaltString+ :: Char -- ^ Variant, must be @\'b\'@ for now+ -> Word8 -- ^ Cost factor, must be between 4 and 31 inclusive+ -> ByteString -- ^ Binary salt, must be 16 bytes long+ -> ByteString -- ^ Binary hash, must be 0 or 23 bytes long+ -> Maybe ByteString+bcrypt_formatSaltString variant cost salt hash+ | B.length salt /= 16 = Nothing+ | B.length hash `notElem` [0,23] = Nothing+ | not ( 4 <= cost && cost <= 31 ) = Nothing+ | variant `notElem` ['b'] = Nothing+ | otherwise =+ Just (B.concat [ "$2", B.singleton (c2w variant),+ "$", x, y, "$",+ base64Encode salt,+ base64Encode hash ])+ where+ (toDigit -> x, toDigit -> y) = (cost `divMod` 10)++toDigit :: Word8 -> ByteString+toDigit a = B.singleton (fromIntegral a + c2w '0')++-- | Given a salt string (e.g. "@\$2b\$12\$...@") in the OpenBSD format,+-- returns (variant, work cost, binary salt, binary hash). The only supported+-- variant is currently @\'b\'@. The cost must be between 4 and 31, and the+-- input string must be either 29 or 60 bytes long, depending on whether the+-- salt string includes a password hash.++bcrypt_parseSaltString :: ByteString -> Maybe (Char, Word8, ByteString, ByteString)+bcrypt_parseSaltString salt+ | not (B.length salt `elem` [29, 60]) = Nothing+ | not ("$2" `B.isPrefixOf` salt+ && w2c variant `elem` [ 'b' ]+ && B.index salt 3 == c2w '$' ) = Nothing+ | not ( Char.isDigit (w2c (B.index salt 4))+ && Char.isDigit (w2c (B.index salt 5))+ && B.index salt 6 == c2w '$' ) = Nothing+ | not ( 4 <= cost && cost <= 31 ) = Nothing+ | Just binarySalt <- base64Decode (B.drop 7 (B.take 29 salt))+ , Just binaryHash <- base64Decode (B.drop 29 salt)+ = Just (w2c variant, cost, binarySalt, binaryHash)+ | otherwise = Nothing+ where+ variant = B.index salt 2+ cost = 10 * (B.index salt 4 - c2w '0')+ + (B.index salt 5 - c2w '0')++bcrypt_saltLength :: Int+bcrypt_saltLength = 16++bcrypt_maxPasswordLength :: Int+bcrypt_maxPasswordLength = bcryptXs_maxKeyLength++bcrypt_outputLength :: Int+bcrypt_outputLength = B.length bcrypt_outputSalt++-- | Any input longer than 72 bytes will be truncated.++bcryptRaw_maxInputLength :: Int+bcryptRaw_maxInputLength = bcryptXs_maxKeyLength++-- | Any output hash from 'bcryptRaw' will be exactly 24 bytes long.++bcryptRaw_outputLength :: Int+bcryptRaw_outputLength = B.length bcryptRaw_outputSalt++-- | @bcryptRaw key salt rounds@ Be aware that keys and salts that are longer+-- than 72 bytes do get truncated to exactly 72 bytes. This binding will+-- return a hash that is exactly 24 bytes long.+--+-- Note the rounds parameter is one less than the number of rounds to be+-- computed. Thus if you want something equivalent to the traditional bcrypt+-- cost parameter of 12, you need to specify 4095 rounds. This is because+-- @2^12 - 1 = 4095@.++bcryptRaw :: ByteString -> ByteString -> Word32 -> ByteString+bcryptRaw key salt rounds = bcryptXs (bcryptRaw_genInputs key salt rounds)++formatFnName :: ByteString -> ByteString+formatFnName (B.take 28 -> name) = B.concat [bytestring32 0, name, nameExt]+ where+ nameExt = B.take (28 - B.length name) nullBuffer++bcryptXsFree_tagBytesPerRound :: Int+bcryptXsFree_tagBytesPerRound = bcryptXsCtr_outputLength - 32++concatTakeBs :: Int -> [ByteString] -> ByteString+concatTakeBs n bs = B.concat (takeBs (fromIntegral n) bs)++bcryptXsFree :: forall f a. Foldable f => (a -> ByteString) -> ByteString+ -> f a -> ByteString -> f a -> ByteString -> Word32+ -> HmacKeyPrefixed -> (Int, HmacKeyPrefixed)+bcryptXsFree toString fnName creds longTag contextTags domainTag ctr0 = initRound+ where+ rounds :: Int64 = fromIntegral ctr0 + 1+ -- Do 1-128 minirounds in the first superround, so that we end on an+ -- exact multiple of 128+ miniRoundBytes :: Int64 = fromIntegral bcryptXsFree_tagBytesPerRound+ miniRounds0 = let x = rounds .&. 127+ in if x == 0 then 128 else x+ -- The number of superrounds after the first+ superRounds0 = (rounds - miniRounds0) `div` 128+ tagBytesFrom = chunkifyCycle 32 longTag++ -- minimum number of half blocks to complete a local commitment to the+ -- entirety of an excessively long extended salt for a single bcrypt round.++ -- The first and last rounds of the superround have their extended salts+ -- committed to as part of deriving the keys in use for that superround.++ -- This turns into cryptoacoustic repetition if the longTag is not+ -- excessively long.++ halfBlocks :: Int = ceiling ((fromIntegral miniRoundBytes :: Float) / 32)++ initRound :: HmacKeyPrefixed -> (Int, HmacKeyPrefixed)+ initRound !sha0 =+ let+ -- Locally ensure that the extended salt for the first and last+ -- rounds have been committed to before deriving the keys.++ -- (This turns into cryptoacoustic repetition if the extended salt+ -- isn't excessively long.)++ lastOffset = (miniRounds0 - 1) * miniRoundBytes++ ltA = take halfBlocks $ tagBytesFrom 0+ ltZ = take (halfBlocks + 4) $ tagBytesFrom lastOffset++ -- Now actually perform the commitment:+ ("", sha1) = hmacKeyPrefixed_feeds (ltA ++ ltZ) sha0++ in superRound 0 sha1 (Just creds) Nothing ctr0 (fromIntegral miniRounds0) (fromIntegral superRounds0)++ superRound :: Word32 -> HmacKeyPrefixed -> Maybe (f a) -> Maybe BCryptState -> Word32 -> Word32 -> Word32 -> (Int, HmacKeyPrefixed)+ superRound tagPos !sha0 mCreds mBcrypt0 ctr miniRounds superRounds =+ let+ -- The derivation of the keys for the superround will locally commit+ -- to the first 96 - 222 bytes of the extended salt of the+ -- penultimate miniround.+ penOffset = fromIntegral tagPos + (fromIntegral miniRounds - 2) * miniRoundBytes+ penBytes = tagBytesFrom penOffset+ endPad0 n = concatTakeBs n (tagBytesFrom (penOffset + 96))+ endPad1 n = concatTakeBs n (tagBytesFrom (penOffset + 96 + fromIntegral n))+ addCredentials :: f a -> PhkdfCtx -> PhkdfCtx+ addCredentials cs ctx0 = ctx2+ where+ n0 = phkdfCtx_byteCount ctx0 `mod` 64+ n1 = phkdfCtx_byteCount ctx1 `mod` 64+ ctx1 = phkdfCtx_feedArgsBy toString cs ctx0+ ctx2 = phkdfCtx_feedArg credsPad ctx1 &+ phkdfCtx_assertBufferPosition n0+ -- Length of PHKDF end-of-args padding+ endPadLen = phkdfCtx_endPaddingLength ctx0+ -- Encoded length of credentials vector, mod 64+ credsLen = (n1 - n0) `mod` 64+ -- We want to add 32 - 95 bytes as needed to bring the length+ -- of the encoded credentials vector + padding equivalent to+ -- 0 (mod 64). Then use endPaddingLen to commit to more extended+ -- salt (Note that this padding will require 3 bytes to+ -- encode it's length.)+ credsPadLen = 32 + (29 - fromIntegral credsLen) `mod` 64+ -- Now we'll commit to the next 32-95 bytes of the extended salt+ -- on the penultimate miniround:+ credsPadOffset = penOffset + 96 + 2 * fromIntegral endPadLen+ credsPad = B.concat (takeBs credsPadLen (tagBytesFrom credsPadOffset))++ key0 = phkdfCtx_initPrefixed (penBytes !! 0) sha0 &+ phkdfCtx_feedArgsBy toString contextTags &+ maybe id addCredentials mCreds &+ phkdfCtx_finalize endPad0 (word32 "KEY0") domainTag++ ("",sha1) = hmacKeyPrefixed_feeds [penBytes !! 1, key0] sha0++ key1 = phkdfCtx_initPrefixed (penBytes !! 2) sha1 &+ phkdfCtx_feedArgsBy toString contextTags &+ phkdfCtx_finalize endPad1 (word32 "KEY1") domainTag++ args = BCryptXsCtr+ { bcryptXsCtr_key0 = key0+ , bcryptXsCtr_key1 = key1+ , bcryptXsCtr_tag = longTag+ , bcryptXsCtr_name = formatFnName fnName+ }++ (tagPos', bcrypt1) = bcryptXsCtrSuperRound args+ tagPos (fromIntegral miniRounds) ctr mBcrypt0++ (pBit, pBox) = B.splitAt 8 (bcryptState_toByteString bcrypt1)++ chunksR = key1 : key0 : orpheanBeholderScryDoubt <> pBit :+ chunkify 32 pBox++ list2 x y = [x,y]+ in+ if assert (fromIntegral tagPos' == (penOffset + 2*miniRoundBytes) `mod` fromIntegral (B.length longTag + 1)) $+ superRounds > 0+ then let++ -- Now we need to do the local commitment for the *next* superround++ -- The next local commitment needs the offset into the tag used+ -- for the last miniround. There is always 128 minirounds in the+ -- next superround.++ lastOffset = fromIntegral tagPos' + 127 * miniRoundBytes++ ltA = take halfBlocks (tagBytesFrom (fromIntegral tagPos'))+ ltZ = tagBytesFrom lastOffset++ nextChunks = concat (zipWith list2 ltZ chunksR) ++ ltA++ ("",nextSha) = hmacKeyPrefixed_feeds nextChunks sha0+ in+ superRound tagPos' nextSha Nothing (Just bcrypt1)+ (ctr - miniRounds) 128 (superRounds - 1)+ else let+ -- Now we need to do the end-of-key-stretching finalization.++ -- Repeat the most recent tag:++ endOffset = fromIntegral tagPos' - 32*(fromIntegral (length chunksR))++ endChunksL = tagBytesFrom endOffset++ endChunks = concat (zipWith list2 endChunksL chunksR)++ ("",endSha) = hmacKeyPrefixed_feeds endChunks sha0+ in+ ((,) $! fromIntegral tagPos') $! endSha
− lib/Crypto/G3P/BCrypt.hsc
@@ -1,88 +0,0 @@-{-# LANGUAGE CApiFFI, ViewPatterns #-}---- | A very minimal binding to the core of the bcrypt algorithm, adapted from--- OpenBSD's implementation. The Global Password Prehash Protocol version--- G3Pb1 cannot be implemented in terms of standard bcrypt interfaces for--- several reasons:------ 1. Standard bcrypt hashes are truncated to 23 bytes. The G3P depends--- on all 24 output bytes.------ 2. Standard bcrypt must specify a number of rounds that is a power of--- two. The G3P allows any number of rounds between 1 and 2^32 inclusive.------ 3. the G3P needs unimpeded access to the full 72 byte password input.--- This is not doable with all bcrypt variants.------ 4. Standard bcrypt limits salt length to 16 bytes. The G3P depends on--- 72 byte salt parameters.------ For this reason, this binding completely removes the code for handling--- unix-style bcrypt hashes, which has repeatedly proven problematic. One--- of the major design motifs of the G3P is to replace this cruft with PHKDF,--- which is intended to be bulletproof.------ Similarly, this binding cannot be directly used to process unix-style--- bcrypt hashes, which does make testing a bit of a challenge. However,--- the core algorithm is unmodified, so implementing unix-style hash--- handling in terms of this binding is very much possible.------ This will be done in the test suite for this library. Hopefully that--- implementation will eventually migrate here, once it's production-ready,--- so that this binding might also be used to handle standard bcrypt hashes--- directly.--module Crypto.G3P.BCrypt- ( bcryptRaw- , bcryptRaw_maxInputLength- , bcryptRaw_outputLength- ) where--#include "bcrypt_raw.h"--import Data.ByteString(ByteString)-import qualified Data.ByteString as B-import qualified Data.ByteString.Unsafe as B-import Data.Word--import Foreign.C.String-import System.IO.Unsafe--foreign import capi "bcrypt_raw.h bcrypt_raw" c_bcrypt_raw :: CString -> Word32 -> CString -> Word32 -> CString -> Word32 -> IO ()---- | Any input longer than 72 bytes will be truncated.--bcryptRaw_maxInputLength :: Int-bcryptRaw_maxInputLength = (#const BCRYPT_RAW_MAX_INPUT_LENGTH)---- | Any output hash from 'bcryptRaw' will be exactly 24 bytes long.--bcryptRaw_outputLength :: Int-bcryptRaw_outputLength = (#const BCRYPT_RAW_OUTPUT_LENGTH)---- | @bcryptRaw key salt rounds@ Be aware that keys and salts that are longer--- than 72 bytes do get truncated to exactly 72 bytes. This binding will--- return a hash that is exactly 24 bytes long.------ Note the rounds parameter is one less than the number of rounds to be--- computed. Thus if you want something equivalent to the traditional bcrypt--- cost parameter of 12, you need to specify 4095 rounds. This is because--- @2^12 - 1 = 4095@.--bcryptRaw :: ByteString -> ByteString -> Word32 -> ByteString-bcryptRaw (f -> key) (f -> salt) rounds- = unsafePerformIO $ do- B.unsafeUseAsCString key $ \keyPtr -> do- B.unsafeUseAsCString salt $ \saltPtr -> do- -- using a superfluous `seq` to try to ensure that this allocates a new- -- unique bytestring. FIXME: there's almost certainly a better, more- -- proper, more idiomatic solution here- let output = B.replicate bcryptRaw_outputLength (saltPtr `seq` 0)- B.unsafeUseAsCString output $ \outPtr -> do- c_bcrypt_raw keyPtr (len key) saltPtr (len salt) outPtr rounds- return output- where- len x = fromIntegral (min bcryptRaw_maxInputLength (B.length x))--f :: ByteString -> ByteString-f key = if B.null key then B.replicate bcryptRaw_maxInputLength 0 else key
+ lib/Crypto/G3P/BCrypt/Subtle.hsc view
@@ -0,0 +1,365 @@+{-# LANGUAGE CApiFFI, OverloadedStrings, ViewPatterns #-}++-------------------------------------------------------------------------------+-- |+-- Module: Crypto.G3P.BCrypt.Subtle+-- Copyright: (c) 2024 Auth Global+-- License: Apache2+--+-------------------------------------------------------------------------------++{- |++Bcrypt with an excessive amount of freedom and salt, appropriate for+our excessively salty era. This module exports bindings that are+potentially cryptographically unsafe to lower-level functions written in C.++Bcrypt's state machine exhibits a beautiful grouplike structure known as a+/quasigroup/, see [wikipedia](https://en.wikipedia.org/wiki/Quasigroup) or the+[ncat wiki](https://ncatlab.org/nlab/show/quasigroup). Blowfish's state machine+is exactly 4168 bytes, and bcrypt's modification to blowfish's key expansion+represents a /transition code/ that is also exactly 4168 bytes long.++Basically, each call to /Blowfish_expandstate/ encrypts the transition code+with the Blowfish block cipher in Cipher Block Chaining (CBC) mode of operation.+Well, not quite, as this key setup process actually uses each output block+to overwrite part of the key, so it's more like CBC with key feedback.++/Blowfish_expandstate/ is part of a relation between input states, transition+codes, and output states. Given any two components of any one of these+3-tuples, one can efficiently compute the third component.++/Blowfish_expandstate/ is what computes output states from input states+and transition codes. One can also implement /Blowfish_reverseExpandstate/+that computes input states from output states and transition codes, and+/Blowfish_transitionCode/ that computes transition codes from input states+and output states. Together they form the triple of functions needed to satisfy+the universal-algebra-flavored definition of a quasigroup.++This quasigroup implies that no state or transition code is particularly+special, and that choosing a different transition code does not change the+dynamical properties of the blowfish state machine /on average/.++Of course, the molecules in a cup full of room-temperature water /on average/+are moving much too slowly to ever become a gas, yet a cup full of water that+is exposed to the open air will reliably evaporate over time. Similarly,+this quasigroup structure also implies that allowing unrestricted use of+transition codes, as the 'bcryptXs' binding allows you to do, is horribly+broken from a security perspective.++Yet this also implies that choosing transition codes in an open and honest+way is a perfectly safe modification to the bcrypt algorithm. Thus the goal+of bcrypt-xs-ctr is to add enough restrictions to how these transition codes+are chosen and used to tame excessively long tags and keep everything secure.++The first and safest recommendation is to include the excess salt in the+derivation of other inputs to bcrypt, thus enforcing the requirement+that the tags be chosen before the inputs are examined.++As a fallback, the bcryptXsCtrSuperRound has a couple of design features that+somewhat naively attempt to address this issue:++1. The initial call to @expand@ is designed to rapidly and completely+ encode both @key0@ and @key1@ into the bcrypt state, relative to+ the starting round state. This ensures a complete transfer of entropy+ after a small number of Blowfish block encryptions.++ (This argument assumes @length key0 + length key1 <= 72@ bytes long)++2. The first N bytes of the p-box are protected by the function name, which+ exists primarily to prohibit nearly all possible transition codes, to+ ensure the firt N bytes of the transition code aren't under any possible+ control of external input.++ (This argument assumes @length name == N@)++3. Every bcrypt round (a miniround within the superround) repeats the same+ 4168 - N external bytes in four different places, each in the same relative+ position with respect to a 4168-byte state or transition code.++ Two of these repetitions occur by xor-ing the external bytes with the+ last bytes of the state vector. The first N bytes of the state-xor are+ reserved, once for key0 and once for key1.++ Two of these repeititons occur as the last bytes of the transition code.+ The first 4 bytes of the transition code is reserved for a counter, which+ is complemented between repetitions for a guaranteed non-linear effect.+ The remaining (N - 4) bytes are taken up by the function name.++ This breaks all the obvious attacks, and may well break many or all of+ the less obvious attacks too. I wouldn't want to rely too much on this+ particular combinatorial block design structure without further study,+ which is likely to suggest further design improvements.++ However, this was a no-risk move that didn't cost the intended use cases+ anything, but looked plausibly strong against issues that lay well beyond+ the intended scope of the design.++ (This argument assumes @4 < length key0 == length key1 == length name@)++4. The transition code includes a counter to ensure that the transitions+ are different on every call to @expand@. The counter takes up the+ first four bytes of the transition code to ensure it affects the first+ output block.++ This implies that in the highly unlikely case that bcrypt's machine+ ever loops back around to the same state within a single key-stretching+ computation, this counter /ensures/ that the next state transitioned+ to /will/ be different than before, and will be different within the+ first blowfish block, thus breaking any cycles.++-}++module Crypto.G3P.BCrypt.Subtle+ ( orpheanBeholderScryDoubt+ , bcrypt_outputSalt+ , bcryptRaw_outputSalt+ , bcryptRaw_genInputs+ , BCryptXs(..)+ , bcryptXs+ , BCryptXsCtr(..)+ , bcryptXsCtrSuperRound+ , bcryptXs_maxKeyLength+ , bcryptXs_maxSaltLength+ , bcryptXsCtr_outputLength+ , BCryptState(..)+ , base64Encode+ , base64Decode+ ) where++#include "g3p_bcrypt.h"++import Data.ByteString(ByteString)+import qualified Data.ByteString as B+import qualified Data.ByteString.Unsafe as B+import qualified Data.ByteString.Internal as B+import Data.Word+import Data.Int++import Foreign.C.String+import Foreign.C.Types+import Foreign.ForeignPtr+import Foreign.Ptr+import System.IO.Unsafe+++orpheanBeholderScryDoubt :: ByteString+orpheanBeholderScryDoubt = "OrpheanBeholderScryDoubt"++bcrypt_outputSalt :: ByteString+bcrypt_outputSalt = orpheanBeholderScryDoubt++bcryptRaw_outputSalt :: ByteString+bcryptRaw_outputSalt = orpheanBeholderScryDoubt++-- uhh, whut? Am I looking at the wrong version of some documentation? Figuring+-- out why this is at least sometimes necessary is a good puzzle for later:++myUseAsCString :: ByteString -> (CString -> IO a) -> IO a+myUseAsCString x f = if B.null x then f nullPtr else B.unsafeUseAsCString x f++data BCryptXs = BCryptXs+ { bcryptXs_key0 :: !ByteString+ , bcryptXs_salt0 :: !ByteString+ , bcryptXs_keyL :: !ByteString+ , bcryptXs_saltL :: !ByteString+ , bcryptXs_keyR :: !ByteString+ , bcryptXs_saltR :: !ByteString+ , bcryptXs_saltZ :: !ByteString -- ^ not subject to maxSaltLength, but that doesn't seem overly relevant+ , bcryptXs_rounds :: !Word32+ , bcryptXs_implicitNull :: !Bool+ }++data BCryptXsCtr = BCryptXsCtr+ { bcryptXsCtr_key0 :: !ByteString+ , bcryptXsCtr_key1 :: !ByteString+ , bcryptXsCtr_tag :: !ByteString+ , bcryptXsCtr_name :: !ByteString+ }++foreign import capi "g3p_bcrypt.h G3P_bcrypt_xs"+ c_bcrypt_xs+ :: CString -> Word16 -> CString -> Word16+ -> CString -> Word16 -> CString -> Word16+ -> CString -> Word16 -> CString -> Word16+ -> CString -> Word32 -> Word32 -> Bool -> Ptr Word8 -> IO ()++foreign import capi "g3p_bcrypt.h G3P_bcrypt_xs_ctr_superround"+ c_bcrypt_xs_ctr_superround+ :: CString+ -> CString -> Word32 -> CString -> Word32+ -> CString -> Word32 -> CString -> Word32+ -> Word32 -> Word32 -> Word32 -> CString -> IO Word32++foreign import capi "g3p_bcrypt_base64.h G3P_bcrypt_base64Encode"+ c_bcrypt_base64Encode+ :: Ptr Word8+ -> CString+ -> Word32+ -> IO ()++foreign import capi "g3p_bcrypt_base64.h G3P_bcrypt_base64Decode"+ c_bcrypt_base64Decode+ :: Ptr Word8+ -> CString+ -> Word32+ -> IO CInt++-- | Any key longer than 72 bytes will be truncated.++bcryptXs_maxKeyLength :: Int+bcryptXs_maxKeyLength = (#const BCRYPT_XS_MAX_KEY_LENGTH)++-- | Any salt longer than 4168 bytes will be truncated.++bcryptXs_maxSaltLength :: Int+bcryptXs_maxSaltLength = (#const BCRYPT_XS_MAX_SALT_LENGTH)++-- | returns 4168 bytes+bcryptXsCtr_outputLength :: Int+bcryptXsCtr_outputLength = (#const G3P_BLF_CTX_LENGTH)++-- | bcrypt with an excessive amount of freedom. As such, this function+-- is trivially insecure, but it can still be used to implement secure+-- password hashing functions, including standard bcrypt and the very+-- lightly generalized bcryptRaw.+--+-- This was the starting point for 'bcryptXsCtrSuperRound' and 'bcryptXsFree'++bcryptXs :: BCryptXs -> ByteString+bcryptXs x = if B.null sZ then "" else unsafePerformIO $ do+ myUseAsCString k0 $ \k0' -> do+ myUseAsCString s0 $ \s0' -> do+ myUseAsCString kL $ \kL' -> do+ myUseAsCString sL $ \sL' -> do+ myUseAsCString kR $ \kR' -> do+ myUseAsCString sR $ \sR' -> do+ myUseAsCString sZ $ \sZ' -> do+ B.create (B.length sZ) $ \out' -> do+ (c_bcrypt_xs+ k0' (len16 k0) s0' (len16 s0)+ kL' (len16 kL) sL' (len16 sL)+ kR' (len16 kR) sR' (len16 sR)+ sZ' (len32 sZ) rounds implicitNull out')+ where+ k0 = bcryptXs_key0 x+ s0 = bcryptXs_salt0 x+ kL = bcryptXs_keyL x+ sL = bcryptXs_saltL x+ kR = bcryptXs_keyR x+ sR = bcryptXs_saltR x+ sZ = bcryptXs_saltZ x+ rounds = bcryptXs_rounds x+ implicitNull = bcryptXs_implicitNull x++-- | Likely at least somewhat less subtle than the one above, thanks to the addition of a counter.++bcryptXsCtrSuperRound :: BCryptXsCtr -> Word32 -> Word32 -> Word32 -> Maybe BCryptState -> (Word32, BCryptState)+bcryptXsCtrSuperRound x tagPos rounds ctr mst = unsafePerformIO $ do+ myUseAsCString k0 $ \k0' -> do+ myUseAsCString k1 $ \k1' -> do+ myUseAsCString tt $ \tt' -> do+ myUseAsCString nn $ \nn' -> do+ myUseAsCString st $ \st' -> do+ outPtr <- B.mallocByteString bcryptXsCtr_outputLength+ let out = B.BS outPtr bcryptXsCtr_outputLength+ myUseAsCString out $ \out' -> do+ tagPos' <- c_bcrypt_xs_ctr_superround+ st'+ k0' (len32 k0) k1' (len32 k1)+ nn' (len32 nn) tt' (len32 tt)+ tagPos rounds ctr out'+ return (tagPos',BCryptState out)+ where+ k0 = bcryptXsCtr_key0 x+ k1 = bcryptXsCtr_key1 x+ tt = bcryptXsCtr_tag x+ nn = bcryptXsCtr_name x+ st = maybe "" bcryptState_toByteString mst++maxLen16 :: Int+maxLen16 = fromIntegral (maxBound :: Word16)++len16 :: ByteString -> Word16+len16 x = fromIntegral (min maxLen16 (B.length x))+++maxWord32 :: Int64+maxWord32 = fromIntegral (maxBound :: Word32)++maxInt :: Int64+maxInt = fromIntegral (maxBound :: Int)++maxLen32 :: Int+maxLen32 = fromIntegral (min maxWord32 maxInt)++len32 :: ByteString -> Word32+len32 x = fromIntegral (min maxLen32 (B.length x))++newtype BCryptState = BCryptState { bcryptState_toByteString :: ByteString } deriving (Eq, Ord, Show)++-- | Given the length of some binary blob of data, how long will the base64 encoded+-- version be, without padding?++-- There's probably a "cleaner" way to compute this with bit tricks+base64EncodeLength :: Int -> Int+base64EncodeLength n =+ 4 * q + if r == 0 then 0 else 1 + r+ where+ (q,r) = n `divMod` 3++-- | Given the length of some base64 encoded data, how long will the binar blob be?+-- The input length must not include any padding, commonly appearing as one or+-- two @=@ characters at the end of a string.++-- There's probably a "cleaner" way to compute this with bit tricks+base64DecodeLength :: Int -> Maybe Int+base64DecodeLength n+ | r == 0 = Just (3 * q)+ | r == 1 = Nothing+ | otherwise = Just ((3 * q) + (r - 1))+ where+ (q,r) = n `divMod` 4++base64Decode :: ByteString -> Maybe ByteString+base64Decode input =+ case base64DecodeLength inLen of+ Nothing -> Nothing+ Just outLen ->+ unsafePerformIO $ do+ myUseAsCString input $ \inPtr -> do+ out <- B.mallocByteString outLen+ err <- withForeignPtr out $ \outPtr -> do+ c_bcrypt_base64Decode outPtr inPtr (fromIntegral inLen)+ if err == 0+ then return $! Just $! B.BS out outLen+ else return Nothing+ where+ inLen = B.length input++base64Encode :: ByteString -> ByteString+base64Encode input =+ B.unsafeCreate outLen $ \outPtr -> do+ myUseAsCString input $ \inPtr -> do+ c_bcrypt_base64Encode outPtr inPtr (fromIntegral inLen)+ where+ inLen = B.length input+ outLen = base64EncodeLength inLen++bcryptRaw_genInputs :: ByteString -> ByteString -> Word32 -> BCryptXs+bcryptRaw_genInputs (truncateKey -> key) (truncateKey -> salt) rounds =+ BCryptXs+ { bcryptXs_key0 = key+ , bcryptXs_salt0 = salt+ , bcryptXs_keyL = key+ , bcryptXs_saltL = B.empty+ , bcryptXs_keyR = salt+ , bcryptXs_saltR = B.empty+ , bcryptXs_saltZ = bcryptRaw_outputSalt+ , bcryptXs_rounds = rounds+ , bcryptXs_implicitNull = True+ }++truncateKey :: ByteString -> ByteString+truncateKey = B.take bcryptXs_maxKeyLength+
+ lib/Crypto/G3P/V1.hs view
@@ -0,0 +1,575 @@+{-# LANGUAGE OverloadedStrings #-}+-------------------------------------------------------------------------------+-- |+-- Module: Crypto.G3P.V1+-- Copyright: (c) 2024 Auth Global+-- License: Apache2+--+-------------------------------------------------------------------------------++{- |++The [Global Password Prehash Protocol (G3P)](https://github.com/auth-global/self-documenting-cryptography/blob/prerelease/design-documents/g3p.md)+is a slow, attribution-armored password hash and key derivation function. It+supports [self-documenting deployments](https://www.cut-the-knot.org/Curriculum/Algebra/SelfDescriptive.shtml)+whose password hashes are /traceable/ or /useless/ after they have been /stolen/.+This secondary security goal seeks to use [/cryptoacoustics/](https://github.com/auth-global/self-documenting-cryptography/)+to provide [/embedded attributions/](https://joeyh.name/blog/entry/attribution_armored_code/)+that are as difficult as possible for an adversarial implementation to remove.++The G3P revisits the role of cryptographic salt, splitting the salt into the+cartesian product of the /seguid/, /username/, and /tag/ parameters. Any+parameter with "tag" as part of the name is an embedded attribution to anybody+providing the inputs to the /username/ or /password/ parameters. Tags are+themselves directly self-documenting embedded attributions, in the sense that+one cannot easily or efficiently replace the tag with anything else without+losing the ability to compute the correct hash function.++The /seguid/ corresponds to the key used for every call to HMAC-SHA256, right up+until final output expansion. In this way the G3P mimicks the construction of+HKDF, with the seguid corresponding to HKDF's /salt/ parameter. The G3P also+mimicks PBKDF2 used in an alternate mode of operation.++The seguid can be trivially replaced with a /precomputed HMAC key/, thus the+seguid is not a direct tag. However this precomputed key is a cryptographic+hash of the seguid, and for this reason the seguid is capable of serving as+an /indirect/ tag, which the Seguid Protocol is designed to utilize via+Self-Documenting Globally Unique Identifiers (seguids).++It is strongly recommend a deployment identify itself with a single 64-byte+(512-bit) seguid, and the deployment's choice of plaintext messages to be+delivered via tags. These salts can be constants across the entire deployment,+as the username is intended to be used as the final bit of salt within a+deployment.++In a traditional password hash function, the salt is a random bytestring+typically between 8 and 32 bytes long. One of its primary purposes is to+identifiy a unique hash function so that one cannot attempt to crack multiple+password hashes with a single key-stretching computation. Oftentimes this+is implemented by storing a salt per user.++However, in the context of a client-side prehash, storing a salt per user has+the potential to leak whether or not an account exists, or if a password has+changed. The G3P has the option to eliminate these complications, because+it is safe to use a plain username as the salt, in addition to the+deployment-identifying seguid and tags.++On the other hand, if one is aware of the potential issues surrounding the+implementation of a random per-user salt in a client-side hashing context, and+is willing to mitigate or live with them, then there are potential advantages+to using a random salt as the input to the G3P's @username@ parameter instead.++All parameter names are suggestive, not prescriptive. Usage is ultimately+defined by the deployment.++When somebody is guessing a username, they must also know (or guess) the+password. However, the username need not be revealed to somebody who is guessing+the password, as the raw username can always be replaced by a precomputed hash.+If this intentional feature is not desired, a deployment might choose to swap+the username and password, as these inputs are otherwise functionally identical.++The usage and interpretation(s) of any given parameter is always defined by the+deployment, and is never defined by offical G3P documentation or specifications.++The G3P always has room for more salt. It doesn't really make sense to inject+more than 256 bits of entropy into the username parameter, because when the G3P+is partially applied to a constant username, the raw input can be replaced with+a SHA256 state. This is not true of any of the tags: it doesn't matter how long+it is, the whole tag must be present for the hash computation to be correct.++Every parameter with the word _tag_ in its name exhibits this property.+Theoretically, one could specify a G3P-based hash function that requires+terabytes of salt to be hashed billions of times over. However it is unclear+what purpose such an impractical specification might serve.++This initial variant of the G3P employs a combination of PHKDF and bcrypt.+PHKDF serves as the primary cryptoacoustic component, and bcrypt serves as the+primary key-stretching component of the G3P. Both are secondarily used in the+alternate role as well, with the PHKDF adding a tiny bit of key stretching and+bcrypt providing significant additional cryptoacoustic plaintext repetitions.++1. Every bit of every parameter matters. Every boundary between parameters+ matters. The presence and position of every null byte and every empty+ string matters. There aren't supposed to be any trivial collisions, the+ only exception being null-byte extension collisions on the seguid, which+ serves as an HMAC-SHA256 key.++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 minimized.++ 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.++Note that the username, password, long-tag, and credentials vector are all+/horn-loaded inputs/ in the sense that they are consumed a constant number of+times near the beginning of the hashing protocol, and after each PHKDF+round, the hash with the least key-stretching applied is discarded.++This implies that particularly paranoid password-handling implementations can+eliminate the password from memory even before key-stretching is complete.+Additionally, assuming all the sensitive secrets are contained in horn-loaded+parameters, this implies the key-stretching computation can be relocated at+nearly any time with full credit for any key-stretching already performed.++One of the associated costs is that collisions on horn-loaded inputs can be+found over the entire G3P by "only" colliding the first call to HMAC-SHA256,+/G3Pb1 alfa/. If it were trivial to produce collisions on HMAC-SHA256, this+would very likely make collisions on the horn-loaded inputs trivial. However+such an attack would be unlikely to be able to immediately produce collisions+that vary any of the other inputs. This is because all the other inputs are+repeated elsewhere in the protocol, thus colliding /G3Pb1 alfa/ isn't enough+to collide the final output of the G3P.++This "cost" seems acceptable in the context of password-based authentication+flows, where collision resistance and second preimage resistance are not+directly relevant. What is crucially important is preimage resistance and+maximizing the cost of parallelizing multiple key-stretching computations while+minimizing the latency of a single key-stretching computation.++-}++module Crypto.G3P.V1 where++import Control.Exception(assert)+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(add64WhileLt)+import Crypto.Encoding.PHKDF.V1+ ( cycleByteString+ , cycleByteStringWithNull+ , usernamePadding+ , passwordPaddingBytes+ , credentialsPadding+ )+import Crypto.Encoding.SHA3.TupleHash+import Crypto.PHKDF.Primitives+import Crypto.PHKDF.Primitives.Assert+import Crypto.G3P.BCrypt+++-- | 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. In this+-- role, all these parameters function as salt.+--+-- 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 to use the deployment to spec.+--+-- The remaining string parameters are all directly-documenting, embedded+-- attributions. A deployment can use these tags to encode a message into the+-- password hash function so that it must be known to whomever can compute it.+-- There are a variety of different parameters because there are different+-- lengths of messages that can be expressed for free, and there are different+-- incremental costs for exceeding that limit.+--+-- It is particularly important to include some kind of actionable message+-- in the @domainTag@, @longTag@, @bcryptTag@, and @bcryptSaltTag@+-- parameters. Specifying an empty string in any of these parameters+-- means that a significant quantity of cryptoacoustic messaging space will+-- be filled with silence.+--+-- Especially useful messages include URIs, legal names, and domain names.++data G3PInputBlock = G3PInputBlock+ { g3pInputBlock_seguid :: !ByteString+ -- ^ HMAC-SHA256 key, usable as a high-repetition indirect tag via+ -- self-documenting globally unique identifiers (seguids).+ , g3pInputBlock_domainTag :: !ByteString+ -- ^ plaintext tag with one repetition per PHKDF round. 0-19 bytes are+ -- free, 20-82 bytes cost a additional sha256 block /per PHKDF round/,+ -- with 83-146 and every 64 bytes thereafter incurring a similar cost.+ --+ -- Tags up to 82 or maybe even 146 bytes long are reasonable in most+ -- contexts. In the case of long domain tags, it is strategically+ -- advantageous to ensure that the first 32 bytes are highly actionable,+ -- as these bytes are commonly used as filler padding.+ --+ -- This parameter provides [domain separation](https://csrc.nist.gov/glossary/term/domain_separation).+ -- A suggested value is a ICANN domain name controlled by the deployment.+ -- The name is also a bit of an homage to the "realm" parameter of HTTP+ -- basic authentication, which in part inspired it.+ , g3pInputBlock_longTag :: !ByteString+ -- ^ plaintext tag with 1x repetition, then cycled for roughly+ -- 8 kilobytes. Constant time on inputs up to nearly 5 kilobytes.+ --+ -- Overages incur one sha256 block per 64 bytes.+ , g3pInputBlock_tags :: !(Vector ByteString)+ -- ^ plaintext tags with 3x repetition. Constant-time on 0-63 encoded bytes,+ -- which includes the length encoding of each string. Thus 60 of those+ -- free bytes are usable if the tags vector is a single string, or less if+ -- it contains two or more strings.+ --+ -- Overages incur three sha256 blocks per 64 bytes.+ --+ -- This parameter is notable because it is the least expensive purely+ -- auxiliary input that is not horn-loaded. Thus if you want a very long+ -- salt input that provides a bit of extra collision resistance, (because+ -- the collision resistance of HMAC-SHA256 isn't enough?!) this would+ -- be a logical candidate input location to consider.+ , g3pInputBlock_phkdfRounds :: !Word32+ -- ^ How expensive will the PHKDF component be? An optimal implementation+ -- computes exactly three SHA256 blocks per round if the domain tag is+ -- 19 bytes or less, plus a reasonably large but constant number of+ -- additional blocks. I recommend at least 20,000 rounds, if not 40,000.+ -- You might consider adjusting that recommendation downward in the+ -- case of domain tags that exceed 19 bytes in length: 15,000 rounds+ -- of PHKDF with a domain tag that is 83 bytes long should cost about+ -- the same number of SHA256 blocks as 20,000 rounds of PHKDF with a+ -- domain tag that is 19 bytes long.+ , g3pInputBlock_bcryptRounds :: !Word32+ -- ^ How expensive will the bcrypt component be? 4000 rounds recommended,+ -- give or take a factor of 2 or so. Each bcrypt round is approximately+ -- as time consuming as 60 PHKDF rounds. Using the recommendation, the+ -- cost should be dominated by bcrypt.+ , g3pInputBlock_bcryptTag :: !ByteString+ -- ^ Repeated once or twice per bcrypt round, plus once in PHKDF.+ -- This tag has exactly two full repetitions per bcrypt round+ -- when the tag is up to 56 bytes long. Above 56 bytes,+ -- this tag is cyclically extended to 112 bytes and then split+ -- into two strings of 56 bytes, each repeated once. Hashed+ -- once in PHKDF to avoid any truncation gotchas, and to force+ -- recomputation of PHKDF if this tag is varied.+ --+ -- 0-112 bytes can be handled in a constant number of cryptographic+ -- operations. Overages incur a cost of one SHA-256 block per+ -- 64 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 alongside the bcrypt tag and long tag+-- are incrementally the least expensive options for plaintext tagging.+--+-- Note that the username and password are subjected to additional length+-- hardening. The G3P operates in a constant number of SHA256 blocks so long+-- as the combined length of the username and password is less than about+-- 3 KiB, or the combined length of the username, password, and long tag is+-- less than about 8 KiB. The actual numbers are somewhat less in both cases,+-- but this is a reasonable approximation. Note that the bcrypt tag can+-- subtract up to 113 bytes from the 8 KiB total, and don't effect the 3 KiB+-- total.+--+-- In the case of all of the inputs in this record, longer values incur one+-- SHA256 block per 64 bytes.++data G3PInputArgs = G3PInputArgs+ { g3pInputArgs_username :: !ByteString+ -- ^ constant time on 0-101 bytes, or if any of the other conditions are met.+ , g3pInputArgs_password :: !ByteString+ -- ^ constant time on 0-101 bytes, or if any of the other conditions are met.+ , g3pInputArgs_credentials :: !(Vector ByteString)+ -- ^ constant time on 0-90 encoded bytes. This includes a variable-length+ -- field that encodes the bit length of each string; this field itself+ -- requires two or more bytes per string.+ } 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 the G3P+-- 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 ensure that these+-- parameters themselves are constant-length. Alternatively, a deployment+-- could specify an additional variable-length string in the role vector,+-- used to control the ending position relative to the SHA256 buffer.++newtype G3PInputRole = G3PInputRole+ { g3pInputRole_roleTags :: Vector ByteString+ -- ^ This is the least expensive parameter that will vary the secret HMAC+ -- key used to generate the final output. Very much analogous to HKDF's+ -- initial keying material (ikm) parameter. This is the recommended last+ -- call for mixing additional secrets into the output.+ } deriving (Eq, Ord, Show)++newtype G3PInputEcho = G3PInputEcho+ { g3pInputEcho_echoTag :: ByteString+ -- ^ the absolute least expensive parameter to vary, if your implementation+ -- supports efficent parameter variation. Very much analogous to HKDF's+ -- info parameter. 0-19 bytes are free. This incurs a cost of one SHA-256+ -- block per output block at 20 bytes, 83 bytes, and every 64 bytes+ -- thereafter.+ } deriving (Eq, Ord, Show)++-- | A plain-old-data explicit representation of the intermediate 'g3pHash'+-- computation after the 'G3PInputBlock' and 'G3PInputArgs' 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 'g3pHash_seedInit' and consumed+-- by 'g3pHash_seedFinalize'.++data G3PSeed = G3PSeed+ { g3pSeed_seguid :: !ByteString -- ^ filled in for convenience by 'g3pHash_seedInit', but ignored by 'g3p_seedFinalize'+ , g3pSeed_seguidKey :: !HmacKey+ , g3pSeed_domainTag :: !ByteString+ , g3pSeed_secret :: !ByteString+ } -- deriving (Eq)++data G3PKey = G3PKey+ { g3pKey_secret :: !ByteString+ , g3pKey_secretKey :: HmacKey+ , g3pKey_domainTag :: !ByteString+ } -- deriving (Eq)++data G3PGen = G3PGen+ { g3pGen_secret :: !ByteString+ , g3pGen_phkdfGen :: !PhkdfGen+ }++-- | The Global Password Prehash Protocol (G3P). Note that this function is very+-- intentionally implemented in such a way that the following idiom is+-- efficient. It performs the expensive key stretching phase only once.+--+-- @+-- let mySeed = g3pHash block args+-- myKey0 = mySeed myRole0+-- myKey1 = mySeed myRole1+-- in [ myKey0 myEcho, myKey0 altEcho, myKey1 myEcho, myKey1 altEcho ]+-- @+--+-- Although the savings from sharing the computation of the seed among all+-- four computations is much larger, this expression also shares the+-- computation of the @G3PKey@ among these four output streams, so that+-- only two keys are computed.+--+-- In the case that you want or need to persist or serialize the intermediate+-- seed, or change the seguid or domain tag before final output expansion,+-- then the plain-old-datatype 'G3PSeed' and its companion functions+-- 'g3pHash_keyInit' and 'g3pHash_finalizeGen' are needed.++g3pHash :: G3PInputBlock -> G3PInputArgs -> G3PInputRole -> G3PInputEcho -> Stream ByteString+g3pHash block args role echoTag =+ g3pHash_seedInit block args &+ g3pHash_keyInit role &+ g3pHash_finalizeGen echoTag &+ g3pGen_finalizeStream++-- | This generates a seed, which encapsulates the expensive key-stretching+-- component of 'g3pHash' into a reusable, tweakable cryptographic value.+-- This function is way slower than it's companions, 'g3pHash_keyInit' and+-- 'g3pHash_finalizeGen'+--+-- Broadly comparable to PBKDF2, and incorporates bcrypt.++g3pHash_seedInit :: G3PInputBlock -> G3PInputArgs -> G3PSeed+g3pHash_seedInit block args =+ G3PSeed {+ g3pSeed_seguid = seguid,+ g3pSeed_seguidKey = seguidKey,+ g3pSeed_domainTag = domainTag,+ g3pSeed_secret = secret+ }+ 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 = g3pInputBlock_domainTag block+ seguid = g3pInputBlock_seguid block+ longTag = g3pInputBlock_longTag block+ seedTags = g3pInputBlock_tags block+ phkdfRounds = g3pInputBlock_phkdfRounds block+ bcryptRounds = g3pInputBlock_bcryptRounds block+ bcryptTag = g3pInputBlock_bcryptTag block++ username = g3pInputArgs_username args+ password = g3pInputArgs_password args+ credentials = g3pInputArgs_credentials args++ headerAlfa = [ "G3Pb1 alfa username", username ]++ headerUsername = headerAlfa ++ [+ usernamePadding headerAlfa bcryptTag+ (domainTag <> "\x00password G3Pb1\x00")+ ]++ -- password will go here++ headerLongTag =+ [ longTag+ , B.concat+ [ "Global Password Prehash Protocol bcrypt (v1) G3Pb1"+ , leftEncode phkdfRounds+ , bareEncode bcryptRounds+ ]+ ]++ longPadding = passwordPaddingBytes bytes headerUsername headerLongTag+ longTag (domainTag <> "\x00creds G3Pb1\x00") password+ where+ bl = encodedByteLength bcryptTag+ bytes = add64WhileLt (8413 - bl) 8298++ credsPadding = credentialsPadding credentials bcryptTag+ (domainTag <> "\x00tags G3Pb1\x00")++ seguidKey = hmacKey seguid++ secretStream =+ phkdfCtx_init seguidKey &+ phkdfCtx_feedArgs headerUsername &+ phkdfCtx_assertBufferPosition' 32 &+ phkdfCtx_feedArg password &+ phkdfCtx_feedArg bcryptTag &+ phkdfCtx_feedArgs headerLongTag &+ -- FIXME: fusing feedArg and longPadding can save ~ 8 KiB RAM+ phkdfCtx_feedArg longPadding &+ phkdfCtx_assertBufferPosition' 32 &+ phkdfCtx_feedArgs credentials &+ phkdfCtx_feedArg credsPadding &+ phkdfCtx_assertBufferPosition' 29 &+ phkdfCtx_feedArgs seedTags &+ phkdfCtx_feedArg (bareEncode (V.length seedTags)) &+ phkdfSlowCtx_extract+ (cycleByteStringWithNull bcryptTag)+ (word32 "go\x00\x00" + 2024) domainTag+ "G3Pb1 bravo" phkdfRounds &+ phkdfSlowCtx_assertBufferPosition' 32 &+ phkdfSlowCtx_feedArgs seedTags &+ phkdfSlowCtx_toStream (cycleByteStringWithNull bcryptTag)++ (Cons phkdfHash (Cons bcryptInput _)) = secretStream++ dup a = (a,a)++ (bKeyInput, bSaltInput) = B.splitAt 16 bcryptInput++ (bKeyTag, bSaltTag) =+ if B.length bcryptTag <= 56+ then dup $ cycleByteString (bcryptTag <> "\x00G3Pb1 bcrypt\x00") 56+ else B.splitAt 56 $ cycleByteStringWithNull bcryptTag 112++ bKey = bKeyTag <> bKeyInput+ bSalt = bSaltInput <> bSaltTag++ bcryptHash = assert (B.length bKey == 72 && B.length bSalt == 72) $+ bcryptRaw bKey bSalt bcryptRounds++ headerCharlie = B.concat [+ "G3Pb1 charlie",+ phkdfHash,+ cycleByteStringWithNull bcryptTag 56,+ bcryptHash,+ cycleByteStringWithNull (domainTag <> "\x00G3Pb1 charlie\x00") 32+ ]++ secret =+ phkdfCtx_init seguidKey &+ phkdfCtx_feedArg headerCharlie &+ phkdfCtx_assertBufferPosition' 32 &+ phkdfCtx_feedArgs seedTags &+ phkdfCtx_finalize (cycleByteStringWithNull bcryptTag) (word32 "SEED") domainTag++-- | This applies the role vector to a computed G3P seed to arrive at+-- a precomputed G3P key used during the final output expansion.+-- Broadly comparable to @HKDF-Extract@. This function applies no+-- key stretching, and thus output can be available in as little as+-- 6 or 7 SHA256 block computations when you vary its parameters.+--+-- Note that this function ignores 'g3pSeed_seguid' in favor of+-- 'g3pSeed_seguidKey'.++g3pHash_keyInit :: G3PInputRole -> G3PSeed -> G3PKey+g3pHash_keyInit roleInput seed = G3PKey+ { g3pKey_secret = secretKey+ , g3pKey_secretKey = hmacKey secretKey+ , g3pKey_domainTag = g3pSeed_domainTag seed+ }+ where+ -- seguid = g3pSeed_seguid seed+ seguidKey = g3pSeed_seguidKey seed+ domainTag = g3pSeed_domainTag seed+ secret = g3pSeed_secret seed++ role = g3pInputRole_roleTags roleInput++ headerDelta = B.concat [+ "G3Pb1 delta",+ secret+ ]++ secretKey =+ phkdfCtx_init seguidKey &+ phkdfCtx_feedArg headerDelta &+ phkdfCtx_feedArgs role &+ phkdfCtx_finalize (cycleByteStringWithNull domainTag) (word32 "KEY\x00") domainTag++-- | Apply the echo tag parameter to a precomputed G3P key, obtaining a+-- cryptographically secure output generator. Broadly comparable to+-- @HKDF-Expand@. Applies no key stretching, so output is available in+-- as little as 2 SHA256 block computations.+++g3pHash_finalizeGen :: G3PInputEcho -> G3PKey -> G3PGen+g3pHash_finalizeGen inputEcho gKey = G3PGen+ { g3pGen_secret = g3pKey_secret gKey+ , g3pGen_phkdfGen = echo+ }+ where+ secretKey = g3pKey_secretKey gKey+ domainTag = g3pKey_domainTag gKey+ echoTag = g3pInputEcho_echoTag inputEcho++ echoHeader = cycleByteString (domainTag <> "\x00G3Pb1 echo\x00") 32++ echoCtr = word32 "OUT\x00"++ echo = phkdfGen_init secretKey echoHeader echoCtr echoTag++-- | Read a 32-byte hash from the G3P's output generator.++g3pGen_read :: G3PGen -> (ByteString, G3PGen)+g3pGen_read gen = let (out, next) = phkdfGen_read (g3pGen_phkdfGen gen)+ in (out, gen { g3pGen_phkdfGen = next })++-- | Turn a G3P output generator into an unbounded stream.++g3pGen_finalizeStream :: G3PGen -> Stream ByteString+g3pGen_finalizeStream = phkdfGen_toStream . g3pGen_phkdfGen
+ lib/Crypto/G3P/V2.hs view
@@ -0,0 +1,1589 @@+{-# LANGUAGE OverloadedStrings, ViewPatterns #-}++-------------------------------------------------------------------------------+-- |+-- Module: Crypto.G3P.V2+-- Copyright: (c) 2024 Auth Global+-- License: Apache2+--+-------------------------------------------------------------------------------++{- |++The [Global Password Prehash Protocol (G3P)](https://github.com/auth-global/self-documenting-cryptography/)+is a slow, attribution-armored password hash and key derivation function. Its+intented purpose is to ensure the delivery of plaintext salts from deployed+authentication databases to password crackers in order to support+[self-documenting deployments](https://www.cut-the-knot.org/Curriculum/Algebra/SelfDescriptive.shtml)+whose password hashes are /traceable/ or /useless/ after they have been /stolen/.+This secondary security goal seeks to use /cryptoacoustics/+to provide [/embedded attributions/](https://joeyh.name/blog/entry/attribution_armored_code/)+that are as difficult as possible for an adversarial implementation to remove.++The G3P revisits the role of cryptographic salt, splitting the salt into the+cartesian product of the /seguid/, /username/, and /tag/ parameters. Any+parameter with "tag" as part of the name is an embedded attribution to anybody+providing the inputs to the /username/ or /password/ parameters. Tags are+themselves directly self-documenting embedded plaintext salts, in the sense+that one cannot easily or efficiently replace the tag with anything else+without losing the ability to compute the correct hash function.++There are several themes worked into this design:++1. Always Be enCoding: one of the plaintext salts or another should be mixed+ into the final state as often and frequently as possible. If an attacker+ chooses to deploy fully homomorphic encryption in a password cracker,+ let there be no rest for the wicked. Key stretching and plaintext salting+ should be two sides of the same coin.++2. Always Be Forgetting: it should be possible to transfer the key stretching+ process to another semi-trusted computing element without providing that+ element with a password cracking attack that is signficantly less expensive+ than the work done so far. These opportunities occur at every PHKDF round,+ and at every bcrypt superround. Furthermore, any device that completes the+ PHKDF key-stretching computation can outsource some or all of the bcrypt+ superrounds without losing control of the end result.++3. Excessively Extended Salt: our philosophy of applying salt is that it's+ the first thing you do, it's the last thing you do, it's something you do+ at every opportunity, and sometimes we even create new opportunities to+ add more salt.++4. Free Plaintext Salt by Countering Excess Freedom: the G3P starts from+ conventional keys as it's first and primary layer of security. Excessively+ long keys are often considered cryptographically suspect, but very long+ keys that likely result in totally unique hash functions are also exactly+ what is needed in the information theoretic sense in order for there to+ plausibly be much if any cryptoacoustic advantage. Therefore, we use long+ plaintext salts with low entropy density throughout our hashing process,+ and use short, fixed-length, (ideally) entropy-dense HMAC keys as both the+ starting and ending point of our hashing process.++5. These goals align extremely well, and often lead to similar outcomes as,+ the advice found in [RFC 5869: HMAC-based Extract-and-Expand Key Derivation Function (HKDF)](https://datatracker.ietf.org/doc/html/rfc5869)+ and [NIST SP 108r1 Recommendation for Key Derivation Using Pseudorandom Functions](https://csrc.nist.gov/pubs/sp/800/108/r1/upd1/final).+ Both of these documents have profoundly contributed to the design of the+ G3P. PHKDF can be thought of as backporting the advice of these newer+ documents to the older PBKDF2 design, as well as finding new applications+ and justifications for the use of contextual parameters in password hash+ functions and key derivation protocols.++6. From the viewpoint of an implementer, standard PBKDF2, HKDF, and bcrypt+ interfaces cannot be used to implement this design. HMAC-SHA256 is the+ only standard library primitive this password hash function relies upon.+ However, most library implementations of HMAC-SHA256 won't do, as G3Pb2+ uses bitstring end-of-message padding. Moreover, any reasonably practical+ implementation of the G3P requires an HMAC implementation that supports+ precomputed HMAC keys (for PHKDF) as well as streaming with backtracking+ (for bcrypt).++ These latter features aren't strictly required to compute the correct+ result, meaning that the G3P respects the HMAC's abstract specification.+ However, precomputed keys, streaming, and backtracking are all strictly+ required in order to implement the G3P in the most secure way possible.++ A function that does not support precomputed HMAC keys will take twice+ as many SHA256 blocks to compute the PHKDF key-stretching phase, if the+ domain tag is 19 bytes or less. If the domain tag is 20-83 bytes long,+ it would take 1.66x as many blocks, asymptotically decaying very slowly+ to 1x every 64 bytes of domain tag thereafter.++ An implementation that does not support streaming and backtracking cannot+ possibly be /Always Forgetting/ during bcrypt key-stretching. The most+ straightforward such implementation, which is still much more complicated+ than a secure implementation that uses backtracking, would generate+ and store nearly 400 kilobytes of data, assuming the recommended 4000+ bcrypt rounds. About a third of that 400 KB would be strictly required.++ With streaming and backtracking, the entire bcrypt key-stretching+ computation can be carried out in just over 4 kilobytes of memory. Not to+ mention that if your implementation supports streaming and backtracking,+ you aren't that far away from also supporting precomputed HMAC keys.++7. From the viewpoint of an academic cryptographer, morally speaking, this+ design is literally a PBKDF2, an HKDF, and a bcrypt all at the same time,+ via a carefully designed pun. Preserving useful opportunities for employing+ partial evaluation and continuations is a particularly notable design theme.+-}++module Crypto.G3P.V2+ ( g3pHash+ , g3pStream+ , G3PSalt(..)+ , G3PInputs(..)+ , G3PSeedInputs(..)+ , G3PSpark()+ , g3pSpark+ , g3pSpark_toSeed+ , g3pSpark_toSprout+ , g3pSpark_toTree+ , g3pSpark_toKey+ , g3pSpark_toSource+ , g3pSpark_toStream+ , G3PSeed()+ , g3pSeed+ , g3pSeed_fromSpark+ , g3pSeed_toSprout+ , g3pSeed_toTree+ , g3pSeed_toKey+ , g3pSeed_toSource+ , g3pSeed_toStream+ , G3PSprout()+ , g3pSprout+ , g3pSprout_feedArg+ , g3pSprout_feedArgs+ , g3pSprout_arg+ , g3pSprout_args+ , g3pSprout_fromSpark+ , g3pSprout_fromSeed+ , g3pSprout_toTree+ , g3pSprout_toKey+ , g3pSprout_toSource+ , g3pSprout_toStream+ , G3PTree()+ , g3pTree+ , g3pTree_fromSpark+ , g3pTree_fromSeed+ , g3pTree_fromSprout+ , g3pTree_toKey+ , g3pTree_toSource+ , g3pTree_toStream+ , G3PKey()+ , g3pKey+ , g3pKey_fromSpark+ , g3pKey_fromSeed+ , g3pKey_fromSprout+ , g3pKey_fromTree+ , g3pKey_toSource+ , g3pKey_toStream+ , G3PSource+ , g3pSource+ , g3pSource_peek+ , g3pSource_read+ , g3pSource_fromSpark+ , g3pSource_fromSeed+ , g3pSource_fromSprout+ , g3pSource_fromTree+ , g3pSource_fromKey+ , g3pSource_toStream+ , Stream(..)+ , g3pStream_fromSpark+ , g3pStream_fromSeed+ , g3pStream_fromSprout+ , g3pStream_fromTree+ , g3pStream_fromKey+ , g3pStream_fromSource+ , word32+ ) where++import Data.Bits (xor)+import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import Data.Function((&))+import Data.Word+import Data.Stream (Stream(..))+import qualified Data.Stream as Stream+import Data.Vector (Vector)+import qualified Data.Vector as V+import Network.ByteOrder (word32, bytestring64)++import Crypto.Encoding.PHKDF+import Crypto.Encoding.SHA3.TupleHash+import Crypto.PHKDF.HMAC+import Crypto.PHKDF+import Crypto.PHKDF.Assert+import Crypto.G3P.BCrypt (bcryptXsFree)+import Crypto.G3P.V2.Subtle++-- | These input parameters are grouped together because they are the+-- parameters that will have to persist in memory for most or all of+-- the PHKDF-based key-stretching computation.+--+-- It is intended that deployments of an authentication database will+-- specify these as constants or near-constants. User-supplied inputs+-- would typically not go here. In this role, all these parameters+-- function as salt.+--+-- 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 to use the deployment to spec.+--+-- The remaining string parameters are all directly-documenting, embedded+-- attributions. A deployment can use these tags to encode a message into the+-- password hash function so that it must be known to whomever can compute it.+-- There are a variety of different parameters because there are different+-- lengths of messages that can be expressed for free, and there are different+-- incremental costs for exceeding that limit.+--+-- It is particularly important to include some kind of actionable message+-- in the @domainTag@ and @longTag@ parameter. Specifying an empty string+-- in either of these parameters means that a significant quantity of+-- cryptoacoustic messaging space will be filled with silence.+--+-- Especially useful messages include URIs, legal names, and domain names.++data G3PSalt = G3PSalt+ { g3pSalt_seguid :: !HmacKey+ -- ^ usable as a high-repetition indirect tag via+ -- self-documenting globally unique identifiers (seguids)+ , g3pSalt_longTag :: !ByteString+ -- ^ plaintext tag with 1x repetition, then cycled for roughly+ -- 8 kilobytes which is used as filler padding after the password.+ --+ -- This is typically duplicated as the 'g3pSeedInputs_bcryptLongTag'+ -- parameter, which provides a very large number of cryptoacoustic+ -- repetitions. If this step is not taken, then this parameter+ -- can be discarded after the first call to HMAC is complete, making+ -- it essentially horn-loaded which would be a bit of an anomaly for+ -- this input block.+ --+ -- The first 0-63 bytes is also used as filler padding after the+ -- contextTags, possibly making part of this parameter not horn-loaded.+ --+ -- Constant time on inputs 0-4095 bytes. Overages incur one sha256+ -- block per 64 bytes, rounded up.+ , g3pSalt_contextTags :: !(Vector ByteString)+ -- ^ plaintext tags with 4x repetition. Constant-time on 0-63 encoded bytes,+ -- which includes the length encoding of each string. Thus 60 of those+ -- free bytes are usable if the tags vector is a single string, or less if+ -- it contains two or more strings. The empty vector is a good default+ -- choice here.+ --+ -- Overages incur four sha256 blocks per 64 bytes.+ --+ -- This parameter is notable because it is the least expensive purely+ -- auxiliary input that is not horn-loaded. Thus if you want a very long+ -- salt input that provides a bit of extra collision resistance, this+ -- would be a logical candidate input location to consider.+ --+ -- If your deployment uses a random salt per account, this is an ideal+ -- location in which to place a copy of that salt. This is also a+ -- reasonable location for salts derived from hashed usernames.+ --+ -- If your deployment uses a plain login name as the username salt, by+ -- including it here your deployment would then require that anybody+ -- who can crack the password must know the login name.+ --+ -- This would be a highly atypical deployment design decision. In most+ -- contexts, it would seem to be better to omit plaintext login names+ -- from this parameter.+ , g3pSalt_domainTag :: !ByteString+ -- ^ plaintext tag with one repetition per PHKDF round. 0-19 bytes are+ -- free, 20-83 bytes cost five additional sha256 blocks plus one block+ -- /per PHKDF round/, with every 64 bytes thereafter incurring a similar+ -- cost.+ --+ -- In the case of long domain tags, it is strategically advantageous+ -- to ensure that the first 32 bytes are highly actionable, as these+ -- bytes are commonly used as filler padding.+ --+ -- This parameter provides [domain separation](https://en.wikipedia.org/wiki/Domain_separation). (also see the [NIST glossary](https://csrc.nist.gov/glossary/term/domain_separation))+ -- A suggested value is a ICANN domain name controlled by the deployment.+ --+ -- The name is also a bit of an homage to the "realm" parameter of HTTP+ -- basic authentication, which in part inspired the domain tag by+ -- inspiring the question "What would the realm parameter do if it did+ -- something useful?"+ , g3pSalt_phkdfRounds :: !Word32+ -- ^ How expensive will the PHKDF component be? An optimal implementation+ -- computes exactly two SHA256 blocks per round if the domain tag is+ -- 19 bytes or less, plus one block per round for every 64 characters+ -- over 19, rounded up.+ --+ -- I recommend 20,000 rounds or so. You might consider adjusting that+ -- recommendation downward in the case of domain tags that exceed 19+ -- bytes in length: 13,333 rounds of PHKDF with a domain tag that is+ -- 83 bytes long will cost exactly one SHA256 block less than 20,000+ -- rounds of PHKDF with a domain tag that is 19 bytes long.+ --+ -- Note that this cost comparison is exact only when looking at only the+ -- PHKDF key stretching phase. The G3P also computes a reasonably large+ -- but constant number of additional SHA256 blocks as part of it's+ -- initial HMAC-Extract operation, /G3Pb2 alfa/.+ --+ -- Thus if you are tuning this parameter via empirical timing tests,+ -- the direct linear relationship between this parameter and time is+ -- approximate, not exact, due to a this reasonably large offset.+ } deriving (Eq)++-- | These parameters are grouped together because they are hashed once+-- near the beginning of the protocol and then are no longer needed, unless+-- a deployment specifies duplicating (part of) one of these parameters+-- into another. Thus all of these parameters are horn-loaded.+--+-- The input string to the "username" parameter could be provided directly+-- by the user. Alternatively, it could be a random salt retrieved from+-- a server or database, typically looked up via a plaintext username.+-- The password is normally expected to be supplied by the users of a+-- deployment.+--+-- Furthermore, the credentials vector is here because it is an ideal+-- location to include other 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 alongside the bcrypt tag and long tag+-- are incrementally the least expensive options for plaintext tagging.+--+-- Note that the username and password are subjected to additional length+-- hardening. The G3P operates in a constant number of SHA256 blocks so long+-- as the combined length of the username and password is less than about+-- 4 KiB, or the combined length of the username, password, and long tag is+-- less than about 8 KiB. The actual numbers are somewhat less in both cases,+-- but this is a reasonable approximation.+--+-- In the case of all of the inputs in this record, longer values incur one+-- SHA256 block per 64 bytes.++data G3PInputs = G3PInputs+ { g3pInputs_username :: !ByteString+ -- ^ constant time on 0-293 bytes, or if the combined length of the+ -- username and password is less than about 4 kilobytes, or if the+ -- combined length of the username, password, and long tag is+ -- less than about 8 kilobytes.+ --+ -- Using deployment-identifying seguids and domain tags makes it+ -- perfectly safe to put normalized plaintext login names here, as then+ -- this salt would then only need to be unique within a deployment.+ --+ -- This approach comes with the cost that you will have to reliably+ -- perform username normalization everywhere this hash function is+ -- computed. Offering a server-side remote procedure call to perform+ -- this normalization is recommended.+ --+ -- The G3P is intentionally designed to allow the plaintext username+ -- to be hidden from a password cracker via partial evaluation, preventing+ -- the cracker from immediately logging in if successful. However, this+ -- partial application doesn't apply any key-stretching, meaning that+ -- guessable login names can be cracked relatively quickly.+ --+ -- Thus this approach is less of a defensive line and more of a "sand in+ -- the gears" tactic. It might also be useful as a legal damages+ -- enhancement strategy against unauthorized password crackers who+ -- fail to take this step to help protect users' privacy.+ --+ -- Even better, apply key-stretching to the plaintext login name, possibly+ -- via another call to the G3P, and duplicate that derived salt in the+ -- 'g3pInputs_username' and 'g3pSalt_contextTags' parameters, and inside+ -- the role parameter of 'G3PSprout'.+ --+ -- This repetition of an account's salt ensures that any collision that+ -- happens between accounts must occur quite late. Even if a+ -- cryptographically non-trivial collision happens before the+ -- repetition, the account's salt will push the output hashes apart+ -- once again.+ --+ -- Including the account's salt in either the echo key or echo tag+ -- parameters implies a cryptographically non-trivial collision must+ -- happen on the very last HMAC call that generates any output block,+ -- and that must happen on every output block.+ --+ -- Disconnecting login names from publicly-facing screen names can+ -- represent a significant upgrade of "sand in the gears" to something+ -- resembling a proper defensive line, though this disconnection can+ -- also benefit any approach.+ --+ -- For another upgrade, using a random salt per acccount has the potential+ -- to be a far more meaningful defensive line. This brings the advantage+ -- of completely disconnecting public salts from login names, except by+ -- talking to (or compromising) a salt server.+ --+ -- In a rare alignment of interests, this can serve both legitimate+ -- deployments and the password hash thieves that attack them. Some thieves+ -- will want to be able to outsource password cracking work without giving+ -- successful crackers an opportunity to log in, and this makes it easier+ -- for the thief to securely withhold the login name from the cracker.+ --+ -- The cost is that in typical client-side prehashing scenarios, a salt+ -- server will have to reveal the actual salt for arbitrary accounts+ -- to arbitrary members of the public. This has the potential to leak+ -- information about the (non-)existence of accounts and to leak+ -- information about recent account activity.+ --+ -- Of particular concern is providing reidentification hooks that+ -- enable deanonymization attacks. Though I don't exactly understand+ -- how a public-facing salt server could become such a hook, it's also+ -- something that seems possible.+ --+ -- This issue could be largely mitigated by ensuring your salt server is+ -- capable of handing out convincing nonsense that is consistent over time.+ -- The server does not even need to remember every fake salt you've ever+ -- handed out. For example, the server could generate a fake salt by+ -- hashing a normalized version of the nonexistent login name with a+ -- secret key.+ --+ -- If you are running a sufficiently sensitive identity service to+ -- justify the additional complexity and ongoing operational costs,+ -- running a salt server seems very much worthwhile.+ --+ -- One reason is that when a password hash is stolen, having a login+ -- name in its derivation can be a reidentification hook without even+ -- needing to talk to a server. On the other hand, salt derived from+ -- a login name seems far preferable to a poorly implemented salt server.+ --+ -- A poorly implemented server can become a toehold for attackers to+ -- get inside your infrastructure, and has the potential to leak+ -- whether or not an account exists, possibly even to the general public.+ -- Account existence can itself be an extremely juicy reidentification+ -- hook, and querying a public server doesn't require stealing password+ -- hashes first.+ --+ -- In a few specialized cases it might be possible to hide a random salt+ -- from members of the general public by requiring pre-authentication+ -- before the password can even be attempted. However, this cannot+ -- be a general-purpose solution, as passwords are one of the fundamental+ -- solutions to the problem of key management, and key management is+ -- the fundamental problem behind authentication.+ --+ -- One might also supplement or replace any salt applied here with an+ -- oblivious pseudorandom function (OPRF) in your authentication flow,+ -- especially if password-authenticated key agreement (PAKE) is used.+ --+ -- Through the magic of multiparty computation, it's possible to apply a+ -- salt that only the server knows to a password attempt that only the+ -- client knowns. However, OPRF cannot be directly integrated into the G3P,+ -- though the G3P should be an excellent choice for a key derivation+ -- function to prepare a password for OPRF.+ --+ -- I see this choice of plain usernames versus random salts or possibly+ -- even none at all as a fairly fundamental tradeoff in the design of G3P+ -- deployments. I took the time to ensure that all are possible.+ --+ -- In my estimation, of the approaches that I've started to sketch,+ -- deriving salts directly from plaintext login names in a public,+ -- non-secret way is the hardest to mess up badly.+ --+ -- Implementing a salt server potentially enables signficant security+ -- advantages, but also represents additional complexity, operational+ -- expense, and itself creates additional attack surfaces and security+ -- risks.+ --+ -- This decision has significant strategic consequences. I don't think+ -- there exists a one-size-fits-all solution, and there are quite a few+ -- ways to sensibly customize each approach. Any can be executed poorly,+ -- and any can be executed well. Pick your poison wisely.+ , g3pInputs_password :: !ByteString+ -- ^ constant time on 0-293 bytes, or if any of the other conditions are met.+ , g3pInputs_credentials :: !(Vector ByteString)+ -- ^ constant time on 0-282 encoded bytes. This includes variable-length+ -- fields that encode the bit length of each string; these fields itself+ -- require two or more bytes per string.+ } deriving (Eq)++data G3PSeedInputs = G3PSeedInputs -- ^ bcrypt parameters+ { g3pSeedInputs_bcryptSeguid :: !HmacKey+ -- ^ Key to used to generate keys for bcrypt superrounds and to soak up+ -- the entropy from bcrypt's state at the end of each superround.+ -- Duplicating the 'g3pSalt_seguid' is a good default choice.+ , g3pSeedInputs_bcryptCredentials :: !(Vector ByteString)+ -- ^ Used directly once to derive @key0@ for the first bcrypt superround,+ -- and indirectly affects every cryptographic operation after that.+ --+ -- 0-29 bytes are free. The length encoding for each bytestring needs+ -- to be included, thus 0-27 data bytes incur zero incremental cost if+ -- you encode everything in one string, or less if you use more than+ -- one string.+ --+ -- Overages cost one SHA256 block per 64 bytes, rounded up. Thus, this+ -- is a horn-loaded parameter introduced after the phkdf key-stretching+ -- phase is complete.+ --+ -- If some unusual deployment of the G3P accepts arbitrary external+ -- inputs into the @bcryptLongTag@, one possible way to handle this+ -- situation efficiently and safely would be to include the entire+ -- input in this parameter. This is not necessary if such a deployment+ -- duplicated the external input into both the 'g3pSalt_longTag' and+ -- 'g3pSeedInputs_bcryptLongTag' parameters.+ , g3pSeedInputs_bcryptLongTag :: !ByteString+ -- ^ Be aware this is truncated to (rounds + 1) * 4136 bytes, but+ -- length still matters after that. The primary intended use is to+ -- duplicate 'g3pSalt_longTag' a very large number of times.+ --+ -- Also be aware that nobody should trust this parameter with arbitrary,+ -- potentially hostile input that is selected after all of the other+ -- inputs to the bcrypt computation are known. There are, however,+ -- a large number of ways to avoid any potential issue, including:+ --+ -- 1. Ensuring that this input is fully commited to before looking+ -- at all of the other input parameters /by convention/, which is+ -- true in the primary intended use case as an extended plaintext+ -- salt for password hashing.+ --+ -- 2. Ensure that this input has been committed to by including the+ -- entirety of its contents in the derivation of at least one other+ -- input parameter. Note that duplicating 'g3pSalt_longTag' is+ -- sufficient to meet this requirement. Including it somewhere in+ -- a 'G3PSalt' parameter is highly recommended as it ensures that+ -- varying any part of this parameter is expensive as possible.+ --+ -- 3. Ensure that this input is 4287 bytes or less. Local HMAC+ -- computations ensure at least this many bytes are automatically+ -- committed to before the bcrypt key stretching is allowed to+ -- move forward.+ --+ -- 4. Ensure that this input has some kind, any kind, of recognizable+ -- pattern. If this input is a valid UTF8 encoding, it doesn't matter+ -- if the textual content is random gibberish. It's extremely+ -- doubtful that an attacker could achieve any particularly+ -- nefarious goal under this restriction.+ --+ -- Note that any single one of these conditions should be sufficient+ -- to avoid problems, and that the primary intended use case for this+ -- parameter meets all of them. After all, the entire point of this+ -- parameter is to ensure the delivery of plaintext salts from+ -- authentication database deployments to password crackers.+ --+ -- Failing all of that, there's still an attempt to make the G3P+ -- resistant to hostile inputs. Within each bcrypt round, the exact same+ -- longTag bytes are repeated four times in a combinatorial block design+ -- that ensures nonlinear effects.+ --+ -- I wouldn't want to rely on this design feature of last resort without+ -- careful study, which is likely to suggest further improvements.+ -- Yet this hedge doesn't cost anything with respect to the intended+ -- use case, and seems plausibly strong in situations that fall well+ -- outside any intended use case.+ --+ -- Regarding condition 2, any of the 'G3PInput' parameters would also+ -- qualify. However, it would be rather silly to repeat the user's+ -- password here, as that would prevent the bcrypt key stretching+ -- computation from being securely outsourced to a semi-trusted device.+ --+ -- Regarding condition 3, the actual size of a parameter that is fully+ -- committed to via baked-in hashing is likely more than 8192 bytes, but+ -- this would require further verification.+ --+ -- The shortest plausible attack string would seem to need to be as long+ -- as the truncation limit, which is north of 16 megabytes if you specify+ -- the suggested 4000 rounds.+ --+ -- Here, the attack model of concern is preventing any external input+ -- from affecting the final bcrypt state in any way that's better than+ -- a multiplicity of fair coin flips.+ --+ -- Failing to fully commit to the bcrypt long tag up-front potentially+ -- allows external input to tweak the final result without redoing+ -- the entirety of the key-stretching computation, even if the desired+ -- fairness property is ensured.+ --+ -- This final bcrypt state is then consumed as part of an HMAC message.+ -- This outer HMAC provides the next line of defense against this type of+ -- attack. This line of defense pretends this outer HMAC doesn't exist.+ , g3pSeedInputs_bcryptContextTags :: !(Vector ByteString)+ -- ^ Also used to derive super round keys for bcrypt. 0-63 encoded+ -- bytes are free, meaning that 60 bytes impose zero incremental cost+ -- if you encode everything into one string, or somewhat less if you use+ -- more than one string.+ --+ -- Overages cost two SHA-256 blocks per 64 bytes per bcrypt superround.+ --+ -- Leaving this empty is a good default choice. In particular, one+ -- /should not/ default to duplicating anything between this parameter+ -- and the 'g3pSeed_contextTags' parameter.+ --+ -- For example, if your deployment uses a random account salt, or+ -- an account salt derived transparently from a login name, then it's+ -- a good idea to include that salt in the 'username' and 'contextTags'+ -- parameters, but exclude the plaintext of that salt from+ -- 'bcryptContextTags'.+ --+ -- This use of an account salt implies that if some or all of the bcrypt+ -- computation is outsourced to another device, that device cannot+ -- break the password without knowing the salt, or successfully+ -- guessing both the salt and password at the same time.+ --+ -- Assuming simplest and most-intended outsourcing algorithm, directly+ -- including that account salt in the 'bcryptContextTags' vector would+ -- require that the plaintext of this salt be known to the device+ -- performing the key-stretching computation, thus automatically+ -- obviating this possible line of defense.+ --+ -- If one is absolutely set on including that random salt here, one+ -- could hash the salt first to derive a new salt that cannot itself+ -- be used to crack the password. What things are forgotten and when+ -- are important details in cryptographic processes, and these choices+ -- have strategic implications.+ --+ -- Alternatively, one could implement the G3P using a more sophisticated+ -- outsourcing algorithm. This would require interactive communication+ -- every transition between bcrypt superrounds. By contrast, the design+ -- intension is to be able to treat outsourcing bcrypt as (relatively)+ -- simple remote procedure call (RPC).+ , g3pSeedInputs_bcryptDomainTag :: !ByteString+ -- ^ Used to derive the keys for a super round in bcrypt-xs-ctr mode.+ -- Duplicating the 'g3pSalt_domainTag' is a good default choice.+ --+ -- 0-19 bytes are free. 20-83 bytes and every 64 bytes thereafter+ -- impose a cost of two SHA-256 blocks per bcrypt superround.+ , g3pSeedInputs_bcryptRounds :: !Word32+ -- ^ How expensive will the bcrypt component be? 4000 rounds recommended,+ -- give or take a factor of 2 or so. Each bcrypt round is approximately+ -- as time consuming as 60 PHKDF rounds. Using the recommended cost,+ -- parameters, the cost should be dominated by bcrypt.+ --+ -- The number of superrounds matters for some cost calculations. This is+ -- always @ceiling ((bcryptRounds + 1) / 128)@.+ }++-- | The Global Password Prehash Protocol (G3P). Note that this function is very+-- intentionally implemented in such a way that the following idiom is+-- efficient. It performs the expensive key stretching phase only once,+-- and results in 3 cryptographically independent output hashes, i.e.+-- statistically independent to any efficient attacker that does not have+-- access to the underlying password and other secrets.+--+-- @+-- let myDomain = "my.domain.example"+-- myLoginDomain = "login.my.domain.example"+-- myStorageDomain = "cloud.my.domain.example"+-- myLongTag = "My Corporation, Inc. https://my.domain.example/.well-known/security.txt"+-- mySeguid = hmacKey "9c08053b7e507a78b571b5b93e1326674540d7106da6408fcafeddcfcdf1ed76"+-- userRandomSalt = "60473b8010e16d46"+-- userSecondSecretHash = "0c06f683f093cb899b4a1e9836fc7281"+-- mySalt =+-- G3PSalt {+-- g3pSalt_seguid = mySeguid,+-- g3pSalt_longTag = myLongTag,+-- g3pSalt_contextTags = [userRandomSalt],+-- g3pSalt_domainTag = myDomain,+-- g3pSalt_phkdfRounds = 20240+-- }+-- myInputs =+-- G3PInputs {+-- g3pInputs_username = userRandomSalt,+-- g3pInputs_password = "correct horse battery staple",+-- g3pInputs_credentials = [userSecondSecretHash]+-- }+-- mySeedInputs =+-- G3PSeedInputs {+-- g3pSeedInputs_bcryptSeguid = mySeguid,+-- g3pSeedInputs_bcryptCredentials = [],+-- g3pSeedInputs_bcryptLongTag = myLongTag,+-- g3pSeedInputs_bcryptContextTags = [],+-- g3pSeedInputs_bcryptDomainTag = myDomain,+-- g3pSeedInputs_bcryptRounds = 4202+-- }+-- mySprout = g3pHash mySalt myInputs mySeedInputs mySeguid+-- myHeader = userRandomSalt <> myDomain+-- myAuthKey = mySprout ["auth",userRandomSalt]+-- myLoginDomain myHeader myHeader (word32 "AUTH")+-- myDiskKey = mySprout+-- ["disk", myStorageDomain, myLongTag,+-- "key", "7014dad47f0e7f7157d99b39a06553ce"]+-- myStorageDomain myHeader myHeader (word32 "DISK")+-- in [ myAuthKey myLongTag+-- , myDiskKey "filename0.txt"+-- , myDiskKey "quarterly-report.pdf"+-- ]+-- @+--+-- In addition to sharing the main key-stretching computation among+-- all three independent output hashes, @myDiskKey@ also shares the+-- 'G3PSprout' to 'G3PKey' computation among two different calls.+-- Although this savings is relatively miniscule, it can also be+-- relevant in certain contexts.+--+-- Note that this example is intended to be an extremely accurate sketch+-- of what a good authentication deployment that uses random salts or hashed+-- usernames and not plaintext usernames would look like.+--+-- Other details are more to stimulate ideas about how one might use these+-- things: for example I'd highly recommend using 64-byte binary @mySeguid@,+-- a 16-byte binary @userRandomSalt@, and a 32-byte binary+-- @userSecondSecretHash@, and I'd probably not use @myDiskKey@ in exactly+-- that way.+--+-- This example emphasizes that the G3P is designed to preserve endless+-- possibilites for keying end-to-end encryption (E2EE) off of the user's+-- password, though deploying a the G3P as a client-side prehash function+-- is absolutely required to make use of that particular capability.+--+-- In the example above, the extended interface could be used to partially+-- evaluate the sprout on the storage domain, allowing the 'G3PSeed' to be+-- immediately forgotten. Later, the continuation of that partially evaluated+-- sprout can be finalized once the storage key is provided by the server+-- upon a successful authentication.+--+-- This approach has the minor complication of needing to ensure that+-- any important data has been fully committed to and isn't sitting around+-- inside the sprout's SHA256 context buffer. This can be done by including+-- at least 63 bytes of non-committing data anywhere you need a safe+-- partial evaluation point.+--+-- Thus the inclusion of @myLongTag@ in the storage role vector ensures that+-- the original seed, the @"disk"@, and the @"cloud.my.domain.example"@+-- strings can be fully committed to while waiting for the disk key.+--+-- Another possibility is to use filler padding to control the context+-- buffer position; I suggest using 32-95 or more bytes, as this ensures+-- the encoded length is 3 bytes long and thus ensures that your desired+-- buffer position can be reached from any starting point.+--+-- In the case that you want or need to persist or serialize the+-- intermediate structures, then the plain-old-datatypes 'G3PSpark',+-- 'G3PSeed', 'G3PSprout', 'G3PTree','G3PKey', 'G3PSource', and their+-- associated functions are more relevant than implicit closures.+--+-- These data structures explicitly represent the result of a partial+-- evaluation, and provide a continuation onward to any one of innumerable+-- final results.++-- Oof, I didn't actually succeed in my claim about the g3pHash supporting+-- efficient partial application in the first release of G3Pb1.+--+-- I now have a deeper appreciation for point-less programming.++g3pHash+ :: Foldable f+ => G3PSalt -- ^ All the parameters needed throughout the entire key-stretching computation.+ -> G3PInputs -- ^ All the parameters that can be forgotten as soon as they are hashed once.+ -> G3PSeedInputs -- ^ All the parameters needed for bcrypt-based key stretching+ -> HmacKey -- ^ Sprout Seguid. A good default is to duplicate 'g3pSalt_seguid'.+ -> f ByteString -- ^ Sprout Role, an arbitrary number of bytestring parameters for late domain separation occuring after key-stretching is complete. Meaning is deployment defined.+ -> ByteString -- ^ Sprout Tag. A good default is to duplicate 'g3pSalt_domainTag'.+ -> ByteString -- ^ This @echo key@ is the right half of the output key. It is truncated to 32 bytes.+ -> ByteString+ -- ^ The @echo header@ is truncated to 32 bytes.+ --+ -- As the initial state of the output stream generator, if more than one+ -- block of the resulting output stream is ever examined, then this+ -- parameter must not include any new secrets. Otherwise the old secrets+ -- are potentially still crackable from the relationship between output+ -- stream blocks.+ --+ -- This problem can be avoided by ensuring at least one of these are true:+ --+ -- 1. Sticking to anodyne messages that aren't too specifically+ -- related to this password attempt, like a company name.+ --+ -- 2. including data that's already been included earlier in the+ -- derivation chain, i.e. deeper in the Merkle tree+ --+ -- 3. duplicating the content of this parameter in the @echo key@+ -- and/or @echo tag@ parameters.+ --+ -- 4. never examine more than one output block.+ --+ -- It is possible to use this parameter safely in ways that don't exactly+ -- meet any of the criteria above, but these criteria would seem to fairly+ -- comprehensively cover typical use cases. I'm not sure why a deployment+ -- designer might feel a need to go beyond these criteria.+ --+ -- Also note that if you feed an output block from _G3Pb2 echo_ back into+ -- this parameter, keep the keys and tag the same, and update the counter+ -- accordingly, then this will "collide" with the next output block of the+ -- original generator. This issue can be avoided by a deployment, so it's+ -- better to not get too creative with this specific parameter.+ -> Word32 -- ^ echo counter+ -> ByteString -- ^ echo tag. A good default is to duplicate the sprout's tag.+ -> ByteString+ -- ^ a 32-byte output hash. You can use the stream variant if you want more+ -- blocks. This is the first output block of that stream.+g3pHash = ( fmap . fmap . fmap . fmap . fmap+ . fmap . fmap . fmap . fmap . fmap $ g3pSource_head) g3pSource++-- | 'g3pSpark' encompasses calls to @G3Pb2 alfa@ and @G3Pb2 bravo@, which+-- provides the PHKDF-based key-stretching phase.+--+-- All 8 parameters get unambiguously encoded into @G3Pb2 alfa@, which is+-- the initial call to HMAC. 7 of them can be unambiguously parsed out of+-- the input message, thus proving that all collisions over them are+-- cryptographically non-trivial. The eighth is used as the HMAC key.+--+-- Moreover, the G3P's syntax generators never examine the content of any+-- input, only length. Thus by [parametricity](https://en.wikipedia.org/wiki/Parametricity),+-- any vaguely reasonable attempt to implement the G3P cannot be directly+-- responsible for introducing a data-dependent side channel.+--+-- The hash resulting from this initial HMAC-Extract, in addition to the+-- 'G3PSalt' parameters, determine the exact size, shape, and content of the+-- Merkle tree that describes the resulting spark. At this point in time+-- every computation is fully determined all the way to the end of the+-- PHKDF key-stretching phase. The next opportunity to make a choice is+-- the bcrypt key-stretching phase.+--+-- A spark consists of two cryptographically independent keys: keyB which+-- begins bcrypt, and keyC which is the continuation control key. The+-- continuation control key allows some or all of the bcrypt computation+-- to be outsourced to another semi-trusted device, without giving that+-- device the ability to compute the final seed.++g3pSpark+ :: G3PSalt -- ^ salt parameters, typically specified by deployment, typically needed throughout PHKDF key stretching+ -> G3PInputs -- ^ input parameters, often provided by the user, ready to be forgotten soon after the computation starts+ -> G3PSpark -- ^ the end of @G3Pb2 bravo@, the beginning of @G3P charlie@+g3pSpark salt inputs = spark+ 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 = g3pSalt_domainTag salt+ seguid = g3pSalt_seguid salt+ longTag = g3pSalt_longTag salt+ contextTags = g3pSalt_contextTags salt+ phkdfRounds = g3pSalt_phkdfRounds salt++ username = g3pInputs_username inputs+ password = g3pInputs_password inputs+ credentials = g3pInputs_credentials inputs++ headerAlfa = [ "G3Pb2 alfa username", username ]++ usernamePadLen = a+ where+ al = encodedVectorByteLength headerAlfa+ a = add64WhileLt (349 - al) 32++ usernamePadding = B.concat $+ takeBs (fromIntegral (usernamePadLen - 32)) (cycle [longTag, "\x00"]) +++ takeBs 32 [domainTag, "\x00", "password G3Pb2", nullBuffer]++ headerUsername = headerAlfa ++ [ usernamePadding ]++ -- password will go here++ headerLongTag =+ [ longTag+ , B.concat+ [ bareEncode phkdfRounds+ , "Global Password Prehash Protocol bcryptXsFree v2 G3Pb2"+ ]+ ]++ passwordPadLen = c+ where+ al = encodedVectorByteLength headerLongTag+ a = add64WhileLt (8605 - al) 4449+ bl = encodedVectorByteLength headerUsername+ b = add64WhileLt (a - bl) 328+ cl = encodedByteLength password+ c = add64WhileLt (b - cl) 32++ longPadding =+ takeBs (fromIntegral (passwordPadLen - 32)) (cycle [longTag, "\x00"]) +++ takeBs 32 [domainTag, "\x00", "creds G3Pb2", nullBuffer]++ credsPadLen = a+ where+ al = encodedVectorByteLength credentials+ a = add64WhileLt (314 - al) 32++ credsPadding =+ takeBs (fromIntegral (credsPadLen - 29)) (cycle [longTag, "\x00"]) +++ takeBs 29 [domainTag, "\x00", "tags G3Pb2", nullBuffer]++ (PairBS alfaSum alfaExt) =+ phkdfCtx_init seguid &+ phkdfCtx_feedArgs headerUsername &+ phkdfCtx_assertBufferPosition' 32 &+ phkdfCtx_feedArg password &+ phkdfCtx_feedArgs headerLongTag &+ phkdfCtx_feedArgConcat longPadding &+ phkdfCtx_assertBufferPosition' 32 &+ phkdfCtx_feedArgs credentials &+ phkdfCtx_feedArgConcat credsPadding &+ phkdfCtx_assertBufferPosition' 29 &+ phkdfCtx_feedArgs contextTags &+ phkdfCtx_feedArg (bareEncode (V.length contextTags)) &+ phkdfCtx_toStream endPadding+ (word32 "go\x00\x00" + 2024) domainTag &+ xorScan & myDrop' 1 & -- ensure that the sum is not filled with nulls+ myDrop' phkdfRounds & -- do the requested number of additional rounds+ Stream.head++ endPadding = B.concat . flip takeBs (cycle [longTag, "\x00"]) . fromIntegral++ bravo = "G3Pb2 bravo"++ headerBravo =+ [ bravo, alfaExt ] ++ takeBs 53 (cycle [longTag, "\x00"]) +++ [ alfaSum ]++ -- keyB: key bravo begins bcrypt (and key bullshit baffles brains)+ -- keyB was generated by the call to G3Pb2 bravo, and starts bcrypt+ -- keyB is used to generate a prefixed hmac key to bcrypt,+ -- which is used to generate round keys for bcrypt, as well+ -- to summarize bcrypt's pBox and sBox++ ("",prefixBravo) = hmacKeyPrefixed_init seguid &+ hmacKeyPrefixed_feeds headerBravo++ bravoKeyPad b = B.concat $+ takeBs 31 [domainTag, "\x00", bravo, nullBuffer] ++ [b]++ keyB = phkdfCtx_initPrefixed (bravoKeyPad "B") prefixBravo &+ phkdfCtx_feedArgs contextTags &+ phkdfCtx_finalize endPadding (word32 "KEYB") domainTag++ -- keyC, charlie's continuation control key+ -- It's inclusion (or omission) is a fundamental design tradeoff.++ -- This key allows a low-power device to outsource (part of) the+ -- bcrypt computation without losing control of the continuation+ -- that leads to the seed.++ -- The downside is that keyC has less key stretching than the bcrypt+ -- computation, and cannot be forgotten until after the bcrypt key+ -- stretching is complete, or the computation is abandoned.++ -- Given that this is already at PBKDF2-level key stretching,+ -- and that many password hash functions have historically not+ -- cared about how soon (and often) their computation becomes+ -- unreversable, I think this is a good tradeoff in the+ -- primary intended context of a client-side prehash function.++ keyC = phkdfCtx_initPrefixed (bravoKeyPad "C") prefixBravo &+ phkdfCtx_feedArgs contextTags &+ phkdfCtx_finalize endPadding (word32 "KEYC") domainTag++ -- Note that the two keys above are derived to be independent of+ -- each other regardless of whether the seguid and domain tag are+ -- public or private knowledge. Once you get the final output+ -- stream, there's no need to do this yourself, as the hmac key+ -- powering the stream generator can be assumed to be secret.++ -- Thus, if you want to use the G3P to loft something bigger than+ -- bcrypt, you could just put (word32 "KEYB") in as the echo counter+ -- and take the first key of the stream to be the beginning key+ -- take the second key of the stream to be the continuation key.+ -- However we avoid doing this here because of the issue above.++ -- There is of course no harm in a deployment choosing to emulate+ -- the construction used above. One could use the echo header,+ -- echo counter, echo tag, or even move this particular form of+ -- domain separation earlier in the derivation chain.+ -- (i.e. deeper in the Merkle tree)++ -- It's just that using the PHKDF output stream is an option then,+ -- and it isn't now. A deployment just has to commit to one mode of+ -- operation or the other, and I don't understand why it might matter+ -- too much one way or the other.++ spark = G3PSpark+ { g3pSpark_beginKey = keyB+ , g3pSpark_contKey = keyC+ , g3pSpark_contextTags = contextTags+ , g3pSpark_domainTag = domainTag+ }++myDrop' :: Word32 -> Stream a -> Stream a+myDrop' = go+ where+ go 0 s = s+ go n (Cons x s) = x `seq` go (n-1) s++xorBS :: ByteString -> ByteString -> ByteString+xorBS = B.packZipWith xor++data PairBS = PairBS !ByteString !ByteString++xorScan :: Stream ByteString -> Stream PairBS+xorScan = Stream.tail . Stream.scan' f (PairBS blankChunk blankChunk)+ where f (PairBS acc old) new = PairBS (xorBS acc old) new+ blankChunk = B.replicate 32 0++-- | The bcrypt key-stretching phase.++g3pSpark_toSeed+ :: G3PSpark -- ^ the end of @G3Pb2 bravo@, the beginning of @G3Pb2 charlie@+ -> G3PSeedInputs -- ^ bcrypt parameters+ -> G3PSeed -- ^ the end of @G3Pb2 charlie@, the beginning of @G3Pb2 delta@+g3pSpark_toSeed spark inputs = G3PSeed seed+ where+ beginKey = g3pSpark_beginKey spark+ contKey = g3pSpark_contKey spark+ contextTags = g3pSpark_contextTags spark+ domainTag = g3pSpark_domainTag spark++ bSeguid = g3pSeedInputs_bcryptSeguid inputs+ bCreds = g3pSeedInputs_bcryptCredentials inputs+ bRounds = g3pSeedInputs_bcryptRounds inputs+ bLongTag = g3pSeedInputs_bcryptLongTag inputs+ bDomainTag = g3pSeedInputs_bcryptDomainTag inputs+ bContextTags = g3pSeedInputs_bcryptContextTags inputs++ charlie = "G3Pb2 charlie"++ charlieHeader = charlie : beginKey : takeBs 19 [domainTag, nullBuffer]++ ("", charliePrefix) =+ hmacKeyPrefixed_init bSeguid &+ hmacKeyPrefixed_feeds charlieHeader++ bcryptName = B.concat+ [ "G3Pb2 bcrypt-xs-free"+ , bytestring64 (8 * fromIntegral (B.length bLongTag))+ ]++ (_, charlieCont) =+ bcryptXsFree id bcryptName bCreds bLongTag bContextTags bDomainTag+ bRounds charliePrefix++ contPad = B.concat $ takeBs 32 [domainTag, "\x00", charlie, nullBuffer]++ ("", endCont) = hmacKeyPrefixed_feeds [contPad, contKey] charlieCont++ seed = phkdfCtx_initPrefixed contPad endCont &+ phkdfCtx_feedArgs contextTags &+ phkdfCtx_finalize endPadding (word32 "SEED") domainTag++ endPadding = B.concat . flip takeBs (cycle [domainTag, "\x00"]) . fromIntegral++-- | Start a call to @G3Pb2 delta@, starting with the Sprout's Seguid.++g3pSeed_toSprout+ :: G3PSeed -- ^ the end of @G3Pb2 charlie@, the beginning of @G3Pb2 delta@+ -> HmacKey -- ^ Sprout Seguid+ -> G3PSprout -- ^ the middle of @G3Pb2 delta@+g3pSeed_toSprout (G3PSeed seed) key = G3PSprout ctx+ where+ delta = "G3Pb2 delta"+ ctx = phkdfCtx_init key &+ phkdfCtx_feedArg (delta <> seed)++-- | flipped version of 'g3pSprout_arg'++g3pSprout_feedArg+ :: ByteString -- ^ arg+ -> G3PSprout -- ^ the middle of @G3Pb2 delta@+ -> G3PSprout -- ^ a later middle of @G3Pb2 delta@, ready for more args, or grow into a @G3PTree@+g3pSprout_feedArg x = G3PSprout . phkdfCtx_feedArg x . g3pSprout_phkdfCtx++-- | flipped version of 'g3pSprout_args'++g3pSprout_feedArgs+ :: Foldable f+ => f ByteString -- ^ zero or more args+ -> G3PSprout -- ^ the middle of @G3Pb2 delta@+ -> G3PSprout -- ^ a later middle of @G3Pb2 delta@, ready for more args, or grow into a @G3PTree@+g3pSprout_feedArgs xs = G3PSprout . phkdfCtx_feedArgs xs . g3pSprout_phkdfCtx++-- | The name of this function is a mnemonic for the argument order, which+-- takes an sprout and adds a single length-delimited argument to it.++g3pSprout_arg+ :: G3PSprout -- ^ the middle of @G3Pb2 delta@+ -> ByteString -- ^ arg+ -> G3PSprout -- ^ a later middle of @G3Pb2 delta@, ready for more args, or grow into a @G3PTree@+g3pSprout_arg = flip g3pSprout_feedArg++-- | The name of this function is a mnemonic for the argument order, which+-- takes a sprout and adds zero or more length-delimited arguments to it.++g3pSprout_args+ :: Foldable f+ => G3PSprout -- ^ the middle of @G3Pb2 delta@+ -> f ByteString -- ^ zero or more args+ -> G3PSprout -- ^ a later middle of @G3Pb2 delta@, ready for more args, or grow into a @G3PTree@+g3pSprout_args = flip g3pSprout_feedArgs++g3pSprout_toTree+ :: G3PSprout -- ^ the middle of @G3Pb2 delta@+ -> ByteString -- ^ Sprout Domain Tag+ -> G3PTree -- the end of @G3Pb2 delta@, the beginning of @G3Pb2 echo@+g3pSprout_toTree (G3PSprout ctx) domainTag = G3PTree key+ where+ key = phkdfCtx_finalize endPadding (word32 "KEYL") domainTag ctx+ endPadding = B.concat . flip takeBs (cycle [domainTag, "\x00"]) . fromIntegral++g3pTree_toKey+ :: G3PTree -- the end of @G3Pb2 delta@, the beginning of @G3Pb2 echo@+ -> ByteString -- ^ echo key right+ -> G3PKey -- ^ the middle of @G3Pb2 echo@+g3pTree_toKey (G3PTree echoKeyL) echoKeyR = G3PKey (hmacKeyHashed key)+ where+ keyR = takeBs 32 [echoKeyR, "\x00", "G3Pb2 echo key right padding", nullBuffer]+ -- Note that echoKeyL should already be 32 bytes, so this should be id:+ keyL = takeBs 32 [echoKeyL, nullBuffer]+ key = B.concat (keyL ++ keyR)++-- | Variant of 'g3pKey_toStream' that returns plain old data.++g3pKey_toSource+ :: G3PKey -- ^ the middle of @G3Pb2 echo@+ -> ByteString -- ^ echo header+ -> Word32 -- ^ echo counter+ -> ByteString -- ^ echo tag+ -> G3PSource -- ^ plain-old data representation of an output stream+g3pKey_toSource (G3PKey key) echoHeader echoCtr echoTag = gen+ where+ hdr = B.concat $+ takeBs 32 [echoHeader, "\x00", "G3Pb2 echo header padding", nullBuffer]+ gen = phkdfGen_initHashed key hdr echoCtr echoTag++g3pSource_head :: G3PSource -> ByteString+g3pSource_head = fst . phkdfGen_read++g3pSource_read :: G3PSource -> (ByteString, G3PSource)+g3pSource_read = phkdfGen_read++g3pSource_peek :: G3PSource -> Maybe ByteString+g3pSource_peek = phkdfGen_peek++g3pSource_toStream :: G3PSource -> Stream ByteString+g3pSource_toStream = phkdfGen_toStream++type G3PSource = PhkdfGen++-- | Turn a secret, derived 'HmacKeyHashed' into an unbounded+-- stream of 32-byte output blocks.++g3pKey_toStream+ :: G3PKey -- ^ the middle of @G3Pb2 echo@+ -> ByteString -- ^ echo header+ -> Word32+ -- ^ The @echo counter@, functionally a bonus HKDF info parameter. The test suite defaults to (word32 "OUT\x00")+ -> ByteString+ -- ^ The @echo tag@, functionally identical to HKDF's info parameter.+ -> Stream ByteString+g3pKey_toStream key hdr ctr tag =+ phkdfGen_toStream (g3pKey_toSource key hdr ctr tag)++g3pTree_toStream+ :: G3PTree -- ^ the end of @G3Pb2 delta@, the beginning of @G3Pb2 echo@+ -> ByteString -- ^ echo key right+ -> ByteString -- ^ echo header+ -> Word32 -- ^ echo counter+ -> ByteString -- ^ echo tag+ -> Stream ByteString+g3pTree_toStream = fmap g3pKey_toStream . g3pTree_toKey++g3pSprout_toStream+ :: Foldable f+ => G3PSprout -- ^ the middle of @G3Pb2 delta@+ -> f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> ByteString -- ^ echo key right+ -> ByteString -- ^ echo header+ -> Word32 -- ^ echo counter+ -> ByteString -- ^ echo tag+ -> Stream ByteString+g3pSprout_toStream =+ fmap (fmap g3pTree_toStream . g3pSprout_toTree) . g3pSprout_args++g3pSeed_toStream+ :: Foldable f+ => G3PSeed -- ^ the end of @G3Pb2 charlie@, the beginning of @G3Pb2 delta@+ -> HmacKey -- ^ sprout seguid+ -> f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> ByteString -- ^ echo key right+ -> ByteString -- ^ echo header+ -> Word32 -- ^ echo counter+ -> ByteString -- ^ echo tag+ -> Stream ByteString+g3pSeed_toStream = fmap g3pSprout_toStream . g3pSeed_toSprout++g3pSpark_toStream+ :: Foldable f+ => G3PSpark -- ^ a partial evaluation of @G3Pb2 bravo@+ -> G3PSeedInputs -- ^ bcrypt parameters+ -> HmacKey -- ^ sprout seguid+ -> f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> ByteString -- ^ echo key right+ -> ByteString -- ^ echo header+ -> Word32 -- ^ echo counter+ -> ByteString -- ^ echo tag+ -> Stream ByteString+g3pSpark_toStream = fmap g3pSeed_toStream . g3pSpark_toSeed+++g3pSpark_toSprout+ :: G3PSpark -- ^ a partial evaluation of @G3Pb2 bravo@+ -> G3PSeedInputs -- ^ bcrypt parameters+ -> HmacKey -- ^ sprout seguid+ -> G3PSprout -- ^ the middle of @G3Pb2 delta@+g3pSpark_toSprout = fmap g3pSeed_toSprout . g3pSpark_toSeed++g3pSpark_toTree+ :: Foldable f+ => G3PSpark -- ^ a partial evaluation of @G3Pb2 bravo@+ -> G3PSeedInputs -- ^ bcrypt parameters+ -> HmacKey -- ^ sprout seguid+ -> f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> G3PTree -- ^ the end of @G3Pb2 delta@, the beginning of @G3Pb2 echo@+g3pSpark_toTree = fmap g3pSeed_toTree . g3pSpark_toSeed++g3pSpark_toKey+ :: Foldable f+ => G3PSpark -- ^ a partial evaluation of @G3Pb2 bravo@+ -> G3PSeedInputs -- ^ bcrypt parameters+ -> HmacKey -- ^ sprout seguid+ -> f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> ByteString -- ^ echo key right+ -> G3PKey -- ^ the middle of @G3Pb2 echo@+g3pSpark_toKey = fmap g3pSeed_toKey . g3pSpark_toSeed++g3pSpark_toSource+ :: Foldable f+ => G3PSpark -- ^ a partial evaluation of @G3Pb2 bravo@+ -> G3PSeedInputs -- ^ bcrypt parameters+ -> HmacKey -- ^ sprout seguid+ -> f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> ByteString -- ^ echo key right+ -> ByteString -- ^ echo header+ -> Word32 -- ^ echo counter+ -> ByteString -- ^ echo tag+ -> G3PSource -- ^ plain-old data representation of an output stream+g3pSpark_toSource = fmap g3pSeed_toSource . g3pSpark_toSeed++g3pSeed+ :: G3PSalt -- ^ salt parameters, typically specified by deployment, typically needed throughout PHKDF key stretching+ -> G3PInputs -- ^ input parameters, often provided by the user, ready to be forgotten soon after the computation starts+ -> G3PSeedInputs+ -> G3PSeed -- ^ the end of @G3Pb2 charlie@, the beginning of @G3Pb2 delta@+g3pSeed = (fmap . fmap $ g3pSpark_toSeed) g3pSpark++g3pSeed_fromSpark+ :: G3PSeedInputs -- ^ bcrypt parameters+ -> G3PSpark -- ^ a partial evaluation of @G3Pb2 bravo@+ -> G3PSeed -- ^ the end of @G3Pb2 charlie@, the beginning of @G3Pb2 delta@+g3pSeed_fromSpark = flip g3pSpark_toSeed++g3pSeed_toTree+ :: Foldable f+ => G3PSeed -- ^ the end of @G3Pb2 charlie@, the beginning of @G3Pb2 delta@+ -> HmacKey -- ^ sprout seguid+ -> f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> G3PTree -- ^ the end of @G3Pb2 delta@, the beginning of @G3Pb2 echo@+g3pSeed_toTree = fmap (fmap g3pSprout_toTree . g3pSprout_args) . g3pSeed_toSprout++g3pSeed_toKey+ :: Foldable f+ => G3PSeed -- ^ the end of @G3Pb2 charlie@, the beginning of @G3Pb2 delta@+ -> HmacKey -- ^ sprout seguid+ -> f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> ByteString -- ^ echo key right+ -> G3PKey -- ^ the middle of @G3Pb2 echo@+g3pSeed_toKey = fmap g3pSprout_toKey . g3pSeed_toSprout++g3pSeed_toSource+ :: Foldable f+ => G3PSeed -- ^ the end of @G3Pb2 charlie@, the beginning of @G3Pb2 delta@+ -> HmacKey -- ^ sprout seguid+ -> f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> ByteString -- ^ echo key right+ -> ByteString -- ^ echo header+ -> Word32 -- ^ echo counter+ -> ByteString -- ^ echo tag+ -> G3PSource -- ^ plain-old data representation of an output stream+g3pSeed_toSource = fmap g3pSprout_toSource . g3pSeed_toSprout++g3pSprout+ :: G3PSalt -- ^ salt parameters, typically specified by deployment, typically needed throughout PHKDF key stretching+ -> G3PInputs -- ^ input parameters, often provided by the user, ready to be forgotten soon after the computation starts+ -> G3PSeedInputs -- ^ bcrypt parameters+ -> HmacKey -- ^ sprout seguid+ -> G3PSprout -- ^ the middle of @G3Pb2 delta@+g3pSprout = fmap g3pSpark_toSprout . g3pSpark++-- There is no need to use the point-free style on the "from" variants, as the+-- order of arguments obviates the useful and interesting partial applications+g3pSprout_fromSpark+ :: G3PSeedInputs -- ^ bcrypt parameters+ -> HmacKey -- ^ sprout seguid+ -> G3PSpark -- ^ the end of @G3Pb2 bravo@, the beginning of @G3P charlie@+ -> G3PSprout -- ^ the middle of @G3Pb2 delta@+g3pSprout_fromSpark inputs key spark =+ g3pSpark_toSprout spark inputs key++g3pSprout_fromSeed+ :: HmacKey -- ^ sprout seguid+ -> G3PSeed -> G3PSprout -- ^ the middle of @G3Pb2 delta@+g3pSprout_fromSeed = flip g3pSeed_toSprout++g3pSprout_toKey+ :: Foldable f+ => G3PSprout -- ^ a partial evaluation of @G3Pb2 delta@+ -> f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> ByteString -- ^ echo key right+ -> G3PKey -- ^ the beginning of @G3P echo@+g3pSprout_toKey = fmap (fmap g3pTree_toKey . g3pSprout_toTree) . g3pSprout_args++g3pSprout_toSource+ :: Foldable f+ => G3PSprout -- ^ the middle of @G3Pb2 delta@+ -> f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> ByteString -- ^ echo key right+ -> ByteString -- ^ echo header+ -> Word32 -- ^ echo counter+ -> ByteString -- ^ echo tag+ -> G3PSource -- ^ plain-old data representation of an output stream+g3pSprout_toSource =+ fmap (fmap g3pTree_toSource . g3pSprout_toTree) . g3pSprout_args++g3pTree+ :: Foldable f+ => G3PSalt -- ^ salt parameters, typically specified by deployment, typically needed throughout PHKDF key stretching+ -> G3PInputs -- ^ input parameters, often provided by the user, ready to be forgotten soon after the computation starts+ -> G3PSeedInputs -- ^ bcrypt parameters+ -> HmacKey -- ^ sprout seguid+ -> f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> G3PTree -- ^ the end of @G3Pb2 delta@, the beginning of @G3Pb2 echo@+g3pTree = fmap g3pSpark_toTree . g3pSpark++g3pTree_fromSpark+ :: Foldable f+ => G3PSeedInputs -- ^ bcrypt parameters+ -> HmacKey -- ^ sprout seguid+ -> f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> G3PSpark -- ^ the end of @G3Pb2 bravo@, the beginning of @G3Pb2 charlie@+ -> G3PTree -- ^ the end of @G3Pb2 delta@, the beginning of @G3Pb2 echo@+g3pTree_fromSpark inputs key role tag spark =+ g3pSpark_toTree spark inputs key role tag++g3pTree_fromSeed+ :: Foldable f+ => HmacKey -- ^ sprout seguid+ -> f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> G3PSeed -- ^ the end of @G3Pb2 charlie@, the beginning of @G3Pb2 delta@+ -> G3PTree -- ^ the end of @G3Pb2 delta@, the beginning of @G3Pb2 echo@+g3pTree_fromSeed key role tag seed =+ g3pSeed_toTree seed key role tag++g3pTree_fromSprout+ :: ByteString -- ^ sprout tag+ -> G3PSprout -> G3PTree -- ^ the end of @G3Pb2 delta@, the beginning of @G3Pb2 echo@+g3pTree_fromSprout = flip g3pSprout_toTree++g3pTree_toSource+ :: G3PTree -- ^ the end of @G3Pb2 delta@, the beginning of @G3Pb2 echo@+ -> ByteString -- ^ echo key right+ -> ByteString -- ^ echo header+ -> Word32 -- ^ echo counter+ -> ByteString -- ^ echo tag+ -> G3PSource -- ^ plain-old data representation of an output stream+g3pTree_toSource = fmap g3pKey_toSource . g3pTree_toKey++g3pKey+ :: Foldable f+ => G3PSalt -- ^ salt parameters, typically specified by deployment, typically needed throughout PHKDF key stretching+ -> G3PInputs -- ^ input parameters, often provided by the user, ready to be forgotten soon after the computation starts+ -> G3PSeedInputs -- ^ bcrypt parameters+ -> HmacKey -- ^ sprout seguid+ -> f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> ByteString -- ^ echo key right+ -> G3PKey -- ^ the beginning of @G3P echo@+g3pKey = fmap g3pSpark_toKey . g3pSpark++g3pKey_fromSpark+ :: Foldable f+ => G3PSeedInputs -- ^ bcrypt parameters+ -> HmacKey -- ^ sprout seguid+ -> f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> ByteString -- ^ echo key right+ -> G3PSpark -- ^ a partial evaluation of @G3P bravo@+ -> G3PKey -- ^ the beginning of @G3P echo@+g3pKey_fromSpark inputs key role tag ekey spark =+ g3pSpark_toKey spark inputs key role tag ekey++g3pKey_fromSeed+ :: Foldable f+ => HmacKey -- ^ sprout seguid+ -> f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> ByteString -- ^ echo key right+ -> G3PSeed -- ^ the end of @G3Pb2 charlie@, the beginning of @G3Pb2 delta@+ -> G3PKey -- ^ the middle of @G3Pb2 echo@+g3pKey_fromSeed key role tag ekey seed =+ g3pSeed_toKey seed key role tag ekey++g3pKey_fromSprout+ :: Foldable f+ => f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> ByteString -- ^ echo key right+ -> G3PSprout -- ^ the middle of @G3Pb2 delta@+ -> G3PKey -- ^ the middle of @G3Pb2 echo@+g3pKey_fromSprout role tag key sprout =+ g3pSprout_toKey sprout role tag key++g3pKey_fromTree+ :: ByteString -- ^ echo key right+ -> G3PTree -- ^ the end of @G3Pb2 delta@, the beginning of @G3Pb2 echo@+ -> G3PKey -- ^ the middle of @G3Pb2 echo@+g3pKey_fromTree = flip g3pTree_toKey++g3pSource+ :: Foldable f+ => G3PSalt -- ^ salt parameters, typically specified by deployment, typically needed throughout PHKDF key stretching+ -> G3PInputs -- ^ input parameters, often provided by the user, ready to be forgotten soon after the computation starts+ -> G3PSeedInputs -- ^ bcrypt parameters+ -> HmacKey -- ^ sprout seguid+ -> f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> ByteString -- ^ echo key right+ -> ByteString -- ^ echo header+ -> Word32 -- ^ echo counter+ -> ByteString -- ^ echo tag+ -> G3PSource -- ^ A plain-old-data representation of a G3P output stream+g3pSource = fmap g3pSpark_toSource . g3pSpark++g3pSource_fromSpark+ :: Foldable f+ => G3PSeedInputs -- ^ bcrypt parameters+ -> HmacKey -- ^ sprout seguid+ -> f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> ByteString -- ^ echo key right+ -> ByteString -- ^ echo header+ -> Word32 -- ^ echo counter+ -> ByteString -- ^ echo tag+ -> G3PSpark -> G3PSource -- ^ plain-old data representation of an output stream+g3pSource_fromSpark inputs key role tag ekey ehdr ectr etag spark =+ g3pSpark_toSource spark inputs key role tag ekey ehdr ectr etag++g3pSource_fromSeed+ :: Foldable f+ => HmacKey -- ^ sprout seguid+ -> f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> ByteString -- ^ echo key right+ -> ByteString -- ^ echo header+ -> Word32 -- ^ echo counter+ -> ByteString -- ^ echo tag+ -> G3PSeed -- ^ the end of @G3Pb2 charlie@, the beginning of @G3Pb2 delta@+ -> G3PSource -- ^ plain-old data representation of an output stream+g3pSource_fromSeed key role tag ekey ehdr ectr etag seed =+ g3pSeed_toSource seed key role tag ekey ehdr ectr etag++g3pSource_fromSprout+ :: Foldable f+ => f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> ByteString -- ^ echo key right+ -> ByteString -- ^ echo header+ -> Word32 -- ^ echo counter+ -> ByteString -- ^ echo tag+ -> G3PSprout -- ^ the middle of @G3Pb2 delta@+ -> G3PSource -- ^ plain-old data representation of an output stream+g3pSource_fromSprout role tag ekey ehdr ectr etag sprout =+ g3pSprout_toSource sprout role tag ekey ehdr ectr etag++g3pSource_fromTree+ :: ByteString -- ^ echo key right+ -> ByteString -- ^ echo header+ -> Word32 -- ^ echo counter+ -> ByteString -- ^ echo tag+ -> G3PTree -- ^ the end of @G3Pb2 delta@, the beginning of @G3Pb2 echo@+ -> G3PSource -- ^ plain-old data representation of an output stream+g3pSource_fromTree ekey ehdr ectr etag tree =+ g3pTree_toSource tree ekey ehdr ectr etag++g3pSource_fromKey+ :: ByteString -- ^ echo header+ -> Word32 -- ^ echo counter+ -> ByteString -- ^ echo tag+ -> G3PKey -- ^ the middle of @G3Pb2 echo@+ -> G3PSource -- ^ plain-old data representation of an output stream+g3pSource_fromKey ehdr ectr etag key =+ g3pKey_toSource key ehdr ectr etag++-- | This variant of 'g3pHash' returns an unbounded stream of 32-byte output+-- blocks. Use as many or as few as you want. Assuming the non-echo-header+-- inputs contain at least one strong secret, the output blocks are+-- cryptographically independent. You can partition the output into+-- non-overlapping chunks and use those chunks however you see fit.+--+-- NIST SP 800-108 recommendations imply that you shouldn't look at more+-- than 137.4 GB of output. This recommendation is extremely cautious, and+-- it's probably okay-ish in most circumstances to exceed that limit by a+-- considerable margin.+--+-- On the other hand, if you really want that much CSPRNG data, you may+-- well be better off using this function to generate keys for another,+-- faster CSPRNG.++g3pStream+ :: Foldable f+ => G3PSalt -- ^ salt parameters, typically specified by deployment, typically needed throughout PHKDF key stretching+ -> G3PInputs -- ^ input parameters, often provided by the user, ready to be forgotten soon after the computation starts+ -> G3PSeedInputs -- ^ bcrypt parameters+ -> HmacKey -- ^ sprout seguid+ -> f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> ByteString -- ^ echo key right+ -> ByteString -- ^ echo header+ -> Word32 -- ^ echo counter+ -> ByteString -- ^ echo tag+ -> Stream ByteString+g3pStream = fmap g3pSpark_toStream . g3pSpark++g3pStream_fromSpark+ :: Foldable f+ => G3PSeedInputs -- ^ bcrypt parameters+ -> HmacKey -- ^ sprout seguid+ -> f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> ByteString -- ^ echo key right+ -> ByteString -- ^ echo header+ -> Word32 -- ^ echo counter+ -> ByteString -- ^ echo tag+ -> G3PSpark -> Stream ByteString+g3pStream_fromSpark inputs key role tag ekey ehdr ectr etag spark =+ g3pSpark_toStream spark inputs key role tag ekey ehdr ectr etag++g3pStream_fromSeed+ :: Foldable f+ => HmacKey -- ^ sprout seguid+ -> f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> ByteString -- ^ echo key right+ -> ByteString -- ^ echo header+ -> Word32 -- ^ echo counter+ -> ByteString -- ^ echo tag+ -> G3PSeed -- ^ the end of @G3Pb2 charlie@, the beginning of @G3Pb2 delta@+ -> Stream ByteString+g3pStream_fromSeed key role tag ekey ehdr ectr etag seed =+ g3pSeed_toStream seed key role tag ekey ehdr ectr etag++g3pStream_fromSprout+ :: Foldable f+ => f ByteString -- ^ sprout role+ -> ByteString -- ^ sprout tag+ -> ByteString -- ^ echo key right+ -> ByteString -- ^ echo header+ -> Word32 -- ^ echo counter+ -> ByteString -- ^ echo tag+ -> G3PSprout -> Stream ByteString+g3pStream_fromSprout role tag ekey ehdr ectr etag sprout =+ g3pSprout_toStream sprout role tag ekey ehdr ectr etag++g3pStream_fromTree+ :: ByteString -- ^ echo key right+ -> ByteString -- ^ echo header+ -> Word32 -- ^ echo counter+ -> ByteString -- ^ echo tag+ -> G3PTree -> Stream ByteString+g3pStream_fromTree key hdr ctr tag tree =+ g3pTree_toStream tree key hdr ctr tag++g3pStream_fromKey+ :: ByteString -- ^ echo header+ -> Word32 -- ^ echo counter+ -> ByteString -- ^ echo tag+ -> G3PKey -> Stream ByteString+g3pStream_fromKey hdr ctr tag key =+ g3pKey_toStream key hdr ctr tag++g3pStream_fromSource :: G3PSource -> Stream ByteString+g3pStream_fromSource = g3pSource_toStream
+ lib/Crypto/G3P/V2/Foxtrot.hs view
@@ -0,0 +1,148 @@+{-# LANGUAGE OverloadedStrings #-}++-------------------------------------------------------------------------------+-- |+-- Module: Crypto.G3P.V2.Foxtrot+-- Copyright: (c) 2024 Auth Global+-- License: Apache2+--+-------------------------------------------------------------------------------++{- |++Stripped-down version of G3Pb2 charlie, primarily intended for server-side+application. This uses HMAC-SHA256 both to generate inputs to bcrypt and to+summarize the resulting bcrypt state.++Assuming your deployment is basically a traditional give-me-your-password+authentication protocol but with prehashing, I recommend a two-step approach+to counteracting prehash precomputation attacks. First, I recommend that the+server apply at least as much key-stretching as the client. Second, I recommend+this server-side key-stretching be protected by a secret HMAC key.++In such a deployment, a cracker could front-load half of the computation+needed to guess a password at significant storage expense. This is far less+appealing to the cracker than being able to front-load nearly all of+the computation needed while incurring the same expense to store the+intermediate guesses.++The test suite's MyCorpExample.hs contains an example of how one might use+these functions in a deployment. By using reduced-round calls to 'g3pFoxtrot'+in conjuction with argon2, one can make precomputation an even less appealing+strategy, as the key-stretching occurs server-side is significantly more+expensive than the prehash itself.++-}+module Crypto.G3P.V2.Foxtrot where++import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import Data.Function((&))+import Data.Word+import Data.Vector(Vector)+import qualified Data.Vector as V++import Crypto.G3P.BCrypt (bcryptXsFree)+import Crypto.PHKDF+import Crypto.Encoding.PHKDF (takeBs, nullBuffer)++import Network.ByteOrder(bytestring64)++data G3PFoxtrotSalt = G3PFoxtrotSalt+ { g3pFoxtrotSalt_key :: !HmacKey+ , g3pFoxtrotSalt_longTag :: !ByteString+ , g3pFoxtrotSalt_contextTags :: !(Vector ByteString)+ , g3pFoxtrotSalt_domainTag :: !ByteString+ , g3pFoxtrotSalt_bcryptRounds :: !Word32+ }+++-- | G3Pb2 foxtrot is a function that incorporates a bcrypt-like key-stretching+-- phase. Stripped down version of G3Pb2 charlie, without a built-in continuation+-- control key. @test/MyCorpExample.hs@ uses this as a server-side cryptoacoustic+-- component that sandwiches the comparatively silent argon2.++g3pFoxtrot+ :: (Foldable f, Foldable g)+ => G3PFoxtrotSalt+ -> f ByteString+ -> g ByteString+ -> Word32+ -> ByteString+g3pFoxtrot salt inputs = doTweak+ where+ foxtrot = "G3Pb2 foxtrot"+ key = g3pFoxtrotSalt_key salt+ longTag = g3pFoxtrotSalt_longTag salt+ contextTags = g3pFoxtrotSalt_contextTags salt+ domainTag = g3pFoxtrotSalt_domainTag salt+ rounds = g3pFoxtrotSalt_bcryptRounds salt++ spark =+ phkdfCtx_init key &+ phkdfCtx_feedArg foxtrot &+ phkdfCtx_feedArgs inputs &+ phkdfCtx_toHmacKeyPrefixed (B.concat . flip takeBs [domainTag, "\x00", longTag, nullBuffer] . fromIntegral)++ -- G3Pb2 foxtrot doesn't ever explicitly encode the length of the syntax+ -- generated by bcryptXsFree in the plaintext of the HMAC message itself.++ -- This length of this syntax is determined by the number of superrounds,+ -- which in turn is determined by the number of bcrypt rounds.++ -- This doesn't create any homophones, a.k.a. "canonicalization attacks",+ -- which is terminology that the algebraist in me isn't fond of.++ -- It can't cause homophones because the number of rounds, and thus this+ -- syntax length, is encoded in the bcrypt key-stretching phase well before+ -- the very first bcrypt output byte is generated. As these bytes are+ -- then consumed by HMAC, their overall length is implicitly encoded into+ -- the HMAC message.++ -- Not to mention that there are heurstic methods to parse the generated+ -- HMAC syntax without knowing the number of bcrypt rounds up front+ -- that will perform with perfect accuracy on most or all actual+ -- deployments. Every superround results in a fixed-length, multi-kilobyte+ -- syntax string appended to the message, which is much much longer than+ -- the expected length of the remaining parameters. Futhermore each+ -- multi-kilobyte superround syntax string always contains the literal+ -- sequence of bytes "OrpheanBeholderScryDoubt" in a fixed location,+ -- which in practice won't appear in other parameters.++ bcryptName = B.concat+ [ "G3Pb2 bcrypt-xs-free"+ , bytestring64 (8 * fromIntegral (B.length longTag))+ ]++ (_tagPos, seed) =+ bcryptXsFree id bcryptName V.empty longTag contextTags domainTag+ rounds spark++ sprout =+ phkdfCtx_initPrefixed (B.concat $ takeBs 32 [domainTag, "\x00", foxtrot, nullBuffer]) seed &+ phkdfCtx_feedArgs contextTags++ doTweak tweak counter =+ phkdfCtx_feedArgs tweak sprout &+ phkdfCtx_finalize (B.concat . flip takeBs (cycle [domainTag, "\x00"]) . fromIntegral) counter domainTag+++-- | G3Pb2 tango: a simple application of PHKDF used to derive secret server-side+-- salts in @test/MyCorpExample.hs@.++-- TODO: rewrite this in a more point-free style, in order to better support partial application+g3pTango+ :: (Foldable f)+ => HmacKey+ -> f ByteString -- ^ inputs+ -> Word32 -- ^ counter+ -> ByteString -- ^ domain tag+ -> ByteString -- ^ 32-byte output hash+g3pTango key inputs counter domainTag = out+ where+ tango = "G3Pb2 tango"+ out =+ phkdfCtx_init key &+ phkdfCtx_feedArg tango &+ phkdfCtx_feedArgs inputs &+ phkdfCtx_finalize (B.concat . flip takeBs (cycle [domainTag, "\x00"]) . fromIntegral) counter domainTag
+ lib/Crypto/G3P/V2/Subtle.hs view
@@ -0,0 +1,65 @@+-------------------------------------------------------------------------------+-- |+-- Module: Crypto.G3P.V2.Subtle+-- Copyright: (c) 2024 Auth Global+-- License: Apache2+--+-- Plain-old-data explicit representations of intermediate 'g3pHash'+-- computations.+--+-------------------------------------------------------------------------------++module Crypto.G3P.V2.Subtle where++import Data.ByteString(ByteString)+import Data.Vector(Vector)+import Crypto.PHKDF.HMAC(HmacKeyHashed)+import Crypto.PHKDF(PhkdfCtx)++-- | Represents the completion of the PBKDF2-like key stretching computation,+-- and ready for bcrypt. Technically, a partial evaluation at the+-- completion of @G3Pb2 bravo@, ready for @G3Pb2 charlie@.++data G3PSpark = G3PSpark+ { g3pSpark_beginKey :: !ByteString+ -- ^ key bravo begins bcrypt+ , g3pSpark_contKey :: !ByteString+ -- ^ charlie's continuation control key to be used at the end of @G3Pb2 charlie@+ , g3pSpark_contextTags :: !(Vector ByteString)+ -- ^ the original PHKDF context tags to be used at the end of @G3Pb2 charlie@.+ , g3pSpark_domainTag :: !ByteString+ -- ^ the original PHKDF domain tag to be used at the end of @G3Pb2 charlie@.+ } deriving (Eq)++-- | A plain 32-byte hash that represents the completion of both phkdf and+-- bcrypt key stretching phases. Technically, a partial evaluation at+-- the completion of @G3Pb2 charlie@, ready for @G3Pb2 delta@.++newtype G3PSeed = G3PSeed+ { g3pSeed_seedKey :: ByteString+ } deriving (Eq)++-- | Represents a partial evaluation of @G3P delta@, initialized with+-- the Sprout Seguid and possibly commited to part of the role argument.+-- This comes before the Sprout Domain Tag, and in fact can be finalized+-- with that parameter at any time.++newtype G3PSprout = G3PSprout+ { g3pSprout_phkdfCtx :: PhkdfCtx+ }++-- | A plain 32-byte hash that represents the leftmost bytes of the output+-- hmac key. Technically, a partial evaluation ending at @G3Pb2 delta@+-- and ready for the right half of the echo key, as needed to begin the+-- evaluation of @G3Pb2 echo@++newtype G3PTree = G3PTree+ { g3pTree_echoKeyL :: ByteString -- ^ This is expected to be a 32-byte hash value+ } deriving (Eq)++-- | A precomputed hmac key intended for use with @G3Pb2 echo@. Technically,+-- a partial evaluation of the HMAC-SHA256 construction.++newtype G3PKey = G3PKey+ { g3pKey_streamKey :: HmacKeyHashed+ } -- deriving (Eq)
+ test/Bcrypt.hs view
@@ -0,0 +1,203 @@+{-# LANGUAGE OverloadedStrings #-}++module Bcrypt where++import Data.ByteString(ByteString)+import qualified Data.ByteString as B+import Test.Tasty+import Test.Tasty.HUnit+import Crypto.G3P.BCrypt+import Crypto.BCrypt++tests :: [TestTree]+tests =+ [ testGroup "external bcrypt binding"+ [ testCase ("bcrypt-" ++ show n ++ "-ext") (runRef x)+ | (n,x) <- zip [0..] testVectors+ ],+ testGroup "bcrypt test vectors"+ [ testCase ("bcrypt-" ++ show n) (run x)+ | (n,x) <- zip [0..] testVectors+ ]+ ]+ where+ run (pass,salt) = bcrypt pass (B.take 29 salt') @?= Just salt'+ where salt' = "$2b" <> B.drop 3 salt+ runRef (pass,salt) = hashPassword pass (B.take 29 salt) @?= Just salt++-- test vectors copied from+-- https://github.com/pyca/bcrypt/blob/c48c293102e0c8a9f0499adae165eebb9dc11d0b/tests/test_bcrypt.py++testVectors ::[(ByteString, ByteString)]+testVectors =+ [+ (+ "Kk4DQuMMfZL9o",+ "$2b$04$cVWp4XaNU8a4v1uMRum2SO026BWLIoQMD/TXg5uZV.0P.uO8m3YEm"+ ),+ (+ "9IeRXmnGxMYbs",+ "$2b$04$pQ7gRO7e6wx/936oXhNjrOUNOHL1D0h1N2IDbJZYs.1ppzSof6SPy"+ ),+ (+ "xVQVbwa1S0M8r",+ "$2b$04$SQe9knOzepOVKoYXo9xTteNYr6MBwVz4tpriJVe3PNgYufGIsgKcW"+ ),+ (+ "Zfgr26LWd22Za",+ "$2b$04$eH8zX.q5Q.j2hO1NkVYJQOM6KxntS/ow3.YzVmFrE4t//CoF4fvne"+ ),+ (+ "Tg4daC27epFBE",+ "$2b$04$ahiTdwRXpUG2JLRcIznxc.s1.ydaPGD372bsGs8NqyYjLY1inG5n2"+ ),+ (+ "xhQPMmwh5ALzW",+ "$2b$04$nQn78dV0hGHf5wUBe0zOFu8n07ZbWWOKoGasZKRspZxtt.vBRNMIy"+ ),+ (+ "59je8h5Gj71tg",+ "$2b$04$cvXudZ5ugTg95W.rOjMITuM1jC0piCl3zF5cmGhzCibHZrNHkmckG"+ ),+ (+ "wT4fHJa2N9WSW",+ "$2b$04$YYjtiq4Uh88yUsExO0RNTuEJ.tZlsONac16A8OcLHleWFjVawfGvO"+ ),+ (+ "uSgFRnQdOgm4S",+ "$2b$04$WLTjgY/pZSyqX/fbMbJzf.qxCeTMQOzgL.CimRjMHtMxd/VGKojMu"+ ),+ (+ "tEPtJZXur16Vg",+ "$2b$04$2moPs/x/wnCfeQ5pCheMcuSJQ/KYjOZG780UjA/SiR.KsYWNrC7SG"+ ),+ (+ "vvho8C6nlVf9K",+ "$2b$04$HrEYC/AQ2HS77G78cQDZQ.r44WGcruKw03KHlnp71yVQEwpsi3xl2"+ ),+ (+ "5auCCY9by0Ruf",+ "$2b$04$vVYgSTfB8KVbmhbZE/k3R.ux9A0lJUM4CZwCkHI9fifke2.rTF7MG"+ ),+ (+ "GtTkR6qn2QOZW",+ "$2b$04$JfoNrR8.doieoI8..F.C1OQgwE3uTeuardy6lw0AjALUzOARoyf2m"+ ),+ (+ "zKo8vdFSnjX0f",+ "$2b$04$HP3I0PUs7KBEzMBNFw7o3O7f/uxaZU7aaDot1quHMgB2yrwBXsgyy"+ ),+ (+ "I9VfYlacJiwiK",+ "$2b$04$xnFVhJsTzsFBTeP3PpgbMeMREb6rdKV9faW54Sx.yg9plf4jY8qT6"+ ),+ (+ "VFPO7YXnHQbQO",+ "$2b$04$WQp9.igoLqVr6Qk70mz6xuRxE0RttVXXdukpR9N54x17ecad34ZF6"+ ),+ (+ "VDx5BdxfxstYk",+ "$2b$04$xgZtlonpAHSU/njOCdKztOPuPFzCNVpB4LGicO4/OGgHv.uKHkwsS"+ ),+ (+ "dEe6XfVGrrfSH",+ "$2b$04$2Siw3Nv3Q/gTOIPetAyPr.GNj3aO0lb1E5E9UumYGKjP9BYqlNWJe"+ ),+ (+ "cTT0EAFdwJiLn",+ "$2b$04$7/Qj7Kd8BcSahPO4khB8me4ssDJCW3r4OGYqPF87jxtrSyPj5cS5m"+ ),+ (+ "J8eHUDuxBB520",+ "$2b$04$VvlCUKbTMjaxaYJ.k5juoecpG/7IzcH1AkmqKi.lIZMVIOLClWAk."+ ),+ (+ "U*U",+ "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"+ ),+ (+ "U*U*",+ "$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK"+ ),+ (+ "U*U*U",+ "$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a"+ ),+ (+ B.concat+ [ "0123456789abcdefghijklmnopqrstuvwxyz"+ , "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"+ , "chars after 72 are ignored" ],+ "$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui"+ ),+ (+ B.concat+ [ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"+ , "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"+ , "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"+ , "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"+ , "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"+ , "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"+ , "chars after 72 are ignored as usual" ],+ "$2a$05$/OK.fbVrR/bpIqNJ5ianF.swQOIzjOiJ9GHEPuhEkvqrUyvWhEMx6"+ ),+ (+ "\xa3",+ "$2a$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq"+ ),+ (+ "pass\x00word",+ "$2b$06$XXXXXXXXXXXXXXXXXXXXXOCgx4gZM7pzl7cruTaiQNiG5S4PGv9Ki"+ ),+{--+--- Not sure why these test vectors aren't working, but both this binding and+--- https://hackage.haskell.org/package/bcrypt compute the same result.++--- Perhaps there is some subtle difference between the lexical syntax of+-- literal strings in Python versus Haskell?++--- Suprisingly, that other bcrypt binding doesn't have a test suite...++--- https://hackage.haskell.org/package/password also includes a bcrypt binding,+--- but the test suite is a bit disappointing.++--- On the other hand, the test case that follows is the only test case with+-- a null character in the input. So I made my own test case above.++--- TODO: verify (via code review) that $2b$ and $2y$ are exactly equivalent++--- TODO? find test cases that distinguish $2a$ from $2b$ from $2x$, and+--- implement those other variants++--- note that pyca has a test case that is supposed to expose the 2a bug,+--- but that test case is not failing on this implementation, which+--- truncates at 72 bytes. Also, the same behavior is observed in the+--- external binding... so that's a couple more mysteries. (Perhaps+--- not that important anymore.)++ (+ B.concat+ [ "}>\xb3\xfe\xf1\x8b\xa0\xe6(\xa2Lzq\xc3P\x7f\xcc\xc8b{\xf9\x14\xf6"+ , "\xf6`\x81G5\xec\x1d\x87\x10\xbf\xa7\xe1}I7 \x96\xdfc\xf2\xbf\xb3Vh"+ , "\xdfM\x88q\xf7\xff\x1b\x82~z\x13\xdd\xe9\x84\x00\xdd4"+ ],+ "$2b$10$keO.ZZs22YtygVF6BLfhGOI/JjshJYPp8DZsUtym6mJV2Eha2Hdd."+ ),+ (+ B.concat+ [ "g7\r\x01\xf3\xd4\xd0\xa9JB^\x18\x007P\xb2N\xc7\x1c\xee\x87&\x83C"+ , "\x8b\xe8\x18\xc5>\x86\x14/\xd6\xcc\x1cJ\xde\xd7ix\xeb\xdeO\xef"+ , "\xe1i\xac\xcb\x03\x96v1' \xd6@.m\xa5!\xa0\xef\xc0("+ ],+ "$2a$04$tecY.9ylRInW/rAAzXCXPOOlyYeCNzmNTzPDNSIFztFMKbvs/s5XG"+ ),+--}+ (+ "\xa3",+ "$2y$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq"+ ),+ (+ "\xff\xff\xa3",+ "$2y$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e"+ )+ ]
− test/G3P.hs
@@ -1,352 +0,0 @@-{-# LANGUAGE OverloadedStrings, LambdaCase, RecordWildCards, ViewPatterns, ScopedTypeVariables #-}--module G3P where--import Control.Exception(try)-import Control.Applicative-import Data.Aeson(Object, Value(..), parseJSON, (.:), (.:?), withObject)-import Data.Aeson.Types(Parser)-import qualified Data.Aeson as Aeson-import Data.Aeson.Key(Key)-import qualified Data.Aeson.Key as K-import Data.Aeson.KeyMap(KeyMap)-import qualified Data.Aeson.KeyMap as KM-import Data.ByteString(ByteString)-import qualified Data.ByteString as B-import qualified Data.ByteString.Base16 as B-import Data.Function(fix)-import Data.Map(Map)-import qualified Data.Map as Map-import Data.Maybe(fromMaybe)-import Data.Text(Text)-import qualified Data.Text as T-import qualified Data.Text.Encoding as T-import qualified Data.Text.Encoding.Base16 as T-import Data.Stream(Stream(..))-import qualified Data.Stream as S-import Data.Vector(Vector, (!))-import qualified Data.Vector as V--import Crypto.G3P-import Test.Tasty-import Test.Tasty.HUnit--type Args = KeyMap Val--data Val- = Int !Int- | Str !ByteString- | Vec !(Vector ByteString)- | Nul- | Ref !TestId !Int- deriving (Show)--data Result = Result- { result_args :: !Args- , result_hashes :: !(KeyMap ByteString)- }--data TestVector = TestVector- { testVector_name :: !Text- , testVector_arguments :: !Args- , testVector_results :: !(Vector Result)- }--data TestId = TestId- { testId_name :: !Text- , testId_index :: !Int- , testId_algorithm :: !Text- } deriving (Eq, Ord, Show)--data SimpleTestVector = SimpleTestVector- { simpleTestVector_id :: !TestId- , simpleTestVector_arguments :: !Args- , simpleTestVector_result :: !ByteString- }--type TestVectors = Vector TestVector--type SimpleTestVectors = Vector SimpleTestVector--type ResultEnv = Map TestId (Either String (Stream ByteString))--blankResult :: Result-blankResult = Result- { result_args = KM.empty- , result_hashes = KM.fromList [ ("G3Pb1","") ]- }--flattenTestVectors :: TestVectors -> SimpleTestVectors-flattenTestVectors tvs =- V.fromList $- [ SimpleTestVector- { simpleTestVector_id =- TestId { testId_name = testVector_name tv- , testId_index = i- , testId_algorithm = alg- }- , simpleTestVector_arguments = args- , simpleTestVector_result = outHash- }- | tv <- V.toList tvs- , (i, res) <- zip [0..] (seedEmpty (V.toList (testVector_results tv)))- , let args = KM.union (result_args res) (testVector_arguments tv)- , (K.toText -> alg, outHash) <- KM.toAscList (result_hashes res)- ]- where- seedEmpty xs- | null xs = [blankResult]- | otherwise = map addBlankResult xs- addBlankResult x- | null (result_hashes x) = x { result_hashes = result_hashes blankResult }- | otherwise = x--genResultEnv :: SimpleTestVectors -> ResultEnv-genResultEnv tvs =- -- FIXME? The resulting scoping rules in the test vector file is analogous- -- to Haskell or scheme's letrec, whereas I really want let* here- fix $ \resultEnv ->- Map.fromList $- [ (simpleTestVector_id tv, interpret tv resultEnv)- | tv <- V.toList tvs- ]- where- interpret tv resultEnv- | alg == "G3Pb1" =- case getG3PInputs resultEnv args of- Just inputs -> Right (uncurry4 g3pHash inputs)- Nothing -> Left "arguments not parsed"- | otherwise = Left "algorithm name not recognized"- where- alg = testId_algorithm $ simpleTestVector_id tv- args = simpleTestVector_arguments tv--genSimpleTestCases :: SimpleTestVectors -> ResultEnv -> [ TestTree ]-genSimpleTestCases tvs resultEnv =- [ testCase testName $ runTest tv resultEnv- | tv <- V.toList tvs- , let testId = simpleTestVector_id tv- name = T.unpack (testId_name testId)- idx = show (testId_index testId)- alg = T.unpack (testId_algorithm testId)- testName = name ++ " | " ++ idx ++ " " ++ alg- ]--genTestCases :: TestVectors -> [ TestTree ]-genTestCases tvs = genSimpleTestCases stvs (genResultEnv stvs)- where- stvs = flattenTestVectors tvs--uncurry4 :: (a -> b -> c -> d -> e) -> (a,b,c,d) -> e-uncurry4 f (a,b,c,d) = f a b c d--instance Aeson.FromJSON Val where- parseJSON val =- (Int <$> parseJSON val) <|>- (Str <$> parseJSONByteString val) <|>- (Vec <$> parseJSONVectorByteString val) <|>- (parseRef val) <|>- (parseNul val)--instance Aeson.FromJSON Result where- parseJSON = \case- Object obj -> do- mArgs <- obj .:? "args"- args <- maybe (pure KM.empty) parseJSON mArgs- hashes <- KM.traverse parseJSONHash (KM.delete "args" obj)- pure (Result args hashes)- _ -> empty--instance Aeson.FromJSON TestVector where- parseJSON = withObject "TestVector" $ \v -> TestVector- <$> v .: "name"- <*> v .: "args"- <*> parseResults v--takeBytes :: Int -> Stream ByteString -> ByteString-takeBytes n stream = B.concat (go n stream)- where- go n ~(Cons out outStream')- | n <= 0 = []- | n <= B.length out = [B.take n out]- | otherwise = out : go (n - B.length out) outStream'--parseRef :: Value -> Parser Val-parseRef = \case- Object obj -> do- ref <- obj .: "ref"- len <- obj .: "len"- mAlg <- obj .:? "algorithm"- mIdx <- obj .:? "index"- let alg = fromMaybe "G3Pb1" mAlg- idx = fromMaybe 0 mIdx- testId = TestId ref idx alg- return $ Ref testId len- _ -> empty--parseNul :: Value -> Parser Val-parseNul = \case- Null -> return Nul- _ -> empty--parseJSONByteString :: Value -> Parser ByteString-parseJSONByteString = \case- String txt -> pure (T.encodeUtf8 txt)- Object obj | KM.size obj == 1 -> do- txt <- obj .: "hex"- case B.decodeBase16 (T.encodeUtf8 txt) of- Left _ -> empty- Right x -> pure x- _ -> empty--parseJSONVectorByteString :: Value -> Parser (Vector ByteString)-parseJSONVectorByteString val =- (V.singleton <$> parseJSONByteString val) <|>- case val of- Array bs -> V.generateM (V.length bs) (\i -> parseJSONByteString (bs ! i))- _ -> empty--parseJSONHash :: Value -> Parser ByteString-parseJSONHash = \case- String txt ->- case B.decodeBase16 (T.encodeUtf8 txt) of- Left _ -> empty- Right x -> pure x- _ -> empty--parseResults :: Object -> Parser (Vector Result)-parseResults v =- case KM.lookup "results" v of- Nothing -> pure V.empty- Just v@(Object _) ->- V.singleton <$> parseJSON v- Just (Array v) ->- V.generateM (V.length v) (\i -> parseJSON (v ! i))- _ -> empty--readTestVectorsFromFile :: String -> IO (String, Either String TestVectors)-readTestVectorsFromFile fileName =- try (Aeson.eitherDecodeFileStrict' fileName) >>= \case- Left (err :: IOError) -> return (fileName, Left (show err))- Right result -> return (fileName, result)--testVectorDefaultFileName :: String-testVectorDefaultFileName = "g3p-test-vectors.json"--testFile :: (String, Either String TestVectors) -> TestTree-testFile (fileName, mTestVectors) =- case mTestVectors of- Left err -> testCase testName $ assertFailure err- Right tvs -> testGroup testName $ genTestCases tvs- where- testName = "testfile: " ++ fileName--runTest :: SimpleTestVector -> ResultEnv -> Assertion-runTest tv resultEnv =- case Map.lookup (simpleTestVector_id tv) resultEnv of- Nothing -> assertFailure "test result not found (this shouldn't be possible)"- Just (Left err) -> assertFailure err- Just (Right result) -> compareAu alg goldenOutput result- where- alg = T.unpack . testId_algorithm $ simpleTestVector_id tv- goldenOutput = simpleTestVector_result tv--compareAu :: String -> ByteString -> Stream ByteString -> Assertion-compareAu name bs outStream- | B.null bs = assertFailure ("\"" ++ name ++ "\":\"" ++ concatMap toHex (S.take 4 outStream) ++ "\"")- | otherwise = B.encodeBase16 (takeBytes (B.length bs) outStream) @?= B.encodeBase16 bs- where- toHex = T.unpack . B.encodeBase16---- FIXME? Allow computation of tweaks without recomputing seed--getG3PInputs :: ResultEnv -> KeyMap Val -> Maybe (G3PInputBlock, G3PInputArgs, G3PInputRole, G3PInputEcho)-getG3PInputs env = \case- (getG3PBlock env -> Just (block,- getG3PArgs env -> Just (args,- getG3PRole env -> Just (role,- getG3PEcho env -> Just (echo,- args'))))) | KM.null args'- -> Just (block, args, role, echo)- _ -> Nothing--getG3PArgs :: ResultEnv -> KeyMap Val -> Maybe (G3PInputArgs, KeyMap Val)-getG3PArgs env = \case- (matchKey env "username" -> (Just (Str g3pInputArgs_username),- matchKey env "password" -> (Just (Str g3pInputArgs_password),- matchKey env "credentials" -> (- getByteStringVector_defaultEmpty -> Just g3pInputArgs_credentials,- args'))))- -> Just (G3PInputArgs {..}, args')- _ -> Nothing--getByteStringVector_defaultEmpty :: Maybe Val -> Maybe (Vector ByteString)-getByteStringVector_defaultEmpty = \case- Nothing -> Just V.empty- Just Nul -> Just V.empty- Just (Str str) -> Just (V.singleton str)- Just (Vec vec) -> Just vec- _ -> Nothing--getByteString_defaultEmpty :: Maybe Val -> Maybe ByteString-getByteString_defaultEmpty- = fmap (fromMaybe B.empty) . getMaybeByteString--getByteString :: Maybe Val -> Maybe ByteString-getByteString = \case- Just (Str str) -> Just str- _ -> Nothing--getMaybeByteString :: Maybe Val -> Maybe (Maybe ByteString)-getMaybeByteString = \case- Just (Str str) -> Just (Just str)- Just Nul -> Just Nothing- Nothing -> Just Nothing- _ -> Nothing--getG3PBlock :: ResultEnv -> KeyMap Val -> Maybe (G3PInputBlock, KeyMap Val)-getG3PBlock env = \case- (matchKey' env "domain-tag" -> (Just (Str g3pInputBlock_domainTag),- matchKey env "seguid" -> (getByteString_defaultEmpty -> Just g3pInputBlock_seguid,- matchKey env "long-tag" -> (getMaybeByteString -> Just mLongTag,- matchKey env "tags" -> (getByteStringVector_defaultEmpty -> Just tags,- matchKey env "seed-tags" -> (getByteStringVector_defaultEmpty -> Just seedTags,- matchKey env "phkdf-rounds" -> (Just (Int (fromIntegral -> g3pInputBlock_phkdfRounds)),- matchKey env "bcrypt-rounds" -> (Just (Int (fromIntegral -> g3pInputBlock_bcryptRounds)),- matchKey env "bcrypt-tag" -> (getMaybeByteString -> Just mBcryptTag,- matchKey env "bcrypt-salt-tag" -> (getMaybeByteString -> Just mBcryptSaltTag,- args'))))))))))- -> let g3pInputBlock_tags = tags <> seedTags- g3pInputBlock_longTag = fromMaybe g3pInputBlock_domainTag mLongTag- g3pInputBlock_bcryptTag = fromMaybe g3pInputBlock_domainTag mBcryptTag- g3pInputBlock_bcryptSaltTag = fromMaybe g3pInputBlock_bcryptTag mBcryptSaltTag- in Just (G3PInputBlock {..}, args')- _ -> Nothing--getG3PRole :: ResultEnv -> KeyMap Val -> Maybe (G3PInputRole, KeyMap Val)-getG3PRole env = \case- (matchKey env "role" -> (getByteStringVector_defaultEmpty -> Just roleTags,- args'))- -> Just (G3PInputRole roleTags, args')- _ -> Nothing--getG3PEcho :: ResultEnv -> KeyMap Val -> Maybe (G3PInputEcho, KeyMap Val)-getG3PEcho env = \case- (matchKey env "echo-tag" -> (getMaybeByteString -> Just mEchoTag,- matchKey env "domain-tag" -> (getByteString -> Just domainTag,- args')))- -> let echoTag = fromMaybe domainTag mEchoTag- in Just (G3PInputEcho echoTag, args')- _ -> Nothing--matchKey, matchKey' :: ResultEnv -> Key -> KeyMap Val -> (Maybe Val, KeyMap Val)-matchKey env key map = (interpRefs env (KM.lookup key map), KM.delete key map)-matchKey' env key map = (interpRefs env (KM.lookup key map), map)--interpRefs :: ResultEnv -> Maybe Val -> Maybe Val-interpRefs env (Just ref@(Ref testId bytes)) =- case Map.lookup testId env of- Nothing -> Just ref- Just (Left _) -> Just ref- Just (Right echo) -> Just (Str (takeBytes bytes echo))-interpRefs _ val = val
+ test/G3Pb1.hs view
@@ -0,0 +1,353 @@+{-# LANGUAGE OverloadedStrings, LambdaCase, RecordWildCards, ViewPatterns, ScopedTypeVariables #-}++module G3Pb1 where++import Control.Exception(try)+import Control.Applicative+import Data.Aeson(Object, Value(..), parseJSON, (.:), (.:?), withObject)+import Data.Aeson.Types(Parser)+import qualified Data.Aeson as Aeson+import Data.Aeson.Key(Key)+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+import Data.Function(fix)+import Data.Map(Map)+import qualified Data.Map as Map+import Data.Maybe(fromMaybe)+import Data.Text(Text)+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Data.Text.Encoding.Base16 as T+import Data.Stream(Stream(..))+import qualified Data.Stream as S+import Data.Vector(Vector, (!))+import qualified Data.Vector as V++import Crypto.G3P.V1+import Test.Tasty+import Test.Tasty.HUnit++type Args = KeyMap Val++data Val+ = Int !Int+ | Str !ByteString+ | Vec !(Vector ByteString)+ | Nul+ | Ref !TestId !Int+ deriving (Show)++data Result = Result+ { result_args :: !Args+ , result_hashes :: !(KeyMap ByteString)+ }++data TestVector = TestVector+ { testVector_name :: !Text+ , testVector_arguments :: !Args+ , testVector_results :: !(Vector Result)+ }++data TestId = TestId+ { testId_name :: !Text+ , testId_index :: !Int+ , testId_algorithm :: !Text+ } deriving (Eq, Ord, Show)++data SimpleTestVector = SimpleTestVector+ { simpleTestVector_id :: !TestId+ , simpleTestVector_arguments :: !Args+ , simpleTestVector_result :: !ByteString+ }++type TestVectors = Vector TestVector++type SimpleTestVectors = Vector SimpleTestVector++type ResultEnv = Map TestId (Either String (Stream ByteString))++blankResult :: Result+blankResult = Result+ { result_args = KM.empty+ , result_hashes = KM.fromList [ ("G3Pb1","") ]+ }++flattenTestVectors :: TestVectors -> SimpleTestVectors+flattenTestVectors tvs =+ V.fromList $+ [ SimpleTestVector+ { simpleTestVector_id =+ TestId { testId_name = testVector_name tv+ , testId_index = i+ , testId_algorithm = alg+ }+ , simpleTestVector_arguments = args+ , simpleTestVector_result = outHash+ }+ | tv <- V.toList tvs+ , (i, res) <- zip [0..] (seedEmpty (V.toList (testVector_results tv)))+ , let args = KM.union (result_args res) (testVector_arguments tv)+ , (K.toText -> alg, outHash) <- KM.toAscList (result_hashes res)+ ]+ where+ seedEmpty xs+ | null xs = [blankResult]+ | otherwise = map addBlankResult xs+ addBlankResult x+ | null (result_hashes x) = x { result_hashes = result_hashes blankResult }+ | otherwise = x++genResultEnv :: SimpleTestVectors -> ResultEnv+genResultEnv tvs =+ -- FIXME? The resulting scoping rules in the test vector file is analogous+ -- to Haskell or scheme's letrec, whereas I really want let* here+ fix $ \resultEnv ->+ Map.fromList $+ [ (simpleTestVector_id tv, interpret tv resultEnv)+ | tv <- V.toList tvs+ ]+ where+ interpret tv resultEnv+ | alg == "G3Pb1" =+ case getG3PInputs resultEnv args of+ Just inputs -> Right (uncurry4 g3pHash inputs)+ Nothing -> Left "arguments not parsed"+ | otherwise = Left "algorithm name not recognized"+ where+ alg = testId_algorithm $ simpleTestVector_id tv+ args = simpleTestVector_arguments tv++genSimpleTestCases :: SimpleTestVectors -> ResultEnv -> [ TestTree ]+genSimpleTestCases tvs resultEnv =+ [ testCase testName $ runTest tv resultEnv+ | tv <- V.toList tvs+ , let testId = simpleTestVector_id tv+ name = T.unpack (testId_name testId)+ idx = show (testId_index testId)+ alg = T.unpack (testId_algorithm testId)+ testName = name ++ " | " ++ idx ++ " " ++ alg+ ]++genTestCases :: TestVectors -> [ TestTree ]+genTestCases tvs = genSimpleTestCases stvs (genResultEnv stvs)+ where+ stvs = flattenTestVectors tvs++uncurry4 :: (a -> b -> c -> d -> e) -> (a,b,c,d) -> e+uncurry4 f (a,b,c,d) = f a b c d++instance Aeson.FromJSON Val where+ parseJSON val =+ (Int <$> parseJSON val) <|>+ (Str <$> parseJSONByteString val) <|>+ (Vec <$> parseJSONVectorByteString val) <|>+ (parseRef val) <|>+ (parseNul val)++instance Aeson.FromJSON Result where+ parseJSON = \case+ Object obj -> do+ mArgs <- obj .:? "args"+ args <- maybe (pure KM.empty) parseJSON mArgs+ hashes <- KM.traverse parseJSONHash (KM.delete "args" obj)+ pure (Result args hashes)+ _ -> empty++instance Aeson.FromJSON TestVector where+ parseJSON = withObject "TestVector" $ \v -> TestVector+ <$> v .: "name"+ <*> v .: "args"+ <*> parseResults v++takeBytes :: Int -> Stream ByteString -> ByteString+takeBytes n stream = B.concat (go n stream)+ where+ go n ~(Cons out outStream')+ | n <= 0 = []+ | n <= B.length out = [B.take n out]+ | otherwise = out : go (n - B.length out) outStream'++parseRef :: Value -> Parser Val+parseRef = \case+ Object obj -> do+ ref <- obj .: "ref"+ len <- obj .: "len"+ mAlg <- obj .:? "algorithm"+ mIdx <- obj .:? "index"+ let alg = fromMaybe "G3Pb1" mAlg+ idx = fromMaybe 0 mIdx+ testId = TestId ref idx alg+ return $ Ref testId len+ _ -> empty++parseNul :: Value -> Parser Val+parseNul = \case+ Null -> return Nul+ _ -> empty++parseJSONByteString :: Value -> Parser ByteString+parseJSONByteString = \case+ String txt -> pure (T.encodeUtf8 txt)+ Object obj | KM.size obj == 1 -> do+ txt <- obj .: "hex"+ case B.decodeBase16Untyped (T.encodeUtf8 txt) of+ Left _ -> empty+ Right x -> pure x+ _ -> empty++parseJSONVectorByteString :: Value -> Parser (Vector ByteString)+parseJSONVectorByteString val =+ (V.singleton <$> parseJSONByteString val) <|>+ case val of+ Array bs -> V.generateM (V.length bs) (\i -> parseJSONByteString (bs ! i))+ _ -> empty++parseJSONHash :: Value -> Parser ByteString+parseJSONHash = \case+ String txt ->+ case B.decodeBase16Untyped (T.encodeUtf8 txt) of+ Left _ -> empty+ Right x -> pure x+ _ -> empty++parseResults :: Object -> Parser (Vector Result)+parseResults v =+ case KM.lookup "results" v of+ Nothing -> pure V.empty+ Just v@(Object _) ->+ V.singleton <$> parseJSON v+ Just (Array v) ->+ V.generateM (V.length v) (\i -> parseJSON (v ! i))+ _ -> empty++readTestVectorsFromFile :: String -> IO (String, Either String TestVectors)+readTestVectorsFromFile fileName =+ try (Aeson.eitherDecodeFileStrict' fileName) >>= \case+ Left (err :: IOError) -> return (fileName, Left (show err))+ Right result -> return (fileName, result)++testVectorDefaultFileName :: String+testVectorDefaultFileName = "g3pb1-test-vectors.json"++testFile :: (String, Either String TestVectors) -> TestTree+testFile (fileName, mTestVectors) =+ case mTestVectors of+ Left err -> testCase testName $ assertFailure err+ Right tvs -> testGroup testName $ genTestCases tvs+ where+ testName = "testfile: " ++ fileName++runTest :: SimpleTestVector -> ResultEnv -> Assertion+runTest tv resultEnv =+ case Map.lookup (simpleTestVector_id tv) resultEnv of+ Nothing -> assertFailure "test result not found (this shouldn't be possible)"+ Just (Left err) -> assertFailure err+ Just (Right result) -> compareAu alg goldenOutput result+ where+ alg = T.unpack . testId_algorithm $ simpleTestVector_id tv+ goldenOutput = simpleTestVector_result tv++compareAu :: String -> ByteString -> Stream ByteString -> Assertion+compareAu name bs outStream+ | B.null bs = assertFailure ("\"" ++ name ++ "\":\"" ++ concatMap toHex (S.take 4 outStream) ++ "\"")+ | otherwise = B.encodeBase16 (takeBytes (B.length bs) outStream) @?= B.encodeBase16 bs+ where+ toHex = T.unpack . extractBase16 . B.encodeBase16++-- FIXME? Allow computation of tweaks without recomputing seed++getG3PInputs :: ResultEnv -> KeyMap Val -> Maybe (G3PInputBlock, G3PInputArgs, G3PInputRole, G3PInputEcho)+getG3PInputs env = \case+ (getG3PBlock env -> Just (block,+ getG3PArgs env -> Just (args,+ getG3PRole env -> Just (role,+ getG3PEcho env -> Just (echo,+ args'))))) | KM.null args'+ -> Just (block, args, role, echo)+ _ -> Nothing++getG3PArgs :: ResultEnv -> KeyMap Val -> Maybe (G3PInputArgs, KeyMap Val)+getG3PArgs env = \case+ (matchKey env "username" -> (Just (Str g3pInputArgs_username),+ matchKey env "password" -> (Just (Str g3pInputArgs_password),+ matchKey env "credentials" -> (+ getByteStringVector_defaultEmpty -> Just g3pInputArgs_credentials,+ args'))))+ -> Just (G3PInputArgs {..}, args')+ _ -> Nothing++getByteStringVector_defaultEmpty :: Maybe Val -> Maybe (Vector ByteString)+getByteStringVector_defaultEmpty = \case+ Nothing -> Just V.empty+ Just Nul -> Just V.empty+ Just (Str str) -> Just (V.singleton str)+ Just (Vec vec) -> Just vec+ _ -> Nothing++getByteString_defaultEmpty :: Maybe Val -> Maybe ByteString+getByteString_defaultEmpty+ = fmap (fromMaybe B.empty) . getMaybeByteString++getByteString :: Maybe Val -> Maybe ByteString+getByteString = \case+ Just (Str str) -> Just str+ _ -> Nothing++getMaybeByteString :: Maybe Val -> Maybe (Maybe ByteString)+getMaybeByteString = \case+ Just (Str str) -> Just (Just str)+ Just Nul -> Just Nothing+ Nothing -> Just Nothing+ _ -> Nothing++getG3PBlock :: ResultEnv -> KeyMap Val -> Maybe (G3PInputBlock, KeyMap Val)+getG3PBlock env = \case+ (matchKey' env "domain-tag" -> (Just (Str g3pInputBlock_domainTag),+ matchKey env "seguid" -> (getByteString_defaultEmpty -> Just g3pInputBlock_seguid,+ matchKey env "long-tag" -> (getMaybeByteString -> Just mLongTag,+ matchKey env "tags" -> (getByteStringVector_defaultEmpty -> Just tags,+ matchKey env "seed-tags" -> (getByteStringVector_defaultEmpty -> Just seedTags,+ matchKey env "phkdf-rounds" -> (Just (Int (fromIntegral -> g3pInputBlock_phkdfRounds)),+ matchKey env "bcrypt-rounds" -> (Just (Int (fromIntegral -> g3pInputBlock_bcryptRounds)),+ matchKey env "bcrypt-tag" -> (getMaybeByteString -> Just mBcryptTag,+ matchKey env "bcrypt-salt-tag" -> (getMaybeByteString -> Just mBcryptSaltTag,+ args'))))))))))+ -> let g3pInputBlock_tags = tags <> seedTags+ g3pInputBlock_longTag = fromMaybe g3pInputBlock_domainTag mLongTag+ g3pInputBlock_bcryptTag = fromMaybe g3pInputBlock_domainTag mBcryptTag+ g3pInputBlock_bcryptSaltTag = fromMaybe g3pInputBlock_bcryptTag mBcryptSaltTag+ in Just (G3PInputBlock {..}, args')+ _ -> Nothing++getG3PRole :: ResultEnv -> KeyMap Val -> Maybe (G3PInputRole, KeyMap Val)+getG3PRole env = \case+ (matchKey env "role" -> (getByteStringVector_defaultEmpty -> Just roleTags,+ args'))+ -> Just (G3PInputRole roleTags, args')+ _ -> Nothing++getG3PEcho :: ResultEnv -> KeyMap Val -> Maybe (G3PInputEcho, KeyMap Val)+getG3PEcho env = \case+ (matchKey env "echo-tag" -> (getMaybeByteString -> Just mEchoTag,+ matchKey env "domain-tag" -> (getByteString -> Just domainTag,+ args')))+ -> let echoTag = fromMaybe domainTag mEchoTag+ in Just (G3PInputEcho echoTag, args')+ _ -> Nothing++matchKey, matchKey' :: ResultEnv -> Key -> KeyMap Val -> (Maybe Val, KeyMap Val)+matchKey env key map = (interpRefs env (KM.lookup key map), KM.delete key map)+matchKey' env key map = (interpRefs env (KM.lookup key map), map)++interpRefs :: ResultEnv -> Maybe Val -> Maybe Val+interpRefs env (Just ref@(Ref testId bytes)) =+ case Map.lookup testId env of+ Nothing -> Just ref+ Just (Left _) -> Just ref+ Just (Right echo) -> Just (Str (takeBytes bytes echo))+interpRefs _ val = val
+ test/G3Pb2.hs view
@@ -0,0 +1,539 @@+{-# LANGUAGE OverloadedStrings, LambdaCase, RecordWildCards, ViewPatterns, ScopedTypeVariables #-}++module G3Pb2 where++import Control.Exception(try)+import Control.Applicative+import Data.Aeson(Object, Value(..), parseJSON, (.:), (.:?), withObject)+import Data.Aeson.Types(Parser)+import qualified Data.Aeson as Aeson+import Data.Aeson.Key(Key)+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+import Data.Function(fix)+import Data.Int+import Data.Map(Map)+import qualified Data.Map as Map+import Data.Maybe(fromMaybe)+import Data.Text(Text)+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Data.Text.Encoding.Base16 as T+import Data.Stream(Stream(..))+import qualified Data.Stream as S+import Data.Vector(Vector, (!))+import qualified Data.Vector as V+import Data.Word++import Network.ByteOrder(word32)++import Crypto.G3P.V2+import Crypto.G3P.V2.Subtle(G3PSpark(..), G3PSeed(..))+import Crypto.G3P.V2.Foxtrot+import Crypto.PHKDF.HMAC(HmacKey, hmacKey)+import Crypto.Encoding.PHKDF(takeBs, nullBuffer)+import Test.Tasty+import Test.Tasty.HUnit++type Args = KeyMap Val++data Val+ = Int !Int64+ | Str !ByteString+ | Vec !(Vector ByteString)+ | Nul+ | Ref !TestId !Int+ deriving (Show)++data G3PArgs = G3PArgs+ { g3pArgs_salt :: !G3PSalt+ , g3pArgs_inputs :: !G3PInputs+ , g3pArgs_seedInputs :: !G3PSeedInputs+ , g3pArgs_delta :: !G3PDelta+ }++data G3PDelta = G3PDelta+ { g3pDelta_sproutSeguid :: !HmacKey+ , g3pDelta_sproutRole :: !(Vector ByteString)+ , g3pDelta_sproutTag :: !ByteString+ , g3pDelta_echoKey :: !ByteString+ , g3pDelta_echoHeader :: !ByteString+ , g3pDelta_echoCounter :: !Word32+ , g3pDelta_echoTag :: !ByteString+ }+++data Result = Result+ { result_args :: !Args+ , result_hashes :: !(KeyMap ByteString)+ }++data G3PFoxtrotArgs = G3PFoxtrotArgs+ { g3pFoxtrotArgs_salt :: !G3PFoxtrotSalt+ , g3pFoxtrotArgs_inputs :: !(Vector ByteString)+ , g3pFoxtrotArgs_tweaks :: !(Vector ByteString)+ , g3pFoxtrotArgs_counter :: !Word32+ }++data G3PTangoArgs = G3PTangoArgs+ { g3pTangoArgs_key :: !HmacKey+ , g3pTangoArgs_inputs :: !(Vector ByteString)+ , g3pTangoArgs_counter :: !Word32+ , g3pTangoArgs_domainTag :: !ByteString+ }++data TestVector = TestVector+ { testVector_name :: !Text+ , testVector_arguments :: !Args+ , testVector_results :: !(Vector Result)+ }++data TestId = TestId+ { testId_name :: !Text+ , testId_index :: !Int+ , testId_algorithm :: !Text+ } deriving (Eq, Ord, Show)++data SimpleTestVector = SimpleTestVector+ { simpleTestVector_id :: !TestId+ , simpleTestVector_arguments :: !Args+ , simpleTestVector_result :: !ByteString+ }++type TestVectors = Vector TestVector++type SimpleTestVectors = Vector SimpleTestVector++type ResultEnv = Map TestId (Either String [ByteString])++blankResult :: Result+blankResult = Result+ { result_args = KM.empty+ , result_hashes = KM.fromList [ ("G3Pb2","") ]+ }++flattenTestVectors :: TestVectors -> SimpleTestVectors+flattenTestVectors tvs =+ V.fromList $+ [ SimpleTestVector+ { simpleTestVector_id =+ TestId { testId_name = testVector_name tv+ , testId_index = i+ , testId_algorithm = alg+ }+ , simpleTestVector_arguments = args+ , simpleTestVector_result = outHash+ }+ | tv <- V.toList tvs+ , (i, res) <- zip [0..] (seedEmpty (V.toList (testVector_results tv)))+ , let args = KM.union (result_args res) (testVector_arguments tv)+ , (K.toText -> alg, outHash) <- KM.toAscList (result_hashes res)+ ]+ where+ seedEmpty xs+ | null xs = [blankResult]+ | otherwise = map addBlankResult xs+ addBlankResult x+ | null (result_hashes x) = x { result_hashes = result_hashes blankResult }+ | otherwise = x++genResultEnv :: SimpleTestVectors -> ResultEnv+genResultEnv tvs =+ -- FIXME? The resulting scoping rules in the test vector file is analogous+ -- to Haskell or scheme's letrec, whereas I really want let* here+ fix $ \resultEnv ->+ Map.fromList $+ [ (simpleTestVector_id tv, interpret tv resultEnv)+ | tv <- V.toList tvs+ ]+ where+ interpret tv resultEnv+ | alg == "G3Pb2" =+ case getG3PArgs resultEnv args of+ Just inputs -> Right (doG3P inputs)+ Nothing -> Left "arguments not parsed"+ | alg == "G3PSpark" =+ case getG3PArgs resultEnv args of+ Just inputs -> Right (doG3PSpark inputs)+ Nothing -> Left "arguments not parsed"+ | alg == "G3PSeed" =+ case getG3PArgs resultEnv args of+ Just inputs -> Right (doG3PSeed inputs)+ Nothing -> Left "arguments not parsed"+ | alg == "G3PFoxtrot" =+ case getG3PFoxtrotArgs resultEnv args of+ Just inputs -> Right (doG3PFoxtrot inputs)+ Nothing -> Left "arguments not parsed"+ | alg == "G3PTango" =+ case getG3PTangoArgs resultEnv args of+ Just inputs -> Right (doG3PTango inputs)+ Nothing -> Left "arguments not parsed"+ | otherwise = Left "algorithm name not recognized"+ where+ alg = testId_algorithm $ simpleTestVector_id tv+ args = simpleTestVector_arguments tv++doG3P :: G3PArgs -> [ByteString]+doG3P args = S.toList (g3pStream salt inputs seedInputs sproutKey role sproutTag echoKey echoHeader echoCounter echoTag)+ where+ salt = g3pArgs_salt args+ inputs = g3pArgs_inputs args+ seedInputs = g3pArgs_seedInputs args+ delta = g3pArgs_delta args+ sproutKey = g3pDelta_sproutSeguid delta+ role = g3pDelta_sproutRole delta+ sproutTag = g3pDelta_sproutTag delta+ echoKey = g3pDelta_echoKey delta+ echoHeader = g3pDelta_echoHeader delta+ echoCounter = g3pDelta_echoCounter delta+ echoTag = g3pDelta_echoTag delta++doG3PSpark :: G3PArgs -> [ByteString]+doG3PSpark args = [g3pSpark_beginKey spark, g3pSpark_contKey spark]+ where+ salt = g3pArgs_salt args+ inputs = g3pArgs_inputs args+ spark = g3pSpark salt inputs++doG3PSeed :: G3PArgs -> [ByteString]+doG3PSeed args = [g3pSeed_seedKey seed]+ where+ salt = g3pArgs_salt args+ inputs = g3pArgs_inputs args+ seedInputs = g3pArgs_seedInputs args+ seed = g3pSeed salt inputs seedInputs++doG3PFoxtrot :: G3PFoxtrotArgs -> [ByteString]+doG3PFoxtrot args = [g3pFoxtrot salt inputs tweaks counter]+ where+ salt = g3pFoxtrotArgs_salt args+ inputs = g3pFoxtrotArgs_inputs args+ tweaks = g3pFoxtrotArgs_tweaks args+ counter = g3pFoxtrotArgs_counter args++doG3PTango :: G3PTangoArgs -> [ByteString]+doG3PTango args = [g3pTango key inputs counter tag]+ where+ key = g3pTangoArgs_key args+ inputs = g3pTangoArgs_inputs args+ counter = g3pTangoArgs_counter args+ tag = g3pTangoArgs_domainTag args++genSimpleTestCases :: SimpleTestVectors -> ResultEnv -> [ TestTree ]+genSimpleTestCases tvs resultEnv =+ [ testCase testName $ runTest tv resultEnv+ | tv <- V.toList tvs+ , let testId = simpleTestVector_id tv+ name = T.unpack (testId_name testId)+ idx = show (testId_index testId)+ alg = T.unpack (testId_algorithm testId)+ testName = name ++ " | " ++ idx ++ " " ++ alg+ ]++genTestCases :: TestVectors -> [ TestTree ]+genTestCases tvs = genSimpleTestCases stvs (genResultEnv stvs)+ where+ stvs = flattenTestVectors tvs++uncurry4 :: (a -> b -> c -> d -> e) -> (a,b,c,d) -> e+uncurry4 f (a,b,c,d) = f a b c d++instance Aeson.FromJSON Val where+ parseJSON val =+ (Int <$> parseJSON val) <|>+ (Str <$> parseJSONByteString val) <|>+ (Vec <$> parseJSONVectorByteString val) <|>+ (parseRef val) <|>+ (parseNul val)++instance Aeson.FromJSON Result where+ parseJSON = \case+ Object obj -> do+ mArgs <- obj .:? "args"+ args <- maybe (pure KM.empty) parseJSON mArgs+ hashes <- KM.traverse parseJSONHash (KM.delete "args" obj)+ pure (Result args hashes)+ _ -> empty++instance Aeson.FromJSON TestVector where+ parseJSON = withObject "TestVector" $ \v -> TestVector+ <$> v .: "name"+ <*> v .: "args"+ <*> parseResults v++takeBytes :: Int -> [ByteString] -> ByteString+takeBytes n stream = B.concat (go n stream)+ where+ go n _ | n <= 0 = []+ go _ [] = []+ go n (out : outStream')+ | n <= B.length out = [B.take n out]+ | otherwise = out : go (n - B.length out) outStream'++parseRef :: Value -> Parser Val+parseRef = \case+ Object obj -> do+ ref <- obj .: "ref"+ len <- obj .: "len"+ mAlg <- obj .:? "algorithm"+ mIdx <- obj .:? "index"+ let alg = fromMaybe "G3Pb2" mAlg+ idx = fromMaybe 0 mIdx+ testId = TestId ref idx alg+ return $ Ref testId len+ _ -> empty++parseNul :: Value -> Parser Val+parseNul = \case+ Null -> return Nul+ _ -> empty++parseJSONByteString :: Value -> Parser ByteString+parseJSONByteString = \case+ String txt -> pure (T.encodeUtf8 txt)+ Object obj | KM.size obj == 1 -> do+ txt <- obj .: "hex"+ case B.decodeBase16Untyped (T.encodeUtf8 txt) of+ Left _ -> empty+ Right x -> pure x+ _ -> empty++parseJSONVectorByteString :: Value -> Parser (Vector ByteString)+parseJSONVectorByteString val =+ (V.singleton <$> parseJSONByteString val) <|>+ case val of+ Array bs -> V.generateM (V.length bs) (\i -> parseJSONByteString (bs ! i))+ _ -> empty++parseJSONHash :: Value -> Parser ByteString+parseJSONHash = \case+ String txt ->+ case B.decodeBase16Untyped (T.encodeUtf8 txt) of+ Left _ -> empty+ Right x -> pure x+ _ -> empty++parseResults :: Object -> Parser (Vector Result)+parseResults v =+ case KM.lookup "results" v of+ Nothing -> pure V.empty+ Just v@(Object _) ->+ V.singleton <$> parseJSON v+ Just (Array v) ->+ V.generateM (V.length v) (\i -> parseJSON (v ! i))+ _ -> empty++readTestVectorsFromFile :: String -> IO (String, Either String TestVectors)+readTestVectorsFromFile fileName =+ try (Aeson.eitherDecodeFileStrict' fileName) >>= \case+ Left (err :: IOError) -> return (fileName, Left (show err))+ Right result -> return (fileName, result)++testVectorDefaultFileName :: String+testVectorDefaultFileName = "g3p-test-vectors.json"++testFile :: (String, Either String TestVectors) -> TestTree+testFile (fileName, mTestVectors) =+ case mTestVectors of+ Left err -> testCase testName $ assertFailure err+ Right tvs -> testGroup testName $ genTestCases tvs+ where+ testName = "testfile: " ++ fileName++runTest :: SimpleTestVector -> ResultEnv -> Assertion+runTest tv resultEnv =+ case Map.lookup (simpleTestVector_id tv) resultEnv of+ Nothing -> assertFailure "test result not found (this shouldn't be possible)"+ Just (Left err) -> assertFailure err+ Just (Right result) -> compareAu alg goldenOutput result+ where+ alg = T.unpack . testId_algorithm $ simpleTestVector_id tv+ goldenOutput = simpleTestVector_result tv++compareAu :: String -> ByteString -> [ByteString] -> Assertion+compareAu name bs outStream+ | B.null bs = assertFailure ("\"" ++ name ++ "\":\"" ++ concatMap toHex (take 4 outStream) ++ "\"")+ | otherwise = B.encodeBase16 (takeBytes (B.length bs) outStream) @?= B.encodeBase16 bs+ where+ toHex = T.unpack . extractBase16 . B.encodeBase16++-- FIXME? Allow computation of tweaks without recomputing seed++getG3PArgs :: ResultEnv -> KeyMap Val -> Maybe G3PArgs+getG3PArgs env = \case+ (getG3PSalt env -> Just (salt,+ getG3PInputs env -> Just (inputs,+ getG3PSeedInputs env -> Just (seedInputs,+ getG3PDelta env -> Just (delta,+ args'))))) | KM.null args'+ -> Just (G3PArgs salt inputs seedInputs delta)+ _ -> Nothing++getG3PSalt :: ResultEnv -> KeyMap Val -> Maybe (G3PSalt, KeyMap Val)+getG3PSalt env = \case+ (matchKey' env "domain-tag" -> (Just (Str g3pSalt_domainTag),+ matchKey' env "seguid" -> (getByteString_defaultEmpty -> Just (hmacKey -> g3pSalt_seguid),+ matchKey' env "long-tag" -> (getMaybeByteString -> Just mLongTag,+ matchKey' env "tags" -> (getMaybeByteStringVector -> Just mTags,+ matchKey env "context-tags" -> (getMaybeByteStringVector -> Just mCtxTags,+ matchKey env "phkdf-rounds" -> (Just (Int (fromIntegral -> g3pSalt_phkdfRounds)),+ args')))))))+ -> let g3pSalt_contextTags = fromMaybe (fromMaybe V.empty mTags) mCtxTags+ g3pSalt_longTag = fromMaybe g3pSalt_domainTag mLongTag+ in Just (G3PSalt {..}, args')+ _ -> Nothing++getG3PInputs :: ResultEnv -> KeyMap Val -> Maybe (G3PInputs, KeyMap Val)+getG3PInputs env = \case+ (matchKey env "username" -> (Just (Str g3pInputs_username),+ matchKey env "password" -> (Just (Str g3pInputs_password),+ matchKey env "credentials" -> (+ getByteStringVector_defaultEmpty -> Just g3pInputs_credentials,+ args'))))+ -> Just (G3PInputs {..}, args')+ _ -> Nothing++getG3PSeedInputs :: ResultEnv -> KeyMap Val -> Maybe (G3PSeedInputs, KeyMap Val)+getG3PSeedInputs env = \case+ (matchKey' env "domain-tag" -> (getMaybeByteString -> Just mDomainTag,+ matchKey' env "bcrypt-domain-tag" -> (getMaybeByteString -> Just mBcryptDomainTag,+ matchKey' env "seguid" -> (getMaybeByteString -> Just mSeguid,+ matchKey' env "bcrypt-seguid" -> (getMaybeByteString -> Just mBcryptSeguid,+ matchKey env "long-tag" -> (getMaybeByteString -> Just mLongTag,+ matchKey env "bcrypt-long-tag" -> (getMaybeByteString -> Just mBcryptLongTag,+ matchKey' env "tags" -> (getMaybeByteStringVector -> Just mTags,+ matchKey env "bcrypt-credentials" -> (getMaybeByteStringVector -> Just mCreds,+ matchKey env "bcrypt-context-tags" -> (getMaybeByteStringVector -> Just mCtxTags,+ matchKey env "bcrypt-rounds" -> (Just (Int (fromIntegral -> g3pSeedInputs_bcryptRounds)),+ args')))))))))))+ -> let g3pSeedInputs_bcryptSeguid = hmacKey (fromMaybe (fromMaybe B.empty mSeguid) mBcryptSeguid)+ g3pSeedInputs_bcryptContextTags = fromMaybe (fromMaybe V.empty mTags) mCtxTags+ g3pSeedInputs_bcryptDomainTag = fromMaybe (fromMaybe B.empty mDomainTag) mBcryptDomainTag+ g3pSeedInputs_bcryptLongTag = fromMaybe (fromMaybe (fromMaybe B.empty mDomainTag) mLongTag) mBcryptLongTag+ g3pSeedInputs_bcryptCredentials = fromMaybe V.empty mCreds+ in Just (G3PSeedInputs {..}, args')+ _ -> Nothing++getG3PDelta :: ResultEnv -> KeyMap Val -> Maybe (G3PDelta, KeyMap Val)+getG3PDelta env = \case+ (matchKey env "seguid" -> (getMaybeByteString -> Just mSeguid,+ matchKey env "bcrypt-seguid" -> (getMaybeByteString -> Just mBcryptSeguid,+ matchKey env "sprout-seguid" -> (getMaybeByteString -> Just mSproutSeguid,+ matchKey env "tags" -> (getMaybeByteStringVector -> Just mTags,+ matchKey env "role" -> (getMaybeByteStringVector -> Just mRole,+ matchKey env "domain-tag" -> (getMaybeByteString -> Just mDomainTag,+ matchKey env "bcrypt-domain-tag" -> (getMaybeByteString -> Just mBcryptDomainTag,+ matchKey env "sprout-tag" -> (getMaybeByteString -> Just mSproutTag,+ matchKey env "echo-key" -> (getMaybeByteString -> Just mEchoKey,+ matchKey env "echo-header" -> (getMaybeByteString -> Just mEchoHeader,+ matchKey env "echo-counter" -> (getEchoCounter -> Just g3pDelta_echoCounter,+ matchKey env "echo-tag" -> (getMaybeByteString -> Just mEchoTag,+ args')))))))))))))+ -> let g3pDelta_sproutSeguid = hmacKey (fromMaybe (fromMaybe (fromMaybe B.empty mSeguid) mBcryptSeguid) mSproutSeguid)+ g3pDelta_sproutRole = fromMaybe (fromMaybe V.empty mTags) mRole+ g3pDelta_sproutTag = fromMaybe (fromMaybe (fromMaybe B.empty mDomainTag) mBcryptDomainTag) mSproutTag+ g3pDelta_echoKey = fromMaybe g3pDelta_echoHeader mEchoKey+ g3pDelta_echoHeader = fromMaybe g3pDelta_sproutTag mEchoHeader+ g3pDelta_echoTag = fromMaybe g3pDelta_sproutTag mEchoTag+ in Just (G3PDelta {..}, args')+ _ -> Nothing++getG3PFoxtrotArgs :: ResultEnv -> KeyMap Val -> Maybe G3PFoxtrotArgs+getG3PFoxtrotArgs env = \case+ (+ matchKey env "domain-tag" -> (Just (Str g3pFoxtrotSalt_domainTag),+ matchKey env "key" -> (getMaybeByteString -> Just mKey,+ matchKey env "inputs" -> (getMaybeByteStringVector -> Just mInputs,+ matchKey env "long-tag" -> (getMaybeByteString -> Just mLongTag,+ matchKey env "bcrypt-rounds" -> (Just (Int (fromIntegral -> g3pFoxtrotSalt_bcryptRounds)),+ matchKey env "context-tags" -> (getMaybeByteStringVector -> Just mContextTags,+ matchKey env "tweaks" -> (getMaybeByteStringVector -> Just mTweaks,+ matchKey env "counter" -> (getEchoCounter -> (Just g3pFoxtrotArgs_counter),+ args'))))))))) | KM.null args'+ -> let g3pFoxtrotSalt_key = hmacKey (fromMaybe B.empty mKey)+ g3pFoxtrotSalt_contextTags = fromMaybe V.empty mContextTags+ g3pFoxtrotSalt_longTag = fromMaybe g3pFoxtrotSalt_domainTag mLongTag+ g3pFoxtrotArgs_salt = G3PFoxtrotSalt{..}+ g3pFoxtrotArgs_inputs = fromMaybe V.empty mInputs+ g3pFoxtrotArgs_tweaks = fromMaybe V.empty mTweaks+ in Just G3PFoxtrotArgs{..}+ _ -> Nothing++getG3PTangoArgs :: ResultEnv -> KeyMap Val -> Maybe G3PTangoArgs+getG3PTangoArgs env = \case+ (+ matchKey env "key" -> (getMaybeByteString -> Just mKey,+ matchKey env "inputs" -> (getMaybeByteStringVector -> Just mInputs,+ matchKey env "counter" -> (getEchoCounter -> (Just g3pTangoArgs_counter),+ matchKey env "domain-tag" -> (Just (Str g3pTangoArgs_domainTag),+ args'))))) | KM.null args'+ -> let g3pTangoArgs_key = hmacKey (fromMaybe B.empty mKey)+ g3pTangoArgs_inputs = fromMaybe V.empty mInputs+ in Just G3PTangoArgs{..}+ _ -> Nothing++defaultEchoCounter :: Word32+defaultEchoCounter = word32 "OUT\x00"++getEchoCounter :: Maybe Val -> Maybe Word32+getEchoCounter = \case+ Nothing -> Just defaultEchoCounter+ Just Nul -> Just defaultEchoCounter+ Just (Int ctr)+ | 0 <= ctr && ctr <= fromIntegral (maxBound :: Word32)+ -> Just (fromIntegral ctr)+ | otherwise -> Nothing+ Just (Str str) ->+ if B.length str <= 4+ then Just (word32 (B.concat (takeBs 4 [str, nullBuffer])))+ else Nothing+ _ -> Nothing++getByteStringVector_defaultEmpty :: Maybe Val -> Maybe (Vector ByteString)+getByteStringVector_defaultEmpty = \case+ Nothing -> Just V.empty+ Just Nul -> Just V.empty+ Just (Str str) -> Just (V.singleton str)+ Just (Vec vec) -> Just vec+ _ -> Nothing++getMaybeByteStringVector :: Maybe Val -> Maybe (Maybe (Vector ByteString))+getMaybeByteStringVector = \case+ Nothing -> Just Nothing+ Just Nul -> Just Nothing+ Just (Str str) -> Just (Just (V.singleton str))+ Just (Vec vec) -> Just (Just vec)+ _ -> Nothing++getByteString_defaultEmpty :: Maybe Val -> Maybe ByteString+getByteString_defaultEmpty+ = fmap (fromMaybe B.empty) . getMaybeByteString++getByteString :: Maybe Val -> Maybe ByteString+getByteString = \case+ Just (Str str) -> Just str+ _ -> Nothing++getMaybeByteString :: Maybe Val -> Maybe (Maybe ByteString)+getMaybeByteString = \case+ Just (Str str) -> Just (Just str)+ Just Nul -> Just Nothing+ Nothing -> Just Nothing+ _ -> Nothing++matchKey, matchKey' :: ResultEnv -> Key -> KeyMap Val -> (Maybe Val, KeyMap Val)+matchKey env key map = (interpRefs env (KM.lookup key map), KM.delete key map)+matchKey' env key map = (interpRefs env (KM.lookup key map), map)++interpRefs :: ResultEnv -> Maybe Val -> Maybe Val+interpRefs env (Just ref@(Ref testId bytes)) =+ case Map.lookup testId env of+ Nothing -> Just ref+ Just (Left _) -> Just ref+ Just (Right echo) -> Just (Str (takeBytes bytes echo))+interpRefs _ val = val
test/Main.hs view
@@ -1,13 +1,21 @@ import Test.Tasty import Data.Monoid-import qualified G3P+import qualified G3Pb1+import qualified G3Pb2+import qualified Bcrypt+-- import qualified MyCorpExample main = do- let fileName = G3P.testVectorDefaultFileName- g3pTvs <- G3P.readTestVectorsFromFile fileName- defaultMain (tests g3pTvs)+ let fileName1 = G3Pb1.testVectorDefaultFileName+ g3pb1Tvs <- G3Pb1.readTestVectorsFromFile fileName1+ let fileName2 = G3Pb2.testVectorDefaultFileName+ g3pb2Tvs <- G3Pb2.readTestVectorsFromFile fileName2+ defaultMain (tests g3pb1Tvs g3pb2Tvs) -tests :: (String, Either String G3P.TestVectors) -> TestTree-tests g3pTvs = testGroup "Test" [- testGroup "G3Pb1" [G3P.testFile g3pTvs]+tests :: (String, Either String G3Pb1.TestVectors) -> (String, Either String G3Pb2.TestVectors) -> TestTree+tests g3pb1Tvs g3pb2Tvs = testGroup "Test" [+ testGroup "G3Pb1" [G3Pb1.testFile g3pb1Tvs],+ testGroup "G3Pb2" [G3Pb2.testFile g3pb2Tvs],+ testGroup "bcrypt" Bcrypt.tests+-- testGroup "examples" MyCorpExample.tests ]
+ test/MyCorpExample.hs view
@@ -0,0 +1,212 @@+{-# LANGUAGE OverloadedStrings, OverloadedLists #-}++{- |++This is intended to be a fairly realistic sketch of what a reasonable-quality+deployment of the G3P might look like for authentication purposes.++TODO: this file may appear to use base16-encoded inputs, but it's literally+base16-encoded inputs, not things decoded to binary and then used as an input.+Fix this.++It starts to sketch how end-to-end encryption might work, but this is intended+more to stimulate the imagination than be a complete sketch.++-}++-- TODO: get the JSON test harness capable of handling partial evaluation+-- TODO: actually set up haddock example testing+-- TODO: include good examples of rehearsals+-- G3Pb2 full dress rehearsal on password change+-- G3Pb2 login tech rehearsal on login page load+-- TODO: G3Pb2 suggested approaches to handling second secrets++module MyCorpExample where++import Data.ByteString(ByteString)+import qualified Data.ByteString as B+import qualified Data.ByteString.Base16 as B+import Data.Text(Text)+import Data.Vector()+import Test.Tasty+import Test.Tasty.HUnit++import Crypto.G3P.V2+import Crypto.G3P.V2.Foxtrot+import Crypto.PHKDF(phkdfGen_head)+import Crypto.PHKDF.HMAC(hmacKey)+import Crypto.Argon2++tests :: [TestTree]+tests =+ [ testCase "My Corporation" $ map B.encodeBase16 results @?= auResults+ ]++results :: [ByteString]+results =+ let myDomain = "my.domain.example"+ myLoginDomain = "login.my.domain.example"+ myStorageDomain = "cloud.my.domain.example"+ myLongTag = "My Corporation, Inc. https://my.domain.example/.well-known/security.txt" :: ByteString+ mySeguid = hmacKey "9c08053b7e507a78b571b5b93e1326674540d7106da6408fcafeddcfcdf1ed76"+ -- If your deployment uses a public salt server, I recommend keeping+ -- random salts directly in a database. More specifically, I do not+ -- recommend deriving public salts from non-public information, as an+ -- evesdropper could use this as evidence that they have actually+ -- compromised your stuff. Or, your deployment could apply key-stretching+ -- to a login name to derive a salt in a transparent way, avoiding the+ -- pitfalls of running a public salt server.++ userPublicSalt = "60473b8010e16d46"+ userSecondSecretHash = "0c06f683f093cb899b4a1e9836fc7281"+ userSalt =+ G3PSalt {+ g3pSalt_seguid = mySeguid,+ g3pSalt_longTag = myLongTag,+ g3pSalt_contextTags = [userPublicSalt],+ g3pSalt_domainTag = myDomain,+ g3pSalt_phkdfRounds = 20240+ }+ userInputs =+ G3PInputs {+ g3pInputs_username = userPublicSalt,+ g3pInputs_password = "correct horse battery staple",+ g3pInputs_credentials = [userSecondSecretHash]+ }+ mySeedInputs =+ G3PSeedInputs {+ g3pSeedInputs_bcryptSeguid = mySeguid,+ g3pSeedInputs_bcryptCredentials = [],+ g3pSeedInputs_bcryptLongTag = myLongTag,+ g3pSeedInputs_bcryptContextTags = [],+ g3pSeedInputs_bcryptDomainTag = myDomain,+ g3pSeedInputs_bcryptRounds = 4202+ }+ userSprout = g3pHash userSalt userInputs mySeedInputs mySeguid+ userHeader = userPublicSalt <> myDomain+ userAuthPrehash =+ mySprout ["auth",userPublicSalt] myLoginDomain+ userHeader userHeader (word32 "AUTH") myLongTag++ -- Now, everything above would ideally happen on the client device, not+ -- the server. However, the server needs to hash the result further before+ -- storage. To deter precomputation attacks on an account, this sketch+ -- of a hypothetical deployment combines two somewhat crude but effective+ -- strategies:+ --+ -- 1. The auth servers perform the main key-stretching computation, and+ --+ -- 2. The auth servers perform this computation behind a secret HMAC key++ -- In my estimation, in many contexts argon2 is likely to be the most+ -- easily accepted hash function for key-stretching. However without+ -- modification argon2 doesn't have much cryptoacoustic potential,+ -- unlike yescrypt and Catena which appear to have some (probably+ -- largely "accidental") cryptoacoustic potential.++ -- I'd love to build a new hash function based closely on argon2, but+ -- this is delicate, time-consuming, and unpredictable work. For the+ -- time being, combining argon2 and g3pFoxtrot is almost certainly an+ -- excellent choice for server-side hashing.++ -- If the overall authentication flow is based on sending a plaintext+ -- prehash to the server which is then hashed further, I recommend using+ -- a secret, server-side salt per account. As this salt is never intended+ -- to be publicly acknowledged, one could derive this salt from+ -- non-public information without directly providing an evedropper the+ -- ability to prove to others they've been in your infrastructure.++ -- However, I would still recommend always storing a random secret per+ -- account so that an evesdropper cannot steal your entire secret salt+ -- database, possibly including secret salts that aren't yet in use,+ -- by stealing a single key. On the other hand, deriving the secret salt+ -- using a relatively small number of keys stored outside the database+ -- means that even if somebody steals your auth database, they won't+ -- necessarily have access to your secret salts.++ mySecretSeguid = hmacKey "7db250698fe555f6832f33189f97e14ef3c1c2dcada5807119aa7676c24f3fac"++ userPrivateSeed = "4314a11c2620a8ad"+ userPrivatePreSalt = g3pTango mySecretSeguid [userPrivateSeed,userPublicSalt, "user private presalt"] (word32 "SALT") myLoginDomain+ userPrivateSalt = g3pTango mySeguid [userPrivatePreSalt, "user private salt"] (word32 "SALT") myLoginDomain++ -- This derivation scheme allows My Corp to prove that its secret+ -- salts are in fact its trade secrets even in the face of the most dogged+ -- liars. Moreover this fact can possibly remain plausibly deniable even+ -- after the derivation has been stolen and published, so neither does+ -- this necessarily commit My Corp to claiming its secrets.++ -- Deriving a secret HMAC key per account allows My Corp to outsource+ -- offline cracking attacks on individual accounts without revealing an+ -- offline cracking attack on every account.++ -- Moreover, this derivation allows the proof-of-trade-secret to also be+ -- revealed/claimed on a per-account basis.++ foxtrot = g3pFoxtrot (G3PFoxtrotSalt+ { g3pFoxtrotSalt_key = hmacKey (userPrivateSalt <> B.take 32 userHeader)+ , g3pFoxtrotSalt_longTag = myLongTag+ , g3pFoxtrotSalt_contextTags = [userPublicSalt]+ , g3pFoxtrotSalt_domainTag = myLoginDomain+ , g3pFoxtrotSalt_bcryptRounds = 383+ })++ argon2 = hash $ HashOptions+ { hashIterations = 3+ , hashMemory = 384 * 1024 -- 384 MiB+ , hashParallelism = 1+ , hashVariant = Argon2id+ , hashVersion = Argon2Version13+ , hashLength = 32+ }++ userPrestoreHash = foxtrot ("P" <> userAuthPrehash) [] (word32 "PASS")+ (Right userArgon2Hash) = argon2 userRandomSalt (userPrestoreHash <> myLongTag)+ foxtrot' = foxtrot ("A" <> userArgon2Hash)+ userStoredHash = foxtrot' [] (word32 "HASH")++ -- userStoredHash is suitable to be stored in an auth database, and+ -- subsequent authentication attempts can compare this hash against the+ -- database. If this authentication is successful, we can efficiently+ -- compute a storage key that includes all of the key-stretching work+ -- performed thus far:++ userStorageKey = foxtrot' ["storage-key"] (word32 "KEY\x00")++ -- Note that this storage key will be re-combined with the original+ -- client-side seed before end-to-end encrypted files can be unlocked.+ -- The storage key is useless on its own, and therefore the auth server+ -- never gains the information needed to unlock the files without first+ -- guessing "correct horse battery staple".++ -- An attacker who has access to the user's encrypted files but does not+ -- have that user's secret server-side salt would not be able to confirm+ -- or deny that the user's password is "correct horse battery staple",+ -- unless the user has a backup method to unlock that particular file+ -- without talking to the auth server, and that backup method is the+ -- user's reused password.++ userDiskKey = mySprout+ ["disk",myStorageDomain,myLongTag,+ "key",userStorageKey]+ myStorageDomain userHeader userHeader (word32 "DISK")++ -- myLongTag is included above because it is sufficiently long to be able+ -- to commit to the "disk" and myStorageDomain values by partially+ -- evalating the sprout and then forgetting the seed.++ in [ userAuthPrehash+ , userStoredHash+ , userDiskKey "filename0.txt"+ , userDiskKey "quarterly-report.pdf"+ ]++-- FIXME: these are currently wrong, change these once this part of the test+-- suite is working again.+auResults :: [Text]+auResults =+ [ "3759cc63959878c79e9077f7c8dc401cad1700e03bab7ca52ef2982553c37197"+ , "e8c26138add0f16e49ad1e2b55ff333eda42fa7330969146f55ac48a49f7166e"+ , "d0a3b6c432b6b612fb82a60554fa3fa906e8a4cc324c6f1de38e52d8eec254cf"+ , "c2fb84c71dbe52280bd0d481c770e4e476a5e0daeeddc3e9eee00423bef9a7e4"+ ]