Skip to content

Commit

Permalink
improve a bit the app
Browse files Browse the repository at this point in the history
  • Loading branch information
oxarbitrage committed Apr 14, 2024
1 parent 2f86b78 commit 1093c75
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 54 deletions.
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ 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` or `cabal run` commands.
- Salsa20 Encryption and Decryption Demo: Explore the Salsa20 encryption and decryption application by running the `stack run` or `cabal run salsa20-exe` commands.

- 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.

Expand All @@ -22,6 +22,32 @@ This codebase is primarily a personal project, initiated for the purpose of lear

For a detailed guide on using this Salsa20 cipher, check out our [tutorial](book/tutorial.md). The tutorial breaks down the Salsa20 algorithm into its core components, explains the underlying concepts, and provides practical examples.

## Application

You can run the demo application with `stack run` command, for example:

```bash
---Salsa20 encryption and decryption

Insert your secret key phrase:
secret key here
Insert message to be encrypted or decrypted:
testing some sort of message

Plain text: testing some sort of message
Secret key: secret key here
Key used: Tix73W8AQv7OKbGbxr7W7d3ZqR9YwcHYazNoUXrP0vo=

---Salsa20 Encrypt
Nonce: Gww7O2hiZ2Q=
Ciphertext: xwlqEAOSot6wzT37HuBFLhJiacLSC90yobCoTA==

---Salsa20 Decrypt
Decrypted: testing some sort of message
```

Please refer to the [application source code](https://github.com/oxarbitrage/hsalsa20/blob/main/app/Main.hs) for more information.

## API Documentation

Explore the API documentation for a deeper understanding of the project. Visit [here](https://oxarbitrage.github.io/salsa20-docs/) for detailed information on the project's functions and modules.
Expand Down
138 changes: 85 additions & 53 deletions app/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,96 +5,128 @@ This Haskell program demonstrates the encryption and decryption of messages usin
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.
This code uses:
- [hsalsa20 Crypt module](https://github.com/oxarbitrage/hsalsa20/blob/main/src/Crypt.hs) for encryption and decryption.
- [SHA256](https://hackage.haskell.org/package/cryptohash-sha256) for key hashing.
- [Crypto.Nonce](https://hackage.haskell.org/package/nonce) for nonce generation.
- [base64-bytestring](https://hackage.haskell.org/package/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 Data.ByteString as B
import qualified Crypto.Hash.SHA256 as SHA256
import Crypto.Nonce
import Data.ByteString.Base64
import Data.ByteString.Base64 as B64
import Data.List.Split

import Crypt

{-@ ignore main @-}
main :: IO ()
main = do
putStrLn "---Salsa20 encryption and decryption"
putStrLn ""
printInfo ["---Salsa20 encryption and decryption", ""]

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

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

let messageBytes = intToWord32 $ stringToBytes message
-- Hash the key
let key_hash = keyHash key_string
-- Encode the key as base64
let key_base64 = toBase64 key_hash

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 key_base64]

printInfo ["Key used: " ++ byteStringtoCharList keyB64, "", "---Salsa20 Encrypt"]
printInfo ["", "---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)
-- Nonce
generated_nonce <- generateNonce
printInfo ["Nonce: " ++ byteStringtoCharList (toBase64 generated_nonce)]

putStrLn ("Nonce: " ++ byteStringtoCharList nonceB64)
-- Split the hashed key into two keys of 16 bytes each
let (key1, key2) = splitKey key_hash

-- 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
-- Convert nonce to a list of `[Word32]` that we can use as input in `cryptBlock32Compute`
let nonce = word8ListToWord32List generated_nonce

let nonceAsList = word8ToWord32 (head nonces)
-- Convert the mesage to a list of `[Word32]` that we can use as input in `cryptBlock32Compute`
--let message = intToWord32 $ stringToBytes message_string
let message = stringToWord32List message_string

-- 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
-- Call encryption function.
let v2encrypted = cryptBlock32Compute message key1 key2 nonce 0

let b64_cipher = Data.ByteString.Base64.encode $ Data.ByteString.pack (word32ToWord8 v2encrypted)
-- Encode result
let b64_cipher = toBase64 (word32ListToWord8List 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)

-- Decrypt
let v2decrypted = cryptBlock32Compute v2encrypted key1 key2 nonce 0
let b64_decrypted = word32ListToByteString v2decrypted
putStrLn ("Decrypted: " ++ byteStringtoCharList b64_decrypted)

return ()

where
getInput prompt = do
putStrLn prompt
getLine
getInput :: String -> IO String
getInput prompt = do
putStrLn prompt
getLine

printInfo :: [String] -> IO ()
printInfo = mapM_ putStrLn

printInfo = mapM_ putStrLn
-- | Converts a string to a ByteString using ASCII encoding.
stringToByteString :: String -> B.ByteString
stringToByteString = B.pack . map (fromIntegral . ord)

stringToBytes :: String -> [Int]
stringToBytes = map ord
-- | Converts a string to a list of Word32.
stringToWord32List :: String -> [Word32]
stringToWord32List = map fromIntegral . map ord

intToWord32 :: [Int] -> [Word32]
intToWord32 = map fromIntegral
-- | Converts a list of Word32 back to a ByteString.
word32ListToByteString :: [Word32] -> B.ByteString
word32ListToByteString = B.pack . map fromIntegral

intToWord8 :: [Int] -> [Word8]
intToWord8 = map fromIntegral
-- | Converts a ByteString to a human-readable character list.
byteStringtoCharList :: B.ByteString -> [Char]
byteStringtoCharList = map (toEnum . fromEnum) . B.unpack

byteStringtoCharList :: Data.ByteString.ByteString -> [Char]
byteStringtoCharList = map (toEnum . fromEnum) . Data.ByteString.unpack
-- | Converts a list of Word8 to a list of Word32.
word8ListToWord32List :: [Word8] -> [Word32]
word8ListToWord32List = map fromIntegral

word8ToWord32 :: [Word8] -> [Word32]
word8ToWord32 = map fromIntegral
-- | Converts a list of Word32 back to a list of Word8.
word32ListToWord8List :: [Word32] -> [Word8]
word32ListToWord8List = map fromIntegral

-- | Hashes a string using SHA256 and returns a list of Word8.
keyHash :: String -> [Word8]
keyHash key = B.unpack $ SHA256.hash (stringToByteString key)

-- | Encodes a list of Word8 into a Base64 ByteString.
toBase64 :: [Word8] -> B.ByteString
toBase64 bytes = B64.encode $ B.pack bytes

-- | Generates a cryptographic nonce.
{-@ ignore generateNonce @-}
generateNonce :: IO [Word8]
generateNonce = do
-- Generate nonce
g <- Crypto.Nonce.new
nonce <- Crypto.Nonce.nonce128 g

let unpackedNonce = B.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
return $ head nonces

word32ToWord8 :: [Word32] -> [Word8]
word32ToWord8 = map fromIntegral
-- | Splits a hashed key into two separate keys, each being a list of Word32.
splitKey :: [Word8] -> ([Word32], [Word32])
splitKey key_hash = do
let keys = Data.List.Split.chunksOf 16 key_hash
(word8ListToWord32List $ keys!!0, word8ListToWord32List $ keys!!1)

0 comments on commit 1093c75

Please sign in to comment.