techniques pour masquer les chaînes sensibles en C ++


87

J'ai besoin de stocker des informations sensibles (une clé de chiffrement symétrique que je souhaite garder privée) dans mon application C ++. L'approche simple est de faire ceci:

std::string myKey = "mysupersupersecretpasswordthatyouwillneverguess";

Cependant, l'exécution de l'application à travers le stringsprocessus (ou tout autre qui extrait des chaînes d'une application binaire) révélera la chaîne ci-dessus.

Quelles techniques devraient être utilisées pour masquer ces données sensibles?

Éditer:

OK, à peu près vous avez tous dit "votre exécutable peut être rétro-ingénierie" - bien sûr! Ceci est une bête noire de la mienne, alors je vais déclamer un peu ici:

Pourquoi est-ce que 99% (OK, alors peut-être que j'exagère un peu) de toutes les questions liées à la sécurité sur ce site reçoivent une réponse avec un torrent de "il n'y a aucun moyen possible de créer un programme parfaitement sécurisé" - ce n'est pas utile répondre! La sécurité est une échelle mobile entre une utilisabilité parfaite et aucune sécurité à une extrémité, et une sécurité parfaite mais aucune convivialité à l'autre.

Le fait est que vous choisissez votre position sur cette échelle mobile en fonction de ce que vous essayez de faire et de l'environnement dans lequel votre logiciel fonctionnera. Je n'écris pas d'application pour une installation militaire, j'écris une application pour un PC domestique . J'ai besoin de crypter des données sur un réseau non approuvé avec une clé de cryptage pré-connue. Dans ces cas, la "sécurité par l'obscurité" est probablement suffisante! Bien sûr, quelqu'un avec suffisamment de temps, d'énergie et de compétences pourrait effectuer une rétro-ingénierie du binaire et trouver le mot de passe, mais devinez quoi? Je m'en fous:

Le temps qu'il me faut pour implémenter un système sécurisé de premier ordre coûte plus cher que la perte de ventes due aux versions fissurées (non pas que je vends réellement cela, mais vous comprenez mon point). Cette tendance du ciel bleu «faisons-le de la meilleure façon possible» dans la programmation chez les nouveaux programmeurs est pour le moins insensée.

Merci d'avoir pris le temps de répondre à cette question - ils ont été très utiles. Malheureusement, je ne peux accepter qu'une seule réponse, mais j'ai voté toutes les réponses utiles.


2
Peut-être que si vous décrivez ce que vous essayez d'accomplir avec la clé de chiffrement, nous pouvons vous offrir des conseils sur la manière d'éviter d'avoir à le faire.
joshperry


4
@Kirill: il est difficile d'appeler cette question exactement la même que celle que vous évoquez. En effet, l'idée est la même. La question n'est pas.
xtofl

1
@xtofl, vous êtes libre de ne pas voter. Pour moi, cela ressemble à deux questions identiques.
Kirill V. Lyadvinsky

2
(Sans rapport avec la diatribe) La définition même d'une "clé privée" est la moitié d'une paire de clés publique / privée que vous ne donnez pas. Dans ce cas également, la clé privée est celle que vous conservez sur votre serveur; celui de votre application cliente est public.
MSalters

Réponses:


42

Fondamentalement, toute personne ayant accès à votre programme et un débogueur peut et va trouver la clé dans l'application si elles le souhaitent.

Mais, si vous voulez simplement vous assurer que la clé ne s'affiche pas lors de l'exécution stringssur votre binaire, vous pouvez par exemple vous assurer que la clé n'est pas dans la plage imprimable.

Clé obscurcissante avec XOR

Par exemple, vous pouvez utiliser XOR pour diviser la clé en deux tableaux d'octets:

key = key1 XOR key2

Si vous créez key1 avec la même longueur d'octet que keyvous pouvez utiliser des valeurs d'octets (complètement) aléatoires, puis calculer key2:

key1[n] = crypto_grade_random_number(0..255)
key2[n] = key[n] XOR key1[n]

Vous pouvez le faire dans votre environnement de génération, puis uniquement stocker key1et key2dans votre application.

Protéger votre binaire

Une autre approche consiste à utiliser un outil pour protéger votre binaire. Par exemple, il existe plusieurs outils de sécurité qui peuvent s'assurer que votre binaire est obscurci et démarre une machine virtuelle sur laquelle il s'exécute. Cela rend le débogage (euh) difficile, et c'est aussi la façon conventionnelle de protéger de nombreuses applications sécurisées de qualité commerciale (aussi, hélas, les logiciels malveillants).

L'un des principaux outils est Themida , qui fait un excellent travail de protection de vos binaires. Il est souvent utilisé par des programmes bien connus, tels que Spotify, pour se protéger contre l'ingénierie inverse. Il a des fonctionnalités pour empêcher le débogage dans des programmes tels que OllyDbg et Ida Pro.

Il existe également une liste plus longue, peut-être un peu obsolète, d' outils pour protéger votre binaire .
Certains d'entre eux sont gratuits.

Correspondance de mot de passe

Quelqu'un ici a discuté du mot de passe de hachage + sel.

Si vous avez besoin de stocker la clé pour la comparer à un type de mot de passe soumis par l'utilisateur, vous devez utiliser une fonction de hachage unidirectionnelle, de préférence en combinant un nom d'utilisateur, un mot de passe et un sel. Le problème avec cela, cependant, est que votre application doit connaître le sel pour pouvoir faire le sens unique et comparer les hachages résultants. Vous devez donc toujours stocker le sel quelque part dans votre application. Mais, comme le souligne @Edward dans les commentaires ci-dessous, cela protégera efficacement contre une attaque par dictionnaire utilisant, par exemple, des tables arc-en-ciel.

Enfin, vous pouvez utiliser une combinaison de toutes les techniques ci-dessus.


Si c'est un mot de passe que l'utilisateur doit entrer, stockez le hash + salt du mot de passe. Voir la réponse ci-dessous.

1
@hapalibashi: Comment stockez-vous le sel en toute sécurité dans votre application? Je ne pense pas que l'OP avait besoin d'un système de correspondance de mot de passe unidirectionnel, juste une manière généralisée de stocker des clés statiques.
csl

2
Lorsque je regarde des programmes désassemblés, il n'y a généralement pas beaucoup de XOR, donc si vous espérez utiliser XOR pour obscurcir quelque chose, gardez à l'esprit qu'ils attirent l'attention sur eux-mêmes.
kb.

2
@kb - c'est un point intéressant. Je suppose que vous verriez des ands et ors au niveau du bit se produire beaucoup plus que xor. a ^ b == (a & ~ b) || (~ a & b)
Jeremy Powell

4
Connaître la valeur du sel ne donne généralement pas un avantage à un adversaire - le but du sel est d'éviter une "attaque par dictionnaire", par laquelle l'attaquant a précalculé les hachages pour de nombreuses entrées probables. L'utilisation d'un sel les oblige à pré-calculer leur dictionnaire avec un nouveau basé sur le sel. Si chaque sel n'est utilisé qu'une seule fois, l'attaque par dictionnaire devient complètement inutile.
Edward Dixon

8

Tout d'abord, sachez que vous ne pouvez rien faire pour arrêter un hacker suffisamment déterminé, et il y en a beaucoup autour. La protection de chaque jeu et console autour est finalement fissurée, donc ce n'est qu'une solution temporaire.

Il y a 4 choses que vous pouvez faire pour augmenter vos chances de rester caché pendant un certain temps.

1) Cachez les éléments de la chaîne d'une manière ou d'une autre - quelque chose d'évident comme xoring (l'opérateur ^) la chaîne avec une autre chaîne sera suffisamment bonne pour rendre la chaîne impossible à rechercher.

