81 votes

Comment cacher une chaîne de caractères en code binaire ?

Il est parfois utile de cacher une chaîne de caractères dans un fichier binaire (exécutable). Par exemple, il est judicieux de cacher les clés de chiffrement des fichiers binaires.

Quand je dis "cacher", je veux dire rendre les chaînes plus difficiles à trouver dans le binaire compilé.

Par exemple, ce code :

const char* encryptionKey = "My strong encryption key";
// Using the key

après la compilation, produit un fichier exécutable avec ce qui suit dans sa section de données :

4D 79 20 73 74 72 6F 6E-67 20 65 6E 63 72 79 70   |My strong encryp|
74 69 6F 6E 20 6B 65 79                           |tion key        |

Vous pouvez voir que notre chaîne secrète peut être facilement trouvée et/ou modifiée.

Je pourrais cacher la ficelle

char encryptionKey[30];
int n = 0;
encryptionKey[n++] = 'M';
encryptionKey[n++] = 'y';
encryptionKey[n++] = ' ';
encryptionKey[n++] = 's';
encryptionKey[n++] = 't';
encryptionKey[n++] = 'r';
encryptionKey[n++] = 'o';
encryptionKey[n++] = 'n';
encryptionKey[n++] = 'g';
encryptionKey[n++] = ' ';
encryptionKey[n++] = 'e';
encryptionKey[n++] = 'n';
encryptionKey[n++] = 'c';
encryptionKey[n++] = 'r';
encryptionKey[n++] = 'y';
encryptionKey[n++] = 'p';
encryptionKey[n++] = 't';
encryptionKey[n++] = 'i';
encryptionKey[n++] = 'o';
encryptionKey[n++] = 'n';
encryptionKey[n++] = ' ';
encryptionKey[n++] = 'k';
encryptionKey[n++] = 'e';
encryptionKey[n++] = 'y';

mais ce n'est pas une méthode agréable. Vous avez une meilleure idée ?

PS : Je sais que le simple fait de cacher des secrets ne fonctionne pas contre un attaquant déterminé, mais c'est bien mieux que rien

De plus, je connais le cryptage asymétrique, mais il n'est pas acceptable dans ce cas. Je suis en train de remanier une application existante qui utilise le cryptage Blowfish et transmet des données cryptées au serveur (le serveur décrypte les données avec la même clé).

I ne peut pas changer l'algorithme de cryptage parce que je dois assurer une compatibilité descendante. I ne peut pas même changer la clé de cryptage.

13 votes

Il existe des systèmes de cryptage à clé publique pour lesquels il n'est pas nécessaire de cacher la clé.

5 votes

Je connais les paires de clés, mais ce n'est pas acceptable dans ce cas. Je remanie une application existante qui utilise le cryptage Blowfish. Les données cryptées sont transmises au serveur et le serveur décrypte les données. Je ne peux pas changer l'algorithme de cryptage car je dois assurer une compatibilité ascendante.

7 votes

Cacher une clé dans l'exécutable n'est presque jamais une bonne idée.

54voto

Dmitriy Points 1208

Je suis désolé pour la longue réponse.

Vos réponses sont tout à fait correctes, mais la question était de savoir comment cacher une chaîne et le faire joliment.

Je l'ai fait de cette façon :

#include "HideString.h"

DEFINE_HIDDEN_STRING(EncryptionKey, 0x7f, ('M')('y')(' ')('s')('t')('r')('o')('n')('g')(' ')('e')('n')('c')('r')('y')('p')('t')('i')('o')('n')(' ')('k')('e')('y'))
DEFINE_HIDDEN_STRING(EncryptionKey2, 0x27, ('T')('e')('s')('t'))

int main()
{
    std::cout << GetEncryptionKey() << std::endl;
    std::cout << GetEncryptionKey2() << std::endl;

    return 0;
}

HideString.h :

#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/seq/enum.hpp>

#define CRYPT_MACRO(r, d, i, elem) ( elem ^ ( d - i ) )

#define DEFINE_HIDDEN_STRING(NAME, SEED, SEQ)\
static const char* BOOST_PP_CAT(Get, NAME)()\
{\
    static char data[] = {\
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH_I(CRYPT_MACRO, SEED, SEQ)),\
        '\0'\
    };\
\
    static bool isEncrypted = true;\
    if ( isEncrypted )\
    {\
        for (unsigned i = 0; i < ( sizeof(data) / sizeof(data[0]) ) - 1; ++i)\
        {\
            data[i] = CRYPT_MACRO(_, SEED, i, data[i]);\
        }\
\
        isEncrypted = false;\
    }\
\
    return data;\
}

La ligne la plus délicate dans HideString.h est :

BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH_I(CRYPT_MACRO, SEED, SEQ))

