This forum uses cookies
This forum makes use of cookies to store your login information if you are registered, and your last visit if you are not. Cookies are small text documents stored on your computer; the cookies set by this forum can only be used on this website and pose no security risk. Cookies on this forum also track the specific topics you have read and when you last read them. Please confirm whether you accept or reject these cookies being set.

Buderus KM200 api
#1
Hello

Here is a script I made that allows you to interface a LM5 with a Logamatic KM200 Buderus gateway.
Logamatic web KM50, KM100, KM300 modules, and Junkers/Bosch MB LANi should also be supported in theory.
 
This script allows you to read any endpoint from the REST interface of the gateway, and assign the values to a KNX group.
It is also possible to set some of the values, ie. change from "auto" to "day" or "night" mode.
Included in an event script, and with a few customizations at the end, you could control the central heating from a few KNX address groups.

For all intent and purposes, this script gives you the same capabilities as the KNX10 module from Bosch, that is available almost nowhere, and is only in German.

Because I do not read many endpoints in my case, I run the script in a cyclic mode on a 60s cycle. 
Accessing a lot of the endpoints would probably require some changes to the script to allow more, or less, polling on some of the endpoints (ie: 10min for the outdoor temperature, 15s for the mode, ...). 
This would reduce the load on the gateway and the network.

Two scripts are required: A custom aes.lua library installed in the user library, and the km200.lua

The aes.lua is different than the one published in another thread on the forum because this one supports no-padding and can decode strings encrypted in other tools, which the original library cannot do.

The list of all endpoints that exist in my system (and some more) is included in the km200.lua script. 
Your system may have more or less depending on the hardware installed.

Of course, if you decide to use the scripts, you do it at your own risk. 
I am not responsible if something goes wrong.

That said, my house is still standing.  Wink

Enjoy.
Michel.

KM200.lua:
Code:
--
-- Control of Buderus boilers using a Logamatic web KM200 module.
-- Logamatic web KM50, KM100, KM300 modules, and Junkers/Bosch MB LANi, should also be supported.
--
-- Reading and writing from/to the gateway is supported.
--
-- v1.0.0 - 2019-04-28
-- Michel De Ligne
--

encdec = require('encdec')
aes = require('user.aes')
json = require('json')
ltn12 = require('ltn12')
http = require('socket.http')

--
-- !!! The three following parameters need to be filled in properly !!!
--
-- IP address or DNS name of the Buderus KM200.
local km200_gateway_host = "TK-850XXXNET.home" -- or "192.168.xxx.xxx"
-- Gateway password - found on the sticker on the KM200.
-- Remove the "-", and put everything in one word.
local km200_gateway_password = "gateway_password" -- ie: fjbvJH5IjYndKdhf

-- Password defined in the app.
local km200_private_password = "app_password" -- ie: qweYTR!rtyEWQ

--
-- Code
--

-- MD5 salt used for the AES key generation (do not change!)
local km200_crypt_md5_salt = string.char(
       0x86, 0x78, 0x45, 0xe9, 0x7c, 0x4e, 0x29, 0xdc,
       0xe5, 0x22, 0xb9, 0xa7, 0xd3, 0xa3, 0xe0, 0x7b,
       0x15, 0x2b, 0xff, 0xad, 0xdd, 0xbe, 0xd7, 0xf5,
       0xff, 0xd8, 0x42, 0xe9, 0x89, 0x5a, 0xd1, 0xe4
);

-- First half of the key: MD5 of Gateway password followed by km200_crypt_md5_salt
local key_1 = encdec.md5(km200_gateway_password .. km200_crypt_md5_salt, true)
-- Second part of the key - private: MD5 from salt followed by private_password
local key_2_private = encdec.md5(km200_crypt_md5_salt .. km200_private_password, true)

km200_crypt_key_private = key_1 .. key_2_private

-- PKCS7 pad/unpad
function pad(data, blocksize, optional)
   blocksize = blocksize or 16
   if type(blocksize) ~= "number" then
       return nil, "invalid block size data type"
   end
   if blocksize < 1 or blocksize > 256 then
       return nil, "invalid block size"
   end
   local ps = blocksize - #data % blocksize
   if optional and ps == blocksize then return data end
   return data .. string.rep(string.char(ps), ps)
