Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix extremely unfortunate and unlikely error with encryption in nightly #1702

Merged
merged 2 commits into from
Nov 12, 2024

Conversation

ccuser44
Copy link
Contributor

From: #1652 (comment)
Oh my fucking god, Apparently there was a small 1 character typo that didn't lead to an error in the test case but made it succeed even if it should have failed. image

I'll make a new pull to fix this

So apparently, when I converted the Adonis encryption functions to buffers for more performance, an extremely unfortunate mistake existed in the text function where the test strings table strings was accidentally misspelled for the key and instead it pooled the Roblox Luau global string table. This caused the key to be nil. If the key misses the Adonis encryption function will return the unencrypted string and there was no comparison check if the string was encrypted. Therefor the test script didn't fail even when it should have and thus the errors in the encryptor functions weren't found.

This pull was extremely painful to test due to multiple reasons but now it finally works.

PoF:
image

PoF script:

-- Constants

local STRING_AMOUNT = 5e4
local STRING_MAX_LEN = 6e3
local STRING_MIN_CHAR = 1
local STRING_MAX_CHAR = 125
local ALLOW_ERROR = true

-- Code
local Remote = {}

local function hexEncode(data)
	local isString = type(data) == "string"
	local lenght = isString and string.len(data) or buffer.len(data)
	local encoded = {}
	
	if isString then
		for i = 1, lenght do
			encoded[i] = string.format("%02x", string.sub(data, i, i))
		end
	else
		for i = 1, lenght do
			encoded[i] = string.format("%02x", buffer.readu8(data, i - 1))
		end
	end

	return string.upper(table.concat(encoded, " "))
end

