Hello all and happy new year !
I had troubles with the API system, particularly regarding the encryption system. It follows this previous locked discussion : https://www.projeqtor.org/fr/forum-fr/5-ask-questions/10767-python-connection-to-api
I have the same problem as Xav38 : the API works perfectly if I bypass the decryption system in the "if" block line 269 of api/index.php, replacing the
$data=AesCtr::decrypt ...
with a dirty
$data=$dataEncoded ;
Just for information, here is the python script :
import pycurl
from io import BytesIO
import ast
from Crypto.Cipher
import AES
import binascii
from base64 import b64encode, b64decode
import json
login="admin.api"
key="d87b5ca7c5fdf827aa35914e966bf768"
##################### Reading test###################
buffer = BytesIO()
c_getProj = pycurl.Curl()
c_getProj.setopt(c_getProj.URL, 'http://localhost/projeqtor/api/Project/all')
c_getProj.setopt(c_getProj.HTTPAUTH , pycurl.HTTPAUTH_BASIC)
c_getProj.setopt(c_getProj.USERPWD , "%s:%s"%(login,key))
#c_getProj.setopt(c_getProj.SSL_VERIFYPEER, False)
c_getProj.setopt(pycurl.WRITEDATA, buffer)
c_getProj.perform()
c_getProj.close()
body = ast.literal_eval(buffer.getvalue().decode("UTF-8"))
# Juste pour simuler une sélection
projetRetenu = [x for x in body["items"] if x["nameStatus"] == 'qualified'][0]
print (projetRetenu)
##################### Writing test###################
# Définition d’une activité à créer
activite = {"name" : "Activité de test","idActivityType" : "26","idProject" : projetRetenu["id"],"idStatus" : "1","idResource" : "3","creationDate" : "2020-12-24",'idActivityPlanningMode': '1'}
c_setAct = pycurl.Curl()
c_setAct.setopt(c_setAct.URL, 'http://localhost/projeqtor/api/Activity')
c_setAct.setopt(c_setAct.HTTPAUTH , pycurl.HTTPAUTH_BASIC)
c_setAct.setopt(c_setAct.USERPWD , "%s:%s"%(login,key))
res = json.dumps(activite)
c_setAct.setopt(c_setAct.CUSTOMREQUEST, "POST")
c_setAct.setopt(c_setAct.POSTFIELDS, res)
c_setAct.perform()
c_setAct.close()
In that case, I can see the succesful result in the logs :
===== TRACE ===== {"apiResult":"OK", "apiResultMessage":"Activité #432 inséré", "id":"432", "reference":"-TAS-40", "name":"Activité de test", "idActivityType":"26", ...
Now, regarding the encryption, I use pycryptodome and I use it with several other encrypted APIs not written in python ... and it works, so I consider this library as reliable (at least in a first approach).
Here is the writing test part of the script to test encryption / decryption (replacing previous one) :
##################### Writing test###################
# Définition d’une activité à créer
activite = {"name" : "Activité de test","idActivityType" : "26","idProject" : projetRetenu["id"],"idStatus" : "1","idResource" : "3","creationDate" : "2020-12-24",'idActivityPlanningMode': '1'}
c_setAct = pycurl.Curl()
c_setAct.setopt(c_setAct.URL, 'http://localhost/projeqtor/api/Activity')
c_setAct.setopt(c_setAct.HTTPAUTH , pycurl.HTTPAUTH_BASIC)
c_setAct.setopt(c_setAct.USERPWD , "%s:%s"%(login,key))
print("-----------------------------------")
res = json.dumps(activite)
print("Chaine json : " + res)
key_b=binascii.a2b_hex(key)
cipher = AES.new(key_b, AES.MODE_CTR, nonce=binascii.a2b_hex("CAFEBEEFCAFEBEEF")) # Forced 8 bytes nonce as this is the expected size on server side
ct_bytes = cipher.encrypt(res.encode("utf-8"))
ciphertext = b64encode(cipher.nonce + ct_bytes)
print("Texte encrypté : " + str(ciphertext))
print("Texte b64decode :" + str(b64decode(ciphertext)))
c_setAct.setopt(c_setAct.CUSTOMREQUEST, "POST")
c_setAct.setopt(c_setAct.POSTFIELDS, ciphertext)
c_setAct.perform()
c_setAct.close()
# just to test local decryption
tmp = b64decode(ciphertext)
cipher_d = AES.new(key_b, AES.MODE_CTR, nonce=tmp[:8])
msg_d = cipher_d.decrypt(tmp[8:])
print("Texte décrypté : " + str(msg_d))
The output is (I added return lines) :
-----------------------------------
Chaine json : {"name": "Activitu00e9 de test", "idActivityType": "26", "idProject": "2", "idStatus": "1", "idResource": "3", "creationDate": "2020-12-24", "idActivityPlanningMode": "1"}
Texte encrypté : b'yv6+78r+vu/Z4Ln+J0/Ke0pUDZrbrQHKtql/wxi3ZESJGDQk/fm09NN270CSpUmIsQ/cwjUT1U+5FdwSBcCreT/uNyVS13N1WqmWMtg1UjNsfjrgiD7DF5A+K6iqv9CMUeHQOheczLbtz+XQjC/ZUXCc+uVOagwrX1R2WoPjrqd1yERIOEFr8v/omdf1LIeSVeocqPP2JexfSZ957uK9tCaECluEBekBizAGU4R80Q2evlG9'
Texte b64decode :b"xcaxfexbexefxcaxfexbexefxd9xe0xb9xfe'Oxca{JTrx9axdbxadx01xcaxb6xa9x7fxc3x18xb7dDx89x184$xfdxf9xb4xf4xd3vxef@x92xa5Ix88xb1x0fxdcxc25x13xd5Oxb9x15xdcx12x05xc0xaby?xee7%Rxd7suZxa9x962xd85R3l~:xe0x88>xc3x17x90>+xa8xaaxbfxd0x8cQxe1xd0:x17x9cxccxb6xedxcfxe5xd0x8c/xd9Qpx9cxfaxe5Njx0c+_TvZx83xe3xaexa7uxc8DH8Akxf2xffxe8x99xd7xf5,x87x92Uxeax1cxa8xf3xf6%xec_Ix9fyxeexe2xbdxb4&x84n[x84x05xe9x01x8b0x06Sx84|xd1rx9exbeQxbd"
Texte décrypté : b'{"name": "Activit\u00e9 de test", "idActivityType": "26", "idProject": "2", "idStatus": "1", "idResource": "3", "creationDate": "2020-12-24", "idActivityPlanningMode": "1"}'
On the server side, the b64 decoded string is exactly the same, the nonce is properly extracted ($ctrTxt) ... but the string is not properly decrypted 🙁
I had a look to external/phpAES/aesctr.class.php ... and several points let me think there might be some implementation issue :
- The decryption process seems to be 32bits only (I am not sure of that, but regarding the magic numbers, I guess so).
- For production use, Chris Veness recommends https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto ... but it is javascript 🙁
At the end, solving this problem seems quite time consumming and maybe a workaround could be acceptable : would it be possible to have a setting to activate / deactivate encryption for the API ?
By the way, why is the GET unencrypted ?
Any help is welcome ... :/
Btw, maybe https://github.com/stymiee/php-simple-encryption could help.
data must be encrypted with AES using the API key, and default uses AES decryption with 128bits keys .
If your encryption uses other key length (256 or else), you can define $aesKeyLength in parameters.php
Hello,
I added the script to have a clearer comprehension.
The key is a 128bits, as generated for the dedicated user I created in Projeqtor (see lines 15 & 61).
Hi
I don't know python, so it's a bit hard to read for me.
But I don't understand why you encode in base 64 the encrypted data.
Here is some PHP code that works (maybe AES encrption should be changed to 128)
$fullUrl="http://myserver/api/Ticket";
$data='{"id":"1", "name":"name to be changed for Ticket 1"}';
$data=AesCtr::encrypt($data, 'ApiKeyForUserProjeqtor', 256);
$curl = curl_init($fullUrl);
curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($curl, CURLOPT_USERPWD, "projeqtor:projeqtor");
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "PUT");
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
$curl_response = curl_exec($curl);
echo $curl_response;
curl_close($curl);
Hello @babynus,
following this topic I have had an intern who developped an apiV2 using libsodium so we can have a full encryption of the data. We currently have written the php client classes and the python client classes (I am struggeling to find some time to write a C++/Qt and C# client). We fully tested it on v9.2.2 (and merged evolutions of API) and I am going to validate the upgrade in v9.2.3.
It works really well (even if some improvements will be welcome), so I was wondering if you could be interested in such a contribution? If yes, I obviously provide you all the required code, explanations and eventually some modification to better fit into the original projeqtor source code.
Yes, please share your code.
We'll have a look and may include it in community version.
Dear @babynus,
I'd appreciate if you could consider the encryption functionality. I more than once had to deal with project clients/users complaining about the existing encryption.
Most appreciated thanks,
StephanE
Hello,
In the zip, you can find enclosed :
- apiv2 folder : server side php code. sodium has to be enabled on the server (uncomment extension in php.ini)
- php client folder : client side class. Actually, as a user of the API you only uses the class ProjeqtorRequests (which itself uses DataEncryption)
- python client folder : exact same thing but in python and with an example of use (be careful, last calls delete the Activity 2891 if it exists ...)
Some improvement have to be made to be integrated properly, but it is functionnal and is a good basis of discussion for an integration process. I’d be glad to talk with you about it if you are interested.
Have a nice day,
Hi,
Thanks for sharing your code.
Ticket #5699 will deal with that point.
Hello,
Could it be more efficient to disable the encryption for sites using the https protocol? In this case, this protocol ensures a valid encryption. So far, the immediate solution we could implement is the "dirty" manipulation proposed by fsouliers (thanks!). But could it be a rapid fix to discard it, even by installation options?
In our community, Python development is a real asset, since this language is adopted by many persons. So I think it is worth achieving this feature.
Bernard
No, not as is
Because encryption with the API Key ensures that the source of data is correcly identified.
Otherwise a hacker could easily post some request to your ProjeQtOr instance and destoy your data.