This article has been moved from «perl6.eu» and updated to reflect the language rename in 2019.
See also: The Introduction | Part 1: Base 36 | Part 2: Base 400 | Part 3: Changing Keys.
use lib "lib";
use Cryptoish;
sub MAIN (Cryptoish::Alphabet $key, Cryptoish::Alphabet $text, :$secret,
Cryptoish::Modulo :$modulo = 40, Bool :$flip = False)
{
my $value = Cryptoish::multiencrypt($key, $text, :$secret, :$modulo, :$flip);
my $roundtrip
= Cryptoish::multidecrypt($key, $value, :$secret, :$modulo, :$flip);
if $text eq $roundtrip
{
say $value;
}
else
{
say "** The encrypted text (2. line) doesn't roundtrip when decrypted",
" (3. line) **";
say $value;
say $roundtrip;
}
}
It should be self-explanatory. Use this program instead of «multiencrypt» to encrypt messages. If the roundtrip, heaven forbid, should fail, please notify me.
Enable verbose mode with the «--verbose» command line option (and note the newlines I had to add in the code blocks below, marked with «\», to make the lines fit the web page):
$ raku multiencrypt --verbose AA "ARNE SOMMER WRITES ABOUT PERL6."
Count: 16 ('G' -> 28)
Partial: 'ARNE SOMMER WRIT' -> (307 235 281 261 160 169 373 369 353 9 54 \
153 283 389 11 303)
Key: U1 -> (391 305)
Count: 29 ('T' -> 273)
Partial: 'ES ABOUT PERL6.' -> (266 23 248 235 144 396 18 357 344 249 320 \
150 201 131 332)
All: (28 307 235 281 261 160 169 373 369 353 9 54 153 283 389 11 303 391 \
305 273 266 23 248 235 144 396 18 357 344 249 320 150 201 131 332)
Int: 98086136933496711163995899614360639306435717956795745557845378712086\
57671657069099621722828
Str: 5BL47IGLOQG44PUZ6HANWN87HLVF1HK5DO10OS0ZUS0TB9C0PHOJLYI6BPO
5BL47IGLOQG44PUZ6HANWN87HLVF1HK5DO10OS0ZUS0TB9C0PHOJLYI6BPO
$ raku multidecrypt --verbose AA 5BL47IGLOQG44PUZ6HANWN87HLVF1HK5DO10OS0\
ZUS0TB9C0PHOJLYI6BPO
Key: AA
Str: 5BL47IGLOQG44PUZ6HANWN87HLVF1HK5DO10OS0ZUS0TB9C0PHOJLYI6BPO
Int: 98086136933496711163995899614360639306435717956795745557845378712086\
57671657069099621722828
All: 28 307 235 281 261 160 169 373 369 353 9 54 153 283 389 11 303 391 \
305 273 266 23 248 235 144 396 18 357 344 249 320 150 201 131 332
Count: 28 -> G -> 16
Partial: (307 235 281 261 160 169 373 369 353 9 54 153 283 389 11 303) \
-> 'ARNE SOMMER WRIT'
Key: (391 305) -> U1
Count: 273 -> T -> 29
Partial: (266 23 248 235 144 396 18 357 344 249 320 150 201 131 332) \
-> 'ES ABOUT PERL6.'
Text: 'ARNE SOMMER WRITES ABOUT PERL6.'
ARNE SOMMER WRITES ABOUT PERL6.
The step in «Partial» from the letters to the integers involves the whole key (the 400 characters). The algorithm picks one of the 10 positions for the given letter, and the integer is the index of that position. Note that you will have to look up the whole key (the 400 characters) in the Secret File manually if you want to check that part.
It is available in «multiencrypt-check» as well:
$ raku multiencrypt-check --verbose AA "ARNE SOMMER WRITES ABOUT PERL6."
Count: 3 ('3' -> 213)
Partial: 'ARN' -> (95 362 109)
Key: C0 -> (74 19)
Count: 32 ('W' -> 187)
Partial: 'E SOMMER WRITES ABOUT PERL6.' -> (2 14 353 49 163 248 13 8 14 \
377 142 15 131 100 295 10 80 16 47 200 370 211 233 23 253 207 368 149)
All: (213 95 362 109 74 19 187 2 14 353 49 163 248 13 8 14 377 142 15 \
131 100 295 10 80 16 47 200 370 211 233 23 253 207 368 149)
Int: 442489569591193670868987620434352034575830597538646700229098159471\
4088411082948461433958213
Str: 2EFG8RJF2THJZVUPMHF3MEXN29F0Y0GPI4LECQ4R6VHGSHGJWS365A5IPMT
-----------------------------------------------------------------------
Key: AA
Str: 2EFG8RJF2THJZVUPMHF3MEXN29F0Y0GPI4LECQ4R6VHGSHGJWS365A5IPMT
Int: 442489569591193670868987620434352034575830597538646700229098159471\
4088411082948461433958213
All: 213 95 362 109 74 19 187 2 14 353 49 163 248 13 8 14 377 142 15 \
131 100 295 10 80 16 47 200 370 211 233 23 253 207 368 149
Count: 213 -> 3 -> 3
Partial: (95 362 109) -> 'ARN'
Key: (74 19) -> C0
Count: 187 -> W -> 32
Partial: (2 14 353 49 163 248 13 8 14 377 142 15 131 100 295 10 80 \
16 47 200 370 211 233 23 253 207 368 149) -> 'E SOMMER WRITES ABOUT PERL6.'
Text: 'ARNE SOMMER WRITES ABOUT PERL6.'
-----------------------------------------------------------------------
2EFG8RJF2THJZVUPMHF3MEXN29F0Y0GPI4LECQ4R6VHGSHGJWS365A5IPMT
Testing it with «--modulo=2»:
$ raku multiencrypt-check --modulo=2 --verbose AA "ARNE"
Count: 38 ('?' -> 130) (modulo: 0)
Key: WD -> (382 165)
Count: 31 ('V' -> 109) (modulo: 1)
Partial: 'A' -> (220)
Key: 5O -> (52 287)
Count: 25 ('P' -> 308) (modulo: 1)
Partial: 'R' -> (134)
Key: UV -> (122 180)
Count: 13 ('D' -> 376) (modulo: 1)
Partial: 'N' -> (3)
Key: 15 -> (359 377)
Count: 0 ('0' -> 197) (modulo: 0)
Key: EY -> (156 104)
Count: 31 ('V' -> 42) (modulo: 1)
Partial: 'E' -> (59)
All: (130 382 165 109 220 52 287 308 134 122 180 376 3 359 377 197 \
156 104 42 59)
Int: 1624683803270494727871458737477892043290119002552930
Str: PNEIEXG8IW6759HQMYRSH0ZQEMFYHLV8I
-----------------------------------------------------------------------
Key: AA
Str: PNEIEXG8IW6759HQMYRSH0ZQEMFYHLV8I
Int: 1624683803270494727871458737477892043290119002552930
All: 130 382 165 109 220 52 287 308 134 122 180 376 3 359 377 197 \
156 104 42 59
Count: 130 -> ? -> 38 (modulo: 0)
Partial: () -> ''
Key: (382 165) -> WD
Count: 109 -> V -> 31 (modulo: 1)
Partial: (220) -> 'A'
Key: (52 287) -> 5O
Count: 308 -> P -> 25 (modulo: 1)
Partial: (134) -> 'R'
Key: (122 180) -> UV
Count: 376 -> D -> 13 (modulo: 1)
Partial: (3) -> 'N'
Key: (359 377) -> 15
Count: 197 -> 0 -> 0 (modulo: 0)
Key: (156 104) -> EY
Count: 42 -> V -> 31 (modulo: 1)
Partial: (59) -> 'E'
Text: 'ARNE'
-----------------------------------------------------------------------
PNEIEXG8IW6759HQMYRSH0ZQEMFYHLV8I
The first key («AA», given in the command line) was used to encode zero characters. Then we encoded three characters with a new key for each of them («A» with «WD», «R» with «5O», «N» with «UV»), followed by another zero character key (with the key «15»). And finally the fourth and last character was encoded by the sixth key («E» with «EY»).
Note how the modulo function change the count. If we try do decrypt it without using the modulo option (with the correct value), we'd get gibberish:
$ raku multidecrypt --verbose AA PNEIEXG8IW6759HQMYRSH0ZQEMFYHLV8I
Key: AA
Str: PNEIEXG8IW6759HQMYRSH0ZQEMFYHLV8I
Int: 1624683803270494727871458737477892043290119002552930
All: 130 382 165 109 220 52 287 308 134 122 180 376 3 359 377 197 156 \
104 42 59
Count: 130 -> ? -> 38
Partial: (382 165 109 220 52 287 308 134 122 180 376 3 359 377 197 156 \
104 42 59) -> 'WDNI22O!G11UL5.3I?M'
Text: 'WDNI22O!G11UL5.3I?M'
WDNI22O!G11UL5.3I?M
Not even the first character is correct, as the key was changed before decrypting it. That was lucky for us.
If the first key is used to encrypt the first two characters (before the modulo function kicks in), we'd get the two first characters correct in the decrypted message:
$ raku multiencrypt-check --modulo=5 --verbose AA "ARNE"
Count: 7 ('7' -> 200) (modulo: 2)
Partial: 'AR' -> (380 324)
Key: 9K -> (34 193)
Count: 29 ('T' -> 292) (modulo: 4)
Partial: 'NE' -> (391 278)
All: (200 380 324 34 193 292 391 278)
Int: 457079731023027992200
Str: 2OGOE1ZZB80MMG
-----------------------------------------------------------------------
Key: AA
Str: 2OGOE1ZZB80MMG
Int: 457079731023027992200
All: 200 380 324 34 193 292 391 278
Count: 200 -> 7 -> 7 (modulo: 2)
Partial: (380 324) -> 'AR'
Key: (34 193) -> 9K
Count: 292 -> T -> 29 (modulo: 4)
Partial: (391 278) -> 'NE'
Text: 'ARNE'
-----------------------------------------------------------------------
2OGOE1ZZB80MMG
$ raku multidecrypt --verbose AA 2OGOE1ZZB80MMG
Key: AA
Str: 2OGOE1ZZB80MMG
Int: 457079731023027992200
All: 200 380 324 34 193 292 391 278
Count: 200 -> 7 -> 7
Partial: (380 324 34 193 292 391 278) -> 'AR9KRUF'
Text: 'AR9KRUF'
AR9KRUF
> my @a; @a[(0..9).pick]++ for ^1_000_000; say @a;
[100431 99925 99936 99841 99962 100371 99763 100228 99150 100393]
Selecting a random integer between 0 and 9 (both included) one million times (and note the underscore that Raku allows us to insert into numbers to make them more readable for humans) (with «pick») turns out to be pretty evenly distributed. That is good news for randomness, bad actually bad luck when we encrypt very long messages - as it makes it possible to use Letter Frequency Analysis on them encrypted string.
If we reduce the number of iterations, we get a much more random distribution:
> my @a; @a[(0..9).pick]++ for ^100; say @a; # 100 times
[12 9 10 5 4 11 10 10 14 15]
> my @a; @a[(0..9).pick]++ for ^1000; say @a; # 1000 times
[105 89 103 107 93 105 103 104 109 82]
It is possible to avoid the loop, by rewriting it to use «map» instead of «for»:
> my @a; (^1000).map({ @a[(0..9).pick]++ }); say @a;
[96 98 96 94 100 107 122 91 105 91]
But «map» is harder to understand, and is actually a loop construct in disguise.
Conclusion: A large selection of random numbers (from a small range) will be almost evenly distributed.
But what about the randomness of the sequence? Does any single value follow another value regularly, or is that random as well? Let's try:
File: random-next:
my @a;
my $old = (0..9).pick;
my $new;
for ^1_000_000
{
$new = (0..9).pick;
@a[$old][$new]++; # [1]
$old = $new;
}
print " ";
print $_.fmt('%6d') for 0..9; # [2]
print "\n";
for 0..9 -> $row
{
print "$row: ";
for 0..9 -> $col
{
print @a[$row][$col].fmt('%6d'); # [2]
}
print "\n";
}
[1] I store the values in a two dementional array. The first dimension is a random value, the second is the next random value, and the value stored in that two dimentional position is the frequency of the combination.
[2] The fmt
method is used to ensure correct tabulation.
I have specified 6 characters for each value (and missing characters are added as
spaces to the front of number).
See my Raku P(i)ermutations article for more information about «fmt».
Running it:
$ raku random-next
0 1 2 3 4 5 6 7 8 9
0: 9895 9946 10117 9888 9987 10014 10035 10022 10083 9986
1: 9816 9922 10040 10069 9961 10002 9925 10094 10018 9936
2: 10082 9950 10164 9980 9953 10060 9911 10126 10030 9989
3: 10031 9847 10056 10042 10002 9886 10083 10015 10024 9902
4: 10085 9812 10070 10004 9790 10162 10061 9836 9941 9867
5: 9960 9944 10053 9992 9950 9830 10007 10084 10026 9999
6: 10020 10021 10020 10139 10034 9900 10179 9981 10013 10049
7: 10142 10130 9927 9997 10002 9955 9886 10152 9922 10140
8: 10037 10110 9836 9808 10083 9916 10122 9925 10100 10120
9: 9905 10101 9962 9969 9866 10119 10147 10018 9900 9985
Locate the random number in a column. The number of times another number follows it is the value shown in that row. E.g. is the number 4 followed by the number 9 9866 times.
An even distribution would have given 1000 everywhere, but we are pretty close.
Conclusion: A large selection of random numbers (from a limited range) will come in an almost random order.
If you know that certain characters are used more often than others (as they probably are if you write normal text), simply change the way the Secret File is set up so that some of them are more frequent than others. (The punctuation characters stand out as candidates for this.)
As long as the total length is kept at 400 characters, you don't need to change anything except the «mksecret» script. This has the added benefit of adding more possible permutations, making it even harder to guess the key. (But this will probably not make that much a difference in practice if you use «multiencrypt» and «multidecrypt».)
unit sub MAIN ($name = "", :$randomsize = False);
sub get-base
{
return (@base40 xx 10).flat.pick(*) unless $randomsize;
my @common = (@base40 xx 7).flat; @common.append(@common.pick(40 * 3));
return @common.flat.pick(*);
}
«xx» is the List Repetion Operatator. It duplicates the left hand side the number of times given on the right hand side. See docs.raku.org/routine/xx for more information.
Running it:
$ raku mksecret --randomsize junk
Written file lib/Cryptoish/Secret/junk.pm6
Then a somewhat larger program to test it, assuming the Secret File «junk» (but it would be easy to extend it to get the Secret File name as argument):
File: secret-distribution
use lib "lib";
use Cryptoish::Secret::junk;
constant @base40 := (0 .. 9, "A" .. "Z", " ", ".", "?", "!").flat;
my @keys = <00 0Y 2K 39 89 AX B0 FA HJ M8 OT R3 TM ZU>;
my %frequency;
for @keys -> $key
{
my $secret = get-secret($key);
$secret.comb.map( { %frequency{$key}{$_}++; });
}
print " ";
print "$_ " for @keys;
print "\n";
for @base40 -> $col
{
print "$col: ";
for @keys -> $key
{
print (%frequency{$key}{$col}).fmt('%2d'), " ";
}
print "\n";
}
And running it shows the distribution for the chosen keys:
$ raku secret-distribution
00 0Y 2K 39 89 AX B0 FA HJ M8 OT R3 TM ZU
0: 10 9 12 9 13 10 11 13 12 9 12 11 13 9
1: 11 9 10 11 10 11 10 9 10 10 13 9 9 8
2: 10 12 10 9 9 8 9 11 9 9 11 9 11 12
3: 11 10 14 11 10 10 12 8 9 10 13 10 11 10
4: 10 9 10 9 8 10 10 12 10 10 10 12 10 12
5: 8 13 8 10 12 8 10 11 8 8 9 11 10 9
6: 10 11 9 9 11 12 9 10 11 11 8 11 9 10
7: 10 11 8 8 9 8 11 11 11 9 9 10 11 10
8: 11 9 9 10 10 10 11 10 11 14 11 9 9 12
9: 11 11 9 12 9 13 10 10 11 9 10 11 10 10
A: 11 8 8 11 11 11 10 9 10 10 11 9 12 9
B: 10 9 9 11 8 8 8 9 8 9 11 10 8 8
C: 11 10 10 9 10 11 10 10 12 11 11 9 9 11
D: 11 12 8 8 11 9 11 10 11 9 11 8 11 10
E: 9 9 12 12 8 12 9 11 10 10 9 11 10 10
F: 10 11 12 8 11 10 11 11 10 9 10 7 10 11
G: 12 11 11 11 12 10 11 9 11 8 9 10 10 10
H: 8 9 10 11 8 10 10 11 12 12 10 11 13 12
I: 9 9 13 9 11 12 10 11 9 12 11 11 11 9
J: 8 12 10 10 12 12 8 10 11 9 11 10 9 9
K: 10 9 9 10 11 10 9 8 9 10 9 9 12 9
L: 11 9 9 10 10 9 9 9 9 9 8 8 12 8
M: 9 12 10 9 9 8 10 11 9 11 9 11 9 11
N: 12 11 8 9 13 10 9 10 10 11 11 11 10 10
O: 10 12 9 10 9 9 9 9 9 9 11 8 8 12
P: 12 8 12 10 9 10 12 10 9 9 10 10 11 9
Q: 8 10 11 9 10 11 10 10 10 9 7 9 7 8
R: 9 8 10 9 9 10 9 10 7 11 8 11 11 8
S: 8 11 9 12 8 11 11 9 11 12 10 8 11 11
T: 9 9 12 11 7 10 8 12 11 8 11 10 11 10
U: 8 11 8 12 12 10 11 9 10 10 7 10 8 9
V: 12 8 10 11 10 12 10 8 9 13 11 8 9 11
W: 10 10 10 9 8 9 11 10 9 11 9 11 10 11
X: 8 9 11 10 10 9 11 11 12 11 9 11 9 9
Y: 9 9 12 11 11 11 11 10 10 9 8 9 8 9
Z: 12 8 10 10 10 9 11 9 9 10 9 11 12 13
: 10 10 10 9 11 8 11 11 9 11 12 11 10 12
.: 12 11 10 11 8 9 10 8 10 11 9 10 8 9
?: 9 11 9 10 11 10 10 9 10 9 12 14 9 10
!: 11 10 9 10 11 10 7 11 12 8 10 11 9 10
The distribution is pretty good, and the number of possible keys have grown considerably. The math to compute the number this time is above my pay grade, but feel free to have a go.
Just for fun I ran it on a Secret File genereated without the «--randomsize» flag:
$ raku secret-distribution
00 0Y 2K 39 89 AX B0 FA HJ M8 OT R3 TM ZU
0: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
1: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
2: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
3: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
4: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
5: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
6: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
7: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
8: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
9: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
A: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
B: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
C: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
D: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
E: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
F: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
G: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
H: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
I: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
J: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
K: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
L: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
M: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
N: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
O: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
P: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
Q: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
R: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
S: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
T: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
U: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
V: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
W: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
X: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
Y: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
Z: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
.: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
?: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
!: 10 10 10 10 10 10 10 10 10 10 10 10 10 10
See the next part; Part 5: Real Text - Added 26. May 2019.