local oldEncrypt = function(str, key, cache)
	cache = cache or Remote.EncodeCache or {}

	if not key or not str then
		return str
	elseif cache[key] and cache[key][str] then
		return cache[key][str]
	else
		local byte = string.byte
		local sub = string.sub
		local char = string.char

		local keyCache = cache[key] or {}
		local endStr = {}

		for i = 1, #str do
			local keyPos = (i % #key) + 1
			endStr[i] = char(((byte(sub(str, i, i)) + byte(sub(key, keyPos, keyPos)))%126) + 1)
		end

		endStr = table.concat(endStr)
		cache[key] = keyCache
		keyCache[str] = endStr
		return endStr
	end
end;

local oldDecrypt = function(str, key, cache)
	cache = cache or Remote.DecodeCache or {}

	if not key or not str then
		return str
	elseif cache[key] and cache[key][str] then
		return cache[key][str]
	else
		local keyCache = cache[key] or {}
		local byte = string.byte
		local sub = string.sub
		local char = string.char
		local endStr = {}

		for i = 1, #str do
			local keyPos = (i % #key)+1
			endStr[i] = char(((byte(sub(str, i, i)) - byte(sub(key, keyPos, keyPos)))%126) - 1)
		end

		endStr = table.concat(endStr)
		cache[key] = keyCache
		keyCache[str] = endStr
		return endStr
	end
end;

local newEncrypt = function(str: string, key: string, cache: {[string]: (buffer|{[string]: string})}?)
	cache = cache or Remote.EncodeCache or {}
	local keyCache = cache[key] or {}

	if not key or not str then
		return str
	elseif keyCache[str] then
		return keyCache[str]
	else
		local writeu8, readu8 = buffer.writeu8, buffer.readu8
		local rawStr, rawKey = buffer.fromstring(str), keyCache[1] or buffer.fromstring(key)
		local keyLen = #key

		for i = 0, #str - 1 do
			writeu8(rawStr, i, (readu8(rawStr, i) + readu8(rawKey, (i + 1) % keyLen)) % 126 + 1)
		end

		cache[key] = keyCache
		keyCache[str], keyCache[1] = buffer.tostring(rawStr), rawKey
		return keyCache[str]
	end
end;

local newDecrypt = function(str: string, key: string, cache: {[string]: (buffer|{[string]: string})}?)
	cache = cache or Remote.DecodeCache or {}
	local keyCache = cache[key] or {}

	if not key or not str then
		return str
	elseif keyCache[str] then
		return keyCache[str]
	else
		local writeu8, readu8 = buffer.writeu8, buffer.readu8
		local rawStr, rawKey = buffer.fromstring(str), keyCache[1] or buffer.fromstring(key)
		local keyLen = #key

		for i = 0, #str - 1 do
			writeu8(rawStr, i, (readu8(rawStr, i) - readu8(rawKey, (i + 1) % keyLen)) % 126 - 1)
		end

		cache[key] = keyCache
		keyCache[str], keyCache[1] = buffer.tostring(rawStr), rawKey
		return keyCache[str]
	end
end;

task.wait(5)
print("Creating character list...")
task.wait()

local strings = table.create(STRING_AMOUNT)
local rawLenght = 0

for i = 1, STRING_AMOUNT do
	local str = buffer.create(math.random(1, STRING_MAX_LEN))

	for i2 = 0, buffer.len(str) - 1 do
		buffer.writeu8(str, i2, math.random(STRING_MIN_CHAR, STRING_MAX_CHAR))
	end

	if i % 2 ~= 0 then
		rawLenght += buffer.len(str)
	end
	strings[i] = buffer.tostring(str)
end

print("Encrypting characters...")
task.wait(0.1)
local amount = 0
local encryptedLenght = 0
local start = os.clock()

for i = 1, STRING_AMOUNT, 2 do
	local str, key = assert(strings[i], "String is missing!"), assert(strings[i + 1], "Key is missing!")
	local newEncVal, oldEncVal = newEncrypt(str, key), oldEncrypt(str, key)
	local newDecVal, oldDecVal = newDecrypt(newEncVal, key), oldDecrypt(oldEncVal, key)
	local strLen, newEncLen = string.len(str), string.len(newEncVal)

	assert(type(newEncVal) == "string", "New encryption value isn't a string!")
	assert(type(oldEncVal) == "string", "Old encryption value isn't a string!")
	assert(type(newDecVal) == "string", "New decryption value isn't a string!")
	assert(type(oldDecVal) == "string", "Old decryption value isn't a string!")
	assert(xpcall(function()
		assert(newEncLen == strLen, "New encryption value has an incorrect length!")
		assert(string.len(oldEncVal) == strLen, "Old encryption value has an incorrect length!")
		assert(newEncVal == oldEncVal, "New encrypt returned incorrect value!")
		assert(newDecVal == str, "New decryption value isn't original!")
		assert(oldDecVal == str, "Old decryption value isn't original!")
		assert(newDecVal == oldDecVal, "Decryption values don't match!")
		assert(newEncVal ~= str, "New encryption value isn't encrypted! V1")
		assert(newEncVal ~= newDecVal, "New encryption value isn't encrypted! V2")
	end, function(reason)
		warn(`Encrypting of string #{math.floor(i / 2) + 1} failed due to: {reason}`)
		print(`Key:\t{hexEncode(key)}\nSourceStr:\t{hexEncode(str)}\nNewEncrypt:\t{hexEncode(newEncVal)}\nOldEncrypt:\t{hexEncode(oldEncVal)}\nNewDecrypt:\t{hexEncode(newDecVal)}\nOldDecrypt:\t{hexEncode(oldDecVal)}`)
	end) or ALLOW_ERROR, "")

	amount += 1
	encryptedLenght += newEncLen
end

assert(encryptedLenght == rawLenght, `Amount of characters doesn't match! Encrypted lenght: {encryptedLenght} Raw lenght: {rawLenght}`)
print("Encryption funcs successfully tested. Amount tested:", amount, "Amount of strings:", #strings, "Took:", tostring(os.clock() - start), "seconds", "Amount of characters encrypted:", encryptedLenght)

@ccuser44 ccuser44 changed the title Fix extremely unfortunate and unlikely error with encryption Fix extremely unfortunate and unlikely error with encryption in nightly Nov 12, 2024
@Dimenpsyonal Dimenpsyonal merged commit 20f164a into Epix-Incorporated:master Nov 12, 2024
2 of 3 checks passed
@ccuser44 ccuser44 deleted the patch-2 branch November 12, 2024 21:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants