220 lines
7.2 KiB
ObjectPascal
220 lines
7.2 KiB
ObjectPascal
|
|
program chaocipher(input, output);
|
|||
|
|
|
|||
|
|
const
|
|||
|
|
{ This denotes a `set` literal: }
|
|||
|
|
alphabet = ['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'];
|
|||
|
|
{ The `card` function is an Extended Pascal (ISO 10206) extension. }
|
|||
|
|
alphabetCardinality = card(alphabet);
|
|||
|
|
{ 1st character denotes “zenith”. }
|
|||
|
|
zenith = 1;
|
|||
|
|
{ In a 26-character alphabet the 14th character denotes “nadir”. }
|
|||
|
|
nadir = alphabetCardinality div 2 + 1;
|
|||
|
|
{ For simplicity use compile-time-defined maximum lengths. }
|
|||
|
|
messageMaximumLength = 80;
|
|||
|
|
|
|||
|
|
type
|
|||
|
|
{ This “discriminates” the Extended Pascal schema data type `string` to be }
|
|||
|
|
{ capable of holding strings up to `alphabetCardinality` `char` values. }
|
|||
|
|
map = string(alphabetCardinality);
|
|||
|
|
{ Variables of this data type can only assume integer values within 1..26: }
|
|||
|
|
mapCharacterIndex = 1..alphabetCardinality;
|
|||
|
|
{ Later used as a buffer for the input/output. }
|
|||
|
|
message = string(messageMaximumLength);
|
|||
|
|
messageCharacterIndex = 1..messageMaximumLength;
|
|||
|
|
{ Stores a key for the Chaocipher algorithm. }
|
|||
|
|
key = record
|
|||
|
|
cipherText: map;
|
|||
|
|
plainText: map;
|
|||
|
|
end;
|
|||
|
|
|
|||
|
|
{ --- auxilliary routines ---------------------------------------------- }
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
\brief verifies that a key is valid for the Chaocipher
|
|||
|
|
\param sample a potential `key` for a Chaocipher
|
|||
|
|
\return `true` iff \param sample is an acceptable `key`
|
|||
|
|
}
|
|||
|
|
{ `protected` (Extended Pascal extension) denotes an immutable parameter. }
|
|||
|
|
function isValid(protected sample: key): Boolean;
|
|||
|
|
{ Determines whether a `map` contains all characters of `alphabet`. }
|
|||
|
|
{ Nesting this function allows for a neat expression below. }
|
|||
|
|
function isComplete(protected text: map): Boolean;
|
|||
|
|
var
|
|||
|
|
i: integer;
|
|||
|
|
{ `value []` will initialize this variable to an empty set value. }
|
|||
|
|
{ This is an Extended Pascal (ISO 10206) extension. }
|
|||
|
|
s: set of char value [];
|
|||
|
|
begin
|
|||
|
|
{ NB: In Pascal `for`-loop limits are inclusive. }
|
|||
|
|
for i := 1 to length(text) do
|
|||
|
|
begin
|
|||
|
|
{ This adds the set containing one character to the set `s`. }
|
|||
|
|
s := s + [text[i]]
|
|||
|
|
end;
|
|||
|
|
isComplete := card(s) = alphabetCardinality
|
|||
|
|
end;
|
|||
|
|
begin
|
|||
|
|
{ This way `sample.cipherText` can be simply written as `cipherText`. }
|
|||
|
|
with sample do
|
|||
|
|
begin
|
|||
|
|
{ `and_then` is an EP extension indicating “lazy evaluation”. }
|
|||
|
|
isValid := (alphabetCardinality > 8) and_then
|
|||
|
|
isComplete(cipherText) and_then isComplete(plainText)
|
|||
|
|
end
|
|||
|
|
end;
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
\brief permutes a key for the next encryption/decryption step
|
|||
|
|
\param shift the index of the characters just substituted
|
|||
|
|
}
|
|||
|
|
{ `var` means the parameter value will be modified _at_ the call site. }
|
|||
|
|
procedure permute(var state: key; protected shift: mapCharacterIndex);
|
|||
|
|
begin
|
|||
|
|
with state do
|
|||
|
|
begin
|
|||
|
|
{ Indices in `cipherText[1..pred(shift)]` _must_ be non-descending: }
|
|||
|
|
if shift > 1 then
|
|||
|
|
begin
|
|||
|
|
cipherText := subStr(cipherText, shift) + cipherText[1..pred(shift)]
|
|||
|
|
{ `subStr(str, ini)` is equivalent to `str[ini..length(str)]`. }
|
|||
|
|
end;
|
|||
|
|
{ Likewise, `succ(shift)` must be a valid index in `plainText`: }
|
|||
|
|
if shift < alphabetCardinality then
|
|||
|
|
begin
|
|||
|
|
plainText := subStr(plainText, succ(shift)) + plainText[1..shift]
|
|||
|
|
end;
|
|||
|
|
|
|||
|
|
{ If it does _not_ _alter_ the _entire_ string’s _length_, you can }
|
|||
|
|
{ modify parts of a string like this (Extended Pascal extension): }
|
|||
|
|
cipherText[zenith+1..nadir] := cipherText[zenith+2..nadir] + cipherText[zenith+1];
|
|||
|
|
plainText[zenith+2..nadir] := plainText[zenith+3..nadir] + plainText[zenith+2]
|
|||
|
|
end
|
|||
|
|
end;
|
|||
|
|
|
|||
|
|
{ --- the core routine of the algorithm -------------------------------- }
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
\brief performs Chaocipher common steps
|
|||
|
|
\param line the message to encrypt/decrypt
|
|||
|
|
\param state the initial key to start encrpytion/decryption with
|
|||
|
|
\param locate a function determining the 2-tuple index in the key
|
|||
|
|
\param substitute the procedure substituting the correct characters
|
|||
|
|
}
|
|||
|
|
procedure chaocipher(
|
|||
|
|
var line: message;
|
|||
|
|
var state: key;
|
|||
|
|
{ These are “routine parameters”. Essentially the address of a routine }
|
|||
|
|
{ matching the specified routine signature is passed to `chaocipher`. }
|
|||
|
|
function locate(protected i: messageCharacterIndex): mapCharacterIndex;
|
|||
|
|
procedure substitute(
|
|||
|
|
protected i: messageCharacterIndex;
|
|||
|
|
protected z: mapCharacterIndex
|
|||
|
|
)
|
|||
|
|
);
|
|||
|
|
var
|
|||
|
|
{ For demonstration purposes: In this program }
|
|||
|
|
{ `line.capacity` refers to `messageMaximumLength`. }
|
|||
|
|
i: 1..line.capacity;
|
|||
|
|
substitutionPairIndex: mapCharacterIndex;
|
|||
|
|
begin
|
|||
|
|
{ Don’t trust user input, even though this is just a RosettaCode example. }
|
|||
|
|
if not isValid(state) then
|
|||
|
|
begin
|
|||
|
|
writeLn('Error: Key is invalid. Got:');
|
|||
|
|
writeLn('Cipher text: ', state.cipherText);
|
|||
|
|
writeLn(' Plain text: ', state.plainText);
|
|||
|
|
halt
|
|||
|
|
end;
|
|||
|
|
|
|||
|
|
for i := 1 to length(line) do
|
|||
|
|
begin
|
|||
|
|
{ We’ll better skip characters that aren’t in the `alphabet`. }
|
|||
|
|
if line[i] in alphabet then
|
|||
|
|
begin
|
|||
|
|
{ Here you see the beauty of using routine parameters. }
|
|||
|
|
{ Depending on whether we’re encrypting or decrypting, }
|
|||
|
|
{ you need to find a character in the `cipherText` or }
|
|||
|
|
{ `plainText` key value respectively, yet the basic order
|
|||
|
|
{ of the steps are still the same. }
|
|||
|
|
substitutionPairIndex := locate(i);
|
|||
|
|
substitute(i, substitutionPairIndex);
|
|||
|
|
permute(state, substitutionPairIndex)
|
|||
|
|
end
|
|||
|
|
end
|
|||
|
|
end;
|
|||
|
|
|
|||
|
|
{ --- entry routines --------------------------------------------------- }
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
\brief encrypts a message according to Chaocipher
|
|||
|
|
\param line a message to encrypt
|
|||
|
|
\param state the key to begin with
|
|||
|
|
\return the encrypted message \param line using the provided key
|
|||
|
|
}
|
|||
|
|
{ Note: without `var` or `protected` both `encrypt` and `decrypt`get }
|
|||
|
|
{ and have their own independent copies of the parameter values. }
|
|||
|
|
function encrypt(line: message; state: key): message;
|
|||
|
|
function encryptor(protected i: messageCharacterIndex): mapCharacterIndex;
|
|||
|
|
begin
|
|||
|
|
encryptor := index(state.plainText, line[i])
|
|||
|
|
end;
|
|||
|
|
procedure substitutor(
|
|||
|
|
protected i: messageCharacterIndex;
|
|||
|
|
protected z: mapCharacterIndex
|
|||
|
|
);
|
|||
|
|
begin
|
|||
|
|
line[i] := state.cipherText[z]
|
|||
|
|
end;
|
|||
|
|
begin
|
|||
|
|
chaocipher(line, state, encryptor, substitutor);
|
|||
|
|
encrypt := line
|
|||
|
|
end;
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
\brief decrypts a message according to Chaocipher
|
|||
|
|
\param line the encrypted message
|
|||
|
|
\param state the key to begin with
|
|||
|
|
\return the decrypted message \param line using the provided key
|
|||
|
|
}
|
|||
|
|
function decrypt(line: message; state: key): message;
|
|||
|
|
function decryptor(protected i: messageCharacterIndex): mapCharacterIndex;
|
|||
|
|
begin
|
|||
|
|
decryptor := index(state.cipherText, line[i])
|
|||
|
|
end;
|
|||
|
|
procedure substitutor(
|
|||
|
|
protected i: messageCharacterIndex;
|
|||
|
|
protected z: mapCharacterIndex
|
|||
|
|
);
|
|||
|
|
begin
|
|||
|
|
line[i] := state.plainText[z]
|
|||
|
|
end;
|
|||
|
|
begin
|
|||
|
|
chaocipher(line, state, decryptor, substitutor);
|
|||
|
|
decrypt := line
|
|||
|
|
end;
|
|||
|
|
|
|||
|
|
{ === MAIN ============================================================= }
|
|||
|
|
var
|
|||
|
|
exampleKey: key;
|
|||
|
|
line: message;
|
|||
|
|
begin
|
|||
|
|
{ Instead of writing `exampleKey.cipherText := '…', you can }
|
|||
|
|
{ write in Extended Pascal a `record` literal like this: }
|
|||
|
|
exampleKey := key[
|
|||
|
|
cipherText: 'HXUCZVAMDSLKPEFJRIGTWOBNYQ';
|
|||
|
|
plainText: 'PTLNBQDEOYSFAVZKGJRIHWXUMC';
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
{ `EOF` is shorthand for `EOF(input)`. }
|
|||
|
|
while not EOF do
|
|||
|
|
begin
|
|||
|
|
{ `readLn(line)` is shorthand for `readLn(input, line)`. }
|
|||
|
|
readLn(line);
|
|||
|
|
line := encrypt(line, exampleKey);
|
|||
|
|
writeLn(decrypt(line, exampleKey));
|
|||
|
|
{ Likewise, `writeLn(line)` is short for `writeLn(output, line)`. }
|
|||
|
|
writeLn(line)
|
|||
|
|
end
|
|||
|
|
end.
|