Jump to content

One-time pad: Difference between revisions

added a Haskell version
(→‎{{header|Wren}}: Now uses Wren-srandom module.)
(added a Haskell version)
Line 390:
<pre>
Similar (not exactly the same, of course) as the Kotlin sample session.
</pre>
 
=={{header|Haskell}}==
<lang haskell>-- To compile into an executable:
-- ghc -main-is OneTimePad OneTimePad.hs
-- To run:
-- ./OneTimePad --help
 
module OneTimePad (main) where
 
import Control.Monad
import Data.Char
import Data.Function (on)
import qualified Data.Text as T
import qualified Data.Text.IO as TI
import Data.Time
import System.Console.GetOpt
import System.Environment
import System.Exit
import System.IO
 
-- Command-line options parsing
data Options = Options { optCommand :: String
, optInput :: IO T.Text
, optOutput :: T.Text -> IO ()
, optPad :: (IO T.Text, T.Text -> IO ())
, optLines :: Int
}
 
startOptions :: Options
startOptions = Options { optCommand = "decrypt"
, optInput = TI.getContents
, optOutput = TI.putStr
, optPad = (TI.getContents, TI.putStr)
, optLines = 0
}
 
options :: [ OptDescr (Options -> IO Options) ]
options =
[ Option "e" ["encrypt"]
(NoArg
(\opt -> return opt { optCommand = "encrypt" }))
"Encrypt file"
, Option "d" ["decrypt"]
(NoArg
(\opt -> return opt { optCommand = "decrypt" }))
"Decrypt file (default)"
, Option "g" ["generate"]
(NoArg
(\opt -> return opt { optCommand = "generate" }))
"Generate a one-time pad"
, Option "i" ["input"]
(ReqArg
(\arg opt -> return opt { optInput = TI.readFile arg })
"FILE")
"Input file (for decryption and encryption)"
, Option "o" ["output"]
(ReqArg
(\arg opt -> return opt { optOutput = TI.writeFile arg })
"FILE")
"Output file (for generation, decryption, and encryption)"
, Option "p" ["pad"]
(ReqArg
(\arg opt -> return opt { optPad = (TI.readFile arg,
TI.writeFile arg) })
"FILE")
"One-time pad to use (for decryption and encryption)"
, Option "l" ["lines"]
(ReqArg
(\arg opt -> return opt { optLines = read arg :: Int })
"LINES")
"New one-time pad's length (in lines of 48 characters) (for generation)"
, Option "V" ["version"]
(NoArg
(\_ -> do
hPutStrLn stderr "Version 0.01"
exitWith ExitSuccess))
"Print version"
, Option "h" ["help"]
(NoArg
(\_ -> do
prg <- getProgName
putStrLn "usage: OneTimePad [-h] [-V] [--lines LINES] [-i FILE] [-o FILE] [-p FILE] [--encrypt | --decrypt | --generate]"
hPutStrLn stderr (usageInfo prg options)
exitWith ExitSuccess))
"Show this help message and exit"
]
 
main :: IO ()
main = do
args <- getArgs
let (actions, nonOptions, errors) = getOpt RequireOrder options args
opts <- Prelude.foldl (>>=) (return startOptions) actions
let Options { optCommand = command
, optInput = input
, optOutput = output
, optPad = (inPad, outPad)
, optLines = linecnt } = opts
 
case command of
"generate" -> generate linecnt output
"encrypt" -> do
inputContents <- clean <$> input
padContents <- inPad
output $ format $ encrypt inputContents $ unformat $ T.concat
$ dropWhile (\t -> T.head t == '-' || T.head t == '#')
$ T.lines padContents
"decrypt" -> do
inputContents <- unformat <$> input
padContents <- inPad
output $ decrypt inputContents $ unformat $ T.concat
$ dropWhile (\t -> T.head t == '-' || T.head t == '#')
$ T.lines padContents
let discardLines = ceiling
$ ((/) `on` fromIntegral) (T.length inputContents) 48
outPad $ discard discardLines $ T.lines padContents
 
{- | Discard used pad lines. Is only called at decryption to enable using the
same pad file for both encryption and decryption.
-}
discard :: Int -> [T.Text] -> T.Text
discard 0 ts = T.unlines ts
discard x (t:ts) = if (T.head t == '-' || T.head t == '#')
then T.unlines [t, (discard x ts)]
else T.unlines [(T.append (T.pack "- ") t), (discard (x-1) ts)]
 