Laissez-moi expliquer la ligne. Pour le code :

DEFINE_HIDDEN_STRING(EncryptionKey2, 0x27, ('T')('e')('s')('t'))

BOOST\_PP\_SEQ\_FOR\_EACH\_I(CRYPT\_MACRO, SEED, SEQ)générer une séquence :

( 'T'  ^ ( 0x27 - 0 ) ) ( 'e'  ^ ( 0x27 - 1 ) ) ( 's'  ^ ( 0x27 - 2 ) ) ( 't'  ^ ( 0x27 - 3 ) )

BOOST\_PP\_SEQ\_ENUM(BOOST\_PP\_SEQ\_FOR\_EACH\_I(CRYPT\_MACRO, SEED, SEQ))générer :

'T' ^ ( 0x27 - 0 ), 'e' ^ ( 0x27 - 1 ), 's' ^ ( 0x27 - 2 ), 't' ^ ( 0x27 - 3 )

et enfin,

DEFINE_HIDDEN_STRING(EncryptionKey2, 0x27, ('T')('e')('s')('t'))

générer :

static const char* GetEncryptionKey2()
{
    static char data[] = {
        'T' ^ ( 0x27 - 0 ), 'e' ^ ( 0x27 - 1 ), 's' ^ ( 0x27 - 2 ), 't' ^ ( 0x27 - 3 ),
        '\0'
    };
    static bool isEncrypted = true;
    if ( isEncrypted )
    {
        for (unsigned i = 0; i < ( sizeof(data) / sizeof(data[0]) ) - 1; ++i)
        {
            data[i] = ( data[i] ^ ( 0x27 - i ) );
        }
        isEncrypted = false;
    }
    return data;
}

pour "Ma clé de cryptage forte" ressemble à des données :

0x00B0200C  32 07 5d 0f 0f 08 16 16 10 56 10 1a 10 00 08  2.]......V.....
0x00B0201B  00 1b 07 02 02 4b 01 0c 11 00 00 00 00 00 00  .....K.........

Merci beaucoup pour vos réponses !

2 votes

Merci de partager votre solution ! J'ai besoin de cacher les chaînes de caractères des éditeurs hexadécimaux et des décompilateurs de base.

0 votes

@Dmitriy vous pouvez partager ceci pour le langage C ? Peut-être sur un lien github.

1 votes

@Florida - désolé, mais la solution utilise la bibliothèque Boost qui (en général) ne supporte pas le C

47voto

Adam Liss Points 27815

Comme indiqué dans le commentaire sur le rapport de Pavium. responder vous avez deux choix :

  • Sécuriser la clé
  • Sécuriser l'algorithme de décryptage

Malheureusement, si vous devez recourir à l'intégration de la clé et de l'algorithme dans le code, aucun des deux n'est vraiment secret, ce qui vous laisse avec l'alternative (bien plus faible) suivante la sécurité par l'obscurité . En d'autres termes, comme vous l'avez mentionné, vous devez trouver un moyen astucieux de cacher l'un ou l'autre ou les deux à l'intérieur de votre exécutable.

Voici quelques options, mais vous devez vous rappeler que aucune de ces solutions n'est vraiment sûre selon les meilleures pratiques cryptographiques, et chacun a ses inconvénients :

  1. Déguisez votre clé en une chaîne qui apparaîtrait normalement dans le code. Un exemple serait la chaîne de format d'un printf() qui a tendance à contenir des chiffres, des lettres et des signes de ponctuation.
  2. Hash une partie ou la totalité des segments de code ou de données au démarrage, et l'utiliser comme clé. (Vous devrez faire preuve d'un peu d'ingéniosité pour vous assurer que la clé ne change pas de façon inattendue). Ceci a un effet secondaire potentiellement désirable de vérification de la partie hachée de votre code à chaque exécution.
  3. Générer la clé au moment de l'exécution à partir d'un élément unique (et constant) au sein du système, par exemple en hachant l'adresse MAC d'une carte réseau.
  4. Créez la clé en choisissant des octets parmi d'autres données. Si vous avez des données statiques ou globales, quel que soit leur type ( int , char , etc. ), prenez un octet quelque part dans chaque variable après qu'elle ait été initialisée (à une valeur non nulle, bien sûr) et avant qu'elle ne change.

Faites-nous savoir comment vous résolvez le problème !

Editar: Vous avez indiqué que vous remaniez du code existant, je suppose donc que vous ne pouvez pas nécessairement choisir la clé vous-même. Dans ce cas, suivez un processus en 2 étapes : Utilisez l'une des méthodes ci-dessus pour chiffrer la clé elle-même, puis utilisez la méthode suivante que pour décrypter les données des utilisateurs.

9voto

Stephen C Points 255558

Cacher les mots de passe dans votre code est une sécurité par l'obscurité. C'est dangereux car cela vous fait croire que vous avez un certain niveau de protection, alors qu'en fait vous en avez très peu. Si une chose vaut la peine d'être sécurisée, elle doit l'être correctement.

PS : Je sais que ça ne marche pas contre un vrai hacker, mais c'est beaucoup mieux que rien...

En fait, dans de nombreuses situations, rien ne vaut une sécurité faible. Au moins, vous savez exactement où vous en êtes. Il n'est pas nécessaire d'être un "vrai pirate" pour contourner un mot de passe intégré...

EDIT : Je réponds à ce commentaire :

Je connais les paires de clés, mais ce n'est pas acceptable dans ce cas. acceptable dans ce cas. Je remanie application existante qui utilise le cryptage Blowfish. Les données cryptées sont transmises au serveur et le serveur décrypte données. Je ne peux pas changer l'algorithme d'encryptage algorithme de cryptage car je dois fournir une compatibilité descendante.

Si vous vous souciez un tant soit peu de la sécurité, le maintien de la compatibilité ascendante est une TRÈS MAUVAISE raison pour vous rendre vulnérable avec des mots de passe intégrés. C'est une BONNE CHOSE de rompre la rétrocompatibilité avec un système de sécurité non sécurisé.

C'est comme lorsque les enfants de la rue découvrent que vous laissez la clé de votre porte d'entrée sous le paillasson, mais que vous continuez à le faire parce que grand-père s'attend à la trouver là.

33 votes

La quasi-totalité des clés de licence et des numéros de série de logiciels sont des exemples de sécurité par l'obscurité et sont des cas d'utilisation parfaitement légitimes. Selon votre raisonnement et celui d'autres personnes, vous ne devriez jamais verrouiller un vélo à un porte-vélo, car tous les cadenas de vélo peuvent être battus assez facilement avec les bons outils. Au moins, lorsque votre vélo est déverrouillé, "vous savez exactement où vous en êtes".

0 votes

@Harvey - Ce que je veux dire, c'est qu'avec un système sans mot de passe, il est évident pour tous ceux qui UTILISENT le système qu'il n'est pas sûr ... et qu'ils ne doivent rien attendre. En revanche, si votre système est faible, beaucoup de gens penseront qu'il est sûr alors qu'il ne l'est pas vraiment... et pourraient lui confier des secrets qu'ils ne devraient pas.

0 votes

Harvey - Ce n'est pas moi qui ai inventé cette analogie avec le vélo. C'est vous qui l'avez inventée. Mais une meilleure analogie serait un support à vélo public verrouillable. Si je pense que le support a une serrure solide, je peux lui faire confiance pour garer mon vélo en toute sécurité. Mais si le cadenas pouvait être crocheté en deux secondes avec un trombone, ma confiance serait mal placée...

9voto

T.J. Crowder Points 285826

Votre exemple ne cache pas du tout la chaîne de caractères ; celle-ci est toujours présentée comme une série de caractères dans la sortie.

Il existe de nombreuses façons d'obscurcir les chaînes de caractères. Il y a la simple code de substitution Vous pouvez également effectuer une opération mathématique sur chaque caractère (un XOR, par exemple), dont le résultat alimente l'opération du caractère suivant, etc.

L'objectif est d'obtenir des données qui ne ressemblent pas à une chaîne de caractères. Par exemple, si vous travaillez dans la plupart des langues occidentales, la plupart des valeurs de caractères seront comprises entre 32 et 127. out de cette gamme, afin qu'ils n'attirent pas l'attention.

0 votes

Je peux utiliser quelque chose comme encryptionKey[n++] = 'M' ^ 0x79 ; mais ce n'est toujours pas une bonne méthode.

1 votes

Quelle est votre définition de "gentil" ?

3 votes

@Dmitry Je pense que ce type de "dissimulation" est à peu près le mieux que vous puissiez faire. J'ai fait quelque chose de similaire auparavant pour les mêmes raisons que vous voulez (sauf que j'ai tourné à travers 4 constantes xor différentes sur la longueur de la chaîne). Si un pirate a la capacité de trouver et de décoder mes chaînes, alors il peut aussi modifier directement les valeurs de retour des routines etc J'aimerais avoir une meilleure réponse.

8voto

Wim ten Brink Points 12422

C'est aussi sûr que de laisser son vélo non verrouillé à Amsterdam, aux Pays-Bas, près de la gare centrale. (Clignez des yeux, et il n'est plus là !)

Si vous essayez d'ajouter de la sécurité à votre application, vous êtes voué à l'échec dès le départ, car tout système de protection est voué à l'échec. Tout ce que vous pouvez faire, c'est rendre plus complexe pour un pirate la recherche des informations dont il a besoin. Quelques astuces tout de même :

