ISAAC is a cryptographically secure pseudo-random number generator (CSPRNG) and stream cipher. It was developed by Bob Jenkins from 1993 (http://burtleburtle.net/bob/rand/isaac.html) and placed in the Public Domain. ISAAC is fast - especially when optimised - and portable to most architectures in nearly all programming and scripting languages.
It is also simple and succinct, using as it does just two 256-word arrays for its state.

ISAAC stands for "Indirection, Shift, Accumulate, Add, and Count" which are the principal bitwise operations employed.
To date - and that's after more than 20 years of existence - ISAAC has not been broken (unless GCHQ or NSA did it, but they wouldn't be telling).
ISAAC thus deserves a lot more attention than it has hitherto received and it would be salutary to see it more universally implemented.



Task

Translate ISAAC's reference C or Pascal code into your language of choice.

The RNG should then be seeded with the string "this is my secret key" and
finally the message "a Top Secret secret" should be encrypted on that key.
Your program's output cipher-text will be a string of hexadecimal digits.

Optional: Include a decryption check by re-initializing ISAAC and performing
the same encryption pass on the cipher-text.

Please use the C or Pascal as a reference guide to these operations.

Two encryption schemes are possible:
(1) XOR (Vernam) or
(2) Caesar-shift mod 95 (Vigenère).
XOR is the simplest; C-shifting offers greater security.

You may choose either scheme, or both, but please specify which you used.
Here are the alternative sample outputs for checking purposes:

Message: a Top Secret secret
Key    : this is my secret key
XOR    : 1C0636190B1260233B35125F1E1D0E2F4C5422
MOD    : 734270227D36772A783B4F2A5F206266236978
XOR dcr: a Top Secret secret
MOD dcr: a Top Secret secret

No official seeding method for ISAAC has been published, but for this task
we may as well just inject the bytes of our key into the randrsl array,
padding with zeroes before mixing, like so:

// zeroise mm array
FOR i:= 0 TO 255 DO mm[i]:=0;
// check seed's highest array element
m := High(seed);
// inject the seed
FOR i:= 0 TO 255 DO BEGIN
	// in case seed[] has less than 256 elements.
	IF i>m THEN randrsl[i]:=0  
		ELSE randrsl[i]:=seed[i];
END;
// initialize ISAAC with seed
RandInit(true);

ISAAC can of course also be initialized with a single 32-bit unsigned integer in the manner of traditional RNGs, and indeed used as such for research and gaming purposes.
But building a strong and simple ISAAC-based stream cipher - replacing the irreparably broken RC4 - is our goal here: ISAAC's intended purpose.




 
MODULE RosettaIsaac;
 
FROM Strings IMPORT
  Length, Assign, Append;
FROM STextIO IMPORT
  WriteString, WriteLn;
FROM Conversions IMPORT
  CardBaseToStr;
 
CONST
  MaxStrLength = 256;
 
TYPE
  TMode = (iEncrypt, iDecrypt);
  TString = ARRAY [0 .. MaxStrLength - 1] OF CHAR;
  TCardIndexedFrom0To7 = ARRAY [0 .. 7] OF CARDINAL;
 
VAR
(* TASK globals *)
  Msg: TString = 'a Top Secret secret';
  Key: TString = 'this is my secret key';
  XorCipherText: TString = '';
  ModCipherText: TString = '';
  XorPlainText: TString = '';
  ModPlainText: TString = '';
  Mode: TMode = iEncrypt;
  HexText: TString;
 
(* ISAAC globals *)
(* external results *)
  RandRsl: ARRAY [0 .. 256] OF CARDINAL;
  RandCnt: CARDINAL;
 
(* internal state *)
  MM: ARRAY [0 .. 256] OF CARDINAL;
  AA: CARDINAL = 0;
  BB: CARDINAL = 0;
  CC: CARDINAL = 0;
 
PROCEDURE Isaac;
VAR
  I, X, Y: CARDINAL;
