Mon E.Leclerc (com.infomil.bill)

About

Mon E.Leclerc is both Android and iOS app developed by Infomil, a company based in Toulouse that handles all of E.Leclerc’s IT needs (POS, connectivity, hosting, digital signage…).

The application is a Xamarin app.

API basics

Environments

Requests

Almost all requests use the POST method, which isn’t very RESTful, but it’s partially justified by the fact that every request includes authentication data. Even the most basic requests require a minimum amount of parameters:

{
    "CRC": 0,
    "DATE_REQUETE": "2020-09-25T20:01:38.176Z",
    "NO_VERIFICATION": "sHPMdfgY9tSfRgcGPWXulGNIA5teM4wFsZi8cF2nx2nBupITJqz1EtV1HP5uhH/q6/wjyTe/21XmmbBg9lFxky=="
}

For some reason (possibly for making debugging easier ?) the Android application adds two query parameters to each request:

These two parameters are not (yet) required so can be dropped.

Responses

Most responses have the following structure:

{
    "CR": 0,
    "CRC": 868374834,
    "DONNEES": {}
}

CRCs

In both requests and responses you may come across a CRC parameter, contrary to popular belief, this is not used for error detection (in this particular API), but for caching and saving bandwidth: standard HTTP caching doesn’t work when every request is a POST request you see… It also appears that the client app doesn’t know how to calculate a CRC, it’s something only the server can do with some mathematical magic.

The CRC is sent in requests to prevent the server from sending back data that the client already has. Let’s say I have already requested a list of shops, and the CRC is 876543210, the app saves the data and associated CRC in an internal cache system. Whenever the app wants to display the list of shops, it first fires off a request containing the known CRC, and there are two possibilities:

If no CRC is known (first request) or inappropriate, then you can just provide the value 0.

Request signatures

Alongside the CRC parameter, most requests require DATE_REQUETE and NO_VERIFICATION parameters: this appears to be an attempt at ensuring the request originates from a genuine client: their app.

If the user is unauthenticated, then it’s just a Base64 representation of an SHA512 hash of the current date and time in a YmdHisv format, for example: TODO

If the user is authenticated, then it’s just a Base64 representation of an SHA512 hash of the user’s fidelity card number and the current date and time concatenated together !

Which gives us something like this in PHP:

function generateNoVerification(DateTime $dateTime, ?string $cardNumber = null) {
    $string = $dateTime->format("YmdHisv");

    if (!empty($cardNumber)) {
        $string = "{$cardNumber}{$string}";
    }

    return base64_encode(hash("sha512", $string, true));
}

Authentication

Device authentication

To authenticate your device, the app requires the user to first choose a shop. A list of shops is provided by a simple POST /Authentification/RecupererMagasinsPublic, the response looks something like this:

{
    "CR": 0,
    "CRC": 868526804,
    "DONNEES": [
        {
            "ADRESSE": "46 AV FRANKLIN ROOSEVELT",
            "CODE_POSTAL": "06110",
            "DEPARTEMENT": "06",
            "LATITUDE": 43.57246,
            "LONGITUDE": 7.000745,
            "NOM_MAGASIN": "LE CANNET ROCHEVILLE",
            "NO_MAGASIN": "0033",
            "VILLE": "LE CANNET ROCHEVILLE"
        },
        {
            ...
        },
        {
            "ADRESSE": "ZAC DES BATERSES",
            "CODE_POSTAL": "01700",
            "DEPARTEMENT": "01",
            "LATITUDE": 45.82461,
            "LONGITUDE": 4.997535,
            "NOM_MAGASIN": "BEYNOST",
            "NO_MAGASIN": "0101",
            "VILLE": "BEYNOST"
        }
    ]
}

All you need to keep handy is the NO_MAGASIN of your chosen shop.

The device authentication endpoint (POST /Authentification/Authentifier) uses encryption in an attempt to hide what’s really being sent to the server and also guarantee that the request is coming from their app, of course, there’s a way around that… The top-secret encryption key is stored in a Base64 encoded format in the app’s code: NEY0OUM4OEM2RkU4NDQx9zg3NjJFOEIx, which gives us 4F49C88C6FE8441÷8762E8B1 once decoded.

