Wednesday, September 10, 2008

Conjugal

Cory Doctorow has a wedding ring designed by Bruce Schneier. It's a secret encoder ring, naturally. Now they're looking for an encryption algorithm that can use the ring. I hit a slow patch at work, waiting for some documents to arrive (which means it's going to be all the worse when they actually do arrive), so I spent a few minutes on one. I'm going to post the short version of the algorithm at boingboing, but here's the long version, implemented in Perl. (I deliberately avoided using Perl tricks to keep it clear.)

Update 9/10/08: you get better results if you calculate the advance a bit differently. Start with 1. If ring 2 has a dot above, add 2; if it has a dot below, add 1. If ring 3 has a dot above, add 6; if it has a dot below, add 3. I'm still experimenting with the advance, though.


#!/usr/bin/perl -w

# Calculates how many positions to advance a ring to get to the desired letter
sub calcAdvance {
my ($pos, $newPos) = @_;
my $advance = $newPos - $pos;
if ($advance < 0) {
$advance = $advance + 26;
}
return $advance;
}

# Ring1 is inner-most, Ring3 is outer-most.
#
# A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
my @ring2Dots = (9,9,0,0,3,3,9,9,0,0,3,3,9,9,0,0,3,3,9,9,0,0,3,3,9,9);
my @ring3Dots = (2,2,2,0,0,0,1,1,1,2,2,2,0,0,0,1,1,1,2,2,2,0,0,0,1,1);

#
# The Conjugal Encryption Algorithm.
#
# Initialization using the crypto key.
#
# Start by aligning all the "A"'s.
my $ring2Pos = 0;
my $ring3Pos = 0;

# Then initialize the ring.
my $key = "congratulations";
while ($key ne "") {

# 1. Get the next character of the key
my $keyChar = substr ($key, 0, 1);
$key = substr ($key, 1);

# 2. Rotate rings 2 and 3 together so the A on
# Ring 1 aligns with the key character on
# Ring 3.
my $advance = calcAdvance ($ring3Pos,
ord (lc ($keyChar)) - ord ("a"));
$ring2Pos = ($ring2Pos + $advance) % 26;
$ring3Pos = ($ring3Pos + $advance) % 26;

# 3. Calculate an advancement amount.
# a. Start with 1.
# b. Look at the Ring 2 letter next to the A of Ring 1.
# If there's a dot above, add 9. If there's a dot
# below, add 3.
# c. Look at the Ring 3 letter next to the A of Ring 1.
# If there's a dot above, add 2. If there's a dot
# below, add 1.
$advance = 1 + $ring2Dots[$ring2Pos] + $ring3Dots[$ring3Pos];

# 4. Advance Ring 3 that many letters.
$ring3Pos = ($ring3Pos + $advance) % 26;
}


# Generate some useful output
my $plaintext = "Wishing you a happy life together!";
print $plaintext, "\n";

# Skip whitespace and punctuation when encrypting.
$plaintext = lc($plaintext);
$plaintext =~ tr/a-z//cd;
print uc($plaintext), "\n";

#
# Encrypt text. Initialization vector omitted for clarity.
#
my $cyphertext = "";
while ($plaintext ne "") {

# 1. Look at the letter on Ring 3 adjacent to the A on Ring 1.
# Rotate Rings 2 and 3 together so that same letter on
# Ring 2 is adjacent to the A on Ring 1 (and some different
# letter on Ring 3 will now be adjacent.)
my $advance = calcAdvance ($ring2Pos, $ring3Pos);
$ring2Pos = ($ring2Pos + $advance) % 26;
$ring3Pos = ($ring3Pos + $advance) % 26;

# 2. Calculate an advancement amount.
# a. Start with 1.
# b. Look at the Ring 2 letter next to the A of Ring 1.
# If there's a dot above, add 9. If there's a dot
# below, add 3.
# c. Look at the Ring 3 letter next to the A of Ring 1.
# If there's a dot above, add 2. If there's a dot
# below, add 1.
$advance = 1 + $ring2Dots[$ring2Pos] + $ring3Dots[$ring3Pos];

# 3. Advance Ring 3 that many letters.
$ring3Pos = ($ring3Pos + $advance) % 26;

# 4. Get the next character of the plaintext.
my $plainChar = substr ($plaintext, 0, 1);
$plaintext = substr ($plaintext, 1);

# 5. To encrypt this letter of plaintext, find the
# plaintext on Ring 1 and write down the matching
# cyphertext on Ring 3. (To decrypt, look at
# the cyphertext on Ring 3 and write down the matching
# plaintext on Ring 1.)
my $ring3CryptoPos
= (ord ($plainChar) - ord ("a") + $ring3Pos) % 26;
$cyphertext = $cyphertext . chr ($ring3CryptoPos + ord ("A"));
}

# Print the cyphertext.
print $cyphertext, "\n";

No comments: