EncryptImpl.mesa; Client interface for DES encryption
Copyright © 1985 by Xerox Corporation. All rights reserved.
Edited by McCreight, June 2, 1983 10:44 am
Edited by: Mitchell, May 23, 1983 5:44 pm
Edited by: McCreight, February 9, 1984 9:05 am
Last Edited by: Greene, June 27, 1984 3:43:12 pm PDT
Swinehart, July 5, 1985 2:27:55 pm PDT
DIRECTORY
DESFace,
Encrypt,
FS,
IO,
Rope;
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[0], -- 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: BOOLFALSE] =
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 = BOOLTRUE;
EncryptFile: PUBLIC PROC [key, plain, cipher: Rope.ROPE] =
BEGIN
in, out: IO.STREAMNIL;
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.STREAMNIL;
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.STREAMNIL;
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.STREAMNIL;
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]]]]}};
END.