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 consiste à le faire:
std::string myKey = "mysupersupersecretpasswordthatyouwillneverguess";
Cependant, l'exécution de l'application via le processus strings
(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?
Modifier:
OK, donc vous avez tous dit "votre exécutable peut être rétroconçu" - bien sûr! Ceci est une bête noire de mon animal de compagnie, je vais donc un peu déclamer ici:
Pourquoi est-ce que 99% (OK, alors j'exagère peut-être un peu) de toutes les questions liées à la sécurité sur ce site sont répondues par un torrent "il n'y a pas de 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 utilisation parfaite et aucune sécurité à une extrémité, et une sécurité parfaite mais aucune utilisation à 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 s'exécutera. Je n'écris pas d'application pour une installation militaire, j'écris une application pour un PC domestique . J'ai besoin de crypter les données sur un réseau non fiable 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étence pourrait effectuer une rétro-ingénierie du binaire et trouver le mot de passe, mais devinez quoi? Je m'en fiche:
Le temps qu'il me faut pour mettre en œuvre un système sécurisé de premier ordre est plus cher que la perte de ventes due aux versions craquées (pas que je vends réellement cela, mais vous comprenez mon argument). Cette tendance du ciel bleu "permet de le faire de la meilleure façon possible" dans la programmation parmi les nouveaux programmeurs est pour le moins idiote.
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é contre toutes les réponses utiles.
Fondamentalement, toute personne ayant accès à votre programme et à un débogueur peut et le fera trouvera la clé dans l'application si elle le souhaite.
Mais, si vous voulez simplement vous assurer que la clé n'apparaît pas lors de l'exécution de strings
sur votre binaire, vous pouvez par exemple vous assurer que la clé ne se trouve pas dans la plage imprimable.
Clé d'obscurcissement 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 key
, vous pouvez utiliser des valeurs d'octets aléatoires (complètement) 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 stocker uniquement key1
et key2
dans votre candidature.
Protection de 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 vous assurer que votre binaire est obscurci et démarre une machine virtuelle sur laquelle il s'exécute. Cela rend plus difficile (er) le débogage, et c'est également la manière conventionnelle de protéger de nombreuses applications sécurisées de qualité commerciale (également, hélas, les logiciels malveillants).
L'un des meilleurs 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 quelque peu dépassée, de outils pour protéger votre binaire .
Certains d'entre eux sont gratuits.
Correspondance du mot de passe
Quelqu'un a discuté ici du mot de passe de hachage + sel.
Si vous devez stocker la clé pour la faire correspondre à une sorte de mot de passe soumis par l'utilisateur, vous devez utiliser une fonction de hachage unidirectionnelle, de préférence en combinant le nom d'utilisateur, le 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. Par conséquent, vous devez 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 en utilisant, par exemple, les tableaux Rainbow.
Enfin, vous pouvez utiliser une combinaison de toutes les techniques ci-dessus.
Tout d'abord, sachez qu'il n'y a rien que vous puissiez faire qui puisse arrêter un pirate suffisamment déterminé, et il y en a beaucoup autour. La protection sur chaque jeu et console autour est finalement craquée, il ne s'agit donc que d'une correction temporaire.
Il y a 4 choses que vous pouvez faire pour augmenter vos chances de rester caché pendant un certain temps.
1) Masquer 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 assez bonne pour rendre la chaîne impossible à rechercher.
2) Divisez la chaîne en morceaux - divisez votre chaîne et éclatez-en des morceaux en méthodes étrangement nommées dans des modules étranges. Ne facilite pas la recherche et la recherche de la méthode avec la chaîne. Bien sûr, une méthode devra appeler tous ces bits, mais cela rend encore un peu plus difficile.
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 afin que la chaîne entière ne soit jamais là. Bien sûr, si vous l'utilisez à partir de quelque chose comme le codage RSA, c'est plus délicat.
4) Faites un algorithme ad hoc - en plus de tout cela, ajoutez une ou deux torsions uniques. Peut-être ajoutez simplement 1 à tout ce que vous produisez, ou effectuez deux fois le cryptage, ou ajoutez un sucre. Cela rend juste un peu plus difficile pour le pirate qui sait déjà quoi rechercher lorsque 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 sera quand si votre application devient assez populaire) votre clé est découverte!
Une stratégie que j'ai utilisée dans le passé consiste à créer un tableau de caractères apparemment aléatoires. Vous insérez d'abord, 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 obscurcie maintenant!)
Exemple:
Étant donné un tableau de caractères (les nombres et les tirets sont à titre indicatif uniquement)
0123456789
----------
ALFHNFELKD
LKFKFLEHGT
FLKRKLFRFK
FJFJJFJ!JL
Et une équation dont les six premiers résultats sont: 3, 6, 7, 10, 21, 47
Donnerait le mot "BONJOUR!" du tableau ci-dessus.
J'ai créé un outil de cryptage simple pour les chaînes, il peut générer automatiquement des chaînes cryptées et a 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échiffrement:
// 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;
Je suis d'accord avec @Checkers, votre exécutable peut être rétroconçu.
Un meilleur moyen est de le créer dynamiquement, par exemple:
std::string myKey = part1() + part2() + ... + partN();
Bien sûr, le stockage de données privées dans un logiciel qui est livré à l'utilisateur est toujours un risque. Tout ingénieur suffisamment formé (et dévoué) pourrait procéder à une rétro-ingénierie des données.
Cela 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 lors 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;
// ...
}
Comme cela a été dit précédemment, il n'y a aucun moyen de protéger totalement votre chaîne. Mais il existe des moyens de le protéger avec une sécurité raisonnable.
Quand j'ai dû le faire, j'ai mis une chaîne d'apparence innocente dans le code (un avis de droit d'auteur, par exemple, ou une invite d'utilisateur falsifiée ou toute autre chose qui ne sera pas modifiée par quelqu'un fixant du code non lié), crypté celui-ci 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 pirate déterminé pour le faire.
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 embêtez pas. Ils vont éventuellement le désosser. Utilisez simplement un chiffrement simple comme ROT-13 pour le protéger contre les attaques simples (ligne exécutant des chaînes dessus). S'il s'agit de 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, alors une combinaison de techniques que Chris souligne sera bien meilleure que rot13.
Si vous utilisez Windows DPAPI, http://msdn.Microsoft.com/en-us/library/ms995355.aspx
Comme un article précédent l'a dit si vous êtes sur Mac, utilisez le trousseau.
Fondamentalement, toutes ces jolies idées 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 les fassiez pas. Quiconque obtient votre clé privée est un gros problème, ne la conservez pas dans votre programme. Selon le mode d'importation de votre application, vous pouvez conserver vos clés privées sur une carte à puce, sur un ordinateur distant auquel votre code parle ou vous pouvez faire ce que la plupart des gens font et le conserver dans un endroit très sécurisé sur l'ordinateur local (la "clé"). store "qui est un peu comme un registre sécurisé bizarre) qui est protégé par des autorisations et toute la force de votre système d'exploitation.
Il s'agit d'un problème résolu et la réponse n'est PAS de conserver la clé dans votre programme :)
J'ai récemment essayé une méthode:
part1
part2
part1
et part2
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 lui définissons une macro 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;\
}
Utilisez maintenant cette macro dans le code où vous devez enregistrer part1
et 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);
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é.
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.
Dépendant du contexte mais vous pouvez simplement stocker le hachage de la clé plus un sel (chaîne constante, facile à masquer).
Ensuite, lorsque (si) l'utilisateur entre la clé, vous ajoutez le sel, calculez le hachage et comparez.
Le sel n'est probablement pas nécessaire dans ce cas, il arrête une attaque par dictionnaire de force brute si le hachage peut être isolé (une recherche Google a également été connue pour fonctionner).
Un pirate n'a encore qu'à insérer une instruction jmp quelque part pour contourner le tout, mais c'est plutôt plus compliqué qu'une simple recherche de texte.