ImageMagick: Reversible Image Masking for lossy image formats



A demonstration of an image masking procedure using ImageMagick, presented as a Bash script. The concept is to obfuscate an image such that it becomes meaningless to the observer (human or machine) but that can be easily recovered using the correct steps. Since it is based on visual alterations rather than altering the file itself, it does not suffer from informational corruption if the image is resaved and/or resized.

In its current form, it's deemed as weakly cryptographic. It could be reversed via brute force study of patterns and edges (as was seen with VideoCrypt, the analogue video encryption used with satellite television¹). Image masking is an old idea that may still have some value today. Further notes are found within the bash script below.
#!/bin/bash
# Demo implementation of reversible image obfuscation for lossy file formats 
# (jpeg), based on ImageMagick[6] command chains.
#
# USAGE: imageMask.sh ['hide'/'recover'] ['image']
#        (Images cropped to multiples of 64 in this implementation.)
#
# * See 'NOTES' at bottom of script for further information and ideas.
#
# N.B. Regarding cryptography: reversible by brute force, edge-analysis ,etc.
# Designed for privacy from casual scanning (human/machine). Inspired by
# previously developed image masking systems: (GMask, JMask, VideoCrypt, etc.)
#
# Source: https://oioiiooixiii.blogspot.com
# Version: 2018.02.19.05.02.27

function obsfucate() # Takes: 'filename', 'width', and 'height'
{
   local width="$2" height="$3"
   
   # Crop into 64x64 blocks, rotate 90 degrees, and negate 1/4.
   # Tile blocks in reversed image orientation (Height x Width).
   # Crop into 16x16 blocks, rotate 90 degrees, and negative 1/4.
   # Tile blocks in reversed image orientation (Height x Width).
   # One extra 'rotate' at end to return to original orientation.
   
   convert "$1" -crop 64x64 -rotate 90  \
      \( +repage -region 32x32+0+0 -negate \) miff:- \
   | montage miff:- -mode concatenate \
      -tile "$((height/64))"x"$((width/64))" miff:- \
   | convert miff:- -crop 16x16 -rotate 90 \
      \( +repage -region 8x8+0+0 -negate \)  miff:- \
   | montage miff:- -mode concatenate \
      -tile "$((height/16))"x"$((width/16))" miff:- \
   | convert miff:- -rotate 90 ${1%.*}_HIDDEN.jpg
   
}

function deobfuscate() # Takes: 'filename', 'width', and 'height'
{
   local width="$3" height="$2"
   # width,height values swapped, 270 rotate to match 'obfuscate' re-orientation
   
   convert "$1" -rotate 270 -crop 64x64 -rotate 270 \
      \( +repage -region 32x32+0+32 -negate \) miff:- \
   | montage miff:- -mode concatenate \
      -tile "$((height/64))"x"$((width/64))"  miff:- \
   | convert miff:- -crop 16x16 -rotate 270 \
      \( +repage -region 8x8+8+8 -negate \) miff:- \
   | montage miff:- -mode concatenate \
      -tile "$((height/16))"x"$((width/16))" ${1%.*}_RECOVERED.jpg
}

function main()
{
   local width="$(identify -format "%w" "$2")"
   local height="$(identify -format "%h" "$2")"    

   # Crude method of making the image dimensions multiples of 64
   if [[ "$((width%64))" -gt 0 || "$((height%64))" -gt 0 ]]
   then
      local width="$(((width/64)*64))"
      local height="$(((height/64)*64))"
      convert "$2" -crop "$width"x"$height"+0+0 +repage "${2%.*}_CROPPED.png"
      local filename="${2%.*}_CROPPED.png"
   fi

   [[ "$1" == "hide" ]] && obsfucate "${filename:-$2}" "$width" "$height"
   [[ "$1" == "recover" ]] && deobfuscate "${filename:-$2}" "$width" "$height"
}

main "$@"
exit

### NOTES ###################################################################

# The command chain 'algorithm' demonstrated here, is just one particular way of
# rearranging an image, using rotation, negation, and altering aspect ratios. 
# More complex chaining as well as extra measures will result in more obscurity.
# Saving files at interim stage and reordering blocks allows for greater 
# manipulation and security (e.g. unique block ordering based on pass phrases). 
#
# Advantages and uses: survives rescaling and re-compression, with minimal 
# additional losses due to principles of DCT quantisation. It allows for images 
# to be stored on-line using public/private 'cloud' services that destroy 
# cryptographic information by rescaling/compressing the image. Reversible via 
# alternate means (e.g. Python PIL etc.) if software becomes unavailable. 
# Cons: Relatively slow, cumbersome, non-dynamic way to browse images.
#
# A side-effect of the procedure is the removal of EXIF information from the 
# image, thus no need for including the '-strip' argument such was desired.
download: imageMask.sh




To show the differences created when the image is resaved [with heavy compression] while in a state of obfuscation, the following was completed: The image was masked and saved as a jpeg with quality set to '25'. The original image was also saved as a jpeg with quality set to '25'. Both of these images were 'differenced' with the original, and the result of each were 'differenced' with each other. This image was then normailised for clarity. N.B. "Difference" does not imply quality loss but variance in compression artifacting.

Below left: Differences between original and image that underwent obfuscation then deobfuscation.
Below right: Differences [normalised] between heavily compressed images, as mentioned above.



more info: http://gmask.awardspace.info/
see also: http://oioiiooixiii.blogspot.com/2014/03/a-novel-approach-to-encrypting-images.html
related: http://oioiiooixiii.blogspot.com/2014/11/illegal-pixels.html
related: https://oioiiooixiii.blogspot.com/2015/05/creating-two-images-from-one-set-of.html
related: https://oioiiooixiii.blogspot.com/2015/06/gimpmask-encrypting-all-or-part-of-image.html
image source: c⃠  https://images.nasa.gov/details-201409250001HQ.html
image source: c⃠  https://commons.wikimedia.org/wiki/File:Moscow,_Krasnaya_Square,_Sunset.jpg

¹ http://www.cl.cam.ac.uk/~mgk25/tv-crypt/image-processing/antisky.html
¹ http://www.techmind.org/vdc/
¹ https://guru.multimedia.cx/decrypting-videocrypt/