Graphic Encryption with Visual Basic

Elsewhere in this section we look at a simple encryption program that encodes text messages by substituting each printable character in the ASCII character set with another character, chosen from the first 256 characters of the Unicode character encoding scheme. In this program we are going to encode a text message into a bitmapped image using a very subtle encoding scheme that will make small changes to the image that cannot be detected by the human eye. In fact, the coding is so subtle that providing you choose the right kind of image, it would be extremely difficult for a third party to extract the encoded message without a copy of the program, even if they suspected that the image might contain a hidden message.

The program, as written, does make it rather obvious that the images contain encoded messages, because we have given the encoded image files their own special file extension (.ebm for encoded bitmap). You could just save the image with the normal extension for a bitmap file (.bmp), and no-one would be any the wiser. In fact, the only way to tell the difference between the original bitmap image and the encoded version would be to compare the byte values at the start of the file to see if there are any differences. Note that on average, only about half of the bytes used to encode the message will actually change from their original value, for reasons that will become apparent as you begin to understand how the code works.

Before we proceed, it is probably worth mentioning that what we are doing here is not, strictly speaking, "encryption". The correct term is steganography. Steganography differs from encryption, because the way in which the message is hidden means that only the sender and receiver of the message will be aware that a hidden message exists. By contrast an encrypted message is usually pretty obviously just that, because it consists of a message that is unintelligible to anyone not possessing the necessary key and decryption algorithm.

Stenography encompasses a broad range of techniques used to hide messages, some of which go back several thousand years. Such techniques have included, for example, tattooing a message on a courier's shaved head and allowing the hair to grow again in order to hide the message (slow but effective); the use of invisible ink; and making small changes to the size, spacing and typeface of printed matter in order to convey a hidden subtext.

The computer age has provided countless additional ways in which to hide messages. Digital media files provide excellent hiding places for covert information because of their size. The approach we are taking here is a relatively simple one; we are hiding our messages in bitmapped image files because each pixel in a bitmapped image is preserved in its original state when the image is saved, and does not undergo further encoding of the kind used in other image file formats. There are probably many far more ingenious methods that could be used to hide information in electronic media files.

  1. Open a new project called "GraphicEncryption" and create an interface like the one illustrated below. The images used in the sample screenshots are contained in a zipped file called "graphic_encryption.zip", which can be downloaded here:

graphic_encryption.zip


The GraphicEncryption program interface

The GraphicEncryption program interface


  1. Set the control names and other properties as shown in the table below.

frmGraphicEncryption Form Controls
ControlNameAdditional Properties
FormfrmGraphicEncryptionSize: 520, 600
Text: "GraphicEncryption"
PictureBoxpicImageBorderStyle: Fixed3D
Location: 10, 10
Size: 320, 240
LabellblMessageLocation: 12, 262
Text: None
TextBoxtxtMessageEnabled: False
Location: 10, 280
Multiline: True
ScrollBars: Vertical
Size: 320, 240
LabellblMessageLengthLocation: 10, 534
Text: "Message length: 0 characters"
ButtoncmdLoadBmpLocation: 345, 12
Size: 140, 23
Text: "Load Bitmap Image"
ButtoncmdEncryptEnabled: False
Location: 345, 41
ize: 140, 23
Text: "Encrypt Data"
ButtoncmdSaveImageEnabled: False
Location: 345, 70
Size: 140, 23
Text: "Save Encrypted Image"
ButtoncmdLoadEncLocation: 345, 99
Size: 140, 23
Text: "Load Encrypted Image"
ButtoncmdDecryptEnabled: False
Location: 345, 128
Size: 140, 23
Text: "Decrypt Data"
ButtoncmdSaveTextEnabled: False
Location: 345, 157
Size: 140, 23
Text: "Save Plain Text"
ButtoncmdCancelEnabled: False
Location: 345, 186
Size: 140, 23
Text: "Cancel"
ButtoncmdExitForeColor: Red
Location: 410, 529
ext: "Exit"
OpenFileDialogofdLoadBitmap-
SaveFileDialogsfdSaveBitmap-
SaveFileDialogsfdSavePlainText-

