29.04.2019, 18:46
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.
Enjoy.
Michel.
KM200.lua:
aes.lua
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.
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