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.php
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.php
in Example 9-1.
Example 9-1: index.php
- <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.php.
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.php
and
http://developer.netscape.com/docs/manuals/js/client/jsref/index.php
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.php#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.php
has the email functionality added.