• [Un peu HS] Utilisation de l'API OpenSSL

    From =?UTF-8?Q?BERTRAND_Jo=c3=abl?=@21:1/5 to All on Thu Jul 4 17:50:01 2024
    This is an OpenPGP/MIME signed message (RFC 4880 and 3156) --BrLLzbjqG0ErRvTFAjxJlIwJD0hoU8mM7
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: quoted-printable

    Bonjour à tous,

    Désolé de venir avec un problème pas tout à fait Debian. J'essaye de
    m'inscrire sur les listes de diffusion OpenSSL depuis plusieurs jours
    sans aucun retour. Je pense que des lecteurs ont déjà été confrontés au problème.

    J'utilise OpenSSL (3.3.1) pour chiffrer des messages et les déchiffrer.
    J'observe un comportement que je ne comprends pas. Je n'utilise qu'un chiffrement de type AES256-ECB (donc le chiffrement d'un bloc ne dépend
    que du bloc en question. Je sais, ce n'est pas bien, mais le
    périphérique en face est un microcontrôleur qui cause en 4G et qui
    possède très peu de mémoire).

    Lorsque mon périphérique parle à mon serveur Debian, mon programme plante méchamment lors de l'appel à la fonction EVP_CipherFinal_ex().

    Que fais-je ?

    Je crée un contexte, je l'initialise avec la clef et tout ce qui va bien puis je chiffre :

    if ((contexte = EVP_CIPHER_CTX_new()) == NULL)
    {
    return(NULL);
    }

    EVP_CIPHER_CTX_reset(contexte);

    longueur_bloc_de_chiffrement =
    EVP_CIPHER_block_size(type_chiffrement);

    if (EVP_CipherInit_ex(contexte, type_chiffrement, NULL, clef,
    vecteur_initialisation, (encodage == d_vrai) ? 1 : 0) != 1)
    {
    EVP_CIPHER_CTX_free(contexte);
    return(NULL);
    }

    nombre_blocs = longueur_message / longueur_bloc_de_chiffrement;

    if ((longueur_message % longueur_bloc_de_chiffrement) != 0)
    {
    nombre_blocs++;
    }

    (*longueur_message_chiffre) = nombre_blocs *
    longueur_bloc_de_chiffrement;

    printf("longueur_message_chiffre = %lld\n", (*longueur_message_chiffre));

    // On prévoit une zone de garde pour EVP_CipherFinal_ex() en
    // espérant
    // qu'il ne faille pas plus qu'une longueur de bloc de chiffrement.
    // Méchant hack en raison d'un comportement étrange de
    // EVP_CipherFinal_ex().
    if ((message_chiffre = malloc(((size_t) ((*longueur_message_chiffre)
    + longueur_bloc_de_chiffrement)) *
    sizeof(unsigned char))) == NULL)
    {
    EVP_CIPHER_CTX_free(contexte);
    return(NULL);
    }

    printf("longueur_message = %d\n", (int) longueur_message);

    if (EVP_CipherUpdate(contexte, message_chiffre, &longueur_message_1,
    message, (int) longueur_message) != 1)
    {
    free(message_chiffre);
    EVP_CIPHER_CTX_free(contexte);
    return(NULL);
    }

    printf("longueur_message_1 = %d\n", longueur_message_1);

    if (EVP_CipherFinal_ex(contexte, message_chiffre +
    longueur_message_1,
    &longueur_message_2) != 1)
    {
    free(message_chiffre);
    EVP_CIPHER_CTX_free(contexte);
    return(NULL);
    }

    printf("longueur_message_2 = %d\n", longueur_message_2);

    (*longueur_message_chiffre) = longueur_message_1 +
    longueur_message_2;

    // Mise à jour du vecteur d'initialisation

    EVP_CIPHER_CTX_get_updated_iv(contexte, vecteur_initialisation,
    (size_t) EVP_CIPHER_iv_length(type_chiffrement));

    EVP_CIPHER_CTX_free(contexte);

    Cette routine est appelée pour chiffrer et pour déchiffrer. Je ne mets
    pas tout le code, ça n'a pas beaucoup d'intérêt. Maintenant, mon problème.

    Le code AES256 utilise des blocs de 16 octets. Si je chiffre un message de 15 octets, j'obtiens un message chiffré de 16 octets. Normal.

    longueur_message_chiffre = 16
    longueur_message = 15
    longueur_message_1 = 0
    longueur_message_2 = 16

    Ainsi, EVP_CipherUpdate() ne fait rien (aucun bloc entier) et le chiffrement est effectué par EVP_CipherFinal_ex().

    Si maintenant je cherche à chiffrer un message de 16 octets, voici ce qui sort :

    longueur_message_chiffre = 16
    longueur_message = 16
    longueur_message_1 = 16
    longueur_message_2 = 16
    longueur_message_chiffre donne la longueur nécessaire au chiffrement
    du message. La longueur effectivement renvoyée est longueur_message_1+longueur_message_2 soit ici 32 !

    Si je comprends bien, EVP_CipherUpdate() chiffre un bloc de 16 octets. Mais pourquoi EVP_CipherFinal_ex() rajoute-t-il un autre bloc de 16
    octets ? J'obtiens un message de 32 octets alors que seuls les 16
    premiers sont signifiants.

    Dans mon cas, j'obtiens : "?r\xF1\xF0-\x89\x83]\xD3\xEE\xF0bj\xE2\xB7\x9E\xE34\xF2\xD2f \xF6w\x02\x14\x0DbS\xD5\x01\x1A"

    alors que seul

    "?r\xF1\xF0-\x89\x83]\xD3\xEE\xF0bj\xE2\xB7\x9E" contient le message
    chiffré (le formalisme est le suivant : les caractères affichables sont directement affichés, les autres sont \x + octet en hexadécimal).

    Le problème est aussi que je suis capable de déchiffrer le message complet "?r\xF1\xF0-\x89\x83]\xD3\xEE\xF0bj\xE2\xB7\x9E\xE34\xF2\xD2f \xF6w\x02\x14\x0DbS\xD5\x01\x1A"
    en une chaîne de 16 octets (qui est bien le message envoyé). En
    revanche, si je tente le déchiffrement des seuls 16 premiers octets, EVP_CipherFinal_ex() renvoie une erreur.

    Je ne comprends pas mon erreur. Quelqu'un a-t-il déjà été confronté à
    ce problème ? J'ai trouvé des tas d'exemples sur internet, mais, à
    chaque fois, EVP_CipherFinal_ex() est appelé.

    Je prends tout avis, tout pointeur sur une documentation compréhensible
    (celle d'OpenSSL est un peu... touffue).

    Merci de votre lecture,

    JKB


    --BrLLzbjqG0ErRvTFAjxJlIwJD0hoU8mM7--

    -----BEGIN PGP SIGNATURE-----

    iHUEABYIAB0WIQQj8MW8iOsC2RXEznnFW/s/mMLXCAUCZobECQAKCRDFW/s/mMLX CNA5AP9bzUygPylzaNYJTuoTQ7sCTMVh9MFj8p4SD3TWgkMlNAEA6gpL1JL29VFB UGG4OojJ+Mum2T84MV20GCAiCG84AAM=
    =Fbpj
    -----END PGP SIGNATURE-----

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From =?UTF-8?Q?BERTRAND_Jo=c3=abl?=@21:1/5 to All on Thu Jul 4 20:50:01 2024
    This is an OpenPGP/MIME signed message (RFC 4880 and 3156) --2fL27FuYX3ETynFM1xFghhz1TzoxWtPV3
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: quoted-printable

    didier gaumet a écrit :
    préambule lamentable: j'ai lu ta bafouille en diagonale, je n'ai pas le niveau, j'ai pas envie de creuser, etc... donc ma réponse ne va
    peut-être pas être très pertinente, désolé ;-)

    En très gros de la part d'un inculte du sujet, même si la fonction
    openssl semble légèrement différente sur cette page, je me demande si le mécanisme sur lequel tu butes n'est pas le même (du padding qui
    nécessite la décomposition en sous-variables dont la dernière doit être traitée par une fonction de type "final"): https://stackoverflow.com/questions/71763461/purpose-of-evp-encryptfinal-ex-function-in-openssl


    j'ai peut-être rien compris, hein, c'est hors de mes maigres compétences
    et je n'ai de plus pas envie de vriller les pauvres neurones qui me
    restent à chercher à comprendre ;-)

    Ça tourne effectivement autour de cela. Je connais la différence entre
    les deux fonctions. Ce qui me dérange, c'est que dans tous les exemples
    que j'ai pu trouver EVP_CipherFinal_ex() est toujours appelée sur le
    dernier bloc (pour bourrer les octets manquant pour avoir un multiple de
    la longueur de bloc). Ce que je ne saisis pas, c'est pour quoi lorsqu'on
    a déjà un multiple d'une longueur de bloc, la fonction en rajoute un. Et pourquoi elle plante lamentablement en cas de déchiffrement.

    Je pense (naïvement sans doute) que si aucun test n'est fait dans les exemples sur la longueur des données à chiffrer, cette fonction doit
    être appelée dans tous les cas.

    Bien cordialement,

    JB


    --2fL27FuYX3ETynFM1xFghhz1TzoxWtPV3--

    -----BEGIN PGP SIGNATURE-----

    iHUEABYIAB0WIQQj8MW8iOsC2RXEznnFW/s/mMLXCAUCZobtjQAKCRDFW/s/mMLX CNvmAQDrFRZcNk5prqQKmuoXYN/oKCcDJ4LO8yWvijuOyJhO+gD9GuU3pp9n5jx2 RGxyfUuclKV5DQRc/4myFZLgeia8FQ4=
    =Hfm1
    -----END PGP SIGNATURE-----

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From =?UTF-8?Q?BERTRAND_Jo=c3=abl?=@21:1/5 to All on Thu Jul 4 22:30:01 2024
    This is an OpenPGP/MIME signed message (RFC 4880 and 3156) --8DgtOrgDBxTOzZMmY4UIco1KNW6qJ7BpN
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: quoted-printable

    didier gaumet a écrit :
    Le 04/07/2024 à 20:44, BERTRAND Joël a écrit :
    didier gaumet a écrit :
    [...]
    https://stackoverflow.com/questions/71763461/purpose-of-evp-encryptfinal-ex-function-in-openssl

    [...]

    Ce que je ne saisis pas, c'est pour quoi lorsqu'on
    a déjà un multiple d'une longueur de bloc, la fonction en rajoute un. Et >> pourquoi elle plante lamentablement en cas de déchiffrement.
    [...]

    comme dit précédemment, tout ça me passe un peu au-dessus de ce qui me sert malaisément de cerveau mais le lien ci-dessus a quelques
    explications intéressantes fournies par un type quia l'air de comprendre
    à peu près de quoi il parle, et ça renvoie vers l'explication Wikipedia
    du mode d'opération de chiffrement, section "remplissage": https://fr.wikipedia.org/wiki/Mode_d%27op%C3%A9ration_(cryptographie)#Remplissage

    (comme on est sur une liste en français, je pointe vers un lien en français, mais le texte original en anglais est peut-être plus à jour ou plus complet)

    donc j'ai pas creusé mais une explication du comportement que tu
    observes (des blocs de 16 bits dont seuls 15 utilisés passent mais pas
    des blocs de 16 bits comportant 16 bits utiles) pourrait résider dans le mode utilisé, je cite:
    "[...]  Un peu plus complexe est la méthode DES originale, qui consiste
    à ajouter un seul bit, suivi de suffisamment de bits zéro pour remplir
    le bloc ; si le message se termine sur une limite de bloc, un bloc de remplissage entier sera ajouté. [...]"

    Rien à voir, je suis en ECB. Donc chaque bloc est chiffré séparément.
    Le problème est "pourquoi openssl bourre-t-il un nouveau bloc de 16
    octets alors qu'il n'a pas besoin de le faire ?"

    Le chiffrement est le suivant :
    16 octets -> AES256 -> 16 octets (et non 32).

    JB


    --8DgtOrgDBxTOzZMmY4UIco1KNW6qJ7BpN--

    -----BEGIN PGP SIGNATURE-----

    iHUEABYIAB0WIQQj8MW8iOsC2RXEznnFW/s/mMLXCAUCZocEpQAKCRDFW/s/mMLX CE5wAP4nDgTUHAAApe9zBRDmzLc7a7xxfP/tvgqCZCCI9NBLRQD+K4mCnh1ETgwK Rafhi3LT7y2YSinIt/aRiHaNpohPQwk=
    =rIL6
    -----END PGP SIGNATURE-----

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From =?UTF-8?Q?BERTRAND_Jo=c3=abl?=@21:1/5 to All on Fri Jul 5 11:10:01 2024
    This is an OpenPGP/MIME signed message (RFC 4880 and 3156) --liHbyQrj69Jekc0p8aqKzAvwuEUnND6Yq
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: quoted-printable

    J'ai enfin réussi à avoir un retour des devs d'OpenSSL. Il faut dans leur technique de détection des fins de message toujours au moins un
    octet de bourrage. Donc si le message fait un multiple entier d'une
    longueur de bloc, on se tape un bloc entier en bourrage. Une fois
    expliqué, c'est assez logique. Mais ça n'apparaît nulle part dans la documentation d'OpenSSL.

    Désolé pour le bruit.

    JB


    --liHbyQrj69Jekc0p8aqKzAvwuEUnND6Yq--

    -----BEGIN PGP SIGNATURE-----

    iHUEABYIAB0WIQQj8MW8iOsC2RXEznnFW/s/mMLXCAUCZoe3qgAKCRDFW/s/mMLX CCoZAP0cs6lF8v5lX63cdAJs+5bCWBBbParNHrXhuJZ5i2CrSgEAhz6PqDYNIxjr gUxuvuPlMRE2AmDhUrI37JCfev7HggY=
    =Ljnz
    -----END PGP SIGNATURE-----

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)