The program code for the form's class definition is presented below in its entirety. There are no lengthy explanations provided; the code is annotated with numerous comments that should be sufficient to allow the reader to understand how the program works. At the bottom of this page, however, there is a detailed explanation of how message data is encoded into the images, since this is something that may not be entirely obvious from either the code or the accompanying comments - especially if the reader is not too well acquainted with binary logic.


The program code:

Public Class frmGraphicEncryption
  'Bitmap variables to hold image data
  Dim bmp01, bmp02, bmpTemp As Bitmap
  'Variable to temporarily store pixel colour values
  Dim clr As Color
  'Variables to hold screen coordinates and row and column values
  Dim x, y, rows, cols As Integer
  'Variable to hold modified colour component values
  Dim ORcol As Integer
  'Variables to hold actual and maximum permitted message lengths
  Dim intMessageLength, intMaxLength As Integer
  'Variable to keep track of whether message exists or not
  Dim boolMsgFlag As Boolean
  
  Private Sub cmdLoadBmp_Click(sender As Object, e As _
    EventArgs) Handles cmdLoadBmp.Click
  
    'Multiplier value used for resizing image if needed
    Dim sngMul As Single
  
    'Set OpenFileDialog control to see bitmap files only
    ofdLoadBitmap.Filter = "Windows Bitmap Files|*.bmp"
  
    'This code executes if user selects (or types) a valid file name and clicks OK
    If ofdLoadBitmap.ShowDialog = DialogResult.OK Then
      'Assign image from file to bmp01
      bmp01 = New Bitmap(Image.FromFile(ofdLoadBitmap.FileName))
      'Display image file's name at top of form
      Me.Text = "GraphicEncryption(" & ofdLoadBitmap.FileName & ")"
      'Assign a copy of the image to bmp02
      bmp02 = New Bitmap(bmp01)
      'If the image in bmp01 is too big for the picture box to display,
      'scale it down as required
      If bmp01.Width > 320 Or bmp01.Height > 240 Then
        If(bmp01.Width / 4) >= (bmp01.Height / 3) Then
          sngMul = 1 / (bmp01.Width / 320)
        Else
          sngMul = 1 / (bmp01.Height / 240)
        End If
        bmpTemp = New Bitmap(bmp01, (bmp01.Width * sngMul), _
          (bmp01.Height * sngMul))
        bmpTemp.SetResolution(bmp01.HorizontalResolution, _
          bmp01.VerticalResolution)
        bmp01 = bmpTemp
      End If
      'Load the [resized] image into the picture box
      picImage.Image = bmp01
      'Get the height of the original version of the image (as rows)
      rows = bmp02.Height
      'Get the width of the original version of the image (as cols)
      cols = bmp02.Width
      'Calculate the maximum number of characters that can be encoded
      intMaxLength = rows * cols / 8
      'Limit message length to largest 16-bit value
      If intMaxLength > 65535 Then intMaxLength = 65535
      'Set the text box's maximum length property
      txtMessage.MaxLength = intMaxLength
      'Display message and inform user of maximum message length allowed
      lblMessage.Text = "Type your message here (maximum " _
        & intMaxLength & " characters) :"
      'Initialise message-related variables and set up display
      'to allow user to input message
      intMessageLength = 0
      x = 0
      y = 0
      txtMessage.Enabled = True
      txtMessage.Focus()
      lblMessageLength.Visible = True
      cmdLoadEnc.Enabled = False
      cmdLoadBmp.Enabled = False
      cmdCancel.Enabled = True
    End If
  End Sub
  
  Private Sub cmdLoadEnc_Click(sender As Object, e As _
    EventArgs) Handles cmdLoadEnc.Click
  
    'Multiplier value used for resizing image if needed
    Dim sngMul As Single
  
    'Set OpenFileDialog control to see encoded bitmap files only
    ofdLoadBitmap.Filter = "Encrypted Bitmap Files|*.ebm"
    'This code executes if user selects (or types) a valid file name and clicks OK
    If ofdLoadBitmap.ShowDialog = DialogResult.OK Then
      'Assign image from file to bmp01
      bmp01 = New Bitmap(Image.FromFile(ofdLoadBitmap.FileName))
      'Display encoded image file's name at top of form
      Me.Text = "GraphicEncryption(" & ofdLoadBitmap.FileName & ")"
      'Assign a copy of the image to bmp02
      bmp02 = New Bitmap(bmp01)
      'If the image in bmp01 is too big for the picture box to display,
      'scale it down as required
      If bmp01.Width > 320 Or bmp01.Height > 240 Then
        If(bmp01.Width / 4) >= (bmp01.Height / 3) Then
          sngMul = 1 / (bmp01.Width / 320)
        Else
            sngMul = 1 / (bmp01.Height / 240)
        End If
        bmpTemp = New Bitmap(bmp01, (bmp01.Width * sngMul), _
          (bmp01.Height * sngMul))
        bmpTemp.SetResolution(bmp01.HorizontalResolution, _
          bmp01.VerticalResolution)
        bmp01 = bmpTemp
      End If
      'Load the [resized] image into the picture box
      picImage.Image = bmp01
      'Get the height of the original version of the image(as rows)
      rows = bmp02.Height
      'Get the width of the original version of the image(as cols)
      cols = bmp02.Width
      lblMessage.Text = "Encrypted message:"
      'Initialise message-related variables and set up display
      'to allow user to decrypt message
      intMessageLength = 0
      x = 0
      y = 0
      lblMessageLength.Visible = True
      cmdLoadEnc.Enabled = False
      cmdLoadBmp.Enabled = False
      cmdDecrypt.Enabled = True
      cmdCancel.Enabled = True
    End If
  End Sub
  
  Private Sub txtMessage_TextChanged(sender As Object, e As _
    EventArgs) Handles txtMessage.TextChanged
    'This code executes each time the message text input by the user changes
  
    'Get the current message length
    intMessageLength = Len(txtMessage.Text)
    'If the message is not zero-length and this is the first character
    ' (boolMsgFlag = False), set boolMsgFlag to True to indicate message
    'is in progress and enable Encrypt Data button
    If Len(txtMessage.Text) > 0 And txtMessage.ReadOnly = False Then
      If boolMsgFlag = False Then
        boolMsgFlag = True
        cmdEncrypt.Enabled = True
      End If
    'If message is zero length (user has deleted all characters)
    'set boolMsgFlag to False and disable Encrypt Data button
    Else
      boolMsgFlag = False
      cmdEncrypt.Enabled = False
    End If
    'Update displayed message length
    lblMessageLength.Text = "Message length: " & intMessageLength & " characters"
  End Sub
  
  Private Sub cmdEncrypt_Click(sender As Object, e As _
    EventArgs) Handles cmdEncrypt.Click
  
    'ByteArray() holds the values for each bit in the ASCII code
    Dim ByteArray(0 To 7) As Integer
    'ByteMask() holds the binary masks used to extract the individual bit values
    Dim ByteMask() As Integer = {128, 64, 32, 16, 8, 4, 2, 1}
    'LengthArray() Holds the values for each bit in the message length value
    Dim LengthArray(0 To 15) As Integer
    'LengthMask() holds the binary masks used to extract the individual bit values
    Dim LengthMask() As Integer = _
      {32768, 16384, 8192, 4096, 2048, 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1}
    'Encode the message length into the red pixel component of the first 16 pixels
    'This loop will assign either a one or a zero to each element of LengthArray
    'The integer value MessageLength is ANDed with each LengthMask element in turn,
    'resulting in integer values that are either zero or a binary power within the
    'range: [1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16,384,32768]
    'The resulting integer value is right-shifted in order to leave 0 or 1
    For i = 0 To 15
      LengthArray(i) = (intMessageLength And LengthMask(i)) >> (15 - i)
      'Get the colour of the pixel
      clr = bmp02.GetPixel(i, 0)
      'Set ORcol to the value of the pixel's red component,
      'with the least significant bit is set to 1
      ORcol = clr.R Or 1
      'Modify the pixel's colour so that the least significant bit of the red
      'component is set to the same value (0 or 1) as bit i in the
      'intMessageLength variable
      clr = Color.FromArgb((ORcol And (254 + LengthArray(i))), clr.G, clr.B)
      'Set the pixel to the modified colour
      bmp02.SetPixel(i, 0, clr)
    Next
    'Encode the bits from one character's ASCII code into 8 pixels
    'This loop will assign either a one or a zero to each element of ByteArray
    For i = 0 To (intMessageLength - 1)
      For j = 0 To 7
        'The ASCII code is ANDed with each ByteMask element in turn, resulting in
        'integer values that are either zero or a binary power within the range:
        '[1,2,4,8,16,32,64,128] - the resulting integer value is right-shifted
        'in order to leave 0 or 1
        ByteArray(j) = (Asc(txtMessage.Text(i)) And ByteMask(j)) >> (7 - j)
        'Get the colour of the pixel
        clr = bmp02.GetPixel(x, y)
        'Set ORcol to the value of the pixel's blue component,
        'with the least significant bit set to 1
        ORcol = clr.B Or 1
        'Modify the pixel's colour so that the least significant bit of the blue
        'component is set to the same value (0 or 1) as bit j in the ASCII
        'character code
        clr = Color.FromArgb(clr.R, clr.G, (ORcol And (254 + ByteArray(j))))
        'Set the pixel to the modified colour
        bmp02.SetPixel(x, y, clr)         'If this is the last pixel in the row, go to the start of the next row,
        'otherwise go to the next pixel in the row
        If x = cols - 1 Then
          x = 0
          y += 1
        Else
          x += 1
          End If
      Next
    Next
    'Set up the user interface to allow the user to save the encoded image file
    txtMessage.Enabled = False
    cmdEncrypt.Enabled = False
    cmdSaveImage.Enabled = True
  End Sub
  
  Private Sub cmdDecrypt_Click(sender As Object, e As _
    EventArgs) Handles cmdDecrypt.Click
  
    'ByteArray() holds the values for each bit in the ASCII code
    Dim ByteArray(0 To 7) As Integer
    'LengthArray() Holds the values for each bit in the message length value
    Dim LengthArray(0 To 15) As Integer
    'CodedChar holds the result of each decoding operation
    Dim CodedChar As Char
    'CodedASCII holds the ASCII character code of each character exctracted
    Dim CodedASCII As Integer
  
    'Set up the text box for read-only output
    txtMessage.Enabled = True
    txtMessage.ReadOnly = True
  
    'Get the message length from the red pixel value using the first 16 pixels
    For i = 0 To 15
      'Get the colour of the current pixel
      clr = bmp02.GetPixel(i, 0)
      'Get the LSB value for the pixel's red component
      LengthArray(i) = (clr.R And 1)
      'Add the binary component to the length variable
      intMessageLength += 65535 And LengthArray(i) << (15 - i)
    Next
  
    For i = 0 To (intMessageLength - 1)
      'Initialise the ASCII character code variable to zero
      CodedASCII = 0
      'extract the eight bits that make up the ASCII character code
      For j = 0 To 7
        'Get the colour of the current pixel
        clr = bmp02.GetPixel(x, y)
        'Get the LSB value for the pixel's blue component
        ByteArray(j) = (clr.B And 1)
        'Add the binary component to the character code
        CodedASCII += 255 And ByteArray(j) << (7 - j)
        'If this is the last pixel in the row, go to the start of the next row,
        'otherwise go to the next pixel in the row
        If x = cols - 1 Then
          x = 0
          y += 1
        Else
           x += 1
        End If
      Next
      'Output the message to the text box
      CodedChar = Chr(CodedASCII)
      txtMessage.Text &= CodedChar
    Next
    'Set up the user interface to allow the user to
    'save the decoded message as a text file
    cmdDecrypt.Enabled = False
    cmdSaveText.Enabled = True
    lblMessageLength.Text = "Message length: " & intMessageLength & " characters"
  End Sub
  
  Private Sub cmdSaveImage_Click(sender As Object, e As _
    EventArgs) Handles cmdSaveImage.Click
    'This procedure saves the image with its encrypted message
    'as a bitmap file with the extension .ebm
    sfdSaveBitmap.Filter = "Encrypted Bitmaps |*.ebm"
    If sfdSaveBitmap.ShowDialog = DialogResult.OK Then
      bmp02.Save(sfdSaveBitmap.FileName, Drawing.Imaging.ImageFormat.Bmp)
    End If
    'Set the program back to its initial state
    resetProgram()
  End Sub
  
  Private Sub cmdSaveText_Click(sender As Object, e As _
    EventArgs) Handles cmdSaveText.Click
    'This procedure saves the decoded message as a text file
    Dim FileWriter As IO.StreamWriter
  
    sfdSavePlainText.Filter = "Text |*.txt"
    If sfdSavePlainText.ShowDialog = DialogResult.OK Then
      FileWriter = New IO.StreamWriter(sfdSavePlainText.FileName, False)
      FileWriter.Write(txtMessage.Text)
      FileWriter.Close()
    End If
    'Set the program back to its initial state
    resetProgram()
  End Sub
  
  Private Sub cmdCancel_Click(sender As Object, e As _
    EventArgs) Handles cmdCancel.Click
    'Set the program back to its initial state
    resetProgram()
  End Sub
  
  Sub resetProgram()
    'This procedure resets the program variables and
    'restores the user interface to its original state
    bmp01 = Nothing
    bmp02 = Nothing
    bmpTemp = Nothing
    Me.Text = "GraphicEncryption"
    picImage.Image = Nothing
    txtMessage.Clear()
    txtMessage.Enabled = False
    txtMessage.ReadOnly = False
    lblMessage.Text = ""
    lblMessageLength.Visible = False
    cmdLoadEnc.Enabled = True
    cmdLoadBmp.Enabled = True
    cmdEncrypt.Enabled = False
    cmdSaveImage.Enabled = False
    cmdDecrypt.Enabled = False
    cmdSaveText.Enabled = False
    cmdCancel.Enabled = False
  End Sub
  
  Private Sub cmdExit_Click _
    (sender As Object, e As EventArgs) Handles cmdExit.Click
    'This command exits the program unconditionally
    End
  End Sub
