Skip to content(if available)orjump to list(if available)

Preventing IoT Edge Device Cloning

Preventing IoT Edge Device Cloning

5 comments

·September 23, 2025

mrbluecoat

> when attackers capture real devices, extract cryptographic keys or identifiers, and use them to build duplicates

If they've captured the device, what if they don't clone it but use it directly instead?

imglorp

AWS Greengrass was a pain in the tail to use, but I feel they had several ideas to mitigate this problem at least: identity was managed centrally with certificate rotation. I'm pretty sure a captured device could not be cloned and continue to phone into the cloud. They had several other good ideas, maybe standard now, like device shadows, where intent is propagated towards the edge and state is propagated back to the cloud.

tylerflick

I hate managing clients certs, but I doubt there’s a better way.

evgpbfhnr

"How it works" https://realtimelogic.com/ba/doc/en/SoftTPM.html

I couldn't find "actual sources", but one of their github repo has this: https://github.com/RealTimeLogic/BAS/blob/main/examples/Mako...

Which extracts to this .config file (looks like lua code, that creates a secret from PBKDF2 of... what? I couldn't find where secrets would come from here, but that repo obviously misses the interesting bindings; from the how it works link it looks like they're just hashing the SN to generate a pseudorandom key but I don't see why you couldn't just generate a key for neighboring devices by just faking the SN then...)

    local maxHash=pcall(function() ba.crypto.hash("sha512") end) and "sha512" or "sha256"
    local sfmt,jencode,jdecode,symmetric,PBKDF2,keyparams,sign,jwtsign,createkey,createcsr,sharkcert=
    string.format,ba.json.encode,ba.json.decode,ba.crypto.symmetric,ba.crypto.PBKDF2,ba.crypto.keyparams,
    ba.crypto.sign,require"jwt".sign,ba.create.key,ba.create.csr,ba.create.sharkcert
    local function setuser(ju,db,name,pwd)
    if pwd then
    if type(pwd) == "string" then
    pwd={pwd=pwd,roles={}}
    end
    db[name]=pwd
    else
    db[name]=nil
    end
    local ok,err=ju:set(db)
    if not ok then error(err,3) end
    end
    local function tpm(gpkey,upkey)
    local keys={}
    local function tpmGetKey(kname)
    local key=keys[kname]
    if not key then error(sfmt("ECC key %s not found",tostring(kname)),3) end
    return key
    end
    local function tpmSign(h,kname,op) return sign(h,tpmGetKey(kname),op) end
    local function tpmJwtsign(p,kname,op) return jwtsign(p,function(h) return sign(h,tpmGetKey(kname)) end,op) end
    local function tpmKeyparams(kname) return keyparams(tpmGetKey(kname)) end
    local function tpmCreatecsr(kname,...) return createcsr(tpmGetKey(kname),...) end
    local function tpmCreatekey(kname,op)
    if keys[kname] then error(sfmt("ECC key %s exists",kname),2) end
    op = op or {}
    if op.key and op.key ~= "ecc" then error("TPM can only create ECC keys",2) end
    local newOp={}
    for k,v in pairs(op) do newOp[k]=v end
    newOp.rnd=PBKDF2(maxHash,"@#"..kname,upkey,5,1024)
    local key=createkey(newOp)
    keys[kname]=key
    return true
    end
    local function tpmHaskey(kname) return keys[kname] and true or false end
    local function tpmSharkcert(kname,certdata) return sharkcert(certdata,tpmGetKey(kname)) end
    require"acme/engine".setTPM{jwtsign=tpmJwtsign,keyparams=tpmKeyparams,createcsr=tpmCreatecsr,createkey=tpmCreatekey,haskey=tpmHaskey}
    local t={}
    function t.haskey(k) return tpmHaskey(k) end
    function t.createkey(k,...) return tpmCreatekey(k,...) end
    function t.createcsr(k,...) return tpmCreatecsr(k,...) end
    function t.sign(h,k,o) return tpmSign(h,k,o) end
    function t.jwtsign(k,...) return tpmJwtsign(k,...) end
    function t.keyparams(k,...) return tpmKeyparams(k,...) end
    function t.sharkcert(k,...) return tpmSharkcert(k,...) end
    function t.globalkey(n,l) return PBKDF2(maxHash,n,gpkey,5,l) end
    function t.uniquekey(n,l) return PBKDF2(maxHash,n,upkey,5,l) end
    function t.jsonuser(k,global)
    k=PBKDF2("sha256","@#"..k,global and gpkey or upkey,6,1)
    local function enc(db)
    local iv=ba.rndbs(12)
    local gcmEnc=symmetric("GCM",k,iv)
    local cipher,tag=gcmEnc:encrypt(jencode(db),"PKCS7")
    return iv..tag..cipher
    end
    local function dec(encdb)
    if encdb and #encdb > 30 then
    local iv=encdb:sub(1,12)
    local tag=encdb:sub(13,28)
    local gcmDec=symmetric("GCM",k,iv)
    local db
    pcall(function() db=jdecode(gcmDec:decrypt(encdb:sub(29,-1),tag,"PKCS7")) end)
    if db then return db end
    end
    return nil,"Data corrupt"
    end
    local ju,db=ba.create.jsonuser(),{}
    return {
    users=function() local x={} for u in pairs(db) do table.insert(x,u) end return x end,
    setuser=function(name,pwd) setuser(ju,db,name,pwd) return enc(db) end,
    setdb=function(encdb) local d,err,ok=dec(encdb) if d then ok,err=ju:set(d) if ok then db=d return ok end end return nil,err end,
    getauth=function() return ju end
    }
    end
    ba.tpm=t
    end
    
    local klist={}
    return function(x)
    if true == x then
    local hf=ba.crypto.hash(maxHash)
    for _,k in ipairs(klist) do hf(k) end
    tpm(ba.crypto.hash(maxHash)(klist[1])(true),hf(true))
    klist=nil
    return
    end
    table.insert(klist,x)
    end

baybal2

[dead]