packages feed

pg-wire-0.1.0.0: test/PgWire/Auth/MD5Spec.hs

module PgWire.Auth.MD5Spec (spec) where

import Crypto.Hash (MD5 (..), hashWith)
import Data.ByteArray qualified as BA
import Data.ByteString (ByteString)
import Data.ByteString qualified as BS
import Data.ByteString.Char8 qualified as BS8
import Test.Hspec

spec :: Spec
spec = do
  describe "MD5 password hash" $ do
    it "produces correct md5(md5(password+user)+salt) format" $ do
      -- PG MD5 auth: md5(md5(password + username) + salt)
      -- where md5() returns lowercase hex string
      let user = "testuser" :: ByteString
          password = "testpass" :: ByteString
          salt = BS.pack [0x01, 0x02, 0x03, 0x04]
          inner = md5Hex (password <> user)
          outer = "md5" <> md5Hex (inner <> salt)
      -- The hash should be deterministic
      BS.length outer `shouldBe` 35 -- "md5" + 32 hex chars
      BS8.isPrefixOf "md5" outer `shouldBe` True

    it "different passwords produce different hashes" $ do
      let user = "user" :: ByteString
          salt = BS.pack [0, 0, 0, 0]
          h1 = md5Hex (md5Hex ("pass1" <> user) <> salt)
          h2 = md5Hex (md5Hex ("pass2" <> user) <> salt)
      h1 `shouldNotBe` h2

md5Hex :: ByteString -> ByteString
md5Hex bs = BS8.pack $ concatMap (pad2 . showHex') (BS.unpack (BA.convert (hashWith MD5 bs)))
  where
    showHex' n = showHexByte n ""
    pad2 s = replicate (2 - length s) '0' ++ s
    showHexByte n = showHex n
    showHex 0 = ('0' :)
    showHex n
      | n < 16 = (hexDigit (fromIntegral n) :)
      | otherwise = showHex (n `div` 16) . (hexDigit (fromIntegral (n `mod` 16)) :)
    hexDigit i
      | i < 10 = toEnum (fromEnum '0' + i)
      | otherwise = toEnum (fromEnum 'a' + i - 10)