This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
AgentE382's profile picture

Rc4 Encryption / Decryption Functions

Started by AgentE382, 10 July 2013 - 09:07 PM
AgentE382 #1
Posted 10 July 2013 - 11:07 PM
This is a fully standard-compliant, pure Lua, CC-compatible implementation of the RC4 / ARC4 / ARCFOUR symmetric stream cipher.
  1. Standard-compliant: This implementation is a real implementation. For example, you can encrypt a string in an external program and decrypt it with this function. You can also use the streams however long you want without the internal state breaking.
  2. Pure Lua: Originally designed for Lua 5.2.2, it required only 2 tweaks to convert to CC-Lua 5.1. As such, it uses standard Lua functions except for the bitwise XOR function. (Tweaks: Use `bit` instead of `bit32` and use `unpack` instead of `table.unpack`.)
  3. CC-compatible: CC-Lua doesn't implement the upper range of the ASCII character set. To work around this, the function can return a table of character codes instead of a string. It can also take a table of character codes as input instead of a string, to facilitate a round-trip.
  4. RC4: This implementation is based off of the Wikipedia article and the gnulib implementation.
Pastebin link: http://pastebin.com/Rxe673BJ

Update:

I will add other RC4-family ciphers, which attempt to fix RC4's weaknesses. Most have failed, but I will be posting the one that no one has broken as of yet.