End Class


Notes on the encoding/decoding algorithms

The program can encode an ASCII text-message (i.e. a message containing only eight-bit ASCII characters) into a bitmapped image. It does this by setting the value of the least significant bit (LSB) of the blue colour component in each pixel used. Each character in the message is encoded using eight contiguous pixels, and the characters in the message are encoded sequentially into eight-byte blocks within the bitmap, in the same order in which they appear in the original message. In order to encode one character, eight bytes of image data are required.

The possibilities for encoding data into a bitmap are almost endless. You could, for example, use all three colour components to encode the hidden information without noticably changing the image. This would increasing the amount of information that could be hidden (i.e. the encoding density) by a factor of three for any given image. Alternatively, you could increase the number of bytes used for the character encoding in order to allow the full range of Unicode characters to be encoded. This would reduce the encoding density, but would allow you to encode messages containing virtually any kind of character or symbol.

We leave more ambitious implementations to those with the enthusiasm to pursue them. In the meantime, a more detailed look how the existing program encodes an ASCII text message might be illuminating. Let's assume that the message we want to encode is:

"The boy stood on the burning deck ..."

Let us further assume that the bitmap image file "Tigger.bmp" (one of the files in graphic_encryption.zip) will be used as the carrier. By carrier, we mean the medium within which we will hide the information. The following screenshots outline the steps the user must follow in order to end up with an encoded bitmap file containing this information:


Click on the Load Bitmap Image button

Click on the Load Bitmap Image button



Browse to and select the file "Tigger.bmp", then click the Open button

Browse to and select the file "Tigger.bmp", then click the Open button



Type the message into the text box and click on the Encrypt Data button

Type the message into the text box and click on the Encrypt Data button



Click on the Save Encrypted Data button

Click on the Save Encrypted Data button



Save the encoded file with the .ebm file extension

Save the encoded file with the .ebm file extension


The encoding of the message is done within a loop that iterates through the characters in the message one at a time. A second loop, nested within the first, is used to extract the eight bits of the ASCII character code for each character and encode it onto eight contiguous pixels within the image. Let's look at how this works for the first character in the message (in this example, the upper-case letter "T") :

  1. First of all, we get the ASCII character code for "T", which is 84
  2. To get the ByteArray() elements for the character, we use each ByteMask() element in turn, ANDing it with the character code to isolate bits 0 through 7 (starting with bit 7 and working from left to right). We then right-shift them the requisite number of places to move them into the least significant bit (LSB) position:

84 AND 128
Binary: 01010100 AND 10000000   =   00000000
Right-shifting 00000000 by 7 places = 0

84 AND 64
Binary: 01010100 AND 01000000   =   01000000
Right-shifting 01000000 by 6 places = 1