end

-- PKCS7 unpad
function unpad(data, blocksize)
   blocksize = blocksize or 16
   if type(blocksize) ~= "number" then
       return nil, "invalid block size data type"
   end
   if blocksize < 1 or blocksize > 256 then
       return nil, "invalid block size"
   end
   local len = #data
   if len % blocksize ~= 0 then
       return nil, "data length is not a multiple of the block size"
   end
   local chr = string.sub(data, -1)
   local rem = string.byte(chr)
   if rem > 0 and rem <= blocksize then
       local chk = string.sub(data, -rem)
       if chk == string.rep(chr, rem) then
           return string.sub(data, 1, len - rem)
       end
   end
   return data
end

function km200_Encrypt(encryptData)
   -- add PKCS #7 padding
   encryptData = pad(encryptData)

   local hash = { iv = string.rep('\0', 16) } -- no hashing method for key
   local aes_256_ecb, err = aes:new(km200_crypt_key_private, nil, aes.cipher(256, 'ecb'), hash, nil, 0)
   return encdec.base64enc(aes_256_ecb:encrypt(encryptData))
   end

function km200_Decrypt(decryptData)
   local hash = { iv = string.rep('\0', 16) } -- no hashing method for key
   local aes_256_ecb, err = aes:new(km200_crypt_key_private, nil, aes.cipher(256, 'ecb'), hash, nil, 0)
   local res = aes_256_ecb:decrypt(encdec.base64dec(decryptData)) -- data block size is always 128 bits

   -- Search the position of the last "}", and remove all the remaining padding after that (I am lazy).
   local endposition = string.find(res, "}", -0)
   res = string.sub(res, 0, endposition)
   return res
end

function dorequest(url)
   local tbl = {}
   local res, err = http.request({
       url = url,
       headers = {
           ['Accept'] = 'application/json',
           ['User-Agent'] = 'TeleHeater/2.2.3'
       },
       sink = ltn12.sink.table(tbl)
   })

   if res then
       return table.concat(tbl)
   else
       log('http error: ' .. err)
       return nil
   end
end

function km200_GetData(REST_URL)
   local res = dorequest('http://' .. km200_gateway_host .. REST_URL)

   if res then
       return km200_Decrypt(res)
   else
       return nil
   end
end

function doput(url, reqbody)
   local tbl = {}
   local res, err = http.request({
       url = url,
       method = 'POST',
       source = ltn12.source.string(reqbody),
       headers = {
           ['User-Agent'] = 'TeleHeater/2.2.3',
           ['Content-Type'] = 'application/json',
           ['Content-Length'] = string.len(reqbody)
       },
       sink = ltn12.sink.table(tbl)
   })
   if res then
       return table.concat(tbl)
   else
       log('http error: ' .. err)
       return nil
   end
end

function km200_SetData(REST_URL, Value)
   local content = json.encode({ value = Value })
   local res = doput('http://' .. km200_gateway_host .. REST_URL, km200_Encrypt(content))

   if res then
       return km200_Decrypt(res)
   else
       return nil
   end
end

-- List of all the REST endpoints available.
-- The endpoints available depends on the system configuration (ie: solar vs no solar, type of regulation, ...)

--[[
/dhwCircuits/dhw1
/dhwCircuits/dhw1/actualTemp
/dhwCircuits/dhw1/charge
/dhwCircuits/dhw1/chargeDuration
/dhwCircuits/dhw1/cpStartph
/dhwCircuits/dhw1/currentSetpoint
/dhwCircuits/dhw1/operationMode
/dhwCircuits/dhw1/singleChargeSetpoint
/dhwCircuits/dhw1/status
/dhwCircuits/dhw1/switchPrograms
/dhwCircuits/dhw1/tdMode
/dhwCircuits/dhw1/tdsetPoint
/dhwCircuits/dhw1/temperatureLevels
/dhwCircuits/dhw1/temperatureLevels/high
/dhwCircuits/dhw1/temperatureLevels/off
/dhwCircuits/dhw1/waterFlow
/dhwCircuits/dhw1/workingTime
/gateway/boschSHPassword
/gateway/DateTime
/gateway/firmware
/gateway/haiPassword
/gateway/instAccess
/gateway/instPassword
/gateway/instWriteAccess
/gateway/knxPassword
/gateway/portalPassword
/gateway/update
/gateway/userpassword
/gateway/uuid
/gateway/version
/gateway/versionFirmware
/gateway/versionHardware
/heatingCircuits
/heatingCircuits/hc1
/heatingCircuits/hc1/activeSwitchProgram
/heatingCircuits/hc1/actualSupplyTemperature
/heatingCircuits/hc1/controlType
/heatingCircuits/hc1/currentOpModeInfo
/heatingCircuits/hc1/currentRoomSetpoint
/heatingCircuits/hc1/designTemp
/heatingCircuits/hc1/fastHeatupFactor
/heatingCircuits/hc1/heatCurveMax
/heatingCircuits/hc1/heatCurveMin
/heatingCircuits/hc1/manualRoomSetpoint
/heatingCircuits/hc1/nextSetpoint
/heatingCircuits/hc1/operationMode
/heatingCircuits/hc1/pumpModulation
/heatingCircuits/hc1/roomInfluence
/heatingCircuits/hc1/roomtemperature
/heatingCircuits/hc1/roomTempOffset
/heatingCircuits/hc1/setpointOptimization
/heatingCircuits/hc1/solarInfluence
/heatingCircuits/hc1/status
/heatingCircuits/hc1/suWiSwitchMode
/heatingCircuits/hc1/suWiThreshold
/heatingCircuits/hc1/switchPrograms
/heatingCircuits/hc1/switchPrograms/A
/heatingCircuits/hc1/switchPrograms/B
/heatingCircuits/hc1/temperatureLevels
/heatingCircuits/hc1/temperatureLevels/comfort2
/heatingCircuits/hc1/temperatureLevels/eco
/heatingCircuits/hc1/temperatureRoomSetpoint
/heatingCircuits/hc1/temporaryRoomSetpoint
/heatingCircuits/hc1/timeToNextSetpoint
/heatSources
/heatSources/actualCHPower
/heatSources/actualDHWPower
/heatSources/actualModulation
/heatSources/actualPower
/heatSources/actualSupplyTemperature
/heatSources/applianceSupplyTemperature
/heatSources/burnerModulationSetpoint
/heatSources/burnerPowerSetpoint
/heatSources/ChimneySweeper
/heatSources/CHpumpModulation
/heatSources/flameCurrent
/heatSources/flameStatus
/heatSources/gasAirPressure
/heatSources/hs1
/heatSources/hs1/actualCHPower
/heatSources/hs1/actualDHWPower
/heatSources/hs1/actualModulation
/heatSources/hs1/actualPower
/heatSources/hs1/CHpumpModulation
/heatSources/hs1/energyReservoir
/heatSources/hs1/flameStatus
/heatSources/hs1/fuel
/heatSources/hs1/fuel/caloricValue
/heatSources/hs1/fuel/density
/heatSources/hs1/fuelConsmptCorrFactor
/heatSources/hs1/info
/heatSources/hs1/nominalCHPower
/heatSources/hs1/nominalDHWPower
/heatSources/hs1/nominalFuelConsumption
/heatSources/hs1/numberOfStarts
/heatSources/hs1/reservoirAlert
/heatSources/hs1/supplyTemperatureSetpoint
/heatSources/hs1/type
/heatSources/info
/heatSources/nominalCHPower
/heatSources/nominalDHWPower
/heatSources/numberOfStarts
/heatSources/powerSetpoint
/heatSources/returnTemperature
/heatSources/supplyTemperatureSetpoint
/heatSources/systemPressure
/heatSources/workingTime
/heatSources/workingTime/centralHeating
/heatSources/workingTime/secondBurner
/heatSources/workingTime/totalSystem
/notifications
/recordings
/recordings/heatingCircuits
/recordings/heatingCircuits/hc1
/recordings/heatingCircuits/hc1/roomtemperature
/recordings/heatSources
/recordings/heatSources/actualCHPower
/recordings/heatSources/actualDHWPower
/recordings/heatSources/actualPower
/recordings/heatSources/hs1
/recordings/heatSources/hs1/actualPower
/recordings/system
/recordings/system/heatSources
/recordings/system/heatSources/hs1
/recordings/system/heatSources/hs1/actualPower
/recordings/system/sensors
/recordings/system/sensors/temperatures
/recordings/system/sensors/temperatures/outdoor_t1
/solarCircuits
/solarCircuits/sc1/collectorTemperature
/solarCircuits/sc1/pumpModulation
/solarCircuits/sc1/solarYield
/solarCircuits/sc1/status
/system
/system/appliance
/system/appliance/actualPower
/system/appliance/actualSupplyTemperature
/system/appliance/ChimneySweeper
/system/appliance/CHpumpModulation
/system/appliance/flameCurrent
/system/appliance/gasAirPressure
/system/appliance/nominalBurnerLoad
/system/appliance/numberOfStarts
/system/appliance/powerSetpoint
/system/appliance/systemPressure
/system/appliance/workingTime
/system/appliance/workingTime/centralHeating
/system/appliance/workingTime/secondBurner
/system/appliance/workingTime/totalSystem
/system/brand
/system/bus
/system/healthStatus
/system/heatSources
/system/heatSources/hs1
/system/heatSources/hs1/actualModulation
/system/heatSources/hs1/actualPower
/system/heatSources/hs1/energyReservoir
/system/heatSources/hs1/fuel
/system/heatSources/hs1/fuel/caloricValue
/system/heatSources/hs1/fuel/density
/system/heatSources/hs1/fuelConsmptCorrFactor
/system/heatSources/hs1/nominalFuelConsumption
/system/heatSources/hs1/reservoirAlert
/system/holidayModes
/system/holidayModes/hm1
/system/holidayModes/hm1/assignedTo
/system/holidayModes/hm1/delete
/system/holidayModes/hm1/dhwMode
/system/holidayModes/hm1/hcMode
/system/holidayModes/hm1/startStop
/system/holidayModes/hm2
/system/holidayModes/hm2/assignedTo
/system/holidayModes/hm2/delete
/system/holidayModes/hm2/dhwMode
/system/holidayModes/hm2/hcMode
/system/holidayModes/hm2/startStop
/system/holidayModes/hm3
/system/holidayModes/hm3/assignedTo
/system/holidayModes/hm3/delete
/system/holidayModes/hm3/dhwMode
/system/holidayModes/hm3/hcMode
/system/holidayModes/hm3/startStop
/system/holidayModes/hm4
/system/holidayModes/hm4/assignedTo
/system/holidayModes/hm4/delete
/system/holidayModes/hm4/dhwMode
/system/holidayModes/hm4/hcMode
/system/holidayModes/hm4/startStop
/system/holidayModes/hm5
/system/holidayModes/hm5/assignedTo
/system/holidayModes/hm5/delete
/system/holidayModes/hm5/dhwMode
/system/holidayModes/hm5/hcMode
/system/holidayModes/hm5/startStop
/system/info
/system/minOutdoorTemp
/system/sensors
/system/sensors/temperatures
/system/sensors/temperatures/chimney
/system/sensors/temperatures/hotWater_t1
/system/sensors/temperatures/hotWater_t2
/system/sensors/temperatures/outdoor_t1
/system/sensors/temperatures/return
/system/sensors/temperatures/supply_t1
/system/sensors/temperatures/supply_t1_setpoint
/system/sensors/temperatures/switch
/system/systemType
]]

--
-- Mapping of some endpoints to KNX group addresses
--

-- {"id":"/heatSources/flameStatus","type":"stringValue","writeable":0,"recordable":1,"value":"off","allowedValues":["on","off"]}
jsondata = json.decode(km200_GetData("/heatSources/flameStatus"))
if (jsondata.value == 'on') then
   grp.write('heatSources_flameStatus', true)
else
   grp.write('heatSources_flameStatus', false)
end

-- Afficher la performance actuelle Générateur de chaleur (5.001)
-- {"id":"/heatSources/actualCHPower","type":"floatValue","writeable":0,"recordable":1,"value":0.0,"unitOfMeasure":"%"}
jsondata = json.decode(km200_GetData("/heatSources/actualCHPower"))
grp.write('heatSources_actualCHPower', jsondata.value)

jsondata = json.decode(km200_GetData("/heatingCircuits/hc1/roomtemperature"))
grp.write('heatingCircuits_hc1_roomtemperature', jsondata.value)

-- {"id":"/system/sensors/temperatures/outdoor_t1","type":"floatValue","writeable":0,"recordable":1,"value":14.0,"unitOfMeasure":"C"}
jsondata = json.decode(km200_GetData("/system/sensors/temperatures/outdoor_t1"))
grp.write('temperatures_outdoor_t1', jsondata.value)

