Cody Pendant has asked for the wisdom of the Perl Monks concerning the following question:

I've had this thing around for a while and I would like to get my fellow monks' opinions.

I've tried to do it the Right Way, exporting functions and so on, based on Tachyon's tutorial.

It's an encryption module, of sorts -- not one you'd want to use for real-life encryption, but an interesting academic exercise for people interested in Cryptography maybe? Also demonstrates something interesting for learners about substring and negative numbers?

It's enclosed below.

It probably could be tidied a bit, plus it doesn't have an OO interface. I'd like the monks to guide me towards making it OO if you wouldn't mind. I'm a bit wooly when it comes to blessings and objects.

package LewisCarrollCode; use CGI::Carp qw(fatalsToBrowser); use strict; use Exporter; use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); $VERSION = 0.10; @ISA = qw(Exporter); @EXPORT = (); @EXPORT_OK = qw(&lcc_encrypt &lcc_decrypt); %EXPORT_TAGS = ( Both => [qw(&lcc_encrypt &lcc_decrypt)], Encrypt => [qw(&lcc_encrypt)], Decrypt => [qw(&lcc_decrypt)] ); my $upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; my $lower = 'abcdefghijklmnopqrstuvwxyz'; # I didn't do join('' (a..z)) because (in 5.00503) # it caused errors for some reason. sub lcc_encrypt { my ($key_phrase, @clear_text) = @_; unless (@clear_text && $key_phrase) { die "Too few arguments. Syntax: lcc_encrypt(KEYPHRASE,CLEARTEXT)"; } my @key_array = $key_phrase =~ /[a-z]/gi; if (scalar(@key_array) == 0){ die "Keyphrase contains no alphabetic characters."; } my $i = 0; my $output = ''; foreach (@clear_text) { foreach (split ('', $_)) { if (/[a-z]/) { $output .= substr( $lower, ( index($lower, $_) - index($lower, lc($key_array[$i])) ), 1 ) ; # encoded output is the letter in the alphabet found at: # (letter's normal position) minus (letter-position of # the current keyphrase-letter). As substr() is cool with # negative numbers we don't have to worry about using # abs() or mod to fix the number. $i = (($i + 1) % @key_array); } # end of if lowercase elsif (/[A-Z]/) { $output .= substr( $upper, ( index($upper, $_) - index($upper, uc($key_array[$i])) ), 1 ); $i = (($i + 1) % @key_array); } else { $output .= $_; } } } return $output; } sub lcc_decrypt { my ($key_phrase, @cypher_text) = @_; unless (@cypher_text && $key_phrase) { die "Too few arguments. Syntax: lcc_decrypt(KEYPHRASE,CYPHERTEXT)"; } $key_phrase =~ tr/a-z/A-Z/; my @key_array = $key_phrase =~ /[a-z]/gi; if (scalar(@key_array) == 0){ die "Keyphrase contains no alphabetic characters."; } my $i = 0; my $output = ''; foreach (@cypher_text) { foreach (split ('', $_)) { if (/[a-z]/) { $output .= substr( $lower, ( ( index($lower, $_) + index($lower, lc($key_array[$i])) ) % 26 ), 1 ) ; # decoded output is the letter in the alphabet found at: # (letter's normal position) plus (letter-position of # the current keyphrase-letter), mod 26 to stop us # going past the end of the alphabet. $i = (($i + 1) % @key_array); } # end of if lowercase elsif (/[A-Z]/) { $output .= substr( $upper, ( ( index($upper, $_) + index($upper, uc($key_array[$i])) ) % 26 ), 1 ); $i = (($i + 1) % @key_array); } # end of if uppercase else { # neither kind of letter $output .= $_; } } } return $output; } 1; =pod =head1 LewisCarrollCode This is an encryption module. Not a very secure one. I wrote it purely as an exercise in creating a Perl Module. It uses a text keyphrase as the encryption/decryption key for a string + or an array. The way manual encryption/decription with a pen or pencil would occur +is as follows. You'd write the alphabet across your page, and your keyphrase vertically below it. In this case the keyphrase is "PERL": ABCDEFGHIJKLMNOPQRSTUVWXYZ -------------------------- P E R L Then you complete the alphabet on each line, starting with the letter from your keyphrase. Start again with A when you reach Z: ABCDEFGHIJKLMNOPQRSTUVWXYZ -------------------------- PQRSTUVWXYZABCDEFGHIJKLMNO EFGHIJKLMNOPQRSTUVWXYZABCD RSTUVWXYZABCDEFGHIJKLMNOPQ LMNOPQRSTUVWXYZABCDEFGHIJK Then you encrypt by cycling through the lines, each time converting your letter from the one in the lower lines to the one in the top line. So, for example, B<"JAPH"> encoded using the keyphrase B<"PERL"> would + be B<"UWYW">. If you have more letters to encrypt, you would just go back to the top + and start again with the "P" line. Decryption, of course, simply goes the other way. This module implements this encryption scheme in Perl. As you can see, it's not much different to a substitution scheme like ROT-13, only it's not always 13. In fact, if you used as your keyphrase the single letter "n" LewisCarrollCode would B<be> ROT-13. I suppose the longer the keyphrase, the harder it will be to decypher, though I don't really know enough about cryptography to say. =head1 How To Use This Module use LewisCarrollCode qw(:Encrypt); or use LewisCarrollCode qw(:Decrypt); or use LewisCarrollCode qw(:Both); =head1 Functions lcc_encrypt('perl','japh'); will encrypt 'japh' as 'UWYW'. lcc_decrypt('perl','uwyw'); will decrypt'uwyw' as 'JAPH' =head1 Notes The second argument for the functions lcc_encrypt() and lcc_decrypt() can be an array or a scalar, but the first must be a scalar. Keyphrases can contain non-alphabetical characters, to make them easier to remember if needed, but they get stripped. B<"Tom's a-cold"> + can be used as the phrase, but B<TOMSACOLD> will be the actual keyphrase used. This module was suggested by the novel B<Red Shift> by Alan Garner, in which I first learned about this "code". I realise that the term "code" is less correct than the term "encrypti +on" but that's the way it's referred to in the book. =cut

