Lua - Code to encrypt / decrypt with AES 128 CBC
-
Ok, not sure how far I’ve progressed with this, but hopefully I’m getting somewhere ??
Here are the values created earlier..
local key= "\\S„ßÍ}/Ìa4!" local iv = "{¬£áæ‚2žâ3ÐÞË€ú" local payload = "0000000000001234"
I’ve put the payload value into a file..
local file = "etc/payload.txt" local outf = io.open(file, "w") outf:write(payload) outf:close()
Then created Hex values of the key and iv
local function hexlify(s) local a = {} for i=1,#s do a[i] = ('%02X'):format( s:byte( i ) ) end return table.concat(a) end print(hexlify(key)) print(hexlify(iv))
keyHEX = "5C53E2809EC39FC38D7D191E2FC38C1D61057F3421"
ivHEX = "7BC2ACC2A3C3A1C3A6E2809A32C5BEC3A233C390C39EC38BE282ACC3BA"Then I’ve taken those new hex values and accessed the command line of vera to run the following openssl command I created…
openssl enc -aes-128-cbc -nosalt -e -a -A -in etc/payload.txt -K '5C53E2809EC39FC38D7D191E2FC38C1D61057F3421' -iv '7BC2ACC2A3C3A1C3A6E2809A32C5BEC3A233C390C39EC38BE282ACC3BA' -out etc/payload2.txt
The above resulted in etc/payload2.txt being created, however it's empty and the command line returns the following messages.
hex string is too long
invalid hex iv valueOther than checking how the initial key and I’ve values are created again, is there anything else I need to look into/check ?
-
Those initial values for key and IV being binary data in strings could be a problem. Your editor may be wrecking them because it thinks they are Unicode, or they've already been wrecked along the way by other means. Both are supposed to be 16 bytes, and you've got 29 for IV and 21 for the key, so something has definitely gone wrong, and Unicode/UTF-8 encoding and decoding in the file handling is a prime suspect.
Fortunately, the IV doesn't really matter, I think. You could just use the first 16 bytes of the payload for testing. But it's supposed to be a 16-byte (128 bit) nonce, used only one time with the key (so if you encrypt something else with the same key, you should use a different IV). Maybe just generate 16 random bytes and call it good, but make sure to seed the random number generator, otherwise it generates the same 16 "random" numbers after every restart (that's a feature, actually, but you have to remember to seed in production to get away from it).
And really, probably the key doesn't matter either. You need both the key and IV to encrypt and decrypt, so it's not like you are dealing with a known key from another system like a remote API (unless they are doing this all wrong and using the same key and IV for every payload--that would be an... error). Pick 16 bytes and go to town!
patrick@drupal:/tmp$ echo "I am a secret message." > in.txt patrick@drupal:/tmp$ cat in.txt I am a secret message. # Encrypt patrick@drupal:/tmp$ openssl aes-128-cbc -e -a -in in.txt -K '9988227744aaff003388ffccee1188ff' -iv '112233445566778899aabbccddeeff00' -out out.txt patrick@drupal:/tmp$ cat out.txt MQi9QVm1/R3dvZncbX0nQeRqlf0+2oFcMuW/vp0FQ2Q= # Now decrypt patrick@drupal:/tmp$ openssl aes-128-cbc -d -a -in out.txt -K '9988227744aaff003388ffccee1188ff' -iv '112233445566778899aabbccddeeff00' I am a secret message.
-
Thanks @toggledbits
I think you’re right about the Lua/Luup editor wrecking the format of the values being generated, plus I’m perhaps not helping matters by working on various parts of the overall code separately ; so I have been copying things over.
FYI - You can see how the generated values are stored/presented differently (one image is via a print screen using LuaView and the other is writing them as new variables into Vera)
I’m going to try and do as much as I can using the Vera written values, as ultimately that’s where my target for where this code will need to eventually run..
-
OK, yeah, so big problem, you cannot store binary data in state variables. That is going to fail, 100%. State variables are character strings, and assumed to contain character data, and when they are stored, they are put through UTF-8 encoding and decoding. Your binary data is going to look like Unicode characters to the innards of Luup throwing this stuff around, and anything that isn't a valid codepoint is going to get changed to something else (and boom, data corrupted), while converting it to JSON to store on flash and back, etc.
Store everything in hex or base64.
-
What would I do without you
Ok, so I can still write to a device variable in Vera, I’d just need to encode it first , in either one of those two options. e.g like this if base64?
local function base64_encode(data) local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' return ((data:gsub('.', function(x) local r,b='',x:byte() for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end return r; end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x) if (#x < 6) then return '' end local c=0 for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end return b:sub(c+1,c+1) end)..({ '', '==', '=' })[#data%3+1]) end local key = string.char(unpack(key_vals)) - - local keyEnc = base64_encode(key) luup.variable_set("urn:nodecentral-net:serviceId:NodeCentral1","key",keyEnc, 1187)
-
It's easier than that in Luup...
local mime = require("mime") local encoded = mime.b64( "Hello world!" ); local decoded = mime.unb64( encoded ); luup.log( decoded, 2 )
-
Awesome, thanks - I often forget what’s already there in luup.. !
Also am I right in saying that i only need to enc/dec in hex or base64 if I’m planning to store (read/write) those values/variables, if I can keep them ‘active; and flowing within the code itself (in memory) then, I’m ok ?
-
Yes, in memory is fine. So for example, you may generate IV from random numbers byte by byte, put it through
b64
orhex
before storing it on a state variable. Foropenssl
, you'll be giving key and IV in hex, so that seems a sensible way to keep it. Once it's converted to hex, you'll never need to reverse that, since it can be stored in a state variable as hex, and can be handed directly toopenssl
as hex. -
This time round, I seem to have generated some new hex values this time, which I’ve used in a io.popen openssl command, feels a bit better, but sadly not quite yet right (yet).. FYI
openssl enc -aes-128-cbc -nosalt -e -a -A -in etc/payload.txt -K 'enNIQi9wNmYxd1RDUXMvNHJhdHNGZz09' -iv 'B6B1919A88C696AE87A692B1BBADCE9B' -out etc/payload2.txt non-hex digit invalid hex key value
-
Well the key you show is definitely not hex, because 16 bytes of hex should be 32 characters long with only 0-9, A-F. Looks like you have base64 for the key, not hex.
-
Progress at last ! well I’ve at least got the key and iv in the correct hex format for the OpenSSL aes cbc encryption command to work.🥳
Which leads me on to the next part, where the output of the above is referred to as the ciphertext;, of which I now need to do the following with..
ciphertext = MyopensslAEScbcCall() sig = encdec.hmacsha256(ciphertext, hmac_key, true) encrypted_payload = encdec.base64enc(ciphertext .. sig) return encrypted_payload
hmacsha256
seems to be next, and it might also be something I could do with OpenSSL too ? -> https://unix.stackexchange.com/questions/610039/how-to-do-hmacsha256-using-openssl-from-terminal ?I also found this too - https://github.com/jqqqi/Lua-HMAC-SHA256/blob/master/sha256.lua
@toggledbits any suggestions/recommendations on what to do with this next part ?
-
toggledbitswrote on Jan 10, 2022, 3:20 AM last edited by toggledbits Jan 9, 2022, 10:23 PM
I would stick with OpenSSL. You just need to assemble the correct bits (HMAC key and ciphertext) to send to the hash function, compute the hash, then append the hash to the ciphertext. The trick here will be that you currently have the AES encyption outputting its result base64-encoded... you can't append the signature to that. You need the ciphertext output in binary for that, so you need to remove the
-a
(and-A
) to let the output be binary. Then you can put that into the hash. And then you can take the (binary) hash output and append that to the binary ciphertext, and then you base64 encode the assembled result at the very end. The whole thing takes a few steps. I would recommend writing a shell script to do all the steps, and just have Lua run the shell script. You can then more easily develop and test the steps (because they are all in the shell script and runnable from the command line). -
Thanks @toggledbits - I struggle enough with Lua, so i admire your optimism in thinking I could create a shell script too
I must admit, I can’t help but feel like I’m making this more complicated that it needs to be, especially as there are working Lua and Python scripts already out there, which seem to be doing everything natively (although I do admit much of this thread is trying to plug holes in Vera
)
With that said, the following might help you understand the bigger picture of what I’m trying to replicate, and maybe help me more, as I found some related posts, the first one which uses python e.g. https://github.com/florianholzapfel/panasonic-viera/issues/9 - and I’ve extracted the related HMAC part below.
… # Let's encrypt it with AES-CBC! We need to make sure we pad it to a multiple of 16 bytes beforehand aes = AES.new(key, AES.MODE_CBC, iv) ciphertext = aes.encrypt(pad(payload)) # Calculate the HMAC-SHA-256 signature of our encrypted payload sig = hmac.new(hmac_key, ciphertext, hashlib.sha256).digest() # Concatenate the HMAC signature to the encrypted payload and base64 encode it, and we're done! encrypted_payload = base64.b64encode(ciphertext + sig)
Plus there’s this pure Lua version called Haslib -> https://github.com/howmanysmall/Rewrites/blob/master/src/HashLib.lua - which looks like it might be promising to add to Vera as a module ?
USAGE: Input data should be a string Result (SHA digest) is returned in hexadecimal representation as a string of lowercase hex digits. Simplest usage example: local HashLib = require(script.HashLib) local your_hash = HashLib.sha256("your string")
Looking at the functions within the latter HashLib.lua file, it has one called
local function hmac(hash_func, key, message)
which makes me wonder could i use that and do the following.. ?local HashLib = require(script.HashLib) local mySig = HashLib.hmac(sha256, ciphertext, hmac_key)
-
toggledbitswrote on Jan 10, 2022, 2:04 PM last edited by toggledbits Jan 10, 2022, 1:28 PM
Approach however you want, it's yours to choose, of course. I can just recommend (and you asked). My experience is that snatching random code snippets from Github can be hit or miss, and if it's a miss, it will take longer to find because of your implicit trust of "working" code by others. OpenSSL is widely-used and well-known. It is, for practical purposes, a reference standard. If you find "good code" on Github and it turns out it isn't, it will likely be a long time before you figure it out, because you'll trust it, even though you should not. And in this scenario, with so much computation, and in so many steps, it's all a black box and debugging any piece of it will be extremely challenging. You'll post here when it doesn't work and I and anyone else won't be able to help you, because we can verify it either. Using a known-good tool, even if you struggle to get to know it, will give you a more reliable result, because then you are only fighting with your handling of the inputs and outputs and getting the steps right and in the right order. You should use the best tools possible to minimize the number of variables in the equation. Once you know how it all works, you see it all hang together, and you've got some "sand in your pants" as they say, then you can start replacing with other things, if necessary.
The other issue is that no Lua implementation, of crypto in particular, which can be very complex and iterative, is going to be more efficient than the C/C++-based, optimized implementation of OpenSSL. That matters on the weak CPUs we are working with.
At a minimum, you should get your procedure working on the command line, with all the steps and a full cycle of encode and decode with a couple of different payloads. Once you have that, you have something (known inputs and known outputs/results) you can use to test Lua replacements, because you have known benchmarks to compare to. If you don't know what a particular input will produce on the output, you will never know if any piece of code you find is correct or not.
-
Thanks so much @toggledbits for your open and honest feedback, I really appreciate it. Taking your advice I’m going to persist with OpenSSL to get this next encrypted payload step done.
How does this set of next steps look to you ..?
Step 1, Adjust the OpenSSL ciphertext aes-cbc command, so it outputs in binary (do this by removing the -a (and -A) parameters ).
Step 2, When it comes to the hmac_key , it looks like it created in the following way, and as I can’t see any hex or base64 references, onlybit
- is it already using the right binary format ?local bit = require("bit") local binascii = require("binascii") function createHMACKey() local iv_vals = { iv:byte(1, -1) } local hmac_key_mask = binascii.unhexlify('15C95AC2B08AA7EB4E228F811E34D04FA54BA7DCAC9879FA8ACDA3FC244F3854') local hmac_key_mask_vals = { hmac_key_mask:byte(1, -1) } local hmac_vals = {} for i = 1, 32, 4 do hmac_vals[ i ] = bit.bxor(hmac_key_mask_vals[ i ], iv_vals[ bit.band(i + 1, 0xF) + 1 ]) hmac_vals[ i + 1 ] = bit.bxor(hmac_key_mask_vals[ i + 1 ], iv_vals[ bit.band(i + 2, 0xF) + 1 ]) hmac_vals[ i + 2 ] = bit.bxor(hmac_key_mask_vals[ i + 2 ], iv_vals[ bit.band(i - 1, 0xF) + 1 ]) hmac_vals[ i + 3 ] = bit.bxor(hmac_key_mask_vals[ i + 3 ], iv_vals[ bit.band(i, 0xF) + 1 ]) end local hmac_key = string.char(unpack(hmac_vals)) print(hmac_key) return hmac_key end createHMACKey()
Step 3. Use openSSL to encrypt the binary ciphertext and binary hmac_key with hmacsha256 to create the
sig
(signature)
Step 4. Then using mime.base64, encrypt the ciphertext with the new singnature to create thepayload
-
Hi @toggledbits
If/when you have time , I’d really appreciate some further help..
Quick reminder of my current goal, which is to convert the following 3 steps, so an equivalent can run on Vera.
One..
aes_cbc, err = aes:new(key, nil, aes.cipher(128, 'cbc'), { iv = iv }, nil, 1) ciphertext = aes_cbc:encrypt(payload)
Two..
sig = encdec.hmacsha256(ciphertext, hmac_key, true)
Three..
encrypted_payload = encdec.base64enc(ciphertext .. sig)
@toggledbits said in Lua - Code to encrypt / decrypt with AES 128 CBC:
You just need to assemble the correct bits (HMAC key and ciphertext) to send to the hash function, compute the hash, then append the hash to the ciphertext.
You make it sound so easy, I’d love to get this to work..
The trick here will be that you currently have the AES encyption outputting its result base64-encoded...
Looking at the guide (https://linux.die.net/man/1/openssl ) I’m trying to see how the command results in a the output being base64 encoded ?
opensslAES128command = "openssl enc -aes-128-cbc -nosalt -e -a -A -in etc/payload.txt -K "..key.." -iv "..iv.." -out etc/payload2.txt"
The above command output this....
gGBjfMi+lMGhQYdhjy1UWuRCtY1WIE07CmQ5aW/jITDYbe8I01QxtYfXzkpk4km8
you can't append the signature to that. You need the ciphertext output in binary for that, so you need to remove the
-a
(and-A
) to let the output be binary.I also tried the above command, without both the -a -A options and it returns this…
€`c|Ⱦ”Á¡A‡a-TZäBµV M; d9ioã!0ØmïÓT1µ‡×ÎJdâI¼
is this binary ?
Then you can put that into the hash.
Next when you say
hash
do you mean the second part, the encdec.hmacsha256 piece ? If so what exactly am I hashing ? Are (ciphertext, hmac_key, true) all concatenation into one long string ? Like this maybe ?openssl dgst -sha256 -hmac $(<etc/my ciphertext,-hmac_key.txt) etc/hmacoutput.txt
And then you can take the (binary) hash output and append that to the binary ciphertext, and then you base64 encode the assembled result at the very end.
With mime.b64 I think, I’m good on the last step ..
The whole thing takes a few steps.
Totally agree I just need to ensure I have right ones ?
-
I’m still persevering with this one, but something’s not quite right, so hopefully the following with help people help me..
- I send a request to the TV to obtain a PIN Code, which it presents on the screen e.g 1234
- It also provides me with a Challenge Key.
--[[ challenge key returned will be something like this.. <?xml version="1.0" encoding="utf-8"?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <s:Body> <u:X_DisplayPinCodeResponse xmlns:u="urn:panasonic-com:service:p00NetworkControl:1"> <X_ChallengeKey>iL9XqQOMfkFWz2rvh0Xm+w==</X_ChallengeKey> </u:X_DisplayPinCodeResponse> </s:Body> </s:Envelope>
I extract the challenge key and store it, and to give me options, I also store a base64 and a Hex version too.
challenge_Key = string.match(pairing_res,'<X_ChallengeKey>(.*)</X_ChallengeKey>') luup.variable_set(PANI_SERV,"challenge_Key",challenge_Key,DEVICE) local iv_b64 = mime.b64(challenge_Key) luup.variable_set(PANI_SERV,"iv_b64",iv_b64,DEVICE) local iv_HEX = binascii.hexlify(challenge_Key) luup.variable_set(PANI_SERV,"iv_HEX",iv_HEX,DEVICE)
Which gives me..
challenge_Key = 6Sj5RAitzqplQ860TviWLw== challenge_Key_b64 = NlNqNVJBaXR6cXBsUTg2MFR2aVdMdz09 challenge_Key_HEX = 36536A35524169747A71706C51383630547669574C773D3D
So far so good, and going forward base64 version will be known as IV.
Next step is to take the IV and create a KEY and a HMAC_KEY, which I do the following way..
1st the KEY
local iv = luup.variable_get(PANI_SERV,"iv", DEVICE) local iv_vals = { iv:byte(1, -1) } local key_vals = {} for i = 1, 16, 4 do key_vals[ i ] = bit.band(bit.bnot(iv_vals[ i + 3 ]), 0xFF) key_vals[ i + 1 ] = bit.band(bit.bnot(iv_vals[ i + 2 ]), 0xFF) key_vals[ i + 2 ] = bit.band(bit.bnot(iv_vals[ i + 1 ]), 0xFF) key_vals[ i + 3 ] = bit.band(bit.bnot(iv_vals[ i ]), 0xFF) end local key = string.char(unpack(key_vals)) luup.variable_set(PANI_SERV,"key",key,DEVICE)
And then the HMAC_KEY
local hmac_key_mask = binascii.unhexlify('15C95AC2B08AA7EB4E228F811E34D04FA54BA7DCAC9879FA8ACDA3FC244F3854') local hmac_key_mask_vals = { hmac_key_mask:byte(1, -1) } local hmac_vals = {} for i = 1, 32, 4 do hmac_vals[ i ] = bit.bxor(hmac_key_mask_vals[ i ], iv_vals[ bit.band(i + 1, 0xF) + 1 ]) hmac_vals[ i + 1 ] = bit.bxor(hmac_key_mask_vals[ i + 1 ], iv_vals[ bit.band(i + 2, 0xF) + 1 ]) hmac_vals[ i + 2 ] = bit.bxor(hmac_key_mask_vals[ i + 2 ], iv_vals[ bit.band(i - 1, 0xF) + 1 ]) hmac_vals[ i + 3 ] = bit.bxor(hmac_key_mask_vals[ i + 3 ], iv_vals[ bit.band(i, 0xF) + 1 ]) end local hmac_key = string.char(unpack(hmac_vals)) luup.variable_set(PANI_SERV,"hmac_key",hmac_key,DEVICE)
This then gives me the following..
Key = Ž±“±½µ©±É§žŒ½§œ key_HEX = 8EB193B1BDB5A9B1C9ADA79E8CBDA79C hmac_key = [¸®úÈé½îÙ\G³ë:é°æÚ7¬Øû¤f<[ hmac_key_HEX = 5BB814AEFAC8E9BD1C14EED95C47B317EB3AE9B0E6DA37ACD8FBC2A4663C5B0C
Once I have these available I then need to create the payload which these will be used to help encrypt etc.
The payload creation process is uses the PIN number presented at the very start.local data = "<X_PinCode>5852</X_PinCode>" local payload = '000000000000' local n = #data payload = payload .. string.char(bit.band(bit.rshift(n, 24), 0xFF)) payload = payload .. string.char(bit.band(bit.rshift(n, 16), 0xFF)) payload = payload .. string.char(bit.band(bit.rshift(n, 8), 0xFF)) payload = payload .. string.char(bit.band(n, 0xFF)) payload = payload .. data luup.variable_set(PANI_SERV,"payload",payload,DEVICE)
Which returns this.
payload = 0000000000005852
The modules I have in use are as follows..
local bit = require("bit") local mime = require("mime") local binascii = require("binascii")
Does all of the above look correct?
These are important parts to take this into the openSSL requests earlier in this thread, which so far (used as HEX) are throwing errors (invalid/too long)
-
Calling the likes of @toggledbits , @akbooer , @therealdb to help check my logic below, as I’ve been trying to get this to work for a while now, and feel I need to step back and just check some of the basics are correct, before I even get into the ciphers/encryption part..
Here goes, using a http command , I can get a random PIN number (e.g. 1234) and the following XML response back from the TV, as it’s the
Challenge_key
I need first, I extract that..<?xml version="1.0" encoding="utf-8"?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <s:Body> <u:X_DisplayPinCodeResponse xmlns:u="urn:panasonic-com:service:p00NetworkControl:1"> <X_ChallengeKey>iL9XqQOMfkFWz2rvh0Xm+w==</X_ChallengeKey> </u:X_DisplayPinCodeResponse> </s:Body> </s:Envelope>
Guidance online suggests I then need to encode that in Base64, which would be this..
local mime = require("mime") local challenge_key = "iL9XqQOMfkFWz2rvh0Xm+w==" local challenge_Key_b64 = mime.b64(challenge_key) print (challenge_Key_b64)
Which gives me this..
aUw5WHFRT01ma0ZXejJydmgwWG0rdz09
To then get the
key
which I assume is the real encryption key, it looks like I would do the following…local bit = require("bit") local challenge_Key_b64 = "aUw5WHFRT01ma0ZXejJydmgwWG0rdz09" local challengekey_vals = { challenge_Key_b64:byte(1, -1) } local key_vals = {} for i = 1, 16, 4 do key_vals[ i ] = bit.band(bit.bnot(challengekey_vals[ i + 3 ]), 0xFF) key_vals[ i + 1 ] = bit.band(bit.bnot(challengekey_vals[ i + 2 ]), 0xFF) key_vals[ i + 2 ] = bit.band(bit.bnot(challengekey_vals[ i + 1 ]), 0xFF) key_vals[ i + 3 ] = bit.band(bit.bnot(challengekey_vals[ i ]), 0xFF) end local key = string.char(unpack(key_vals)) print(key)
Which give me this..
ʈªž¹·¨’ÎÏ«§¥Ïž
I’m conscious at this point that I’m using a web based Lua IDE, so I’m not sure if the values returned are accurate, plus @toggledbits has recommended HEX which sounds ideal, but for now I just want to be sure I’m working through it correctly?
Continuing on, next step is to get the
hmac_val
- and it looks like I need to use the samechallengekey_vals
to generate thehmac_key
local bit = require("bit") local binascii = require("binascii") local challengekey_64 = "aUw5WHFRT01ma0ZXejJydmgwWG0rdz09" local challengekey_vals = { challengekey_64:byte(1, -1) } local hmac_key_mask = binascii.unhexlify('15C95AC2B08AA7EB4E228F811E34D04FA54BA7DCAC9879FA8ACDA3FC244F3854') local hmac_key_mask_vals = { hmac_key_mask:byte(1, -1) } local hmac_vals = {} for i = 1, 32, 4 do hmac_vals[ i ] = bit.bxor(hmac_key_mask_vals[ i ], challengekey_vals[ bit.band(i + 1, 0xF) + 1 ]) hmac_vals[ i + 1 ] = bit.bxor(hmac_key_mask_vals[ i + 1 ], challengekey_vals[ bit.band(i + 2, 0xF) + 1 ]) hmac_vals[ i + 2 ] = bit.bxor(hmac_key_mask_vals[ i + 2 ], challengekey_vals[ bit.band(i - 1, 0xF) + 1 ]) hmac_vals[ i + 3 ] = bit.bxor(hmac_key_mask_vals[ i + 3 ], challengekey_vals[ bit.band(i, 0xF) + 1 ]) end local hmac_key = string.char(unpack(hmac_vals)) print(hmac_key)
Which give me this…
bü;—öØð£OÛ±Dl±Ò~ƉêÊ.²» ÷Ì~Yd
And then I think to get the IV, i would do the following.
local bit = require("bit") local payload = '000000000000' -- First 12 bytes are randomised local pincode = "<X_PinCode>1234</X_PinCode>" -- Next 4 bytes are from the pincode prompted by the TV n = #pincode payload = payload .. string.char(bit.band(bit.rshift(n, 24), 0xFF)) payload = payload .. string.char(bit.band(bit.rshift(n, 16), 0xFF)) payload = payload .. string.char(bit.band(bit.rshift(n, 8), 0xFF)) payload = payload .. string.char(bit.band(n, 0xFF)) payload = payload .. pincode local iv = payload print(iv)
Which returns this ..
0000000000001234
Does all of the above look correct to create the 3 items needed below.. ?
Key = ʈªž¹·¨’ÎÏ«§¥Ïž HMAC_KEY = bü;—öØð£OÛ±Dl±Ò~ƉêÊ.²» ÷Ì~Yd IV = 0000000000001234
-
@parkerc said in Lua - Code to encrypt / decrypt with AES 128 CBC:
Guidance online suggests I then need to encode that in Base64, which would be this..
Looks to me that iL9XqQOMfkFWz2rvh0Xm+w== is already in base64, and that would make sense, given that's been sent to you via HTTP.
-
Thanks @akbooer - you’re so right i cant believe i missed that, I’m supposed to decode, not encode ! Doh !!
FYI - Here’s the source.. https://github.com/florianholzapfel/panasonic-viera/issues/9#issuecomment-476919658
How did the rest look ? Here’s my Lua inline with the source.
-- import binascii -- import base64 -- import hmac, hashlib -- from Crypto.Cipher import AES local bit = require("bit") local mime = require("mime") local binascii = require("binascii") -- # Example challenge (which is our IV) -- iv = base64.b64decode("mUQdS7/RyJTMsiojPz9i1Q==") local challenge_key = "iL9XqQOMfkFWz2rvh0Xm+w==" local challenge_Key_unb64 = mime.unb64(challenge_key) print (challenge_Key_unb64) -- # Get character codes from IV bytes -- iv_vals = [ord(c) for c in iv] -- # Initialise key character codes array -- key_vals = [0] * 16 -- # Derive key from IV -- i = 0 -- while i < 16: -- key_vals[i] = ~iv_vals[i + 3] & 0xFF -- key_vals[i + 1] = ~iv_vals[i + 2] & 0xFF -- key_vals[i + 2] = ~iv_vals[i + 1] & 0xFF -- key_vals[i + 3] = ~iv_vals[i] & 0xFF -- i += 4 -- # Convert our key character codes to bytes -- key = ''.join(chr(c) for c in key_vals) local challenge_Key_unb64 = "ˆ¿W©Œ~AVÏjï‡Eæû" local challengekey_vals = { challenge_Key_unb64:byte(1, -1) } local key_vals = {} for i = 1, 16, 4 do key_vals[ i ] = bit.band(bit.bnot(challengekey_vals[ i + 3 ]), 0xFF) key_vals[ i + 1 ] = bit.band(bit.bnot(challengekey_vals[ i + 2 ]), 0xFF) key_vals[ i + 2 ] = bit.band(bit.bnot(challengekey_vals[ i + 1 ]), 0xFF) key_vals[ i + 3 ] = bit.band(bit.bnot(challengekey_vals[ i ]), 0xFF) end local key = string.char(unpack(key_vals)) print(key) -- "V¨@w¾sü•0©ºx " -- # Initialise HMAC key mask (taken from libtvconnect.so) -- hmac_key_mask_vals = [ord(c) for c in binascii.unhexlify("15C95AC2B08AA7EB4E228F811E34D04FA54BA7DCAC9879FA8ACDA3FC244F3854")] -- # Initialise HMAC key character codes array -- hmac_vals = [0] * 32 -- # Calculate HMAC key using HMAC key mask and IV -- i = 0 -- while i < 32: -- hmac_vals[i] = hmac_key_mask_vals[i] ^ iv_vals[(i + 2) & 0xF] -- hmac_vals[i + 1] = hmac_key_mask_vals[i + 1] ^ iv_vals[(i + 3) & 0xF] -- hmac_vals[i + 2] = hmac_key_mask_vals[i + 2] ^ iv_vals[i & 0xF] -- hmac_vals[i + 3] = hmac_key_mask_vals[i + 3] ^ iv_vals[(i + 1) & 0xF] -- i += 4 -- # Convert our HMAC key character codes to bytes -- hmac_key = ''.join(chr(c) for c in hmac_vals) local challenge_Key_unb64 = "ˆ¿W©Œ~AVÏjï‡Eæû" local challengekey_vals = { challenge_Key_unb64:byte(1, -1) } local hmac_key_mask = binascii.unhexlify('15C95AC2B08AA7EB4E228F811E34D04FA54BA7DCAC9879FA8ACDA3FC244F3854') local hmac_key_mask_vals = { hmac_key_mask:byte(1, -1) } local hmac_vals = {} for i = 1, 32, 4 do hmac_vals[i] = bit.bxor(hmac_key_mask_vals[ i ], challengekey_vals[ bit.band(i + 1, 0xF) + 1 ]) hmac_vals[i+1] = bit.bxor(hmac_key_mask_vals[ i + 1 ], challengekey_vals[ bit.band(i + 2, 0xF) + 1 ]) hmac_vals[i+2] = bit.bxor(hmac_key_mask_vals[ i + 2 ], challengekey_vals[ bit.band(i - 1, 0xF) + 1 ]) hmac_vals[i+3] = bit.bxor(hmac_key_mask_vals[ i + 3 ], challengekey_vals[ bit.band(i, 0xF) + 1 ]) end local hmac_key = string.char(unpack(hmac_vals)) print(hmac_key) --"B`Ò}Îˤg$ÍÙNøÏWòâ/cÒÙzvà"õ3´¿"" -- # This is our plaintext SOAP argument for the pin code shown on the TV -- authinfo = "<X_PinCode>4410</X_PinCode>" -- # First 12 bytes are randomised, let's just set them to 0 because it doesn't matter -- payload = "000000000000" -- # The next 4 bytes contain the plaintext (SOAP arg) length in big endian -- n = len(authinfo) -- payload += chr(n >> 24) -- payload += chr((n >> 16) & 0xFF) -- payload += chr((n >> 8) & 0xFF) -- payload += chr(n & 0xFF) -- # Now we concatenate our payload, which is starting at byte 17 of the payload -- payload += authinfo local payload = '000000000000' -- First 12 bytes are randomised local pincode = "<X_PinCode>1234</X_PinCode>" -- Next 4 bytes are from the pincode prompted by the TV n = #pincode payload = payload .. string.char(bit.band(bit.rshift(n, 24), 0xFF)) payload = payload .. string.char(bit.band(bit.rshift(n, 16), 0xFF)) payload = payload .. string.char(bit.band(bit.rshift(n, 8), 0xFF)) payload = payload .. string.char(bit.band(n, 0xFF)) payload = payload .. pincode local iv = payload print(iv) -- "0000000000001234" -- # Let's encrypt it with AES-CBC! We need to make sure we pad it to a multiple of 16 bytes beforehand -- aes = AES.new(key, AES.MODE_CBC, iv) -- ciphertext = aes.encrypt(pad(payload)) -- # Calculate the HMAC-SHA-256 signature of our encrypted payload -- sig = hmac.new(hmac_key, ciphertext, hashlib.sha256).digest() -- # Concatenate the HMAC signature to the encrypted payload and base64 encode it, and we're done! -- encrypted_payload = base64.b64encode(ciphertext + sig)
Still the AES/CBC bit to get working, but hopefully this is progress..