The following example uses "standard" rexx built-in functions to read in some data from a file, convert it to base64, and output to another file.
I have defined the base64 "lookup array" using the stem variable named base64. Then, I read the data (to be converted to base64) from a stream. Each read process reads the next 3 bytes of the source file. I then transform these 3 bytes (24 bit) to 4x6bit (base 64). This gives me the "index" into the base64 array. Finally, I build a part of a record (ie, the converted data).
When a record is full (maybe for sending inside a SMTP-Mail), you must append a 'LF' character ('0A'X).
If you do not have at least 3 bytes inside your file, you must make a 'base64-like-file-end'.
base64.0='A';base64.1='B';base64.2='C';base64.3='D';base64.4='E';base64.5='F';base64.6='G'
base64.7='H';base64.8='I';base64.9='J';base64.10='K';base64.11='L';base64.12='M';base64.13='N'
base64.14='O';base64.15='P';base64.16='Q';base64.17='R';base64.18='S';base64.19='T';base64.20='U'
base64.21='V';base64.22='W';base64.23='X';base64.24='Y';base64.25='Z';base64.26='a';base64.27='b'
base64.28='c';base64.29='d';base64.30='e';base64.31='f';base64.32='g';base64.33='h';base64.34='i'
base64.35='j';base64.36='k';base64.37='l';base64.38='m';base64.39='n';base64.40='o';base64.41='p'
base64.42='q';base64.43='r';base64.44='s';base64.45='t';base64.46='u';base64.47='v';base64.48='w'
base64.49='x';base64.50='y';base64.51='z';base64.52='0';base64.53='1';base64.54='2';base64.55='3'
base64.56='4';base64.57='5';base64.58='6';base64.59='7';base64.60='8';base64.61='9';base64.62='+'
base64.63='/';base64.64='='
filein = 'c:testinp.txt'
fileout = 'c:testout.txt'
sdatal.z=""
size=CHARS(filein)
DO WHILE size>2
sdatalin=CHARIN(filein,,3)
sdataout=X2B(C2X(sdatalin))
x1=X2D(B2X(SUBSTR(sdataout,1,6)))
x2=X2D(B2X(SUBSTR(sdataout,7,6)))
x3=X2D(B2X(SUBSTR(sdataout,13,6)))
x4=X2D(B2X(SUBSTR(sdataout,19,6)))
sdatal.z=sdatal.z||base64.x1||base64.x2||base64.x3||base64.x4
DROP sdatalin
DROP sdataout
size=CHARS(filein)
END
SELECT
WHEN size=2 THEN DO
sdatalin=CHARIN(filein,,sbyte)
sdataout=X2B(C2X(sdatalin))
x1=X2D(B2X(SUBSTR(sdataout,1,6)))
x2=X2D(B2X(SUBSTR(sdataout,7,6)))
h3=SUBSTR(sdataout,13,4)||"00"
x3=X2D(B2X(h3))
x4=X2D(B2X(000000))
sdatal.z=sdatal.z||base64.x1||base64.x2||base64.x3||base64.x4
END
WHEN size=1 THEN DO
sdatalin=CHARIN(filein,,sbyte)
sdataout=X2B(C2X(sdatalin))
x1=X2D(B2X(SUBSTR(sdataout,1,6)))
h2=SUBSTR(sdataout,7,2)||"0000"
x2=X2D(B2X(h2))
x3=X2D(B2X(000000))
x4=X2D(B2X(000000))
sdatal.z=sdatal.z||base64.x1||base64.x2||base64.x3||base64.x4
END
OTHERWISE NOP
END
sdatal.z=sdatal.z||base64.64||'0A'x
x=CHAROUT(fileout,sdatal.z)
question:
CALL STREAM filein,"C","Close"
CALL STREAM fileout,"C","Close"
|
Excellent!
What this allows is to actually use a Base64 approach to do some mild encryption.
We plan to scramble the Base64 table & duplicate the table in mem so that there are 2 identical copies one after the other (128 bytes in size).
We then encrypt a line at a time but generate a random num between 0-63 & the 6-bit code for this number becomes the 1st char in that line. Every other char is encoded by 1st adding that random number to the starting position in the Base64 array.
This means that no two lines will come out the same, but providing the table sequence is maintained, the lines will always decode correctly (once the 1st 6-bit index is picked up).
It effectively provides a simple but workable encryption technique.
By scrambling the Base64 table, I mean re-sequencing it so it no longer follows the Base64 convention.
For example...table = "AbCd0EfGh1IjKlM2nOpQ3rStU4vWxY5zaBc6DeFg7HiJk8LmNo9PqRs+TuVw/XyZ" ...is an example of a scrambled sequence. Then by doubling the table, we get...table = "AbCd0EfGh1IjKlM2nOpQ3rStU4vWxY5zaBc6DeFg7HiJk8LmNo9PqRs+TuVw/XyZAbCd0EfGh1IjKlM2nOpQ3rStU4vWxY5zaBc6DeFg7HiJk8LmNo9PqRs+TuVw/XyZ" Table is now 128 bytes long. If we obtain a random number between 0 & 63, then add 1, we have a number between 1 & 64 (eg 25). This number would be used as the starting point in the 128 byte Table.
If we had an input hex stream where the 1st 24 bits were...00110001 00110010 00110011 (ascii CHARS 1, 2 & 3) ... the 4 positions we would normally index into the table (if it was still only 64 bytes long) would be 001100 010011 001000 110011 (or 12, 19, 8, 51). If we then add the random number (25) to each index, we end up with indexes that will be 25 positions further into the table (37, 44, 33, 76).
If the 1st 6bits of the resulting encrypted string are the random number, & the rest of that line are the 6-bit entries from the table, then when we decrypt we take the 1st 6 bits & use them to establish our base position in the same table, we get the same pattern back out. Because of the random number, no 2 lines will encrypt the same but will always decrypt correctly.
Here are two REXX scripts that show how to encrypt and then decrypt in base64.
filein = 'c:testinp.txt'
fileout = 'c:testout.txt' seedtable='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
basetable=seedtable||seedtable
size=CHARS(filein)
IF size < 1 THEN DO
SAY "Error: input file is empty"
RETURN(1)
END
CALL STREAM fileout,"C","Create"
randomnum=RANDOM(1,64)
coretable=SUBSTR(basetable, randomnum, 64)
keychar=SUBSTR(seedtable,randomnum,1)
output=keychar
DO WHILE size>2
sdatalin=CHARIN(filein,,3); SAY sdatalin
sdataout=X2B(C2X(sdatalin));
index1=X2D(B2X(SUBSTR(sdataout,1,6)))+1
index2=X2D(B2X(SUBSTR(sdataout,7,6)))+1
index3=X2D(B2X(SUBSTR(sdataout,13,6)))+1
index4=X2D(B2X(SUBSTR(sdataout,19,6)))+1
sdata=SUBSTR(coretable,index1,1)||substr(coretable,index2,1)||substr(coretable,index3,1)||substr(coretable,index4,1)
DROP sdatalin
DROP sdataout
output=output||sdata
size=CHARS(filein)
END SELECT
WHEN size=2 THEN DO
sdatalin=CHARIN(filein,,2) || ' '
sdataout=X2B(C2X(sdatalin))
index1=X2D(B2X(SUBSTR(sdataout,1,6)))+1
index2=X2D(B2X(SUBSTR(sdataout,7,6)))+1
index3=X2D(B2X(SUBSTR(sdataout,13,6)))+1
index4=X2D(B2X(SUBSTR(sdataout,19,6)))+1
sdata=SUBSTR(coretable,index1,1)||substr(coretable,index2,1)||substr(coretable,index3,1)||substr(coretable,index4,1)
output=output||sdata
END
WHEN size=1 THEN DO
sdatalin=CHARIN(filein,,2) || ' '
sdataout=X2B(C2X(sdatalin)); SAY sdataout
index1=X2D(B2X(SUBSTR(sdataout,1,6)))+1
index2=X2D(B2X(SUBSTR(sdataout,7,6)))+1
index3=X2D(B2X(SUBSTR(sdataout,13,6)))+1
index4=X2D(B2X(SUBSTR(sdataout,19,6)))+1
SAY index1 index2 index3 index4
sdata=SUBSTR(coretable,index1,1)||substr(coretable,index2,1)||substr(coretable,index3,1)||substr(coretable,index4,1)
output=output||sdata
END
OTHERWISE NOP
END
x=CHAROUT(fileout,output)
SAY output
CALL STREAM filein,"C","Close"
CALL STREAM fileout,"C","Close"
RETURN
filein = 'c:testout.txt'
output=''
size=CHARS(filein)-1
IF size>1 THEN sizerem=size//4
IF (size=0 | sizerem>0) THEN DO
SAY "filesize error - should be multiple of 4. Filesize is = " size
RETURN (1)
END
seedtable='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
basetable=seedtable||seedtable
fileout = 'c:testdec.txt'
CALL STREAM fileout,"C","Create"
keychar=CHARIN(filein,,1)
offset=POS(keychar,seedtable)
coretable=SUBSTR(basetable,offset,64)
i=0
DO WHILE size>3
i=i+1
crypt1=CHARIN(filein,,1)
crypt2=CHARIN(filein,,1)
crypt3=CHARIN(filein,,1)
crypt4=CHARIN(filein,,1)
index1=POS(crypt1,(coretable))-1
index2=POS(crypt2,(coretable))-1
index3=POS(crypt3,(coretable))-1
index4=POS(crypt4,(coretable))-1
binary1=X2B(D2X(index1,2))
binary2=X2B(D2X(index2,2))
binary3=X2B(D2X(index3,2))
binary4=X2B(D2X(index4,2))
binsub1=SUBSTR(binary1,3,6)
binsub2=SUBSTR(binary2,3,6)
binsub3=SUBSTR(binary3,3,6)
binsub4=SUBSTR(binary4,3,6)
hexstr = binsub1||binsub2||binsub3||binsub4
byte1=SUBSTR(hexstr,1,8)
byte2=SUBSTR(hexstr,9,8)
byte3=SUBSTR(hexstr,17,8)
char1=X2C(B2X(byte1))
char2=X2C(B2X(byte2))
char3=X2C(B2X(byte3))
output=output||char1||char2||char3
size=CHARS(filein)
END
x=CHAROUT(fileout, output); sizeout=LENGTH(output)
SAY "Encrypted chars in = " || (i*4) || "."
SAY "Decrypted chars out = " || sizeout ||"."
SAY "De-encrypted output =" output
question:
CALL STREAM filein,"C","Close"
CALL STREAM fileout,"C","Close" |