如何避免对加密密钥进行硬编码 (Objective C)?

How to avoid hardcoding keys for encryption (Objective C)?

在我的 Objective C 代码中,我有一个消费者密钥和密码硬编码在我的代码中,用于 SHA-1 加密。我想知道的是我是否可以避免硬编码以提高安全性。到目前为止,我发现了以下内容,

发现 1 https://www.owasp.org/index.php/Technical_Risks_of_Reverse_Engineering_and_Unauthorized_Code_Modification#Cryptographic_Key_Replacement 步骤说明如下,

  1. 损坏在源代码中声明的静态键。此类密钥应在磁盘上损坏,以防止对手分析和拦截原始密钥;

  2. 接下来,应用程序应该在需要密钥的代码使用它之前修复密钥;

  3. 在使用密钥之前,应用程序应立即执行密钥值的校验和,以验证未损坏的密钥是否与代码在构建时声明的值相匹配;和

  4. 最后,在应用程序完成对特定调用的使用后,应用程序应立即重新损坏内存中的密钥。

发现 2 https://github.com/UrbanApps/UAObfuscatedString

有人可以帮我吗?

示例代码:

+ (NSString *) getOauthHeaderForRequestString:(NSString *)requestString {

NSString *oauthConsumerKey = @"<consumer key which I want avoid hardcoding>";
NSString *oauthConsumerSecret = @"<consumer secret which I want to avoid hardcoding>";
NSString *oauthSignatureMethod = @"HMAC-SHA1";
NSString *oauthVersion = @"1.0";

NSString *oauthNonce = [self generateNonce];
NSString *oauthtimestamp = [NSString stringWithFormat:@"%d", (int)[[NSDate date] timeIntervalSince1970]];

NSArray * params = [NSArray arrayWithObjects:
                    [NSString stringWithFormat:@"%@%%3D%@", @"oauth_consumer_key", oauthConsumerKey],
                    [NSString stringWithFormat:@"%@%%3D%@", @"oauth_nonce", oauthNonce],
                    [NSString stringWithFormat:@"%@%%3D%@", @"oauth_signature_method", oauthSignatureMethod],
                    [NSString stringWithFormat:@"%@%%3D%@", @"oauth_timestamp", oauthtimestamp],
                    [NSString stringWithFormat:@"%@%%3D%@", @"oauth_version", oauthVersion],
                    [NSString stringWithFormat:@"%@%%3D%@", @"request", [requestString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]],
                    nil];

params = [params sortedArrayUsingSelector:@selector(compare:)];
NSString *parameters = [params componentsJoinedByString:@"%26"];

NSString *postURL = @"<my post url>";

NSArray * baseComponents = [NSArray arrayWithObjects:
                            @"POST",
                            [self encodeString:postURL],
                            parameters,
                            nil];
NSString * baseString = [baseComponents componentsJoinedByString:@"&"];

NSArray *signingKeyComponents = [NSArray arrayWithObjects:oauthConsumerSecret, @"", nil];
NSString *signingKey = [signingKeyComponents componentsJoinedByString:@"&"];

NSData *signingKeyData = [signingKey dataUsingEncoding:NSUTF8StringEncoding];
NSData *baseData = [baseString dataUsingEncoding:NSUTF8StringEncoding];

uint8_t digest[20] = {0};
CCHmac(kCCHmacAlgSHA1, signingKeyData.bytes, signingKeyData.length, baseData.bytes, baseData.length, digest);

NSData *signatureData = [NSData dataWithBytes:digest length:20];

NSString *oauthSignature = [self base64forData:signatureData];

// final request build
NSString *oauthHeader = @"OAuth ";
oauthHeader = [oauthHeader stringByAppendingFormat:@"oauth_consumer_key=\"%@\"",oauthConsumerKey];
oauthHeader = [oauthHeader stringByAppendingFormat:@",oauth_nonce=\"%@\"",oauthNonce];
oauthHeader = [oauthHeader stringByAppendingFormat:@",oauth_signature=\"%@\"",[self encodeString:oauthSignature]];
oauthHeader = [oauthHeader stringByAppendingFormat:@",oauth_signature_method=\"%@\"",oauthSignatureMethod];
oauthHeader = [oauthHeader stringByAppendingFormat:@",oauth_timestamp=\"%@\"",oauthtimestamp];
oauthHeader = [oauthHeader stringByAppendingFormat:@",oauth_version=\"1.0\""];

return oauthHeader;
}