-- {"id":"/heatSources/actualSupplyTemperature","type":"floatValue","writeable":0,"recordable":0,"value":21.7,"unitOfMeasure":"C"}
jsondata = json.decode(km200_GetData("/heatSources/actualSupplyTemperature"))
grp.write('heatSources_actualSupplyTemperature', jsondata.value)

-- {"id":"/system/healthStatus","type":"stringValue","writeable":0,"recordable":0,"value":"ok"}
jsondata = json.decode(km200_GetData("/system/healthStatus"))
if (jsondata.value == 'ok') then
   grp.write('system_healthStatus', false)
else
   grp.write('system_healthStatus', true)
end

-- {"id":"/heatingCircuits/hc1/operationMode","type":"stringValue","writeable":1,"recordable":1,"value":"auto","allowedValues":["night","day","auto"]}
jsondata = json.decode(km200_GetData("/heatingCircuits/hc1/operationMode"))
if (jsondata.value == 'auto') then
   grp.write('heatingCircuits_operationMode_Auto', true)
else
   grp.write('heatingCircuits_operationMode_Auto', false)
   if (jsondata.value == 'night') then
       grp.write('heatingCircuits_operationMode_NightDay', false)
   else
       grp.write('heatingCircuits_operationMode_NightDay', true)
   end
end

-- {"id":"/heatingCircuits/hc1/currentRoomSetpoint","type":"floatValue","writeable":0,"recordable":1,"value":18.0,"unitOfMeasure":"C","minValue":0.0,"maxValue":30.0}
jsondata = json.decode(km200_GetData("/heatingCircuits/hc1/currentRoomSetpoint"))
grp.write('heatingCircuits_hc1_currentRoomSetpoint', jsondata.value)

-- {"id":"/heatingCircuits/hc1/pumpModulation","type":"floatValue","writeable":0,"recordable":1,"value":100.0,"unitOfMeasure":"%","minValue":0.0,"maxValue":100.0}
jsondata = json.decode(km200_GetData("/heatingCircuits/hc1/pumpModulation"))
if (jsondata.value == '0.0') then
   grp.write('heatingCircuits_hc1_pumpModulation', false)
else
   grp.write('heatingCircuits_hc1_pumpModulation', true)
end

-- {"id":"/dhwCircuits/dhw1/actualTemp","type":"floatValue","writeable":0,"recordable":1,"value":57.8,"unitOfMeasure":"C"}
jsondata = json.decode(km200_GetData("/dhwCircuits/dhw1/actualTemp"))
grp.write('dhwCircuits_dhw1_actualTemp', jsondata.value)


-- {"id":"/dhwCircuits/dhw1/currentSetpoint","type":"floatValue","writeable":0,"recordable":1,"value":10.0,"unitOfMeasure":"C","minValue":0.0,"maxValue":80.0}
jsondata = json.decode(km200_GetData("/dhwCircuits/dhw1/currentSetpoint"))
grp.write('dhwCircuits_dhw1_currentSetpoint', jsondata.value)

-- {"id":"/dhwCircuits/dhw1/operationMode","type":"stringValue","writeable":1,"recordable":0,"value":"auto","allowedValues":["auto","on","off"]}
jsondata = json.decode(km200_GetData("/dhwCircuits/dhw1/operationMode"))
if (jsondata.value == 'auto') then
   grp.write('dhwCircuits_dhw1_operationMode_Auto', true)
else
   grp.write('dhwCircuits_dhw1_operationMode_Auto', false)
   if (jsondata.value == 'on') then
       grp.write('dhwCircuits_dhw1_operationManualOnOff', true)
   else
       grp.write('dhwCircuits_dhw1_operationManualOnOff', false)
   end
end

-- Change the operation mode to auto mode (it needs to be one of the allowedValues):
-- km200_SetData("/heatingCircuits/hc1/operationMode", "auto")

