#!/bin/sh # BitShade: a simple POSIX-compatible tool for # image-based steganography # Depends on od, paste, dd, awk, sed, ImageMagick and pngcrush # Usage: # - encoding: bitshade.sh e [cover image] [file to hide] [output PNG] # - decoding: bitshade.sh d [container PNG] [output file] # Created by Luxferre in 2026, released into public domain # encode raw file as a netstring to_netstring() { printf '%d:' "$(cat "$1" | wc -c)" cat "$1" printf ',' } # decode a raw file from a netstring (inside another file) from_netstring() { # get the container length flen=$(head -n 1 "$1" | cut -f 1 -d :) # skip the length itself and the : char dd if="$1" of="$2" skip=$((${#flen} + 1)) bs=1 count=$flen } # for every input byte, print its bits conv_input() { to_netstring "$1" | od -An -tu1 -v -w1 | awk '{n=int($0);for(i=0;i<8;i++){print(n%2);n=int(n/2)}}' } # assemble a file bit by bit collect_bits() { outfile="$1" tfile2=$(mktemp) tfile3=$(mktemp) awk 'BEGIN{n=0;b=1} {n+=b*int($1);b*=2;if(b==256){printf("\\%03o",n);n=0;b=1}}' > $tfile2 printf "$(cat $tfile2)" > $tfile3 from_netstring $tfile3 "$1" rm -f $tfile2 $tfile3 } # stego_encode cover_image hidden_text out.png stego_encode() { coverfile="$(mktemp)" infile="$(mktemp)" tfile="$(mktemp)" temp_ppm="$(mktemp)" magick $1 -compress none ppm:$temp_ppm cat $temp_ppm | head -n 3 > $tfile cat $temp_ppm | tail -n +4 | sed 's/\s\+/\n/g' > $coverfile conv_input "$2" > $infile paste $coverfile $infile | awk '{print(NF>1?int(int($1)/4)*4+int($2)*2+(rand()>0.5?1:0):$1)}' >> $tfile magick ppm:$tfile png:$3 pngcrush -ow $3 rm -f $coverfile $infile $temp_ppm $tfile } # stego_decode in.png out_file stego_decode() { temp_ppm="$(mktemp)" magick $1 -compress none ppm:$temp_ppm cat $temp_ppm | tail -n +4 | sed 's/\s\+/\n/g' | awk '{x=int($0)%4;print(x<2?0:1)}' | collect_bits "$2" rm -f $temp_ppm } # entry point if [ "$1" = "e" ]; then [ -z "$2" ] && echo 'No cover file specified! Exiting...' && exit 1 [ -z "$3" ] && echo 'No input file specified! Exiting...' && exit 1 [ -z "$4" ] && echo "No output file specified! Exiting..." && exit 1 stego_encode "$2" "$3" "$4" elif [ "$1" = "d" ]; then [ -z "$2" ] && echo 'No input file specified! Exiting...' && exit 1 [ -z "$3" ] && echo "No output file specified! Exiting..." && exit 1 stego_decode "$2" "$3" else echo 'Invalid mode! Use e for encoding or d for decoding' fi