Category: iOS 教學

[教學]Objective-c LLVM 4.0 新用法

NSNumber

之前用法:

NSNumber *intNumber = [NSNumber numberWithInt:10];
 
NSNumber *doubleNumber = [NSNumber numberWithDouble:1.234567];
 
NSNumber *charNumber = [NSNumber numberWithChar:'x'];
 
NSNumber *boolNumber = [NSNumber numberWithBool:YES];

4.0 後新用法

NSNumber *intNumber = @1000;
 
NSNumber *doubleNumber = @1.234567;
 
NSNumber *charNumber = @'x';
 
NSNumber *boolNumber= @YES;

NSArray

之前用法:

NSArray *weekdays = [NSArray arrayWithObjects:@"monday", @"tuesday", @"wednesday", nil];
 
NSString *monday = [weekdays objectAtIndex:0];

4.0 後新用法

NSArray *weekdays = @[@"monday", @"tuesday", @"wednesday"];
 
NSString *monday = weekdays[0];

NSDictionary

之前用法:

NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:
 
@"ynjan", @"name",
 
@"www.ynjan.com", @"website",
 
nil];
 
NSString *name = [info objectForKey:@"name"];

4.0 後新用法

NSDictionary *info = @{
 
@"name": @"ynjan",
 
@"website": @"www.ynjan.com"
 
};
 
NSString *name = info[@"name"];
Tags : ,

[教學] iOS 上使用 RSA Public Key 加密

使用 openssl 產生 key

private key

openssl genrsa -out private_key.pem 1024

pkcs8 private key

這是程式會用到的 private key

openssl pkcs8 -topk8 -in private_key.pem -out pkcs8_private_key.pem -nocrypt

pkcs8_private_key.pem 內容

-----BEGIN RSA PRIVATE KEY-----
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAJavPhrQDMVIVq5wv5WDFGxeWcQk
XW+fsL/z6hui9WRNztAw+NENAuC/tciF9MibGI/sFcRunriGVyKFsIy7QFknKf4zz3E2uE+GPfeV
RyP3SsWN/cj/OU7QaT6mhpa007Niyy40Uy2PqCCbUQlgHCLr65gLwTz9fLjj7noHjRvFAgMBAAEC
gYAuBEplCdA8l0DReOEpGaStC4tCWRCnvA7QL/74faWPAiv8bFgwD1cnp6QuHqqIcMTpfuGO4XFb
TLtjcYxfDfgyR1N6JbnG06oL1MlTmwfZjIrwGfhDurR0cvlNDzTlMyGfHux+0L6Wgujz4WVvC1l8
cIS/L9LkKM6jri336N2AwQJBAPCBe3Izu4izP7iqYq3FjWtW6WJY2BgG1vefnXFN99SRoEVuQLuM
mC3Ol8Tfvt4+mRuFIPA1PgKtrScNbCZlOy0CQQCgZGRzoo8BvSGfg4/v080vXWDAOGz2v6qidFau
RrYNlD26PL+p+kthMQPiEMotcAy3ssfJRhsftqjt5h5K5OH5AkEArQAW8lEwJub5gTZfASzHy1yb
SLkryCyzWTAhqq0xBmEybn8eZUp+9QRUu7NOssNIkzkyNle2IWsY0KyhDQVoiQJBAJUhIwEdSUg9
mTCee8tqrCH4+YdgL3gxd6866ol597Wies5Zw3+A2GuSzmB4afNbdoeqs/XhuECBbopb5xiNvckC
QQCNyF165oFTkWwi7MvD+y8rsatzzVmwiJDOYEpNDSUB7a3pjppairwi8CgDHbDOuoKlDPPYr7rW
LHsJq+0suREU
-----END RSA PRIVATE KEY-----

public key

openssl rsa -in private_key.pem -out public_key.pem -pubout

public_key.pem 內容

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCWrz4a0AzFSFaucL+VgxRsXlnEJF1vn7C/8+ob
ovVkTc7QMPjRDQLgv7XIhfTImxiP7BXEbp64hlcihbCMu0BZJyn+M89xNrhPhj33lUcj90rFjf3I
/zlO0Gk+poaWtNOzYssuNFMtj6ggm1EJYBwi6+uYC8E8/Xy44+56B40bxQIDAQAB
-----END PUBLIC KEY-----

