Skip to content

Instantly share code, notes, and snippets.

@JustusAdam
Last active June 26, 2020 14:08
Show Gist options
  • Save JustusAdam/e006e77a8407cc76e6dc853f2a255ff2 to your computer and use it in GitHub Desktop.
Save JustusAdam/e006e77a8407cc76e6dc853f2a255ff2 to your computer and use it in GitHub Desktop.
A simple('ish) and transparent password generator
-- A one-file Haskell password generator file.
--
-- has two generation methods
-- chars which works simply by generating random characters
-- dict which selects words from a dictionary.
--
-- The `dict` generation method needs `aspell` to be installed on the system (as
-- it uses the `aspell` wordlists). The language argument to the `dicts` method
-- must be a valid `aspell` language identifierm with the default being 'en'.
--
-- Also need the vector, MonadRandom and optparse-simple libraries to be
-- installed.
import Control.Monad.Random as Random
import qualified Data.Char as C
import Data.Ratio ((%))
import qualified Data.Vector as V
import Options.Applicative.Simple
import qualified System.Process as P
lowerCaseLetters = ['a' .. 'z']
upperCaseLetters = ['A' .. 'Z']
numbers = ['0' .. '9']
symbols = "-_/[]{}()*&^%$#@."
charGen length symbols = go
where
allowedVals = lowerCaseLetters ++ upperCaseLetters ++ numbers ++ symbols
go = do
p <- sequence $ replicate length (uniform allowedVals)
if and $
flip sequence p $
map
(any . flip elem)
[lowerCaseLetters, upperCaseLetters, numbers, symbols]
then return p
else go
capitalize [] = []
capitalize (x:xs) = C.toUpper x : xs
-- aspell -d en dump master | aspell -l en expand > words
dictGen language len symbols = do
dump <- P.readProcess "aspell" ["-d", language, "dump", "master"] ""
words <-
V.fromList . filter ('\'' `notElem`) . words <$>
P.readProcess "aspell" ["-l", language, "expand"] dump
let getWord = do
idx <- getRandomR (0, V.length words)
pure $ capitalize $ words V.! idx
ensure coll word
| any (`elem` coll) word = pure word
| otherwise = (: word) <$> uniform coll
getNumber = pure <$> uniform numbers
getSymbol = pure <$> uniform symbols
selectNext prev = do
new <-
(++ prev) <$>
join
(Random.fromList
[(getNumber, 1 % 4), (getSymbol, 1 % 4), (getWord, 1 % 2)])
if length new < len
then selectNext new
else ensure numbers new >>= ensure symbols
selectNext ""
toClipboard = const (error "Sorry, the clipboard copy does not work yet")
-- void . P.readProcess "xclip" ["-selection", "clipboard"]
main = do
((len, syms, copy), method) <-
simpleOptions
"2.1.0"
"Password Generator"
"Generates strong, random passwords"
((,,) <$>
option
auto
(short 'l' <>
long "length" <> value 20 <> help "(minimal) Length of the password") <*>
strOption
(long "symbols" <> value symbols <> help "valid symbol characters") <*>
switch (long "copy" <> help "Copy result to clipboard (uses xclip)")) $ do
addCommand
"chars"
"Generates each character individually"
id
(pure charGen)
addCommand
"dict"
"Generate using words from a dictionary"
id
(dictGen <$>
strOption
(short 'd' <>
long "language" <>
help "Language to use for the dictionary." <> value "en"))
(if copy then toClipboard else putStr) =<< method len syms
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment