{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Main where
import Unsafe.Coerce
import Control.Exception
import Crypto.Hash
import qualified Data.ByteString.Lazy as B
import Data.Char
import Data.Maybe
import Data.Memorable
import Data.Memorable.Theme.Words
import Data.Monoid
import Data.Type.Equality
import GHC.TypeLits
import Options.Applicative
import System.Environment
import System.IO
type Algorithm = B.ByteString -> String
data Opt
= HashOpt
{ algorithm :: Algorithm
, files :: [FilePath]
}
| Convert
algOption ::
( Show a
, 40 <= MemLen (Digest a)
, KnownNat (MemLen (Digest a))
, Memorable (Digest a)
, HashAlgorithm a
) => a -> Parser Algorithm
algOption a = flag' (goAlg a) (long (map toLower $ show a))
algs :: [Parser Algorithm]
algs =
[ algOption Blake2b_512
, algOption Blake2bp_512
, algOption Blake2s_224
, algOption Blake2s_256
, algOption Blake2sp_224
, algOption Blake2sp_256
, algOption Keccak_224
, algOption Keccak_256
, algOption Keccak_384
, algOption Keccak_512
, algOption MD2
, algOption MD4
, algOption MD5
, algOption RIPEMD160
, algOption SHA1
, algOption SHA224
, algOption SHA256
, algOption SHA384
, algOption SHA3_224
, algOption SHA3_256
, algOption SHA3_384
, algOption SHA3_512
, algOption SHA512
, algOption SHA512t_224
, algOption SHA512t_256
, algOption Skein256_224
, algOption Skein256_256
, algOption Skein512_224
, algOption Skein512_256
, algOption Skein512_384
, algOption Skein512_512
, algOption Tiger
, algOption Whirlpool
]
choice :: Alternative f => f a -> [f a] -> f a
choice = foldr (<|>)
opts :: Parser Opt
opts = parseHashOpts <|> parseConv
where
parseHashOpts = do
algorithm <- parseAlgorithm
files <- parseFiles
pure HashOpt{..}
parseAlgorithm = choice empty algs
parseFiles = many $ strArgument (metavar "FILE" <> help "Files to hash" <> action "file")
parseConv = flag' Convert (long "conv" <> help "Convert hex lines on stdin to a memorable pattern")
main :: IO ()
main = do
opts <- execParser (info opts fullDesc)
case opts of
Convert -> do
ls <- lines <$> getContents
mapM_ (either (hPutStrLn stderr) putStrLn . doConv) ls
HashOpt{..} -> mapM_ (run algorithm) files
run :: Algorithm -> FilePath -> IO ()
run a f = do
res <- try $ do
bs <- B.readFile f
return $! a bs
case res of
Right h -> do
putStr h
putStr "\t"
putStrLn f
Left (e :: SomeException) -> hPutStrLn stderr (displayException e)
goAlg :: forall a.
( 40 <= MemLen (Digest a)
, KnownNat (MemLen (Digest a))
, Memorable (Digest a)
, HashAlgorithm a
) => a -> Algorithm
goAlg a b =
let
h = hashlazy b :: Digest a
in
renderMemorable (padHex $ four flw10) h
moreThan40 :: KnownNat n => Proxy (n :: Nat) -> Maybe ((40 <=? n) :~: True)
moreThan40 p = if natVal p > 40 then Just $ unsafeCoerce Refl else Nothing
doConv :: String -> Either String String
doConv i =
let
Just bitsIn = someNatVal $ fromIntegral $ length i * 4
in
case bitsIn of
SomeNat (p :: Proxy n) ->
case moreThan40 p of
Just Refl ->
let
s = rerender (hex @n) (padHex $ four flw10) i
in
maybe (Left $ "failed to convert '" ++ i ++ "'") Right s
Nothing -> Left $ "failed to convert '" ++ i ++ "'"