加密方式

在 iOS 上有兩種方式可以做到 RSA 加解密的功能

  1. 使用 iOS 內建 Security.framework
  2. 使用 SSCrypto.framework + openssl

Security.framework

設定 public Key

- (NSData *)stripPublicKeyHeader:(NSData *)d_key
{
    // Skip ASN.1 public key header
    if (d_key == nil) return(nil);
 
    unsigned int len = [d_key length];
    if (!len) return(nil);
 
    unsigned char *c_key = (unsigned char *)[d_key bytes];
    unsigned int  idx    = 0;
 
    if (c_key[idx++] != 0x30) return(nil);
 
    if (c_key[idx] > 0x80) idx += c_key[idx] - 0x80 + 1;
    else idx++;
 
    // PKCS #1 rsaEncryption szOID_RSA_RSA
    static unsigned char seqiod[] =
    { 0x30,   0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
     0x01, 0x05, 0x00 };
    if (memcmp(&c_key[idx], seqiod, 15)) return(nil);
 
    idx += 15;
 
    if (c_key[idx++] != 0x03) return(nil);
 
    if (c_key[idx] > 0x80) idx += c_key[idx] - 0x80 + 1;
    else idx++;
 
    if (c_key[idx++] != '\0') return(nil);
 
    // Now make a new NSData from this buffer
    return([NSData dataWithBytes:&c_key[idx] length:len - idx]);
}
- (BOOL)addPublicKey:(NSString *)key withTag:(NSString *)tag
{
    NSString *s_key = [NSString string];
    NSArray  *a_key = [key componentsSeparatedByString:@"\n"];
    BOOL     f_key  = FALSE;
 
    for (NSString *a_line in a_key) {
        if ([a_line isEqualToString:@"-----BEGIN PUBLIC KEY-----"]) {
            f_key = TRUE;
        }
        else if ([a_line isEqualToString:@"-----END PUBLIC KEY-----"]) {
            f_key = FALSE;
        }
        else if (f_key) {
            s_key = [s_key stringByAppendingString:a_line];
        }
    }
    if (s_key.length == 0) return(FALSE);
 
    // This will be base64 encoded, decode it.
    NSData *d_key = [NSData dataFromBase64String:s_key];
    d_key = [self stripPublicKeyHeader:d_key];
    if (d_key == nil) return(FALSE);
 
    NSData *d_tag = [NSData dataWithBytes:[tag UTF8String] length:[tag length]];
 
    // Delete any old lingering key with the same tag
    NSMutableDictionary *publicKey = [[NSMutableDictionary alloc] init];
    [publicKey setObject:(id) kSecClassKey forKey:(id)kSecClass];
    [publicKey setObject:(id) kSecAttrKeyTypeRSA forKey:(id)kSecAttrKeyType];
    [publicKey setObject:d_tag forKey:(id)kSecAttrApplicationTag];
    SecItemDelete((CFDictionaryRef)publicKey);
 
    CFTypeRef persistKey = nil;
 
    // Add persistent version of the key to system keychain
    [publicKey setObject:d_key forKey:(id)kSecValueData];
    [publicKey setObject:(id) kSecAttrKeyClassPublic forKey:(id)
     kSecAttrKeyClass];
    [publicKey setObject:[NSNumber numberWithBool:YES] forKey:(id)
     kSecReturnPersistentRef];
 
    OSStatus secStatus = SecItemAdd((CFDictionaryRef)publicKey, &persistKey);
    if (persistKey != nil) CFRelease(persistKey);
 
    if ((secStatus != noErr) && (secStatus != errSecDuplicateItem)) {
        [publicKey release];
        return(FALSE);
    }
 
    // Now fetch the SecKeyRef version of the key
    SecKeyRef keyRef = nil;
 
    [publicKey removeObjectForKey:(id)kSecValueData];
    [publicKey removeObjectForKey:(id)kSecReturnPersistentRef];
    [publicKey setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnRef
    ];
    [publicKey setObject:(id) kSecAttrKeyTypeRSA forKey:(id)kSecAttrKeyType];
    secStatus = SecItemCopyMatching((CFDictionaryRef)publicKey,
                                    (CFTypeRef *)&_publicKeyRef);
 
    [publicKey release];
 
    if (_publicKeyRef == nil) return(FALSE);
    return(TRUE);
}

這兩段 stripPublicKeyHeader, addPublicKey 來自 Using an RSA public key generated by OpenSSL in iOS

加密

- (NSData*)encrypt:(NSString*)text {
    OSStatus sanityCheck = noErr;
    size_t cipherBufferSize = 0;
    size_t keyBufferSize = 0;
 
    NSData * data = [text dataUsingEncoding:NSUTF8StringEncoding];    
    NSData * cipher = nil;
    uint8_t * cipherBuffer = NULL;
 
    // Calculate the buffer sizes.
    cipherBufferSize = SecKeyGetBlockSize(_publicKeyRef);
    keyBufferSize = [data length];
 
    // Allocate some buffer space. I don't trust calloc.
    cipherBuffer = malloc( cipherBufferSize * sizeof(uint8_t) );
    memset((void *)cipherBuffer, 0x0, cipherBufferSize);
 
    // Encrypt using the public key.
    sanityCheck = SecKeyEncrypt(_publicKeyRef,
                                kSecPaddingPKCS1,
                                (const uint8_t *)[data bytes],
                                keyBufferSize,
                                cipherBuffer,
                                &cipherBufferSize
                                );
 
    // Build up cipher text blob.
    cipher = [NSData dataWithBytes:(const void *)cipherBuffer length:(NSUInteger)cipherBufferSize];
 
    if (cipherBuffer) free(cipherBuffer);
 
    return cipher;
}

SSCrypto.framework + openssl [推薦]

使用 openssl 就相對簡單許多

加密

static NSString *publicKey =
@"-----BEGIN PUBLIC KEY-----\n\
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCdsg9OlT8nJys9U/ZahRXjgy/0\n\
/KrFpdXdu1I8CLmAb8iONKgtbOhkob/BibZR21dIM9gcuc7NIH9zqptflXmZ9hfJ\n\
f0DivZ9ganPITBSkuM3wni67rT2sBY5bOvrUC7v77RZJYmiUlOCpEWrOJhfN23Vx\n\
wJ4MhnGHHYzUadLF3wIDAQAB\n\
-----END PUBLIC KEY-----";
- (NSData*)encrypt:(NSString*)text {
    //Convert to NSData
    NSData *key1 = [_publicKey dataUsingEncoding:NSASCIIStringEncoding];
 
    SSCrypto *rsa = [[SSCrypto alloc] initWithPublicKey:key1];
    [rsa setClearTextWithString:text];
    NSData *dataEncrypt = [rsa encrypt];
    return dataEncrypt;
}
Tags : , , ,

[教學] Apple Push Notification (APN) Service 訊息推播

事前準備

  • iPhone 或是 iPad。推播功能無法在模擬器上使用。
  • iOS 開發者賬號。需要新增 App ID 以及建立相關憑證。
  • 推播 Server。需要一個推播 Server 發送推播訊息。

推播訊息格式

APN 是使用 json 作為傳遞的格式

{
   "aps":
   {
      "alert":"訊息內容",
      "sound":"default",    
      "badge":1
    }
 }

訊息內容(payload)有256 bytes 的長度限制,所以在傳遞時最好把空白字元省略。

{"aps":{"alert":"my message","sound":"default","badge"=1}}

詳信內容可參考 Push Notificaiton Programming Guide

產生憑證 (certificate)

透過下面的步驟你將會得到一系列的檔案

  1. MyPush.certSigningRequest
  2. MyPushKey.p12
  3. aps_development.cer
  4. MyPushCert.pem
  5. MyPushKey.pem
  6. ck.pem (這是我們最後真正會用到的檔案)

1. 產生 Certificate Signing Request (CSR)

到 iOS Provision Protal 申請憑證會需要 CSR

產生步驟:

1. 在你的 mac 電腦內開啓 Keychain Access,然後從選單內選擇 Request a Certificate From a Certificate Authority

2.在下一步的畫面內填寫email 以及 name,記得要選擇 Save to disk,然後就會產生出 MyPush.certSigningRequest 的檔案

3. 匯出 private key

