So- admit it... at least when you were 9 years old, you thought secret codes, as used by spies, were pretty cool?...
Or maybe, really, you could care less about encryption/ decryption, but have realized that if you knew about arrays, you could start "building your (mental) muscles" so that you can avail yourself of the wondrous things that arrays make easy?
You can download the final sourcecode. A copy of the .exe is present in the .zip file.
Either way, this essay is for you. For the array learners, humor me: The codes stuff is just "grist for the mill". For the encryption enthusiasts, arrays are a useful programming tool.
I'm afraid that in this essay, we won't go much beyond a simple substitution code. We will, eventually, at least be using a poly-alphabetic substitution code!
Arrays are collections of things, distinguished from one another by numbers.
If you have a post office box, you go to the post office to collect your mail. On the wall there are a lot of boxes. Each has a number on it. Perhaps your address is PO Box 191. And perhaps your friend's box is number 428.". What you have is an ARRAY of post office boxes. They're all more or less the same, except for the number on them.
The first and simplest place you see arrays in computer programming is arrays of variables.
Even before you ever use a variable, you may have been using variables named, say, bNum0, bNum1, bNum2, etc. (Computer people, by the way, for very good reasons, always "count from" zeros.) This is a bit like an array... but isn't one, quite.
Assuming that you have set them up as byte-type variables, you could say...
b0:=52; b1:=27; b3:=68; b4:=12;
(I'm not sure WHY you would want to store those four numbers, but stay with me, apart from that!)
If you knew about arrays, you would say...
b[0]:=52; b[1]:=27; b[3]:=68; b[4]:=12;
Pretty similar, really!
Until we come to wanting to send them to, say, a TMemo object for a user's wants...
procedure DisplayThem; begin meForUser.lines. add(inttostr(b0)); meForUser.lines. add(inttostr(b1)); meForUser.lines. add(inttostr(b2)); meForUser.lines. add(inttostr(b3)); end;//DisplayThem
... isn't too bad... when there are only four to pass.
Here's how you would do it if the values were held in the elements of an array...
procedure DisplayThem; var iCount:integer; begin for bCount:=0 to 3 do begin meForUser.lines. add(inttostr(b[bCount])); end;//for end;//DisplayThem
Now.. I admit... at first, being more "abstract", the latter isn't the most transparent solution.
But study it. I REALLY isn't so very abstruse.
And I admit... for four numbers, there's not a lot in it.
But there WILL be times when you want to display, say, 50 figures. Even with copy/paste, do you really want to do....
meForUser.lines. add(inttostr(b0));
... fifty times over???
And even more unanswerable, what if... and it DOES happen... you don't always want to list ALL of the numbers every time? Suppose sometimes you want all fifty, but other times you want, say, the 23rd through the 32nd? Or the alternate ones, starting with b0?
(Take a moment, and try to write procedures for those two cases! (If you know about "modulo", it will help in the second case... but it isn't really necessary.))
How have you lived so long without arrays? Fear not, you are about be become one of the cognoscenti!
Major digression... may I be preserved from the internet! All I wanted was to spell that word properly for you. I DID "know" the word. Could I find it??? Here are some of the things I went past to get to cognoscenti...
"Which one sentence is correct: "For those who know" or "For those who knows"? Google gives me about the same number (1 070 000 vs 783 000) of search results."
"Lao Tsu- '0Those who know do not speak. Those who speak do not know.'"
9 different ways to say 'smile'"(!!)
You see how hard I work for you!
Right! Back to arrays and codes...
Let's start with how you would "do" bNum0, bNum1, bNum2... which, remember, is not an array, even though it has similarities.
We'd make those variables available with...
bNum0,bNum1,bNum2:byte;
... somewhere in our code, in a block starting with the word "var".
Right? You knew that.
Sometimes the variable are global. Sometimes they are local. "Local" can be a relative term... there are levels of localness. All irrelevant to us here, because just as bNum0, bNum1, bNum2 can be global or some degree of local, so can bNum[0], bNum[1], bNum[2]. (The right word for "localness" is "scope", as in: "The scope of this variable is global", or "the scope of this variable is local to the procedure DisplayThem". (iCount, earlier, was local to the procedure DisplayThem.)
To declare the array bNum[0], bNum[1], bNum[2], bNum[3], you use...
bNum: array[0..3] of byte;
Again... because we are "crawling" here, getting ready to walk, then run, the power of arrays may not be evident.
I could do the following, too...
bNum: array[0..500] of byte;
... but I'd hate to have to do a comprehensive list for you all of the places to store a number this second example would create!
It was easy to show you all of the elements of the array set up by bNum: array[0..3] of byte;... but all of the elements of created by the second declaration? Not so easy!
bNum[2], for example, is "a variable", by the way... just as bNum2 was a variable. It's just that bNum2 is also an element of an array, and as such it is easier to use it for some things. As we shall see!
We'll put "arrays" to one side for a moment.
Now for a bit about encryption. (What we're doing isn't properly called using codes.) We'll return to arrays later.
We'll start with a really "dumb" system. It will only be about to deal with five letters... A,B,C,D and E.
If the "message" is "AACBBA, our system will make it "bbdccb". A becomes b, C becomes d, B becomes c. Duh.
What does E become? a. (Our typewriter only has 5 keys.)
The START of a program for that would be as below.
"sPlaintext" holds the characters of the message we want to encrypt, (AACBBA in our example.) "sEncrypt", by the end, holds the encrypted version of the plaintext we started with.
begin sPlaintext:='AACBBA'; iLast:=length(sPlaintext);//**1 sEncrypt:=''; for iLetter:=0 to iLast-1 do begin chTmp:=chCharAtPosn(iLetter); if chTmp='A' then sEncrypt:=sEncrypt+'b'; if chTmp='B' then sEncrypt:=sEncrypt+'c'; if chTmp='C' then sEncrypt:=sEncrypt+'d'; if chTmp='D' then sEncrypt:=sEncrypt+'e'; if chTmp='E' then sEncrypt:=sEncrypt+'a'; end;//for... showmessage(sEncrypt); end;
The old "computer people count from zero" thing causes a minor headache along the way.
Even a programmer will answer 6 to "How long is 'AACBBA'.?"
And Lazarus's built-in function "length" takes the same view. Line "//**1" fills iLast with 6.
But the function we must write (we'll come to that), chCharAtPosn, expects to be told the "number" of the letter you want fetched. The "first" being "number 0", and... count it on your fingers... the last in a string SIX characters long being "number" FIVE.
Hence the "-1" in the "for iLetter=...
Tedious. Not "rocket science". But still a nuisance.
ANYWAY... moving on...
We've made a start. We have the heart of an encryting program.
How would it look, in practice?
A crude "starter version" might have an edit box called ePlaintext, a button called buEncrypt, and an edit box called eEncryptedVersion.
The user types some text into ePlainText, clicks the button, and the encrypted version appears in... wait for it!... dEncryptedVersion!
And you thought that making an encryption machine would be hard?
Yeah but.. it isn't very capable yet. And we haven't seen how to do chCharAtPosn. And... worst of all... what we have so far doesn't seem like it can be expanded sensibly. (And, oh by the way, the "encryption" might take an 8 year old about 5 minutes to crack, and one he/ she knew how it was done, he might be able to read messages without even needing to use a pencil to work out the decryption before doing the reading.
But all is not lost! I DID know where I was going, and there IS a way forward... with arrays! Remember arrays?
Easy thing first: "chCharAtPosn"... it is merely...
result:=sPlaintext[iPosn+1];
Yes! VERY like an array, itself. No coincidence. But just forget about that for now. It would be a distraction. The "+1" is tied up with the "2nd letter is the one computer people call "number 1" business. AND with the fact that HERE, the computer is counting as "the man in the street" does... if sTmp holds "ABCD", then "sTmp[2]" will be "B", not "C". (A moment trying to "see" that is probably well spent, but don't struggle too much with it, if why doesn't become clear to you easily. But do keep when you're counting from zero, and when you're counting from 1 clear in your head. (A failure to do that cost me about two hours while writing this!))
Bottom line: charCharAtPosn gives us the character at a given position in the sPlaintext. (And yes, that's "bad programming", as it accesses a global variable inside the function. It would be better to pass both the index (number) of character we want AND the string we want the function to look in. Of to just use the sPlaintext[x] approach, and do without the function.)
To make this expandable, so that we can deal with a larger set of "allowed" characters, we're going to create and fill and array. We are also going to define what characters can be encrypted. Let's say we make A-Z and the space character encryptable. But forget to provide for digits. And then a user says "Encrypt "SEND 1000 GALLONS OF DIESEL FOR THE TANKS."
In the improved version we are working towards, most of that would be encrypted... but not the "1000". (Or the "." at the end!) They would pass through, though. Wouldn't be "lost". (In the previous, first draft version, if the user tried to encrypt "A23B", the output would be "bc"... the A and B encrypted, and the 2 and 3 just "left out".
Our new string will be called sAllowedCharacters, and would look like...
sAllowedCharacters:='ABCDEFGH'+ 'IJKLMNOPQRSTUVWXYZ ';
(Note the space at the end.)
For now, we are going to work on our encryption program... but, for now, it won't encrypt !!
If we ask to see "ABC" encrypted, our program, for now, should give "ABC". (To do that should have been a 20 minute exercise for me. It took me 2 hours because of "a little detail"... one that I will steer you clear of!
We're going to create a look up table.
Let's say that we are still dealing with a program that only aspires to encrypting the characters in this set: ABCDE.
For the moment, our look up table will be like this...
For... Use a... A A B B C C D D E E
As I said: For the moment we are not looking to change the letters as we "encrypt" them.
ONCE WE GET THE REST WORKING, we'll change the table. We might make it...
For... Use a... A B B C C D D E E A
If everything else is in order, then just changing the table changes the way the plaintext is encrypted!
"Inside our program", here's how the table will be built.
We will use an array, based on a string which lists the allowed characters, in other words the ones that the program will encrypt.
For just ABCD and E, we'd have...
sAllowedCharacters:='ABCDE';
... and we'd have an array...
chEncryptsAs[A]='A'; chEncryptsAs[B]='B'; chEncryptsAs[C]='C'; chEncryptsAs[D]='D'; chEncryptsAs[E]='E';
(This, remember, is for the early version, where "A" encrypts as "A", "B" as "B", etc.)
The following will fill the array from the string:
for iTmp:=1 to length(sAllowedCharacters) do begin chEncryptsAs[iTmp-1]:= sAllowedCharacters[iTmp]; end;//for...
That annoying "-1" arises because to get the first character of the string, you use "sAllowedCharacters[1]", but we want to store what that should be translated into in chEncryptsAs[0].
The reasons for that "we want to..." are a little obscure... but they are "real". You can skip over the discussion, if you like, but here it is...
At a very basic level, computers store things as 8 bit binary numbers. As such, they can be zero to 255... which is two hundred and SIX different numbers.
Byte-type variable can store zero to 255 in them. If we want an encrypting program which can encrypt ANY of the 256 numbers, and we want to use a byte-type variable to specify which element of the array chEncryptsAs[] we're going to use to encrypt a particular character with, then we'll need to use chEncryptsAs[0] for one of them, or we won't have enough elements in our array.
Right! Stop skimming. The "why we want to..." stuff ends here.
Before the following will make sense, you have to know that I've "fancied things up" a bit. Now the string to be encrypted is entered by the user in an edit box, which is called ePlainText. And there's a button, called buEncrypt. When the user clicks that, the text in ePlainText is encrypted, with the encrypted version appearing in eEncryptedVersion.
Simples! Here's the code!...
unit lt2n_arrays_codes_simplest_u1; {$mode objfpc}{$H+} interface uses Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls; const kVers='15 Nov 18, 22:40'; kMaxChars=255; type { Tlt2n_arrays_codes_simplest_f1 } Tlt2n_arrays_codes_simplest_f1 = class(TForm) buEncrypt: TButton; ePlainText: TEdit; eEncryptedVersion: TEdit; procedure buEncryptClick(Sender: TObject); procedure FormCreate(Sender: TObject); private { private declarations } chEncryptsAs:array [0..kMaxChars] of char; sPlaintext,sEncrypt,sAllowedCharacters:AnsiString; public { public declarations } end; var lt2n_arrays_codes_simplest_f1: Tlt2n_arrays_codes_simplest_f1; implementation {$R *.lfm} { Tlt2n_arrays_codes_simplest_f1 } procedure Tlt2n_arrays_codes_simplest_f1. FormCreate(Sender: TObject); var iTmp:integer;//Must be type integer //to accomodate 256 characters to be //allowed by the encrypting scheme, //because the string holding the //allowed characters is numbered //from "1". begin application.title:='SimpleCode'; lt2n_arrays_codes_simplest_f1. caption:='Simple Code, vers '+kVers; sAllowedCharacters:='ABCDEFG'+ 'HIJKLMNOPQRST'+ 'UVWXYZ ';// (Note the space at //the end... it's there so //that "space" can be encrypted, //sent with the message) //Fill the "chEncrptsAs" array... // initially with the characters // in the set of allowed characters //Like this, A "encrypts" to A, B to B // and so on. But we will "fix" that, // later! for iTmp:=1 to length(sAllowedCharacters) do begin chEncryptsAs[iTmp-1]:= sAllowedCharacters[iTmp]; end;//for... end;//FormCreate procedure Tlt2n_arrays_codes_simplest_f1. buEncryptClick(Sender: TObject); var iLetter,iLast:integer; chTmp:char; iPosnInAllowedChars:integer;//Even //though this is designed "only" //to be able to encrypt 256 characters, //iPosnInAllowedChars may be 256 in //a special case. It would happen from //iPosnInAllowedChars:= // pos(chTmp,sAllowedCharacters); //when chTmp were whatever the // last allowed character was, as // defined in sAllowedCharacters. //So: iPosnInAllowedChars must be // of type "integer", even though // //almost// all of the time, // "byte" would be good enough. begin sPlainText:=ePlainText.text; iLast:=length(sPlainText); //If sPlainText is "ABC", // length(sPlainText) will be 3. sEncrypt:=''; for iLetter:=1 to iLast do begin chTmp:=sPlaintext[iLetter]; //if sPlainText is "ABC", //sPlaintext[1] will be "A" iPosnInAllowedChars:= pos(chTmp,sAllowedCharacters); //If you say pos('A','ABC'), you get ** 1 ** //If you say pos('x','ABC'), you get ZERO. //Apart from anything else, pos needs a // way to say "couldn't find it.") if (iPosnInAllowedChars<>0) then begin sEncrypt:= sEncrypt+ chEncryptsAs[iPosnInAllowedChars-1]; end//no ; here else begin sEncrypt:=sEncrypt+chTmp;//Characters //not provided for in //sAllowedCharacters go //through untouched. end;//of else of if. end;//for... eEncryptedVersion.text:=sEncrypt; end;//buEncryptClick end.
Play with that. Satisfy yourself that it works... and that you understant what the bits of the code do.
Okay. Our "encryption" is BORING so far.
We can fix that.
First, give the app a global chTmp, type "char";
In the FormCreate handler, at the bottom, just after...
for iTmp:=1 to length(sAllowedCharacters) do begin chEncryptsAs[iTmp-1]:= sAllowedCharacters[iTmp]; end;//for...
... add...
//Start by storing the first element of // the array chEncryptsAs[], temporarily... chTmp:=chEncryptsAs[0]; //Now overwrite the first element with the //second, the second with the 3rd, etc, //all the way to the last-but-one... iTmp:=length(sAllowedCharacters); for iTmp:=0 to length(sAllowedCharacters)-2 do begin chEncryptsAs[iTmp]:=chEncryptsAs[iTmp+1];; end;//for... //chEncryptsAs[sAllowedCharacters-1] is the //LAST element of the array, remember. //-- //Now fill the last element with what WAS in //the first element, before you shifted everything //along!... chEncryptsAs[length(sAllowedCharacters)-1]:=chTmp;
NOW if you try to encrypt "ABC" you get "BCD!
Still not "GCHQ Ultra", but we are making progress!
ABC was pretty poor test data. Try ABC XYZ BBB123. (That should give BCDAYZ ACCC123.) It is better test data because it "wraps around" the end of the look-up table, and it checks that the code deals with characters that aren't provided for, e.g., for the moment, 123.
Want to provide for them? JUST add them to...
sAllowedCharacters
Everything else will just "fall into place". THAT's the sign of a Good Program. You can make sensible changes with minimal effort. It is not about being lazy. If the change is "minor" to effect, the ways for it to Go Wrong are limited.
You can download the final sourcecode. A copy of the .exe is present in the .zip file.
So, hopefully, you have a better understanding of what arraysare, how they are used? And hopefully the simple "make an encrypting engine" was at least mildly "amusing"?
Admittedly our encrypting is pretty basic... but the program is a good start on things that would be MUCH harder to crack.
So... what could you do, to make it harder to crack?
But before that, can you add a button to what we have already, to allow for something in the eEncryptedVersion edit box to be DE-crypted?
(Hint: You might want to build another array, maybe call it "chDecryptsAs".)
When you've got that working... find other ways to "scramble" what is in chEncryptsAs. Every time you move things about in that array, the result of the encryption process changes.
But better yet, build in a way so that AS A MESSAGE IS ENCRYPTED, the chEncryptsAs array changes. But you'll have to do it in a way that can be copied, in reverse, in the DE-crypting program, or no one will be able to read the encrypted message. Not much point in a message, if no one can read it!
Search across all my sites with the Google search...
|
Page tested for compliance with INDUSTRY (not MS-only) standards, using the free, publicly accessible validator at validator.w3.org. Mostly passes. There were two "unknown attributes" in Google+ button code. Sigh.
....... P a g e . . . E n d s .....