How to avoid hardcoding keys for encryption (Objec

2019-03-31 22:49发布

问题:

In my Objective C code, I have a consumer key and secret hardcoded in my code to be used in SHA-1 encryption. What I would like to know is whether I could avoid hardcoding to improve security. I have found the following so far,

Finding 1 https://www.owasp.org/index.php/Technical_Risks_of_Reverse_Engineering_and_Unauthorized_Code_Modification#Cryptographic_Key_Replacement Steps explained are as follows,

  1. Damage static keys that are declared in source code. Such keys should be damaged while on disk to prevent an adversary from analyzing and intercepting the original key;

  2. Next, the application should repair the key just before the code requiring the key uses it;

  3. Immediately before use of the key, the application should perform a checksum of the key’s value to verify that the non-damaged key matches the value that the code declares at build time; and

  4. Finally, the application should immediately re-damage the key in memory after the application has finished using it for that particular call.

Finding 2 https://github.com/UrbanApps/UAObfuscatedString

Can somebody help me please?

Sample code:

+ (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;
}

回答1:

I've written about 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 and literally five minutes of reverse engineering work (I ran a timer; 5:35s, including upgrading Hopper because I hadn't run it in a few months).

So, I wrote an iOS program with UAObfuscatedString. I used Swift because Swift is generally a little harder to reverse engineer than 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
}

I then archived it, so it's optimized code, etc. etc. just like you'd send to the App Store. I then loaded it up in to Hopper and looked at the app delegate's init. That's where constants are initialized, based on the assumption that most people stick this stuff in their app delegate. Obviously if I see a class named KeyStorage or SecretStuffHelper, I'd look there first....

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

Hmm, it calls this anonymous function sub_100005e14(). Let's see what that does.

...
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

I'm not sure why the Swift demangler isn't working here, but anyway, we can easily see the pattern:

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

Realizing there are these USObfuscatedString methods, I search for that and find everywhere in the app that uses obfuscated strings. If I was willing to up my game a little and spend a day or so playing with it, I could probably write a tool to automatically extract every UAObfuscatedString just using otool and the binary.

And this is the deep lesson. You've just labeled all the strings you want to hide. Once I realize that UAObfuscatedString is a thing, you just made it easier for me to find your sensitive information. It is literally worse than nothing. Your only hope here is that the attacker doesn't know this exists. That's the problem with obfuscation, and what separates obfuscation from security.

I want also to emphasize that I spent 5 minutes and 35 seconds attacking this program. Yes, I knew basically what kind of thing I was looking for, but I also have no skills at this. If UAObfuscatedString were to become popular, I assure you that the auto-detect/de-obfuscate tool would become part of every script-kiddie's toolbox ("script-kiddie" is what security folks call attackers who do not know what they're doing, and just use automated tools that others have written).

The lesson here is that if you want to obfuscate, you're better off making up some random approach yourself. It won't be effective, but it probably won't be actively harmful to your goal in the way that most FOSS solutions are. "Free and open source" can be very good for security, but are the worst possible thing for obscurity.

If hiding information is really important to your business plan and you can't change your business plan, then you should expect to spend a lot of money on this problem and hire a team of people dedicated to constantly evolving your obfuscation system to keep ahead of attackers who will adapt to whatever you build.



回答2:

So I recommend use yours second finding

https://github.com/UrbanApps/UAObfuscatedString

and add the regenerated strings to the keychain at the first application launch

so all methods could use values from keychain later in you code. also I recommend build a singleton that will provide access to such values too keep all in one for time you want to change or update this solution

In general I wrote may api keys just inline in code, sometime declare them split it into two parts for safety. I am using a brain factor;)

let's say you API key xxx-xxxx-xxxx I code the string xxx-xxxx-xFFF which as you see has same length as right key, somewhere in other method I cut las FFF and append right xxx postfix.

If you have a paranoia you could do it in different classes that defined in same file for convenience but stays alphabetically different places like AStoredKeys TheRightPostfixes that somehow hides your approach from disassemblers.



回答3:

Honestly there is not much you can do. Literals are probably the best choice, but consider that you really don't know who will have a device and what they are doing with it. It's safe to assume that any talented engineer will be able to get to the keys and code on the device. Even if you protect your keys, there are apps, like screen scrapers and other things than can really get information. Obfuscation is not a good long term practice and good engineers can figure their way around this... it just slows them down. You do have control over your server, so focus on keeping that solid.

In general instead of thinking of securing your keys, think more about limiting information and access.

In regards to data, minimize private data to be received. For example, credit card numbers are never displayed and only the last 4 digits are stored. Or in the case of a banking app, only account balances are displayed, and they also offer two factor authentication (with an image for example). If a device is compromised, while it would suck to have a person's balance known, it's not the end of the world.

This also tends to be good corporate practice as it keeps you safe from any allegations. A person can't allege that information was compromised if you don't have it in the first place.

With access, limit what an app is capable of doing. Firewall it in a sense. Or provide other authentication steps, like SMS verification, two factor authentication. You can also let the server do the secure tasks for you. In the case of banking again, instead of allowing unlimited transfers, limit the number of transfers or the amount. Limit the accounts to which it can be transferred.



回答4:

You could keep the password separately, in a file on an encrypted disc, or encrypted USB stick. Your code would just retrieve the appropriate file from secure storage. All an attacker would see in your code is the name of the password file.

If that is not practical, then you could XOR encrypt the password in your code. Your application will contain text strings like: "Please enter your password." That doesn't look like a key, but you could use that string as the XOR key to encrypt/decrypt the password. This is more vulnerable as an attacker would see the XOR key you are using in your code and be able to decrypt for herself.

Whatever method you use, do remember to overwrite the variable that holds the actual password before it goes out of scope.