--
“Every bit of code is either naturally related to the problem at hand, or else it's an accidental side effect of the fact that you happened to solve the problem using a digital computer.”
M-J D

Replies are listed 'Best First'.
Re: My First Module: LewisCarrolCode.pm
by sauoq (Abbot) on Jan 30, 2003 at 04:13 UTC

    This old thing?! My first node here was a reply to you on this very same subject. :-)

    I probably wouldn't write this as an OO module myself, but if I had to do it, I'd probably provide a constructor new() that took a single argument (the key phrase) and returned a Crypt::LewisCarrollCode object. I'd provide encrypt and decrypt methods which then took a scalar to encrypt or decrypt. So, you'd be able to do something like this

    my $code = Crypt::LewisCarrollCode->new('This is a pass phrase'); my $encrypted = $code->encrypt("You will never decipher this!"); my $decrypted = $code->decrypt($encrypted);

    You may want to be able to pass other things to your encrypt() and decrypt() methods such as an array, for instance. You might also want them to work incrementally. In other words, you might want the object to maintain state and write your encrypt and decrypt methods such that they always pick up where the last call left off. That would allow you to do things like this:

    my $code = Crypt::LewisCarrollCode->new("this is a pass phrase"); my $encrypted = $code->encrypt('Four score and seven years ago, our ' +); $encrypted .= $code->encrypt('fathers brought forth, upon this cont +inent, '); $encrypted .= $code->encrypt('a new nation, conceived in liberty, ' +); $encrypted .= $code->encrypt('and dedicated to the proposition that + '); $encrypted .= $code->encrypt('"all men are created equal."'); my $Gettysburg_Address = $code->decrypt($encrypted);

    That should give you some ideas for the interface. I'll leave the implementation as an exercise because you want it to be a learning experience anyway.

    -sauoq
    "My two cents aren't worth a dime.";
    
