Skip to content

Commit

Permalink
add demo application
Browse files Browse the repository at this point in the history
  • Loading branch information
oxarbitrage committed Nov 9, 2023
1 parent 1c460d3 commit f1065bf
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 2 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ This codebase is primarily a personal project, initiated for the purpose of lear

## Features

- Salsa20 Encryption and Decryption Demo: Explore the Salsa20 encryption and decryption application by running the `stack run` command.

- Extensive Testing: The project includes a comprehensive suite of tests to ensure code quality. You can run the tests locally using `stack test` or `cabal test`. The CI (Continuous Integration) pipeline also runs most of these tests.

## Tutorial
Expand Down
95 changes: 94 additions & 1 deletion app/Main.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,99 @@
{-|
Salsa20 Encryption and Decryption Application
This Haskell program demonstrates the encryption and decryption of messages using the Salsa20 stream cipher.
The application takes a user-provided secret key phrase and a message as input, performs encryption,
and then decrypts the message to demonstrate the Salsa20 algorithm.
The code uses the Salsa20 implementation from the hsalsa Crypt module, SHA256 for key hashing, Crypto.Nonce for
nonce generation, and base64-bytestring for encoding.
Based in https://asecuritysite.com/encryption/salsa20
-}
module Main (main) where

import Data.Word
import Data.Char (ord)
import qualified Data.ByteString
import qualified Crypto.Hash.SHA256 as SHA256
import Crypto.Nonce
import Data.ByteString.Base64
import Data.List.Split

import Crypt

main :: IO ()
main = do
putStrLn "Salsa20 in haskell - Category theory"
putStrLn "---Salsa20 encryption and decryption"
putStrLn ""

key <- getInput "Insert your secret key phrase:"
message <- getInput "Insert message to be encrypted or decrypted:"

printInfo ["", "Plain text: " ++ message, "Secret key: " ++ key]

let messageBytes = intToWord32 $ stringToBytes message

let keyHash = Data.ByteString.unpack $ SHA256.hash (Data.ByteString.pack (intToWord8 (stringToBytes key)))
let keyB64 = Data.ByteString.Base64.encode $ Data.ByteString.pack keyHash

printInfo ["Key used: " ++ byteStringtoCharList keyB64, "", "---Salsa20 Encrypt"]

g <- Crypto.Nonce.new
nonce <- Crypto.Nonce.nonce128 g

let unpackedNonce = Data.ByteString.unpack nonce
-- We just need half of the nonce so we split it in two and take the first half
let nonces = Data.List.Split.chunksOf 8 unpackedNonce
let nonceB64 = Data.ByteString.Base64.encode $ Data.ByteString.pack (head nonces)

putStrLn ("Nonce: " ++ byteStringtoCharList nonceB64)

-- Our API accepts 2 keys of 16 bytes each so we split the 32 bytes generated by the hash in two
let keys = Data.List.Split.chunksOf 16 keyHash
let key1 = keys!!0
let key2 = keys!!1

let nonceAsList = word8ToWord32 (head nonces)

-- TODO: The API is ugly:
-- - We should be able to pass the nonce, keys and message as a strings and the API should convert it?
-- - The `0` at the end should not be needed?
-- - etc
let v2encrypted = cryptBlock32Compute messageBytes (word8ToWord32 key1) (word8ToWord32 key2) nonceAsList 0

let b64_cipher = Data.ByteString.Base64.encode $ Data.ByteString.pack (word32ToWord8 v2encrypted)
printInfo ["Ciphertext: " ++ byteStringtoCharList b64_cipher, "", "---Salsa20 Decrypt"]

let v2decrypted = cryptBlock32Compute v2encrypted (word8ToWord32 key1) (word8ToWord32 key2) nonceAsList 0

let b64_decrypted = Data.ByteString.pack (word32ToWord8 v2decrypted)

putStrLn ("Decrypted: " ++ byteStringtoCharList b64_decrypted)

return ()

where
getInput prompt = do
putStrLn prompt
getLine

printInfo = mapM_ putStrLn

stringToBytes :: String -> [Int]
stringToBytes = map ord

intToWord32 :: [Int] -> [Word32]
intToWord32 = map fromIntegral

intToWord8 :: [Int] -> [Word8]
intToWord8 = map fromIntegral

byteStringtoCharList :: Data.ByteString.ByteString -> [Char]
byteStringtoCharList = map (toEnum . fromEnum) . Data.ByteString.unpack

word8ToWord32 :: [Word8] -> [Word32]
word8ToWord32 = map fromIntegral

word32ToWord8 :: [Word32] -> [Word8]
word32ToWord8 = map fromIntegral
4 changes: 4 additions & 0 deletions package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ executables:
dependencies:
- salsa20
- split
- cryptohash-sha256
- bytestring
- nonce
- base64-bytestring

tests:
salsa20-fast-unit-tests:
Expand Down
4 changes: 4 additions & 0 deletions salsa20.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ executable salsa20-exe
ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N
build-depends:
base >=4.7 && <5
, base64-bytestring
, bytestring
, cryptohash-sha256
, keelung
, nonce
, salsa20
, split
, text
Expand Down
2 changes: 1 addition & 1 deletion src/Hash.hs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ salsa20Compute input rounds
| length input == 64 = aument $ coreCompute (Utils.reduce input) rounds
| otherwise = error "input to `salsa20Compute` must be a list of 64 `Word32` numbers"

-- |The salsa20 expression as a string using `core1Display` which is only one round of doubleround.
-- |The salsa20 expression as a string using `coreDisplay`. Call with r = 1, which is one round of doubleround.
salsa20Display :: [String] -> Int -> [String]
salsa20Display input rounds
| length input == 64 = aumentDisplay $ coreDisplay (reduceDisplay input) rounds
Expand Down

0 comments on commit f1065bf

Please sign in to comment.