<> <> <> <> <> <> <> <<>> 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: 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 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; 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]]]]}}; END.