2022年7月26日 星期二

Encrypt in Delphi, Decrypt in Laravel

Delphi provided cross-platform application ability from Delphi XE2, that happened in 2011 or 2012. With the feature, all the applications created by Delphi needs to communicate with client applications or server applications created by various IDE or technologies. RESTful API is the most frequently channel.

Although Delphi deliver package bundled Indy from Delphi 2005, and Indy did provided wonderful internet protocol capability, Indy did not provide the capability for encryption and decryption. The security protocol of Indy comes with OpenSSL, which is almost best security library in the world from early 1990 era.Even OpenSSL is so great, it's still difficult to deliver OpenSSL to various operating systems. For example, iOS, Linux. 

For solving the issue about deliver SSL/TLS secure channel to iOS/Linux, Embarcadero created NET package to provide secure channel feature in FireMonkey framework since XE5 or XE6. Developers can use NET package for RESTful API communication without any overheads. What we developers need to do is to create application, package it with the request by Apple/Linux platform, no more extra library issues happen.

Howerver, it's not so easy for encryption and decryption. In FireMonkey, all secure channel features depend on the platform native solutions. In macOS, Delphi used mac safari libraries for SSL/TLS. In iOS, Delphi used iOS native framework for SSL/TLS. And Delphi used Windows/Linux native API in Windows/Linux platform. What can we do to provide encrypt/decrypt feature?

I used to work with Delphi, Xcode, Android studio for creating client application, and used to create server side applications with PHP, and Laravel/PHPStorm in recent years. I do know that Laravel framework provide strong capability, and Apache will provide standard SSL/TLS channel, but I still wish to encrypt some sensitive content in the secure channel, and I believe some of you do think so.

And I tried to pick a popular symmetric encryption, AES is my choice. As we know, all encryption support various key length and block process mode. My choice is AES-256-CBC, which means AES with 256 bits length key and CBC mode.

For this choice, I need to solve 2 issues: 

  1. Picking a encryption/decryption library which supports Delphi, and no overhead in delivery package.
  2. The created cipher should be decrypted by Laravel.

Encryption package for Delphi

I studied information security in graduated school, and my master thesis is about CA and personal certificate management in 1998. Encryption is not big deal for me. It's a huge deal for me to create all the encryption algorithms by myself. Thank God, we have so many giants on the internet, and we need to stand on giant's shoulder, rather than create wheel from zero.

For this thought, I searched the key word "AES-256-CBC" from github in recent weeks, and so many relative projects pop-up, including LockBox-2, LockBox-3, and Delphi Encryption Compendium, all 3 projects can be found in Delphi GetIt package manager GUI.

After testing LockBox-3 and Delphi Encryption Compendium. I chose Delphi Encryption Compendium (I will used DEC for it in later article) as my further test candidate because I cannot get enough example or document for LockBox-3, and I got enough sample projects in DEC for FireMonkey with Console/GUI.

As we know, Key, initial vector (iv for short in later article), and padding byte are 3 fundmental factors for symmetric encryption. And 4th factor might be the encoding format for cipher. I checked that basic encryption/decryption feature works, and then I tried to modify the parameter/format/iv generation in the DEC sample project.

First of all, the encoding format. The DEC sample project requires HEX format key and iv, and HEX is the only one format it supports. The sample project allowed us to use any custom padding byte, it's very flexible for developer, and it's good for DEC.

But it's very hard to understand for developers who are not familiar to encryption/decryption.

I had chosen Laravel as server side application, hence I just try to make the communication between DEC and Laravel at this moment.

The profile of encrypt decrypt, encryptstring, decryptstring in Laravel

With XDebug and PHPStorm, I created a test project to trace into encrypt, encryption, decrypt, decryptstring function. Both of the encrypt functions invoke the openssl_encrypt function, and both of the decrypt functions invoke the openssl_decrypt function.

The 2 functions encrypt and encryptstring are the basically identical, encryptstirng will pack the string as a JSON Payload before invoke encrypt, and the reverse sequence happened in decryptstring and decrypt.

When you invoke encrypt/encryptstring, a random iv will be created, and the length of the iv will be different with the encrypt algorithm and key length. For example, AES-256-CBC will generate 16 bytes iv, and the OpenSSL library function will generate the 16 bytes iv randomly, encode it, combine in a JSON payload, and return it.

