From 1093c757cf667d403bce77fa0a887abeeb4bcac1 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Sun, 14 Apr 2024 12:05:48 -0300 Subject: [PATCH] improve a bit the app --- README.md | 28 ++++++++++- app/Main.hs | 138 ++++++++++++++++++++++++++++++++-------------------- 2 files changed, 112 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index c442d99..46ec4e3 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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. diff --git a/app/Main.hs b/app/Main.hs index 8fa7e07..6cf3166 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -5,8 +5,11 @@ 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 -} @@ -14,10 +17,10 @@ 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 @@ -25,76 +28,105 @@ 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)