longshot (empty) → 0.1.0.0
raw patch · 9 files changed
+664/−0 lines, 9 filesdep +basedep +base16-bytestringdep +blake2
Dependencies added: base, base16-bytestring, blake2, blake3, bytestring, containers, cryptohash-sha256, cryptonite, deepseq, docopt, longshot, memory, parallel, tasty, tasty-hunit, tasty-quickcheck, template-haskell
Files
- LICENSE +30/−0
- README.md +122/−0
- app/Main.hs +73/−0
- longshot.cabal +119/−0
- src/Crypto/Longshot.hs +70/−0
- src/Crypto/Longshot/Hasher.hs +55/−0
- src/Crypto/Longshot/Internal.hs +62/−0
- src/Crypto/Longshot/TH.hs +49/−0
- test/Spec.hs +84/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Francis Lim (c) 2020++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * 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.++ * Neither the name of Francis Lim nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"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 COPYRIGHT+OWNER OR CONTRIBUTORS 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.
+ README.md view
@@ -0,0 +1,122 @@+<p align="center"> <img src="longshot.png" height="200"/> </p>++> Is it really a __long shot__ to hope that the COVID-19 pandemic will end?++++[](https://travis-ci.com/thyeem/longshot) <!-- [](https://hackage.haskell.org/package/longshot) -->++# longshot++__Search for preimage__ from a given hash value using _Brute-force_ method based on _parallelism_++* Support various __search lengths__, __character sets__ and __hashers__.+* Strict mode: searches only for a given _exact length_+* Deep mode: searches _everything less than or equal_ to a given length.+* Use `CPUs` as _much_ as possible. __Get the most out of them!__+* Use, however, `memory` as _little_ as possible.+ ++Enjoy `longshot`. Good luck!++++```plain+longshot - Fast and concise Brute-force search++Usage:+ longshot run [-n SIZE] [-c CHARS] [-a HASHER] [--deep] HEX+ longshot image [-a HASHER] KEY++Commands:+ run Brute-force search with given hexstring and options+ image Generate image from given key string and hash algorithm++Arguments:+ HEX Specify target hexstring to search+ KEY Specify key string as a preimage++Options:+ -h --help Show this+ -n SIZE Specify search length [default: 8] + -c CHARS Specify characters available in preimage [default: 0123456789]+ -a HASHER Specify hash algorithm [default: sha256]+ HASHER available below:+ md5 sha1 ripemd160 whirlpool+ sha256 sha3_256 sha3_384 sha3_512+ blake2s_256 blake2b_256 blake2b_384 blake2b_512+ blake3_256 blake3_384 blake3_512+ keccak_256 keccak_384 keccak_512+ skein_256 skein_384 skein_512+ --deep Search deeply including less than a given search length+```++## How to build+```bash+$ git clone https://github.com/thyeem/longshot.git++$ make build ++## Optional: test if installed properly using quickcheck+$ make test++## You can see an executable 'longshot' here+$ cd app+```++## Quick start+```bash+## Refer to the following usage:+## Note that the order of arguments and options matter.+$ ./longshot -h++## Generate test hash values+$ ./longshot image 12345+5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5++$ ./longshot image 12345678+ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f++## Brute-force search with default options:+## The same as: ./longshot -n 8 -c 0123456789 -a sha256 ef797c..98a64f+$ ./longshot run ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f+Found 12345678++## Search below fails because default search length is 8.+## In default mode, it searches by the exact length of key.+$ ./longshot run 5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5+Not found++## If you don't know the key length, use deep search. (--deep)+## It will try to search with less than or equal to the given length.+$ ./longshot run --deep 5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5+Found 12345+```+These are all about how to use `longshot`. +See below for more interesting detailed examples.++## More examples+```bash+## Generate a example image using Blake2b hash algorithm+## Blake2b-hashed sofia (my first daughter!)+$ ./longshot image -a blake2b sofia+bb40f637bb211532318965627f15bb165f701230fd83e0adf8cd673a6ee02830++## Need different character set. Don't forget --deep+$ ./longshot run -c 'abcdefghijklmnopqrstuvwxyz' -a blake2b --deep bb40f6..e02830+Found sofia++## You should consider the following if there might be more characters in preimage.+## More characters, much longer time to find!+$ ./longshot run -c 'abcdefghijklmnopqrstuvwxyz0123456789' -a blake2b --deep bb40f6..e02830++## Roughly, time spending is proportional to (Number of char available) ^ (char length).+## Exponent 'char length' is surely more dominant! Use long-long password as always!++## longshot is very efficient and get the most of CPU's power in parallel.+## But this kind of work would be very painful time even for longshot.+$ ./longshot run -n 12 -c 'abcdefghijklmnopqrstuvwxyz0123456789`~!@#$%^&*()-=_+[]{}\|' \+ -a blake2b --deep bb40f6..e02830 +RTS -s++## '+RTS -s' the end of line is optional, and that is for a summary of CPU time and memory.+```
+ app/Main.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE QuasiQuotes #-}+module Main where++import Control.Monad+import System.Environment+import System.Console.Docopt+import qualified Data.ByteString.Char8 as C+import qualified Data.ByteString.Base16 as H+import Crypto.Longshot.Internal+import Crypto.Longshot.Hasher++patterns :: Docopt+patterns = [docopt|+longshot - Fast and concise Brute-force search++Usage:+ longshot run [-n SIZE] [-c CHARS] [-a HASHER] [--deep] HEX+ longshot image [-a HASHER] KEY++Commands:+ run Brute-force search with given hexstring and options+ image Generate image from given key string and hash algorithm++Arguments:+ HEX Specify target hexstring to search+ KEY Specify key string as a preimage++Options:+ -h --help Show this+ -n SIZE Specify search length [default: 8] + -c CHARS Specify characters in preimage [default: 0123456789]+ -a HASHER Specify hash algorithm [default: sha256]+ HASHER available below:+ md5 sha1 ripemd160 whirlpool+ sha256 sha3_256 sha3_384 sha3_512+ blake2s_256 blake2b_256 blake2b_384 blake2b_512+ blake3_256 blake3_384 blake3_512+ keccak_256 keccak_384 keccak_512+ skein_256 skein_384 skein_512+ --deep Search deeply including less than a given search length+|]++-- | Defines args-ops frequently used+(><) = isPresent+(<->|!) = getArgOrExitWith patterns+(<->) = getArg++main :: IO ()+main = do+ args <- parseArgsOrExit patterns =<< getArgs+ when (args >< command "image") $ genImage args+ when (args >< command "run") $ run args++-- | Command: image+genImage :: Arguments -> IO ()+genImage args = do+ key <- C.pack <$> (args <->|! argument "KEY")+ hasher <- getHasher <$> (args <->|! shortOption 'a')+ putStrLn . C.unpack . H.encode . hasher $ key++-- | Command: run+run :: Arguments -> IO ()+run args = do+ hex <- args <->|! argument "HEX"+ chars <- args <->|! shortOption 'c'+ size <- (read <$> (args <->|! shortOption 'n')) :: IO Int+ hasher <- getHasher <$> (args <->|! shortOption 'a')+ let solver | args >< longOption "deep" = bruteforceDeep+ | otherwise = bruteforce+ let found = solver size chars hex hasher+ case found of+ Just key -> putStrLn $ "Found " <> key+ _ -> putStrLn "Not found"
+ longshot.cabal view
@@ -0,0 +1,119 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.33.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: 5c862a15f456f6ba99cb05a6abe2c68fa81755d8855575d2dd63872d01d2dd7c++name: longshot+version: 0.1.0.0+synopsis: Fast Brute-force search using parallelism+description: Longshot enables to search for preimages from a given hash value+ using a brute-force method based on parallelism.+ .+ * Support various search lengths, character sets and hashers+ * Strict mode: searches only for a given exact length+ * Deep mode: searches everything less than or equal to a given length.+ * Use @CPUs@ as much as possible. Get the most out of them!+ * Use, however, @memory@ as little as possible.+ .+ Please see the documentation at https://github.com/thyeem/longshot+ for usage example.+ .+category: algorithm, search, cryptography, parallelism+homepage: https://github.com/thyeem/longshot#readme+bug-reports: https://github.com/thyeem/longshot/issues+author: Francis Lim, Jongwhan Lee+maintainer: thyeem@gmail.com+copyright: Francis, 2020+license: MIT+license-file: LICENSE+build-type: Simple+extra-source-files:+ README.md++source-repository head+ type: git+ location: https://github.com/thyeem/longshot++library+ exposed-modules:+ Crypto.Longshot+ Crypto.Longshot.Hasher+ Crypto.Longshot.Internal+ Crypto.Longshot.TH+ other-modules:+ Paths_longshot+ hs-source-dirs:+ src+ default-extensions: TemplateHaskell KindSignatures DataKinds+ build-depends:+ base >=4.12.0 && <5+ , base16-bytestring >=0.1.0 && <0.2+ , blake2 >=0.3.0 && <0.4+ , blake3 >=0.2 && <0.3+ , bytestring >=0.10.8 && <0.11+ , containers >=0.6 && <0.7+ , cryptohash-sha256 >=0.11.101 && <0.12+ , cryptonite >=0.25+ , deepseq >=1.4.4 && <1.5+ , docopt >=0.7.0 && <0.8+ , memory >=0.14.0 && <0.16+ , parallel >=3.2.2 && <3.3+ , template-haskell >=2.14.0 && <2.17+ default-language: Haskell2010++executable longshot+ main-is: Main.hs+ other-modules:+ Paths_longshot+ hs-source-dirs:+ app+ default-extensions: TemplateHaskell KindSignatures DataKinds+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -dynamic -feager-blackholing -g0 -eventlog+ build-depends:+ base >=4.12.0 && <5+ , base16-bytestring >=0.1.0 && <0.2+ , blake2 >=0.3.0 && <0.4+ , blake3 >=0.2 && <0.3+ , bytestring >=0.10.8 && <0.11+ , containers >=0.6 && <0.7+ , cryptohash-sha256 >=0.11.101 && <0.12+ , cryptonite >=0.25+ , deepseq >=1.4.4 && <1.5+ , docopt >=0.7.0 && <0.8+ , longshot+ , memory >=0.14.0 && <0.16+ , parallel >=3.2.2 && <3.3+ , template-haskell >=2.14.0 && <2.17+ default-language: Haskell2010++test-suite longshot-test+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ other-modules:+ Paths_longshot+ hs-source-dirs:+ test+ default-extensions: TemplateHaskell KindSignatures DataKinds+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ base >=4.12.0 && <5+ , base16-bytestring >=0.1.0 && <0.2+ , blake2 >=0.3.0 && <0.4+ , blake3 >=0.2 && <0.3+ , bytestring >=0.10.8 && <0.11+ , containers >=0.6 && <0.7+ , cryptohash-sha256 >=0.11.101 && <0.12+ , cryptonite >=0.25+ , deepseq >=1.4.4 && <1.5+ , docopt >=0.7.0 && <0.8+ , longshot+ , memory >=0.14.0 && <0.16+ , parallel >=3.2.2 && <3.3+ , tasty+ , tasty-hunit+ , tasty-quickcheck+ , template-haskell >=2.14.0 && <2.17+ default-language: Haskell2010
+ src/Crypto/Longshot.hs view
@@ -0,0 +1,70 @@+-- |+-- Module : Crypto.Longshot+-- License : MIT+-- Maintainer : Francis Lim <thyeem@gmail.com>+-- Stability : experimental+-- Portability : unknown+--+-- How big is the search space? The space consists of two axes.+--+-- * X-axis: Number of characters available+-- * Y-axis: Search length of preimage to find+--+-- Note that it's proportional to @(X ^ Y)@ rather than @(X * Y)@+--+-- The values below are defined by default.+--+-- When not provided as options in CUI, the following values are used.+--+module Crypto.Longshot+ ( defChars+ , defSearchLength+ , defNumPrefix+ , defNumBind+ , image+ , byteChars+ , bytePrefixes+ , toKey+ )+where++import Control.Monad+import qualified Data.ByteString.Char8 as C+import qualified Data.ByteString.Base16 as H++-- | Characters available in a preimage+defChars :: String+defChars = "0123456789"++-- | Search length of preimage+defSearchLength :: Int+defSearchLength = 8++-- | Limit search length of preimage+limitSearchLength :: Int+limitSearchLength = 20++-- | Value related to the number of sparks+defNumPrefix :: Int+defNumPrefix = 3++-- | Number of actions in TH bruteforceN+defNumBind :: Int+defNumBind = limitSearchLength - defNumPrefix+--------------------------------------------------------------------++-- | Image bytestring: target hash value to find+image :: String -> C.ByteString+image = fst . H.decode . C.pack++-- | Bytestring usable for preimage+byteChars :: String -> [C.ByteString]+byteChars chars = C.pack . (: []) <$> chars++-- | Combination of prefixes possible: size of (length of chars) ^ (numPrefix)+bytePrefixes :: Int -> String -> [C.ByteString]+bytePrefixes numPrefix chars = C.pack <$> replicateM numPrefix chars++-- | Convert preimage found into key string+toKey :: C.ByteString -> String+toKey = C.unpack
+ src/Crypto/Longshot/Hasher.hs view
@@ -0,0 +1,55 @@+-- |+-- Module : Crypto.Longshot.Hasher+-- License : MIT+-- Maintainer : Francis Lim <thyeem@gmail.com>+-- Stability : experimental+-- Portability : unknown+--+module Crypto.Longshot.Hasher+ ( Hasher+ , getHasher+ )+where++import GHC.TypeLits+import Numeric.Natural+import qualified Data.ByteArray as B+import qualified Data.ByteString.Char8 as C+import qualified Crypto.Hash as X+import qualified Crypto.Hash.SHA256 as S+import qualified Crypto.Hash.BLAKE2.BLAKE2s as B2s+import qualified Crypto.Hash.BLAKE2.BLAKE2b as B2b+import qualified BLAKE3 as B3++-- | Type for hash functions available+type Hasher = C.ByteString -> C.ByteString++type Blake3_256 = C.ByteString -> B3.Digest (32 :: Nat)+type Blake3_384 = C.ByteString -> B3.Digest (48 :: Nat)+type Blake3_512 = C.ByteString -> B3.Digest (64 :: Nat)++-- | Select hasher by defined name+getHasher :: String -> Hasher+getHasher name = case name of+ "md5" -> B.convert . X.hashWith X.MD5+ "sha1" -> B.convert . X.hashWith X.SHA1+ "ripemd160" -> B.convert . X.hashWith X.RIPEMD160+ "whirlpool" -> B.convert . X.hashWith X.Whirlpool+ "sha256" -> S.hash+ "sha3_256" -> B.convert . X.hashWith X.SHA3_256+ "sha3_384" -> B.convert . X.hashWith X.SHA3_384+ "sha3_512" -> B.convert . X.hashWith X.SHA3_512+ "blake2s_256" -> B2s.hash 32 mempty+ "blake2b_256" -> B2b.hash 32 mempty+ "blake2b_384" -> B2b.hash 48 mempty+ "blake2b_512" -> B2b.hash 64 mempty+ "blake3_256" -> B.convert . (B3.hash . (: []) :: Blake3_256)+ "blake3_384" -> B.convert . (B3.hash . (: []) :: Blake3_384)+ "blake3_512" -> B.convert . (B3.hash . (: []) :: Blake3_512)+ "keccak_256" -> B.convert . X.hashWith X.Keccak_256+ "keccak_384" -> B.convert . X.hashWith X.Keccak_384+ "keccak_512" -> B.convert . X.hashWith X.Keccak_512+ "skein_256" -> B.convert . X.hashWith X.Skein256_256+ "skein_384" -> B.convert . X.hashWith X.Skein512_384+ "skein_512" -> B.convert . X.hashWith X.Skein512_512+ a -> errorWithoutStackTrace $ "Not allowed hash algorithm " <> a
+ src/Crypto/Longshot/Internal.hs view
@@ -0,0 +1,62 @@+-- |+-- Module : Crypto.Longshot.Internal+-- License : MIT+-- Maintainer : Francis Lim <thyeem@gmail.com>+-- Stability : experimental+-- Portability : unknown+--+module Crypto.Longshot.Internal+ ( bruteforce+ , bruteforceDeep+ )+where++import Control.Applicative+import Control.DeepSeq+import Control.Parallel+import Data.Foldable+import qualified Data.ByteString.Char8 as C+import Language.Haskell.TH+import Crypto.Longshot+import Crypto.Longshot.TH+import Crypto.Longshot.Hasher++-- Declaration of bruteforceN: generating code by splicing+$( funcGenerator )++-- | Brute-force search only for a given exact length+bruteforce :: Int -> String -> String -> Hasher -> Maybe String+bruteforce size chars hex hasher = found+ where+ found = foldl' (<|>) empty (runPar <%> prefixes)+ runPar = bruteforcePar numBind (byteChars chars) (image hex) hasher+ numPrefix | size < defNumPrefix = 1+ | otherwise = defNumPrefix+ numBind = size - numPrefix+ prefixes = bytePrefixes numPrefix chars++-- | Pick up an appropriate search function+bruteforcePar+ :: Int+ -> [C.ByteString]+ -> C.ByteString+ -> Hasher+ -> C.ByteString+ -> Maybe String+bruteforcePar n+ | n `elem` [0 .. defNumBind] = $( funcList ) !! n+ | otherwise = errorWithoutStackTrace "Not available search length"++-- | Deep Brute-force search including less than a given search size+bruteforceDeep :: Int -> String -> String -> Hasher -> Maybe String+bruteforceDeep size x y z = foldl' (<|>) empty found+ where+ found = deep x y z <%> [1 .. size]+ deep a b c d = bruteforce d a b c++-- | Parallel map using deepseq, par and pseq+(<%>) :: (NFData a, NFData b) => (a -> b) -> [a] -> [b]+f <%> [] = []+f <%> (x : xs) = y `par` ys `pseq` (y : ys) where+ y = force $ f x+ ys = f <%> xs
+ src/Crypto/Longshot/TH.hs view
@@ -0,0 +1,49 @@+-- |+-- Module : Crypto.Longshot.TH+-- License : MIT+-- Maintainer : Francis Lim <thyeem@gmail.com>+-- Stability : experimental+-- Portability : unknown+--+module Crypto.Longshot.TH where++import Control.Monad+import Data.Foldable+import Language.Haskell.TH+import Crypto.Longshot++-- | Brute-force with N-search-length using TH+bruteforceN :: Int -> Q Exp -> Q Exp -> Q Exp -> Q Exp -> Q Exp+bruteforceN numBind chars hex hasher prefix = do+ names <- replicateM numBind (newName "names")+ let pats = varP <$> names+ let bytes = [| $( prefix ) <>+ $( foldl' (\a b -> [| $a <> $b |])+ [| mempty |]+ (varE <$> names)+ )+ |]+ let cond = condE [| $( appE hasher bytes ) == $(hex) |]+ [| Just (toKey $( bytes )) |]+ [| Nothing |]+ let stmts = ((`bindS` chars) <$> pats) <> [noBindS cond]+ [| foldl' (<|>) empty $( compE stmts ) |]++-- | Declare functions to run in parallel for search+funcGenerator :: Q [Dec]+funcGenerator = forM [0 .. defNumBind] funcG where + funcG numBind = do+ let name = mkName $ "bruteforce" <> show numBind+ chars <- newName "chars"+ hex <- newName "hex"+ hasher <- newName "hasher"+ prefix <- newName "prefix"+ funD name + [ clause [varP chars, varP hex, varP hasher, varP prefix] + (normalB (bruteforceN numBind (varE chars) (varE hex) (varE hasher) (varE prefix))) + []+ ]++-- | Get list of functions to run in parallel for search+funcList :: Q Exp+funcList = listE (varE . mkName . ("bruteforce" <>) . show <$> [0 .. defNumBind])
+ test/Spec.hs view
@@ -0,0 +1,84 @@+import Test.Tasty+import Test.Tasty.QuickCheck+import Test.Tasty.HUnit+import Control.Monad+import qualified Data.ByteString.Char8 as C+import qualified Data.ByteString.Base16 as H+import Crypto.Longshot.Internal+import Crypto.Longshot.Hasher++main :: IO ()+main = defaultMain tests++-- | Search Length limit+-- The key length was limited to 4 as it takes a long time to test+limit :: Int+limit = 4++-- | Characters prepared for test+chars :: String+chars = "0123456789abcdef~'*+-%^?[]/"++-- | Hash algorithms available+hashers :: [String]+hashers =+ [ "md5"+ , "sha1"+ , "ripemd160"+ , "whirlpool"+ , "sha256"+ , "sha3_256"+ , "sha3_384"+ , "sha3_512"+ , "blake2s_256"+ , "blake2b_256"+ , "blake2b_384"+ , "blake2b_512"+ , "blake3_256"+ , "blake3_384"+ , "blake3_512"+ , "keccak_256"+ , "keccak_384"+ , "keccak_512"+ , "skein_256"+ , "skein_384"+ , "skein_512"+ ]++-- | Test for strict mode of Brute-force search+testLongshot :: Hasher -> Gen Bool+testLongshot hasher = do+ size <- choose (1, limit) :: Gen Int+ key <- replicateM size (elements chars :: Gen Char)+ let hex = C.unpack . H.encode . hasher . C.pack $ key+ let found = bruteforce size chars hex hasher+ case found of+ Just x | x == key -> return True+ _ -> return False++-- | Test for deep mode of Brute-force search+testLongshotDeep :: Hasher -> Gen Bool+testLongshotDeep hasher = do+ let size = limit+ size' <- choose (1, limit) :: Gen Int+ key <- replicateM size' (elements chars :: Gen Char)+ let hex = C.unpack . H.encode . hasher . C.pack $ key+ let found = bruteforceDeep size chars hex hasher+ case found of+ Just x | x == key -> return True+ _ -> return False++-- | Property test generator+genTestProp :: (Hasher -> Gen Bool) -> TestName -> TestName -> TestTree+genTestProp f desc algo = testProperty (desc <> algo) (f $ getHasher algo)++-- | Main test of properties+tests :: TestTree+tests = testGroup+ "Longshot Tests"+ [ testGroup "Strict-Longshot search"+ (genTestProp testLongshot "testLongshot with " <$> hashers)+ , testGroup+ "Deep-Longshot search"+ (genTestProp testLongshotDeep "testLongshotDeep with " <$> hashers)+ ]