在 C++ 中隐藏敏感字符串的技术

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1648618/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 20:46:15  来源:igfitidea点击:

techniques for obscuring sensitive strings in C++

c++securityobfuscationdefensive-programming

提问by Thomi

I need to store sensitive information (a symmetric encryption key that I want to keep private) in my C++ application. The simple approach is to do this:

我需要在我的 C++ 应用程序中存储敏感信息(我想保密的对称加密密钥)。简单的方法是这样做:

std::string myKey = "mysupersupersecretpasswordthatyouwillneverguess";

std::string myKey = "mysupersupersecretpasswordthatyouwillneverguess";

However, running the application through the stringsprocess (or any other that extracts strings from a binary app) will reveal the above string.

但是,通过strings进程运行应用程序(或任何其他从二进制应用程序中提取字符串的应用程序)将显示上述字符串。

What techniques should be used to obscure such sensitive data?

应该使用什么技术来隐藏这些敏感数据?

Edit:

编辑:

OK, so pretty much all of you have said "your executable can be reverse engineered"- of course! This is a pet peeve of mine, so I'm going to rant a bit here:

好吧,你们几乎所有人都说过“你的可执行文件可以被逆向工程”——当然!这是我最讨厌的地方,所以我要在这里吐槽一下:

Why is it that 99% (OK, so perhaps I exaggerate a little) of all security-related questions on this site are answered with a torrent of "there is no possible way to create a perfectly secure program" - that is not a helpful answer! Security is a sliding scale between perfect usability and no security at one end, and perfect security but no usability at the other.

为什么本网站上 99%(好吧,也许我夸大了一点)所有与安全相关的问题都以“没有可能的方法来创建一个完全安全的程序”的洪流来回答 - 这没有帮助回答!安全性是一个在完美可用性和无安全性之间的滑动比例,另一端是完美安全但没有可用性。

The point is that you pick your position on that sliding scale depending on what you're trying to do and the environment in which your software will run. I'm not writing an app for a military installation, I'm writing an app for a home PC. I need to encrypt data across an untrusted network with a pre-known encryption key. In these cases, "security through obscurity" is probably good enough! Sure, someone with enough time, energy and skill could reverse-engineer the binary and find the password, but guess what? I don't care:

关键是您根据您尝试做的事情以及您的软件将在其中运行的环境在该滑动比例上选择您的位置。我不是为军事设施编写应用程序,而是为家用 PC 编写应用程序。我需要使用预先知道的加密密钥在不受信任的网络上加密数据。在这些情况下,“通过默默无闻的安全性”可能就足够了!当然,有足够时间、精力和技能的人可以对二进制文件进行逆向工程并找到密码,但你猜怎么着?我不在乎:

The time it takes me to implement a top-notch secure system is more expensive than the loss of sales due to the cracked versions (not that I'm actually selling this, but you get my point). This blue-sky "lets do it the absolute best way possible" trend in programming amongst new programmers is foolish to say the least.

我实施一流的安全系统所花费的时间比由于破解版本造成的销售损失更昂贵(并不是说我实际上在卖这个,但你明白我的意思)。至少可以说,这种蓝天“让我们以绝对最好的方式去做”的编程趋势在新程序员中是愚蠢的。

Thank you for taking the time to answer this question - they were most helpful. Unfortunately I can only accept one answer, but I've up-voted all the useful answers.

感谢您花时间回答这个问题 - 他们是最有帮助的。不幸的是,我只能接受一个答案,但我对所有有用的答案都投了赞成票。

采纳答案by csl

Basically, anyone with access to your program and a debugger canand willfind the key in the application if they want to.

基本上,任何有权访问您的程序和调试器的人都可以并且在需要时在应用程序中找到密钥。

But, if you just want to make sure the key doesn't show up when running stringson your binary, you could for instance make sure that the key is not within the printable range.

但是,如果您只想确保在strings二进制文件上运行时密钥不显示,您可以例如确保密钥不在可打印范围内。

Obscuring key with XOR

用 XOR 隐藏密钥

For instance, you could use XOR to split the key into two byte arrays:

例如,您可以使用 XOR 将密钥拆分为两个字节数组:

key = key1 XOR key2

If you create key1 with the same byte-length as keyyou can use (completely) random byte values and then compute key2:

如果您创建具有相同字节长度的 key1,key您可以使用(完全)随机字节值,然后计算key2

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

You can do this in your build environment, and then only store key1and key2in your application.

您可以在构建环境中执行此操作,然后仅将key1和存储key2在您的应用程序中。

Protecting your binary

保护你的二进制文件

Another approach is to use a tool to protect your binary. For instance, there are several security tools that can make sure your binary is obfuscated and starts a virtual machine that it runs on. This makes it hard(er) to debug, and is also the convential way many commercial grade secure applications (also, alas, malware) is protected.

另一种方法是使用工具来保护您的二进制文件。例如,有几种安全工具可以确保您的二进制文件被混淆并启动运行它的虚拟机。这使得调试变得困难(呃),而且也是许多商业级安全应用程序(还有,唉,恶意软件)受到保护的常规方式。

One of the premier tools is Themida, which does an awesome job of protecting your binaries. It is often used by well known programs, such as Spotify, to protect against reverse engineering. It has features to prevent debugging in programs such as OllyDbg and Ida Pro.

Themida是最重要的工具之一,它在保护二进制文件方面做得非常出色。众所周知的程序(例如 Spotify)经常使用它来防止逆向工程。它具有防止在 OllyDbg 和 Ida Pro 等程序中进行调试的功能。

There is also a larger list, maybe somewhat outdated, of tools to protect your binary.
Some of them are free.

还有一个更大的列表,可能有些过时了,用于保护您的二进制文件工具
其中一些是免费的。

Password matching

密码匹配

Someone here discussed hashing password+salt.

这里有人讨论了哈希密码+盐。

If you need to store the key to match it against some kind of user submitted password, you should use a one-way hashing function, preferrably by combining username, password and a salt. The problem with this, though, is that your application has to know the salt to be able to do the one-way and compare the resulting hashes. So therefore you still need to store the salt somewhere in your application. But, as @Edward points out in the comments below, this will effectively protect against a dictionary attack using, e.g, rainbow tables.

如果您需要存储密钥以将其与某种用户提交的密码相匹配,您应该使用单向散列函数,最好是结合用户名、密码和盐。但是,这样做的问题在于,您的应用程序必须知道 salt 才能单向执行并比较结果哈希。因此,您仍然需要将盐存储在应用程序中的某个位置。但是,正如@Edward 在下面的评论中指出的那样,这将有效地防止使用例如彩虹表的字典攻击。

Finally, you can use a combination of all the techniques above.

最后,您可以结合使用上述所有技术。

回答by Chris Jefferson

First of all, realise that there is nothing you can do that will stop a sufficiently determined hacker, and there are plenty of those around. The protection on every game and console around is cracked eventually, so this is only a temporary fix.

首先,要意识到没有什么可以阻止一个足够坚定的黑客,而且周围有很多这样的黑客。每个游戏和控制台的保护最终都会被破解,所以这只是一个临时修复。

There are 4 things you can do that will increase you chances of staying hidden for a while.

您可以做 4 件事来增加隐藏一段时间的机会。

1) Hide the elements of the string in some way -- something obvious like xoring ( the ^ operator) the string with another string will be good enough to make the string impossible to search for.

1) 以某种方式隐藏字符串的元素——很明显,像异或(^ 运算符)字符串与另一个字符串将足以使字符串无法搜索。

2) Split the string into pieces -- split up your string and pop bits of it into strangely named methods in strange modules. Don't make it easy to search through and find the method with the string in it. Of course some method will have to call all these bits, but it still makes it a little harder.

2) 将字符串拆分为多个部分——将您的字符串拆分并将其中的一部分弹出到奇怪模块中的奇怪命名方法中。不要让搜索和找到包含字符串的方法变得容易。当然,某些方法必须调用所有这些位,但这仍然使它变得有点困难。

3) Don't ever build the string in memory -- most hackers use tools that let them see the string in memory after you have encoded it. If possible, avoid this. If for example you are sending the key off to a server, send it character by character, so the whole string is never around. Of course, if you are using it from something like RSA encoding, then this is trickier.

3)永远不要在内存中构建字符串——大多数黑客使用工具让他们在你编码后看到内存中的字符串。如果可能,请避免这种情况。例如,如果您将密钥发送到服务器,请逐个字符地发送它,因此整个字符串永远不会存在。当然,如果您从 RSA 编码之类的东西中使用它,那么这将更加棘手。

4) Do an ad-hoc algorithm -- on top of all this, add a unique twist or two. Maybe just add 1 to everything you produce, or do any encryption twice, or add a sugar. This just makes it a little harder for the hacker who already knows what to look for when someone is using, for example, vanilla md5 hashing or RSA encryption.

4)做一个特别的算法——在这一切之上,添加一个或两个独特的扭曲。也许只是在你生产的所有东西上加 1,或者做两次加密,或者加糖。这只是让已经知道在有人使用时要寻找什么的黑客变得有点困难,例如,vanilla md5 散列或 RSA 加密。

Above all, make sure it isn't too important when (and it will be when if you application becomes popular enough) your key is discovered!

最重要的是,确保在何时(并且如果您的应用程序变得足够流行)您的密钥被发现并不太重要!

回答by Paul Sasik

A strategy i've used in the past is to create an array of seemingly-random characters. You initially insert, and then locate your particular characters with a algebraic process where each step from 0 to N will yield a number < size of the array which contains the next char in your obfuscated string. (This answer is feeling obfuscated now!)

我过去使用的一种策略是创建一组看似随机的字符。您最初插入,然后使用代数过程定位您的特定字符,其中从 0 到 N 的每一步都会产生一个数字 < 数组的大小,其中包含混淆字符串中的下一个字符。(这个答案现在感觉很模糊!)

Example:

例子:

Given an array of chars (numbers and dashes are for reference only)

给定一个字符数组(数字和破折号仅供参考)

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

And an equation whose first six results are: 3, 6, 7, 10, 21, 47

一个方程的前六个结果是:3, 6, 7, 10, 21, 47

Would yield the word "HELLO!" from the array above.

会产生“你好!”这个词 从上面的数组。

回答by Bartosz Wójcik

I've created a simple encryption tool for strings, it can automatically generate encrypted strings and has a few extra options to do that, a few examples:

我为字符串创建了一个简单的加密工具,它可以自动生成加密的字符串,并有一些额外的选项可以做到这一点,举几个例子:

String as a global variable:

字符串作为全局变量:

// 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;

As unicode string with decryption loop:

作为带解密循环的 unicode 字符串:

// 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;

String as a global variable:

字符串作为全局变量:

// 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;

回答by Frerich Raabe

Of course, storing private data in software which is shipped to the user is always a risk. Any sufficiently educated (and dedicated) engineer could reverse engineer the data.

当然,将私人数据存储在交付给用户的软件中始终存在风险。任何受过足够教育(和敬业)的工程师都可以对数据进行逆向工程。

That being said, you can often make things secure enough by raising the barrier which people need to overcome to reveal your private data. That's usually a good compromise.

话虽如此,您通常可以通过提高人们在泄露您的私人数据时需要克服的障碍来使事情变得足够安全。这通常是一个很好的妥协。

In your case, you could clutter your strings with non-printable data, and then decode that at runtime using a simple helper function, like this:

在您的情况下,您可以使用不可打印的数据来混淆字符串,然后在运行时使用简单的辅助函数对其进行解码,如下所示:

void unscramble( char *s )
{
    for ( char *str = s + 1; *str != 0; str += 2 ) {
        *s++ = *str;
    }
    *s = '
std::string myKey = part1() + part2() + ... + partN();
'; } void f() { char privateStr[] = "
#define POPULATE_DATA(str, i0, i1, i2, i3)\
{\
    char *p = str;\
    p[3] = i3;\
    p[2] = i2;\
    p[0] = i0;\
    p[1] = i1;\
}
1H
char part1[4] = {0};
char part2[4] = {0};
POPULATE_DATA(part1, 1, 2, 3, 4); 
POPULATE_DATA(part2, 5, 6, 7, 8);
2e##代码##3l##代码##4l##代码##5o"; unscramble( privateStr ); // privateStr is 'Hello' now. string s = privateStr; // ... }

回答by Nick Dandoulakis

I agree with @Checkers, your executable can be reverse-engineered.

我同意@Checkers,您的可执行文件可以进行逆向工程。

A bit better way is to create it dynamically, for example:

更好的方法是动态创建它,例如:

##代码##

回答by sbi

As was said before, there's no way to totally protect your string. But there are ways to protect it wis a reasonable safety.

如前所述,没有办法完全保护您的琴弦。但是有一些方法可以在合理的安全范围内保护它。

When I had to do this, I did put some innocent looking string into the code (a copyright notice, for example, or some faked user prompt or anything else that won't be changed by someone fixing unrelated code), encrypted that using itself as a key, hashed that (adding some salt), and used the result as a key to encrypt what I actually wanted to encrypt.

当我不得不这样做时,我确实在代码中放入了一些看起来很无辜的字符串(例如,版权声明,或者一些伪造的用户提示或其他任何不会被修复无关代码的人更改的东西),使用自身对其进行加密作为密钥,对其进行散列(添加一些盐),并将结果用作密钥来加密我真正想要加密的内容。

Of course this could be hacked, but it does take a determined hacker to do so.

当然,这可能会被黑客入侵,但确实需要坚定的黑客才能这样做。

回答by Nic Strong

Somewhat dependent on what you are trying to protect as joshperry points out. From experience, I would say that if it is part of some licensing scheme to protect your software then don't bother. They will eventially reverse engineer it. Simply use a simple cipher like ROT-13 to protect it from simple attacks (line running strings over it). If it is to secure users sensitive data I would be questioning whether protecting that data with a private key stored locally is a wise move. Again it comes down to what you are trying to protect.

正如 joshperry 指出的那样,有点依赖于你想要保护的东西。根据经验,我会说,如果它是某些许可计划的一部分来保护您的软件,那么请不要打扰。他们最终将对其进行逆向工程。只需使用像 ROT-13 这样的简单密码来保护它免受简单攻击(在其上运行字符串的行)。如果是为了保护用户敏感数据,我会质疑使用本地存储的私钥保护该数据是否是明智之举。再次归结为您要保护的对象。

EDIT: If you are going to do it then a combination of techniques that Chris points out will be far better than rot13.

编辑:如果您打算这样做,那么 Chris 指出的技术组合将比 rot13 好得多。

回答by Michael Haephrati

Try this. The source code explains how to encrypt and decrypt on the fly all strings in a given Visual Studio c++ project.

试试这个。源代码解释了如何动态加密和解密给定 Visual Studio c++ 项目中的所有字符串。

回答by Zeeshan Mahmood

One method I recently tried is:

我最近尝试的一种方法是:

  1. Take hash (SHA256) of the private data and populate it in code as part1
  2. Take XOR of private data and its hash and populate it in code as part2
  3. Populate data: Don't store it as char str[], but populate on runtime using assignment instructions (as shown in macro below)
  4. Now, generate the private data on run time by taking the XOR of part1and part2
  5. Additional step: Calculate hash of generated data and compare it with part1. It will verify the integrity of private data.
  1. 获取私有数据的哈希(SHA256)并将其填充到代码中 part1
  2. 对私有数据及其散列进行异或并将其填充在代码中 part2
  3. 填充数据:不要将其存储为 char str[],而是在运行时使用赋值指令填充(如下面的宏所示)
  4. 现在,通过对part1和进行异或来生成运行时的私有数据part2
  5. 附加步骤:计算生成数据的哈希值并将其与part1. 它将验证私有数据的完整性。

MACRO to populate data:

MACRO 填充数据:

Suppose, private data is of 4 bytes. We define a macro for it which saves the data with assignment instructions in some random order.

假设,私有数据是 4 个字节。我们为它定义了一个宏,它以某种随机顺序保存带有赋值指令的数据。

##代码##

Now use this macro in code where you need to save part1and part2, as follows:

现在在需要保存part1and 的代码中使用这个宏part2,如下所示:

##代码##