*) Assurez-vous que la chaîne est stockée en UTF-16 dans votre binaire.

*) Ajoutez des chiffres et des caractères spéciaux à la chaîne de caractères.

*) Utilisez un tableau d'entiers de 32 bits au lieu d'une chaîne de caractères ! Convertissez-les en chaînes de caractères et concaténérez-les.

*) Utiliser un GUID, le stocker sous forme binaire et le convertir en une chaîne de caractères à utiliser.

Et si vous avez vraiment besoin d'un texte prédéfini, chiffrez-le et stockez la valeur chiffrée dans votre binaire. Déchiffrez-le en cours d'exécution, la clé de déchiffrement étant l'une des options que j'ai mentionnées précédemment.

Sachez que les pirates auront tendance à craquer votre application par d'autres moyens que celui-ci. Même un expert en cryptographie ne sera pas en mesure de garantir la sécurité de votre application. En général, la seule chose qui vous protège est le profit qu'un pirate peut tirer du piratage de votre code, par rapport au coût du piratage. (Ces coûts se résument souvent à beaucoup de temps, mais s'il faut une semaine pour pirater votre application et seulement 2 jours pour pirater autre chose, cette autre chose a plus de chances d'être attaquée).


Réponse au commentaire : UTF-16 serait de deux octets par caractère, donc plus difficile à reconnaître pour les utilisateurs qui regardent un dump du binaire, simplement parce qu'il y a un octet supplémentaire entre chaque lettre. Mais on peut quand même voir les mots. L'UTF-32 serait même meilleur car il ajoute plus d'espace entre les lettres. Mais on peut aussi comprimer un peu le texte en passant à un système de 6 bits par caractère. Tous les 4 caractères seraient alors compactés en trois chiffres. Mais cela vous limiterait à 2x26 lettres, 10 chiffres et peut-être l'espace et le point pour arriver à 64 caractères.

L'utilisation d'un GUID est pratique si vous stockez le GUID dans son format binaire, et non dans son format textuel. Un GUID a une longueur de 16 octets et peut être généré de manière aléatoire. Il est donc difficile de deviner le GUID qui est utilisé comme mot de passe. Mais si vous avez toujours besoin d'envoyer du texte brut, un GUID peut être converti en une représentation de chaîne de caractères comme "3F2504E0-4F89-11D3-9A0C-0305E82C3301". (Ou encodé en Base64 comme "7QDBkvCA1+B9K/U0vrQx1A==".) Mais les utilisateurs ne verront pas de texte en clair dans le code, juste des données apparemment aléatoires. Tous les octets d'un GUID ne sont pas aléatoires, cependant. Il y a un numéro de version caché dans les GUID. L'utilisation d'un GUID n'est pas la meilleure option à des fins cryptographiques, cependant. Il est calculé soit sur la base de votre adresse MAC, soit par un nombre pseudo-aléatoire, ce qui le rend raisonnablement prévisible. Néanmoins, il est facile à créer et à stocker, convertir et utiliser. Créer quelque chose de plus long n'apporte pas plus de valeur puisqu'un hacker essaierait simplement de trouver d'autres astuces pour casser la sécurité. Il s'agit simplement de savoir s'ils sont prêts à investir plus de temps dans l'analyse des binaires.

En général, la chose la plus importante pour assurer la sécurité de vos applications est le nombre de personnes qui s'y intéressent. Si personne ne s'intéresse à votre application, personne ne prendra la peine de la pirater non plus. Lorsque vous êtes le produit phare avec 500 millions d'utilisateurs, alors votre application est craquée en une heure.

2 votes

Vos préoccupations sont toutes valables, mais malheureusement le monde des affaires est loin d'être idéal, et les problèmes doivent être résolus dans le cadre de paramètres acceptables. Cela dit, je suis curieux de connaître certaines de vos recommandations. UTF-16 : pourquoi ? Les bits sont des bits :-) Ne serait-il pas préférable d'autoriser tous valeurs, y compris les valeurs non imprimables ? GUID : Pourquoi est-ce mieux que toute autre séquence d'octets (éventuellement plus longue) ?

0 votes

D'autres conseils que j'ai lus : Placez la chaîne secrète à plusieurs endroits de votre application qui ne sont pas liés à la licence. De plus, si vous pouvez retarder l'exécution du code qui utilise le secret, par exemple en le déclenchant par un événement minuté, il sera plus difficile à déboguer pour un pirate.

0 votes

La sécurité basée sur le temps est facilement brisée par la virtualisation, à moins que vous n'ayez une requête chiffrée vers un serveur de licence. Ils doivent donc craquer les données (données réseau) avant de commencer à tester efficacement le code virtualisé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X