Re: My First Module: LewisCarrolCode.pm
by integral (Hermit) on Jan 30, 2003 at 06:07 UTC
    use CGI::Carp qw(fatalsToBrowser);
    One small point is that you shouldn't put CGI::Carp in your module since it may not always be used in a CGI environment. Instead you should use Carp, and replace your dies with calls to croak, which will make the error reporting name lines in the caller's code and not your own, making debugging much easier for the module user.

    A second point is that in your POD you're using non-standard headings. Standard heading would look like the code below:

    =head1 NAME LewisCarollCode - Short Description here =head1 SYNOPSIS use LewisCarollCode; Place an example usage of your functions here =head1 DESCRIPTION Describe the use and operation of the module
    To get the headings and the look of the POD standard you should have a look at standard modules such as Data::Dumper.

    --
    integral, resident of freenode's #perl
    
Re: My First Module: LewisCarrolCode.pm
by Aragorn (Curate) on Jan 30, 2003 at 07:57 UTC
    Some random things:

    %EXPORT_TAGS = ( Both => [qw(&lcc_encrypt &lcc_decrypt)], Encrypt => [qw(&lcc_encrypt)], Decrypt => [qw(&lcc_decrypt)] );

    Maybe you should change the Both tag into ALL. That's how many other modules export all their public functions. If you add just one other function to the module, you'll have to change the tag, possibly breaking existing code. (Of course, exporting an additionaly function can also break existing code, but still :-).

    my $upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; my $lower = 'abcdefghijklmnopqrstuvwxyz'; # I didn't do join('' (a..z)) because (in 5.00503) # it caused errors for some reason.
    I don't see a comma in the join comment. If I do (on Perl 5.00503):

    $ perl -e "print join('' (a..z))"

    then I get an error, but

    perl -e "print join('', (a..z))"

    Gives me the expected

    abcdefghijklmnopqrstuvwxyz

    And for becoming less "wooly" on blessings and objects:

    (The perldoc.com search function seems to be broken right now).

    Arjen

Re: My First Module: LewisCarrolCode.pm
by CountZero (Bishop) on Jan 30, 2003 at 06:53 UTC

    I suppose the longer the keyphrase, the harder it will be to decypher, though I don't really know enough about cryptography to say.

    If your keyphrase is as long as your message and you use a different keyphrase every-time, your code is impossible to analyze and hence cannot be broken.

    A brute force approach (decrypting with all possible keyphrases) would of course yield the plain text message amongst all possible solutions, but it would not be possible to choose with certainty which message was the original one.

    The method to use a keyphrase only once is known as the "one time keypad"-method (see Why Are One-Time Pads Perfectly Secure?).

    An implementation of a one-time keypad in one line of PERL can be found here.

    The weak point of course is the making and distribution of the keys. If this is done in a deterministic way (or even a pseudo-random way) the cypher is open to analysis and can be broken. A workable solution is suggested here.

    CountZero

    "If you have four groups working on a compiler, you'll get a 4-pass compiler." - Conway's Law

      One-time pad cannot be broken if and only if the key is a real random sequence. Even though your keyphrase is as long as the text, if this keyphrase is not a random sequence, then it is possible to break the cypher.
Re: My First Module: LewisCarrolCode.pm
by thraxil (Prior) on Jan 30, 2003 at 22:38 UTC
    I suppose the longer the keyphrase, the harder it will be to decypher, though I don't really know enough about cryptography to say.

    this cipher is more commonly known as a Vigenere Cipher. how hard it is to crack depends on how long the message is relative to the keyphrase. if the keyphrase is longer than the message (and is only used once, ever), it is a one-time pad and is provably unbreakable. in the more realistic case where the message is longer than the keyphrase so that there are a few repeats, it can be broken very easily by calculating the index of coincidence.

    anders pearson