Java Programming, Lecture Notes # 711, Revised 5/13/99.
Students in Prof. Baldwin's Advanced Java Programming classes at ACC will be responsible for knowing and understanding all of the material in this lesson beginning with the spring semester of 1999.
This lesson was originally written on April 19, 1999 and has been updated several times since.
The programs in this lesson were tested using JDK 1.2 under Win95
I claim absolutely no expertise in the area of security. I am simply a college professor attempting to gather information about Java on one hand and present it to my students on the other. I disclaim any responsibility for any security problems that may occur as a result of anyone using any of the material in any of my tutorial lessons.
You are responsible for your own actions. With regard to security, you should study not only the material that I will present, but also material provided by others who possess expertise in the security area. Hopefully my material will be useful in getting you started in that direction.
Two good books on security published by O'Reilly & Associates are:
I highly recommend both of these books.
Integrity and authentication
A previous lesson introduced you to message digests. This lesson will show you how to combine a passphrase with a message digest to confirm data integrity and some degree of authentication.
What is a passphrase?
A passphrase is the new jargon for the old reliable password. It means the same thing but it is more difficult to say. (Perhaps one difference is that a passphrase can contain spaces and special characters that are not allowed in a password on some systems.)
Three sample programs
Three sample programs are presented.
The first program shows you how to create a message digest combining an original document and a passphrase.
The second program shows how to use the message digest, the passphrase, and a copy of the original document to confirm the integrity of the copy of the document.
The third sample program is used to corrupt the original document to illustrate that the procedure implement by the second program will detect the fact that the document has been corrupted.
Serialized objects
These sample programs also show you how to encapsulate the document and the digest as serialized objects in a file for transmission to an intended receiver.
Two aspects of Internet security
As discussed in an earlier lesson, there are two major aspects of Java and security on the Internet:
For the most part, my tutorial lessons will concentrate on the mechanics of using the Java tools and the Java API. I won't attempt to give advice on overall security procedures. Rather, I will leave that to others who have given more thought to the topic than I have.
The security stool has three legs
An earlier lesson suggested that when exchanging data electronically, the parties to the communication might be interested in the following three aspects of that communication:
This lesson deals with only the first and third items:
Generally, some type of encryption is required to achieve confidentiality. Encryption is the topic of a different lesson.
Sometimes confidentiality is not needed
When might an approach like this be appropriate? See my lesson on digital signatures for a discussion of a hypothetical situation where confidentiality is not of concern, but where both the sender and the receiver of a message are very concerned about authentication and integrity.
Another situation where we might be very concerned about authentication and integrity but not concerned about confidentiality is in the distribution of software. We might like to know where it came from, and that it hasn't been tampered with, but might not care if someone else can read it.
Using a passphrase for authentication and integrity
Under some circumstances, a passphrase can be an effective means of achieving authentication and integrity.
Under the right circumstances, it has some operational advantages over the use of public and private keys for the creation of digital signatures (the topic of a subsequent lesson).
For example, when the passphrase is properly constructed, the human mind can easily memorize it eliminating the need for either a written or electronic record. (Few human minds are equipped to memorize public and private keys.)
Don't use password spelled backwards as your passphrase
When you read the many available books that discuss operational procedures, you will find long lists of phrases that you should not use for your passphrase (birthdays, quotations from poems, etc.).
However, it isn't difficult to come up with a long phrase that has never been written down in well-known literature, but is easy for two or more people to memorize. An example of such a phrase might be:
"The purple bear likes to eat honey in the woods"
Since I made that up while sitting at my keyboard, it is unlikely that it is a well-known phrase.
You can also use just the initials
Either the entire phrase or perhaps a concatenation of the first letters from each word could be used as a passphrase. An example created by concatenating the first letters of each word is:
"Tpbltehitw"
After reading the books on operational procedures, you can decide for yourself about the security aspects of using a passphrase. I will simply concentrate on showing you how to do it if you decide to do it.
This program is designed to work in conjunction with the following other programs:
Illustrates passphrase protected message digests
This program illustrates the use of passphrase protected message digests. A file is written to the disk containing serialized versions of a data array and a digest value corresponding to the data array combined with a passphrase.
The program named Security07B is designed to
Intercept and corrupt the file
The program named Security07C is designed to
Program output
When the programs were run in the sequence 7A, 7B, 7C, 7B, the output produced by the programs was as shown below:
Security07A ABCDEFG i8xdQShcLLPBI9J83h+hxXzptrQ= Security07B ABCDEFG i8xdQShcLLPBI9J83h+hxXzptrQ= i8xdQShcLLPBI9J83h+hxXzptrQ= Valid message Security07C Intercept and corrupt the file XBCDEFG L9QY4fxaiTY6ubOZWAaBVduqZ4I= Security07B XBCDEFG L9QY4fxaiTY6ubOZWAaBVduqZ4I= +PqZf+XFDeTv1E3RLGan+Ecc+gc= Corrupted message |
As you can see from the boldface output, when the procedures of Security07B were applied to the original message and digest produced by Security07A, the integrity of the message was verified.
You can't fool me
However, after Security07C was run to corrupt the message, the procedures of Security07B reported that the message had been corrupted.
The digest values are also shown to give you a visual indication of how the program compares digests to confirm or deny integrity.
The first fragment shows the beginning of the main() method which includes the generation of a passphrase (Pass Phrase) and the message (ABCDEFG).
Keeping it simple
In this case, both the passphrase and the message were hard coded into the program to keep it simple.
In a real program, the passphrase would be provided as a user input and the message would most likely be extracted from a file on the disk.
class Security07A { public static void main(String[] args) { System.out.println("Security07A"); byte[] passPhrase = "Pass Phrase".getBytes(); byte[] dataBuffer = "ABCDEFG".getBytes();//create data System.out.println(new String(dataBuffer));//display |
Digesting the data
The next fragment calls the method named digestIt() to get a digest value based on a combination of the message and the passphrase. I will discuss the details of this method later.
byte[] theDigest = digestIt(dataBuffer,passPhrase); displayBase64(theDigest);//display in Base 64 |
Base64 again
The fragment also displays the digest value in Base 64 format. I explained Base 64 format in an earlier lesson. You can view the method named displayBase64() in the complete program listing near the end of the lesson.
Writing serialized objects
The next fragment calls the method named writeTheObject() to write the message and the digest value into a disk file as two serialized objects. This is a convenient way to transfer data from one program to another in Java. I will discuss the method later.
This fragment also ends the main() method.
writeTheObject(dataBuffer,theDigest); }//end main |
The digestIt() method
The next fragment, showing the method named digestIt(), is the key to this program. This method receives two arrays of bytes. One array of bytes contains the message and the other contains the passphrase.
The method generates and returns a digest value obtained by combining the message with the passphrase.
static byte[] digestIt(byte[] dataOne, byte[] dataTwo){ byte[] theDigest = null; try{ //Create a MessageDigest object implementing // the SHA algorithm from the default provider. MessageDigest messageDigest = MessageDigest.getInstance("SHA"); //Feed the byte arrays to the digester. messageDigest.update(dataOne); messageDigest.update(dataTwo); //Complete the digestion and save the result theDigest = messageDigest.digest(); }catch(Exception e){System.out.println(e);} //Return the digest value to the calling method return theDigest; }//end digestIt() |
Similar to previously discussed code
Since the above code is very similar to code explained in an earlier lesson on message digests, I won't discuss it in detail. The significant difference is that the update() method is called twice.
Feeding the update() method
First the message is fed to the update() method and then the passphrase is fed to the update() method.
After all of the data has been fed to the update() method, the digest() method is called to complete the task and return the digest value.
Writing the serialized objects
The final fragment shows the code used to write the message and the digest value into an output file as serialized objects. The methodology for writing and reading serialized objects is covered in other lessons, so I won't discuss it further here. This fragment is presented here for completeness.
static void writeTheObject(byte[] objOne,byte[] objTwo){ try{ FileOutputStream fileOut = new FileOutputStream("Security07.obj"); ObjectOutputStream objOut = new ObjectOutputStream(fileOut); objOut.writeObject(objOne); objOut.writeObject(objTwo); objOut.close(); }catch(Exception e){System.out.println(e);} }//end writeTheObject |
Getting the data
This program reads a file containing two serialized objects produced by the program named Security07A. It extracts
Verifying the integrity of the data
It is assumed that the digest value was produced by combining the original message with a known passphrase in a specified manner.
The program uses the digest value along with the known passphrase to determine if the data array has become corrupted since the digest value was computed and placed in the file along with the data.
Compute digest value for comparison
This is accomplished by
Interpreting the results
If the two are the same, this is a very strong indication that
(Another person who has gained knowledge of the passphrase could corrupt the original message and use the passphrase along with the corrupted message to compute a new digest value that would pass this test.)
Loose lips sink ships
Therefore, the security of the process depends on the ability of the parties involved to keep the passphrase secret.
The first fragment shows the beginning of the main method and includes a hard-coded passphrase. As explained earlier, the passphrase would be provided as a user input in a real program. It was hard coded in this sample program just to keep the program simple.
The fragment also declares two byte arrays that will be used later in the program.
class Security07B { public static void main(String[] args) { System.out.println("Security07B"); try{ byte[] passPhrase = "Pass Phrase".getBytes(); byte[] dataBuffer = null; byte[] originalDigest = null; |
Getting the data
The next fragment reads the disk file and extracts the two byte arrays:
Code for reading serialized objects is explained in other lessons on Java I/O, so I won't discuss this code further at this point.
This fragment also displays the message and the digest value using methodology that should also be well known to you by now.
FileInputStream fileIn = new FileInputStream("Security07.obj"); ObjectInputStream objIn = new ObjectInputStream(fileIn); dataBuffer = (byte[])objIn.readObject(); originalDigest = (byte[])objIn.readObject(); objIn.close(); System.out.println(new String(dataBuffer));//display displayBase64(originalDigest);//display in Base 64 |
Generating a digest value for comparison
The next fragment calls the digestIt() method to generate a digest value for the combination of the message and the passphrase. This process is the same as in the earlier program that produced the original message digest.
This fragment also displays the newly computed digest value.
byte[] theDigest = digestIt(dataBuffer,passPhrase); displayBase64(theDigest);//display in Base 64 |
Do they match?
The final step is to call the isEqual() method of the MessageDigest class to compare the newly computed digest value with the digest value that was extracted from the file.
As indicated earlier, if the two are the same, this is a very good indication that either the message has not been corrupted since the computation of the original digest value or that the passphrase has been compromised.
if(MessageDigest.isEqual(theDigest,originalDigest)){ System.out.println("Valid message"); }else{ System.out.println("Corrupted message"); }//end else |
The methods used to compute the digest value and to display the digest value in Base 64 are essentially the same as in the previous program, so I won't show them here. You can view them in the complete listing of the program near the end of the lesson.
The purpose of this program is to intercept and corrupt the data array in a file written by Security07A before it is read by Security07C.
Corrupting the data and computing the digest
The first element in the data array is overwritten with an 'X'. Then a new digest is computed for the corrupted data array and a new file is written containing the corrupted data array and the digest value corresponding to that array.
Don't have the passphrase
Since the person corrupting the data doesn't (or shouldn't) have access to the passphrase, it is extremely difficult for that person to produce a corrupted version of the data for which the digest value will match the original digest value produced by combining the original data with the passphrase.
Therefore, when Security07B is used to verify the data and the digest value (using the passphrase), it is highly unlikely that the two will pass the verification test.
The first code fragment shows the program reading the data array from the file. This is essentially the same as in the previous program so no further discussion should be needed.
class Security07C { public static void main(String[] args) { try{ System.out.println("Security07C"); System.out.println("Intercept and corrupt the file"); byte[] dataBuffer = null; //Read the object file and extract the data array FileInputStream fileIn = new FileInputStream("Security07.obj"); ObjectInputStream objIn = new ObjectInputStream(fileIn); dataBuffer = (byte[])objIn.readObject(); objIn.close(); |
Corrupting the data
The next fragment corrupts the data by overwriting the first element in the data array with the lower eight bits of the character 'X'. The contents of the array are then displayed.
dataBuffer[0] = (byte)'X'; System.out.println(new String(dataBuffer));//display |
Digesting the corrupted data
The next fragment invokes a method named digestIt() to obtain a digest value for the corrupted data. Note that this version of the method only requires one parameter. In particular, it doesn't make provision for a passphrase to be incorporated into the digest value since the user of the method doesn't know the password.
Doesn't invoke update()
A listing of this version of the method can be viewed in the complete program listing near the end of the lesson. I recommend that you take a look at one aspect of this method. In particular, it doesn't make use of the update() method to produce the digest value.
Whenever the data to be digested is contained in a single byte array, an overloaded version of the digest() method can be used to digest the array and return the digest value without the requirement to first feed the data to the update() method.
The fragment also displays the digest value in Base 64.
byte[] theDigest = digestIt(dataBuffer); displayBase64(theDigest);//display in Base 64 |
Write to a file
The next fragment writes a file containing the corrupted data array and the digest value for that corrupted array as two serialized objects. The writeTheObject() method used to write the file is the same as in the programs discussed earlier and can be viewed in the program listing near the end of the lesson.
writeTheObject(dataBuffer,theDigest); |
Isn't it obvious that the corruption didn't work?
We might surmise that an astute person who might be inclined to corrupt the data in this manner could:
That person should know immediately that they face a formidable task in trying to corrupt the message and provide a digest value that will pass the verification test that is likely to be applied by the lawful recipient of the message.
Thus, the use of passphrase protected message digests could serve as an easily implemented deterrent to those who might otherwise attempt this form of malicious behavior.
Complete listings of all three programs are contained in this section.
/*File Security07A.java Copyright 1999, R.G.Baldwin Rev 4/16/99 This program works in conjunction with the following two other programs: Security07B Security07C This program illustrates passphrase protected message digests. An object is written to the disk containing a data array and a digest value corresponding to the data array plus a passphrase The program named Security07B is designed to read the object and to use the digest value along with the known passphrase to determine if the data in the object was corrupted after the original digest was computed and placed in the object. The program named Security07C is designed to intercept the object, corrupt the data, compute a digest value corresponding to the corrupted data, and write both the corrupted data and the digest value back out into an object file having the same name. Tested using JDK 1.2 under Win95. When the programs were run in the following sequence, the output produced by the programs was: Security07A ABCDEFG i8xdQShcLLPBI9J83h+hxXzptrQ= Security07B ABCDEFG i8xdQShcLLPBI9J83h+hxXzptrQ= i8xdQShcLLPBI9J83h+hxXzptrQ= Valid message Security07C Intercept and corrupt the file XBCDEFG L9QY4fxaiTY6ubOZWAaBVduqZ4I= Security07B XBCDEFG L9QY4fxaiTY6ubOZWAaBVduqZ4I= +PqZf+XFDeTv1E3RLGan+Ecc+gc= Corrupted message **********************************************************/ import java.io.*; import java.security.*; import sun.misc.*; class Security07A { public static void main(String[] args) { System.out.println("Security07A"); //Hard code the pass phrase to keep the program simple byte[] passPhrase = "Pass Phrase".getBytes(); byte[] dataBuffer = "ABCDEFG".getBytes();//create data System.out.println(new String(dataBuffer));//display //Get a digest for the byte array & the passphrase byte[] theDigest = digestIt(dataBuffer,passPhrase); displayBase64(theDigest);//display in Base 64 writeTheObject(dataBuffer,theDigest); }//end main //-----------------------------------------------------// static void writeTheObject(byte[] objOne,byte[] objTwo){ try{ FileOutputStream fileOut = new FileOutputStream("Security07.obj"); ObjectOutputStream objOut = new ObjectOutputStream(fileOut); objOut.writeObject(objOne); objOut.writeObject(objTwo); objOut.close(); }catch(Exception e){System.out.println(e);} }//end writeTheObject //-----------------------------------------------------// //This method generates and returns a digest for two // incoming arrays of bytes. static byte[] digestIt(byte[] dataOne, byte[] dataTwo){ byte[] theDigest = null; try{ //Create a MessageDigest object implementing // the SHA algorithm from the default provider. MessageDigest messageDigest = MessageDigest.getInstance("SHA"); //Feed the byte arrays to the digester. messageDigest.update(dataOne); messageDigest.update(dataTwo); //Complete the digestion and save the result theDigest = messageDigest.digest(); }catch(Exception e){System.out.println(e);} //Return the digest value to the calling method return theDigest; }//end digestIt() //-----------------------------------------------------// //Method to display an array of bytes in base 64 format static void displayBase64(byte[] data){ BASE64Encoder encoder = new BASE64Encoder(); String encoded = encoder.encodeBuffer(data); System.out.println(encoded); }//end base64Display() //-----------------------------------------------------// }//end class Security07A |
.
/*File Security07B.java Copyright 1999, R.G.Baldwin Rev 4/16/99 This program works in conjunction with the following two other programs: Security07A Security07C This program illustrates passphrase protected message digests. The program reads an object produced by Security07A, extracts a data array and a digest value from the object, and uses the digest value along with a known passphrase to determine if the data array has become corrupted since the digest value was computed and placed in the object along with the data. Tested using JDK 1.2 under Win95. **********************************************************/ import java.io.*; import java.security.*; import sun.misc.*; class Security07B { public static void main(String[] args) { System.out.println("Security07B"); try{ //Hard code the pass phrase to keep the // program simple byte[] passPhrase = "Pass Phrase".getBytes(); byte[] dataBuffer = null; byte[] originalDigest = null; //Read the object from the disk file and extract the // two byte arrays from the object. FileInputStream fileIn = new FileInputStream("Security07.obj"); ObjectInputStream objIn = new ObjectInputStream(fileIn); dataBuffer = (byte[])objIn.readObject(); originalDigest = (byte[])objIn.readObject(); objIn.close(); System.out.println(new String(dataBuffer));//display displayBase64(originalDigest);//display in Base 64 //Get a digest for the byte array and the passphrase byte[] theDigest = digestIt(dataBuffer,passPhrase); displayBase64(theDigest);//display in Base 64 //Test the newly computed digest value against the // original digest value to determine if the data // has become corrupted. if(MessageDigest.isEqual(theDigest,originalDigest)){ System.out.println("Valid message"); }else{ System.out.println("Corrupted message"); }//end else }catch(Exception e){System.out.println(e);} }//end main //-----------------------------------------------------// //This method generates and returns a digest for two // incoming arrays of bytes. static byte[] digestIt(byte[] dataOne, byte[] dataTwo){ byte[] theDigest = null; try{ //Create a MessageDigest object implementing // the SHA algorithm from the default provider. MessageDigest messageDigest = MessageDigest.getInstance("SHA"); //Feed the byte array to the digester. Can // accommodate multiple calls if needed messageDigest.update(dataOne); messageDigest.update(dataTwo); //Complete the digestion and save the result theDigest = messageDigest.digest(); }catch(Exception e){System.out.println(e);} //Return the digest value to the calling method return theDigest; }//end digestIt() //-----------------------------------------------------// //Method to display an array of bytes in base 64 format static void displayBase64(byte[] data){ BASE64Encoder encoder = new BASE64Encoder(); String encoded = encoder.encodeBuffer(data); System.out.println(encoded); }//end base64Display() //-----------------------------------------------------// }//end class Security07B |
.
/*File Security07C.java Copyright 1999, R.G.Baldwin Rev 4/16/99 This program works in conjunction with the following two other programs: Security07A Security07B The purpose of this program is to intercept and corrupt the data array in an object file written by Security07A before it is read by Security07C. The first element in the data array is overwritten with an 'X'. Then a new digest is computed for the corrupted data array and a new object is written to the disk containing the corrupted data array and the digest value corresponding to that array. Tested using JDK 1.2 under Win95. **********************************************************/ import java.io.*; import java.security.*; import sun.misc.*; class Security07C { public static void main(String[] args) { try{ System.out.println("Security07C"); System.out.println("Intercept and corrupt the file"); byte[] dataBuffer = null; //Read the object file and extract the data array FileInputStream fileIn = new FileInputStream("Security07.obj"); ObjectInputStream objIn = new ObjectInputStream(fileIn); dataBuffer = (byte[])objIn.readObject(); objIn.close(); //Corrupt the data by overwriting the first byte dataBuffer[0] = (byte)'X'; System.out.println(new String(dataBuffer));//display //Get a digest for the corrupted data array byte[] theDigest = digestIt(dataBuffer); displayBase64(theDigest);//display in Base 64 //Write an object containing the corrupted data // array and the corresponding digest value. writeTheObject(dataBuffer,theDigest); }catch(Exception e){System.out.println(e);} }//end main //-----------------------------------------------------// static void writeTheObject(byte[] objOne,byte[] objTwo){ try{ FileOutputStream fileOut = new FileOutputStream("Security07.obj"); ObjectOutputStream objOut = new ObjectOutputStream(fileOut); objOut.writeObject(objOne); objOut.writeObject(objTwo); objOut.close(); }catch(Exception e){System.out.println(e);} }//end writeTheObject //-----------------------------------------------------// //This method generates and returns a digest for an // incoming arrays of bytes. static byte[] digestIt(byte[] theData){ byte[] theDigest = null; try{ //Create a MessageDigest object implementing // the SHA algorithm from the default provider. MessageDigest messageDigest = MessageDigest.getInstance("SHA"); //Perform the digestion and save the result. Note // that the update() method was not used. theDigest = messageDigest.digest(theData); }catch(Exception e){System.out.println(e);} //Return the digest value to the calling method return theDigest; }//end digestIt() //-----------------------------------------------------// //Method to display an array of bytes in base 64 format static void displayBase64(byte[] data){ BASE64Encoder encoder = new BASE64Encoder(); String encoded = encoder.encodeBuffer(data); System.out.println(encoded); }//end base64Display() //-----------------------------------------------------// }//end class Security07C |
-end-