BEGIN
   CC := CC + 1;    (* CC just gets incremented once per 256 results *)
   BB := BB + CC;   (* then combined with BB *)
   FOR I := 0 TO 255 DO
     X := MM[I];
     CASE (I MOD 4) OF
        0: AA := AA BXOR (AA SHL 13); |
        1: AA := AA BXOR (AA SHR 6); |
        2: AA := AA BXOR (AA SHL 2); |
        3: AA := AA BXOR (AA SHR 16);
     ELSE
     END;
     AA := MM[(I + 128) MOD 256] + AA;
     Y  := MM[(X SHR 2) MOD 256] + AA + BB;
     MM[I] := Y;
     BB := MM[(Y SHR 10) MOD 256] + X;
     RandRsl[I] := BB;
   END; (* FOR *)
   RandCnt := 0;  (* Prepare to use the first set of results. *)
END Isaac;
 
PROCEDURE Mix(VAR A: TCardIndexedFrom0To7);
BEGIN
  A[0] := A[0] BXOR A[1] SHL 11; A[3] := A[3] + A[0]; A[1] := A[1] + A[2];
  A[1] := A[1] BXOR A[2] SHR  2; A[4] := A[4] + A[1]; A[2] := A[2] + A[3];
  A[2] := A[2] BXOR A[3] SHL  8; A[5] := A[5] + A[2]; A[3] := A[3] + A[4];
  A[3] := A[3] BXOR A[4] SHR 16; A[6] := A[6] + A[3]; A[4] := A[4] + A[5];
  A[4] := A[4] BXOR A[5] SHL 10; A[7] := A[7] + A[4]; A[5] := A[5] + A[6];
  A[5] := A[5] BXOR A[6] SHR  4; A[0] := A[0] + A[5]; A[6] := A[6] + A[7];
  A[6] := A[6] BXOR A[7] SHL  8; A[1] := A[1] + A[6]; A[7] := A[7] + A[0];
  A[7] := A[7] BXOR A[0] SHR  9; A[2] := A[2] + A[7]; A[0] := A[0] + A[1];
END Mix;
 
PROCEDURE RandInit(Flag: BOOLEAN);
VAR
  I, J: CARDINAL;
  A: TCardIndexedFrom0To7;
BEGIN
  AA := 0; BB := 0; CC := 0;
  A[0] := 2654435769; (* $9e3779b9: the golden ratio *)
  FOR J := 1 TO 7 DO
    A[J] := A[0];
  END;
 
  FOR I := 0 TO 3 DO (* Scramble it *)
    Mix(A);
  END;
  FOR I := 0 TO 255 BY 8 DO (* Fill in MM[] with messy stuff. *)
    IF Flag THEN (* Use all the information in the seed. *)
      FOR J := 0 TO 7 DO
        A[J] := A[J] + RandRsl[I + J];
      END;
    END;
    Mix(A);
    FOR J := 0 TO 7 DO
      MM[I + J] := A[J];
    END;
  END; (* FOR I*)
 
  IF Flag THEN
    (* Do a second pass to make all of the Seed affect all of MM *)
    FOR I := 0 TO 255 BY 8 DO
      FOR J := 0 TO 7 DO
        A[J] := A[J] + MM[I + J];
      END;
      Mix(A);
      FOR J := 0 TO 7 DO
        MM[I + J] := A[J];
      END;
    END; (* FOR I *)
  END;
  Isaac(); (* Fill in the first set of results *)
  RandCnt := 0; (* Prepare to use the first set of results *)
END RandInit;
 
(* Seed ISAAC with a given string.
  The string can be any size. The first 256 values will be used. *)
PROCEDURE SeedIsaac(Seed: ARRAY OF CHAR; Flag: BOOLEAN);
VAR
  I, M: CARDINAL;
BEGIN
  FOR I := 0 TO 255 DO
    MM[I] := 0;
  END;
  M := Length(Seed);
  FOR I := 0 TO 255 DO
  (* In case seed has less than 256 elements *)
    IF I > M THEN
      RandRsl[I] := 0
    ELSE
      (* Modula-2 strings are 0-based (at least, in this case). *)
      RandRsl[I] := ORD(Seed[I]);
    END;
  END;
  (* Initialize ISAAC with seed. *)
  RandInit(Flag);
END SeedIsaac;
 
(* Get a random 32-bit value 0..MAXINT *)
PROCEDURE GetRandom32Bit(): CARDINAL;
VAR
  Result: CARDINAL;
