0
votes

I have a Lua program which connects to a wifi network. The wifi password is hardcoded in the Lua code. I put the Lua code on an ESP8266 which runs on the NodeMCU firmware.

enter image description here

Here is the code I use:

wifi.setmode(wifi.STATION)
wifi.sta.config("SSID", "password")
wifi.sta.connect()

srv = net.createServer(net.TCP)
srv:listen(80,function(conn)
    conn:on("receive", function(conn, payload)
        print(payload)
        local response = "HTTP/1.1 200 OK\r\n\r\n<h1> Hello, NodeMcu.</h1>"
        conn:send(response, function()
            conn:close()
        end)
    end)
end)

When I use this setup for outdoor sensors anyone can grab the ESP8266, read the Lua script and get my WiFi password.

  1. Is there somewhere to encrypt the password on the ESP8266 with Lua?
  2. Is is possible to use some external crypto unit?
  3. Does compiled Lua code changes anything?
4
Physical access is total access. There's no way you can both encrypt and use the password on the same device that an attacker couldn't reverse engineer.elixenide
Store your program as bytecode, but don't expose your password as string literal: wifi.sta.config("SSID",(('948197979B939684'):gsub('..',function(s)return s.char(tonumber(s,12))end)))Egor Skriptunoff
@EgorSkriptunoff What do you mean my "as bytecode" do you mean compiling the code to .lc? Could you please give a detailed answer how you would handle the password problem?confile
@confile - To compile bytecode: luac -s -o program.luac program.lua. You can use any non-trivial expression evaluated to your password. This is not a complete solution, it just makes extracting your password a bit harder.Egor Skriptunoff
@confile - You can't insert your password in a string literal as it would be visible in .luac-file. That's why you need some expression, which will be evaluated in runtime to get your password.Egor Skriptunoff

4 Answers

3
votes

(Updated)

Within Lua code, there is no security on physical access. In addition to other answers: ESP have an option to save wifi settings. Thus this will allow you not putting credentials into a lua file. Program the credentials once and then remove credential code. This will make it a little bit harder to get the credentials. At least one who reads the code will not see it. (but of course not a total security option because of this.) Every time esp reboots or wifi network is in range, it will connect to the same ssid and network. This is on by default.

wifi.setmode(wifi.STATION)
wifi.sta.config("SSID", "password")
wifi.sta.connect()

Another way to do that is mac address registiration. If your AP allows you to register mac addresses of stations then it will provide one more layer of security since getting password will not be sufficient to connect the wifi network. This is not securing your password! You need to configure your AP to do that.

One more method: This will require more code. I cannot say it is more secure but it will make it harder to discover. You should compile your lua files to use this. Simply XOR your password with ssid and put the xor'ed version in your code. Survey the networks and create password for each network. Try to connect everyone with the produced pass. The matching pair will connect successfully. The others will fail. This will make connection sequence a little bit longer.

--encode
--retuns a table containing bytes
--also prints what should be in code
-- s is ssid, p is password
function encode(s,p)    
    key = s..s --encoding key, must be longer than password 
    enc = {} --new table
    uart.write(0,"Key output: {")

    for i=1,string.len(p) do        
        c = bit.bxor(p:byte(i),key:byte(i))
        table.insert(enc,c)     
        if i == string.len(p) then
            uart.write(0,c.."}")
        else 
            uart.write(0,c..",")
        end
    end 
    return enc
end

--decode
--tries to decode password with table enc
--s is ssid (got from survey) encval is byte table
function decode(s,encval)
    key=s..s
    pass=""
    for ii,i in ipairs(encval) do
        c = bit.bxor( key:byte(ii),i)
        pass = pass..string.char(c)
    end
    print("password: "..pass) 
    return pass
end

-- lets say ssid="network" and password="psswrd12"
encodedpass = encode("network","psswrd12")
-- this will print: Key output: {30,22,7,0,29,22,90,92}
-- this will be included in code
-- and to decode
print(decode("network",encodedpass))
-- will print: password: psswrd12
2
votes

Once you connect the ESP8266 stores the password internally, you don't need it to re-connect after that, you just omit the password argument or pass nil.

I store all runtime settings in a file aptly called settings.lua, that looks like this:

cfg.WiFiAP = "myAP"
cfg.WiFiPwd = "*****"

I load it into a global table in init.lua, like this:

cfg = {}
dofile("settings.lua")

When I'm initializing WiFi I check to see if the password isn't nil. If it is not, I use it to connect, set it to nil, and then overwrite the settings file:

if cfg.WiFiPwd ~= nil then
    wifi.sta.config(cfg.WiFiSSID, cfg.WiFiPwd, 0)
    cfg.WiFiPwd = nil
    require("writecfg")()
end

and writecfg.lua looks like this:

local module =...
return function()
    print("[writecfg]")
    file.open("settings.lua", "w+")
    for k,v in pairs(cfg) do
        if v == nil then
            v = ""
        end
        local buf = "cfg." .. k .. " = \"" .. v .. "\""
        file.writeline(buf)
        buf = nil
    end
    file.close()
    file.remove("settings.lc")
    package.loaded[module] = nil
    module = nil
end

It may not be impossible to get the ESP8266 to give up the password, but it is certainly much more difficult than storing it in code, no matter how you try to obfuscate.

1
votes

Any sort of password encryption and Lua compiling is security by obscurity (and hence no security).

I suggest you don't put the password in the Lua code at all but have it set at runtime. There are at least 3 options:

Caution: while this avoids placing the password in the source code it poses a slight risk because the connection to the ESP is not encrypted when the password is sent from the browser (can't do SSL).

1
votes

I'd also consider using an I2C EEPROM to store a key, so that any compromise also requires the hardware not just the Lua/Flash, but even that is able to be "snooped" if the machine has physically been compromised, as I imagine could be the case by probing the circuit with a data scope.

There's an answer, or at least part of the answer of "you can't", which hasn't been covered in the other warnings on reverse engineering:

Strictly speaking: (and I say this as someone who has worked in the field for a couple of decades) You should not be putting any "Internet of Things" type device in a publicly accessible place which is not connecting to its own "IoT" access point really. While this may sound like overkill (and would be, if it's only for a brief test) it is the only way to really secure yourself against the possibility of reverse engineering taking place.

You should then of course have that access point network fire-walled from your home "private" network with a DMZ network machine to communicate with the "Things". A spare old PC with a few NICs, running something like the IPCop distro makes this fairly straightforward and relatively cheap.

But even just using a different access point/password combo onto the same network allows a stricter policy on that AP

Again, this may seem like overkill for your use-case, and I agree in quick test situations, but if you're serious about network security and you want to free the Things, it's the better way to go - long term.

as a side note: I do feel that home AP/Routers need to soon come with something like this built-in, so that the future Home of Things has that ability without additional skills or hardware.