The result will be the following JSON payload:

{

    "iv":"KSMkCBozRhR3Lxl3MF4OHg==",

    "value":"Cw025jAKF0TLcjxIJ30Vzg==",

    "mac":"e414202d1ae168315bea8d5d68a4e8bc1de22c299f1dd50940e1c8419565d710",

    "tag":""

}

In the above JSON payload, there are 4 elements:

iv: generated by OpenSSL library, and encoded by base64 format.

value: the cipher, encoded by base64 format.

mac: SHA256.hmac(iv + value).base64String // just psudo code, generate hmac by (iv+value), and encoded it with base64.

tag: empty string. 

If the payload does not match above structure, decryption will be failed. Another important factor is padding byte, padding byte MUST following a rule. The "mac" field in above structure is stand for Message Authentic Code, which is generated by SHA256 hmac, the source date is iv contact value, take above data as example, the source string is: 

Cw025jAKF0TLcjxIJ30Vzg==KSMkCBozRhR3Lxl3MF4OHg==

SHA256 hmac requires a key to protect the source, and we will use the encrypt key as the key again. Howerver, DEC does not provide hmac function, hence we need to use the System.Hash.THashSHA2 in FireMonkey. it's a class function of THashSHA2 class, you need to invoke it as following way:

    hMacSrcString := base64IV + base64Cipher;
    hMacResult := THashSHA2.GetHMAC(hMacSrcString,
          TFormat_Base64.Decode(base64Key));

Particular Padding Byte in OpenSSL

In all symmetric encryption algorithms, padding is an important issue. Including DES, 3DES, RC4, RC5, AES, padding is an issue which could not be ignored.

That's caused by the encryption algorithms used block calculation with various block process modes (ECB, CBC, OFB, CFB), all data will be divided into blocks as same size, calculated with key to generate cipher blocks, and process these cipher blocks as completed cipher.

The padding issue will happen in last block, which might be shorter or equal to the block size. If it's shorter, padding byte will be padded in the tail, to make sure the last block size matches the defined block size.

Taking AES-256-CBC as example, the key size is 256 bits (32 bytes), and both iv and block size are 16 bytes. If last block length is less than 16 bytes, we need to pad it to 16 bytes with padding byte.

I mentioned that DEC opens all parameters for developers to config, it's flexible. As we need to make DEC production be decrypted by Laravel decryptstring, we need to figure out how openssl_decrypt handle the padding issue.

I tried every combination from DEC and Laravel, and one conclusion was found: the padding byte value will change with the last block length. If we need to pad 1 byte for the last block, the padding byte should be 0x1, please refer the following table:

Plain Data length
Padding size
Padding Byte value
1 byte or N* 16 + 1 bytes15 byte0xF
2 bytes or N* 16 + 2 bytes14 bytes0xE
3 bytes or N* 16 + 3 bytes13 bytes0xD
4 bytes or N* 16 + 4 bytes12 bytes0xC
5 bytes or N* 16 + 5 bytes11 bytes0xB
6 bytes or N* 16 + 6 bytes10 bytes0xA
7 bytes or N* 16 + 7 bytes9 bytes0x9
8 bytes or N* 16 + 8 bytes8 bytes0x8
9 bytes or N* 16 + 9 bytes7 bytes0x7
10 bytes or N* 16 + 10 bytes6 bytes0x6
11 bytes or N* 16 + 11 bytes5 bytes0x5
12 bytes or N* 16 + 12 bytes4 bytes0x4
13 bytes or N* 16 + 13 bytes3 bytes0x3
14 bytes or N* 16 + 14 bytes2 bytes0x2
15 bytes or N* 16 + 15 bytes1 bytes0x1

If you did not follow the rule in above table, the openssl_decrypt will return failure.

Conclusion

It's very frequently to use Delphi client to communicate with other server technologies. I share this article to make sure every Delphi developer not take time to find a FireMonkey way to do so.

Laravel or any other server side technologies adopt OpenSSL very frequently, so if you check this article out, and use the sample with your Delphi project, it will be very useful.

The sample code is uploaded to github, you can use it as free, I shared the project with Apache License 2.0.


沒有留言:

張貼留言