-------------------------------------------------------------------- -- LingoFish BlowFish Object -------------------------------------------------------------------- -- Script Type: Parent -------------------------------------------------------------------- -- Must be initialised with a key before use. The key must be an -- 8-bit binary string. -- Characters in the string can have any 8-bit value, including 0. -- -- Input and output text are also 8-bit binary strings. -- -- Ciphertext should be expected to include characters of any value, -- including 0. -- -- Also note that for strings not evenly divisible in length by 8 -- bytes (64 bits) the encrypted string and decrypted plaintext -- will be rounded up to the nearest length that is evenly -- divisible by 8. The decrypted plaintext will be padded at the -- end with 0 characters when this occurs. -- -- For ASCII zero terminated strings, a function may be used to strip -- these extra characters from the end of the string. For binary data -- The length of the data should be encrypted in a header, so that -- padding characters can be safely removed without accidentally -- destroying valid 0's at the end of the data stream. -- -- This object is intended to be relatively lightweight and compatible, -- so no attempt is made to implement header schemes, random padding, -- checksums, poratble encoding (such as base64), etc. It is -- recommended that these functions be provided in a separate wrapper -- object or set of library functions. -- -------------------------------------------------------------------- -- Public handler quick reference: -- -- blowfish = (script "LingoFish Blowfish Object").new( key:optional ) -- error = blowfish.SetKey( key ) -- cipherText = blowfish.Encrypt( plainText ) -- plainText = blowfish.Decrypt( cipherText ) -- -------------------------------------------------------------------- -- LICENSE: FREEWARE -- This source code is copyright Killing Moon Ltd. 2002 -- -- You may use this code freely, but if you redistribute the source -- code the you must abide by the following restrictions: -- -- 1 - If the code is unmodified, then all the comments must remain -- intact and in their original form. -- -- 2 - If the code is modified then either: -- 2(a) - All the original comments must be removed. You may -- credit the original author if you wish, but must make -- clear that your version is not identical to the -- original version. -- 2(b) - You must add clear documentation of ALL your alterations -- and additions to the code in the source comments. -- Additionall, you must place a clear notice at the top -- of the file that the source code has been modified. -- -- Code written by Robert Tweed. -- Email: robert@killingmoon.com -- Website: http://www.killingmoon.com/director/ -- -- Adapted from C reference code and the original Blowfish paper -- which can be found at: -- http://www.counterpane.com/blowfish.html -- -------------------------------------------------------------------- -- VERSION HISTORY: -- -- 2003.11.16: Version 1.11 by Robert Tweed -- No longer stores plaintext key in memory -- (minor security enhancement against memory hacking) -- Added getVersion() function -- -- 2002.10.31: Version 1.1 by Robert Tweed -- Fixed Mac numToChar bug and added extra optimisation -- -- 2002.06.01: Version 1.0 by Robert Tweed -- Fully operational, tested under Windows -- -------------------------------------------------------------------- -- OPTIMISATION NOTES: -- -- While the script is heavily optimised, there is one optimisation -- that has deliberately not been implemented. -- -- The script will slow down dramatically when processing strings -- Of 256KB or larger. -- This is due to a limitation of put...after. Optimisation -- techniques are discussed on the Killingmoon site. -- This has been left un-implemented because it would add weight -- and slow the function down slightly for string less than 256KB -- Since most practical use of this code will be for small strings, -- This choice seemed logical. -- -------------------------------------------------------------------- -- PROPERTIES: -- -- This object has no public properties. -- Private properties (internal use only) property blowfish_ok property blowfish_rounds property blowfish_P property blowfish_S1, blowfish_S2, blowfish_S3, blowfish_S4 -------------------------------------------------------------------- -- PUBLIC HANDLER DEFINITIONS -- ---[ Constructor ]-------------------------------------------------- -- object.new( key ) -- -- Creates the object. Does not initialise blowfish unless a key is -- specifed. Blowfish will not work until it has been initialised -- with a key. Note that if the key is invalid, blowfish will not -- work until a valid key is specifed. -- -- Parameters: -- [optional] key, [if specified] must be an 8-bit string -- -- Return values: -- ALWAYS: the blowfish object instance -- NB: The constuctor is unable to provide a return value to indicate -- whether a key was valid or not. If the key is not hard-coded, -- It should be set with SetKey instead to trap such errors. -- on new me, aKey -- Set the number of rounds to the standard for Blowfish of 16. -- May be changed by cryptologists for experimentation. -- Unless an expert, changing this is not recommended. -- If changed to a higer value, the initialisation constants will -- also need to be set accordingly. blowfish_rounds = 16 -- Default the key to false. This prevents encryption and decryption from running. -- It is set to true only after setKey is successful - just in case an attempt is -- made to use an invalid key, no encryption will occur using the wrong key. blowfish_ok = false -- If a key was specifed then pass it to SetKey for initialisation if StringP( aKey ) then me.SetKey( aKey ) -- Return the blowfish object return me end ---[ Version ]------------------------------------------------------ -- object.getVersion() -- -- Returns the current LingoFish version as a a string. -- on getVersion me return "1.11" end ---[ Initialisation ]----------------------------------------------- -- object.SetKey( key ) -- -- Initialises the blowfish engine with a given key. The key must be -- an 8-bit string. String lengths should be even multiples of 4 -- characters and no more than 56 characters (448 bits). -- -- Parameters: -- [required] key, is an 8-bit string -- -- Return values: -- SUCCESS: 0 -- FAILURE: error symbol: #error_notstring, #error_not32bit, #error_empty, #error_toolong -- on SetKey me, aKey -- Safety precaution: If this routine crashes midway, the others will not run. blowfish_ok = false -- Check that the supplied key is valid -- Note that if supplied key was invalid then any old key is erased. This prevents accidental -- encryption with the wrong key. if( not StringP( aKey ) ) then return #error_notstring if( aKey.length mod 4 <> 0 ) then return #error_not32bit if( aKey.length <= 0 ) then return #error_empty if( aKey.length > 56 ) then return #error_toolong -- Key is OK, begin initialisation of subkeys... -- Reset to initial PI constants as per the Blowfish specification LingoFish_GetInitialConstants( me ) -- XOR all P values with each 32-bit value in the key in turn j = 0 -- key character index repeat with i = 1 to blowfish_rounds+2 data = charToNum( aKey.char[j+4] ) \ + ( charToNum( aKey.char[j+3] ) * 256 ) \ + ( charToNum( aKey.char[j+2] ) * 65536 ) \ + ( charToNum( aKey.char[j+1] ) * 16777216 ) j = ( j + 4 ) mod aKey.length blowfish_P[i] = bitXor( blowfish_P[i], data ) end repeat -- Start iterative regeneration of subkeys. -- Begin with zero values as per the Blowfish specification dataLeft = 0 dataRight = 0 -- Regenerate P values i = 1 repeat while i < blowfish_rounds+2 data = me.blowfish_Encipher( dataLeft, dataRight ) dataLeft = data[ #left ] dataRight = data[ #right ] blowfish_P[i] = dataLeft blowfish_P[i+1] = dataRight i = i + 2 end repeat -- Regenerate S1 values i = 1 repeat while i < 256 data = me.blowfish_Encipher( dataLeft, dataRight ) dataLeft = data[ #left ] dataRight = data[ #right ] blowfish_S1[i] = dataLeft blowfish_S1[i+1] = dataRight i = i + 2 end repeat -- Regenerate S2 values i = 1 repeat while i < 256 data = me.blowfish_Encipher( dataLeft, dataRight ) dataLeft = data[ #left ] dataRight = data[ #right ] blowfish_S2[i] = dataLeft blowfish_S2[i+1] = dataRight i = i + 2 end repeat -- Regenerate S3 values i = 1 repeat while i < 256 data = me.blowfish_Encipher( dataLeft, dataRight ) dataLeft = data[ #left ] dataRight = data[ #right ] blowfish_S3[i] = dataLeft blowfish_S3[i+1] = dataRight i = i + 2 end repeat -- Regenerate S4 values i = 1 repeat while i < 256 data = me.blowfish_Encipher( dataLeft, dataRight ) dataLeft = data[ #left ] dataRight = data[ #right ] blowfish_S4[i] = dataLeft blowfish_S4[i+1] = dataRight i = i + 2 end repeat -- Now that everything is finished successfully, the key can be assigned blowfish_ok = true -- Return 0 to indicate success return 0 end ---[ Encryption ]--------------------------------------------------- -- object.Encrypt( plainText ) -- -- Encrypts a string using the current key. -- NB: The string returned may contain any 8-bit character with a -- numerical code from 0 to 255. This is undesirable for general use -- so it may be preferrable to encode it using base64 or similar. -- If such a technique is used, remember to decode the string prior -- to decryption! -- -- Parameters: -- [required] plainText is an 8-bit string -- -- Return values: -- SUCCESS: an 8-bit string containing the cipherText -- FAILURE: void -- on Encrypt me, plainText -- check that the key and the plaintext are both valid if( blowfish_ok <> true or not StringP( plainText ) ) then return void -- Set the output ciphertext to an empty string cipherText = "" -- Start with the first character from the input stream i = 1 plainTextLength = plainText.length repeat while i <= plainTextLength -- Get the first 32 bits from the current index dataLeft = charToNum( plainText.char[i+3] ) \ + ( charToNum( plainText.char[i+2] ) * 256 ) \ + ( charToNum( plainText.char[i+1] ) * 65536 ) \ + ( charToNum( plainText.char[i] ) * 16777216 ) -- and the next 32 bits dataRight = charToNum( plainText.char[i+7] ) \ + ( charToNum( plainText.char[i+6] ) * 256 ) \ + ( charToNum( plainText.char[i+5] ) * 65536 ) \ + ( charToNum( plainText.char[i+4] ) * 16777216 ) -- Encipher the dword pair with Blowfish data = me.blowfish_Encipher( dataLeft, dataRight ) dataLeft = data[ #left ] dataRight = data[ #right ] -- Get the 8 characters that form the returned dword pair -- and append them to the output stream put numToChar( bitAnd( bitAnd( dataLeft, -16777216 ) / 16777216, 255 ) ) after cipherText put numToChar( ( bitAnd( dataLeft, 16711680 ) / 65536 ) ) after cipherText put numToChar( ( bitAnd( dataLeft, 65280 ) / 256 ) ) after cipherText put numToChar( bitAnd( dataLeft, 255 ) ) after cipherText put numToChar( bitAnd( bitAnd( dataRight, -16777216 ) / 16777216, 255 ) ) after cipherText put numToChar( ( bitAnd( dataRight, 16711680 ) / 65536 ) ) after cipherText put numToChar( ( bitAnd( dataRight, 65280 ) / 256 ) ) after cipherText put numToChar( bitAnd( dataRight, 255 ) ) after cipherText -- Add 8 to the input stream index pointer i = i + 8 end repeat -- Return the cipertext return cipherText end ---[ Decryption ]--------------------------------------------------- -- object.Decrypt( cipherText ) -- -- Decrypts a string using the current key. -- -- Parameters: -- [required] cipherText is an 8-bit string -- -- Return values: -- SUCCESS: an 8-bit string containing the plainText -- FAILURE: void -- on Decrypt me, cipherText -- check that the key and the ciphertext are both valid if( blowfish_ok <> true or not StringP( cipherText ) ) then return void -- Set the output plaintext to an empty string plainText = "" -- Start with the first character from the input stream i = 1 cipherTextLength = cipherText.length repeat while i <= cipherTextLength -- Get the first 32-bits from the ciphertext relative to the current index dataLeft = charToNum( cipherText.char[i+3] ) \ + ( charToNum( cipherText.char[i+2] ) * 256 ) \ + ( charToNum( cipherText.char[i+1] ) * 65536 ) \ + bitAnd( charToNum( cipherText.char[i] ) * 16777216, -1 ) -- Get the next 32bits from the ciphertext dataRight = charToNum( cipherText.char[i+7] ) \ + ( charToNum( cipherText.char[i+6] ) * 256 ) \ + ( charToNum( cipherText.char[i+5] ) * 65536 ) \ + bitAnd( charToNum( cipherText.char[i+4] ) * 16777216, -1 ) -- Decipher the dword pair data = me.blowfish_Decipher( dataLeft, dataRight ) dataLeft = data[ #left ] dataRight = data[ #right ] -- Get the 8 individual characters from the 2 dwords -- And append them to the plaintext string put numToChar( bitAnd( bitAnd( dataLeft, -16777216 ) / 16777216, 255 ) ) after plainText put numToChar( ( bitAnd( dataLeft, 16711680 ) / 65536 ) ) after plainText put numToChar( ( bitAnd( dataLeft, 65280 ) / 256 ) ) after plainText put numToChar( bitAnd( dataLeft, 255 ) ) after plainText put numToChar( bitAnd( bitAnd( dataRight, -16777216 ) / 16777216, 255 ) ) after plainText put numToChar( ( bitAnd( dataRight, 16711680 ) / 65536 ) ) after plainText put numToChar( ( bitAnd( dataRight, 65280 ) / 256 ) ) after plainText put numToChar( bitAnd( dataRight, 255 ) ) after plainText -- Add 8 to the input stream index pointer i = i + 8 end repeat -- Return the plaintext return plainText end -------------------------------------------------------------------- -- PRIVATE HANDLES (for internal use only) -- -------------------------------------------------------------------- -- PRIVATE HANDLER: blowfish_F -- Blowfish simple function F(x) -- Used for each "round" of both encryption and decryption processes -- on blowfish_F me, x -- Split 32-bit value x into 4 byte values a, b, c and d. a = bitAnd( bitAnd( x, -16777216 ) / 16777216, 255 ) + 1 b = ( bitAnd( x, 16711680 ) / 65536 ) + 1 c = ( bitAnd( x, 65280 ) / 256 ) + 1 d = bitAnd( x, 255 ) + 1 -- Calculate F(x) using the following formula (as per Blowfish spec): -- F(x) = ((S1,a + S2,b mod 232) XOR S3,c) + S4,d mod 232 f = blowfish_S1[a] + blowfish_S2[b] f = bitXor( f , blowfish_S3[c] ) f = bitAnd( f + blowfish_S4[d], -1 ) -- Return the result return f end -------------------------------------------------------------------- -- PRIVATE HANDLER: blowfish_Encipher -- Used by encryption function(s) to encipher an individual -- dword pair. Should not be called at high-level. -- on blowfish_Encipher me, dataLeft, dataRight repeat with i = 1 to blowfish_rounds -- Do one "round" of simple encryption dataLeft = bitXor( dataLeft, blowfish_P[i] ) dataRight = bitXor( me.blowfish_F( dataLeft ), dataRight ) -- Swap dataLeft and dataRight temp = dataLeft dataLeft = dataRight dataRight = temp end repeat -- Un-swap the last pair temp = dataLeft dataLeft = dataRight dataRight = temp -- Do the final round dataRight = bitXor( dataRight, blowfish_P[ blowfish_rounds+1 ] ) dataLeft = bitXor( dataLeft, blowfish_P[ blowfish_rounds+2 ] ) -- Return the result return [ #left:dataLeft, #right:dataRight ] end -------------------------------------------------------------------- -- PRIVATE HANDLER: blowfish_Decipher -- Used by decryption function(s) to decipher an individual -- dword pair. Should not be called at high-level. -- on blowfish_Decipher me, dataLeft, dataRight repeat with i = blowfish_rounds+2 down to 3 -- Undo one round dataLeft = bitXor( dataLeft, blowfish_P[i] ) dataRight = bitXor( me.blowfish_F( dataLeft ), dataRight ) -- Swap dataLeft and dataRight temp = dataLeft dataLeft = dataRight dataRight = temp end repeat -- Un-swap the last pair temp = dataLeft dataLeft = dataRight dataRight = temp -- Undo the first round dataRight = bitXor( dataRight, blowfish_P[2] ) dataLeft = bitXor( dataLeft, blowfish_P[1] ) -- Return the result return [ #left:dataLeft, #right:dataRight ] end