import base64
from Cryptodome.Cipher import DES3
from Cryptodome.Cipher import AES
import lupa

# Same as the luacode that gets decrypted by the ransomware,
# but with some slight modifications to make it work in lupa.
LUACODE ="""local function _W(f) local e=setmetatable({}, {__index = _ENV or getfenv()}) if setfenv then setfenv(f, e) end return f(e) or e end
local bit=_W(function(_ENV, ...)
--[[
    This bit API is designed to cope with unsigned integers instead of normal integers

    To do this we add checks for overflows: (x > 2^31 ? x - 2 ^ 32 : x)
    These are written in long form because no constant folding.
]]

local floor = math.floor

local lshift, rshift

rshift = function(a,disp)
    return floor(a % 4294967296 / 2^disp)
end

lshift = function(a,disp)
    return (a * 2^disp) % 4294967296
end

local function nand(x,y,z)
    z=z or 2^16
    if z<2 then
        return 1-x*y
    else
        return nand((x-x%z)/z,(y-y%z)/z,math.sqrt(z))*z+nand(x%z,y%z,math.sqrt(z))
    end
end
local function bnot(y,z)
    return nand(nand(0,0,z),y,z)
end
local function band(x,y,z)
    return nand(bnot(0,z),nand(x,y,z),z)
end
local function bor(x,y,z)
    return nand(bnot(x,z),bnot(y,z),z)
end
local function bxor(x,y,z)
    return band(nand(x,y,z),bor(x,y,z),z)
end

return {
    -- bit operations
    bnot = bnot,
    band = band,
    bor  = bor,
    bxor = bxor,
    rshift = rshift,
    lshift = lshift,
}
end)
local gf=_W(function(_ENV, ...)
-- finite field with base 2 and modulo irreducible polynom x^8+x^4+x^3+x+1 = 0x11d
local bxor = bit.bxor
local lshift = bit.lshift

-- private data of gf
local n = 0x100
local ord = 0xff
local irrPolynom = 0x11b
local exp = {}
local log = {}

--
-- add two polynoms (its simply xor)
--
local function add(operand1, operand2)
    return bxor(operand1,operand2)
end

--
-- subtract two polynoms (same as addition)
--
local function sub(operand1, operand2)
    return bxor(operand1,operand2)
end

--
-- inverts element
-- a^(-1) = g^(order - log(a))
--
local function invert(operand)
    -- special case for 1
    if (operand == 1) then
        return 1
    end
    -- normal invert
    local exponent = ord - log[operand]
    return exp[exponent]
end

--
-- multiply two elements using a logarithm table
-- a*b = g^(log(a)+log(b))
--
local function mul(operand1, operand2)
    if (operand1 == 0 or operand2 == 0) then
        return 0
    end

    local exponent = log[operand1] + log[operand2]
    if (exponent >= ord) then
        exponent = exponent - ord
    end
    return  exp[exponent]
end

--
-- divide two elements
-- a/b = g^(log(a)-log(b))
--
local function div(operand1, operand2)
    if (operand1 == 0)  then
        return 0
    end
    -- TODO: exception if operand2 == 0
    local exponent = log[operand1] - log[operand2]
    if (exponent < 0) then
        exponent = exponent + ord
    end
    return exp[exponent]
end

--
-- print logarithmic table
--
local function printLog()
    for i = 1, n do
        print("log(", i-1, ")=", log[i-1])
    end
end

--
-- print exponentiation table
--
local function printExp()
    for i = 1, n do
        print("exp(", i-1, ")=", exp[i-1])
    end
end

--
-- calculate logarithmic and exponentiation table
--
local function initMulTable()
    local a = 1

    for i = 0,ord-1 do
        exp[i] = a
        log[a] = i

        -- multiply with generator x+1 -> left shift + 1
        a = bxor(lshift(a, 1), a)

        -- if a gets larger than order, reduce modulo irreducible polynom
        if a > ord then
            a = sub(a, irrPolynom)
        end
    end
end

initMulTable()

return {
    add = add,
    sub = sub,
    invert = invert,
    mul = mul,
    div = dib,
    printLog = printLog,
    printExp = printExp,
}
end)
util=_W(function(_ENV, ...)
-- Cache some bit operators
local bxor = bit.bxor
local rshift = bit.rshift
local band = bit.band
local lshift = bit.lshift

local sleepCheckIn
--
-- calculate the parity of one byte
--
local function byteParity(byte)
    byte = bxor(byte, rshift(byte, 4))
    byte = bxor(byte, rshift(byte, 2))
    byte = bxor(byte, rshift(byte, 1))
    return band(byte, 1)
end

--
-- get byte at position index
--
local function getByte(number, index)
    if (index == 0) then
        return band(number,0xff)
    else
        return band(rshift(number, index*8),0xff)
    end
end


--
-- put number into int at position index
--
local function putByte(number, index)
    if (index == 0) then
        return band(number,0xff)
    else
        return lshift(band(number,0xff),index*8)
    end
end

--
-- convert byte array to int array
--
local function bytesToInts(bytes, start, n)
    local ints = {}
    for i = 0, n - 1 do
        ints[i + 1] =
                putByte(bytes[start + (i*4)], 3) +
                putByte(bytes[start + (i*4) + 1], 2) +
                putByte(bytes[start + (i*4) + 2], 1) +
                putByte(bytes[start + (i*4) + 3], 0)

        if n % 10000 == 0 then sleepCheckIn() end
    end
    return ints
end

--
-- convert int array to byte array
--
local function intsToBytes(ints, output, outputOffset, n)
    n = n or #ints
    for i = 0, n - 1 do
        for j = 0,3 do
            output[outputOffset + i*4 + (3 - j)] = getByte(ints[i + 1], j)
        end

        if n % 10000 == 0 then sleepCheckIn() end
    end
    return output
end

--
-- convert bytes to hexString
--
local function bytesToHex(bytes)
    local hexBytes = ""

    for i,byte in ipairs(bytes) do
        hexBytes = hexBytes .. string.format("%02x ", byte)
    end

    return hexBytes
end

local function hexToBytes(bytes)
    local out = {}
    for i = 1, #bytes, 2 do
        out[#out + 1] = tonumber(bytes:sub(i, i + 1), 16)
    end

    return out
end

--
-- convert data to hex string
--
local function toHexString(data)
    local type = type(data)
    if (type == "number") then
        return string.format("%08x",data)
    elseif (type == "table") then
        return bytesToHex(data)
    elseif (type == "string") then
        local bytes = {string.byte(data, 1, #data)}

        return bytesToHex(bytes)
    else
        return data
    end
end

local function padByteString(data)
    local dataLength = #data

    local random1 = math.random(0,255)
    local random2 = math.random(0,255)

    local prefix = string.char(random1,
        random2,
        random1,
        random2,
        getByte(dataLength, 3),
        getByte(dataLength, 2),
        getByte(dataLength, 1),
        getByte(dataLength, 0)
    )

    data = prefix .. data

    local paddingLength = math.ceil(#data/16)*16 - #data
    local padding = ""
    for i=1,paddingLength do
        padding = padding .. string.char(math.random(0,255))
    end

    return data .. padding
end

local function properlyDecrypted(data)
    local random = {string.byte(data,1,4)}

    if (random[1] == random[3] and random[2] == random[4]) then
        return true
    end

    return false
end

local function unpadByteString(data)
    if (not properlyDecrypted(data)) then
        return nil
    end

    local dataLength = putByte(string.byte(data,5), 3)
                     + putByte(string.byte(data,6), 2)
                     + putByte(string.byte(data,7), 1)
                     + putByte(string.byte(data,8), 0)

    return string.sub(data,9,8+dataLength)
end

local function xorIV(data, iv)
    for i = 1,16 do
        data[i] = bxor(data[i], iv[i])
    end
end

local function increment(data)
    local i = 16
    while true do
        local value = data[i] + 1
        if value >= 256 then
            data[i] = value - 256
            i = (i - 2) % 16 + 1
        else
            data[i] = value
            break
        end
    end
end

-- Called every encryption cycle
local push, pull, time = os.queueEvent, coroutine.yield, os.time
local oldTime = time()
local function sleepCheckIn()
    local newTime = time()
    if newTime - oldTime >= 0.03 then -- (0.020 * 1.5)
        oldTime = newTime
    end
end

local function getRandomData(bytes)
    local char, random, sleep, insert = string.char, math.random, sleepCheckIn, table.insert
    local result = {}

    for i=1,bytes do
        insert(result, random(0,255))
        if i % 10240 == 0 then sleep() end
    end

    return result
end

local function getRandomString(bytes)
    local char, random, sleep, insert = string.char, math.random, sleepCheckIn, table.insert
    local result = {}

    for i=1,bytes do
        insert(result, char(random(0,255)))
        if i % 10240 == 0 then sleep() end
    end

    return table.concat(result)
end

return {
    byteParity = byteParity,
    getByte = getByte,
    putByte = putByte,
    bytesToInts = bytesToInts,
    intsToBytes = intsToBytes,
    bytesToHex = bytesToHex,
    hexToBytes = hexToBytes,
    toHexString = toHexString,
    padByteString = padByteString,
    properlyDecrypted = properlyDecrypted,
    unpadByteString = unpadByteString,
    xorIV = xorIV,
    increment = increment,

    sleepCheckIn = sleepCheckIn,

    getRandomData = getRandomData,
    getRandomString = getRandomString,
}
end)
aes=_W(function(_ENV, ...)
-- Implementation of AES with nearly pure lua
-- AES with lua is slow, really slow :-)

local putByte = util.putByte
local getByte = util.getByte

-- some constants
local ROUNDS = 'rounds'
local KEY_TYPE = "type"
local ENCRYPTION_KEY=1
local DECRYPTION_KEY=2

-- aes SBOX
local SBox = {}
local iSBox = {}

-- aes tables
local table0 = {}
local table1 = {}
local table2 = {}
local table3 = {}

local tableInv0 = {}
local tableInv1 = {}
local tableInv2 = {}
local tableInv3 = {}

-- round constants
local rCon = {
    0x01000000,
    0x02000000,
    0x04000000,
    0x08000000,
    0x10000000,
    0x20000000,
    0x40000000,
    0x80000000,
    0x1b000000,
    0x36000000,
    0x6c000000,
    0xd8000000,
    0xab000000,
    0x4d000000,
    0x9a000000,
    0x2f000000,
}

--
-- affine transformation for calculating the S-Box of AES
--
local function affinMap(byte)
    mask = 0xf8
    result = 0
    for i = 1,8 do
        result = bit.lshift(result,1)

        parity = util.byteParity(bit.band(byte,mask))
        result = result + parity

        -- simulate roll
        lastbit = bit.band(mask, 1)
        mask = bit.band(bit.rshift(mask, 1),0xff)
        if (lastbit ~= 0) then
            mask = bit.bor(mask, 0x80)
        else
            mask = bit.band(mask, 0x7f)
        end
    end

    return bit.bxor(result, 0x63)
end

--
-- calculate S-Box and inverse S-Box of AES
-- apply affine transformation to inverse in finite field 2^8
--
local function calcSBox()
    for i = 0, 255 do
    if (i ~= 0) then
        inverse = gf.invert(i)
    else
        inverse = i
    end
        mapped = affinMap(inverse)
        SBox[i] = mapped
        iSBox[mapped] = i
    end
end

--
-- Calculate round tables
-- round tables are used to calculate shiftRow, MixColumn and SubBytes
-- with 4 table lookups and 4 xor operations.
--
local function calcRoundTables()
    for x = 0,255 do
        byte = SBox[x]
        table0[x] = putByte(gf.mul(0x03, byte), 0)
                          + putByte(             byte , 1)
                          + putByte(             byte , 2)
                          + putByte(gf.mul(0x02, byte), 3)
        table1[x] = putByte(             byte , 0)
                          + putByte(             byte , 1)
                          + putByte(gf.mul(0x02, byte), 2)
                          + putByte(gf.mul(0x03, byte), 3)
        table2[x] = putByte(             byte , 0)
                          + putByte(gf.mul(0x02, byte), 1)
                          + putByte(gf.mul(0x03, byte), 2)
                          + putByte(             byte , 3)
        table3[x] = putByte(gf.mul(0x02, byte), 0)
                          + putByte(gf.mul(0x03, byte), 1)
                          + putByte(             byte , 2)
                          + putByte(             byte , 3)
    end
end

--
-- Calculate inverse round tables
-- does the inverse of the normal roundtables for the equivalent
-- decryption algorithm.
--
local function calcInvRoundTables()
    for x = 0,255 do
        byte = iSBox[x]
        tableInv0[x] = putByte(gf.mul(0x0b, byte), 0)
                             + putByte(gf.mul(0x0d, byte), 1)
                             + putByte(gf.mul(0x09, byte), 2)
                             + putByte(gf.mul(0x0e, byte), 3)
        tableInv1[x] = putByte(gf.mul(0x0d, byte), 0)
                             + putByte(gf.mul(0x09, byte), 1)
                             + putByte(gf.mul(0x0e, byte), 2)
                             + putByte(gf.mul(0x0b, byte), 3)
        tableInv2[x] = putByte(gf.mul(0x09, byte), 0)
                             + putByte(gf.mul(0x0e, byte), 1)
                             + putByte(gf.mul(0x0b, byte), 2)
                             + putByte(gf.mul(0x0d, byte), 3)
        tableInv3[x] = putByte(gf.mul(0x0e, byte), 0)
                             + putByte(gf.mul(0x0b, byte), 1)
                             + putByte(gf.mul(0x0d, byte), 2)
                             + putByte(gf.mul(0x09, byte), 3)
    end
end


--
-- rotate word: 0xaabbccdd gets 0xbbccddaa
-- used for key schedule
--
local function rotWord(word)
    local tmp = bit.band(word,0xff000000)
    return (bit.lshift(word,8) + bit.rshift(tmp,24))
end

--
-- replace all bytes in a word with the SBox.
-- used for key schedule
--
local function subWord(word)
    return putByte(SBox[getByte(word,0)],0)
        + putByte(SBox[getByte(word,1)],1)
        + putByte(SBox[getByte(word,2)],2)
        + putByte(SBox[getByte(word,3)],3)
end

--
-- generate key schedule for aes encryption
--
-- returns table with all round keys and
-- the necessary number of rounds saved in [ROUNDS]
--
local function expandEncryptionKey(key)
    local keySchedule = {}
    local keyWords = math.floor(#key / 4)


    if ((keyWords ~= 4 and keyWords ~= 6 and keyWords ~= 8) or (keyWords * 4 ~= #key)) then
        error("Invalid key size: " .. tostring(keyWords))
        return nil
    end

    keySchedule[ROUNDS] = keyWords + 6
    keySchedule[KEY_TYPE] = ENCRYPTION_KEY

    for i = 0,keyWords - 1 do
        keySchedule[i] = putByte(key[i*4+1], 3)
                       + putByte(key[i*4+2], 2)
                       + putByte(key[i*4+3], 1)
                       + putByte(key[i*4+4], 0)
    end

    for i = keyWords, (keySchedule[ROUNDS] + 1)*4 - 1 do
        local tmp = keySchedule[i-1]

        if ( i % keyWords == 0) then
            tmp = rotWord(tmp)
            tmp = subWord(tmp)

            local index = math.floor(i/keyWords)
            tmp = bit.bxor(tmp,rCon[index])
        elseif (keyWords > 6 and i % keyWords == 4) then
            tmp = subWord(tmp)
        end

        keySchedule[i] = bit.bxor(keySchedule[(i-keyWords)],tmp)
    end

    return keySchedule
end

--
-- Inverse mix column
-- used for key schedule of decryption key
--
local function invMixColumnOld(word)
    local b0 = getByte(word,3)
    local b1 = getByte(word,2)
    local b2 = getByte(word,1)
    local b3 = getByte(word,0)

    return putByte(gf.add(gf.add(gf.add(gf.mul(0x0b, b1),
                                             gf.mul(0x0d, b2)),
                                             gf.mul(0x09, b3)),
                                             gf.mul(0x0e, b0)),3)
         + putByte(gf.add(gf.add(gf.add(gf.mul(0x0b, b2),
                                             gf.mul(0x0d, b3)),
                                             gf.mul(0x09, b0)),
                                             gf.mul(0x0e, b1)),2)
         + putByte(gf.add(gf.add(gf.add(gf.mul(0x0b, b3),
                                             gf.mul(0x0d, b0)),
                                             gf.mul(0x09, b1)),
                                             gf.mul(0x0e, b2)),1)
         + putByte(gf.add(gf.add(gf.add(gf.mul(0x0b, b0),
                                             gf.mul(0x0d, b1)),
                                             gf.mul(0x09, b2)),
                                             gf.mul(0x0e, b3)),0)
end

--
-- Optimized inverse mix column
-- look at http://fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf
-- TODO: make it work
--
local function invMixColumn(word)
    local b0 = getByte(word,3)
    local b1 = getByte(word,2)
    local b2 = getByte(word,1)
    local b3 = getByte(word,0)

    local t = bit.bxor(b3,b2)
    local u = bit.bxor(b1,b0)
    local v = bit.bxor(t,u)
    v = bit.bxor(v,gf.mul(0x08,v))
    w = bit.bxor(v,gf.mul(0x04, bit.bxor(b2,b0)))
    v = bit.bxor(v,gf.mul(0x04, bit.bxor(b3,b1)))

    return putByte( bit.bxor(bit.bxor(b3,v), gf.mul(0x02, bit.bxor(b0,b3))), 0)
         + putByte( bit.bxor(bit.bxor(b2,w), gf.mul(0x02, t              )), 1)
         + putByte( bit.bxor(bit.bxor(b1,v), gf.mul(0x02, bit.bxor(b0,b3))), 2)
         + putByte( bit.bxor(bit.bxor(b0,w), gf.mul(0x02, u              )), 3)
end

--
-- generate key schedule for aes decryption
--
-- uses key schedule for aes encryption and transforms each
-- key by inverse mix column.
--
local function expandDecryptionKey(key)
    local keySchedule = expandEncryptionKey(key)
    if (keySchedule == nil) then
        return nil
    end

    keySchedule[KEY_TYPE] = DECRYPTION_KEY

    for i = 4, (keySchedule[ROUNDS] + 1)*4 - 5 do
        keySchedule[i] = invMixColumnOld(keySchedule[i])
    end

    return keySchedule
end

--
-- xor round key to state
--
local function addRoundKey(state, key, round)
    for i = 0, 3 do
        state[i + 1] = bit.bxor(state[i + 1], key[round*4+i])
    end
end

--
-- do encryption round (ShiftRow, SubBytes, MixColumn together)
--
local function doRound(origState, dstState)
    dstState[1] =  bit.bxor(bit.bxor(bit.bxor(
                table0[getByte(origState[1],3)],
                table1[getByte(origState[2],2)]),
                table2[getByte(origState[3],1)]),
                table3[getByte(origState[4],0)])

    dstState[2] =  bit.bxor(bit.bxor(bit.bxor(
                table0[getByte(origState[2],3)],
                table1[getByte(origState[3],2)]),
                table2[getByte(origState[4],1)]),
                table3[getByte(origState[1],0)])

    dstState[3] =  bit.bxor(bit.bxor(bit.bxor(
                table0[getByte(origState[3],3)],
                table1[getByte(origState[4],2)]),
                table2[getByte(origState[1],1)]),
                table3[getByte(origState[2],0)])

    dstState[4] =  bit.bxor(bit.bxor(bit.bxor(
                table0[getByte(origState[4],3)],
                table1[getByte(origState[1],2)]),
                table2[getByte(origState[2],1)]),
                table3[getByte(origState[3],0)])
end

--
-- do last encryption round (ShiftRow and SubBytes)
--
local function doLastRound(origState, dstState)
    dstState[1] = putByte(SBox[getByte(origState[1],3)], 3)
                + putByte(SBox[getByte(origState[2],2)], 2)
                + putByte(SBox[getByte(origState[3],1)], 1)
                + putByte(SBox[getByte(origState[4],0)], 0)

    dstState[2] = putByte(SBox[getByte(origState[2],3)], 3)
                + putByte(SBox[getByte(origState[3],2)], 2)
                + putByte(SBox[getByte(origState[4],1)], 1)
                + putByte(SBox[getByte(origState[1],0)], 0)

    dstState[3] = putByte(SBox[getByte(origState[3],3)], 3)
                + putByte(SBox[getByte(origState[4],2)], 2)
                + putByte(SBox[getByte(origState[1],1)], 1)
                + putByte(SBox[getByte(origState[2],0)], 0)

    dstState[4] = putByte(SBox[getByte(origState[4],3)], 3)
                + putByte(SBox[getByte(origState[1],2)], 2)
                + putByte(SBox[getByte(origState[2],1)], 1)
                + putByte(SBox[getByte(origState[3],0)], 0)
end

--
-- do decryption round
--
local function doInvRound(origState, dstState)
    dstState[1] =  bit.bxor(bit.bxor(bit.bxor(
                tableInv0[getByte(origState[1],3)],
                tableInv1[getByte(origState[4],2)]),
                tableInv2[getByte(origState[3],1)]),
                tableInv3[getByte(origState[2],0)])

    dstState[2] =  bit.bxor(bit.bxor(bit.bxor(
                tableInv0[getByte(origState[2],3)],
                tableInv1[getByte(origState[1],2)]),
                tableInv2[getByte(origState[4],1)]),
                tableInv3[getByte(origState[3],0)])

    dstState[3] =  bit.bxor(bit.bxor(bit.bxor(
                tableInv0[getByte(origState[3],3)],
                tableInv1[getByte(origState[2],2)]),
                tableInv2[getByte(origState[1],1)]),
                tableInv3[getByte(origState[4],0)])

    dstState[4] =  bit.bxor(bit.bxor(bit.bxor(
                tableInv0[getByte(origState[4],3)],
                tableInv1[getByte(origState[3],2)]),
                tableInv2[getByte(origState[2],1)]),
                tableInv3[getByte(origState[1],0)])
end

--
-- do last decryption round
--
local function doInvLastRound(origState, dstState)
    dstState[1] = putByte(iSBox[getByte(origState[1],3)], 3)
                + putByte(iSBox[getByte(origState[4],2)], 2)
                + putByte(iSBox[getByte(origState[3],1)], 1)
                + putByte(iSBox[getByte(origState[2],0)], 0)

    dstState[2] = putByte(iSBox[getByte(origState[2],3)], 3)
                + putByte(iSBox[getByte(origState[1],2)], 2)
                + putByte(iSBox[getByte(origState[4],1)], 1)
                + putByte(iSBox[getByte(origState[3],0)], 0)

    dstState[3] = putByte(iSBox[getByte(origState[3],3)], 3)
                + putByte(iSBox[getByte(origState[2],2)], 2)
                + putByte(iSBox[getByte(origState[1],1)], 1)
                + putByte(iSBox[getByte(origState[4],0)], 0)

    dstState[4] = putByte(iSBox[getByte(origState[4],3)], 3)
                + putByte(iSBox[getByte(origState[3],2)], 2)
                + putByte(iSBox[getByte(origState[2],1)], 1)
                + putByte(iSBox[getByte(origState[1],0)], 0)
end

--
-- encrypts 16 Bytes
-- key           encryption key schedule
-- input         array with input data
-- inputOffset   start index for input
-- output        array for encrypted data
-- outputOffset  start index for output
--
local function encrypt(key, input, inputOffset, output, outputOffset)
    --default parameters
    inputOffset = inputOffset or 1
    output = output or {}
    outputOffset = outputOffset or 1

    local state = {}
    local tmpState = {}

    if (key[KEY_TYPE] ~= ENCRYPTION_KEY) then
        error("No encryption key: " .. tostring(key[KEY_TYPE]) .. ", expected " .. ENCRYPTION_KEY)
        return
    end

    state = util.bytesToInts(input, inputOffset, 4)
    addRoundKey(state, key, 0)


    local round = 1
    while (round < key[ROUNDS] - 1) do
        -- do a double round to save temporary assignments
        doRound(state, tmpState)
        addRoundKey(tmpState, key, round)
        round = round + 1

        doRound(tmpState, state)
        addRoundKey(state, key, round)
        round = round + 1
    end

    doRound(state, tmpState)
    addRoundKey(tmpState, key, round)
    round = round +1

    doLastRound(tmpState, state)
    addRoundKey(state, key, round)

    util.sleepCheckIn()
    return util.intsToBytes(state, output, outputOffset)
end

--
-- decrypt 16 bytes
-- key           decryption key schedule
-- input         array with input data
-- inputOffset   start index for input
-- output        array for decrypted data
-- outputOffset  start index for output
---
local function decrypt(key, input, inputOffset, output, outputOffset)
    -- default arguments
    inputOffset = inputOffset or 1
    output = output or {}
    outputOffset = outputOffset or 1

    local state = {}
    local tmpState = {}

    if (key[KEY_TYPE] ~= DECRYPTION_KEY) then
        error("No decryption key: " .. tostring(key[KEY_TYPE]))
        return
    end

    state = util.bytesToInts(input, inputOffset, 4)
    addRoundKey(state, key, key[ROUNDS])

    local round = key[ROUNDS] - 1
    while (round > 2) do
        -- do a double round to save temporary assignments
        doInvRound(state, tmpState)
        addRoundKey(tmpState, key, round)
        round = round - 1

        doInvRound(tmpState, state)
        addRoundKey(state, key, round)
        round = round - 1
    end


    doInvRound(state, tmpState)
    addRoundKey(tmpState, key, round)
    round = round - 1

    doInvLastRound(tmpState, state)
    addRoundKey(state, key, round)

    util.sleepCheckIn()
    return util.intsToBytes(state, output, outputOffset)
end

-- calculate all tables when loading this file
calcSBox()
calcRoundTables()
calcInvRoundTables()

return {
    ROUNDS = ROUNDS,
    KEY_TYPE = KEY_TYPE,
    ENCRYPTION_KEY = ENCRYPTION_KEY,
    DECRYPTION_KEY = DECRYPTION_KEY,

    expandEncryptionKey = expandEncryptionKey,
    expandDecryptionKey = expandDecryptionKey,
    encrypt = encrypt,
    decrypt = decrypt,
}
end)
local buffer=_W(function(_ENV, ...)
local function new ()
    return {}
end

local function addString (stack, s)
    table.insert(stack, s)
end

local function toString (stack)
    return table.concat(stack)
end

return {
    new = new,
    addString = addString,
    toString = toString,
}
end)
ciphermode=_W(function(_ENV, ...)
local public = {}

--
-- Encrypt strings
-- key - byte array with key
-- string - string to encrypt
-- modefunction - function for cipher mode to use
--

local random = math.random
function public.encryptString(key, data, modeFunction, iv)
    if iv then
        local ivCopy = {}
        for i = 1, 16 do ivCopy[i] = iv[i] end
        iv = ivCopy
    else
        iv = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    end

    local unpack = table.unpack
    local keySched = aes.expandEncryptionKey(key)
    local encryptedData = buffer.new()

    for i = 1, #data/16 do
        local offset = (i-1)*16 + 1
        local byteData = {string.byte(data,offset,offset +15)}

        iv = modeFunction(keySched, byteData, iv)
        buffer.addString(encryptedData, string.char(unpack(byteData)))
    end

    return buffer.toString(encryptedData)
end

--
-- the following 4 functions can be used as
-- modefunction for encryptString
--

-- Electronic code book mode encrypt function
function public.encryptECB(keySched, byteData, iv)
    aes.encrypt(keySched, byteData, 1, byteData, 1)
end

-- Cipher block chaining mode encrypt function
function public.encryptCBC(keySched, byteData, iv)
    util.xorIV(byteData, iv)
    aes.encrypt(keySched, byteData, 1, byteData, 1)
    return byteData
end

-- Output feedback mode encrypt function
function public.encryptOFB(keySched, byteData, iv)
    aes.encrypt(keySched, iv, 1, iv, 1)
    util.xorIV(byteData, iv)
    return iv
end

-- Cipher feedback mode encrypt function
function public.encryptCFB(keySched, byteData, iv)
    aes.encrypt(keySched, iv, 1, iv, 1)
    util.xorIV(byteData, iv)
    return byteData
end

function public.encryptCTR(keySched, byteData, iv)
    local nextIV = {}
    for j = 1, 16 do nextIV[j] = iv[j] end

    aes.encrypt(keySched, iv, 1, iv, 1)
    util.xorIV(byteData, iv)

    util.increment(nextIV)

    return nextIV
end

--
-- Decrypt strings
-- key - byte array with key
-- string - string to decrypt
-- modefunction - function for cipher mode to use
--
function public.decryptString(key, data, modeFunction, iv)
    if iv then
        local ivCopy = {}
        for i = 1, 16 do ivCopy[i] = iv[i] end
        iv = ivCopy
    else
        iv = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    end

    local keySched
    if modeFunction == public.decryptOFB or modeFunction == public.decryptCFB or modeFunction == public.decryptCTR then
        keySched = aes.expandEncryptionKey(key)
    else
        keySched = aes.expandDecryptionKey(key)
    end

    local decryptedData = buffer.new()
    local unpack = table.unpack

    for i = 1, #data/16 do
        local offset = (i-1)*16 + 1
        local byteData = {string.byte(data,offset,offset +15)}

        iv = modeFunction(keySched, byteData, iv)

        buffer.addString(decryptedData, string.char(unpack(byteData)))
    end

    return buffer.toString(decryptedData)
end

--
-- the following 4 functions can be used as
-- modefunction for decryptString
--

-- Electronic code book mode decrypt function
function public.decryptECB(keySched, byteData, iv)
    aes.decrypt(keySched, byteData, 1, byteData, 1)
    return iv
end

-- Cipher block chaining mode decrypt function
function public.decryptCBC(keySched, byteData, iv)
    local nextIV = {}
    for j = 1, 16 do nextIV[j] = byteData[j] end

    aes.decrypt(keySched, byteData, 1, byteData, 1)
    util.xorIV(byteData, iv)

    return nextIV
end

-- Output feedback mode decrypt function
function public.decryptOFB(keySched, byteData, iv)
    aes.encrypt(keySched, iv, 1, iv, 1)
    util.xorIV(byteData, iv)

    return iv
end

-- Cipher feedback mode decrypt function
function public.decryptCFB(keySched, byteData, iv)
    local nextIV = {}
    for j = 1, 16 do nextIV[j] = byteData[j] end

    aes.encrypt(keySched, iv, 1, iv, 1)
    util.xorIV(byteData, iv)

    return nextIV
end

public.decryptCTR = public.encryptCTR

return public
end)
-- Simple API for encrypting strings.
--
AES128 = 16
AES192 = 24
AES256 = 32

ECBMODE = 1
CBCMODE = 2
OFBMODE = 3
CFBMODE = 4
CTRMODE = 4

local function pwToKey(password, keyLength, iv)
    local padLength = keyLength
    if (keyLength == AES192) then
        padLength = 32
    end

    if (padLength > #password) then
        local postfix = ""
        for i = 1,padLength - #password do
            postfix = postfix .. string.char(0)
        end
        password = password .. postfix
    else
        password = string.sub(password, 1, padLength)
    end

    local pwBytes = {string.byte(password,1,#password)}
    password = ciphermode.encryptString(pwBytes, password, ciphermode.encryptCBC, iv)

    password = string.sub(password, 1, keyLength)

    return {string.byte(password,1,#password)}
end

--
-- Encrypts string data with password password.
-- password  - the encryption key is generated from this string
-- data      - string to encrypt (must not be too large)
-- keyLength - length of aes key: 128(default), 192 or 256 Bit
-- mode      - mode of encryption: ecb, cbc(default), ofb, cfb
--
-- mode and keyLength must be the same for encryption and decryption.
--
function encrypt(password, data, keyLength, mode, iv)
    assert(password ~= nil, "Empty password.")
    assert(password ~= nil, "Empty data.")

    local mode = mode or CBCMODE
    local keyLength = keyLength or AES128

    local key = pwToKey(password, keyLength, iv)

    local paddedData = util.padByteString(data)

    if mode == ECBMODE then
        return ciphermode.encryptString(key, paddedData, ciphermode.encryptECB, iv)
    elseif mode == CBCMODE then
        return ciphermode.encryptString(key, paddedData, ciphermode.encryptCBC, iv)
    elseif mode == OFBMODE then
        return ciphermode.encryptString(key, paddedData, ciphermode.encryptOFB, iv)
    elseif mode == CFBMODE then
        return ciphermode.encryptString(key, paddedData, ciphermode.encryptCFB, iv)
    elseif mode == CTRMODE then
        return ciphermode.encryptString(key, paddedData, ciphermode.encryptCTR, iv)
    else
        error("Unknown mode", 2)
    end
end




--
-- Decrypts string data with password password.
-- password  - the decryption key is generated from this string
-- data      - string to encrypt
-- keyLength - length of aes key: 128(default), 192 or 256 Bit
-- mode      - mode of decryption: ecb, cbc(default), ofb, cfb
--
-- mode and keyLength must be the same for encryption and decryption.
--
function decrypt(password, data, keyLength, mode, iv)
    local mode = mode or CBCMODE
    local keyLength = keyLength or AES128

    local key = pwToKey(password, keyLength, iv)

    local plain
    if mode == ECBMODE then
        plain = ciphermode.decryptString(key, data, ciphermode.decryptECB, iv)
    elseif mode == CBCMODE then
        plain = ciphermode.decryptString(key, data, ciphermode.decryptCBC, iv)
    elseif mode == OFBMODE then
        plain = ciphermode.decryptString(key, data, ciphermode.decryptOFB, iv)
    elseif mode == CFBMODE then
        plain = ciphermode.decryptString(key, data, ciphermode.decryptCFB, iv)
    elseif mode == CTRMODE then
        plain = ciphermode.decryptString(key, data, ciphermode.decryptCTR, iv)
    else
        error("Unknown mode", 2)
    end

    result = util.unpadByteString(plain)

    if (result == nil) then
        return nil
    end

    return result
end
"""


def decrypt_stage_1(key, ct):
    key = base64.b64decode(key)
    ct = base64.b64decode(ct)
    iv = ct[:DES3.block_size]
    data = ct[DES3.block_size:]
    cipher = DES3.new(key, DES3.MODE_CFB, iv, segment_size=64)
    pt = cipher.decrypt(data)
    return pt

def decrypt_stage_2(key, data):
    lua = lupa.LuaRuntime(unpack_returned_tuples=False)
    aes = lua.execute(LUACODE)
    g = lua.globals()
    return g.decrypt(key, data)


efile = '/tmp/safety-folder-1s1f822vh897c29nnbl10/secret_file.txt.mitr3.encrypted'
stage1key = '5JOz7Os6PA10jffVqz8MbT1uePrJOrVX'
stage2key = '1539741125'

data_before_2 = decrypt_stage_2(stage2key, open(efile, 'rb').read())
print('before 2', data_before_2)

data_before_1 = decrypt_stage_1(stage1key, data_before_2)
print('before 1', data_before_1)

