|
|
|
|
JavaScript Application CookbookBy Jerry Bradenbaugh1st Edition September 1999 1-56592-577-7, Order Number: 5777 478 pages, $34.95 |
Chapter 9
Ciphers in JavaScript
Application Features
- Message Encryption Application Using Multiple Ciphers
- Object-Oriented Design Makes Adding Ciphers Easy
- Entertaining Application and Utility for Your Visitors
JavaScript Techniques
- Assigning Methods to Your Objects
- More String Matching and Replacing
- Tapping into JavaScript Object Inheritance
- Using Alternate Syntax
If you just finished the previous chapter, this one will give your brain a little breather. This chapter is lighter and deals with an application based on pure, simple fun--ciphering techniques with JavaScript. The application jumbles text messages into what seems like a bunch of junk, meaningful only to those who possess the key to reveal its secret.
This interface displayed in Figure 9-1 is fairly simple. With the Caesar cipher selected, it's just a paragraph describing the cipher, a select list used to choose a number key, and a text area
to enter text to encipher and decipher.
Figure 9-1. The cipher interface
![]()
The text "JavaScript is the scripting language of choice across the planet, don't you agree?" is entered in the text area. Selecting 6 from the Shift list, then choosing the "Encipher" button yields the scrambled text you see in Figure 9-2. Here it is again:
pg1gyixovz oy znk yixovzotm rgtm0gmk ul inuoik jutz 4u0 gmxkk
Figure 9-2. Using the Caesar cipher
![]()
Choosing "Decipher" returns the text to its original form. Notice that the text is returned in all lowercase. The Vigenere cipher works about the same way. Choose "Vigenere Cipher" from the cipher list at the top, and Figure 9-3 is what you'll see.
Figure 9-3. The Vigenere cipher interface
![]()
With this cipher, there is no select list to choose a number key. This time, there is a field to enter a word or phrase as the key. Check out how the term "code junky" ciphers the original text. It's listed here and also shown in Figure 9-4:
loye1w4sdv lw duo uqumydvx4 zdrpenq2 2i l11s0g dg0852 vvh y5nx2v gswd 8cw dk0yr
Figure 9-4. The Vigenere cipher in action
![]()
Of course, choosing "Decipher" using "code junky" as the key returns the text to meaningful form. Since you might be new to the concept of ciphers, here's a crash course on the subject. It explains cipher basics and offers details about the two ciphers used in the application--the Caesar cipher and the Vigenére cipher.
How Ciphers Work
So, what is a cipher anyway? A cipher is an algorithm or set of algorithms that systematically convert a sender's intended message text to what appears to be meaningless text, which can be converted back to the sender's original message only by authorized recipients. The following terms and definitions will help you understand ciphering and deciphering in general and the code behind them.
The term plaintext refers the sender's original message. The meaning in plaintext is what the sender wants to convey to the recipient(s).
The term ciphertext refers to plaintext whose appearance has been encrypted, or algorithmically changed. Ciphertext becomes plaintext once it has been decrypted.
Many ciphers use one or more keys. A key is string of text or bits used to encrypt or decrypt data. RSA Data Security, Inc. (http://www.rsa.com/ ), a leading encryption technology firm, states that a key determines the mapping of the plaintext to the ciphertext. A key could be just about anything, such as the word "cleveland," the phrase "winners never quit, quitters never win," the binary number 10011011, or even some wild string, such as %_-.;,(<<*&^.
Ciphers in which both the sender and the recipient use the same key to encrypt and decrypt the message are said to be part of a symmetric-key cryptosystem. Ciphers in which data is encrypted and decrypted with a pair of keys--one freely distributed to the public, the other known only to the recipient--are said to be part of a public-key cryptosystem. Ciphers in this application employ an asymmetric-key cryptosystem.
There are hundreds of documented ciphers. Some date back thousands of years, devised by great leaders or scientists of the past; others date back to only last week, devised by some geeky teenager who experienced epiphany after setting a personal high score on Tomb Raider. Whatever the source, ciphers fall into three general categories: concealment, transposition, and substitution.
Concealment ciphers include the plaintext within the ciphertext. It is up to the recipient to know which letters or symbols to exclude from the ciphertext in order to yield the plaintext. Here is an example of a concealment cipher:
i2l32i5321k34e1245ch456oc12ol234at567eRemove all the numbers, and you'll have i like chocolate. How about this one?
Larry even appears very excited. No one worries.The first letter from each word reveals the message leave now. Both are easy, indeed, but many people have crafted more ingenious ways of concealing the messages. By the way, this type of cipher doesn't even need ciphertext, such as that in the above examples. Consider the invisible drying ink that kids use to send secret messages. In a more extreme example, a man named Histiaeus, during 5th century B.C., shaved the head of a trusted slave, then tattooed the message onto his bald head. When the slave's hair grew back, Histiaeus sent the slave to the message's intended recipient, Aristagoros, who shaved the slave's head and read the message instructing him to revolt.
Transposition ciphers also retain the characters of the plaintext within the ciphertext. Ciphertext is created simply by changing the order of the existing plaintext characters. Try this one:
uo yn os dn ep ed yx al ag eh tf oy te fa se htBunch those letters together, then reverse their order. You'll get the message "the safety of the galaxy depends on you."
Substitution ciphers replace each character of plaintext with another character or symbol. Consider this:
9-15-14-12-25-20-8-9-14-11-9-14-14-21-13-2-5-18-19If you substitute each number with the associated letter of the alphabet, you'll reveal the phrase "I only think in numbers." (For example, "I" is the 9th letter of the alphabet, "o" is the 15th, etc.) Substitution ciphers can utilize just about any character set for encryption and decryption. Both ciphers in this application are substitution ciphers.
A Few Words on Cracking the Code
The ciphertext that this application generates can, at first glance, look remarkably complex. In reality, any decent cryptanalyst could break the cipher in a matter of minutes with only a pencil and paper. Fortunately, security is much more ensured by using such algorithms as the RSA, IDEA, and triple DES. I can't show you how to crack those, but I'll give you a hint about why simple substitution and transposition ciphers are so vulnerable.
The primary weapon against these types of ciphers is letter-frequency distribution. That is, some letters show up more than others in everyday conversation in the English language. The most common letters in the English language, from most to least frequent, are E-T-N-R-O-A-I-S. The least common are J, K, Q, X, and Z.[1]
Another way to compromise a simple cipher is to analyze digraphs and trigraphs. A digraph is a two-character string, such as ab or cd. A trigraph is a three-letter string, such as abc or bcd. Digraphs and trigraphs also have high and low frequencies in the English language. The U.S. Army considers the following digraphs most frequent: en, er, re, nt, th, on, and in. The least frequent are df, hu, ia, lt, and mp. For trigraphs, the most common are ent, ion, and, ing, ive, tho, and for. The least common are eri, hir, iet, der, and dre.
The most frequent letters, digraphs, and trigraphs not only hint at what many letters might be, but also indicate what they and surrounding letters probably are not. Consider how many digraphs and trigraphs you use in everyday conversation: is, be, am, or, not, are, yes, the. The list goes on. Even though the ciphers used in this application aren't top quality, they're still a lot of fun, and a great way to keep out the casual nosey intruder.
The Caesar Cipher
Used by Julius Caesar to communicate with his army general, this cipher is one of the first known to be used for securing messages. The algorithm here is simply to shift the letters of the alphabet between 1 and 25 places (from b-z) so that a shift of 3 causes a plaintext letter a to become a ciphertext d, and vice versa. Letters that are shifted past z resume at the beginning. In other words, a shift of 3 converts a plaintext z to a ciphertext c. The number is the key that both sending and receiving parties use to encipher and decipher the message.
Notice that once a key is chosen, each character always has the same corresponding plaintext or ciphertext character associated with it. For example, a shift of 3 means that the plaintext a is always a ciphertext d. That is, there is only one cipher alphabet. The Caesar cipher is said to be monoalphabetic.
The Vigenere Cipher
This cipher was proposed by mathematician Blaise de Vigenere in the 16th century. It is a polyalphabetic cipher because it uses more than one cipher alphabet. In other words, a plaintext a does not always equal a ciphertext d, as the Caesar cipher does with a shift of 3.
Instead of a number, this cipher utilizes a keyword. Suppose you want to cipher the plaintext meet at midnight, and you choose the keyword vinegar. The letters of the keyword are then lined up in succession with the letters of the plaintext, like so:
vine ga rvinegarmeet at midnightOK. V is the 22nd letter in the alphabet. I is 9th. Letters n, e, g, a, and r are 14th, 5th, 7th, 1st, and 18th, respectively. So plaintext letter m is shifted 22, the first e is shifted 9, the second e is shifted 14, and so on. Here's what you get:
hmrx gt ddlammhkIf you think about it, this cipher is like the Caesar cipher on the fly. A new Caesar is performed on every character.
TIP: If you want to learn more about ciphers, you can download a multitude of the once "classified" U.S. Army documents in PDF format at http://www.und.nodak.edu/org/crypto/crypto/army.field.manual/separate.chaps/.
This copy is stored on the web site of the Crypto Drop Box. Check out the home page at http://www.und.nodak.edu/org/crypto/crypto/. You'll find enough resources there to keep you busy for days.
Execution Requirements
This application uses JavaScript 1.2 and DHTML only, so browsers 4.x and higher are allowed to play. There is a lot of string matching and replacement, which makes JavaScript 1.2 really shine.
The Syntax Breakdown
Fortunately, this application requires only two files. Better yet, we'll only be looking at the code in one of them. The two files are index.html and dhtml.js (dhtml.js is covered in Chapter 6, Implementing JavaScript Source Files). Before we look at any code, let's consider a few abstract concepts about how this application might "look." This application is constructed from a very basic object-oriented perspective. The shopping cart in Chapter 8, Shopping Bag: The JavaScript Shopping Cart, covers another application that utilizes object orientation, but the cipher app takes that approach a little further.
There are two ciphers in this application. Each cipher has certain things in common with all other ciphers, no matter what kind of cipher each may be. Remember that there are three basic types of ciphers--concealment, transposition, and substitution. This application contains two substitution ciphers: the Caesar cipher and the Vigenére cipher. Figure 9-5 shows a basic structure of the hierarchy just described.
Figure 9-5. The cipher structure
![]()
Figure 9-6. Extending the cipher structure
![]()
The figure shows that the ConcealmentCipher, TranspositionCipher, and SubstitutionCipher objects inherit everything from the Cipher object, somewhat like a subclass. Therefore, the Vigenére cipher and the Caesar cipher are instances of the SubstitutionCipher object and contain all its properties and methods.
For the sake of intellectual curiosity, let's see how this model can be extended. Figure 9-6 shows how other cipher types can easily be added to the hierarchy without redesigning anything. The bold portion of the structure identifies the part of the hierarchy used in the application.
As you can see, the number of cipher types and individual ciphers can be added to this structure, ad infinitum, without changing any of the existing code of the ciphers currently in place. You can also "subclass" the subclasses. Object-oriented design proves beneficial once again. Keep this in mind as we go through the supporting code in the next few pages. You'll see how easy it is to add more ciphers to the application without having to retool. The "Potential Extensions" section offers a few parting words on more ciphers to add.
Let's take a look at index.html in Example 9-1.
Example 9-1: index.html
- <HTML>
- <HEAD>
- <TITLE>Cipher</TITLE>
- <STYLE TYPE="text/css">
- <!--
- BODY { margin-left: 50 px; font-family: arial; }
- I { font-weight: bold; }
- //-->
- </STYLE>
- <SCRIPT LANGUAGE="JavaScript1.2" SRC="dhtml.js"></SCRIPT>
- <SCRIPT LANGUAGE="JavaScript1.2">
- <!--
- var caesar = '<FONT SIZE=2>Made famous by Julius Caesar, this cipher ' +
- 'performs character shifting (substitution). Plaintext is ' +
- 'enciphered by shifting forward each character of the alphabet a ' +
- 'fixed number of characters.<BR><BR>For example, shifting by 1 ' +
- 'changes plaintext <I>a</I> to <I>b</I>, <I>b</I> to <I>c</I>, ' +
- 'and so on. Plaintext characters at the end of, say, the alphabet, ' +
- 'are enciphered by starting at the beginning. In other words, ' +
- '<I>z</I> becomes <I>a</I>. This application also includes digits ' +
- '0-9. So a plaintext <I>z</I> becomes <I>0</I>, and a plaintext ' +
- '<I>9</I> becomes <I>a</I>. The process is reversed for deciphering.' +
- '<BR><FORM>Shift: ' +
- genSelect('Shift', 35, 0, 0) +
- '</FORM><BR>Note: Caesar was rumored to prefer a shift of 3.';
- var vigenere = '<FONT SIZE=2>Made famous by mathematician Blaise de ' +
- 'Vigenere, the Vigenere cipher can be considered a "dynamic" ' +
- 'version of the Caesar cipher. Instead of shifting each plaintext ' +
- 'character by a fixed number, this cipher shifts characters ' +
- 'according to the character index of a keyword you choose such as ' +
- '<I>dog</I>.<BR><BR>Since <I>d</I>, <I>o</I>, and <I>g</I> are ' +
- 'letters 4, 15, and 7 of the alphabet, each three plaintext ' +
- 'characters are shifted by 4, 15, and 7, respectively. This ' +
- 'application includes digits 0-9. So your keyword can have letters ' +
- 'and numbers.<BR><BR><FORM>Keyword: <INPUT TYPE=TEXT NAME="KeyWord" ' +
- 'SIZE=25></FORM><BR>Note: This cipher has many versions, one of ' +
- 'which was devised by Lewis Carroll, author of Alice in Wonderland.';
- var curCipher = "caesar";
- function Cipher() {
- this.purify = purify;
- this.chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
- }
- function purify(rawText) {
- if (!rawText) { return false; }
- var cleanText = rawText.toLowerCase();
- cleanText = cleanText.replace(/\s+/g,' ');
- cleanText = cleanText.replace(/[^a-z0-9\s]/g,'');
- if(cleanText.length == 0 || cleanText.match(/^\s+$/) != null) {
- return false;
- }
- return cleanText
- }
- function SubstitutionCipher(name, description, algorithm) {
- this.name = name;
- this.description = description;
- this.substitute = substitute;
- this.algorithm = algorithm;
- }
- SubstitutionCipher.prototype = new Cipher;
- function substitute(baseChar, shiftIdx, action) {
- if (baseChar == ' ') { return baseChar; }
- if(action) {
- var shiftSum = shiftIdx + this.chars.indexOf(baseChar);
- return (this.chars.charAt((shiftSum < this.chars.length) ?
- shiftSum : (shiftSum % this.chars.length)));
- }
- else {
- var shiftDiff = this.chars.indexOf(baseChar) - shiftIdx;
- return (this.chars.charAt((shiftDiff < 0) ?
- shiftDiff + this.chars.length : shiftDiff));
- }
- }
- function caesarAlgorithm (data, action) {
- data = this.purify(data);
- if(!data) {
- alert('No valid text to ' + (action ? 'cipher.' : 'decipher.'));
- return false;
- }
- var shiftIdx =
- (NN ? refSlide("caesar").document.forms[0].Shift.selectedIndex : document.forms[1].Shift.selectedIndex);
- var cipherData = '';
- for (var i = 0; i < data.length; i++) {
- cipherData += this.substitute(data.charAt(i), shiftIdx, action);
- }
- return cipherData;
- }
- function vigenereAlgorithm (data, action) {
- data = this.purify(data);
- if(!data) {
- alert('No valid text to ' + (action ? 'cipher.' : 'decipher.'));
- return false;
- }
- var keyword = this.purify((NN ?
- refSlide("vigenere").document.forms[0].KeyWord.value :
- document.forms[2].KeyWord.value));
- if(!keyword || keyword.match(/\^s+$/) != null) {
- alert('No valid keyword for ' + (action ? 'ciphering.' :
- 'deciphering.'));
- return false;
- }
- keyword = keyword.replace(/\s+/g, '');
- var keywordIdx = 0;
- var cipherData = '';
- for (var i = 0; i < data.length; i++) {
- shiftIdx = this.chars.indexOf(keyword.charAt(keywordIdx));
- cipherData += this.substitute(data.charAt(i), shiftIdx, action);
- keywordIdx = (keywordIdx == keyword.length - 1 ? 0 : keywordIdx + 1);
- }
- return cipherData;
- }
- var cipherArray = [
- new SubstitutionCipher("caesar", caesar, caesarAlgorithm),
- new SubstitutionCipher("vigenere", vigenere, vigenereAlgorithm)
- ];
- function showCipher(name) {
- hideSlide(curCipher);
- showSlide(name);
- curCipher = name;
- }
- function routeCipher(cipherIdx, data, action) {
- var response = cipherArray[cipherIdx].algorithm(data, action);
- if(response) {
- document.forms[0].Data.value = response;
- }
- }
- //-->
- </SCRIPT>
- </HEAD>
- <BODY BGCOLOR=#FFFFFF>
- <DIV>
- <TABLE BORDER=0>
- <TR>
- <TD ALIGN=CENTER COLSPAN=3>
- <IMG SRC="images/cipher.jpg">
- </TD>
- </TR>
- <TR>
- <TD VALIGN=TOP WIDTH=350>
- <FORM>
- <SELECT NAME="Ciphers"
- onChange="showCipher(this.options[this.selectedIndex].value);">
- <OPTION VALUE="caesar">Caesar Cipher
- <OPTION VALUE="vigenere">Vigenére Cipher
- </SELECT>
- </TD>
- <TD ALIGN=CENTER>
- <TEXTAREA NAME="Data" ROWS="15" COLS="40" WRAP="PHYSICAL"></TEXTAREA>
- <BR><BR>
- <INPUT TYPE=BUTTON VALUE="Encipher"
- onClick="routeCipher(this.form.Ciphers.selectedIndex,
- this.form.Data.value, true);">
- <INPUT TYPE=BUTTON VALUE="Decipher"
- onClick="routeCipher(this.form.Ciphers.selectedIndex,
- this.form.Data.value, false);">
- <INPUT TYPE=BUTTON VALUE=" Reset "
- onClick="this.form.Data.value='';">
- </FORM>
- </TD>
- </TR>
- </TABLE>
- </DIV>
- <SCRIPT LANGUAGE="JavaScript1.2">
- <!--
- document.forms[0].Ciphers.selectedIndex = 0;
- genLayer("caesar", 50, 125, 350, 200, showName, caesar);
- genLayer("vigenere", 50, 125, 350, 200, hideName, vigenere);
- //-->
- </SCRIPT>
- </BODY>
- </HTML>
The JavaScript source file dhtml.js is the first code interpreted. The code in that file utilizes DHTML to set up the layers and generate select lists on the fly. We'll get to that shortly. The next code of interest comes in lines 14-39. Variables caesar and vigenere are designed and set to the value of HTML strings. Each of these, as you might have guessed, defines an interface layer of a cipher. Everything is static, expect for the call to function
genSelect()in the value of caesar. Here it is:genSelect('Shift', 35, 0, 0)This creates a select list named Shift, which starts at 0, ends at 35, and has Option 0 selected. The
VALUEandTEXTattributes are set to the number used for counting. This code comes straight from Chapter 5, ImageMachine. If you made the JavaScript library that I had suggested in Chapter 6, you should definitely have included this code in it. You'll find this function defined at the bottom of dhtml.js.Defining a Cipher
The next lines of code define all ciphers that are and will be. Lines 43-46 contain the
Cipher()constructor:function Cipher() { this.purify = purify; this.chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; }That's a pretty small constructor. If you expected some huge complex definition with all sorts of differential equations and spherical geometry designed to split the fourth dimension, sorry to let you down.
Cipher()defines ciphers at a really high level. The only two assumptions made about all ciphers in this application is that:
- They all know how to format user data (using its only method), be it plaintext or ciphertext.
- Each cipher knows which characters it will include for ciphering (using its only property).
JavaScript Technique:
Assigning Methods to Your ObjectsNo matter if the data will be enciphered or deciphered, it must conform to certain rules. Here are those rules:
- Each character must be a-z or 1-9. All others will be omitted. Case does not matter.
- Whitespaces will be neither enciphered, or deciphered. Multiple adjacent whitespaces will be reduced to single whitespaces.
- One or more newline characters are converted to single whitespaces.
Nice rules. Simple, too. All we need is something to enforce them. Enter function
purify()in lines 48-57:function purify(rawText) { if (!rawText) { return false; } var cleanText = rawText.toLowerCase(); cleanText = cleanText.replace(/\s+/g,' '); cleanText = cleanText.replace(/[^a-z0-9\s]/g,''); if(cleanText.length == 0 || cleanText.match(/^\s+$/) != null) { return false; } return cleanText }This function returns one of two values:
falseor formatted text ready for cipher action. Returningfalsewill cancel any cipher operation. If rawText contains anything to format,purify()first converts all letters to lowercase. Here's how:cleanText = cleanText.replace(/\s+/g,' ');Using regular expression matching, the
replace()method of the String object searches for all the whitespaces in the entire string, which are replaced by a single whitespace, no matter how many adjacent ones there are. After that,purify()replaces all other characters that are not a-z or 0-9 or single whitespaces with an empty character. This removes all non-qualifying characters. Here is the workhorsereplace()method at work again:cleanText = cleanText.replace(/[^a-z0-9\s]/g,'');JavaScript Technique:
More String Matching and Replacing/[^a-z0-9\s]/g/[a-z]/g/[a-z0-9\s]/g/[^a-z0-9\s]/gThe formatting is now complete. The time has come to check whether there is anything useful remaining to cipher. As long as the formatted string contains at least one character that is a-z or 0-9, everything is fine. However, there are two cases where this is untrue:
- After all the non-qualifying characters have been removed, there are no characters left.
- After all the non-qualifying characters have been removed, only whitespaces are left.
If either is the case, it's time to call off the operation and hold out for better data. Lines 53-55 perform the check. This causes
purify()to returnfalseif either occurs:if(cleanText.length == 0 || cleanText.match(/^\s+$/) != null) { return false; }As far as knowing which characters qualify,
Cipheruses the following string:this.chars = 'abcdefghijklmnopqrstuvwxyz0123456789';Defining a Substitution Cipher
Now that the mother of all cipher objects--
Cipher()--has been defined, let's create a more specific version. That's right--the spec for all substitution ciphers:SubstitutionCipher(). Study lines 59-65:function SubstitutionCipher(name, description, algorithm) { this.name = name; this.description = description; this.substitute = substitute; this.algorithm = algorithm; } SubstitutionCipher.prototype = new Cipher;The assumption for every cipher object is that each one knows how to format any user data. Substitution ciphers contain further assumptions. Here they are:
- Each has a name and description.
- Each uses a general method for substituting characters for both enciphering and deciphering.
- Each has a specific implementation of the general substitution method. This is what makes one substitution cipher different from other substitution ciphers.
- Each SubstitutionCipher object is also a cipher object.
Assigning a name and description to each is pretty simple. Any two strings you pass in when you call
new SubstitutionCipher()will work just fine. Incidentally, the variables caesar and vigenere instantiated earlier with all that HTML will be the description of each. That takes care of the first assumption. Now, what about defining a general substitution method? This method can substitute one character for another. That's it. Each call to this method returns one character, which is a substitute for another.Performing Basic Substitution
Each
SubstitutionCipheruses the same method to replace one character in the chars string with another. Thesubstitute()function, shown below, is defined as a method for each instantiation ofSubstitutionCipher:function substitute(baseChar, shiftIdx, action) { if (baseChar == ' ') { return baseChar; } if(action) { var shiftSum = shiftIdx + this.chars.indexOf(baseChar); return (this.chars.charAt((shiftSum < this.chars.length) ? shiftSum : (shiftSum % this.chars.length))); } else { var shiftDiff = this.chars.indexOf(baseChar) - shiftIdx; return (this.chars.charAt((shiftDiff < 0) ? shiftDiff + this.chars.length : shiftDiff)); } }This method expects three arguments. baseChar is the character that will be replaced by another. shiftIdx is an integer that determines "how much" shift to apply in order to find the correct substitution. action is a Boolean value that specifies whether baseChar should be treated as plaintext or ciphertext. To leave whitespace unchanged, the first line returns baseChar as is if baseChar is indeed a whitespace. Otherwise, this method uses action to determine how to calculate the amount of shift. If action is
true, the enciphering algorithm is used. If action isfalse, the deciphering algorithm is used.Remember that chars contains a string of all the qualifying characters. The enciphering algorithm simply determines the index of baseChar within chars, then chooses the character of chars at that index plus the value of shiftIdx.
Here's an example. Suppose that baseChar is
d, shiftIdx is8, andchars.indexOf(`d')is 3. That brings us to line 70:var shiftSum = shiftIdx + this.chars.indexOf(baseChar);Variable shiftSum equals 11 (8 + 3). So
chars.charAt(11)is the letter l. That is whatsubstitute()would return in this case. That seems straightforward. It is, but suppose baseChar is letter o, and shiftIdx is 30. Check the math. shiftSum now equals 45. The problem is, chars has only 36 characters (a-z and 0-9). Therefore,chars.charAt(45)doesn't exist.When the algorithm reaches the last character of chars, it must "wrap" around and start over with 0, and begin adding again from there. You can use the modulus operator to get the desired effect. Think about it: the modulus operator returns the integer remainder of two operands. Here are several examples:
4 % 3 = 1. Dividing 4 by 3 leaves a remainder of 1.5 % 3 = 2. Dividing 5 by 3 leaves a remainder of 2.6 % 3 = 0. Dividing 6 by 3 leaves no remainder.All you need to do is use the return of the modulus operation. So instead of using a
shiftSumof 45, you would useshiftSum%chars.length, which equals 6.chars.charAt(6)is the letterg. This explains the ensuing code for the enciphering algorithm:return (this.chars.charAt((shiftSum < this.chars.length) ? shiftSum : (shiftSum % this.chars.length)));In this case,
substitute()returnschars.charAt(shiftSum)orchars.charAt(shiftSum % this.chars.length), depending on the size of shiftSum and the length of chars. How about the keyword this? You may be wondering what it is doing there. Keep in mind thatsubstitute()is not a function; it is a method of whatever variable is instantiated as aSubstitutionCipher. Using this, within this method will refer to any property of the instantiated variable. SinceSubstitutionCipherinherits all the properties ofCipher, the instantiated variable "owns" a property called chars.The procedure isn't much different for the deciphering algorithm. The only change is that it subtracts shiftIdx to reach the correct character in chars. In this case, variable shiftDiff is set to the difference of the index of baseChar and shiftIdx, which is as follows.
var shiftDiff = this.chars.indexOf(baseChar) - shiftIdx;Again, this is fairly simple. If shiftDiff is less than 0, however, you run into the same problem as when shiftSum was more than
chars.length-1. The solution is to add shiftDiff tochars.length. That's right . . . add. shiftDiff is negative, which means adding the two together yields a number shiftDiff less thanchars.length, which is the desired index for deciphering. The code below reflects whethersubstitute()uses shiftDiff or shiftDiff +chars.lengthas the index for deciphering:return (this.chars.charAt((shiftDiff < 0) ? shiftDiff + this.chars.length : shiftDiff));Different Substitutions for Different Ciphers
We just examined what all of the SubstitutionCiphers have in common--the
substitute()method. Now let's take a look at what sets them apart. The SubstitutionCipher constructor expects an argument named algorithm. This argument is not a string, a Boolean, a number, or even an object. This argument is a reference to a function that will implement (call) thesubstitute()method in a unique way.For the Caesar cipher, the argument passed in is a reference to function
caesarAlgorithm(). The Vigenere cipher, not surprisingly, receives a reference to functionvigenereAlgorithm(). Let's look at the functions of each.Caesar algorithm
The Caesar algorithm is the easier of the two. Lines 81-94 contain the code:
function caesarAlgorithm (data, action) { data = this.purify(data); if(!data) { alert('No valid text to ' + (action ? 'cipher.' : 'decipher.')); return false; } var shiftIdx = (NN ? refSlide("caesar").document.forms[0].Shift.selectedIndex : document.forms[1].Shift.selectedIndex); var cipherData = ''; for (var i = 0; i < data.length; i++) { cipherData += this.substitute(data.charAt(i), shiftIdx, action); } return cipherData; }The first few lines format the data, then check to see whether there is any qualifying character left over. The string in argument data is formatted by calling
purify()and passing in data as the argument. As long as the call topurify()doesn't returnfalse, the cipher continues. See the earlier section on thepurify()method for details about the method's return.The next thing to do is determine the number of characters by which the user wants to shift the text. That's pretty easy. It comes from the select list in the form on the layer named caesar. I haven't mentioned anything about that yet, but you can jump ahead to lines 180-181 if you want to see the call to create both layers. However, the Navigator DOM differs from the Internet Explorer DOM when it comes to accessing form elements in different layers. The select list has the name Shift.
In Navigator, it looks like this:
document.layers['caesar'].document.Shift.selectedIndexIn MSIE, though, it looks like this:
document.forms[1].Shift.selectedIndexTIP: As you just saw, accessing forms and form elements in layers requires different syntax. The document object model in NN differs from the one in MSIE. This isn't the first time we've seen it in this book. In fact, the majority of code in dhtml.js exists only for creating and manipulating layers in both browsers. Do yourself a favor. Make sure you know when you'll have to accommodate both and when you won't. Until we see a unified DOM, keep the following resources handy.
Microsoft's DHTML Objects:
http://www.microsoft.com/workshop/author/dhtml/reference/
objects.aspNetscape's Style Sheet Reference and Client-Side JavaScript
Reference:http://developer1.netscape.com:80/docs/manuals/communicator/dynhtml/jss34.htm and http://developer.netscape.com/docs/manuals/js/client/jsref/index.htm
Variable shiftIdx accounts for that difference by using the NN variable to determine which of the two to access. The call to
refSlide()in line 88 is a convenient way to refer todocument.layers["caesar"].Now that shiftIdx has been assigned,caesarAlgorithm()iteratesdata.lengthtimes, callingsubstitute()each time and concatenating its return to the once-empty local variable cipherData. Argumentactionis passed in each time to properly indicate tosubstitute()whether to encipher or decipher. After the last iteration,caesarAlgorithm()returns cipherData, which now contains the properly ciphered string.That is the simpler of the two cipher algorithms explained. Let's look at
vigenereAlgorithm(). The primary difference here is that the argument shiftIdx passed tosubstitute()incaesarAlgorithm()remains constant. With this function, shiftIdx can (and usually does) change with every call tosubstitute(). The other difference is that the user chooses a keyword instead of a number. Here are lines 96-119:function vigenereAlgorithm (data, action) { data = this.purify(data); if(!data) { alert('No valid text to ' + (action ? 'cipher.' : 'decipher.')); return false; } var keyword = this.purify((NN ? refSlide("vigenere").document.forms[0].KeyWord.value : document.forms[2].KeyWord.value)); if(!keyword || keyword.match(/\^s+$/) != null) { alert('No valid keyword for ' + (action ? 'ciphering.' : 'deciphering.')); return false; } keyword = keyword.replace(/\s+/g, ''); var keywordIdx = 0; var cipherData = ''; for (var i = 0; i < data.length; i++) { shiftIdx = this.chars.indexOf(keyword.charAt(keywordIdx)); cipherData += this.substitute(data.charAt(i), shiftIdx, action); keywordIdx = (keywordIdx == keyword.length - 1 ? 0 : keywordIdx + 1); } return cipherData; }The first five lines are the same as in
caesarAlgorithm(). They do the same formatting and validating. The next few lines perform similar work on the keyword. The keyword comes from the form field located on the layer named vigenere. Remember that we have to accommodate both Navigator and MSIE DOMs.In Navigator, it looks like this:
document.layers['vigenere'].document. KeyWord.valueIn MSIE, though, it looks like this:
document.forms[2]. KeyWord.valueVariable keyword then is assigned as follows:
var keyword = this.purify((NN ? refSlide("vigenere").document.forms[0].KeyWord.value : document.forms[2].KeyWord.value));Notice that the
purify()method is used again. It is designed for plaintext and ciphertext, but the demands for the keyword are very similar. Since thesubstitute()method can substitute only characters in chars, the keyword must contain characters fromcharsas well. Acceptable keywords include people, machines, init2wnit, and 1or2or3. However, using characters not in chars can still be acceptable. Remember thatpurify()removes all characters that aren't a-z or 0-9, and replaces all newline and carriage return characters and multiple whitespaces with single whitespaces. While the user might enter 1@@#derft as a keyword,purify()formats that string and returns 1derft,and that contains qualifying characters. Now consider a keyword with whitespaces, say all the spaces in between. This contains qualifying characters, except for those whitespaces. Line 110 removes them:keyword = keyword.replace(/\s+/g, '');The bottom line is: as long as there is at least one qualifying character in the keyword, that is what will be used in
vigenereAlgorithm().How shiftIdx Changes
The plaintext (or ciphertext) and the keyword have been formatted. All that remains is to substitute each of the characters accordingly. By definition of the Vigenére cipher, each character of text is enciphered or deciphered according to the index of the next character in the keyword. This brings us to lines 111-118:
var keywordIdx = 0; var cipherData = ''; for (var i = 0; i < data.length; i++) { shiftIdx = this.chars.indexOf(keyword.charAt(keywordIdx)); cipherData += this.substitute(data.charAt(i), shiftIdx, action); keywordIdx = (keywordIdx == keyword.length - 1 ? 0 : keywordIdx + 1); } return cipherData;Using variable keywordIdx starting at 0, we can get the index of each keyword character as follows:
keyword.charAt(keywordIdx)For each character of data (the plaintext or ciphertext), shiftIdx is set to the index of chars at
keyword.charAt(keywordIdx). Variable cipherData is then set equal to itself plus the return of thesubstitute()method, which receives a fresh copy ofdata.charAt(i)and shiftIdx, along withaction. Incrementing keywordIdx by 1 afterwards sets things up for the next iteration.Each SubstitutionCipher Is Also a Cipher
Since all ciphers, no matter what kind they are, must have the same basic characteristics, the SubstitutionCipher constructor must inherit all the properties of Cipher. That takes place in one line:
SubstitutionCipher.prototype = new Cipher;Now each instantiated SubstitutionCipher object has a property called chars and a method called
purify(). Every SubstitutionCipher then, is a more specific version of a Cipher.JavaScript Technique:
Tapping into JavaScript Object InheritanceCreating Each Instance of SubstitutionCipher
Up to this point, we've seen how the two ciphers work. Now it's time to examine how to create the objects that represent the two ciphers and how to construct the interface for using them. Creating the objects takes only four lines. Here they are, lines 121-124:
var cipherArray = [ new SubstitutionCipher("caesar", caesar, caesarAlgorithm), new SubstitutionCipher("vigenere", vigenere, vigenereAlgorithm) ];Variable cipherArray is set to an array. Each of the elements is a SubstitutionCipher. Why put them in an array? The reason is that the application knows which cipher to use according to the
OPTIONselected in the first select list on the page. We'll cover that in a moment.JavaScript Technique: Using Alternate Syntax
var myArray = new Array(1,2,3);var myArray = [1,2,3];function myObj() {this.name="A New Object";this.description = "Old School Object";}var objOne = new myObj();var myObj = {name: "A New Object", description: "New School Object"};For now, notice that each call to the
SubstitutionCipher()constructor passes with it the expected strings, a name and a description, and a reference to a function, which will be assigned to thealgorithmproperty of each SubstitutionCipher object created. That creates the objects. Let's look at the interface. This happens between the BODY tags:<DIV> <TABLE BORDER=0> <TR> <TD ALIGN=CENTER COLSPAN=3> <IMG SRC="images/cipher.jpg"> </TD> </TR> <TR> <TD VALIGN=TOP WIDTH=350> <FORM> <SELECT NAME="Ciphers" onChange="showCipher(this.options[this.selectedIndex].value);"> <OPTION VALUE="caesar">Caesar Cipher <OPTION VALUE="vigenere">Vigenére Cipher </SELECT> </TD> <TD ALIGN=CENTER> <TEXTAREA NAME="Data" ROWS="15" COLS="40" WRAP="PHYSICAL"></TEXTAREA> <BR><BR> <INPUT TYPE=BUTTON VALUE="Encipher" onClick="routeCipher(this.form.Ciphers.selectedIndex, this.form.Data.value, true);"> <INPUT TYPE=BUTTON VALUE="Decipher" onClick="routeCipher(this.form.Ciphers.selectedIndex, this.form.Data.value, false);"> <INPUT TYPE=BUTTON VALUE=" Reset " onClick="this.form.Data.value='';"> </FORM> </TD> </TR> </TABLE> </DIV>This code creates a two-row table. The top row houses the graphic in a
TDwithCOLSPANset to 2. The bottom row contains two data cells. The one at the left contains a single select list, and looks like this:<SELECT NAME="Ciphers" onChange="showCipher(this.options[this.selectedIndex].value);"> <OPTION VALUE="caesar">Caesar Cipher <OPTION VALUE="vigenere">Vigenére Cipher </SELECT>This list determines which cipher interface is currently displayed. Since there are only two, it's either one or the other. The onChange event handler calls the
showCipher()function, passing in the value of the option currently selected. This function is pretty short. You'll find it in lines 126-130:function showCipher(name) { hideSlide(curCipher); showSlide(name); curCipher = name; }The code inside might look familiar. It hails from previous chapters like Chapter 3, The Interactive Slideshow, or Chapter 6. You'll find functions
hideSlide()andshowSlide()in dhtml.js. Refer to Chapter 3 for detailed coverage.Notice that the data cell is set to a width of 350 pixels. Other than a select list, that data cell is pretty empty. Fortunately, two layers will fill in that available browser real estate. You can see the calls to create them in lines 180-181. Function
genLayer()creates the cipher layers and is also in dhtml.js. This, too, is a function from the past and won't be covered here:genLayer("caesar", 50, 125, 350, 200, showName, caesar); genLayer("vigenere", 50, 125, 350, 200, hideName, vigenere);This creates the text displays for each cipher, along with the additional select list for the Caesar cipher and the text field for the Vigenere cipher. As just mentioned, you can change the option between Caesar cipher and Vigenére cipher in the top select list, which then displays the proper cipher layer.
As for the other data cell in the bottom table row, it contains a text area and three buttons. Here they are again in lines 161-170:
<TEXTAREA NAME="Data" ROWS="15" COLS="40" WRAP="PHYSICAL"></TEXTAREA> <BR><BR> <INPUT TYPE=BUTTON VALUE="Encipher" onClick="routeCipher(this.form.Ciphers.selectedIndex, this.form.Data.value, true);"> <INPUT TYPE=BUTTON VALUE="Decipher" onClick="routeCipher(this.form.Ciphers.selectedIndex, this.form.Data.value, false);"> <INPUT TYPE=BUTTON VALUE=" Reset " onClick="this.form.Data.value='';">The textarea field holds the plain text (or ciphertext). The "Encipher" button causes the text contained within it to be enciphered. It's the reverse for the "Decipher" button. Both call the same function,
routeCipher(). Both pass in the value of the textarea field. The difference is that the last argument is true for one and false for the other.Choosing the Right Cipher
Choosing the right cipher is easy. The correct cipher always corresponds with the index of the top select list in the form and the index of
cipherArray. You can see this inrouteCipher()shown here:function routeCipher(cipherIdx, data, action) { var response = cipherArray[cipherIdx].algorithm(data, action); if(response) { document.forms[0].Data.value = response; } }This function accepts three arguments. We've already discussed the last two. data is the text in the textarea, and action is either
trueorfalse. The first one, cipherIdx, comes fromdocument.forms[0].Ciphers.selectedIndex. It has to be 0 or 1. Whichever it is, thealgorithm()method of the corresponding SubstitutionCipher object in cipherArray gets the call. Ifalgorithm()returns a non-false value, it must be qualified enciphered (or deciphered) text.A Final Thought
You've probably realized by now, but the code in line 179:
document.forms[0].Ciphers.selectedIndex = 0;simply resets the selected
OPTIONin the top select list to the first one. This forces theOPTIONselected to match the cipher layer in view, even if the user reloads the page.Potential Extensions
While this application is cool to play with as is, the next level is to send it in email. You can do that in three easy steps. First, copy the following function, and paste it between your SCRIPT tags:
function sendText(data) { paraWidth = 70; var iterate = parseInt(data.length / paraWidth); var border = '\n-------\n'; var breakData = ''; for (var i = 1; i <= iterate; i++) { breakData += data.substring((i - 1) * paraWidth, i * paraWidth) + '\r'; } breakData += data.substring((i - 1) * paraWidth, data.length); document.CipherMail.Message.value = border + breakData + border; document.CipherMail.action = "mailto:someone@somewhere.com\?subject=The Secret Message"; return true; }This performs some last millisecond formatting before sending the email. The formatting inserts carriage returns every paraWidth characters. This ensures that the email message that the recipient receives isn't one line of text 40 miles long. The next thing to do is add the second form required. Insert this code after the closing FORM tag in the current document:
FORM NAME="CipherMail" ACTION="" METHOD="POST" ENCTYPE="text/plain" onSubmit="return sendText(document.forms[0].Data.value);"> <INPUT TYPE=HIDDEN NAME="Message"> <INPUT TYPE=SUBMIT VALUE=" Send "> </FORM>This form, named
CipherMail, contains a loneHIDDENfield. The last thing to do is change the form references in the cipher algorithm functions.Change lines 87-89:
var shiftIdx = (NN ? refSlide("caesar").document.forms[0].Shift.selectedIndex : document.forms[1].Shift.selectedIndex);to this:
var shiftIdx = (NN ? refSlide("caesar").document.forms[0].Shift.selectedIndex : document.forms[2].Shift.selectedIndex);Then lines 102-104 from this:
var keyword = this.purify((NN ? refSlide("vigenere").document.forms[0].KeyWord.value : document.forms[2].KeyWord.value));to this:
var keyword = this.purify((NN ? refSlide("vigenere").document.forms[0].KeyWord.value : document.forms[3].KeyWord.value));You need to make these changes because you added another form to the hierarchy in the previous step.
sendText()sets the value of this hidden field to the value of whatever text is entered in the textarea.sendText()then submits this form, which has theACTIONattribute set tomailto:your_e-mail@your_mail_server.com. Figure 9-7 shows what the message looks like when it arrives. That's the view from my Hotmail account. Upon receipt, the user can cut and paste the text between the dashed lines, then decipher the message with the previously agreed-upon cipher and key. Now your visitors are using encrypted mail, and you're the genius behind it!
Figure 9-7. The encrypted email
![]()
P.S. This will work only if the user has the NN or MSIE email client correctly configured, which is most likely the case.
P.S.S. \ch09\cipher2.html has the email functionality added.
Back to: JavaScript Application Cookbook
© 2001, O'Reilly & Associates, Inc.