local Config = require('opus.config') local SHA = require('opus.crypto.sha2') local Util = require('opus.util') local PBKDF2_ITERATIONS = 100 local Security = { } local function generateSalt() local salt = { } for _ = 1, 16 do salt[#salt + 1] = math.random(0, 0xFF) end return setmetatable(salt, Util.byteArrayMT):toHex() end function Security.verifyPassword(password) local stored = Security.getPassword() if not stored then return false end -- New format: { hash = hex, salt = hex, iter = N } if type(stored) == 'table' and stored.hash and stored.salt then local iter = stored.iter or PBKDF2_ITERATIONS local derived = SHA.pbkdf2(password, Util.hexToByteArray(stored.salt), iter) return derived:toHex() == stored.hash end -- Legacy format: plain SHA-256 hex string if type(stored) == 'string' then return SHA.compute(password) == stored end return false end function Security.hasPassword() return not not Security.getPassword() end function Security.getIdentifier() local config = Config.load('os') if not config.identifier then local key = { } for _ = 1, 32 do table.insert(key, ("%02x"):format(math.random(0, 0xFF))) end config.identifier = table.concat(key) Config.update('os', config) end return config.identifier end function Security.updatePassword(password) local salt = generateSalt() local derived = SHA.pbkdf2(password, Util.hexToByteArray(salt), PBKDF2_ITERATIONS) local config = Config.load('os') config.password = { hash = derived:toHex(), salt = salt, iter = PBKDF2_ITERATIONS, } Config.update('os', config) end function Security.getPassword() return Config.load('os').password end return Security