2) Divisez la chaîne en morceaux - divisez votre chaîne et mettez-en des morceaux en méthodes étrangement nommées dans des modules étranges. Ne facilitez pas la recherche et la recherche de la méthode contenant la chaîne. Bien sûr, une méthode devra appeler tous ces bits, mais cela rend les choses un peu plus difficiles.

3) Ne construisez jamais la chaîne en mémoire - la plupart des pirates utilisent des outils qui leur permettent de voir la chaîne en mémoire après l'avoir encodée. Si possible, évitez cela. Si, par exemple, vous envoyez la clé à un serveur, envoyez-la caractère par caractère, de sorte que toute la chaîne ne soit jamais présente. Bien sûr, si vous l'utilisez à partir de quelque chose comme l'encodage RSA, alors c'est plus compliqué.

4) Faites un algorithme ad hoc - en plus de tout cela, ajoutez une touche unique ou deux. Ajoutez peut-être simplement 1 à tout ce que vous produisez, ou effectuez un chiffrement deux fois ou ajoutez un sucre. Cela rend juste un peu plus difficile pour le pirate informatique qui sait déjà quoi rechercher quand quelqu'un utilise, par exemple, le hachage vanilla md5 ou le cryptage RSA.

Surtout, assurez-vous que ce n'est pas trop important quand (et ce le sera quand si votre application devient suffisamment populaire) votre clé est découverte!