Let’s take a look at what’s inside this encrypted blob:

{
    "NO_MACHINE": "f0867762-b1a3-4515-9fd3-742ad8295751",
    "CODE_SYSTEME": "A",
    "CODE_MODELE": "JAT-L29",
    "NO_VERSION_SYSTEME": "9",
    "NO_VERSION_CLIENT": "5.0.1",
    "NO_MAGASIN": "0033",
    "NO_CARTE_FIDELITE": null,
    "LISTE_REFERENTIEL": { ... },
    "JETON": null,
    "CRC": 0
}

A random 16 byte IV is then generated, and the string representation is then encrypted with AES256 according to the decompiled application code, but it’s actually using AES192 due to the length of the key: 24 bytes. AES256 would require a 32 byte encryption key.

Pretty simple to reproduce with a bit of PHP:

$iv = random_bytes(16);
$encryptedData = openssl_encrypt(json_encode($data), "aes-192-cbc", '4F49C88C6FE8441÷8762E8B1', 0, $iv);

In the response, if successful, you will find a JETON parameter, this is your authentication token, so keep it safe and handy for future requests !

User authentication

Once your device is authenticated the next step is to let the API know who you are, get your bright orange fidelity card ready !

E.Leclerc Fidelity Card

Request: POST /MonCompte/EnregistrerCarteFidelitev2:

{
  "CRC": 0,
  "DATE": "2020-11-17T21:48:12.500Z",
  "DATE_REQUETE": "2020-11-17T21:48:12.500Z",
  "DateGenerated": "2020-11-17T21:48:12.500Z",
  "HASH": "lk2Hi[...]P1MyaPg==",
  "JETON": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "LISTE_REFERENTIEL": { ... },
  "NO_VERIFICATION": "Gh2h9[...]ujEwbg==",
  "NUMERO_CARTE_FIDELITE": "2950972719579"
}

The important parameters here are:

Calculating the HASH parameter can be performed with the following PHP function:

function generateCardHash($cardNumber, $cardPassword, $dateTime) {
    return base64_encode(hash("sha512", "{$cardNumber}{$cardPassword}" . $dateTime->format("YmdHisv"), true));
}

Generating barcodes

Request: GET /CodeBarre/Image?codeBarre=1234567890 (unauthenticated)

Response:

Generated barcode

Scanning barcodes

You can also find out some basic information about a product from it’s barcode with the following request: POST /Scan/Scanner

{
  "CODE": "3564700339398",
  "CRC": 0,
  "DATE_REQUETE": "2020-09-28T00:55:13.053Z",
  "JETON": "string",
  "NO_VERIFICATION": "string",
  "ORIGINE_CONSULTATION": 4,
  "TYPE": "EAN_13"
}

With the following parameters:

Partial response:

{
    "OBJET": {
      "DESIGNATION": "MOUCHOIRS 2 PLIS X110 CARESSE",
      "EAN": "3564700339398",
      "EST_EN_PROMO": true,
      "EST_HEYO_GRATTAGE": true,
      "IMAGE_PRODUITS": [
        {
          "URL_PRODUIT": "https://hpcs.mservices.eu/mobile/api/Photo/Obtenir?idPhoto=4118704"
        }
      ]
    }
}

Generating promotional stickers

Request: https://hpcs.mservices.eu/StickersPromo/StickerPromo.ashx?type=208&qte=1&val=30&prix=1.25&prixhp=9999.99&unit=0&cli=100&mindim=128&module=maSel

Response:

E.Leclerc sticker 1 E.Leclerc sticker 2 E.Leclerc sticker 3

Fetching photos

Most products have photos, and these can be downloaded (unauthenticated) via two URLs:

GET /Photo/ObtenirPhotoGalec?id=13534 or GET /Photo/Obtenir?idPhoto=4118700

For example, here’s a special family-pack of Tropicana (GET /Photo/ObtenirPhotoGalec?id=13530):

Orange juice

Fetching documents

Request: GET /Document/ObtenirDocument?id=1165 (no authentication required)

APKs

8.4.1

7.8.2

7.4.0

5.7.3

5.7.0

5.3.1

5.2.4

5.1.3

5.0.1