{- | Clean the text from symbols that cannot be encrypted.
-}
clean :: T.Text -> T.Text
clean = T.map toUpper . T.filter (\c -> let oc = ord c
in oc >= 65 && oc <= 122
&& (not $ oc >=91 && oc <= 96))
 
{- | Format text (usually encrypted text) for pretty-printing it in a similar
way to the example from Wikipedia (see Rosetta Code page for this task)
-}
format :: T.Text -> T.Text
format = T.unlines . map (T.intercalate (T.pack " ") . T.chunksOf 6)
. T.chunksOf 48
 
{- | Unformat encrypted text, getting rid of characters that are irrelevant for
decryption.
-}
unformat :: T.Text -> T.Text
unformat = T.filter (\c -> c/='\n' && c/=' ')
 
{- | Generate a one-time pad and write it to file (specified as second
parameter). Note: this only works on operating systems that have the
"/dev/random" file.
-}
generate :: Int -> (T.Text -> IO ()) -> IO ()
generate lines output = do
withBinaryFile "/dev/random" ReadMode
(\handle -> do
contents <- replicateM (48 * lines) $ hGetChar handle
time <- getCurrentTime
output
$ T.unlines [ T.pack
$ "# OTP pad, generated by https://github.com/kssytsrk/one-time-pad on "
++ show time
, format $ T.pack
$ map (chr . (65 +) . flip mod 26 . ord) contents
])
 
-- Helper function for encryption/decryption.
crypt :: (Int -> Int -> Int) -> T.Text -> T.Text -> T.Text
crypt f = T.zipWith ((chr .) . f `on` ord)
 
-- Encrypt first parameter's contents, using the second parameter as a key.
encrypt :: T.Text -> T.Text -> T.Text
encrypt = crypt ((((+65) . flip mod 26 . subtract 130) .) . (+))
 
-- Decrypt first parameter's contents, using the second parameter as a key.
decrypt :: T.Text -> T.Text -> T.Text
decrypt = crypt ((((+65) . flip mod 26) .) . (-))</lang>
 
{{out}}
Help message:
<pre>
$ ./OneTimePad --help
usage: OneTimePad [-h] [-V] [--lines LINES] [-i FILE] [-o FILE] [-p FILE] [--encrypt | --decrypt | --generate]
OneTimePad
-e --encrypt Encrypt file
-d --decrypt Decrypt file (default)
-g --generate Generate a one-time pad
-i FILE --input=FILE Input file (for decryption and encryption)
-o FILE --output=FILE Output file (for generation, decryption, and encryption)
-p FILE --pad=FILE One-time pad to use (for decryption and encryption)
-l LINES --lines=LINES New one-time pad's length (in lines of 48 characters) (for generation)
-V --version Print version
-h --help Show this help message and exit
</pre>
 
Generate a pad, outputting into test-pad.1tp file:
<pre>
$ ./OneTimePad -g -o test-pad.1tp -l 2
</pre>
 
Encrypt a message using test-pad.1tp and jabberwock.txt (contents of jabberwock.txt: "Beware the Jabberwock, my son! The jaws that bite, the claws that catch!"), outputting the result into encrypted.txt:
<pre>
$ ./OneTimePad -e -i jabberwock.txt -o encrypted.txt -p test-pad.1tp
</pre>
 
Decrypt the message, outputting the result into decrypted.txt:
<pre>
$ ./OneTimePad -d -i encrypted.txt -o decrypted.txt -p test-pad.1tp
</pre>
 
Contents of test-pad.1tp:
<pre>
# OTP pad, generated by https://github.com/kssytsrk/one-time-pad on 2021-08-08 06:53:52.91928977 UTC
- XAKUVI GLSWZB TEQCIR GNPHNR FBAPXQ IKXBCX BAAQIK OTTCQR
- YCLYOE YTPFWC SBVHSL MTEKHN QYOVMN GXWTUV VHQWUE DSWVJJ
</pre>
 
Contents of encrypted.txt:
<pre>
YEGUMM ZSWFZC UIHYWT QZNZBE YIEYXM ADEBVY JTEJPO QETYIK
FCEAOX AA
</pre>
 
Contents of decrypted.txt:
<pre>
BEWARETHEJABBERWOCKMYSONTHEJAWSTHATBITETHECLAWSTHATCATCH
</pre>
 
Anonymous user
Cookies help us deliver our services. By using our services, you agree to our use of cookies.