5

Une stratégie que j'ai utilisée dans le passé consiste à créer un tableau de personnages apparemment aléatoires. Vous insérez initialement, puis localisez vos caractères particuliers avec un processus algébrique où chaque étape de 0 à N produira un nombre <taille du tableau qui contient le caractère suivant dans votre chaîne obscurcie. (Cette réponse se sent maintenant obscurcie!)

Exemple:

Étant donné un tableau de caractères (les nombres et les tirets sont pour référence seulement)

0123456789
----------
ALFHNFELKD
LKFKFLEHGT
FLKRKLFRFK
FJFJJFJ!JL

Et une équation dont les six premiers résultats sont: 3, 6, 7, 10, 21, 47

Céderait le mot "HELLO!" du tableau ci-dessus.


Bonne idée - Je suppose que vous pourriez l'améliorer davantage en utilisant des caractères non imprimables dans le tableau ...
Thomi

4

Je suis d'accord avec @Checkers, votre exécutable peut être rétro-conçu.

Un peu mieux est de le créer dynamiquement, par exemple:

std::string myKey = part1() + part2() + ... + partN();

Certes, cela évite d'avoir la chaîne révélée lors de la recherche dans le binaire. Cependant, votre chaîne est toujours résidente en mémoire .. Votre solution est probablement assez bonne pour ce que je fais.
Thomi

@Thomi, vous pouvez, bien sûr, le détruire dès que vous en avez terminé avec lui. Mais ce n'est toujours pas la meilleure façon de gérer les chaînes sensibles .
Nick Dandoulakis

... car sa destruction ne garantit pas que la mémoire sera réutilisée immédiatement.
Thomi

4

Bien entendu, stocker des données privées dans un logiciel livré à l'utilisateur est toujours un risque. Tout ingénieur suffisamment formé (et dédié) pourrait procéder à une rétro-ingénierie des données.

Cela étant dit, vous pouvez souvent sécuriser suffisamment les choses en élevant la barrière que les gens doivent surmonter pour révéler vos données privées. C'est généralement un bon compromis.

Dans votre cas, vous pouvez encombrer vos chaînes de données non imprimables, puis les décoder au moment de l'exécution à l'aide d'une simple fonction d'aide, comme ceci:

void unscramble( char *s )
{
    for ( char *str = s + 1; *str != 0; str += 2 ) {
        *s++ = *str;
    }
    *s = '\0';
}

void f()
{
    char privateStr[] = "\001H\002e\003l\004l\005o";
    unscramble( privateStr ); // privateStr is 'Hello' now.

    string s = privateStr;
    // ...
}

4

J'ai créé un outil de cryptage simple pour les chaînes, il peut générer automatiquement des chaînes cryptées et dispose de quelques options supplémentaires pour le faire, quelques exemples:

Chaîne comme variable globale:

// myKey = "mysupersupersecretpasswordthatyouwillneverguess";
unsigned char myKey[48] = { 0xCF, 0x34, 0xF8, 0x5F, 0x5C, 0x3D, 0x22, 0x13, 0xB4, 0xF3, 0x63, 0x7E, 0x6B, 0x34, 0x01, 0xB7, 0xDB, 0x89, 0x9A, 0xB5, 0x1B, 0x22, 0xD4, 0x29, 0xE6, 0x7C, 0x43, 0x0B, 0x27, 0x00, 0x91, 0x5F, 0x14, 0x39, 0xED, 0x74, 0x7D, 0x4B, 0x22, 0x04, 0x48, 0x49, 0xF1, 0x88, 0xBE, 0x29, 0x1F, 0x27 };

myKey[30] -= 0x18;
myKey[39] -= 0x8E;
myKey[3] += 0x16;
myKey[1] += 0x45;
myKey[0] ^= 0xA2;
myKey[24] += 0x8C;
myKey[44] ^= 0xDB;
myKey[15] ^= 0xC5;
myKey[7] += 0x60;
myKey[27] ^= 0x63;
myKey[37] += 0x23;
myKey[2] ^= 0x8B;
myKey[25] ^= 0x18;
myKey[12] ^= 0x18;
myKey[14] ^= 0x62;
myKey[11] ^= 0x0C;
myKey[13] += 0x31;
myKey[6] -= 0xB0;
myKey[22] ^= 0xA3;
myKey[43] += 0xED;
myKey[29] -= 0x8C;
myKey[38] ^= 0x47;
myKey[19] -= 0x54;
myKey[33] -= 0xC2;
myKey[40] += 0x1D;
myKey[20] -= 0xA8;
myKey[34] ^= 0x84;
myKey[8] += 0xC1;
myKey[28] -= 0xC6;
myKey[18] -= 0x2A;
myKey[17] -= 0x15;
myKey[4] ^= 0x2C;
myKey[9] -= 0x83;
myKey[26] += 0x31;
myKey[10] ^= 0x06;
myKey[16] += 0x8A;
myKey[42] += 0x76;
myKey[5] ^= 0x58;
myKey[23] ^= 0x46;
myKey[32] += 0x61;
myKey[41] ^= 0x3B;
myKey[31] ^= 0x30;
myKey[46] ^= 0x6C;
myKey[35] -= 0x08;
myKey[36] ^= 0x11;
myKey[45] -= 0xB6;
myKey[21] += 0x51;
myKey[47] += 0xD9;

En tant que chaîne unicode avec boucle de décryptage:

// myKey = "mysupersupersecretpasswordthatyouwillneverguess";
wchar_t myKey[48];

myKey[21] = 0x00A6;
myKey[10] = 0x00B0;
myKey[29] = 0x00A1;
myKey[22] = 0x00A2;
myKey[19] = 0x00B4;
myKey[33] = 0x00A2;
myKey[0] = 0x00B8;
myKey[32] = 0x00A0;
myKey[16] = 0x00B0;
myKey[40] = 0x00B0;
myKey[4] = 0x00A5;
myKey[26] = 0x00A1;
myKey[18] = 0x00A5;
myKey[17] = 0x00A1;
myKey[8] = 0x00A0;
myKey[36] = 0x00B9;
myKey[34] = 0x00BC;
myKey[44] = 0x00B0;
myKey[30] = 0x00AC;
myKey[23] = 0x00BA;
myKey[35] = 0x00B9;
myKey[25] = 0x00B1;
myKey[6] = 0x00A7;
myKey[27] = 0x00BD;
myKey[45] = 0x00A6;
myKey[3] = 0x00A0;
myKey[28] = 0x00B4;
myKey[14] = 0x00B6;
myKey[7] = 0x00A6;
myKey[11] = 0x00A7;
myKey[13] = 0x00B0;
myKey[39] = 0x00A3;
myKey[9] = 0x00A5;
myKey[2] = 0x00A6;
myKey[24] = 0x00A7;
myKey[46] = 0x00A6;
myKey[43] = 0x00A0;
myKey[37] = 0x00BB;
myKey[41] = 0x00A7;
myKey[15] = 0x00A7;
myKey[31] = 0x00BA;
myKey[1] = 0x00AC;
myKey[47] = 0x00D5;
myKey[20] = 0x00A6;
myKey[5] = 0x00B0;
myKey[38] = 0x00B0;
myKey[42] = 0x00B2;
myKey[12] = 0x00A6;

