Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Hi everyone

Sorry for posting all my code but I wanted you to be able to load it up fairly quickly and see what's happening.

Is is a very basic "roll your own basic encryption by s/// chars". I created a hash with my randomoness. When you load the application it asks you to encrypt or decrypt, if you encrypt and encrypt a file you actually have it'll perform an encryption. That part works.

However, when you then go back and choose to decrypt the file you just created it doesn't decrypt. It creates a new file and the only ouput I'm getting is a "^" for every character in the file. No other letters, numbers or characters. The output is just carrets.

What am I doing wrong? I assumed I could just reverse my map with s/$map{$key}/$key)/ but that doesn't seem to be the case.

Can someone see what I'm doing wrong? This is for a class and I know there are better ways to do it but this is what they're looking for.

#!/usr/bin/perl use warnings; use strict; #map contains our character mapping to expedite character replacement/ +encryption my %map = ( "a" => "i", "A" => "j", "b" => "s", "B" => "4", "c" => "S", "C" => 'Y', "d" => "u", "D" => "t", "e" => "a", "E" => "O", "f" => "&", "F" => " ", "g" => "A", "G" => "v", "h" => "g", "H" => "J", "i" => "%", "I" => "B", "j" => "\$", "J" => "T", "k" => "K", "K" => "k", "l" => "@", "L" => "#", "m" => "!", "M" => "b", "n" => "0", "N" => "M", "o" => "q", "O" => "l", "p" => "r", "P" => "R", "q" => "U", "Q" => "Q", "r" => "n", "R" => "C", "s" => "9", "S" => "G", "t" => "8", "T" => "L", "u" => "i", "U" => "7", "v" => "h", "V" => ",", "w" => "H", "W" => "P", "x" => "6", "X" => "m", "y" => "c", "Y" => "x", "z" => "5", "Z" => "w", "1" => "F", "2" => "y", "3" => "z", "4" => "Z", "5" => "p", "6" => "f", "7" => "W", "8" => "v", "9" => "3", "0" => "E", "!" => "d", "@" => "2", "#" => "1", "\$" => "e", "%" => "D", "^" => ".", "&" => "n", "," => "x", "," => "o", " " => "~" ); print "\n\nWould you like to Encrypt or Decrypt a file [e/d]? "; my $action = <STDIN>; chomp($action); # If action matches the letter e, perform an encryption if ($action =~ m/e/i) { print "\n"; print "Enter input file name: "; my $infile = <STDIN>; chomp($infile); print "Enter output file name: "; my $outfile = <STDIN>; chomp($outfile); # open source file for reading and store it locally (and check for +errors) open(INFILE, $infile) or die "Error opening input file $infile: $!" +; my @source = <INFILE>; close(INFILE) or die "Error closing input file $infile: $!"; # since we stored the data from the source file as an array (like w +e had to) # we need to now join everything together as a large scalar to per +form s/// my $source = join("", @source); foreach my $key (keys %map) { $source =~ s/$key/$map{$key}/g; } open(OUTFILE, ">$outfile") or die "Error creating new file $outfile +: $!"; print OUTFILE $source; close(OUTFILE) or die "Error closing new file $outfile: $!"; sleep(5); # for added affect! print "\n\nEncryption completed.\n\n"; exit; } elsif($action =~ m/d/i) { print "Enter file name to decrypt: "; my $infile = <STDIN>; chomp($infile); print "Enter output file name: "; my $outfile = <STDIN>; chomp($outfile); open(INFILE, $infile) or die "Error opening input file $infile: $!" +; my @source = <INFILE>; close(INFILE) or die "Error closing input file $infile: $!"; # since we stored the data from the source file as an array (like w +e had to) # we need to now join everything together as a large scalar to per +form s/// my $source = join("", @source); foreach my $key (keys %map) { $source =~ s/$map{$key}/$key/g; } open(OUTFILE, ">$outfile") or die "Error creating new file $outfile +: $!"; print OUTFILE $source; close(OUTFILE) or die "Error closing new file $outfile: $!"; print "Decryption completed."; exit; } else { print "Sorry, I did not understand your command."; print "\nPlease run this program again, and"; print "\nType in \"e\" to encrypt a file or \"d\" to decrypt a file +\n\n"; }

Replies are listed 'Best First'.
Re: Using a hash to globally s/// a string
by roboticus (Chancellor) on May 02, 2012 at 21:33 UTC

    You'll probably want to use the tr/abc/xyz/ function. (Which will refer you to perldoc perlop, specifically the Quote and Quote-like operators.) The scheme you have will likely cause you a bit of grief:

    $ cat cryppled.pl #!/usr/bin/perl use strict; use warnings; my %map = ( a=>'b', b=>'d', c=>'g', d=>'c', e=>'f', f=>'a' ); my $orig = 'abcdefgh'; print "orig: '$orig'\n"; my $new1 = $orig; for my $k (keys %map) { $new1 =~ s/$map{$k}/$k/g; } print "new1: '$new1'\n"; my $new2 = $orig; for my $k (reverse keys %map) { $new2 =~ s/$map{$k}/$k/g; } print "new2: '$new2'\n"; my $new3 = $orig; $new3 =~ tr/abcdef/bdgcfa/; print "new3: '$new3'\n"; $ perl crypplyd.pl orig: 'abcdefgh' new1: 'ffdbeedh' new2: 'eaaaeech' new3: 'bdgcfagh'

    ...roboticus

    Update: The original documentation link was a bit brief, so I added the second one.

    When your only tool is a hammer, all problems look like your thumb.

Re: Using a hash to globally s/// a string
by toolic (Bishop) on May 02, 2012 at 21:19 UTC
    I believe part of the problem has to do with quoting metacharacters for the regex (quotemeta). Change:
    $source =~ s/$map{$key}/$key/g;

    to:

    $source =~ s/\Q$map{$key}/$key/g;

    UPDATE: Your encryption is broken too. You are making several passes over your input file, globally replacing all characters every time. You need to map each character of your input only once. Don't use global: s///g

    for my $line (@source) { chomp $line; my $tmpline = ''; for my $key (split //, $line) { $tmpline .= $map{$key}; } print "$tmpline\n"; }
Re: Using a hash to globally s/// a string
by ww (Archbishop) on May 02, 2012 at 21:28 UTC
    Marginally OT: What you're trying to do is NOT "encryption."
    It's simple "substitution," readily restored to cleartext with a frequency attack or any one of several other approaches. It's also sometimes called "transposition" (about either, search out "ROT13").

    Your options section (at line 87) has another kind of problem: if ($action =~ m/e/i) will cheerfully match on "Decrypt" if the user is careless about reading your instructions.

    And at that point, your code got longer than I chose to follow. That's not to be harsh; rather, it's prelude to the PM advice to boil your illustration down to the smallest possible example that actually compiles and illustrates the problem. In this case, you could make your OP considerably shorted by omitting the transposition code.