Whats'app Web et son QRCode

 druide

Whats'app Web

Pour pouvoir utiliser whats'app à l'aide de son navigateur web préféré, il faut se rendre sur leur site web et scanner un QRCode à l'aide de l'application du même nom. Mais que contient ce QRCode ?



Il contient 3 valeurs encodées en base 64, séparées par des virgules :


@CqkaWzA6Am/6BJ8Kdwi42W/Nw21cJrK8URXocOiRGbaC0VXiF2ubKhJv,CkqrBtPPUNQNe4b3H6HS8v/J4sLEYu/OZuL0IpzmkAk=,hmPPBhrGJQU4tyrCIZOJQg==
La première valeur @CqkaWzA6Am/6BJ8Kdwi42W/Nw21cJrK8URXocOiRGbaC0VXiF2ubKhJv semble être un identifiant unique

$ echo -n CqkaWzA6Am/6BJ8Kdwi42W/Nw21cJrK8URXocOiRGbaC0VXiF2ubKhJv | base64 -d | hd
00000000  0a a9 1a 5b 30 3a 02 6f  fa 04 9f 0a 77 08 b8 d9  |...[0:.o....w...|
00000010  6f cd c3 6d 5c 26 b2 bc  51 15 e8 70 e8 91 19 b6  |o..m\&..Q..p....|
00000020  82 d1 55 e2 17 6b 9b 2a  12 6f                    |..U..k.*.o|
0000002a
La seconde valeur

La seconde valeur est une clé publique générée à l'aide d'une clé privée selon une courbe elliptique x25519. Pour trouver cette réponse, j'ai du plonger dans le code javascript reçu avec la page web. En effet, c'est le code javascript qui génère le QRCode.

Pour faire ce travail, j'ai utilisé Chrome car il possède une fonctionnalité très utile symbolisée par cette icône en bas à gauche de la fenêtre de code javascript :


Il remet en forme le code javascript compacté! Quand on veut mettre des points d'arrêts et comprendre le code, c'est quand même vachement plus simple 😎

Analyse

1er point d'arrêt à la ligne 23099 du fichier app_XXXXXXXXXXXXXX.js


getData: function() {
            if (!Store.Conn.connected) {
                var e = Store.Conn.ref                 e = "@CqkaWzA6Am/6BJ8Kdwi42W/Nw21cJrK8URXocOiRGbaC0VXiF2ubKhJv"
                  , t = y.getOrGenerate()
                  , r = b.id();
                return e + "," + t + "," + r
            }
        },
C'est là qu'on trouve la génération des 3 valeurs séparées par des virgules «
return e + "," + t + "," + r
». Celle qui m'intéresse c'est la valeur t. Elle est générée à la ligne 5724 du même fichier, lors de l'appelle de la fonction
b.keyPair()

    function u() {
        if (g && g.key && g.keyPair)
            return g.key.toString(CryptoJS.enc.Base64);
        var e = b.keyPair()
          , t = CryptoJS.lib.WordArray.create(e.pubKey)
          , r = CryptoJS.enc.Base64.stringify(t)
          , n = {
            key: CryptoJS.enc.Base64.parse(r),
            keyPair: e
        };
        return o(n),
        r
    }
Dans cette fonction, on commence par crée la clé privée avec des valeurs aléatoires puis on génère la clé publique avec l'appelle de la fonction «
u._curve25519_donna(e.pubKey, e.privKey, e.basepoint);
». Pour être sûr de ça, j'ai tenté de reproduire la valeur de la clé publique à l'aide d'un programme en C utilisant l'API openssl. J'ai du compiler la librairie de développement 1.1 car openssl ne supporte pas encore X25519 dans la version stable.

$ apps/openssl version
OpenSSL 1.1.0-pre5-dev  xx XXX xxxx

Comparaison

Voici la comparaison des deux générations. D'abords, celle faite par le navigateur en générant le QRCode puis, en-dessous, celle réalisée par le programme C

Javascript

La clé privée


Et la clé publique générée, stringifiée et base64 encodée:



$ echo -n "HZ0emJ9bAkSAjtZlfj4JAxevQmdjENtxKlGngst4QTA=" | base64 -d | hd
00000000  1d 9d 1e 98 9f 5b 02 44  80 8e d6 65 7e 3e 09 03  |.....[.D...e~>..|
00000010  17 af 42 67 63 10 db 71  2a 51 a7 82 cb 78 41 30  |..Bgc..q*Q...xA0|
00000020

OpenSSL

La preuve en générant la même clé publique en partant de la clé privée

Pour commencer, il faut préparer la clé privée, c'est-à-dire, convertir l'affichage des valeurs décimales du debuggeur javascript de chrome en valeurs hexadécimales.

Clé privée


70DF92B5832077226BC209722F5CF717397769AF61BB716139733EEB07526438


Ensuite, on crée le programme C

PS: j'ai du un peu bricoler car la fonction
static int x25519_set_private(EC_KEY *eckey, const BIGNUM *priv_key)
return
systématiquement « 0 »

static int x25519_init_private(EC_KEY *dst, const void *src)
{
    if (dst->custom_data == NULL) {
        dst->custom_data = OPENSSL_secure_malloc(EC_X25519_KEYLEN);
        if (dst->custom_data == NULL)
            return 0;
    }
    if (src != NULL)
        memcpy(dst->custom_data, src, EC_X25519_KEYLEN);
    return 1;
}

static int x25519_set_private(EC_KEY *eckey, const BIGNUM *priv_key)
{
    if (BN_num_bytes(priv_key) > EC_X25519_KEYLEN)
        return 0;
    if (x25519_init_private(eckey, NULL))                       <-- retourne 1 donc on entre dans le if et on quitte sans copier le bignum!
        return 0;
    /* Convert BIGNUM form private key to internal format */
    if (BN_bn2lebinpad(priv_key, eckey->custom_data, EC_X25519_KEYLEN)
        != EC_X25519_KEYLEN)
        return 0;
    return 1;
}


#include <stdio.h>
#include <stdlib.h>
#include <openssl/ec.h>
#include <openssl/bn.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include "./openssl-master/crypto/ec/ec_25519.c"

int custom_x25519_keygen(EC_KEY *eckey, const BIGNUM *privKey)
{
    unsigned char *key;
    if (x25519_init_private(eckey, NULL) == 0)
        return 0;
    key = eckey->custom_data;
    
    if (BN_bn2lebinpad(privKey, key, EC_X25519_KEYLEN)
        != EC_X25519_KEYLEN)
	{
		fprintf(stderr, "Failed to copy BN in private key\n");
		exit(EXIT_FAILURE);
	}

    key[0] &= 248;  // 1111'1000
    key[31] &= 127; // 0111'1111
    key[31] |= 64;  // 0100'0000
    /*
     * Although the private key is kept as an array in eckey->custom_data
     * Set eckey->priv_key too so existing code which uses
     * EC_KEY_get0_private_key() still works.
     */
    if (eckey->priv_key == NULL)
        eckey->priv_key = BN_secure_new();
    if (eckey->priv_key == NULL)
        return 0;
    if (BN_lebin2bn(eckey->custom_data, EC_X25519_KEYLEN, eckey->priv_key) ==
        NULL)
        return 0;
    if (eckey->pub_key == NULL)
        eckey->pub_key = EC_POINT_new(eckey->group);
    if (eckey->pub_key == NULL)
        return 0;
    return x25519_keygenpub(eckey);
}

int main(int argc, char** argv)
{
	BIO             *outbio 	= NULL;
	EC_KEY 		*key 		= NULL;
	EC_POINT 	*pubKey 	= NULL;
	BIGNUM 		*bn;
	
	OpenSSL_add_all_algorithms();
	ERR_load_BIO_strings();
	ERR_load_crypto_strings();

	/* Préparation de la sortie standard */
	outbio = BIO_new(BIO_s_file());
	outbio = BIO_new_fp(stdout, BIO_NOCLOSE);
	
	/* Préparation de la structure pour les clés */
	key = EC_KEY_new_by_curve_name(NID_X25519);
	
	/* On charge la clé privée */
	bn = BN_new();
	BN_hex2bn(&bn, "70DF92B5832077226BC209722F5CF717397769AF61BB716139733EEB07526438");

	/* On calcul la clé publique */
	custom_x25519_keygen(key, bn))
	
	/* On affiche le résultat */
	BIO_printf(outbio, "\nPrivate key\n");
	BN_print(outbio, key->priv_key);
	BIO_printf(outbio, "\nPublic key\n");
	bn = BN_bin2bn(key->pub_key->custom_data, EC_X25519_KEYLEN, bn);
	BN_print(outbio, bn);
	BIO_printf(outbio, "\n");
	
	/* On termine */
	BIO_free_all(outbio);
	exit(EXIT_SUCCESS);
}


On compile


$ gcc -o x25519 X25519.c -I./openssl-master/include/ -I./openssl-master/ -L./openssl-master -lcrypto -lssl -ldl -lpthread


On test


$ ./x25519 
Private key
70DF92B5832077226BC209722F5CF717397769AF61BB716139733EEB07526438
Public key
1D9D1E989F5B0244808ED6657E3E090317AF42676310DB712A51A782CB784130
$ printf "\x1D\x9D\x1E\x98\x9F\x5B\x02\x44\x80\x8E\xD6\x65\x7E\x3E\x09\x03\x17\xAF\x42\x67\x63\x10\xDB\x71\x2A\x51\xA7\x82\xCB\x78\x41\x30" | base64
HZ0emJ9bAkSAjtZlfj4JAxevQmdjENtxKlGngst4QTA=
La troisième valeur La troisième valeur correspond à une valeur envoyée via le WebSocket



Conclusion

Reste plus qu'à comprendre où sont utilisées toutes ces valeurs

That's all folks

  • 3 years 5 months before
  • |