for (unsigned int fngdouk = 0; fngdouk < 48; fngdouk++) myKey[fngdouk] ^= 0x00D5;

Chaîne comme variable globale:

// myKey = "mysupersupersecretpasswordthatyouwillneverguess";
unsigned char myKey[48] = { 0xAF, 0xBB, 0xB5, 0xB7, 0xB2, 0xA7, 0xB4, 0xB5, 0xB7, 0xB2, 0xA7, 0xB4, 0xB5, 0xA7, 0xA5, 0xB4, 0xA7, 0xB6, 0xB2, 0xA3, 0xB5, 0xB5, 0xB9, 0xB1, 0xB4, 0xA6, 0xB6, 0xAA, 0xA3, 0xB6, 0xBB, 0xB1, 0xB7, 0xB9, 0xAB, 0xAE, 0xAE, 0xB0, 0xA7, 0xB8, 0xA7, 0xB4, 0xA9, 0xB7, 0xA7, 0xB5, 0xB5, 0x42 };

for (unsigned int dzxykdo = 0; dzxykdo < 48; dzxykdo++) myKey[dzxykdo] -= 0x42;

Non, j'ai utilisé le site Web stringencrypt.com pour faire le travail. Il a des exemples pour C / C ++ stringencrypt.com/c-cpp-encryption que vous pourriez envisager de l'utiliser pour automatiser le cryptage de chaînes simples.
Bartosz Wójcik

2

Un peu dépendant de ce que vous essayez de protéger, comme le souligne Joshperry. Par expérience, je dirais que si cela fait partie d'un système de licence pour protéger votre logiciel, ne vous inquiétez pas. Ils procéderont éventuellement à une rétro-ingénierie. Utilisez simplement un chiffrement simple comme ROT-13 pour le protéger des attaques simples (ligne exécutant des chaînes dessus). Si c'est pour sécuriser les données sensibles des utilisateurs, je me demanderais si la protection de ces données avec une clé privée stockée localement est une sage décision. Encore une fois, cela revient à ce que vous essayez de protéger.

EDIT: Si vous allez le faire, une combinaison de techniques que Chris souligne sera bien meilleure que rot13.


2

Comme cela a été dit précédemment, il n'y a aucun moyen de protéger totalement votre corde. Mais il existe des moyens de le protéger avec une sécurité raisonnable.

Quand j'ai dû faire cela, j'ai mis une chaîne d'apparence innocente dans le code (un avis de droit d'auteur, par exemple, ou une fausse invite utilisateur ou tout autre élément qui ne sera pas modifié par quelqu'un corrigeant un code non lié), crypté cela en utilisant lui-même comme clé, haché cela (en ajoutant du sel) et utilisé le résultat comme clé pour chiffrer ce que je voulais réellement chiffrer.

Bien sûr, cela pourrait être piraté, mais il faut un hacker déterminé pour le faire.


Bonne idée - une autre forme d'obscurité consiste à utiliser une chaîne qui est encore raisonnablement forte (longue, ponctuation et tout ce jazz) mais qui ne ressemble évidemment pas à un mot de passe.
Thomi

1

Si vous êtes sur DPAPI utilisateur Windows, http://msdn.microsoft.com/en-us/library/ms995355.aspx

Comme indiqué dans un article précédent, si vous êtes sur Mac, utilisez le trousseau.

Fondamentalement, toutes ces idées mignonnes sur la façon de stocker votre clé privée dans votre binaire sont suffisamment pauvres du point de vue de la sécurité pour que vous ne devriez pas les faire. Quiconque obtient votre clé privée est un gros problème, ne la gardez pas dans votre programme. Selon la façon dont vous importez votre application, vous pouvez conserver vos clés privées sur une carte à puce, sur un ordinateur distant avec lequel votre code communique ou vous pouvez faire ce que font la plupart des gens et la conserver dans un endroit très sécurisé sur l'ordinateur local (la clé " store "qui est un peu comme un registre sécurisé étrange) qui est protégé par des autorisations et toute la force de votre système d'exploitation.

C'est un problème résolu et la réponse n'est PAS de garder la clé à l'intérieur de votre programme :)


1

