NEHotspotHelper not getting Authenication / Evaulate Commands NetworkExtension - ios

I've been incorporating NEHotspotHelper into my app that manages a captive network, but I'm having multiple issues. I'm not receiving an Authentication Command (kNEHotspotHelperCommandTypeAuthenticate) after my network that is set with a high level of confidence is in the Evaluating State. Also, my WISPr network is never receiving an Evaluate Command( kNEHotspotHelperCommandTypeEvaluate) when the SSID is selected in the Wi-Fi list in Settings. My goal for the WISPr Hotspot is to send a UINotification requiring a user action. ANyone know what I'm missing as far as not receiving kNEHotspotHelperCommandTypeAuthenticate & kNEHotspotHelperCommandTypeEvaluate in the two situations?
I set up HotspotHelper registerWithOptions in my app delegate as such:
NSMutableDictionary* options = [[NSMutableDictionary alloc] init];
[options setObject:#"Hotspot" forKey:kNEHotspotHelperOptionDisplayName];/
dispatch_queue_t queue = dispatch_queue_create("com.myapp.ex", 0);
BOOL returnType = [NEHotspotHelper registerWithOptions:options queue:queue handler: ^(NEHotspotHelperCommand * cmd) {
NEHotspotNetwork* network;
NSLog(#"COMMAND TYPE: %ld", (long)cmd.commandType);
if (cmd.commandType == kNEHotspotHelperCommandTypeEvaluate || cmd.commandType ==kNEHotspotHelperCommandTypeFilterScanList) {
for (network in cmd.networkList) {
NSLog(#"COMMAND TYPE After: %ld", (long)cmd.commandType);
if ([network.SSID isEqualToString:#"test-WPA-2"]|| [network.SSID isEqualToString:#"WISPr Hotspot"]) {
double signalStrength = network.signalStrength;
NSLog(#"Signal Strength: %f", signalStrength);
[network setConfidence:kNEHotspotHelperConfidenceHigh];
[network setPassword:#"myPassword"];
NEHotspotHelperResponse *response = [cmd createResponse:kNEHotspotHelperResultSuccess];
NSLog(#"Response CMD %#", response);
[response setNetworkList:#[network]];
[response setNetwork:network];
[response deliver];
}
}
}
}];

The first mistake I made in the code above: I was expecting the command type Evaluate to enumerate through the network list. However, the Evaluate command is actually looking to be delivered the connected network. I get the current network with the following code:
NSArray *array = [NEHotspotHelper supportedNetworkInterfaces];
NEHotspotNetwork *connectedNetwork = [array lastObject];
NSLog(#"supported Network Interface: %#", connectedNetwork);
Then check to see if the connected list matches my SSID, if so I set the confidence level of this network and deliver the response to Evaluate:
[connectedNetwork setConfidence:kNEHotspotHelperConfidenceLow];
//[response setNetworkList:#[network]];
[response setNetwork:connectedNetwork];
[response deliver];
The next command the handler is given is Authenticate. My complete code looks as following, I am still working on processing the commands after authenticate. The complete code line is:
BOOL returnType = [NEHotspotHelper registerWithOptions:options queue:queue handler: ^(NEHotspotHelperCommand * cmd) {
NEHotspotNetwork* network;
if (cmd.commandType ==kNEHotspotHelperCommandTypeFilterScanList) {
for (network in cmd.networkList) {
//need to check against list of directories
if ([network.SSID isEqualToString:#"test-WPA-2"]) {
NSLog(#"%#", network.SSID);
NSLog(#"SSID is in Directory: %#", network.SSID);
double signalStrength = network.signalStrength;
NSLog(#"Signal Strength: %f", signalStrength);
[network setConfidence:kNEHotspotHelperConfidenceLow];
[network setPassword:#"password"];
NEHotspotHelperResponse *response = [cmd createResponse:kNEHotspotHelperResultSuccess];
NSLog(#"Response CMD %#", response);
[response setNetworkList:#[network]];
[response setNetwork:network];
[response deliver];
}
}
}
if (cmd.commandType == kNEHotspotHelperCommandTypeEvaluate) {
/* * When a network is joined initially, the state machine enters
* the Evaluating state. In that state, each HotspotHelper receives a
* command of type Evaluate. If one or more helpers indicates that it
* is able to handle the network, the one with the highest confidence
* level is chosen before entering the Authenticating state. As an
* optimization, the first helper to assert a high confidence wins and
* the state machine ignores the other helpers.
*
* If no helpers claim the network, the state machine enters the
* Authenticated state.
*/
NSArray *array = [NEHotspotHelper supportedNetworkInterfaces];
NEHotspotNetwork *connectedNetwork = [array lastObject];
NSLog(#"supported Network Interface: %#", connectedNetwork);
NEHotspotHelperResponse *response = [cmd createResponse:kNEHotspotHelperResultSuccess];
NSLog(#"Response CMD %#", response);
[connectedNetwork setConfidence:kNEHotspotHelperConfidenceLow];
//[response setNetworkList:#[network]];
[response setNetwork:connectedNetwork];
[response deliver];
}
if (cmd.commandType == kNEHotspotHelperCommandTypeAuthenticate) {
NSLog(#"COMMAND TYPE In Auth ***********: %ld \n\n\n\n\n\n", (long)cmd.commandType);
/*
* In the Authenticating state, the chosen helper is given a command of type
* Authenticate. The helper is expected to perform whatever network
* processing is required to make the network available for general
* network traffic. If the authentication is successful, the helper
* indicates that with a Success result. The state machine enters the
* Authenticated state.
*
* On a network that has been authenticated by a helper, the state machine
* enters the Maintaining state when the network is joined again, and also
* periodically while the system remains associated with the network. In the
* Maintaining state, the helper is expected to perform whatever network
* operations are required to determine if the network is still able to
* carry general network traffic. If that is the case, the helper returns
* Success. If not, and authentication is again required, it returns
* AuthenticationRequired to cause the state machine to re-enter the
* Authenticating state.
*
* In the Authenticating state, if the helper determines that it requires
* user interaction to proceed, the helper first arranges to alert
* the user via a UserLocalNotification, then returns a result of
* UIRequired. The state machine enters the PresentingUI state.*/
}
if (cmd.commandType == kNEHotspotHelperCommandTypePresentUI) {
NSLog(#"COMMAND TYPE In Present UI ***********: %ld \n\n\n\n\n\n", (long)cmd.commandType);
}
}];

Related

GCDWebServer via Hotspot network

I'm developing an application where devices can connect and interact with each other via common wi-fi network and for the purpose of file exchange I'm using GCDWebServer.
Everything is working great when I use usual wi-fi network or devices are connected to hotspot network with 3rd party host. But I encounter a strange issue when one of devices with the launched app is actually a host of a Hotspot.
I have this code:
- (void)startStreamHLSServer
{
dispatch_async(dispatch_get_main_queue(), ^{
if (!_webServer.isRunning)
{
_webServer = [GCDWebServer new];
[_webServer addGETHandlerForBasePath:#"/" directoryPath:[_fileManager videosURL].path indexFilename:nil cacheAge:3600 allowRangeRequests:YES];
[_webServer startWithPort:1000 bonjourName:nil];
NSLog(#"URL: %#", _webServer.serverURL.absoluteString);
}
});
}
The problem is that serverURL is nil. Which actually seems logical because I checked a function GCDWebServerGetPrimaryIPAddress which is supposed to tell the address and this function is only looking for addresses in the en0 interface when Hotspot network is actually bridge100.
So question is - Is there a "normal" way to make GCDWebServer work with bridge100?
SECOND PART:
Although serverURL is nil, method startWithPort returns true. So I thought maybe server is running, it just can not tell me its address. So I got device's address with my custom method (if you're interested, I can attach it here, but I'm 100% sure it gives a correct address) and tried to use it in order to "speak" with web server, but no luck with that - server doesn't respond. So maybe startWithPort returns a false result after all.
Very interesting observation - when I change primaryInterface to bridge100 in GCDWebServerGetPrimaryIPAddress method, it fixes the issue. GCDWebServer shows a correct address and it is definitely running since I can have an access to the device folder.
Any help would be appreciated!
So question is - Is there a "normal" way to make GCDWebServer work with bridge100?
No. You would need to fork GCDWebServer and patch this function.
Although serverURL is nil, method startWithPort returns true.
The server is certainly running if this method returns true. The ports are open and listening (and by default are bound to all interfaces). The problem is that you need to figure out what IP to use to reach the server from outside the iPhone.
In order to summarize:
GCDWebServer can be used in the hotspot network although serverURL is nil.
What you need to do is next:
Define IP address of your device on your own. Here's a method you can use:
- (void)getDeviceAddress
{
NSString *address = #"";
struct ifaddrs *interfaces = NULL;
struct ifaddrs *temp_addr = NULL;
int success = 0;
// retrieve the current interfaces - returns 0 on success
success = getifaddrs(&interfaces);
if (success == 0)
{
temp_addr = interfaces;
while(temp_addr != NULL)
{
if(temp_addr->ifa_addr->sa_family == AF_INET)
{
NSString *interfaceName = [NSString stringWithUTF8String:temp_addr->ifa_name];
if([interfaceName isEqualToString:#"bridge100"] || [interfaceName isEqualToString:#"en0"])
{
//fetch ip address
address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];
break;
}
}
temp_addr = temp_addr->ifa_next;
}
}
freeifaddrs(interfaces);
return address;
}
2) Remember the port you used in order to start GCDWebServer
3) Build your serverURL:
- (NSString *)serverURL {
NSString *serverURL = [NSString stringWithFormat:#"http:/%#:%d", [self getDeviceAddress], serverPort]; //serverPort is the port your GCDWebServer is running on
return serverURL;
}

Requests to MS Graph API gives me "Authorization Request Denied - Insufficient privileges to complete the operation"

I have a question about "Authorization Request Denied - Insufficient privileges to complete the operation" message that I keep getting back from my requests to Windows Graph API.
Specifically, I'm working in Azure cloud. I have an iOS mobile app that invokes an API.
I have turned on "Authentication for Active Directory" in my Portal.
Then, on the client side (iOS):
[self.todoService.client loginWithProvider:#"windowsazureactivedirectory"
controller:self
animated:YES
completion:^(MSUser *user, NSError *error) {
if(!error && user) {
[self refresh];
}
}]; //loginWithProvider
So returns a valid MSUser object. I see the web login controller appear, I sign in with my un/pw, and then it lets me access my Easy Table's data...etc.
Now, I want to invoke an Easy API that I've created in Azure called getUserData. Hence, I simply insert the invokeAPI code like this (iOS):
[self.todoService.client loginWithProvider:#"windowsazureactivedirectory"
controller:self
animated:YES
completion:^(MSUser *user, NSError *error) {
if(!error && user) {
//NSMutableDictionary * dict = [NSMutableDictionary dictionary];
//[dict setObject:#YES forKey:#"complete"];
NSLog(#"%s - %#", __FUNCTION__, user);
[self refresh];
[self.todoService.client invokeAPI:#"getUserData"
body:nil
HTTPMethod:#"POST"
parameters:nil
headers:nil
completion:^(id _Nullable result, NSHTTPURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(#"%s - API returned response! ", __FUNCTION__);
NSLog(#"%#", result); //TODO: user info here!! :D
}]; //invokAPI
} //if user returned from AAD login is valid
}]; //loginWithProvider
Everything is fine as the API is called and I can see the response data.
On the server side (Node JS), I basically do 3 things:
1st is to get the user object id from the request object:
req.azureMobile.user.getIdentity().then((data) => {
//get user object ID
}
2nd, make a request to https://login.windows.net to get an Access Token with a username/password.
var options = {
url: "https://login.windows.net/" + tenant_domain + "/oauth2/token?api-version=1.0",
method: 'POST',
form: {
grant_type: "client_credentials",
resource: "https://graph.windows.net",
client_id: clientID,
client_secret: key
}
};
req(options, function (err, resp, body) {
//get the result back
}
I get a whole bunch of data back including the Access Token.
3rd, make a request to https://graph.windows.net/, and provide this Access Token along with my User Object ID:
var options = {
url: "https://graph.windows.net/" + tenant_domain + "/users/" + objectId + "?api-version=1.0",
method: 'GET',
headers: {
"Authorization": "Bearer " + access_token
}
};
This is so that I can User data. Now, in a separate test Subscription, I set up all the basic read permissions for AAD and Graph in my AAD management. I successfully get the user's full data back like so:
user = {
accountEnabled = 1;
assignedLicenses = (
);
assignedPlans = (
);
city = xxxxxxxxx;
country = xxxxxxxxxx;
department = Dev;
dirSyncEnabled = "<null>";
displayName = xxxxxx;
facsimileTelephoneNumber = "<null>";
givenName = hehe;
jobTitle = "iOS dev";
lastDirSyncTime = "<null>";
mail = "<null>";
mailNickname = "xxxxxxxxxx.com#EXT#";
mobile = "+xx xxx xxxx 3852";
objectId = "xxxxxxx-2c70-4aab-b261-3b2b97dc5c50";
objectType = User;
"odata.metadata" = "https://graph.windows.net/xxxxxxxxxx.onmicrosoft.com/$metadata#directoryObjects/Microsoft.WindowsAzure.ActiveDirectory.User/#Element";
"odata.type" = "Microsoft.WindowsAzure.ActiveDirectory.User";
otherMails = (
"xxxxxxxxxxxx#gmail.com"
);
...etc
}
However, in another subscription, I did the exact same steps. Even going as far as checking all the permissions like so:
I keep getting an "Authorization Request Denied, Insufficient privileges" message. The error is null so I know everything else went through correctly.
I can't figure out why because everything processes through and I checked all of my AAD and Graph permissions.
log result:
-----body------
'{"odata.error":{"code":"Authorization_RequestDenied","message":{"lang":"en","value":"Insufficient privileges to complete the operation."}}}'
Thanks for any help, and appreciate everyone's time
You can try to upgrade the role of the AD application you use to a administrator permission. Run the following commands in PowerShell:
Connect-MsolService
$ClientIdWebApp = '{your_AD_application_client_id}'
$webApp = Get-MsolServicePrincipal –AppPrincipalId $ClientIdWebApp
#use Add-MsolRoleMember to add it to "Company Administrator" role).
Add-MsolRoleMember -RoleName "Company Administrator" -RoleMemberType ServicePrincipal -RoleMemberObjectId $webApp.ObjectId

What is the current Parse email verification regex pattern?

I am currently using the PFUser method ' signUpInBackgroundWithBlock: ' to sign up my users, but constraints on my UX mean that i can't sign them up on the same ViewController, hence I'm trying to validate the email before calling that method on a PFUser Parse object.
The alternative is to send my users back to earlier view controllers if parse gives me an error back after method call (which I do not want to do)
I have found this Regex pattern, but this is quite an old answer and I know fancier domains have been out since are now out:
https://www.parse.com/questions/email-validation-rules-for-pfsignupviewcontroller
"The alternative is to send my users back to earlier view controllers if parse gives me an error back after method call (which I do not want to do)"
Note - Unfortunately, you simply won't be able to build parse apps unless you "send them back" like that. Unfortunately "it's that simple." Quite literally every single such "step" when dealing with Parse, you have to be able to "go back" in the sense you describe.
In answer to your question, as you probably know essentially THERE IS NO really good way to truly check if a string is an email, due to various problems with the nature of defining an email, and the fact that you simply don't actually want the full set of "really possible" email strings, for any app.
In practice the following category works well.
It's in use in many high volume production apps.
Note that NSPredicate is, I feel, the most natural, reliable way to do this in iOS.
-(BOOL)basicLocalEmailCheck
{
if ( self.length > 50 ) return NO;
// note, first if it ends with a dot and one letter - that is no good
// (the regex below from W3C does allow a final single-letter tld)
NSString *rx = #".*\\..$";
NSPredicate *emailTest = [NSPredicate
predicateWithFormat:#"SELF MATCHES %#", rx];
if ( [emailTest evaluateWithObject:self] ) return NO;
// here's the original from the W3C HTML5 spec....
// ^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+#[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$
// i made a modification,
// you can't have eg "localhost" with no .com,
// and note you have to escape one backslash for the string from the W3C
rx = #"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+#[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?){1,5}$";
emailTest = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", rx];
return [emailTest evaluateWithObject:self];
}
If you are a beginner and not familiar with categories, it's a good opportunity to use one.
Here are some typical real-world uses...particularly relating to Parse since you mention that.
-(IBAction)joinNow
{
[self.view endEditing:YES];
if ( [self _fieldBlank] )
{ [self woe:#"Please complete all fields."]; return; }
if ( ! [self.email.text basicLocalEmailCheck] )
{ [self woe:#"Please enter a valid email."]; return; }
if ( self.imageHasBeenSet == NO )
{ [self woe:#"Please add a picture."]; return; }
if ( self.password.text.length > 20 ||
self.firstname.text.length > 20 ||
self.surname.text.length > 20 )
{ [self woe:#"20 character limit for names and password."]; return; }
[self ageConfirmThenJoin];
}
-(IBAction)clickLogin:(id)sender
{
[self.view endEditing:YES];
[PFUser logOut];
if ( ! [self.loginEmail.text basicLocalEmailCheck] )
{
[UIAlertView ok:#"Please enter your email in the email field."];
[self begin];
return;
}
[APP huddie];
APP.hud.labelText = #"Logging in ...";
[PFAnalytics trackEvent:#"loginAttempt"];
[PFUser logInWithUsernameInBackground: [self.loginEmail.text lowercaseString]
password: self.loginPassword.text
block:^(PFUser* user, NSError* error)
{
[APP.hud hide:YES];
if (user) // Login successful
{
[PFAnalytics trackEvent:#"loginSuccess"];
[self isLoggedInCheckValid];
return;
}
else
{
// note, with Parse it SEEMS TO BE THE CASE that
// 100, no connection 101, bad user/pass
NSString *msg;
NSString *analyticsMsg = #"otherProblem";
if ( !error)
{
msg = #"Could not connect. Try again later...";
// seems unlikely/impossible this could happen
}
else
{
if ( [error code] == 101 )
{
msg = #"Incorrect email or password. Please try again.";
analyticsMsg = #"passwordWrong";
}
else
{
msg = #"Could not connect. Try again later.";
}
}
[PFAnalytics trackEvent:#"loginFailure"
dimensions:#{ #"reason":analyticsMsg }];
[UIAlertView ok:msg];
[self begin]; // not much else we can do
return;
}
}];
}
If you are after a regular expression, then you could take a look here and here for some solutions.
That being said, if you truly want to ensure that the user has provided you with a valid, active email account you should simply do some very basic validation (see it contains the # character for instance) and then simply send a mail with a link to activate the account.
The regular expressions linked to the answers provided aren't exactly user friendly. To add insult to injury, users can still provide you with bogus email addresses. It might also be the case where an edge case scenario email address fails the verification, thus according to your site the user won't be able to sign up.

XMPPFramework - Which XMPPRoster function to use to send a friend request?

I am trying to implement an Instant Messaging App where users can chat as well as add other users to their roster and accept buddy requests. So, far I have been able to implement the chat and I am also able to receive and accept/reject friend requests.
For accepting/rejecting a subscription request, the code is as follows:
- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence
{
NSString *presenceType = [presence type]; // online / offline
NSString *myUsername = [[sender myJID] user];
NSString *presenceFromUser = [[presence from] user];
NSString *presencefromStr=[presence fromStr];
if ([presenceType isEqualToString:#"subscribe"]) {
if(buttonIndex==1) { // For accept button
[xmppRoster acceptPresenceSubscriptionRequestFrom:[tmpPresence from] andAddToRoster:YES];
}
else { // For reject button
[xmppRoster rejectPresenceSubscriptionRequestFrom:[tmpPresence from]];
}
}
However, now I am stuck with the problem of not being able to send a friend request. Can anyone guide me on which function of XMPPRoster to use? I tried using the subscribePresenceToUser function, but, it didn't work. Any help will be highly appreciated.
Also, can someone tell if the way I am going with this XMPPRoster subscription mechanism is right or is there a better way to handle the friend requests in XMPPFramework?
Thanks in advance.
Answer by OP in comment:
XMPPJID *jid = [XMPPJID jidWithString:self.addFriendField.text];
[xmppRoster addUser:jid withNickname:nil];
This code snippet sends the request to other users and adds them to their Roster.
You can see XMPPRoster.h to see all the functions available inside the roster extension.
For your answer you have three options:
/**
* Adds the given user to the roster with an optional nickname
* and requests permission to receive presence information from them.
**/
- (void)addUser:(XMPPJID *)jid withNickname:(nullable NSString *)optionalName;
/**
* Adds the given user to the roster with an optional nickname,
* adds the given user to groups
* and requests permission to receive presence information from them.
**/
- (void)addUser:(XMPPJID *)jid withNickname:(nullable NSString *)optionalName groups:(nullable NSArray<NSString*> *)groups;
/**
* Adds the given user to the roster with an optional nickname,
* adds the given user to groups
* and optionally requests permission to receive presence information from them.
**/
- (void)addUser:(XMPPJID *)jid withNickname:(nullable NSString *)optionalName groups:(nullable NSArray<NSString*> *)groups subscribeToPresence:(BOOL)subscribe;
And to accept the friend request : (add as friend, as Fan or Decline)
addToRoster flag = true : Friend
addToRoster flag = false : Fan
/**
* Accepts the presence subscription request the given user.
*
* If you also choose, you can add the user to your roster.
* Doing so is similar to the traditional IM model.
**/
- (void)acceptPresenceSubscriptionRequestFrom:(XMPPJID *)jid andAddToRoster:(BOOL)flag;
/**
* Rejects the presence subscription request from the given user.
*
* If you are already subscribed to the given user's presence,
* rejecting they subscription request will not affect your subscription to their presence.
**/
- (void)rejectPresenceSubscriptionRequestFrom:(XMPPJID *)jid;

best and secured way of storing paypal api credentials

I'm creating an iPhone application that integrates the PayPal API for iOS. The API requires the API credentials when sending a request. I read the PayPal API and it says
Never send Express Checkout requests from your mobile application directly to PayPal. The requests require your PayPal API credentials. Placing your credentials on mobile devices exposes you and PayPal to unacceptable security risks. Send Express Checkout requests only from secure servers.
My question is, what is the best way of storing the API credentials so as to decrease the possibilities for my credentials being exposed or hacked? Is attaching the credentials to an iPhone build risky? Why or how? Is storing these credentials on a secure server reliable enough?
EDIT: how can keychain access api on iOS can help me with this?
Putting the API keys in your app is completely insecure. Via a verity of techniques, anyone who can download the app or gets their hands on a phone with the app on it can simply read the API key. This holds even if you do what #MatthiasBauch suggested and download the secret later. It also holds even if you do what #Rexeisen suggested and obfuscate the string.
You best bet is to user apple's built in subscription services to handle payments ( which may not be applicable and they take a cut but is likely more secure than what you can do on the phone)
In the likely even that you don't want to or can't do that, give each instance of the app a unique id that they register when they download with a server you control. That sever than has the paypal credentials and will make api calls on their behalf . This way, if any given phone is stolen/ has its api key to your server read, you can simply revoke that key and your paypal API keys are still safe. Important caveat: until you actually revoke that app's key, anyone who has it can still use it to make what ever calls your server supports. This could be a very bad thing.
It is risky as you can run the Strings utility on just about any app (try it, it's kinda scary) and get the strings from the code. Generally I'd recommend packaging the secrets in the app, but leave them on a secure server elsewhere. If you must put it in the app, one thing you can do is obfuscate the strings so it's not obvious.
NSString *secret = kTwitterClientSecret;
NSData *secretData = [secret dataUsingEncoding:NSUTF8StringEncoding];
NSString *key = #"Twitter";
[secretData obfuscateOrDeobfuscateWithKey:key];
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *path = [NSString stringWithFormat:#"%#/%#-%#", documentsPath, key, #"output"];
[secretData writeToFile:path atomically:NO];
NSLog(#"Wrote obfuscated data to: %#", documentsPath);
Where obfuscateOrDeobfuscateWithKey is a category on NSData
// Inspiration from: http://iosdevelopertips.com/cocoa/obfuscation-encryption-of-string-nsstring.html
- (void)obfuscateOrDeobfuscateWithKey:(NSString *)key
{
// Get pointer to data to obfuscate
char *dataPtr = (char *) [self bytes];
// Get pointer to key data
char *keyData = (char *) [[key dataUsingEncoding:NSUTF8StringEncoding] bytes];
// Points to each char in sequence in the key
char *keyPtr = keyData;
int keyIndex = 0;
// For each character in data, xor with current value in key
for (int x = 0; x < [self length]; x++) {
// Replace current character in data with current character xor'd with current key value.
// Bump each pointer to the next character.
*dataPtr = *dataPtr ^ *keyPtr;
dataPtr++;
keyPtr++;
// If at end of key data, reset count and set key pointer back to start of key value
if (++keyIndex == [key length]) {
keyIndex = 0, keyPtr = keyData;
}
}
}
Then you can declare a constant to be something like
static unsigned char const kTwitterClientSecret[] = {
0x00, 0x00, 0x00, ... etc ...
};
static unsigned int const kTwitterClientSecret_len = LENGTH;
Then to get the string back you can do
[NSString deobfuscatedStringWithBytes:kTwitterClientSecret length:kTwitterClientSecret_len key:#"Twitter"];
Where this is a category on NSString
+ (NSString *)deobfuscatedStringWithBytes:(const void *)bytes length:(NSUInteger)length key:(NSString *)key
{
NSData *deobfuscatedData = [NSData dataWithBytes:bytes length:length];
[deobfuscatedData obfuscateOrDeobfuscateWithKey:key];
return [[NSString alloc] initWithData:deobfuscatedData encoding:NSUTF8StringEncoding];
}
This will do very simple obfuscation and will not show up in strings.

Resources