隐藏C ++敏感string的技巧
我需要在我的C ++应用程序中存储敏感信息(我想保密的对称encryption密钥)。 简单的方法是做到这一点:
std::string myKey = "mysupersupersecretpasswordthatyouwillneverguess";
但是,通过strings
进程(或从二进制应用程序提取string的任何其他应用程序)运行应用程序将显示上述string。
应该使用什么技术来掩盖这种敏感数据?
编辑:
好吧,几乎所有人都说“你的可执行文件可以被反向devise” – 当然! 这是我的宠物,所以我要在这里咆哮一下:
为什么99%(好吧,也许我夸大了一点)这个网站上的所有与安全相关的问题都是用“没有办法创build一个完全安全的程序”的洪stream来回答的 – 这不是一个有用的回答! 安全性是一种完美的可用性和无安全性之间的滑动规模,完美的安全性,但另一方面没有可用性。
重点在于你根据自己想要做的事情以及软件运行的环境来select你的职位。 我不是在写军事装置的应用程序,而是在为家用电脑编写应用程序 。 我需要使用预先已知的encryption密钥对不可信networking上的数据进行encryption。 在这些情况下,“通过默默无闻的安全”可能已经足够了! 当然,有足够的时间,精力和技能的人可以对二进制文件进行逆向工程并find密码,但猜猜看是什么? 我不在乎:
实施一stream的安全系统需要花费的时间比由于破解版本造成的销售损失要贵(不是说我实际上是在出售这个,但是你明白了我的观点)。 这个蓝天“让绝对的最好的方式可能”在新程序员的编程趋势是愚蠢的,至less可以说。
感谢您花时间回答这个问题 – 他们是最有帮助的。 不幸的是,我只能接受一个答案,但我已经投了所有有用的答案。
基本上,任何有权访问你的程序和debugging器的人都可以在应用程序中find密钥。
但是,如果只想确保在二进制文件上运行strings
时不出现密钥,则可以确保密钥不在可打印范围内。
用XOR遮挡按键
例如,可以使用XOR将密钥分成两个字节数组:
key = key1 XOR key2
如果使用与key
相同的字节长度创buildkey1,则可以使用(完全)随机字节值,然后计算key2
:
key1[n] = crypto_grade_random_number(0..255) key2[n] = key[n] XOR key1[n]
您可以在构build环境中执行此操作,然后仅将key1
和key2
存储在您的应用程序中。
保护你的二进制文件
另一种方法是使用一个工具来保护你的二进制文件。 例如,有几个安全工具可以确保您的二进制文件被混淆并启动运行的虚拟机。 这使得debugging变得困难,并且也是许多商业级安全应用程序(也是,恶意软件)受保护的传统方式。
其中一个重要的工具是Themida ,它可以保护你的二进制文件。 它经常被众所周知的程序(如Spotify)用来防止逆向工程。 它具有防止在OllyDbg和Ida Pro等程序中debugging的function。
还有一个更大的列表,可能有点过时, 保护你的二进制文件的工具 。
其中一些是免费的。
密码匹配
这里有人讨论了哈希密码+盐。
如果您需要存储密钥以匹配某种用户提交的密码,则应该使用单向散列函数,最好是将用户名,密码和盐组合起来。 然而,这个问题是,你的应用程序必须知道salt能够单向执行,并比较产生的哈希值。 因此,您仍然需要将盐存储在应用程序的某处。 但@Edward在下面的评论中指出,这将有效地防止使用例如彩虹表的字典攻击。
最后,你可以使用上述所有技术的组合。
首先,要意识到没有什么可以做的,这将阻止一个足够坚定的黑客,而且周围有很多人。 每个游戏和游戏机的保护最终都会被破解,所以这只是一个临时的解决scheme。
有4件事你可以做,这会增加你隐藏一段时间的机会。
1)以某种方式隐藏string的元素 – 像xoring(^运算符)这样的显式string与另一个string将足以使string无法search。
2)将string拆分成几块 – 将奇怪的模块中的string和popup位拆分为奇怪的方法。 不要简单地search并find其中的string的方法。 当然,有些方法必须调用所有这些位,但是这样做还是有点困难。
3)永远不要在内存中创buildstring – 大多数黑客使用的工具,让他们看到内存中的string编码后。 如果可能,避免这种情况。 例如,如果您将密钥发送给服务器,请逐字发送,以避免整个string出现。 当然,如果你从RSA编码中使用它,那么这是更棘手的。
4)做一个专门的algorithm – 最重要的是,添加一个或两个独特的扭曲。 也许只要把你生产的所有东西都加1,或者做两次encryption,或者加一个糖。 这对于那些已经知道在某人使用的时候已经知道要查找什么的黑客来说有点困难了,例如,vanilla md5散列或者RSAencryption。
最重要的是,当你的钥匙被发现的时候(当你的应用变得足够stream行的时候),确保它不是太重要!
我以前使用的策略是创build一个看似随机的字符数组。 首先插入,然后用一个代数过程来定位特定的字符,其中从0到N的每个步骤都会产生一个包含混淆string中下一个字符的数组的<数字。 (这个答案现在感觉混乱了!)
例:
给定一个字符数组(数字和破折号仅供参考)
0123456789 ---------- ALFHNFELKD LKFKFLEHGT FLKRKLFRFK FJFJJFJ!JL
而一个方程的前六个结果是:3,6,7,10,21,47
会产生“你好! 从上面的数组中。
我同意@Checkers,您的可执行文件可以被反向工程。
更好的方法是dynamic创build它,例如:
std::string myKey = part1() + part2() + ... + partN();
当然,将私人数据存储在运送给用户的软件中总是有风险的。 任何受过足够教育(和专门)的工程师都可以对数据进行逆向工程。
也就是说,通过提高人们需要克服的障碍来揭示您的私人数据,通常可以使事情变得安全。 这通常是一个很好的妥协。
在你的情况下,你可以用非打印的数据混乱你的string,然后使用一个简单的辅助函数在运行时解码,如下所示:
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; // ... }
我已经为string创build了一个简单的encryption工具,它可以自动生成encryption的string,并有几个额外的选项来做到这一点,例如:
string作为全局variables:
// 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;
作为unicodestring与解密循环:
// 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作为全局variables:
// 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;
当joshperry指出,有些依赖于你想要保护的东西。 从经验来看,我会说,如果它是保护你的软件的一些授权计划的一部分,那就不要麻烦了。 他们将会进行逆向工程。 只需使用一个简单的密码,如ROT-13来保护它免受简单的攻击(线上运行的string)。 如果是为了保护用户敏感数据,我会质疑用本地存储的私钥保护数据是否明智之举。 这又归结于你要保护的东西。
编辑:如果你打算这样做,那么克里斯指出的技术组合将远远好于rot13。
如前所述,没有办法完全保护你的string。 但有办法保护它合理的安全。
当我必须这样做的时候,我确实在代码中添加了一些无辜的string(例如版权声明,或者伪造的用户提示符或其他任何不会被修复不相关代码的人改变的东西),使用自身encryption作为一个关键,哈希(join一些盐),并用结果作为密钥encryption我实际上想要encryption。
当然,这可能会被黑客攻击,但确实需要黑客才能这样做。
您可能不想将私钥存储在可执行文件中,而是需要从用户那里申请,并通过外部密码pipe理器 (类似于Mac OS X Keychain Access)进行存储。
试试这个 。 源代码解释了如何在给定的Visual Studio c ++项目中dynamicencryption和解密所有string。
我最近尝试的一种方法是:
- 采取散列(SHA256)的私人数据和填充代码作为
part1
- 采取私人数据及其散列的XOR并将其作为
part2
填充到代码中 - 填充数据:不要将其存储为char str [],而是使用赋值指令在运行时填充(如下面的macros中所示)
- 现在,通过执行
part1
和part2
的XOR,在运行时生成私有数据 - 附加步骤 :计算生成的数据的散列,并将其与
part1
进行比较。 它将validation私人数据的完整性。
MACRO填充数据:
假设,私人数据是4个字节。 我们为它定义了一个macros,它以一些随机的顺序保存了赋值指令的数据。
#define POPULATE_DATA(str, i0, i1, i2, i3)\ {\ char *p = str;\ p[3] = i3;\ p[2] = i2;\ p[0] = i0;\ p[1] = i1;\ }
现在在需要保存part1
和part2
代码中使用这个macros,如下所示:
char part1[4] = {0}; char part2[4] = {0}; POPULATE_DATA(part1, 1, 2, 3, 4); POPULATE_DATA(part2, 5, 6, 7, 8);
依赖于上下文,但你可以只存储密钥加散列的盐 (常量string,容易模糊)。
然后当(如果)用户input密钥时,您添加盐 ,计算哈希值并进行比较。
在这种情况下, salt可能是不必要的,如果散列可以被隔离(Googlesearch也被认为是可行的),那么它会停止暴力字典攻击。
黑客仍然只需要在某处插入一条jmp指令来绕过整个地段,但这比简单的文本search要复杂得多。
如果您在Windows用户DPAPI, http://msdn.microsoft.com/en-us/library/ms995355.aspx
正如以前的post所说,如果你在Mac上使用钥匙链。
基本上所有关于如何将你的私钥存储在你的二进制文件里的这些可爱的想法从安全的angular度来说是非常糟糕的,你不应该这样做。 任何人获得你的私钥是一件大事,不要把它放在你的程序中。 根据您的应用程序的导入方式,您可以将私钥保存在智能卡,远程计算机上的代码中,或者您可以执行大多数人的操作,并将其保存在本地计算机上非常安全的位置(“密钥存储“这是像一个奇怪的安全registry)受权限和您的操作系统的所有力量保护。
这是一个解决的问题,答案是不保持您的程序中的关键:)