home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1992 March / Source_Code_CD-ROM_Walnut_Creek_March_1992.iso / usenet / altsrcs / 3 / 3458 < prev    next >
Internet Message Format  |  1991-06-07  |  23KB

  1. From: alanf@bruce.cs.monash.OZ.AU (Alan Grant Finlay)
  2. Newsgroups: alt.sources
  3. Subject: Tangle 3.2 MS-DOS file encryption program (Turbo C source)
  4. Message-ID: <4431@bruce.cs.monash.OZ.AU>
  5. Date: 7 Jun 91 10:15:01 GMT
  6.  
  7.  
  8. This is the Turbo-C source distribution of tangle 3.2, a file
  9. encryption program for MS-DOS to run on IBM-PC compatibles.
  10.  
  11. Files are separated and preceded with ---------<file name>---------------
  12.  
  13. The file read.me refers to some files which are only required for an
  14. executable distribution, they can easily be manufactured.
  15.  
  16. --------------------<read.me>-----------------------
  17. This file accompanies a set of  programs  to  support  a  personal  encryption
  18. system  for  use  with  IBM-PC  compatibles  running  MS-DOS.  The encryption/
  19. decryption process is symmetric and takes only a few seconds for  one  or  two
  20. pages of text.  Very large files take a significant length of time to convert.
  21. The source files for "tangle.com" and "untangle.com" are "tangle.c"  with  the
  22. ENCRYPT  constant  set  to  1 or 0 respectively.  Since not all users may have
  23. access to the Turbo-C compiler, executable versions are  also  supplied.   The
  24. file  "tangle.doc"  is a user manual.  A description of the algorithm is given
  25. in "algthm.txt", and the author's address in "author.txt".  The file "dir.cpt"
  26. is a directory of the distribution disk (without itself listed) encrypted with
  27. the password "abracadabra".   You can untangle this file and check file sizes.
  28. To use the programs type "tangle src dst"  or  "untangle src dst" where src is 
  29. source file and dst is the destination file.
  30. ----------------------<tangle.doc>---------------------
  31.                          User manual for Tangle 3.2
  32.                          --------------------------
  33.  
  34. This program supercedes Crypt 2.0, a name change has  been  adopted  to  avoid
  35. confusion with the Unix password encryption function (one way trapdoor).  This
  36. program does not support the encryption algorithm of  Crypt  2.0  however  the
  37. name  change should make it somewhat easier to introduce version 3 without too
  38. much confusion and gradually convert the encrypted files.
  39.  
  40. The source code "tangle.c" can be  used  to  produce  two   executable   files
  41. which  are   assumed   to  be  called  "tangle"  and "untangle" by the program
  42. itself.  The source is in Turbo C (Trademark) and can be  compiled  with   the
  43. tiny   memory model.   Since  use  is  made  of system dependent functions for
  44. obtaining the input file size for tangle, the program is not portable as is.
  45.  
  46. To use tangle and untangle simply type the command name followed by the source
  47. and  destination  file  names.  You will be prompted for a password and status
  48. information is produced.  The password is  echoed  since  for  the  length  of
  49. password  intended it would be too easy to mess it up.  The password should be
  50. a long sequence of characters such as a sentence with some  strange  words  or
  51. characters  in  it.  The maximum length is the same as the maximum block array
  52. width (100 as supplied).  The password is scrolled off the  screen  after  the
  53. line  is  terminated  and  an  error  checking  code  is displayed.  The error
  54. checking code number reveals some  information  about  the  password  but  not
  55. enough to worry about.  The purpose of the error checking code is to alert the
  56. user to an incorrectly typed password, since it is a function of the  password
  57. it should always be the same when the same password is used.
  58.  
  59. The algorithm used was inspired by the Rubik's cube.  The core algorithm works
  60. as follows:
  61.  
  62.    First a fixed block size is determined which is the product of two numbers
  63.    "wide" and "high".  The data is processed in a two dimensional array with
  64.    these dimensions one block at a time.  If the last block cannot be entirely
  65.    filled it is padded with junk characters.  The password is used to
  66.    determine a key on a column per character basis.  The key value for each
  67.    column (modulo the height) is the amount this column is shifted down.  The
  68.    part which emerges from the bottom is inserted into the top (i.e. a cyclic
  69.    shift).  The rows are similarly shifted but using the values 1,2,3,...,etc.
  70.    This two stage process which is called a shuffle is repeated a fixed number
  71.    of times.  The result is a permutation of the array determined by the
  72.    password alone.
  73.  
  74. This algorithm produces a pure permutation of the data which has the advantage
  75. of  not  changing  the character set but weaknesses that cannot be ignored for
  76. any serious use.  A number of modifications to the algorithm produce one which
  77. in  the  author's opinion is secure against a chosen plaintext attack and if a
  78. large enough good password is used then immune to brute force attack  for  the
  79. conceivable future.
  80.  
  81. The first modification is to change the nature of the algorithm so that it  is
  82. no  longer  a pure permutation.  The strategy adopted is to include a phase in
  83. each shuffle which replaces each second line by the  bitwise  exclusive-or  of
  84. itself  and  the  line  before.  This means that the history of the encryption
  85. process, which characters happen to  be  located  above  each  other  in  each
  86. shuffle, affects the final outcome.  An analogy is that of shuffling a pack of
  87. cards on which the ink is still wet as compared to  a  normal  shuffle.   With
  88. this  modification  the  algorithm  is  strong against known plaintext attack.
  89. Secure that is provided the known text is not a simple pattern (e.g. all space
  90. characters).   To secure against simple patterned source a simple substitution
  91. of the block is performed after the first  shuffle.   This  eliminates  simple
  92. patterns  and  also  prevents  a  simple  chosen plaintext attack strategy the
  93. author discovered which applies to Crypt 2.0 (Tangle's predecessor).
  94.  
  95. If the input file is smaller than 450 bytes it is padded to  this  size.   The
  96. block  size  is  determined  according  to  the size of the input file and the
  97. minimum size.  For files smaller than 10 Kbyte there will be only  one  block.
  98. Each  block  has two dimensions - wide and high.  The last block usually needs
  99. some padding which is chosen from whatever happens to be in the array already.
  100. The  junk  is  selected  randomly  though the random number generator has only
  101. about 30,000 possible seeds. The password or key is a string supplied  by  the
  102. user,  this  is extended to the maximum length (100) by repetition however the
  103. repeated strings are modified  by  exclusive-or  '152'  to  help  extract  the
  104. maximum  information  from the supplied string (since later only the remainder
  105. mod high will be utilised).
  106.  
  107. The security of the system is highly dependent on the choice of  password  and
  108. particularly  its  length.   Passwords  which  are  too  short  are  rejected.
  109. Naturally  the  password  is  not   recorded  permanently   anywhere   by  the
  110. encryption  program  and  version  3.0  erases the password from memory before
  111. exit.  The easiest way to choose a good password is to use  a  whole  sentence
  112. (spaces and punctuation are  allowed) with  some  strange  characters  in  it.
  113. It is relatively easy for a cryptanalyst to try out all short  sentences  with
  114. words  from  a  standard  dictionary.  Brute  force  cracking of an  encrypted
  115. file with  n blocks of block size b, a password of p units chosen from  a  set
  116. of  q  values  and  with  s  shuffles  per  block  requires  O(s  * b * (q^p))
  117. operations.  Obviously increasing q and/or p pays great dividends.  For a pure
  118. permutation  it  would be possible to try all b!  rearrangements of  a  block.
  119. This  is ruled out by the minimum block size being 450.  To check all possible
  120. keys  with  the  minimum block dimensions of 32*15 (width*height) and with the
  121. minimum password length of 10 characters we need to  consider  15^10  possible
  122. keys.   15^10  possibilities would take a week to check at the rate of one per
  123. microsecond.  Actually the number of keys is greater than this (note  how  the
  124. password  is extended above) so it would take longer.  Using a password length
  125. of 15 characters the time would be  increased  to  over  ten  thousand  years.
  126. Longer  passwords are supported by the program since these calculations assume
  127. an unpredictable password is used.  Even if the  password  is  chosen  from  a
  128. dictionary of 1000 four letter words the program would allow 6 such words with
  129. the minimum block size corresponding to 1000^6  keys  which  is  approximately
  130. 15^15.   It  is  clear that increasing s is not an effective way of  improving
  131. security against brute force attack.  The time taken by the algorithm is O(s *
  132. b  *  n).   Since  (b*n)  cannot  be changed s should be chosen as small as is
  133. considered safe.  Note that s is  a  compile  time  constant  for  the  Tangle
  134. program and may be changed (it is called SECURE).
  135.  
  136. There is another security issue to consider.  Even when a cyphertext cannot be
  137. deciphered  it  may  reveal  information  about  a  sequence  of  cyphertexts.
  138. Consider the encryption of two files differing by  only  one  character.   The
  139. difference  in  the  input files causes a cascade of differences in the output
  140. files due to the exclusive-or in  the  shuffle  operation.   This  cascade  is
  141. approximately  1.5 to the power of the number of shuffles when the differences
  142. are sparse hence ideally the value chosen for SECURE should be  at  least  the
  143. logarithm base 1.5 of the block size.  The block size varies from 450 to 10000.
  144. Hence a conservative choice for SECURE would be 2*LOG(100)/LOG(1.5)=23.  Using
  145. the minimum block size of 450, SECURE=15 would be sufficient.  This should not
  146. be taken too seriously however since if the files require more than one  block
  147. then  the identical input blocks (except most likely the last block due to the
  148. padding) will be encrypted identically.  The value chosen for SECURE in Tangle
  149. is  10  which  is  a  compromise.
  150.  
  151. The security of the above algorithm to detection of similar  sources  is  also
  152. compromised  by  the  fact  that  so far the algorithm encrypts each bit plane
  153. (corresponding to the 8 bits in a byte) independently.  This is a weakness  of
  154. version  2  which  has  been  corrected  in  version  3.0.   A  one  character
  155. difference will be a one bit difference in some planes but not very likely for
  156. all  planes.  To fix this the exclusive-or phase of the shuffle is modified in
  157. version 3 to add 1 mod 256 to the byte.  This means  the  bit  planes  are  no
  158. longer independent and each entire block is mixed together.  It would have been
  159. preferable to use a cyclic shift instead of adding 1 mod 256 but the C language
  160. does not have this operation.  Because a carry into the 8th bit position will
  161. only occur on average one time in 128, SECURE must be chosen large enough to
  162. cause a cascade of changes in the 8th plane.
  163.  
  164. If similar source produces similar output then it seems likely a chosen 
  165. plaintext attack could be mounted.  Hence for systems requiring this security
  166. SECURE should be chosen as large as the conservative estimates above (15 to 23,
  167. according to the block size).  Since the intended domain of application to
  168. Tangle is personal computer file security, the more modest default value of
  169. SECURE=10 should suffice.  Where protection against casual snooping is all that
  170. is required then SECURE=3 would be sufficient.  In this case it would be a good
  171. idea to remove the redundant phases of the last shuffle.  Of the three phases
  172. of a shuffle, only the first depends upon the key.  This means the final two
  173. phases of the last shuffle are redundant and may be omitted.
  174.  
  175.  
  176. Version 3 also incorporates a change recommended by Russel Herman that using a
  177. floating  point  library  for  a  single  use  of the square root function was
  178. unwarranted.  Version 3 uses a simple linear search  square  root  calculation
  179. and this cuts the size of the executable code by about 50% - thanks Herman!
  180.  
  181. Some final operational advice.  Confidential documents should be created using
  182. a  ram  disk or a floppy which will be destroyed.  Note that deleting and even
  183. overwriting magnetic media does not prevent the data's  recovery.   Watch  out
  184. for  editors  which keep temporary copies of files which they are working upon
  185. in places of their own choice.  A  deleted  temporary  file  is  often  easily
  186. recovered and you may not even be aware of its existence.
  187.  
  188. (The algorithm is the same for all versions 3.x)
  189.  
  190. Version 3.2: The only changes are the removal of a superfluous include and 
  191.              alterations to comments in the source.
  192.  
  193.  
  194. --------------------------------<tangle.c>---------------
  195. /***********************************************************************/
  196. /* (c) Copyright 1989, 1990, Alan Finlay.      Tangle, Version 3.2 .    */
  197. /* This program may be freely distributed but may not be sold or         */
  198. /* marketed in any form without the permission and written consent of     */
  199. /* the author.  I retain all copyrights to this program, in either the    */
  200. /* original or modified forms, and no violation, deletion, or change of   */
  201. /* the copyright notice is allowed.  I will accept no responsibility for  */
  202. /* any loss, damage, or compromised data caused directly or indirectly by */
  203. /* this program.  It is released on an "as is" basis with no warranty    */
  204. /* as to its being fit or suitable for encrypting any form of data.     */
  205. /***********************************************************************/
  206. #include <stdio.h>
  207. #include <stdlib.h>
  208. #include <dos.h>
  209. #include <sys\stat.h>
  210.  
  211. #define ENCRYPT 1        /* Choose tangle (1) or untangle (0) */
  212. #define DEBUG 0          /* Show page after each shuffle if non zero */
  213. #define TRUE -1
  214. #define FALSE 0
  215. #define LIMIT 100        /* Maximum block size is LIMIT*LIMIT */
  216. #define SECURE 10        /* The number of block transformations */
  217. #define MINB 450         /* Minimum block size - insecure if too small */
  218.  
  219. typedef unsigned char line[LIMIT];
  220.  
  221. char copyright[40] = "(c) copyright 1989,1990, Alan Finlay";
  222.  
  223. unpat(page,wide,high) /* Simple substitution to eliminate simple patterns */
  224.    line page[LIMIT];  /* [width,height] */
  225.    int wide,high;
  226.    {
  227.    int i,j,k;
  228.    k = 0;
  229.    for (i=0;i<wide;i++) for (j=0;j<high;j++) {
  230.       k = (k+7)%256;
  231.       page[i][j] = page[i][j] ^ k;
  232.       }
  233.    }
  234.  
  235. #if ENCRYPT
  236. shuffle(page1,code,wide,high)
  237.    line page1[LIMIT];  /* [width,height] */
  238.    line code;
  239.    int wide,high;
  240.    {
  241.    int i,j,k,key,shift;
  242.    line *mix1,*mix2;
  243.    line *oldline,page2[LIMIT];  /* [height,width] */
  244.    for (k=0;k<SECURE;k++) {
  245. #if DEBUG
  246.       show(page1,wide,high);
  247. #endif
  248.       /* Shift columns */
  249.       for (i=0;i<wide;i++) {
  250.          oldline = page1[i];
  251.          key = (int) code[i];
  252.          for (j=0;j<high;j++) page2[j][i] =(*oldline)[(j+key)%high];
  253.          }
  254.       /* Mixup */
  255.          for (j=1;j<high;j+=2) {
  256.          mix1 = page2[j-1];
  257.          mix2 = page2[j];
  258.          for (i=0;i<wide;i++) (*mix2)[i] = ((*mix2)[i]^(*mix1)[i])+1;
  259.                /* Assume overflow ignored so 255+1==0 */
  260.          }
  261.       /* Shift rows */
  262.       for (j=0;j<high;j++) {
  263.          oldline = page2[j];
  264.          shift = (j%(wide-1))+1;
  265.          for (i=0;i<wide;i++) page1[i][j] = (*oldline)[(i+shift)%wide];
  266.          }
  267.       /* Eliminate any pattern (after first iteration only) */
  268.       if (k==0) unpat(page1,wide,high);
  269.       }
  270.    }
  271.  
  272. #else
  273. unshuffle(page1,code,wide,high)
  274.    line page1[LIMIT];  /* [width,height] */
  275.    line code;
  276.    int wide,high;
  277.    {
  278.    int i,j,k,key,shift;
  279.    line *mix1,*mix2;
  280.    line *newline,page2[LIMIT];  /* [height,width] */
  281.    for (k=0;k<SECURE;k++) {
  282. #if DEBUG
  283.       show(page1,wide,high);
  284. #endif
  285.       /* Eliminate any pattern (before last iteration only) */
  286.       if (k==SECURE-1) unpat(page1,wide,high);
  287.       /* Shift rows back */
  288.       for (j=0;j<high;j++) {
  289.          newline = page2[j];
  290.          shift = wide-(j%(wide-1))-1;
  291.          for (i=0;i<wide;i++) (*newline)[i] = page1[(i+shift)%wide][j];
  292.          }
  293.       /* Reverse mixup */
  294.       for (j=1;j<high;j+=2) {
  295.          mix1 = page2[j-1];
  296.          mix2 = page2[j];
  297.          for (i=0;i<wide;i++) (*mix2)[i] = ((*mix2)[i]-1)^(*mix1)[i];
  298.                 /*  Assume underflow is ignored so 0-1==255 */
  299.          }
  300.       /* Shift columns back */
  301.       for (i=0;i<wide;i++) {
  302.          newline = page1[i];
  303.          key = (int) code[i];
  304.          for (j=0;j<high;j++) (*newline)[(j+key)%high] = page2[j][i];
  305.          }
  306.       }
  307.    }
  308. #endif
  309.  
  310. show(page,wide,high)
  311.    line page[LIMIT];
  312.    int wide,high;
  313.    {
  314.    int i,j;
  315.    puts("\n");
  316.    for (j=0;j<high;j++) {
  317.       putc('\n',stdout);
  318.       for (i=0;i<wide;i++) {
  319.          if (page[i][j]<30) putc('*',stdout);
  320.          else putc(page[i][j],stdout);
  321.          }
  322.       }
  323.    }
  324.  
  325. main (argc,argv)
  326.    int argc;
  327.    char *argv[];
  328. {
  329.    FILE *infile,*outfile;
  330.    int wide,high,i,j,k;    /* Block width and height, loop counters */
  331.    int blkn = 1;           /* Block counter */
  332.    int clen;        /* Password code length */
  333.    long chksum;     /* Password checksum */
  334.    int ch = 0;
  335.    int invers;             /* Version of input file for decrypt */
  336.    int vers = 3;           /* Version of this program */
  337.    line page[LIMIT],code;
  338. #if ENCRYPT
  339.    int chrcnt;       /* Character counter */
  340.    long fsize;       /* Input file size */
  341.    int blocksize,nblocks;
  342.    struct time t;  /* For system time */
  343.    struct stat st; /* For input file stats */
  344.    /* Randomise the rand() function */
  345.    gettime(&t);
  346.    srand(t.ti_min*400+t.ti_sec*100+t.ti_hund); /* random seed <30000 */
  347.    /* Check the input arguments */
  348.    if (argc!=3) {puts("\nUsage is: tangle src dst\n"); exit(1);}
  349. #else
  350.    int blkcnt;
  351.    /* Check the input arguments */
  352.    if (argc!=3) {puts("\nUsage is: untangle src dst\n"); exit(1);}
  353. #endif
  354.    if ((infile = fopen(argv[1],"rb")) == NULL) {
  355.       printf("\n%s",argv[1]); perror(" "); exit(1);}
  356.    if ((outfile = fopen(argv[2],"wb")) == NULL) {
  357.       printf("\n%s",argv[2]); perror(" "); exit(1);}
  358. #if ENCRYPT
  359.    /* Get input file size */
  360.    if (stat(argv[1],&st)!=0) {perror(" "); exit(1);}
  361.    fsize = st.st_size;
  362.    printf("The input file size is %ld\n",fsize);
  363.    /* Choose block size accordingly */
  364.    if (fsize<(LIMIT*LIMIT)) blocksize = (int) fsize;
  365.    else {
  366.       nblocks = (int) (fsize/(LIMIT*LIMIT)+1);
  367.       blocksize = (int) (fsize/nblocks+1);
  368.       }
  369.    if (fsize<MINB) blocksize = MINB;    /* Minimum block size enforced */
  370.    wide = 0; while (wide*wide<blocksize) wide++;  /* Approx square root */
  371.    wide = wide+10; if (wide>LIMIT) wide = LIMIT;
  372.    high = blocksize/wide+1; if (high>LIMIT) high = LIMIT;
  373.    while (1) {
  374.       blocksize = wide*high;
  375.       if (fsize<(long) blocksize) break;
  376.       else {
  377.          /* Multiple blocks, check for last block too small */
  378.          if (((fsize-1)%blocksize)>(blocksize*3/4)) break;
  379.          /* (fsize-1) is used above so perfect fit is accepted! */
  380.          high--; wide--; /* Try a smaller block */
  381.          }
  382.       if (wide<50) break;
  383.       }
  384.    printf("The width and height are (%d,%d)\n",wide,high);
  385.    printf("The last block is %ld bytes\n",((fsize-1)%blocksize)+1);
  386.    fprintf(outfile,"%d,%d,%d,",vers,wide,high);
  387. #else
  388.    fscanf(infile,"%d,%d,%d,",&invers,&wide,&high);
  389.    if (invers!=vers) {
  390.       printf("This is version %d of the encryption program.\n",vers);
  391.       printf("The input file is for program version %d or invalid.\n",invers);
  392.       exit(1);
  393.       }
  394. #endif
  395.    /* Get password */
  396.    while(1) {
  397.       puts("\nPlease enter your password");
  398.       fgets(code,LIMIT,stdin);
  399.       clen = strlen(code);
  400.       if (clen>9) break;
  401.       puts("Insecure password, try a longer one.");
  402.       puts("For security do not use a name or word in any dictionary.");
  403.       puts("For example use something like \"Dazed and Konfuzed\"");
  404.       }
  405.    for (i=0;i<25;i++) puts(" ");   /* Clear the screen */
  406.    if (clen>wide) puts("Warning: tail of password ignored");
  407.    /* Extend password to possible limit, not null terminated */
  408.    for (i=clen;i<LIMIT;i++) code[i] = code[i%clen] ^ '\152';
  409.    /* Generate a checksum for the characters */
  410.    for (chksum=0,i=0;i<clen;i++) chksum += (int) code[i]*i;
  411.    printf("The password checksum is %ld.  Please wait ...\n",chksum % 1000);
  412.    do { /* tangle or untangle a block */
  413. #if ENCRYPT
  414.       chrcnt = 0;
  415. #else
  416.       if (fscanf(infile,"%d,",&blkcnt)==EOF) goto NOBLOCK;
  417. #endif
  418.       for (j=0;j<high;j++) {
  419.          for (i=0;i<wide;i++) {
  420.             if ((ch = getc(infile)) != EOF) {
  421.                page[i][j] = ch;
  422. #if ENCRYPT
  423.                chrcnt++;}
  424.             else if (i==0 && j==0) goto NOBLOCK; /* EOF at start of block! */
  425.             /* Pad the last block with existing junk */
  426.             else page[i][j] = page[rand()%wide][rand()%high];
  427. #else
  428.                ;}
  429.             else {puts("Error: unexpected end of file"); goto NOBLOCK;}
  430. #endif
  431.             }
  432.          }
  433. #if ENCRYPT
  434.       fprintf(outfile,"%d,",chrcnt);
  435.       shuffle(page,code,wide,high);
  436.       for (j=0;j<high;j++) for (i=0;i<wide;i++) putc(page[i][j],outfile);
  437. #else
  438.       unshuffle(page,code,wide,high);
  439.       for (j=0;j<high;j++) for (i=0;i<wide;i++)
  440.          if ((j*wide+i)<blkcnt) putc(page[i][j],outfile);
  441. #endif
  442.       printf("Finished block number %d\n",blkn++);
  443.       }
  444.    while (ch != EOF);
  445. NOBLOCK:                  /* Jump here to avoid writing an empty block */
  446.    for (i=0;i<LIMIT;i++) code[i] = ' ';   /* Rubout the password before exit */
  447.    fclose(infile);
  448.    fclose(outfile);
  449. }
  450. ------------------------------<author.txt>-------------------
  451. Alan Finlay
  452. Computer Science Dept.
  453. Monash University
  454. CLAYTON VIC 3168
  455. Australia
  456.  
  457. -------------------------
  458. email: alanf@bruce.oz.au
  459. N.B. I wrote this program on my own machine in my own time, hence it does
  460. not belong to Monash University.
  461. --------------------------------<algthm.txt>-----------------
  462. For each block, let us call it page[x,y] where 0<=x<wide and 0<=y<high:
  463. (it is understood that each line is for all x<wide and for all y<high)
  464. -----------------------------------------------------------------------
  465.    1) Do one shuffle:
  466.          page[x,y] := page[x,(y+key[x]) mod high]
  467.          if y is odd then page[x,y] := (page[x,y] XOR page[x,y-1])+1 mod 256
  468.          page[x,y] := page[(x+(y mod (wide-1))+1) mod wide,y]
  469.  
  470.    2) Perform a simple substitution:
  471.          page[x,y] := page[x,y] XOR ((x*high+y)*7 mod 256)
  472.  
  473.    3) Do 9 shuffles:
  474.          page[x,y] := page[x,(y+key[x]) mod high]
  475.          if y is odd then page[x,y] := (page[x,y] XOR page[x,y-1])+1 mod 256
  476.          page[x,y] := page[(x+(y mod (wide-1))+1) mod wide,y]
  477.  
  478. Each step in the process is easily reversible.
  479. -------------------------------------------------------------------------
  480.  
  481. The encrypted file format is:
  482.         1) Three decimal integers: program version number (= 3), wide, high.
  483.            Each as chars terminated by a comma (i.e. C format "%d,%d,%d,").
  484.         2) for each block:
  485.               The real number of characters in the block as a comma
  486.               terminated decimal integer (i.e. C format "%d,").
  487.               The block of bytes:
  488.                  for (j:=0..high-1), for (i:=0..wide-1) page[i,j]
  489.  
  490. -------------------------------------------------------------------------
  491. ---------------- end of Tangle 3.2 source distribution ------------------
  492.