回到 KeyChain Access 的主畫面,在右上方搜尋框內輸入 “MyPush” 應該會看到剛剛建立的key。然後選擇 private key -> Export My Push。將檔案存成 MyPushKey.p12 以及設定密碼 (passphrase)

2. 產生 SSL certificate

這裡需要登入 iOS Provisioning Portal。我將會省略申請 APP ID 的步驟。

1. Enable for Apple Push Notification

進入 App ID 的設定畫面 (Configure App ID),點選 Enable for Apple Push Notification。你會看到兩個憑證

Development Push SSL Certificate – 在開發過程中使用這組

Production Push SSL Cerificate – 上架時使用這組

(憑證有效期限為一年,記得要重新申請,不然會失效。)

我們這次先設定 Development Push SSL Certificate

2. 下載 SSL Certificate

Enable For Apple Push Notification Service 後,點選 Configure。他會要求你提供前面產生的 CSR 檔案。

將下載下來的檔案存成 aps_development.cer

3. 產生 CK.PEM 檔案

打開 Terminal,進入剛所產生檔案的位置

1. 將 .cer 轉換成 .pem

openssl x509 -in aps_development.cer -inform der -out MyPushCert.pem

2. 將 .p12 轉換成 .pem,這邊會要求你輸入之前所設定的密碼。

$ openssl pkcs12 -nocerts -out MyPushKey.pem -in MyPushtKey.p12
Enter Import Password: 
MAC verified OK
Enter PEM pass phrase: 
Verifying - Enter PEM pass phrase:

3. 最後將兩個 pem 檔案合併

cat MyPushCert.pem MyPushKey.pem > ck.pem

APP 端設定 (更新 iOS 8)

修改 MyPushAppDelegate.m

註冊  push notification

在 didFinishLaunchingWithOptions 內加入下面程式碼

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
{ 
   self.window.rootViewController = self.viewController;
   [self.window makeKeyAndVisible]; 
   ...
   [[UIApplication sharedApplication] registerForRemoteNotificationTypes:
		(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
   return YES; 
}

////////////////////////////////////////////////////////////
/// iOS 8.0 更新
////////////////////////////////////////////////////////////

if ([[UIApplication sharedApplication] respondsToSelector:@selector(registerUserNotificationSettings:)])
{
   [[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
   [[UIApplication sharedApplication] registerForRemoteNotifications];
} else {
   [[UIApplication sharedApplication] registerForRemoteNotificationTypes:UIRemoteNotificationTypeAlert|UIRemoteNotificationTypeSound|UIRemoteNotificationTypeBadge|UIRemoteNotificationTypeNewsstandContentAvailability];
}

接收 device token

在 didRegisterForRemoteNotificationsWithDeviceToken 內加入下面程式碼

這裡也是很好的時機點將所收到的 device token 資料傳到 APN Server

- (void)application:(UIApplication*)application
         didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
 
    NSString *tokenStr = [deviceToken description];
    NSString *pushToken = [[[tokenStr 
      stringByReplacingOccurrencesOfString:@"" withString:@""] 
      stringByReplacingOccurrencesOfString:@" " withString:@""];
 
   // 將所收到的 device token 資料傳到 APN Server
}

「註」在 APP 開啓的情況下,是不會收到APN的,所以記得測試的時候要先將 APP 關閉

Server端範例

<?php
 
// 要發送裝置的 device token
$deviceToken = '0f744707bebcf74f9b7c25d48e3358945f6aa01da5ddb387462c7eaf61bbad78';
 
// 私鑰的密碼
$passphrase = 'password';
 
$message = 'Hello World!';
 
////////////////////////////////////////////////////////////////////////////////
 
$ctx = stream_context_create();
// 帶入 ck.pem
stream_context_set_option($ctx, 'ssl', 'local_cert', 'ck.pem');
stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
 
$fp = stream_socket_client('ssl://gateway.sandbox.push.apple.com:2195', 
      $err, $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
 
$body['aps'] = array('alert' => $message, 'sound' => 'default');
$payload = json_encode($body);
$msg = chr(0).pack('n', 32).pack('H*', $deviceToken).pack('n', strlen($payload)).$payload;
// 送出訊息
fwrite($fp, $msg, strlen($msg));
fclose($fp);
?>
Tags : , , , ,