84 AND 32
Binary: 01010100 AND 00100000   =   00000000
Right-shifting 00000000 by 5 places = 0

84 AND 16
Binary: 01010100 AND 00010000   =   00010000
Right-shifting 00010000 by 4 places = 1

84 AND 8
Binary: 01010100 AND 00001000   =   00000000
Right-shifting 00000000 by 3 places = 0

84 AND 4
Binary: 01010100 AND 00000100   =   00000100
Right-shifting 00000100 by 2 places = 1

84 AND 2
Binary: 01010100 AND 00000010   =   00000000
Right-shifting 00000000 by 1 place = 0

84 AND 1
Binary: 01010100 AND 00000001   =   00000000
Right-shifting 00000000 by 0 places = 0

To (hopefully) clarify things somewhat, the process is summarised in the following tables:


Bitwise Operations (bits 7, 6, 5 and 4)
ASCII Code (84) 01010100010101000101010001010100
Bitmask values (128, 64, 32, 16) 10000000010000000010000000010000
Result of AND00000000010000000000000000010000
Result of right-shifting (7, 6, 5 and 4 places) 00000000000000010000000000000001


Bitwise Operations (bits 3, 2, 1 and 0)
ASCII Code (84) 01010100010101000101010001010100
Bitmask values (8, 4, 2, 1) 00001000000001000000001000000001
Result of AND00000000010001000000000000000000
Result of right-shifting (3, 2, 1 and 0 places) 00000000000000010000000000000000

  1. For each bit of the ASCII code, we get the colour of the pixel into which we are going to encode it, and OR the integer value of the colour's blue component with 1 (effectively forcing the least significant bit to be set to 1).
  2. We then AND the corresponding ByteArray() element with the modified blue colour component, so that its least significant bit will be the same as that of the ByteArray() element.
  3. Finally, we create an updated version of the original colour using the modified (and possibly unchanged) value for the blue component, and set the pixel to the updated colour.

The inner loop of our encoding procedure executes eight times in total in order to encode the bits of a single character into a sequence of eight pixels. The outer loop repeats for as many times as there are characters in the message, until they are all encoded. The encoding of the message length value onto the red component of the first 16 pixels in the image works in exactly the same way - it's just a (potentially) bigger number.

If you understand how the encoding procedure works, you should be able to discern how the decoding algorithm works without too much trouble by looking at the code, so we will forgo any further explanation. Of course, the best way to prove that the code actually works is to try it out, so if we open our encrypted bitmap file, we should be able to retrieve the encoded message. The screenshot below shows the result of doing just that with a longer passage of text which we encoded into the "Elephant.ebm" bitmap file.


A message has been retrieved from an encrypted bitmap image