|
Back
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:
i2l32i5321k34e1245ch456oc12ol234at567e
Remove 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 ht
Bunch 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-19
If 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.
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 rvinegar
meet at midnight
OK. 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 ddlammhk
If 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 VALUE
and TEXT attributes 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 Objects
As small as it is, the Cipher()
constructor introduces a new concept. That is, objects we've created in
other chapters contained only properties. The Cipher()
constructor has a property called chars, but also
has a method named purify().
Properties are easy to assign. Just assign the value
you want to a variable using the this.variable_name
syntax. Assigning methods is a little different. You first define a
function, then use the same this.variable_name
syntax to refer to the function. That's exactly
what happens in the Cipher() constructor. The
script has function purify() defined. Cipher()
has a variable named this.purify referencing the purify
method. Notice that are no parentheses. This identifies a reference. Had this.purify
been set to purify(), the purify()
function would have been called, and this.purify would
be set to whatever the function returned.
Referring to a function within a constructor assigns
a purify() method to any variable set to new
Cipher(). That's what happens with the elements in cipherArray,
as you'll soon see.
No 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: false
or formatted text ready for cipher action. Returning false
will 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 workhorse replace() method at work again:
cleanText = cleanText.replace(/[^a-z0-9\s]/g,'');
JavaScript Technique:
More String Matching and Replacing
You just have to love JavaScript 1.2's regular
expression matching. This application makes more use of it than previous
applications. Let's have another look at the regular expression in line 52.
Although it isn't long, the syntax might be a little
confusing. This regular expression is known as a negated
character set. In other words, anything not
contained within the definition constitutes a match. You can utilize square
brackets in a regular expression to specify a range of characters to include
(or in this case, exclude). Consider this:
This expression matches any of the lowercase letters
of the alphabet. The g indicates that the
search matches all characters in this range, not just the first one
encountered You can include as many ranges as you like.
This expression matches any of the lowercase letters
of the alphabet or any digit or whitespace. However, the cipher application
in this case is interested in anything that does not match these. The
circumflex (^) inside square brackets negates any special characters after
it, which yields our original syntax.
This is the tip of the string-matching iceberg. You
can use these regular expressions to validate and format social security
numbers, email addresses, URLs, phone numbers, zip codes, dates, times, and
more. If you're new to regular expressions, you can get the full reference
of regular expression definitions and special character meanings at
"What's New In JavaScript 1.2" at
http://developer1.netscape.com:80/docs/manuals/communicator/jsguide/regexp.htm.
The 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 return false if either occurs:
if(cleanText.length == 0 || cleanText.match(/^\s+$/) != null) {
return false;
}
As far as knowing which characters qualify, Cipher
uses 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 SubstitutionCipher uses the
same method to replace one character in the chars string
with another. The substitute() function, shown
below, is defined as a method for each instantiation of SubstitutionCipher:
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 is false, 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 is 8,
and chars.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 what
substitute() 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 shiftSum of 45, you would
use shiftSum % chars.length,
which equals 6. chars.charAt(6) is the letter g.
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()
returns chars.charAt(shiftSum) or chars.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 that substitute()
is not a function; it is a method of whatever variable is instantiated as a SubstitutionCipher.
Using this, within this method will refer to any
property of the instantiated variable. Since SubstitutionCipher
inherits all the properties of Cipher, 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 to chars.length.
That's right . . . add. shiftDiff is negative, which
means adding the two together yields a number shiftDiff less
than chars.length, which is the desired index for
deciphering. The code below reflects whether substitute()
uses shiftDiff or shiftDiff
+ chars.length as 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) the substitute()
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 function vigenereAlgorithm().
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 to purify()
doesn't return false, the cipher continues. See
the earlier section on the purify() 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.selectedIndex
In MSIE, though, it looks like this:
document.forms[1].Shift.selectedIndex
TIP: 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.asp
Netscape'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 to document.layers["caesar"].
Now that shiftIdx has been assigned, caesarAlgorithm()
iterates data.length times, calling substitute()
each time and concatenating its return to the once-empty local variable cipherData.
Argument action is passed in each time to
properly indicate to substitute() 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
to substitute() in caesarAlgorithm()
remains constant. With this function, shiftIdx can
(and usually does) change with every call to substitute().
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.value
In MSIE, though, it looks like this:
document.forms[2]. KeyWord.value
Variable 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 the substitute()
method can substitute only characters in chars, the
keyword must contain characters from chars as
well. Acceptable keywords include people, machines,
init2wnit, and 1or2or3.
However, using characters not in chars can still be
acceptable. Remember that purify() 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 the substitute() method, which
receives a fresh copy of data.charAt(i) and shiftIdx,
along with action. 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 Inheritance
As mentioned in the last chapter, JavaScript employs
prototype-based inheritance, not class-based inheritance common to languages
such as Java. Chapter 8's "The JavaScript
Technique: Adding Object Properties" shows
you how to add properties such as strings or numbers to existing objects.
You can also utilize the prototype property of
constructor functions to create inheritance hierarchy. That's what happens
in line 65. SubstitutionCipher inherits all the
properties of Cipher. This lets you leverage the
true power of object-oriented programming (as far as JavaScript is
concerned). You can get more information about JavaScript inheritance at
Netscape's DevEdge Online at:
http://developer1.netscape.com:80/docs/manuals/communicator/jsobj/contents.htm#1030750
Creating 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 OPTION selected in
the first select list on the page. We'll cover that in a moment.
JavaScript Technique: Using Alternate Syntax
As of JavaScript 1.2, you can replace code such as:
with a shortened version like this:
You can also create objects on the fly as follows.
Instead of this:
try this:
Notice that the property and method name-value pairs
are separated by a comma. Both 4.x versions of MSIE and Navigator support
these. Take your pick.
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 the algorithm
property 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 TD with COLSPAN
set 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()
and showSlide() 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 in routeCipher()
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 true or false.
The first one, cipherIdx, comes from document.forms[0].Ciphers.selectedIndex.
It has to be 0 or 1. Whichever it is, the algorithm()
method of the corresponding SubstitutionCipher object
in cipherArray gets the call. If algorithm()
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 OPTION in
the top select list to the first one. This forces the OPTION
selected 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 lone HIDDEN field. 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 the ACTION attribute set to mailto: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.
|