Logic Machine Forum
Rijndael 128 decrypting? - Printable Version

+- Logic Machine Forum (https://forum.logicmachine.net)
+-- Forum: LogicMachine eco-system (https://forum.logicmachine.net/forumdisplay.php?fid=1)
+--- Forum: Scripting (https://forum.logicmachine.net/forumdisplay.php?fid=8)
+--- Thread: Rijndael 128 decrypting? (/showthread.php?tid=2047)



Rijndael 128 decrypting? - MichelDeLigne - 24.04.2019

Hello,

Would it be possible to add the libmcrypt library to LM?

I need to decode Rijndael 128 encrypted strings, which cannot be done in my case with the available libraries.
in some cases AES256 could be used if the key was 256bits long, but it is not the case here.

I found a proper lua library to do it, but unfortunately the required lib is missing from the system.

Here is the code I plan to use (from the same author as the one for aes from another thread).
I made a few changes to the code to hopefully make it work on the system.
Code:
--[[
    12:24 2015/9/30      lilien

]]
local ffi = require 'ffi'
local ffi_new = ffi.new
local ffi_str = ffi.string
local ffi_copy = ffi.copy
local setmetatable = setmetatable
local _M = { }
local mt = { __index = _M }

ffi.cdef[[
struct CRYPT_STREAM;
typedef struct CRYPT_STREAM *MCRYPT;

MCRYPT mcrypt_module_open(char *algorithm,
                         char *a_directory, char *mode,
                         char *m_directory);

int mcrypt_generic_init(const MCRYPT td, void *key, int lenofkey,
                       void *IV);
void free(void *ptr);
void mcrypt_free(void *ptr);

int mcrypt_enc_get_key_size(const MCRYPT td);
int mcrypt_enc_get_supported_key_sizes(const MCRYPT td, int* len);

int mcrypt_generic_deinit(const MCRYPT td);
int mcrypt_generic_end(const MCRYPT td);
int mdecrypt_generic(MCRYPT td, void *plaintext, int len);
int mcrypt_generic(MCRYPT td, void *plaintext, int len);
int mcrypt_module_close(MCRYPT td);
int mcrypt_enc_mode_has_iv(MCRYPT td);
int mcrypt_enc_get_iv_size(MCRYPT td);
int mcrypt_enc_is_block_mode(MCRYPT td);
int mcrypt_enc_get_block_size(MCRYPT td);
]]

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

_M.new = function (self)
   local cipher = 'rijndael-128'
   local mode = 'ecb'

   local c_cipher     =    ffi_new("char[?]",#cipher+1, cipher)
   local c_mode     =    ffi_new("char[4]", mode)

   local td = mcrypt.mcrypt_module_open(c_cipher, nil, c_mode, nil)
   return setmetatable( { _td = td }, mt )
end


_M.pass = function (self, key, raw,enc_or_dec)

        local dencrypt    = enc_or_dec
   local iv_len = 8
   local cipher = 'rijndael-128'
   local mode = 'ecb'

   local c_cipher     =    ffi_new("char[?]",#cipher+1, cipher)
   local c_mode     =    ffi_new("char[4]", mode)
        local td = mcrypt.mcrypt_module_open(c_cipher, nil, c_mode, nil)

        if  td ==0  then
            ngx.log(ngx.ERR , "mcrypt_module_open failed")
            return nil
        end

   local iv_key =    "1234567890123456";
        local key_len=  #key;
        local data_len=  #raw;

        local block_size, max_key_length, use_key_length, i, count, iv_size;
        --/* Checking for key-length */
        max_key_length = mcrypt.mcrypt_enc_get_key_size(td);
        if  key_len > max_key_length  then
            ngx.log(ngx.ERR , "Size of key is too large for this algorithm key_len:",key_len,",max_key:",max_key_length)
            return nil
        end

        count     =    ffi_new("int[1]")
        local key_size_tmp = mcrypt.mcrypt_enc_get_supported_key_sizes(td, count);
        local key_length_sizes = ffi.cast("int *",key_size_tmp)

        local key_s    =    nil;

        if count[0] == 0 and key_length_sizes == nil then --/* all lengths 1 - k_l_s = OK */
            use_key_length = key_len;
            key_s = ffi_new("unsigned char[?]",use_key_length,key)
        end

    if  count[0] == 1 then
        key_s = ffi_new("char[?]",key_length_sizes[0])
        ffi.fill(key_s ,use_key_length,0);
        ffi.copy(key_s, key, math.min(key_len, key_length_sizes[0]));
        use_key_length = key_length_sizes[0];
     else
        use_key_length = max_key_length;

        for i=0,count[0]-1 do
            if  key_length_sizes[i] >= key_len and    key_length_sizes[i] < use_key_length then
                use_key_length = key_length_sizes[i];
            end
        end
        key_s = ffi_new("char[?]",use_key_length)

        ffi.copy(key_s ,key, math.min(key_len,use_key_length));
    end



    if key_length_sizes~=nil then
        mcrypt.mcrypt_free(key_length_sizes);
    end

    local iv_s = nil;
    local  iv_size = mcrypt.mcrypt_enc_get_iv_size (td);

    local has_iv = mcrypt.mcrypt_enc_mode_has_iv(td) ;


    local data_size    =    0;
    local block = mcrypt.mcrypt_enc_is_block_mode(td);
    if  block == 1 then
        block_size =    mcrypt.mcrypt_enc_get_block_size(td);
        data_size = math.floor(((data_len - 1) / block_size) + 1) * block_size;

    else
        data_size = data_len;
    end


    local data_s = ffi_new("char[?]",data_size)
    ffi.fill(data_s ,data_size,0);
    ffi.copy(data_s ,raw ,data_len);

    local ini_ret = mcrypt.mcrypt_generic_init(td, key_s, use_key_length, c_iv)
    if ini_ret < 0 then
        ngx.log(ngx.ERR , "Mcrypt initialisation failed");
        ngx.say(  ini_ret,"ini_ret initialisation failed");
        return nil
    end



    if  dencrypt == 1 then
        mcrypt.mcrypt_generic(td, data_s, data_size);
    else
        mcrypt.mdecrypt_generic(td, data_s, data_size);
    end

    local ret_str = ffi_str(data_s,data_size);

    mcrypt.mcrypt_generic_end(td);


    return ret_str
end

_M.encrypt = function (self, key, raw)
    return _M.pass(self, key, raw,1);
end

_M.decrypt = function(self, key, raw)
    return _M.pass(self, key, raw,0);
end

_M.close = function(self)
   local td = self._td
   if td then
       mcrypt.mcrypt_module_close(td)
    end
end

return _M

Regards.
Michel.


RE: Rijndael 128 decrypting? - admin - 25.04.2019

Rijndael is the same as AES if 128 bit block/key is used.


RE: Rijndael 128 decrypting? - MichelDeLigne - 25.04.2019

Hi Admin,

Yes I found out that it can actually be decoded with AES256, but not with the lua aes library that was shared on this forum.
Rijndael 128 is actually AES with a 256bits key size.

There must be something wrong in aes library because it can encrypt and decrypt itself fine, but it cannot decode my strings properly, and it does not encode the same way either.
I have tried many different combinations, and it never produces the same result as you would get from other tools.

Some issues about this were opened in Github, but with not much success it seems.
I think someone rewrote part of the code to solve the problem, but I am not sure it can be implemented on LM.

The original php code does this:
Code:
$decrypt = (mcrypt_decrypt( MCRYPT_RIJNDAEL_128, km200_crypt_key_private, base64_decode($decryptData), MCRYPT_MODE_ECB, '' ) );

I came up with this code that is supposed to do the same thing:
Code:
local ctx = aes:new(km200_crypt_key_private, nil, aes.cipher(256,"ecb"))  
local res = ctx:decrypt(encdec.base64dec(decryptData))
log ('decryptedData: ', res)
But that does not give any results if I try to decode something that is known to work.
Here is an example. 
The following code should give me {"id":"/system/brand","type":"stringValue","writeable":0,"recordable":0,"value":"Buderus"}
But the result is nil.
And if you run the input and key in any online decryption tools, it works fine.

Code:
encrypteddata = '88a2296d072f83992af086fd293aefa4090acb9e6e8790844fdea4ab3a8604b5ab8871ba73614c8885ebaf434cc6bef54e8eed26f980586525a59b585e69f3fc03c8fcf28bb1648cb0b06e404a8deb719067791d14e339797174eb7dddb1a277'
key = '4aca89d6389f79303e2b58a0370cd7f7f8f1d560e45c66c0634862c184ddbaf7'

--base64 private key: SsqJ1jifeTA+K1igNwzX9/jx1WDkXGbAY0hiwYTduvc=
--hex private key: 4aca89d6389f79303e2b58a0370cd7f7f8f1d560e45c66c0634862c184ddbaf7
--base64 encrypted string: iKIpbQcvg5kq8Ib9KTrvpAkKy55uh5CET96kqzqGBLWriHG6c2FMiIXrr0NMxr71To7tJvmAWGUlpZtYXmnz/API/PKLsWSMsLBuQEqN63GQZ3kdFOM5eXF0633dsaJ3
--hex encrypted string: 88a2296d072f83992af086fd293aefa4090acb9e6e8790844fdea4ab3a8604b5ab8871ba73614c8885ebaf434cc6bef54e8eed26f980586525a59b585e69f3fc03c8fcf28bb1648cb0b06e404a8deb719067791d14e339797174eb7dddb1a277
--results: {"id":"/system/brand","type":"stringValue","writeable":0,"recordable":0,"value":"Buderus"}

function string.fromhex(str)
   return (str:gsub('..', function (cc)
       return string.char(tonumber(cc, 16))
   end))
end

function string.tohex(str)
   return (str:gsub('.', function (c)
       return string.format('%02X', string.byte(c))
   end))
end


aes = require('user.aes')
encdec = require ('encdec')

km200_crypt_key_private = string.fromhex(key)
log(encdec.base64enc(km200_crypt_key_private))

decryptData = string.fromhex(encrypteddata)
log(encdec.base64enc(decryptData))
 
local ctx = aes:new(km200_crypt_key_private, nil, aes.cipher(256,"ecb"))
local res = ctx:decrypt(decryptData)

log ('decryptData: ', res)

user.aes is the library that was shared in the thread https://forum.logicmachine.net/showthread.php?tid=1643[url=https://forum.logicmachine.net/showthread.php?tid=1643][/url]
I am open to any suggestions. Smile
Regards.
Michel.


RE: Rijndael 128 decrypting? - admin - 25.04.2019

There are two issues here that cause data errors:
1. aes.lua hashes the secret key
2. PHP uses different padding for secret key and data.

Solution:
1. Use this aes library which supports turning off padding: https://github.com/openresty/lua-resty-string/blob/d7362104c5cbccc128f8b558a1d77ad660c51848/lib/resty/aes.lua
2. Add library loading as mentioned in this post: https://forum.logicmachine.net/showthread.php?tid=1643&pid=10237#pid10237

Working example for decrypt:
Code:
function pad(str, bits)
  local bytes = bits / 8
  local rem = #str % bytes

  if rem > 0 then
    str = str .. string.rep('\0', bytes - rem)
  end

  return str
end

encdec = require('encdec')
aes = require('aes')

key = encdec.base64dec('SsqJ1jifeTA+K1igNwzX9/jx1WDkXGbAY0hiwYTduvc=')
data = encdec.base64dec('iKIpbQcvg5kq8Ib9KTrvpAkKy55uh5CET96kqzqGBLWriHG6c2FMiIXrr0NMxr71To7tJvmAWGUlpZtYXmnz/API/PKLsWSMsLBuQEqN63GQZ3kdFOM5eXF0633dsaJ3')

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

log(res)



RE: Rijndael 128 decrypting? - MichelDeLigne - 25.04.2019

Great! 
It was the hashing of the key that was the issue, and the fact that it does one round of md5 by default when you don't fill in all the parameters.

Padding was not required because the key is 256bits long (two md5 hashes concatenated), and the output is already coming from AES and as such should be the proper length. It will however come very handy when I start to send commands.

Thanks a lot for your help!  Smile