EncryptImpl:
CEDAR
PROGRAM
IMPORTS DESFace,
FS,
IO, Rope
EXPORTS Encrypt =
BEGIN OPEN IO;
DESVersion: INT = 2;
DESBlkSize: INTEGER = 8;
EncryptionState:
TYPE =
RECORD [
desVersion: INT ← DESVersion,
seed: DESFace.Block ← ALL[LOOPHOLE[CARDINAL[0], UNSPECIFIED]], -- last encrypted block
k: DESFace.Key,
position: [0..DESBlkSize] ← 0,
length: [0..DESBlkSize] ← 0,
source, dest: REF TEXT,
otherStream: STREAM
];
RefEncryptionState: TYPE = REF EncryptionState;
encryptedOutStreamProcs:
REF StreamProcs = CreateStreamProcs[
variety: output,
class: $DESEncryptingStream,
putChar: PutCharEncrypted,
close: CloseOutputEncrypted];
decryptedInStreamProcs:
REF StreamProcs = CreateStreamProcs[
variety: input,
class: $DESDecryptingStream,
getChar: GetCharEncrypted,
endOf: EndOfEncrypted];
StreamOpen:
PUBLIC
PROC [variety: StreamVariety, cipher:
STREAM, key: Rope.
ROPE]
RETURNS [plain:
STREAM] =
BEGIN
state: RefEncryptionState =
NEW[EncryptionState ← [
k: RopeToDESKey[key],
source: NEW[TEXT[2*DESBlkSize]],
dest: NEW[TEXT[2*DESBlkSize]],
otherStream: cipher]];
SELECT variety
FROM
output =>
BEGIN
iv: REF TEXT = GetRandomText[]; -- 8 characters
plain ← CreateStream[streamProcs: encryptedOutStreamProcs, streamData: state];
state.otherStream.PutF["*DES-%g*", IO.int[DESVersion]]; -- encryption algorithm id, in the clear
plain.PutText[iv];
FOR i: [0..20)
IN [0..
LOOPHOLE[iv[0], [0..255]]
MOD 20)
DO
plain.PutChar[GetRandomText[][0]];
ENDLOOP;
END;
input =>
BEGIN
start: CHAR;
plain ← CreateStream[streamProcs: decryptedInStreamProcs, streamData: state];
BEGIN -- try reading the DES encryption version in the clear
FOR i:
INT
IN [0..Rope.Length["*DES-"])
DO
IF state.otherStream.GetChar[ ! EndOfStream =>
GOTO BadSyntax1]#Rope.Fetch["*DES-", i]
THEN
GOTO BadSyntax1;
ENDLOOP;
state.desVersion ← state.otherStream.GetInt[
! IO.Error => GOTO BadSyntax1; EndOfStream => GOTO BadSyntax1];
IF state.otherStream.GetChar[ ! EndOfStream =>
GOTO BadSyntax1]#'*
THEN
GOTO BadSyntax1;
EXITS BadSyntax1 =>
BEGIN -- try it again, this time decrypting. That is only necessary because protocol version 1 had a bug.
state.otherStream.SetIndex[0];
state.desVersion ← 1;
FOR i:
INT
IN [0..Rope.Length["*DES-"])
DO
IF plain.GetChar[ ! EndOfStream =>
GOTO BadSyntax2]#Rope.Fetch["*DES-", i]
THEN
GOTO BadSyntax2;
ENDLOOP;
state.desVersion ← plain.GetInt[
! IO.Error => GOTO BadSyntax2; EndOfStream => GOTO BadSyntax2];
IF plain.GetChar[ ! EndOfStream =>
GOTO BadSyntax2]#'*
THEN
GOTO BadSyntax2;
END;
END;
start ← plain.GetChar[ ! EndOfStream => GOTO BadSyntax2];
FOR i: [0..27)
IN [0..7+(
LOOPHOLE[start,
NAT]
MOD 20))
DO
[] ← plain.GetChar[ ! EndOfStream => GOTO BadSyntax2];
ENDLOOP;
EXITS BadSyntax2 => ERROR BadEncryption;
END;
ENDCASE => ERROR; -- can't do anything except input or output
END;
StreamOpenWithKey:
PUBLIC
PROC [variety: StreamVariety, cipher:
STREAM, key: DESFace.Key]
RETURNS [plain:
STREAM] =
BEGIN
state: RefEncryptionState =
NEW[EncryptionState ← [
k: key,
source: NEW[TEXT[2*DESBlkSize]],
dest: NEW[TEXT[2*DESBlkSize]],
otherStream: cipher]];
SELECT variety
FROM
output =>
BEGIN
iv: REF TEXT = GetRandomText[]; -- 8 characters
plain ← CreateStream[streamProcs: encryptedOutStreamProcs, streamData: state];
state.otherStream.PutF["*DES-%g*", IO.int[DESVersion]]; -- encryption algorithm id, in the clear
plain.PutText[iv];
FOR i: [0..20)
IN [0..
LOOPHOLE[iv[0], [0..255]]
MOD 20)
DO
plain.PutChar[GetRandomText[][0]];
ENDLOOP;
END;
input =>
BEGIN
start: CHAR;
plain ← CreateStream[streamProcs: decryptedInStreamProcs, streamData: state];
BEGIN -- try reading the DES encryption version in the clear
FOR i:
INT
IN [0..Rope.Length["*DES-"])
DO
IF state.otherStream.GetChar[ ! EndOfStream =>
GOTO BadSyntax1]#Rope.Fetch["*DES-", i]
THEN
GOTO BadSyntax1;
ENDLOOP;
state.desVersion ← state.otherStream.GetInt[
! IO.Error => GOTO BadSyntax1; EndOfStream => GOTO BadSyntax1];
IF state.otherStream.GetChar[ ! EndOfStream =>
GOTO BadSyntax1]#'*
THEN
GOTO BadSyntax1;
EXITS BadSyntax1 =>
BEGIN -- try it again, this time decrypting. That is only necessary because protocol version 1 had a bug.
state.otherStream.SetIndex[0];
state.desVersion ← 1;
FOR i:
INT
IN [0..Rope.Length["*DES-"])
DO
IF plain.GetChar[ ! EndOfStream =>
GOTO BadSyntax2]#Rope.Fetch["*DES-", i]
THEN
GOTO BadSyntax2;
ENDLOOP;
state.desVersion ← plain.GetInt[
! IO.Error => GOTO BadSyntax2; EndOfStream => GOTO BadSyntax2];
IF plain.GetChar[ ! EndOfStream =>
GOTO BadSyntax2]#'*
THEN
GOTO BadSyntax2;
END;
END;
start ← plain.GetChar[ ! EndOfStream => GOTO BadSyntax2];
FOR i: [0..27)
IN [0..7+(
LOOPHOLE[start,
NAT]
MOD 20))
DO
[] ← plain.GetChar[ ! EndOfStream => GOTO BadSyntax2];
ENDLOOP;
EXITS BadSyntax2 => ERROR BadEncryption;
END;
ENDCASE => ERROR; -- can't do anything except input or output
END;
PutCharEncrypted:
PROC [self:
STREAM, char:
CHAR] =
BEGIN
state: RefEncryptionState = NARROW[self.streamData];
state.source[state.position] ← char;
state.position ← state.position+1;
IF state.position = DESBlkSize
THEN
TRUSTED BEGIN
DESFace.CBCEncrypt[key: state.k, nBlks: 1, seed: state.seed,
from: RefTextToBlocks[state.source], to: RefTextToBlocks[state.dest]];
state.seed ← RefTextToBlocks[state.dest][0];
ExpandToHexAscii[binary: state.dest, hexAscii: state.source];
state.otherStream.PutBlock[block: state.source, count: 2*DESBlkSize];
state.otherStream.PutChar['\n];
state.position ← 0;
END;
END;
ExpandToHexAscii:
PROC [binary, hexAscii:
REF
TEXT, binaryCount:
INT ← DESBlkSize] =
TRUSTED BEGIN
FOR i:
INT
IN [0..binaryCount)
DO
c: [0..256) ← LOOPHOLE[binary[i]];
c0: [0..16) ← c/16;
c1: [0..16) ← c MOD 16;
hexAscii[2*i] ← IF c0<10 THEN '0+c0 ELSE 'A+(c0-10);
hexAscii[2*i+1] ← IF c1<10 THEN '0+c1 ELSE 'A+(c1-10);
ENDLOOP;
hexAscii.length ← 2*binaryCount;
END;
CloseOutputEncrypted:
PROC [self:
STREAM, abort:
BOOL ←
FALSE] =
BEGIN
r: REF TEXT = GetRandomText[];
self.PutChar['.];
FOR i: (0..DESBlkSize)
IN (0..DESBlkSize)
DO
self.PutChar[IF r[i]='. THEN '? ELSE r[i]];
ENDLOOP;
END;
BadEncryption: PUBLIC ERROR = CODE;
GetCharEncrypted:
PROC [self:
STREAM]
RETURNS [
CHAR] =
BEGIN
state: RefEncryptionState = NARROW[self.streamData];
c: CHAR;
IF state.position>=state.length THEN FillSource[self, state];
c ← state.dest[state.position];
state.position ← (state.position+1) MOD DESBlkSize;
IF state.position = 0 THEN state.length ← 0; -- next char needs new block
RETURN[c];
END;
EndOfEncrypted:
PROC [self:
STREAM]
RETURNS [
BOOL] =
BEGIN
state: RefEncryptionState = NARROW[self.streamData];
IF state.position>=state.length THEN FillSource[self, state ! IO.EndOfStream => GOTO IsEnd];
RETURN[FALSE];
EXITS IsEnd => RETURN[TRUE];
END;
FillSource:
PROC [self:
STREAM, state: RefEncryptionState] =
BEGIN
bs: [0..DESBlkSize] ← 0;
SELECT state.desVersion
FROM
1 =>
WHILE bs<DESBlkSize
AND
NOT state.otherStream.EndOf[]
DO
bs ← bs+state.otherStream.GetBlock[
block: state.source, startIndex: bs, count: DESBlkSize
! EndOfStream => EXIT];
ENDLOOP;
2 =>
BEGIN
state.dest ← state.otherStream.GetToken[breakProc:
IO.IDProc, buffer: state.dest
! EndOfStream => {state.dest.length ← 0; CONTINUE}].token;
[] ← state.otherStream.SkipWhitespace[ ! EndOfStream => CONTINUE];
IF state.dest.length MOD 2 # 0 THEN ERROR BadEncryption;
bs ← state.dest.length/2;
CompressFromHexAscii[binary: state.source, hexAscii: state.dest, binaryCount: bs];
END;
ENDCASE => ERROR BadEncryption;
SELECT bs
FROM
0 => state.length ← 0;
<DESBlkSize => ERROR BadEncryption;
ENDCASE
-- = DESBlkSize -- =>
TRUSTED BEGIN
DESFace.CBCDecrypt[key: state.k, nBlks: 1, seed: state.seed,
from: RefTextToBlocks[state.source], to: RefTextToBlocks[state.dest]];
state.seed ← RefTextToBlocks[state.source][0];
state.dest.length ← DESBlkSize;
IF state.otherStream.EndOf[]
THEN
BEGIN
state.length ← DESBlkSize-1;
WHILE state.dest[state.length]#'. AND state.length>0 DO state.length ← state.length-1 ENDLOOP;
END
ELSE state.length ← DESBlkSize;
END;
IF state.length=0 THEN ERROR EndOfStream[self];
END;
CompressFromHexAscii:
PROC [binary, hexAscii:
REF
TEXT, binaryCount:
INT ← DESBlkSize] =
TRUSTED BEGIN
AsciiToHex:
PROC [c:
CHAR]
RETURNS [n: [0..16)] =
TRUSTED BEGIN
SELECT c
FROM
IN ['0..'9] => n ← c-'0;
IN ['A..'F] => n ← c-'A+10;
ENDCASE => ERROR BadEncryption;
END;
IF hexAscii.length<2*binaryCount-1 THEN ERROR BadEncryption;
FOR i:
INT
IN [0..binaryCount)
DO
binary[i] ← LOOPHOLE[16*AsciiToHex[hexAscii[2*i]]+AsciiToHex[hexAscii[2*i+1]]];
ENDLOOP;
binary.length ← binaryCount;
END;
EncryptRope:
PUBLIC
PROC [plain, key: Rope.
ROPE]
RETURNS [cipher: Rope.
ROPE] =
BEGIN
in, out, crypt: STREAM;
IF Rope.Size[plain]=0 THEN RETURN [""];
in ← RIS[plain];
out ← ROS[];
crypt ← StreamOpen[variety: output, key: key, cipher: out];
DO
crypt.PutChar[in.GetChar[ ! EndOfStream => EXIT ]];
ENDLOOP;
in.Close[];
crypt.Close[];
cipher ← out.RopeFromROS[];
out.Close[];
END;
EncryptRopeWithKey:
PUBLIC
PROC [plain: Rope.
ROPE, key: DESFace.Key]
RETURNS [cipher: Rope.
ROPE] =
BEGIN
in, out, crypt: STREAM;
IF Rope.Size[plain]=0 THEN RETURN [""];
in ← RIS[plain];
out ← ROS[];
crypt ← StreamOpenWithKey[variety: output, key: key, cipher: out];
DO
crypt.PutChar[in.GetChar[ ! EndOfStream => EXIT ]];
ENDLOOP;
in.Close[];
crypt.Close[];
cipher ← out.RopeFromROS[];
out.Close[];
END;
DecryptRope:
PUBLIC
PROC [cipher, key: Rope.
ROPE]
RETURNS [plain: Rope.
ROPE] =
BEGIN
in, out, decrypt: STREAM;
IF Rope.Size[cipher]=0 THEN RETURN [""];
in ← RIS[cipher];
out ← ROS[];
decrypt ← StreamOpen[variety: input, key: key, cipher: in];
DO
out.PutChar[decrypt.GetChar[ ! EndOfStream => EXIT ]];
ENDLOOP;
in.Close[];
decrypt.Close[];
plain ← out.RopeFromROS[];
out.Close[];
END;
DecryptRopeWithKey:
PUBLIC
PROC [cipher: Rope.ROPE, key:
DESFace.Key]
RETURNS [plain: Rope.
ROPE] =
BEGIN
in, out, decrypt: STREAM;
IF Rope.Size[cipher]=0 THEN RETURN [""];
in ← RIS[cipher];
out ← ROS[];
decrypt ← StreamOpenWithKey[variety: input, key: key, cipher: in];
DO
out.PutChar[decrypt.GetChar[ ! EndOfStream => EXIT ]];
ENDLOOP;
in.Close[];
decrypt.Close[];
plain ← out.RopeFromROS[];
out.Close[];
END;
evenTiogaFormatting: UsualStreamOptions = [tiogaRead: FALSE];
UsualStreamOptions: TYPE = PACKED ARRAY FS.StreamOption OF TrueBool;
TrueBool: TYPE = BOOL ← TRUE;
EncryptFile:
PUBLIC
PROC [key, plain, cipher: Rope.
ROPE] =
BEGIN
in, out: IO.STREAM ← NIL;
CloseFiles:
PROC [] =
{IF in#NIL THEN in.Close[]; IF out#NIL THEN out.Close[]};
BEGIN ENABLE UNWIND => CloseFiles[];
crypt: IO.STREAM;
byteCount: INT ← 0;
in ← FS.StreamOpen[fileName: plain, streamOptions: evenTiogaFormatting];
out ← FS.StreamOpen[fileName: cipher, accessOptions: create, streamOptions: evenTiogaFormatting];
crypt ← StreamOpen[variety: output, key: key, cipher: out];
DO
crypt.PutChar[in.GetChar[ ! EndOfStream => EXIT ]];
byteCount ← byteCount+1;
ENDLOOP;
crypt.Close[];
END;
CloseFiles[];
END;
EncryptFileWithKey:
PUBLIC
PROC [key: DESFace.Key, plain, cipher: Rope.
ROPE] =
BEGIN
in, out: IO.STREAM ← NIL;
CloseFiles:
PROC [] =
{IF in#NIL THEN in.Close[]; IF out#NIL THEN out.Close[]};
BEGIN ENABLE UNWIND => CloseFiles[];
crypt: IO.STREAM;
byteCount: INT ← 0;
in ← FS.StreamOpen[fileName: plain, streamOptions: evenTiogaFormatting];
out ← FS.StreamOpen[fileName: cipher, accessOptions: create, streamOptions: evenTiogaFormatting];
crypt ← StreamOpenWithKey[variety: output, key: key, cipher: out];
DO
crypt.PutChar[in.GetChar[ ! EndOfStream => EXIT ]];
byteCount ← byteCount+1;
ENDLOOP;
crypt.Close[];
END;
CloseFiles[];
END;
DecryptFile:
PUBLIC
PROC [key, plain, cipher: Rope.
ROPE] =
BEGIN
in, out: IO.STREAM ← NIL;
CloseFiles:
PROC [] =
{IF in#NIL THEN in.Close[]; IF out#NIL THEN out.Close[]};
BEGIN ENABLE UNWIND => CloseFiles[];
decrypt: IO.STREAM;
byteCount: INT ← 0;
in ← FS.StreamOpen[fileName: cipher, streamOptions: evenTiogaFormatting];
out ← FS.StreamOpen[fileName: plain, accessOptions: create, streamOptions: evenTiogaFormatting];
decrypt ← StreamOpen[variety: input, key: key, cipher: in];
DO
out.PutChar[decrypt.GetChar[ ! EndOfStream => EXIT ]];
byteCount ← byteCount+1;
ENDLOOP;
decrypt.Close[];
END;
CloseFiles[];
END;
DecryptFileWithKey:
PUBLIC
PROC [key: DESFace.Key, plain, cipher: Rope.
ROPE] =
BEGIN
in, out: IO.STREAM ← NIL;
CloseFiles:
PROC [] =
{IF in#NIL THEN in.Close[]; IF out#NIL THEN out.Close[]};
BEGIN ENABLE UNWIND => CloseFiles[];
decrypt: IO.STREAM;
byteCount: INT ← 0;
in ← FS.StreamOpen[fileName: cipher, streamOptions: evenTiogaFormatting];
out ← FS.StreamOpen[fileName: plain, accessOptions: create, streamOptions: evenTiogaFormatting];
decrypt ← StreamOpenWithKey[variety: input, key: key, cipher: in];
DO
out.PutChar[decrypt.GetChar[ ! EndOfStream => EXIT ]];
byteCount ← byteCount+1;
ENDLOOP;
decrypt.Close[];
END;
CloseFiles[];
END;
GetRandomText:
PROC
RETURNS [
REF
TEXT] =
BEGIN
t: REF TEXT = NEW[TEXT[DESBlkSize]];
t.length ← DESBlkSize;
TRUSTED {RefTextToBlocks[t][0] ← DESFace.GetRandomIV[]};
RETURN[t];
END;
RopeToDESKey:
PROC [key: Rope.
ROPE]
RETURNS [k: DESFace.Key] =
TRUSTED {RETURN[DESFace.MakeKey[LOOPHOLE[Rope.ToRefText[key]]]]};
RefTextToBlocks:
PROC [t:
REF
TEXT]
RETURNS [DESFace.Blocks] =
INLINE
{TRUSTED {RETURN[LOOPHOLE[LOOPHOLE[t, LONG POINTER]+SIZE[TEXT[0]]]]}};