// Encrypt.java, version 1.08. This file was created on July 28, 2006.
//
// Applet for encrypting secret messages with XOR private encryption keys.
//
// This file last updated June 6, 2007, by Rick Wagner.
// Copyright 2006 by Rick Wagner, all rights reserved.
//
// Use of this source code is authorized for educational purposes only. No use without
// proper attribution to Rick Wagner (http://iris.usc.edu/home/iris/rwagner/
// e-mail: Richard.J.Wagner@gmail.com).
//
// The prefix naming convention used here is a modified Hungarian notation.
// "s" is string, "sf" is single precision floating point, "i" is integer, "b" is boolean,
// and "d" is dimension.

import java.applet.*;
import java.awt.*;

public class Encrypt extends Applet
{
  // Applet instance variables:
  private final String sVerNum = "1.08";                    // Only constructors can run here ("" is a constructor).
  private final String sCompiledDate = "June 7, 2007";      // Compiled date.

  private Dimension dApplet = null;                         // The applet panel size (set in calling html).
  private Image imOffScreen = null;                         // Offscreen image for double buffering.
  private Graphics grOffScreen = null;                      // Offscreen graphics for double buffering.

  private Button btnRandomKey = null;
  private Button btnEncrypt = null;

  private TextArea taCryptText = null;
  private TextArea taPlainText = null;
  private TextArea taKeyText = null;

  private Label lblOne = null;                              // First label.
  private Label lblTwo = null;                              // Second label.
  private Label lblThree = null;                            // Third label.
  private Label lblMessage = null;                          // Message label.


  // To allow browsers to get information about the applet (not yet implemented in Netscape nor in MSIE):
  public String getAppletInfo()
  {
    return "XOR encryption applet, version " + sVerNum +
           ", by Rick Wagner, copyright 2007,\nall rights reserved.\n\n" +
           "Compiled " + sCompiledDate + ". Source code use authorized for\n" +
           "educational purposes only. No use without attribution.\n";
  }

  // Initialize the applet
  public void init()
  {
    int i = 0;
    this.setBackground(Color.lightGray);
    GridBagLayout gbl = null;                               // GridBagLayout is used for the applet GUI layout.
    GridBagConstraints gbc = null;

    dApplet = this.size();

    gbl = new GridBagLayout();
    this.setLayout(gbl);

    gbc = new GridBagConstraints();
    gbc.insets = new Insets(5, 10, 5, 10);                  // top, left, bottom, right.
    gbc.weightx = 0.0;
    gbc.weighty = 0.0;
    gbc.gridx = 0;

    // First Label
    lblOne = new Label("Plain Text", Label.CENTER);
    gbc.gridy = 0;
    gbl.setConstraints(lblOne, gbc);
    this.add(lblOne);

    // Add the plain text box:
    taPlainText = new TextArea("", 6, 80);                   // 6 rows and 80 columns.
    taPlainText.setEditable(true);
    gbc.gridy = 1;
    gbl.setConstraints(taPlainText, gbc);
    this.add(taPlainText);

    // Second Label
    lblTwo = new Label("Key Text", Label.CENTER);
    gbc.gridy = 2;
    gbl.setConstraints(lblTwo, gbc);
    this.add(lblTwo);

    // Add the Key text box:
    taKeyText = new TextArea("", 6, 80);                     // 6 rows and 80 columns.
    taKeyText.setEditable(true);
    gbc.gridy = 3;
    gbl.setConstraints(taKeyText, gbc);
    this.add(taKeyText);

    // Add the random button:
    btnRandomKey = new Button("Generate Random Key");        // The random key generation button.
    btnRandomKey.setForeground(Color.black);
    btnRandomKey.setBackground(Color.lightGray);
    gbc.gridy = 4;
    gbl.setConstraints(btnRandomKey, gbc);
    this.add(btnRandomKey);

    // Message Label
    lblMessage = new Label("          Welcome to the XOR encryption applet." +
                           "Type or paste text into the upper two text boxes.          ", Label.CENTER);
    gbc.gridy = 5;
    gbl.setConstraints(lblMessage, gbc);
    this.add(lblMessage);

    // Add the encrypt button:
    btnEncrypt = new Button("Encrypt the Text");          // The compute crypttext button.
    btnEncrypt.setForeground(Color.black);
    btnEncrypt.setBackground(Color.lightGray);
    gbc.gridy = 6;
    gbl.setConstraints(btnEncrypt, gbc);
    this.add(btnEncrypt);

    // Third Label
    lblThree = new Label("Encrypted Text", Label.CENTER);
    gbc.gridy = 7;
    gbl.setConstraints(lblThree, gbc);
    this.add(lblThree);

    // Add the crypt text box:
    taCryptText = new TextArea("", 6, 80);                    // 6 rows and 80 columns.
    taCryptText.setEditable(true);
    gbc.gridy = 8;
    gbl.setConstraints(taCryptText, gbc);
    this.add(taCryptText);

  } // End of init()

  // Execute this code after initialization
  public void start()
  {
    System.out.println("\n" + this.getAppletInfo());         // Identify self to the Java console-aware user
    taPlainText.requestFocus();
  }                                                          // End of start()

  // Implements double buffering
  public void update(Graphics g)
  {
    if (imOffScreen == null)
    {
      // Make sure the offscreen and graphics exist:
      imOffScreen = this.createImage(dApplet.width, dApplet.height);
      grOffScreen = imOffScreen.getGraphics();
      grOffScreen.clearRect(0, 0, dApplet.width, dApplet.height);
    }
    this.paint(grOffScreen);
    g.drawImage(imOffScreen, 0, 0, null);
  }

  // The applet frame painting function
  public void paint(Graphics g)
  {
    // Code for displaying images or drawing in the applet frame (called by the OS).
    g.clearRect(0, 0, dApplet.width, dApplet.height);        // Needed for double buffering.
    this.setBackground(Color.lightGray);                     // Ditto.
  
    drawFrame(g);                                            // Draw the frame abound the applet.
  }                                                          // End of function paint()

  private void drawFrame(Graphics g)
  {
    // Draw a recessed frame around the applet border. Designed for gray-on-gray browser background.
    g.setColor(Color.black);
    g.drawLine(0, 0, dApplet.width - 1, 0);
    g.drawLine(0, 0, 0, dApplet.height - 1);
    g.setColor(Color.white);
    g.drawLine(0, dApplet.height - 1, dApplet.width - 1, dApplet.height - 1);
    g.drawLine(dApplet.width - 1, 1, dApplet.width - 1, dApplet.height - 1);
  }

  public boolean action(Event e, Object o)
  {
    if (e.target == btnEncrypt)
    {
      btnEncrypt.disable();
      if (taPlainText.getText().length() == 0)
      {
        System.out.println("Plaintext Empty");
        lblMessage.setText("Plaintext Empty");
      }
      else
      {
        if (taKeyText.getText().length() == 0)
        {
          System.out.println("Keytext Empty");
          lblMessage.setText("Keytext Empty");
        }
        else
        {
          // We can do the key computation
          encrypt();
        }
      }
      btnEncrypt.enable();
    }

    if (e.target == btnRandomKey)
    {
      int i = 0;
      int iTemp = 0;
      int iPlainLength = 0;
      StringBuffer sbTemp = null;

      sbTemp = new StringBuffer();
      iPlainLength = taPlainText.getText().length();
      if (iPlainLength > 0)
      {
        for (i = 0; i < iPlainLength; i++)
        {
          iTemp = (int) (128 * Math.random());
          sbTemp.append(iTemp);
          if (i < iPlainLength - 1)
          {
            sbTemp.append(' ');
          }
        }
        taKeyText.setText(sbTemp.toString());
        lblMessage.setText("Generated random key of " + iPlainLength + " characters.");
      }
      else
      {
        taKeyText.setText("");
        lblMessage.setText("Need some plaintext to generate a key.");
      }
    }
    return true;                                              // Absorb the event;
  }                                                           // End of action().

  public boolean handleEvent(Event e)
  {
    int iLength = 0;

    if (e.target == btnEncrypt && e.id == Event.ACTION_EVENT)
    {
      btnEncrypt.disable();
      if (taPlainText.getText().length() == 0)
      {
        System.out.println("Plaintext Empty");
        lblMessage.setText("Plaintext Empty");
      }
      else
      {
        if (taKeyText.getText().length() == 0)
        {
          System.out.println("Keytext Empty");
          lblMessage.setText("Keytext Empty");
        }
        else
        {
          // We can do the key computation
          encrypt();
        }
      }
      btnEncrypt.enable();
      return true;
    }

    if (e.target == btnRandomKey && e.id == Event.ACTION_EVENT)
    {
      int i = 0;
      int iTemp = 0;
      int iPlainLength = 0;
      StringBuffer sbTemp = null;

      sbTemp = new StringBuffer();
      iPlainLength = taPlainText.getText().length();
      if (iPlainLength > 0)
      {
        for (i = 0; i < iPlainLength; i++)
        {
          iTemp = (int) (128 * Math.random());
          sbTemp.append(iTemp);
          if (i < iPlainLength - 1)
          {
            sbTemp.append(' ');
          }
        }
        taKeyText.setText(sbTemp.toString());
        lblMessage.setText("Generated random key of " + iPlainLength + " characters.");
      }
      else
      {
        taKeyText.setText("");
        lblMessage.setText("Need some plaintext to generate a key.");
      }
      return true;
    }

    if (e.target == taPlainText)
    {
      if (e.id != Event.GOT_FOCUS)
      {
        iLength = taPlainText.getText().length();
        lblMessage.setText("Plain text is " + iLength + " characters long.");
      }
    }

    if (e.target == taKeyText)
    {
      if (e.id != Event.GOT_FOCUS)
      {
        iLength = taKeyText.getText().length();
        lblMessage.setText("Key text is " + iLength + " characters long.");
      }
    }
    return false;
  }

  private void encrypt()
  {
    int i = 0;
    int iPlainLength = 0;
    int iKeyLength = 0;
    int iKeyTextLength = 0;
    int iDelta = 0;
    int iTemp = 0;
    char cTemp = 0;
    String sTemp = null;
    String sPlainText = null;
    String sKeyText = null;
    String sCryptText = null;
    StringBuffer sbTemp = null;
    int iKey[] = null;
    int iKeyBuffer[] = null;
    boolean bNumericKey = false;

    sPlainText = taPlainText.getText();
    sKeyText = taKeyText.getText();

    iPlainLength = sPlainText.length();
    iKeyTextLength = sKeyText.length();
    System.out.println("\n");

    // Find out if the key is numeric:
    if (numericP(iKeyTextLength, sKeyText))
    {
      bNumericKey = true;
      System.out.println("The key is numeric.");

      // Fill the key array:
      iKey = new int[iKeyTextLength];

      while (i < iKeyTextLength)
      {
        cTemp = 0;
        sbTemp = new StringBuffer();
        while (cTemp != ' ' && i < iKeyTextLength)
        {
          try
          {
            cTemp = sKeyText.charAt(i);
          }
          catch (StringIndexOutOfBoundsException e1)
          {
            System.out.println("Caught StringIndexOutOfBoundsException in decrypt() (key).");
          }
          if (cTemp != ' ')
          {
            sbTemp.append(cTemp);
          }
          i++;
        }
        try
        {
          iTemp = Integer.parseInt(sbTemp.toString());
        }
        catch (NumberFormatException e2)
        {
          System.out.println("Caught NumberFormatException in decrypt() (key).");
          break;
        }
        iKey[iKeyLength] = iTemp;
        iKeyLength++;
      }
    }
    else
    {
      System.out.println("The key is not numeric.");
      iKeyLength = iKeyTextLength;
    }

    if (iPlainLength < iKeyLength)
    {
      // Pad out the crypttext.
      System.out.println("Key is longer than plaintext: padding plaintext with spaces.");
      lblMessage.setText("Key is longer than plaintext: padding plaintext with spaces.");
      sbTemp = new StringBuffer();
      iDelta = iKeyLength - iPlainLength;
      for (i = 0; i < iDelta; i++)
      {
        sbTemp.append(" ");
      }
      sPlainText = sPlainText + sbTemp.toString();
      taPlainText.setText(sPlainText);
      iPlainLength = sPlainText.length();
    }
    else
    {
      if (iKeyLength < iPlainLength)
      {
        // Repeat the key as necessary to make it long enough.
        System.out.println("Key is shorter than plaintext: repeating the key.");
        lblMessage.setText("Key is shorter than plaintext: repeating the key.");

        if (bNumericKey)
        {
          iKeyBuffer = new int[iKeyLength];                                  // Need to stash the key characters someplace
          for (i = 0; i < iKeyLength; i++)                                   // while we get more memory for the array.
          {
            iKeyBuffer[i] = iKey[i];
          }
          iKey = new int[iPlainLength];                                      // Expand the key array.
          for (i = 0; i < iKeyLength; i++)
          {
            iKey[i] = iKeyBuffer[i];
          }
          for (i = iKeyLength; i < iPlainLength; i++)
          {
            iKey[i] = iKey[i - iKeyLength];
          }
        }
        else
        {
          // The key is printable text:
          sbTemp = new StringBuffer(sKeyText);
          for (i = iKeyLength; i < iPlainLength; i++)
          {
            sbTemp.append(sbTemp.charAt(i - iKeyLength));
          }
          sKeyText = sbTemp.toString();
          iKeyLength = sKeyText.length();
        }
      }
      else
      {
        // They are equal in length, do nothing.
      }
    }
    // Begin the encryption.
    sbTemp = new StringBuffer();
    for (i = 0; i < iPlainLength; i++)
    {
      if (bNumericKey)
      {
        iTemp = sPlainText.charAt(i) ^ iKey[i];                               // XOR encryption
      }
      else
      {
        iTemp = sPlainText.charAt(i) ^ sKeyText.charAt(i);                    // XOR encryption
      }
      sbTemp.append((char) iTemp);
    }
    sTemp = sbTemp.toString();
    if (unprintableP(iPlainLength, sTemp))
    {
      System.out.println("Unprintable encrypted text, writing " + iPlainLength + " characters as numeric code.");
      lblMessage.setText("Unprintable encrypted text, writing " + iPlainLength + " characters as numeric code.");
      sbTemp = new StringBuffer();
      for (i = 0; i < iPlainLength; i++)
      {
        iTemp = sTemp.charAt(i);

        if (i == iPlainLength - 1)
        {
          sbTemp.append(iTemp);
        }
        else
        {
          sbTemp.append(iTemp + " ");
        }
      }
      taCryptText.setText(sbTemp.toString());
    }
    else
    {
      System.out.println("Printable encrypted text, writing " + iPlainLength + " characters.");
      lblMessage.setText("Printable encrypted text, writing " + iPlainLength + " characters.");
      taCryptText.setText(sTemp);
    }
  }                                                                           // End of function encrypt().

  boolean numericP(int iTextLength, String sText)
  {
    boolean r = true;
    int i = 0;
    char cTemp = 0;
    int iRunLength = 0;
    int iMaxRunLength = 0;

    for (i = 0; i < iTextLength; i++)
    {
      try
      {
        cTemp = sText.charAt(i);
      }
      catch (StringIndexOutOfBoundsException e7)
      {
        System.out.println("Caught StringIndexOutOfBoundsException in decrypt() (e7).");
      }
      if (cTemp != ' ')
      {
        if (cTemp < 48 || cTemp > 57)
        {
          r = false;
          break;                                                          // Break out of for loop.
        }
        else
        {
          if (cTemp > 47 && cTemp < 58)
          {
            // We have a decimal digit.
            iRunLength++;
          }
        }
      }
      else
      {
        // We have a space:                                                // Detect delimiting spaces
        if (iRunLength > iMaxRunLength)
        {
          iMaxRunLength = iRunLength;
        }
        iRunLength = 0;
      }
    }                                                                     // End of for.

    if (iRunLength > iMaxRunLength)                                       // Catch the case where the space
    {                                                                     // comes at the end of the string.
      iMaxRunLength = iRunLength;
    }

    if (iMaxRunLength > 3)                                                // It's not text that happens to to be
    {                                                                     // a string (or strings) of digits.
      r = false;
    }

    return r;
  }
  boolean numericKeyP(int iKeyTextLength, String sKeyText)
  {
    boolean r = true;
    int i = 0;
    char cTemp = 0;

    for (i = 0; i < iKeyTextLength; i++)
    {
      cTemp = sKeyText.charAt(i);
      if (cTemp != ' ')
      {
        if (cTemp < 48 || cTemp > 57)
        {
          r = false;
          break;                                                          // Break out of for loop.
        }
      }
    }

    return r;
  }

  boolean unprintableP(int iLength, String sText)
  {
    boolean r = false;
    int i = 0;
    char cTemp = 0;

    for (i = 0; i < iLength; i++)
    {
      cTemp = sText.charAt(i);
      if (cTemp < 32 || cTemp == 127)
      {
        if (cTemp != 9 && cTemp != 10 && cTemp != 12 && cTemp != 13)
        {
          r = true;
          break;                                                          // Break out of for loop.
        }
      }
    }

    return r;
  }

} // End of Applet class Encrypt