Essayez ceci . Le code source explique comment chiffrer et déchiffrer à la volée toutes les chaînes d'un projet Visual Studio c ++ donné.


1

Une méthode que j'ai récemment essayée est:

  1. Prenez le hachage (SHA256) des données privées et remplissez-le dans le code comme part1
  2. Prenez XOR de données privées et leur hachage et remplissez-le dans le code comme part2
  3. Remplissez les données: ne les stockez pas sous char str [], mais remplissez-les à l'exécution en utilisant les instructions d'affectation (comme indiqué dans la macro ci-dessous)
  4. Maintenant, générez les données privées au moment de l'exécution en prenant le XOR de part1etpart2
  5. Étape supplémentaire : calculez le hachage des données générées et comparez-le avec part1. Il vérifiera l'intégrité des données privées.

MACRO pour remplir les données:

Supposons que les données privées soient de 4 octets. Nous définissons une macro pour cela qui enregistre les données avec des instructions d'affectation dans un ordre aléatoire.

#define POPULATE_DATA(str, i0, i1, i2, i3)\
{\
    char *p = str;\
    p[3] = i3;\
    p[2] = i2;\
    p[0] = i0;\
    p[1] = i1;\
}

Maintenant, utilisez cette macro dans le code où vous devez enregistrer part1et part2, comme suit:

char part1[4] = {0};
char part2[4] = {0};
POPULATE_DATA(part1, 1, 2, 3, 4); 
POPULATE_DATA(part2, 5, 6, 7, 8);

0

Au lieu de stocker la clé privée dans votre exécutable, vous souhaiterez peut-être la demander à l'utilisateur et la stocker au moyen d'un gestionnaire de mots de passe externe , quelque chose de similaire à Mac OS X Keychain Access.


eh bien oui et non .. normalement, je suis d'accord avec vous, mais dans ce cas, j'essaie de cacher cela à l'utilisateur du logiciel, donc le stocker dans un système externe n'est pas une bonne idée (de nombreux systèmes de trousseau peuvent exposer les mots de passe sous forme de texte brut aux utilisateurs disposant d'une autorisation appropriée). Le logiciel de trousseau est idéal pour les mots de passe des utilisateurs, mais pas pour les clés de chiffrement d'application.
Thomi

si cela ne vous semble pas sûr (même si, compte tenu de vos clarifications, cela est peut-être suffisant), vous pouvez combiner un trousseau et quelque chose de codé en dur :)

0

Dépend du contexte, mais vous pouvez simplement stocker le hachage de la clé plus un sel (chaîne constante, facile à obscurcir).

Ensuite, lorsque (si) l'utilisateur entre la clé, vous ajoutez le sel , calculez le hachage et comparez.

Le sel est probablement inutile dans ce cas, il arrête une attaque de dictionnaire par force brute si le hachage peut être isolé (une recherche Google a également été connue pour fonctionner).

Un hacker n'a encore qu'à insérer une instruction jmp quelque part pour contourner le tout, mais c'est un peu plus compliqué qu'une simple recherche de texte.


Il s'agit d'une clé de cryptage et non d'un hachage de mot de passe. J'ai besoin de la clé réelle pour encoder et décoder les données. L'utilisateur ne voit jamais la clé, elle n'est jamais stockée en dehors du binaire.
Thomi

0

Il existe un obfuscate de projet (très léger) en-tête uniquement créé par adamyaxley qui fonctionne parfaitement. Il est basé sur des fonctions lambda et des macros et crypte les chaînes littéralement avec un chiffrement XOR au moment de la compilation. Si nécessaire, nous pouvons changer la graine de chaque chaîne.

Le code suivant ne stockera pas la chaîne "hello world" dans le binaire compilé.

#include "obfuscate.h"

int main()
{
  std::cout << AY_OBFUSCATE("Hello World") << std::endl;
  return 0;
}

J'ai testé avec c ++ 17 et Visual Studio 2019, et vérifie via IDA et je confirme que la chaîne est masquée. Un avantage précieux par rapport à ADVobfuscator est qu'il est convertible en std :: string (tout en étant toujours caché dans le binaire compilé):

std::string var = AY_OBFUSCATE("string");
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.