aes.lua
Code:
-- Copyright (C) by Yichun Zhang (agentzh)


--local asn1 = require "resty.asn1"
local ffi = require "ffi"
local ffi_new = ffi.new
local ffi_gc = ffi.gc
local ffi_str = ffi.string
local ffi_copy = ffi.copy
local C = ffi.C
local setmetatable = setmetatable
--local error = error
local type = type


local _M = { _VERSION = '0.09' }

local mt = { __index = _M }

local lib = '/usr/lib/libcrypto.so'
if not io.exists(lib) then
     lib = '/usr/lib/libcrypto.so.1.0.0'
end
ffi.load(lib, true)

ffi.cdef[[
typedef struct engine_st ENGINE;

typedef struct evp_cipher_st EVP_CIPHER;
typedef struct evp_cipher_ctx_st
{
const EVP_CIPHER *cipher;
ENGINE *engine;
int encrypt;
int buf_len;

unsigned char  oiv[16];
unsigned char  iv[16];
unsigned char buf[32];
int num;

void *app_data;
int key_len;
unsigned long flags;
void *cipher_data;
int final_used;
int block_mask;
unsigned char final[32];
} EVP_CIPHER_CTX;

typedef struct env_md_ctx_st EVP_MD_CTX;
typedef struct env_md_st EVP_MD;

const EVP_MD *EVP_md5(void);
const EVP_MD *EVP_sha(void);
const EVP_MD *EVP_sha1(void);
const EVP_MD *EVP_sha224(void);
const EVP_MD *EVP_sha256(void);
const EVP_MD *EVP_sha384(void);
const EVP_MD *EVP_sha512(void);

const EVP_CIPHER *EVP_aes_128_ecb(void);
const EVP_CIPHER *EVP_aes_128_cbc(void);
const EVP_CIPHER *EVP_aes_128_cfb1(void);
const EVP_CIPHER *EVP_aes_128_cfb8(void);
const EVP_CIPHER *EVP_aes_128_cfb128(void);
const EVP_CIPHER *EVP_aes_128_ofb(void);
const EVP_CIPHER *EVP_aes_128_ctr(void);
const EVP_CIPHER *EVP_aes_192_ecb(void);
const EVP_CIPHER *EVP_aes_192_cbc(void);
const EVP_CIPHER *EVP_aes_192_cfb1(void);
const EVP_CIPHER *EVP_aes_192_cfb8(void);
const EVP_CIPHER *EVP_aes_192_cfb128(void);
const EVP_CIPHER *EVP_aes_192_ofb(void);
const EVP_CIPHER *EVP_aes_192_ctr(void);
const EVP_CIPHER *EVP_aes_256_ecb(void);
const EVP_CIPHER *EVP_aes_256_cbc(void);
const EVP_CIPHER *EVP_aes_256_cfb1(void);
const EVP_CIPHER *EVP_aes_256_cfb8(void);
const EVP_CIPHER *EVP_aes_256_cfb128(void);
const EVP_CIPHER *EVP_aes_256_ofb(void);

void EVP_CIPHER_CTX_init(EVP_CIPHER_CTX *a);
int EVP_CIPHER_CTX_cleanup(EVP_CIPHER_CTX *a);

int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *ctx, int padding);

int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx,const EVP_CIPHER *cipher,
       ENGINE *impl, unsigned char *key, const unsigned char *iv);

int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl,
       const unsigned char *in, int inl);

int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);

int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx,const EVP_CIPHER *cipher,
       ENGINE *impl, unsigned char *key, const unsigned char *iv);

int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl,
       const unsigned char *in, int inl);

int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm, int *outl);

int EVP_BytesToKey(const EVP_CIPHER *type,const EVP_MD *md,
       const unsigned char *salt, const unsigned char *data, int datal,
       int count, unsigned char *key,unsigned char *iv);
]]

local ctx_ptr_type = ffi.typeof("EVP_CIPHER_CTX[1]")

local hash
hash = {
   md5 = C.EVP_md5(),
   sha1 = C.EVP_sha1(),
   sha224 = C.EVP_sha224(),
   sha256 = C.EVP_sha256(),
   sha384 = C.EVP_sha384(),
   sha512 = C.EVP_sha512()
}
_M.hash = hash