BEGIN
  Result := RandRsl[RandCnt];
  INC(RandCnt);
  IF RandCnt > 255 THEN
    Isaac();
    RandCnt := 0;
  END;
  RETURN Result;
END GetRandom32Bit;
 
(* Get a random character in printable ASCII range. *)
PROCEDURE GetRandomChar(): SHORTCARD;
BEGIN
  RETURN GetRandom32Bit() MOD 95 + 32;
END GetRandomChar;
 
(* Convert an ASCII string to a hexadecimal string. *)
PROCEDURE ASCII2Hex(Source: ARRAY OF CHAR; VAR OUT Destination: ARRAY OF CHAR);
VAR
  I: CARDINAL;
  NumbHex: ARRAY [0 .. 1] OF CHAR;
BEGIN
  Assign('', Destination);
  FOR I := 0 TO Length(Source) - 1 DO
    CardBaseToStr(ORD(Source[I]), 16, NumbHex);
    IF Length(NumbHex) <= 1 THEN
      Append('0', Destination);
    END;
    Append(NumbHex, Destination);
  END;
END ASCII2Hex;
 
(* XOR encrypt on random stream. *)
PROCEDURE Vernam(Msg: ARRAY OF CHAR; VAR OUT Destination: ARRAY OF CHAR);
VAR
  I: CARDINAL;
  OrdMsgI: SHORTCARD;
BEGIN
  Assign('', Destination);
  FOR I := 0 TO Length(Msg) - 1 DO
    OrdMsgI := ORD(Msg[I]);
    Append(CHR(GetRandomChar() BXOR OrdMsgI), Destination);
  END;
END Vernam;
 
(* Get position of the letter in chosen alphabet *)
PROCEDURE LetterNum(Letter, Start: CHAR): SHORTCARD;
BEGIN
  RETURN ORD(Letter) - ORD(Start);
END LetterNum;
 
(* Caesar-shift a character <Shift> places: Generalized Vigenere *)
PROCEDURE Caesar(M: TMode; Ch: CHAR; Shift, Modulo: INTEGER; Start: CHAR): CHAR;
VAR
  N, IntOrdStart: INTEGER;
BEGIN
  IF M = iDecrypt THEN
    Shift := -Shift;
  END;
  N := LetterNum(Ch, Start);
  N := N + Shift;
  N := N MOD Modulo;
  IF N < 0 THEN
    N := N + Modulo;
  END;
  IntOrdStart := ORD(Start);
  RETURN CHR(IntOrdStart + N);
END Caesar;
 
(* Vigenere mod 95 encryption & decryption. *)
PROCEDURE Vigenere(Msg: ARRAY OF CHAR; M: TMode; VAR OUT Destination: ARRAY OF CHAR);
VAR
  I: CARDINAL;
BEGIN
  Assign('', Destination);
  FOR I := 0 TO Length(Msg) - 1 DO
    Append(Caesar(M, Msg[I], GetRandomChar(), 95, ' '), Destination);
  END;
END Vigenere;
 
BEGIN
  (* (1) Seed ISAAC with the key *)
  SeedIsaac(Key, TRUE);
  (* (2) Encryption *)
  Mode := iEncrypt;
  (* (a) XOR (Vernam) *)
  Vernam(Msg, XorCipherText);
  (* (b) MOD (Vigenere) *)
  Vigenere(Msg, Mode, ModCipherText);
  (* (3) Decryption *)
  Mode := iDecrypt;
  SeedIsaac(Key, TRUE);
  (* (a) XOR (Vernam) *)
  Vernam(XorCipherText, XorPlainText);
  (* (b) MOD (Vigenere) *)
  Vigenere(ModCipherText, Mode, ModPlainText);
  (* program output *)
  WriteString('Message: '); WriteString(Msg); WriteLn;
  WriteString('Key    : '); WriteString(Key); WriteLn;
  ASCII2Hex(XorCipherText, HexText);
  WriteString('XOR    : '); WriteString(HexText); WriteLn;
  ASCII2Hex(ModCipherText, HexText);
  WriteString('MOD    : '); WriteString(HexText); WriteLn;
  WriteString('XOR dcr: '); WriteString(XorPlainText); WriteLn;
  WriteString('MOD dcr: '); WriteString(ModPlainText); WriteLn;
END RosettaIsaac.
 