所以我建议使用你的第二个发现

https://github.com/UrbanApps/UAObfuscatedString

并在第一次应用程序启动时将重新生成的字符串添加到钥匙串

因此所有方法都可以在您稍后的代码中使用钥匙串中的值。 我还建议构建一个单例,它可以提供对这些值的访问,也可以在您想要更改或更新此解决方案的时间将所有内容集中在一起

一般来说,我写的可能 api 键只是在代码中内联,有时为了安全起见声明它们将其分成两部分。我用的是脑因子;)

假设你 API 键 xxx-xxxx-xxxx 我对字符串 xxx-xxxx-xFFF 进行了编码,如您所见,它与右键的长度相同,在其他方法的某处我剪切了 las FFF 并附加了右 xxx 后缀。

如果你有偏执狂,你可以在同一个文件中定义的不同 类 中进行操作,为方便起见,但保留按字母顺序排列的不同位置,如 AStoredKeys TheRightPostfixes 以某种方式隐藏了反汇编程序的方法。

您可以将密码单独保存在加密光盘或加密 U 盘上的文件中。您的代码只会从安全存储中检索适当的文件。攻击者在您的代码中看到的只是密码文件的名称。

如果这不可行,那么您可以在代码中对密码进行 XOR 加密。您的应用程序将包含如下文本字符串:"Please enter your password." 这看起来不像一个密钥,但您可以将该字符串用作 encrypt/decrypt 密码的 XOR 密钥。这更容易受到攻击,因为攻击者会看到您在代码中使用的 XOR 密钥并能够自行解密。

无论您使用什么方法,请记住在超出范围之前覆盖保存实际密码的变量。

我已经写了关于 the challenges of solving this problem before, but I wanted to demonstrate a little bit using your UAObfuscatedString idea, since I think this is the kind of solution most people are looking for, but is worse than nothing. It's important to note: I'm not particularly good at this. I'm not a seasoned reverse engineer and commercial systems are way beyond my skillset. I'm just a guy with Hopper 和五分钟的逆向工程工作(我 运行 一个计时器;5:35s,包括升级 Hopper,因为我没有 运行 几个月后)。

所以,我用 UAObfuscatedString 写了一个 iOS 程序。我使用 Swift 因为 Swift 通常比 ObjC 更难逆向工程。 ObjC is a reverse engineer's dream.

let identifier = "c".o.m.dot.u.r.b.a.n.a.p.p.s.dot.e.x.a.m.p.l.e

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    print(identifier)
    return true
}

然后我将其存档,因此它是优化代码等,就像您发送到 App Store 一样。然后我将它加载到 Hopper 并查看应用程序委托的 init。这就是常量初始化的地方,基于大多数人将这些东西粘在他们的应用程序委托中的假设。显然,如果我看到名为 KeyStorageSecretStuffHelper 的 class,我会先看那里....

void * -[_TtC13ObfuscateTest11AppDelegate init](void * self, void * _cmd) {
    *(r31 + 0xffffffffffffffe0) = r20;
    *(0xfffffffffffffff0 + r31) = r19;
    *(r31 + 0xfffffffffffffff0) = r29;
    *(r31 + 0x0) = r30;
    r0 = sub_100005e14();
    return r0;
}

嗯,它调用了这个匿名函数sub_100005e14()。让我们看看它做了什么。

...
0000000100005e38         adr        x0, #0x100006859                            ; "c"
...
0000000100005e48         bl         imp___stubs___T0SS18UAObfuscatedStringE1oSSfg
...
0000000100005e50         bl         imp___stubs___T0SS18UAObfuscatedStringE1mSSfg
...
0000000100005e74         bl         imp___stubs___T0SS18UAObfuscatedStringE3dotSSfg
...
0000000100005e98         bl         imp___stubs___T0SS18UAObfuscatedStringE1uSSfg
...
0000000100005ebc         bl         imp___stubs___T0SS18UAObfuscatedStringE1rSSfg
...
0000000100005ee0         bl         imp___stubs___T0SS18UAObfuscatedStringE1bSSfg
...
0000000100005f04         bl         imp___stubs___T0SS18UAObfuscatedStringE1aSSfg
...
0000000100005f28         bl         imp___stubs___T0SS18UAObfuscatedStringE1nSSfg
...
0000000100005f4c         bl         imp___stubs___T0SS18UAObfuscatedStringE1aSSfg
...
0000000100005f70         bl         imp___stubs___T0SS18UAObfuscatedStringE1pSSfg
...
0000000100005f94         bl         imp___stubs___T0SS18UAObfuscatedStringE1pSSfg
...
0000000100005fb8         bl         imp___stubs___T0SS18UAObfuscatedStringE1sSSfg
...
0000000100005fdc         bl         imp___stubs___T0SS18UAObfuscatedStringE3dotSSfg
...
0000000100006000         bl         imp___stubs___T0SS18UAObfuscatedStringE1eSSfg
...
0000000100006024         bl         imp___stubs___T0SS18UAObfuscatedStringE1xSSfg
...
0000000100006048         bl         imp___stubs___T0SS18UAObfuscatedStringE1aSSfg
...
000000010000606c         bl         imp___stubs___T0SS18UAObfuscatedStringE1mSSfg
...
0000000100006090         bl         imp___stubs___T0SS18UAObfuscatedStringE1pSSfg
...
00000001000060b4         bl         imp___stubs___T0SS18UAObfuscatedStringE1lSSfg
...
00000001000060d8          bl         imp___stubs___T0SS18UAObfuscatedStringE1eSSfg

我不确定为什么 Swift demangler 在这里不起作用,但无论如何,我们可以很容易地看到模式:

_T0SS18UAObfuscatedStringE1oSSfg => o
_T0SS18UAObfuscatedStringE1mSSfg => m
_T0SS18UAObfuscatedStringE3dotSSfg => dot => .
_T0SS18UAObfuscatedStringE1uSSfg => u
...

意识到有这些 USObfuscatedString 方法,我搜索了它并在使用混淆字符串的应用程序中找到了所有地方。如果我愿意稍微改进一下我的游戏并花一天左右的时间玩它,我可能会编写一个工具来自动提取每个 UAObfuscatedString 只需使用 otool 和二进制文件。

这是深刻的教训。您刚刚标记了所有要隐藏的字符串。一旦我意识到 UAObfuscatedString 是一个东西,你就让我 更容易 找到你的敏感信息。从字面上看,这比没有还糟糕。您在这里唯一的希望是攻击者不知道它的存在。这就是混淆的问题,也是混淆与安全的区别。

我还要强调一下,我花了5分35秒来攻击这个程序。是的,我基本上知道我在寻找什么样的东西,但我也没有这方面的技能。如果 UAObfuscatedString 变得流行,我向您保证,auto-detect/de-obfuscate 工具将成为每个脚本小子工具箱的一部分("script-kiddie" 是安全人员所说的攻击者,他们不知道自己在做什么正在做,并且只使用其他人编写的自动化工具)。

这里的教训是,如果你想混淆,最好自己编一些 运行dom 方法。它不会有效,但它可能不会像大多数 FOSS 解决方案那样对您的目标产生积极的危害。 "Free and open source" 对于安全性来说可能非常好,但对于默默无闻来说却是最糟糕的事情。

如果隐藏信息对你的商业计划真的很重要并且你不能改变你的商业计划,那么你应该期望在这个问题上花很多钱并聘请一个团队致力于不断改进你的混淆系统以保持领先于将适应您构建的任何内容的攻击者。

老实说,您无能为力。 Literals 可能是最好的选择,但考虑到您真的不知道谁将拥有设备以及他们用它做什么。可以安全地假设任何有才华的工程师都能够获得设备上的密钥和代码。即使您保护好自己的密钥,也有一些应用程序(例如屏幕抓取工具和其他应用程序)无法真正获取信息。混淆不是一个好的长期做法,优秀的工程师可以找到解决这个问题的方法......它只会减慢他们的速度。您确实可以控制您的服务器,因此请专注于保持稳定。

一般来说,与其考虑保护您的密钥,不如多考虑限制信息和访问。

关于数据,尽量减少接收私人数据。例如,从不显示信用卡号,只存储最后 4 位数字。或者在银行应用程序的情况下,只显示账户余额,它们还提供双因素身份验证(例如图像)。如果设备遭到破坏,虽然知道一个人的余额会很糟糕,但这并不是世界末日。

这也往往是良好的公司惯例,因为它可以让您免受任何指控。如果您一开始就没有信息,那么一个人就不能声称该信息已泄露。

通过访问权限,限制应用的功能。从某种意义上说,防火墙。或者提供其他身份验证步骤,如短信验证、双因素身份验证。您还可以让服务器为您执行安全任务。再次以银行业务为例,不是允许无限制的转账,而是限制转账的次数或金额。限制它可以转移到的帐户。