Code:
Spoiler
---------------------------------------------------------------------------------------------------------------------------
--#Usage: Call `rc4` to create a dual-purpose stream. Call the stream to encipher plaintext or decipher ciphertext.
--		
--#		Either strings or tables can be passed as arguments, in any order.
--		
--	 stream = rc4"Key"				-- 	stream = rc4{107,101,121}
-- 	ciphertext = stream"Plaintext"	-- 	ciphertext = stream{80,108,97,105,110,116,101,120,116}
-- 	
-- 	stream = rc4"Key"				-- 	stream = rc4{107,101,121}
-- 	plaintext = stream(ciphertext)	-- 	plaintext = stream(ciphertext)
-- 	
--#		Pass `true` as the stream's second argument to force byte-table output. Otherwise, it will return a string.
-- 		
---------------------------------------------------------------------------------------------------------------------------
function rc4(salt)
	local key = type(salt) == "table" and {unpack(salt)} or {string.byte(salt,1,#salt)}
	local S, j, keylength = {[0] = 0,[1] = 1,[2] = 2,[3] = 3,[4] = 4,[5] = 5,[6] = 6,[7] = 7,[8] = 8,[9] = 9,[10] = 10,[11] = 11,[12] = 12,[13] = 13,[14] = 14,[15] = 15,[16] = 16,[17] = 17,[18] = 18,[19] = 19,[20] = 20,[21] = 21,[22] = 22,[23] = 23,[24] = 24,[25] = 25,[26] = 26,[27] = 27,[28] = 28,[29] = 29,[30] = 30,[31] = 31,[32] = 32,[33] = 33,[34] = 34,[35] = 35,[36] = 36,[37] = 37,[38] = 38,[39] = 39,[40] = 40,[41] = 41,[42] = 42,[43] = 43,[44] = 44,[45] = 45,[46] = 46,[47] = 47,[48] = 48,[49] = 49,[50] = 50,[51] = 51,[52] = 52,[53] = 53,[54] = 54,[55] = 55,[56] = 56,[57] = 57,[58] = 58,[59] = 59,[60] = 60,[61] = 61,[62] = 62,[63] = 63,[64] = 64,[65] = 65,[66] = 66,[67] = 67,[68] = 68,[69] = 69,[70] = 70,[71] = 71,[72] = 72,[73] = 73,[74] = 74,[75] = 75,[76] = 76,[77] = 77,[78] = 78,[79] = 79,[80] = 80,[81] = 81,[82] = 82,[83] = 83,[84] = 84,[85] = 85,[86] = 86,[87] = 87,[88] = 88,[89] = 89,[90] = 90,[91] = 91,[92] = 92,[93] = 93,[94] = 94,[95] = 95,[96] = 96,[97] = 97,[98] = 98,[99] = 99,[100] = 100,[101] = 101,[102] = 102,[103] = 103,[104] = 104,[105] = 105,[106] = 106,[107] = 107,[108] = 108,[109] = 109,[110] = 110,[111] = 111,[112] = 112,[113] = 113,[114] = 114,[115] = 115,[116] = 116,[117] = 117,[118] = 118,[119] = 119,[120] = 120,[121] = 121,[122] = 122,[123] = 123,[124] = 124,[125] = 125,[126] = 126,[127] = 127,[128] = 128,[129] = 129,[130] = 130,[131] = 131,[132] = 132,[133] = 133,[134] = 134,[135] = 135,[136] = 136,[137] = 137,[138] = 138,[139] = 139,[140] = 140,[141] = 141,[142] = 142,[143] = 143,[144] = 144,[145] = 145,[146] = 146,[147] = 147,[148] = 148,[149] = 149,[150] = 150,[151] = 151,[152] = 152,[153] = 153,[154] = 154,[155] = 155,[156] = 156,[157] = 157,[158] = 158,[159] = 159,[160] = 160,[161] = 161,[162] = 162,[163] = 163,[164] = 164,[165] = 165,[166] = 166,[167] = 167,[168] = 168,[169] = 169,[170] = 170,[171] = 171,[172] = 172,[173] = 173,[174] = 174,[175] = 175,[176] = 176,[177] = 177,[178] = 178,[179] = 179,[180] = 180,[181] = 181,[182] = 182,[183] = 183,[184] = 184,[185] = 185,[186] = 186,[187] = 187,[188] = 188,[189] = 189,[190] = 190,[191] = 191,[192] = 192,[193] = 193,[194] = 194,[195] = 195,[196] = 196,[197] = 197,[198] = 198,[199] = 199,[200] = 200,[201] = 201,[202] = 202,[203] = 203,[204] = 204,[205] = 205,[206] = 206,[207] = 207,[208] = 208,[209] = 209,[210] = 210,[211] = 211,[212] = 212,[213] = 213,[214] = 214,[215] = 215,[216] = 216,[217] = 217,[218] = 218,[219] = 219,[220] = 220,[221] = 221,[222] = 222,[223] = 223,[224] = 224,[225] = 225,[226] = 226,[227] = 227,[228] = 228,[229] = 229,[230] = 230,[231] = 231,[232] = 232,[233] = 233,[234] = 234,[235] = 235,[236] = 236,[237] = 237,[238] = 238,[239] = 239,[240] = 240,[241] = 241,[242] = 242,[243] = 243,[244] = 244,[245] = 245,[246] = 246,[247] = 247,[248] = 248,[249] = 249,[250] = 250,[251] = 251,[252] = 252,[253] = 253,[254] = 254,[255] = 255}, 0, #key
	for i = 0, 255 do
		j = (j + S[i] + key[i % keylength + 1]) % 256
		S[i], S[j] = S[j], S[i]
	end
	local i = 0
	j = 0
	return function(plaintext, astable)
		local chars = type(plaintext) == "table" and {unpack(plaintext)} or {string.byte(plaintext,1,#plaintext)}
		for n = 1,#chars do
			i = (i + 1) % 256
			j = (j + S[i]) % 256
			S[i], S[j] = S[j], S[i]
			chars[n] = bit.bxor(S[(S[i] + S[j]) % 256], chars[n])
		end
		return astable and chars or string.char(unpack(chars))
	end
end

----------------------------------------------------------------------------------
--#Usage: Same as above. Use extra parameter to discard that many keystream bytes.
--
----------------------------------------------------------------------------------
function rc4dropn(salt, dropn)
	local key = type(salt) == "table" and {unpack(salt)} or {string.byte(salt,1,#salt)}
	local S, j, keylength = {[0] = 0,[1] = 1,[2] = 2,[3] = 3,[4] = 4,[5] = 5,[6] = 6,[7] = 7,[8] = 8,[9] = 9,[10] = 10,[11] = 11,[12] = 12,[13] = 13,[14] = 14,[15] = 15,[16] = 16,[17] = 17,[18] = 18,[19] = 19,[20] = 20,[21] = 21,[22] = 22,[23] = 23,[24] = 24,[25] = 25,[26] = 26,[27] = 27,[28] = 28,[29] = 29,[30] = 30,[31] = 31,[32] = 32,[33] = 33,[34] = 34,[35] = 35,[36] = 36,[37] = 37,[38] = 38,[39] = 39,[40] = 40,[41] = 41,[42] = 42,[43] = 43,[44] = 44,[45] = 45,[46] = 46,[47] = 47,[48] = 48,[49] = 49,[50] = 50,[51] = 51,[52] = 52,[53] = 53,[54] = 54,[55] = 55,[56] = 56,[57] = 57,[58] = 58,[59] = 59,[60] = 60,[61] = 61,[62] = 62,[63] = 63,[64] = 64,[65] = 65,[66] = 66,[67] = 67,[68] = 68,[69] = 69,[70] = 70,[71] = 71,[72] = 72,[73] = 73,[74] = 74,[75] = 75,[76] = 76,[77] = 77,[78] = 78,[79] = 79,[80] = 80,[81] = 81,[82] = 82,[83] = 83,[84] = 84,[85] = 85,[86] = 86,[87] = 87,[88] = 88,[89] = 89,[90] = 90,[91] = 91,[92] = 92,[93] = 93,[94] = 94,[95] = 95,[96] = 96,[97] = 97,[98] = 98,[99] = 99,[100] = 100,[101] = 101,[102] = 102,[103] = 103,[104] = 104,[105] = 105,[106] = 106,[107] = 107,[108] = 108,[109] = 109,[110] = 110,[111] = 111,[112] = 112,[113] = 113,[114] = 114,[115] = 115,[116] = 116,[117] = 117,[118] = 118,[119] = 119,[120] = 120,[121] = 121,[122] = 122,[123] = 123,[124] = 124,[125] = 125,[126] = 126,[127] = 127,[128] = 128,[129] = 129,[130] = 130,[131] = 131,[132] = 132,[133] = 133,[134] = 134,[135] = 135,[136] = 136,[137] = 137,[138] = 138,[139] = 139,[140] = 140,[141] = 141,[142] = 142,[143] = 143,[144] = 144,[145] = 145,[146] = 146,[147] = 147,[148] = 148,[149] = 149,[150] = 150,[151] = 151,[152] = 152,[153] = 153,[154] = 154,[155] = 155,[156] = 156,[157] = 157,[158] = 158,[159] = 159,[160] = 160,[161] = 161,[162] = 162,[163] = 163,[164] = 164,[165] = 165,[166] = 166,[167] = 167,[168] = 168,[169] = 169,[170] = 170,[171] = 171,[172] = 172,[173] = 173,[174] = 174,[175] = 175,[176] = 176,[177] = 177,[178] = 178,[179] = 179,[180] = 180,[181] = 181,[182] = 182,[183] = 183,[184] = 184,[185] = 185,[186] = 186,[187] = 187,[188] = 188,[189] = 189,[190] = 190,[191] = 191,[192] = 192,[193] = 193,[194] = 194,[195] = 195,[196] = 196,[197] = 197,[198] = 198,[199] = 199,[200] = 200,[201] = 201,[202] = 202,[203] = 203,[204] = 204,[205] = 205,[206] = 206,[207] = 207,[208] = 208,[209] = 209,[210] = 210,[211] = 211,[212] = 212,[213] = 213,[214] = 214,[215] = 215,[216] = 216,[217] = 217,[218] = 218,[219] = 219,[220] = 220,[221] = 221,[222] = 222,[223] = 223,[224] = 224,[225] = 225,[226] = 226,[227] = 227,[228] = 228,[229] = 229,[230] = 230,[231] = 231,[232] = 232,[233] = 233,[234] = 234,[235] = 235,[236] = 236,[237] = 237,[238] = 238,[239] = 239,[240] = 240,[241] = 241,[242] = 242,[243] = 243,[244] = 244,[245] = 245,[246] = 246,[247] = 247,[248] = 248,[249] = 249,[250] = 250,[251] = 251,[252] = 252,[253] = 253,[254] = 254,[255] = 255}, 0, #key
	for i = 0, 255 do
		j = (j + S[i] + key[i % keylength + 1]) % 256
		S[i], S[j] = S[j], S[i]
	end
	local i = 0
	j = 0
	for n = 1, dropn do
		i = (i + 1) % 256
		j = (j + S[i]) % 256
		S[i],S[j] = S[j],S[i]
	end
	return function(plaintext, astable)
		local chars = type(plaintext) == "table" and {unpack(plaintext)} or {string.byte(plaintext,1,#plaintext)}
		for n = 1,#chars do
			i = (i + 1) % 256
			j = (j + S[i]) % 256
			S[i], S[j] = S[j], S[i]
			chars[n] = bit.bxor(S[(S[i] + S[j]) % 256], chars[n])
		end
		return astable and chars or string.char(unpack(chars))
	end
end

Design notes:
  1. No modification is made to any input. A local copy is made of all tables.
  2. Everything is as optimized as well as I know how at this point. Please feel free to suggest optimizations if you see any. I'll test them and incorporate them.
Please test for bugs and let me know how you like it!

Changelog:

12-06-2014 - v2.0.1 - Fixed last change.
12-06-2014 - v2.0.0 - Added parameter to force byte-table output; made string output the default.
09-22-2013 - v1.2 - Added RC4-drpp[n] for convenience.
09-22-2013 - v1.1 - Shortened / optimized RC4. Fix for CC-Lua converting CR to LF (RC4).
07-09-2013 - v1.0 - Initial Release, RC4.
Edited on 06 December 2014 - 05:06 AM
Draktharis #2
Posted 11 August 2013 - 02:04 AM
Is it supposed to return a table most of the time, and a random letter sometimes? (For 1 digit strings encrypted with 1 digit keys.)
Or am I just doing it wrong?

http://pastebin.com/WnBCzA8G
AgentE382 #3
Posted 16 August 2013 - 08:04 PM
Is it supposed to return a table most of the time, and a random letter sometimes? (For 1 digit strings encrypted with 1 digit keys.)
Or am I just doing it wrong?

http://pastebin.com/WnBCzA8G
Basically, since CC-Lua doesn't do well with non-printable characters, it'll return a table instead of a string containing those characters.

In general, it will return a table after encryption. However, for one character, it might return a string (because the one character may encrypt to a printable character). With longer data, RC4 will almost certainly produce at least one non-printable character.
Mitchfizz05 #4
Posted 07 September 2013 - 10:31 PM
Those are big indencies.
AgentE382 #5
Posted 12 September 2013 - 12:00 PM
Those are big indencies.

I'm not sure what you mean, but If you're talking about the S-Box, I hard-coded it for performance reasons. My first priority for this implementation is accuracy. My second is speed.
AgentE382 #6
Posted 22 September 2013 - 08:18 PM
RC4 updated! Working on RC4A, VMPC, and RC4+!
bentallea #7
Posted 08 October 2013 - 01:59 AM
can i replace return astable and chars or string.char(unpack(chars)) with return chars , amd remove
  • if chars[n] > 127 or chars[n] == 13 then
  • astable = true
  • end
to force output a table of bytes, as that would be much mpre useful to me?
AgentE382 #8
Posted 08 October 2013 - 07:59 AM
Change
local chars, astable = type(plaintext) == "table" and {unpack(plaintext)} or {string.byte(plaintext,1,#plaintext)}, false
to
local chars = type(plaintext) == "table" and {unpack(plaintext)} or {string.byte(plaintext,1,#plaintext)}

Delete
if chars[n] > 127 or chars[n] == 13 then
    astable = true
end

Change
return astable and chars or string.char(unpack(chars))
to
return chars


That will completely remove the string output functionality and overhead. Note that it will still accept string input.


Also note that before editing, it would usually output a bytetable when encrypting and a string when decrypting.
bentallea #9
Posted 09 October 2013 - 12:03 AM
thanks. that will make the implementation more usable for my project.