local cipher
cipher = function (size, _cipher)
   local _size = size or 128
   local _cipher = _cipher or "cbc"
   local func = "EVP_aes_" .. _size .. "_" .. _cipher
   if C[func] then
       return { size=_size, cipher=_cipher, method=C[func]()}
   else
       return nil
   end
end
_M.cipher = cipher

function _M.new(self, key, salt, _cipher, _hash, hash_rounds, padding)
   local encrypt_ctx = ffi_new(ctx_ptr_type)
   local decrypt_ctx = ffi_new(ctx_ptr_type)
   local _cipher = _cipher or cipher()
   local _hash = _hash or hash.md5
   local hash_rounds = hash_rounds or 1
   local padding = padding or 1
   local _cipherLength = _cipher.size/8
   local gen_key = ffi_new("unsigned char[?]",_cipherLength)
   local gen_iv = ffi_new("unsigned char[?]",_cipherLength)

   if type(_hash) == "table" then
       if not _hash.iv or #_hash.iv ~= 16 then
         return nil, "bad iv"
       end

       if _hash.method then
           local tmp_key = _hash.method(key)

           if #tmp_key ~= _cipherLength then
               return nil, "bad key length"
           end

           ffi_copy(gen_key, tmp_key, _cipherLength)

       elseif #key ~= _cipherLength then
           return nil, "bad key length"

       else
           ffi_copy(gen_key, key, _cipherLength)
       end

       ffi_copy(gen_iv, _hash.iv, 16)

   else
       if C.EVP_BytesToKey(_cipher.method, _hash, salt, key, #key,
                           hash_rounds, gen_key, gen_iv)
           ~= _cipherLength
       then
           return nil
       end
   end

   C.EVP_CIPHER_CTX_init(encrypt_ctx)
   C.EVP_CIPHER_CTX_init(decrypt_ctx)

   if C.EVP_EncryptInit_ex(encrypt_ctx, _cipher.method, nil,
     gen_key, gen_iv) == 0 or
     C.EVP_DecryptInit_ex(decrypt_ctx, _cipher.method, nil,
     gen_key, gen_iv) == 0 then
       return nil
   end

   if C.EVP_CIPHER_CTX_set_padding(encrypt_ctx, padding) == 0 then
       return nil
   end

   if C.EVP_CIPHER_CTX_set_padding(decrypt_ctx, padding) == 0 then
       return nil
   end

   ffi_gc(encrypt_ctx, C.EVP_CIPHER_CTX_cleanup)
   ffi_gc(decrypt_ctx, C.EVP_CIPHER_CTX_cleanup)

   return setmetatable({
     _encrypt_ctx = encrypt_ctx,
     _decrypt_ctx = decrypt_ctx
     }, mt)
end


function _M.encrypt(self, s)
   local s_len = #s
   local max_len = s_len + 16
   local buf = ffi_new("unsigned char[?]", max_len)
   local out_len = ffi_new("int[1]")
   local tmp_len = ffi_new("int[1]")
   local ctx = self._encrypt_ctx

   if C.EVP_EncryptInit_ex(ctx, nil, nil, nil, nil) == 0 then
       return nil
   end

   if C.EVP_EncryptUpdate(ctx, buf, out_len, s, s_len) == 0 then
       return nil
   end

   if C.EVP_EncryptFinal_ex(ctx, buf + out_len[0], tmp_len) == 0 then
       return nil
   end

   return ffi_str(buf, out_len[0] + tmp_len[0])
end


function _M.decrypt(self, s)
   local s_len = #s
   local buf = ffi_new("unsigned char[?]", s_len)
   local out_len = ffi_new("int[1]")
   local tmp_len = ffi_new("int[1]")
   local ctx = self._decrypt_ctx

   if C.EVP_DecryptInit_ex(ctx, nil, nil, nil, nil) == 0 then
     return nil
   end

   if C.EVP_DecryptUpdate(ctx, buf, out_len, s, s_len) == 0 then
     return nil
   end

   if C.EVP_DecryptFinal_ex(ctx, buf + out_len[0], tmp_len) == 0 then
       return nil
   end

   return ffi_str(buf, out_len[0] + tmp_len[0])
end


return _M
Reply


Forum Jump: