initial commit
This commit is contained in:
commit
9e2fecffbe
11 changed files with 650 additions and 0 deletions
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# editors
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
# direnv
|
||||||
|
.direnv
|
||||||
|
.envrc
|
||||||
|
# nix
|
||||||
|
/result
|
5
CHANGELOG.md
Normal file
5
CHANGELOG.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# Revision history for picsg
|
||||||
|
|
||||||
|
## 0.1.0.0 -- 2024-04-13
|
||||||
|
|
||||||
|
- First version. Released on an unsuspecting world.
|
30
LICENSE
Normal file
30
LICENSE
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
Copyright Author name here (c) 2022
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of Author name here nor the names of other
|
||||||
|
contributors may be used to endorse or promote products derived
|
||||||
|
from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
27
README.md
Normal file
27
README.md
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# picsg
|
||||||
|
|
||||||
|
A tool for steganographing information in a picture encoded using the Vernam
|
||||||
|
cipher.
|
||||||
|
|
||||||
|
## Synopsis
|
||||||
|
|
||||||
|
```sh
|
||||||
|
picsg [subcommand]
|
||||||
|
```
|
||||||
|
|
||||||
|
where subcommand is one of the following:
|
||||||
|
|
||||||
|
- `encode <text> <output-path> [--img <image-path>]`
|
||||||
|
- `encode file <file-path> <output-path> [--img <image-path>]`
|
||||||
|
- `decode [--img] <encoded-path> <key-path> <output-path>`
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
- Encode text using the Vernam cipher to the file `hello.enc` and create a file
|
||||||
|
`hello.enc.key` with a key to decode the `hello.enc` file.
|
||||||
|
|
||||||
|
`picsg encode "ABC" hello.enc`
|
||||||
|
|
||||||
|
- Decode the `hello.enc` file using key and print to the standard output
|
||||||
|
|
||||||
|
`picsg decode hello.enc hello.enc.key -`
|
2
Setup.hs
Normal file
2
Setup.hs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
import Distribution.Simple
|
||||||
|
main = defaultMain
|
138
app/DecodeSteg.hs
Normal file
138
app/DecodeSteg.hs
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
{-| Module for decoding the information hidden in the image using LSB steganography method
|
||||||
|
-}
|
||||||
|
module DecodeSteg (
|
||||||
|
-- * Functions required for decoding information hidden in image
|
||||||
|
getWidth, getHeight, getRed, getGreen, getBlue
|
||||||
|
, getTotalNumPixels, getTotalBits, getPixelsforMessage
|
||||||
|
, getOptimumBits, readPixelBit, readBitOfImage, readByte
|
||||||
|
, readFileName, readAllBytes, decodeImg ) where
|
||||||
|
|
||||||
|
import Codec.Picture
|
||||||
|
import Data.Bits
|
||||||
|
import Data.Char
|
||||||
|
import Data.Word
|
||||||
|
import qualified Data.ByteString.Lazy as B
|
||||||
|
import qualified Data.ByteString.Internal as BI
|
||||||
|
|
||||||
|
-- | Obtain width of image
|
||||||
|
getWidth :: Image a -> Int
|
||||||
|
getWidth (Image w _ _) = w
|
||||||
|
|
||||||
|
-- | Obtain height of image
|
||||||
|
getHeight :: Image a -> Int
|
||||||
|
getHeight (Image _ h _) = h
|
||||||
|
|
||||||
|
-- | Obtain 8 bit red channel values from RGB8pixel of image
|
||||||
|
getRed :: PixelRGB8 -> Pixel8
|
||||||
|
getRed (PixelRGB8 r _ _) = r
|
||||||
|
|
||||||
|
-- | Obtain 8 bit green channel values from RGB8pixel of image
|
||||||
|
getGreen :: PixelRGB8 -> Pixel8
|
||||||
|
getGreen (PixelRGB8 _ g _) = g
|
||||||
|
|
||||||
|
-- | Obtain 8 bit blue channel values from RGB8pixel of image
|
||||||
|
getBlue :: PixelRGB8 -> Pixel8
|
||||||
|
getBlue (PixelRGB8 _ _ b) = b
|
||||||
|
|
||||||
|
-- | Get total number of pixels available to hide information in image
|
||||||
|
getTotalNumPixels :: Image PixelRGB8 -> Int
|
||||||
|
getTotalNumPixels img = getWidth img * getHeight img - 64 - 1
|
||||||
|
|
||||||
|
-- | Get total number of bits in which information can be hidden
|
||||||
|
getTotalBits :: Image PixelRGB8 -> Int -> Int
|
||||||
|
getTotalBits img bitsPerPixel = (getWidth img * getHeight img - 64 - 1)
|
||||||
|
* bitsPerPixel * 3
|
||||||
|
|
||||||
|
-- | Get number of pixels in which information is hidden
|
||||||
|
getPixelsforMessage :: Int -> Int -> Int
|
||||||
|
getPixelsforMessage len bitsPerPixel = div (len * 8) (bitsPerPixel * 3)
|
||||||
|
|
||||||
|
-- | Get optimum number of last significant bits in pixel to hide the information in image
|
||||||
|
getOptimumBits :: Image PixelRGB8 -> Int -> Int
|
||||||
|
getOptimumBits img bytes = max
|
||||||
|
(ceiling ((toRational (bytes * 8))
|
||||||
|
/ (toRational ((getTotalNumPixels img) * 3)))) 1
|
||||||
|
|
||||||
|
-- | A delimiter used to separate file name and its data in encoded image
|
||||||
|
nullWord8 = fromIntegral 0
|
||||||
|
|
||||||
|
-- | Test whether the pixel at given index is 0 or 1
|
||||||
|
readPixelBit :: Pixel8 -> Int -> Bool
|
||||||
|
readPixelBit px idx = testBit px idx
|
||||||
|
|
||||||
|
-- | Identifies which color channel of pixel is to be read and returns the bit at that color channel
|
||||||
|
readBitOfImage :: Image PixelRGB8 -> Int -> Int -> Int -> Int -> Double -> Bool
|
||||||
|
readBitOfImage img bitsPerPixel byteIdx bitIdx offset period
|
||||||
|
| color == 0 = readPixelBit (getRed (pixelAt img px py)) lsbIdx
|
||||||
|
| color == 1 = readPixelBit (getGreen (pixelAt img px py)) lsbIdx
|
||||||
|
| color == 2 = readPixelBit (getBlue (pixelAt img px py)) lsbIdx
|
||||||
|
where
|
||||||
|
pos = floor (fromIntegral (byteIdx * 8 + bitIdx) * period)
|
||||||
|
|
||||||
|
pixIdx = div pos (bitsPerPixel * 3) + offset
|
||||||
|
|
||||||
|
px = mod pixIdx (getWidth img)
|
||||||
|
|
||||||
|
py = div pixIdx (getWidth img)
|
||||||
|
|
||||||
|
color = div (mod pos (bitsPerPixel * 3)) bitsPerPixel
|
||||||
|
|
||||||
|
lsbIdx = mod (mod pos (bitsPerPixel * 3)) bitsPerPixel
|
||||||
|
|
||||||
|
{-| Converts a sequence of bits to Word8
|
||||||
|
-}
|
||||||
|
boolToWord8 :: [ Bool ] -> Word8
|
||||||
|
boolToWord8 = foldl (\byte bit -> byte * 2 + if bit then 1 else 0) 0
|
||||||
|
|
||||||
|
-- | Reads all the 8 bits of a byte (a character at given index of hidden information)
|
||||||
|
readByte :: Image PixelRGB8 -> Int -> Int -> Int -> Double -> Word8
|
||||||
|
readByte img idx lsb offset period = boolToWord8
|
||||||
|
[ readBitOfImage img lsb idx i offset period | i <- reverse [ 0 .. 7 ] ]
|
||||||
|
|
||||||
|
-- | Reads the lsb bits of pixel from given starting index in which filename is stored till nullWord8 is encountered (delimiter)
|
||||||
|
readFileName :: Image PixelRGB8 -> Int -> Int -> Int -> Double -> [ Word8 ]
|
||||||
|
readFileName img start lsb offset period
|
||||||
|
| byte == nullWord8 = []
|
||||||
|
| otherwise = [ byte ] ++ readFileName img (start + 1) lsb offset period
|
||||||
|
where
|
||||||
|
byte = readByte img start lsb offset period
|
||||||
|
|
||||||
|
-- | Read all the bytes of image in which information is hidden
|
||||||
|
readAllBytes
|
||||||
|
:: Image PixelRGB8 -> Int -> Int -> Int -> Int -> Double -> [ Word8 ]
|
||||||
|
readAllBytes img start lsb len offset period
|
||||||
|
| len <= 0 = []
|
||||||
|
| otherwise = (readByte img start lsb offset period)
|
||||||
|
: (readAllBytes img (start + 1) lsb (len - 1) offset period)
|
||||||
|
|
||||||
|
{- | Convert Word8 to Int32
|
||||||
|
-}
|
||||||
|
word8ToInt32 :: [ Word8 ] -> Int
|
||||||
|
word8ToInt32 [] = 0
|
||||||
|
word8ToInt32 [ x ] = fromIntegral x
|
||||||
|
word8ToInt32 (x : xs) = (fromIntegral x) + shiftL (word8ToInt32 xs) 8
|
||||||
|
|
||||||
|
{- | Convert a sequence of Word8 to string.
|
||||||
|
-}
|
||||||
|
word8ListToString :: [ Word8 ] -> String
|
||||||
|
word8ListToString list = map BI.w2c list
|
||||||
|
|
||||||
|
-- | Decrypt (read) bytes from file in which filename, filepath, information is hidden and writes it in a file
|
||||||
|
decodeImg :: Image PixelRGB8 -> IO [ Word8 ]
|
||||||
|
decodeImg img = return file
|
||||||
|
where
|
||||||
|
len = fromIntegral
|
||||||
|
(word8ToInt32 (readAllBytes img 0 1 4 0
|
||||||
|
(63 / (fromIntegral (getPixelsforMessage 4 1)))))
|
||||||
|
|
||||||
|
bitsPerPixel = getOptimumBits img len
|
||||||
|
|
||||||
|
name = readFileName img 0 bitsPerPixel 64
|
||||||
|
(fromIntegral (getTotalBits img bitsPerPixel) / fromIntegral (len * 8))
|
||||||
|
|
||||||
|
filePath = word8ListToString name
|
||||||
|
|
||||||
|
file = readAllBytes img (length name + 1) bitsPerPixel
|
||||||
|
(len - (length name + 1)) 64
|
||||||
|
((fromIntegral (getTotalBits img bitsPerPixel))
|
||||||
|
/ (fromIntegral (len * 8)))
|
137
app/EncodeSteg.hs
Normal file
137
app/EncodeSteg.hs
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
{-| Module for encoding (hiding) text file or image into another image using LSB Steganography.
|
||||||
|
-}
|
||||||
|
module EncodeSteg ( getWidth, getHeight, getRed, getGreen, getBlue
|
||||||
|
, getTotalNumPixels, getTotalBits, getPixelsforMessage
|
||||||
|
, getMinBits, getChangedPixel, encodeImage
|
||||||
|
, writeBitToImage ) where
|
||||||
|
|
||||||
|
import Codec.Picture
|
||||||
|
import Control.Monad.ST
|
||||||
|
import Data.Bits
|
||||||
|
import qualified Codec.Picture.Types as M
|
||||||
|
import Data.Word
|
||||||
|
|
||||||
|
{-| Returns width of image.
|
||||||
|
-}
|
||||||
|
getWidth :: Image a -> Int
|
||||||
|
getWidth (Image w _ _) = w
|
||||||
|
|
||||||
|
{-| Returns height of image.
|
||||||
|
-}
|
||||||
|
getHeight :: Image a -> Int
|
||||||
|
getHeight (Image _ h _) = h
|
||||||
|
|
||||||
|
{-| Returns red channel values from pixel
|
||||||
|
-}
|
||||||
|
getRed :: PixelRGB8 -> Pixel8
|
||||||
|
getRed (PixelRGB8 r _ _) = r
|
||||||
|
|
||||||
|
{-| Returns green channel values from pixel
|
||||||
|
-}
|
||||||
|
getGreen :: PixelRGB8 -> Pixel8
|
||||||
|
getGreen (PixelRGB8 _ g _) = g
|
||||||
|
|
||||||
|
{-| Returns blue channel values from pixel
|
||||||
|
-}
|
||||||
|
getBlue :: PixelRGB8 -> Pixel8
|
||||||
|
getBlue (PixelRGB8 _ _ b) = b
|
||||||
|
|
||||||
|
{-| Returns total number of pixels
|
||||||
|
-}
|
||||||
|
getTotalNumPixels :: Image PixelRGB8 -> Int
|
||||||
|
getTotalNumPixels img = (getWidth img) * (getHeight img) - 64 - 1
|
||||||
|
|
||||||
|
{-| Returns total number of bits
|
||||||
|
-}
|
||||||
|
getTotalBits :: Image PixelRGB8 -> Int -> Int
|
||||||
|
getTotalBits img bitsPerPixel = ((getWidth img) * (getHeight img) - 64 - 1)
|
||||||
|
* bitsPerPixel * 3
|
||||||
|
|
||||||
|
{-| Returns total number of pixels to store message
|
||||||
|
-}
|
||||||
|
getPixelsforMessage :: Int -> Int -> Int
|
||||||
|
getPixelsforMessage len bitsPerPixel = div (len * 8) (bitsPerPixel * 3)
|
||||||
|
|
||||||
|
{-| Returns minimum bits to modify required per pixel color channel
|
||||||
|
-}
|
||||||
|
getMinBits :: Image PixelRGB8 -> Int -> Int
|
||||||
|
getMinBits img bytes = max
|
||||||
|
(ceiling ((toRational (bytes * 8))
|
||||||
|
/ (toRational ((getTotalNumPixels img) * 3)))) 1
|
||||||
|
|
||||||
|
{-| Modifies pixel 'pix' based on value of 'b'.
|
||||||
|
bit i is a value with the ith bit set and all other bits clear.
|
||||||
|
-}
|
||||||
|
getChangedPixel :: Pixel8 -> Int -> Bool -> Pixel8
|
||||||
|
getChangedPixel pix x b
|
||||||
|
| b = pix .|. bit x
|
||||||
|
| not b = pix .&. complement (bit x)
|
||||||
|
|
||||||
|
{- | Converts an integer to sequence of bits.
|
||||||
|
testBit returns True if the nth bit of the argument is 1.
|
||||||
|
-}
|
||||||
|
intToBits :: Bits a => a -> Int -> [ Bool ]
|
||||||
|
intToBits x idx = map (testBit x) [ 0 .. idx - 1 ]
|
||||||
|
|
||||||
|
{- | Convert Int32 to Word8
|
||||||
|
-}
|
||||||
|
int32ToWord8 :: Int -> [ Word8 ]
|
||||||
|
int32ToWord8 x = map fromIntegral
|
||||||
|
[ (shiftR x (pos * 8)) .&. 255 | pos <- [ 0, 1, 2, 3 ] ]
|
||||||
|
|
||||||
|
{-| Hides data 'message' in image.
|
||||||
|
Returns new image similar to old image but with data hidden inside it.
|
||||||
|
-}
|
||||||
|
encodeImage :: Image PixelRGB8 -> Int -> [ Word8 ] -> Either String
|
||||||
|
(Image PixelRGB8)
|
||||||
|
encodeImage img bitsPerPixel message
|
||||||
|
| bitsPerPixel <= 8 = Right
|
||||||
|
(runST $ do
|
||||||
|
newimg <- M.unsafeThawImage img
|
||||||
|
let modifyBits _i [] _start _b _period = M.freezeImage newimg
|
||||||
|
modifyBits i (x : xs) start b period = do
|
||||||
|
writeBitToImage img newimg b i 0 (bits !! 0) start period
|
||||||
|
writeBitToImage img newimg b i 1 (bits !! 1) start period
|
||||||
|
writeBitToImage img newimg b i 2 (bits !! 2) start period
|
||||||
|
writeBitToImage img newimg b i 3 (bits !! 3) start period
|
||||||
|
writeBitToImage img newimg b i 4 (bits !! 4) start period
|
||||||
|
writeBitToImage img newimg b i 5 (bits !! 5) start period
|
||||||
|
writeBitToImage img newimg b i 6 (bits !! 6) start period
|
||||||
|
writeBitToImage img newimg b i 7 (bits !! 7) start period
|
||||||
|
modifyBits (i + 1) xs start b period
|
||||||
|
where
|
||||||
|
bits = intToBits (x) 8
|
||||||
|
modifyBits 0 (int32ToWord8 len) 0 1
|
||||||
|
(63 / (fromIntegral (getPixelsforMessage 4 1)))
|
||||||
|
modifyBits 0 message 64 bitsPerPixel
|
||||||
|
((toRational (getTotalBits img bitsPerPixel))
|
||||||
|
/ (toRational (len * 8))))
|
||||||
|
| otherwise = Left "Too long information to be encoded!!!"
|
||||||
|
where
|
||||||
|
len = length message
|
||||||
|
|
||||||
|
{-| Writes data to image based on bits of message.
|
||||||
|
-}
|
||||||
|
writeBitToImage orig img bitsPerPixel byteIdx bitIdx bitVal offset period
|
||||||
|
| color == 0 = M.writePixel img px py
|
||||||
|
(PixelRGB8 (getChangedPixel (getRed (pixelAt orig px py)) lsbIdx bitVal)
|
||||||
|
(getGreen (pixelAt orig px py)) (getBlue (pixelAt orig px py)))
|
||||||
|
| color == 1 = M.writePixel img px py
|
||||||
|
(PixelRGB8 (getRed (pixelAt orig px py))
|
||||||
|
(getChangedPixel (getGreen (pixelAt orig px py)) lsbIdx bitVal)
|
||||||
|
(getBlue (pixelAt orig px py)))
|
||||||
|
| color == 2 = M.writePixel img px py
|
||||||
|
(PixelRGB8 (getRed (pixelAt orig px py)) (getGreen (pixelAt orig px py))
|
||||||
|
(getChangedPixel (getBlue (pixelAt orig px py)) lsbIdx bitVal))
|
||||||
|
where
|
||||||
|
pos = floor (fromIntegral (byteIdx * 8 + bitIdx) * period)
|
||||||
|
|
||||||
|
p = (div pos (bitsPerPixel * 3)) + offset
|
||||||
|
|
||||||
|
px = mod p (getWidth orig)
|
||||||
|
|
||||||
|
py = div p (getWidth orig)
|
||||||
|
|
||||||
|
color = (div (mod pos (bitsPerPixel * 3)) bitsPerPixel)
|
||||||
|
|
||||||
|
lsbIdx = (mod (mod pos (bitsPerPixel * 3)) bitsPerPixel)
|
114
app/Main.hs
Normal file
114
app/Main.hs
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
{-# LANGUAGE LambdaCase #-}
|
||||||
|
|
||||||
|
module Main where
|
||||||
|
|
||||||
|
import Codec.Picture
|
||||||
|
import Data.Bits (xor)
|
||||||
|
import qualified Data.ByteString.Lazy as BL
|
||||||
|
import qualified Data.ByteString.Lazy.UTF8 as BLU
|
||||||
|
import Data.Char (chr, ord)
|
||||||
|
import Data.Word (Word8)
|
||||||
|
import DecodeSteg
|
||||||
|
import EncodeSteg
|
||||||
|
import System.Environment (getArgs)
|
||||||
|
import System.Random (randomRIO)
|
||||||
|
|
||||||
|
generateKey :: Int -> IO [ Int ]
|
||||||
|
generateKey n = sequence $ replicate n $ randomRIO ( 0, 65535 )
|
||||||
|
|
||||||
|
encodeText :: String -> [ Word8 ] -> IO [ Word8 ]
|
||||||
|
encodeText keypath content = do
|
||||||
|
key <- generateKey . length $ content
|
||||||
|
BL.writeFile keypath $ BL.pack (map fromIntegral key)
|
||||||
|
return $ map (\( a, b ) -> a `xor` b) $ zip content (map fromIntegral key)
|
||||||
|
|
||||||
|
decodeText :: [ Word8 ] -> [ Word8 ] -> [ Word8 ]
|
||||||
|
decodeText encodedText key = map (\( a, b ) -> a `xor` b) $ zip encodedText key
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = getArgs >>= \case
|
||||||
|
[ "encode", "file", filepath, encpath, "--img", imgpath ] -> do
|
||||||
|
let keypath = encpath ++ ".key"
|
||||||
|
filecontent <- BL.unpack <$> BL.readFile filepath
|
||||||
|
encoded <- encodeText keypath filecontent
|
||||||
|
readImage imgpath >>= \case
|
||||||
|
Left err -> putStrLn err
|
||||||
|
Right image -> do
|
||||||
|
let conv = (convertRGB8 image)
|
||||||
|
len = (length filepath) + 1 + (length encoded)
|
||||||
|
bitsPerPixel = getMinBits conv len
|
||||||
|
message = (BL.unpack . BLU.fromString $ filepath)
|
||||||
|
++ [ fromIntegral 0 ] ++ encoded
|
||||||
|
finalimg = (encodeImage conv bitsPerPixel message)
|
||||||
|
case finalimg of
|
||||||
|
Left errorStr -> putStrLn errorStr
|
||||||
|
Right encrypted -> do
|
||||||
|
savePngImage encpath $ ImageRGB8 encrypted
|
||||||
|
putStrLn ("Done")
|
||||||
|
[ "encode", "file", filepath, encpath ] -> do
|
||||||
|
let keypath = encpath ++ ".key"
|
||||||
|
filecontent <- BL.unpack <$> BL.readFile filepath
|
||||||
|
encoded <- encodeText keypath filecontent
|
||||||
|
BL.writeFile encpath $ BL.pack $ encoded
|
||||||
|
[ "encode", text, encpath, "--img", imgpath ] -> do
|
||||||
|
let keypath = encpath ++ ".key"
|
||||||
|
encoded <- encodeText keypath $ BL.unpack . BLU.fromString $ text
|
||||||
|
readImage imgpath >>= \case
|
||||||
|
Left err -> putStrLn err
|
||||||
|
Right image -> do
|
||||||
|
let conv = (convertRGB8 image)
|
||||||
|
len = 1 + (length encoded)
|
||||||
|
bitsPerPixel = getMinBits conv len
|
||||||
|
message = [ fromIntegral 0 ] ++ encoded
|
||||||
|
finalimg = (encodeImage conv bitsPerPixel message)
|
||||||
|
case finalimg of
|
||||||
|
Left errorStr -> putStrLn errorStr
|
||||||
|
Right encrypted -> do
|
||||||
|
savePngImage encpath $ ImageRGB8 encrypted
|
||||||
|
putStrLn ("Done")
|
||||||
|
[ "encode", text, encpath ] -> do
|
||||||
|
let keypath = encpath ++ ".key"
|
||||||
|
encoded <- encodeText keypath $ BL.unpack . BLU.fromString $ text
|
||||||
|
BL.writeFile encpath $ BL.pack $ encoded
|
||||||
|
[ "decode", "--img", imgpath, keypath, decodepath ] -> do
|
||||||
|
readImage imgpath >>= \case
|
||||||
|
Left err -> putStrLn err
|
||||||
|
Right image -> do
|
||||||
|
encodedText <- decodeImg . convertRGB8 $ image
|
||||||
|
key <- BL.unpack <$> BL.readFile keypath
|
||||||
|
printOrWriteFile decodepath $ BL.pack
|
||||||
|
$ decodeText encodedText key
|
||||||
|
[ "decode", encpath, keypath, decodepath ] -> do
|
||||||
|
encodedText <- BL.unpack <$> BL.readFile encpath
|
||||||
|
key <- BL.unpack <$> BL.readFile keypath
|
||||||
|
printOrWriteFile decodepath $ BL.pack $ decodeText encodedText key
|
||||||
|
_ -> help
|
||||||
|
where
|
||||||
|
printOrWriteFile filepath
|
||||||
|
= if filepath == "-" then print else BL.writeFile filepath
|
||||||
|
|
||||||
|
help :: IO ()
|
||||||
|
help = do
|
||||||
|
putStrLn "Name"
|
||||||
|
putStrLn ""
|
||||||
|
putStrLn " picsg - a tool for steganographing information in a picture encoded using the Vernam cipher."
|
||||||
|
putStrLn ""
|
||||||
|
putStrLn "Synopsis"
|
||||||
|
putStrLn ""
|
||||||
|
putStrLn " picsg [subcommand]"
|
||||||
|
putStrLn ""
|
||||||
|
putStrLn " where subcommand is one of the following:"
|
||||||
|
putStrLn ""
|
||||||
|
putStrLn " - encode <text> <output-path> [--img <image-path>]"
|
||||||
|
putStrLn " - encode file <file-path> <output-path> [--img <image-path>]"
|
||||||
|
putStrLn " - decode [--img] <encoded-path> <key-path> <output-path>"
|
||||||
|
putStrLn ""
|
||||||
|
putStrLn "Examples"
|
||||||
|
putStrLn ""
|
||||||
|
putStrLn " - Encode text using the Vernam cipher to the file hello.enc and create a file hello.enc.key with a key to decode the hello.enc file."
|
||||||
|
putStrLn ""
|
||||||
|
putStrLn " | picsg encode \"ABC\" hello.enc"
|
||||||
|
putStrLn ""
|
||||||
|
putStrLn " - Decode the hello.enc file using key and print to the standart output"
|
||||||
|
putStrLn ""
|
||||||
|
putStrLn " | picsg decode hello.enc hello.enc.key -"
|
61
flake.lock
Normal file
61
flake.lock
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1710146030,
|
||||||
|
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1712849433,
|
||||||
|
"narHash": "sha256-flQtf/ZPJgkLY/So3Fd+dGilw2DKIsiwgMEn7BbBHL0=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "f173d0881eff3b21ebb29a2ef8bedbc106c86ea5",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
47
flake.nix
Normal file
47
flake.nix
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, flake-utils }:
|
||||||
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
hPkgs = pkgs.haskellPackages;
|
||||||
|
|
||||||
|
myDevTools = with hPkgs; [
|
||||||
|
ghc
|
||||||
|
ghcid
|
||||||
|
ormolu
|
||||||
|
hlint
|
||||||
|
hoogle
|
||||||
|
haskell-language-server
|
||||||
|
implicit-hie
|
||||||
|
retrie
|
||||||
|
];
|
||||||
|
|
||||||
|
picsg = pkgs.haskellPackages.developPackage {
|
||||||
|
root = ./.;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
|
||||||
|
packages = {
|
||||||
|
default = picsg;
|
||||||
|
picsg = picsg;
|
||||||
|
docker = pkgs.dockerTools.buildImage {
|
||||||
|
name = "picgs";
|
||||||
|
config = {
|
||||||
|
Cmd = [ "${picsg}/bin/picsg" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
buildInputs = myDevTools ++ [ hPkgs.cabal-install ];
|
||||||
|
|
||||||
|
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath myDevTools;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
80
picsg.cabal
Normal file
80
picsg.cabal
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
cabal-version: 3.0
|
||||||
|
-- The cabal-version field refers to the version of the .cabal specification,
|
||||||
|
-- and can be different from the cabal-install (the tool) version and the
|
||||||
|
-- Cabal (the library) version you are using. As such, the Cabal (the library)
|
||||||
|
-- version used must be equal or greater than the version stated in this field.
|
||||||
|
-- Starting from the specification version 2.2, the cabal-version field must be
|
||||||
|
-- the first thing in the cabal file.
|
||||||
|
|
||||||
|
-- Initial package description 'picsg' generated by
|
||||||
|
-- 'cabal init'. For further documentation, see:
|
||||||
|
-- http://haskell.org/cabal/users-guide/
|
||||||
|
--
|
||||||
|
-- The name of the package.
|
||||||
|
name: picsg
|
||||||
|
|
||||||
|
-- The package version.
|
||||||
|
-- See the Haskell package versioning policy (PVP) for standards
|
||||||
|
-- guiding when and how versions should be incremented.
|
||||||
|
-- https://pvp.haskell.org
|
||||||
|
-- PVP summary: +-+------- breaking API changes
|
||||||
|
-- | | +----- non-breaking API additions
|
||||||
|
-- | | | +--- code changes with no API change
|
||||||
|
version: 0.1.0.0
|
||||||
|
|
||||||
|
-- A short (one-line) description of the package.
|
||||||
|
-- synopsis:
|
||||||
|
|
||||||
|
-- A longer description of the package.
|
||||||
|
-- description:
|
||||||
|
|
||||||
|
-- The license under which the package is released.
|
||||||
|
license: GPL-3.0-or-later
|
||||||
|
|
||||||
|
-- The file containing the license text.
|
||||||
|
license-file: LICENSE
|
||||||
|
|
||||||
|
-- The package author(s).
|
||||||
|
author: Dmitriy Pleshevskiy
|
||||||
|
|
||||||
|
-- An email address to which users can send suggestions, bug reports, and patches.
|
||||||
|
maintainer: dmitriy@pleshevski.ru
|
||||||
|
|
||||||
|
-- A copyright notice.
|
||||||
|
-- copyright:
|
||||||
|
build-type: Simple
|
||||||
|
|
||||||
|
-- Extra doc files to be distributed with the package, such as a CHANGELOG or a README.
|
||||||
|
extra-doc-files: CHANGELOG.md
|
||||||
|
|
||||||
|
-- Extra source files to be distributed with the package, such as examples, or a tutorial module.
|
||||||
|
-- extra-source-files:
|
||||||
|
|
||||||
|
common warnings
|
||||||
|
ghc-options: -Wall
|
||||||
|
|
||||||
|
executable picsg
|
||||||
|
-- Import common warning flags.
|
||||||
|
import: warnings
|
||||||
|
|
||||||
|
-- .hs or .lhs file containing the Main module.
|
||||||
|
main-is: Main.hs
|
||||||
|
|
||||||
|
-- Modules included in this executable, other than Main.
|
||||||
|
-- other-modules:
|
||||||
|
|
||||||
|
-- LANGUAGE extensions used by modules in this package.
|
||||||
|
-- other-extensions:
|
||||||
|
|
||||||
|
-- Other library packages from which modules are imported.
|
||||||
|
build-depends: base ^>=4.18.2.0,
|
||||||
|
utf8-string == 1.0.2,
|
||||||
|
random == 1.2.1.2,
|
||||||
|
bytestring == 0.11.5.3,
|
||||||
|
JuicyPixels == 3.3.8
|
||||||
|
|
||||||
|
-- Directories containing source files.
|
||||||
|
hs-source-dirs: app
|
||||||
|
|
||||||
|
-- Base language which the package is written in.
|
||||||
|
default-language: Haskell2010
|
Reference in a new issue