2012-05-14 20 views
17

Potrzebuję zintegrować moją aplikację iPhone z systemem i wymagają szyfrowania danych przez dany klucz publiczny, są 3 pliki w 3 różnych formatach .xml .der i. pem, zbadałem i znalazłem kilka artykułów na temat uzyskiwania SecKeyRef z DER/PEM, ale zawsze są one zwracane zero. Poniżej znajduje się mój kod:Jak mogę uzyskać SecKeyRef z pliku DER/PEM

NSString *pkFilePath = [[NSBundle mainBundle] pathForResource:@"PKFile" ofType:@"der"]; 
NSData *pkData = [NSData dataWithContentsOfFile:pkFilePath]; 

SecCertificateRef cert; 
cert = SecCertificateCreateWithData(NULL, (CFDataRef) pkData); 
assert(cert != NULL); 

OSStatus err; 

    if (cert != NULL) { 
     err = SecItemAdd(
         (CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys: 
              (id) kSecClassCertificate, kSecClass, 
              (id) cert,     kSecValueRef, 
              nil 
              ], 
         NULL 
         ); 
     if ((err == errSecSuccess) || (err == errSecDuplicateItem)) { 
      CFArrayRef certs = CFArrayCreate(kCFAllocatorDefault, (const void **) &cert, 1, NULL); 
      SecPolicyRef policy = SecPolicyCreateBasicX509(); 
      SecTrustRef trust; 
      SecTrustCreateWithCertificates(certs, policy, &trust); 
      SecTrustResultType trustResult; 
      SecTrustEvaluate(trust, &trustResult); 
      if (certs) { 
       CFRelease(certs); 
      } 
      if (trust) { 
       CFRelease(trust); 
      } 
      return SecTrustCopyPublicKey(trust); 
     } 
    } 
return NULL; 

Problem dzieje się w SecCertificateCreateWithData, zawsze zwraca zero, nawet poprzez plik odczytu jest w porządku. Każdy zrobił to proszę pomóżcie mi, dzięki!

EDYCJA: Plik cert był sygnaturą MD5.

+2

myślę, że znajdziesz tu odpowiedź: http://stackoverflow.com/questions/1595013/iphone-how-to-create-a-seckeyref-from-a-public-key-file-pem –

Odpowiedz

50

Zwalczałem wiele z tym samym problemem iw końcu znalazłem rozwiązanie. Mój problem polegał na tym, że do szyfrowania/odszyfrowywania danych w aplikacji na iOS potrzebowałem użyć zewnętrznego klucza prywatnego i publicznego i nie chciałem korzystać z pęku kluczy. Okazuje się, że potrzebny jest również podpisany certyfikat dla biblioteki bezpieczeństwa iOS, aby móc odczytać kluczowe dane i oczywiście pliki muszą mieć poprawny format. Procedura jest zasadniczo następująca:

Załóżmy, że masz prywatny klucz w formacie PEM (z ----- BEGIN RSA PRIVATE KEY ----- i ----- END RSA PRIVATE KEY - --- markery): rsaPrivate.pem

//Create a certificate signing request with the private key 
openssl req -new -key rsaPrivate.pem -out rsaCertReq.csr 

//Create a self-signed certificate with the private key and signing request 
openssl x509 -req -days 3650 -in rsaCertReq.csr -signkey rsaPrivate.pem -out rsaCert.crt 

//Convert the certificate to DER format: the certificate contains the public key 
openssl x509 -outform der -in rsaCert.crt -out rsaCert.der 

//Export the private key and certificate to p12 file 
openssl pkcs12 -export -out rsaPrivate.p12 -inkey rsaPrivate.pem -in rsaCert.crt 

teraz masz dwa pliki, które są zgodne z ramami zabezpieczeń iOS: rsaCert.der (klucz publiczny) i rsaPrivate.p12 (klucz prywatny). Poniższy kod odczytuje w kluczu publicznym zakładając plik zostanie dodany do pakietu:

- (SecKeyRef)getPublicKeyRef { 

    NSString *resourcePath = [[NSBundle mainBundle] pathForResource:@"rsaCert" ofType:@"der"]; 
    NSData *certData = [NSData dataWithContentsOfFile:resourcePath]; 
    SecCertificateRef cert = SecCertificateCreateWithData(NULL, (CFDataRef)certData); 
    SecKeyRef key = NULL; 
    SecTrustRef trust = NULL; 
    SecPolicyRef policy = NULL; 

    if (cert != NULL) { 
     policy = SecPolicyCreateBasicX509(); 
     if (policy) { 
      if (SecTrustCreateWithCertificates((CFTypeRef)cert, policy, &trust) == noErr) { 
       SecTrustResultType result; 
       OSStatus res = SecTrustEvaluate(trust, &result); 

       //Check the result of the trust evaluation rather than the result of the API invocation. 
       if (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified) { 
        key = SecTrustCopyPublicKey(trust); 
       } 
      } 
     } 
    } 
    if (policy) CFRelease(policy); 
    if (trust) CFRelease(trust); 
    if (cert) CFRelease(cert); 
    return key; 
} 

do odczytania w wykorzystaniu klucza prywatnego następujący kod:

SecKeyRef getPrivateKeyRef() { 
    NSString *resourcePath = [[NSBundle mainBundle] pathForResource:@"rsaPrivate" ofType:@"p12"]; 
    NSData *p12Data = [NSData dataWithContentsOfFile:resourcePath]; 

    NSMutableDictionary * options = [[NSMutableDictionary alloc] init]; 

    SecKeyRef privateKeyRef = NULL; 

    //change to the actual password you used here 
    [options setObject:@"password_for_the_key" forKey:(id)kSecImportExportPassphrase]; 

    CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL); 

    OSStatus securityError = SecPKCS12Import((CFDataRef) p12Data, 
              (CFDictionaryRef)options, &items); 

    if (securityError == noErr && CFArrayGetCount(items) > 0) { 
     CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0); 
     SecIdentityRef identityApp = 
     (SecIdentityRef)CFDictionaryGetValue(identityDict, 
              kSecImportItemIdentity); 

     securityError = SecIdentityCopyPrivateKey(identityApp, &privateKeyRef); 
     if (securityError != noErr) { 
      privateKeyRef = NULL; 
     } 
    } 
    [options release]; 
    CFRelease(items); 
    return privateKeyRef; 
} 
+2

Dzięki! Uratuj mój dzień! – mientus

+0

Dziękuję, dziękuję, dziękuję !!! jesteś bogiem! –

+0

Pierwsze rozwiązanie, które udało mi się zrobić bez użycia pęku kluczy. Doskonały. – fishinear

3

Począwszy iOS 10, to jest rzeczywiście możliwe importowanie kluczy prywatnych PEM bez konwersji na PKCS # 12 (co jest bardzo uniwersalnym formatem kontenera dla wszystkiego związanego z kryptografią), a więc również bez użycia OpenSSL w linii poleceń lub statycznego łączenia aplikacji z nim . Na MacOS jest to nawet możliwe od 10,7 przy użyciu innej funkcji niż te wymienione tutaj (ale jak dotąd nie istnieje dla iOS). Dokładnie opisany poniżej sposób będzie działał także na macOS 10.12 i później.

Aby zaimportować certyfikat, to wystarczy, aby tylko pozbawić

-----BEGIN CERTIFICATE----- 

i

-----END CERTIFICATE----- 

linii, a następnie uruchomić dekodowanie base64 nad danymi w lewo, wynik jest certyfikat w standardowym formacie DER , który może być po prostu podany do SecCertificateCreateWithData(), aby uzyskać SecCertificateRef. To zawsze działało, także przed iOS 10.

Aby zaimportować klucz prywatny, może być wymagana dodatkowa praca. Jeśli klucz prywatny jest opakowany za pomocą

-----BEGIN RSA PRIVATE KEY----- 

to bardzo łatwo. Ponownie, pierwsza i ostatnia linia musi zostać usunięta, pozostałe dane muszą zostać zdekodowane w base64, a wynikiem jest klucz RSA w formacie PKCS # 1.Ten format może przechowywać tylko klucze RSA i jest bezpośrednio czytelny, po prostu podaj zdekodowane dane do SecKeyCreateWithData(), aby uzyskać SecKeyRef. Słownik attributes wystarczy następujące pary kluczy/wartości:

  • kSecAttrKeyType: kSecAttrKeyTypeRSA
  • kSecAttrKeyClass: kSecAttrKeyClassPrivate
  • kSecAttrKeySizeInBits: CFNumberRef z wówczas liczba bitów w kluczu (np 1024, 2048, etc.) Jeśli nie jest znana, ta informacja może zostać odczytana z surowych danych klucza, które są danymi ASN.1 (nieco wykracza to poza zakres tej odpowiedzi, ale poniżej podam kilka przydatnych linków o tym, jak przeanalizować ten format). Ta wartość jest opcjonalna! W moich testach nie było potrzeby ustawiania tej wartości; jeśli nie było, API sam ustaliło tę wartość i zawsze było ustawione poprawnie później.

W przypadku, gdy klucz prywatny jest owinięty przez -----BEGIN PRIVATE KEY-----, następnie base64 zakodowane dane nie są na # 1 formacie PKCS ale w PKCS # 8 formatu, jednak jest to tylko bardziej ogólnych kontenerów Wewnętrzne dane tego kontenera są równe PKCS # 1, więc można powiedzieć, że dla kluczy RSA PKCS # 8 to PKCS # 1 z dodatkowym nagłówkiem i wszystko, co musisz zrobić, to rozebrać ten dodatkowy nagłówek. Po prostu usuń pierwsze 26 bajtów dekodowanych danych base64 i ponownie uzyskaj numer PKCS # 1. Tak, to naprawdę takie proste.

Aby uzyskać więcej informacji o formatach PKCS # x w kodowaniu PEM, have a look at this site. Aby dowiedzieć się więcej o formacie ASN.1, here's a good site for that. A jeśli potrzebujesz prostego, ale potężnego i interaktywnego parsera ASN.1 online do zabawy w różnych formatach, takiego, który może bezpośrednio odczytać dane PEM, a także ASN.1 w base64 i hexdump, try this site.

Bardzo ważne: Przy dodawaniu klucz prywatny do pęku kluczy, który został utworzony jak powyżej, należy pamiętać, że taki klucz prywatny nie zawiera klawisz krzyżyka publicznego, jeszcze klawisz krzyżyka publiczny jest ważne dla ich keychain Interfejs API do utworzenia tożsamości (SecIdentityRef), ponieważ używanie klucza publicznego jest funkcją sprawdzania przez interfejs API właściwego klucza prywatnego należącego do zaimportowanego certyfikatu (SecIdentityRef to tylko SecKeyRef klucza prywatnego i SecCertificateRef certyfikatu tworzący kombinację obiekt i jest to skrót klucza publicznego, który wiąże je razem). Jeśli więc zamierzasz dodać klucz prywatny do pęku kluczy, upewnij się, że ustawiłeś ręcznie klucz publiczny, w przeciwnym razie nigdy nie będziesz w stanie uzyskać dla niego tożsamości, a bez tego nie będziesz mógł używać keychain API do zadań takich jak podpisywanie lub odszyfrowywanie. dane. Hash klucza publicznego musi być przechowywany w atrybucie o nazwie kSecAttrApplicationLabel (głupia nazwa, wiem, ale tak naprawdę nie jest to etykieta i nic, czego użytkownik nigdy nie zobaczy, sprawdź dokumentację). Np .:

OSStatus error = SecItemAdd(
    (__bridge CFDictionaryRef)@{ 
     (__bridge NSString *)kSecClass: 
      (__bridge NSString *)kSecClassKey, 
     (__bridge NSString *)kSecAttrApplicationLabel: 
      hashOfPublicKey, // hashOfPublicKey is NSData * 
#if TARGET_OS_IPHONE 
     (__bridge NSString *)kSecValueRef: 
      (__bridge id)privateKeyToAdd, // privateKeyToAdd is SecKeyRef 
#else 
     (__bridge NSString *)kSecUseItemList: 
       @[(__bridge id)privateKeyToAdd], // privateKeyToAdd is SecKeyRef 
#endif 
    }, 
    &outReference // Can also be NULL, 
        // otherwise reference to added keychain entry 
        // that must be released with CFRelease() 
); 
+0

Oprócz usuwania ----- BEGIN i ----- END linie znaków nowej linii powinny być również usunięte przed dekodowaniem base64. – mbonness

+0

@mbonness Każdy standardowy dekoder base64 musi być w stanie ignorować znaki nowej linii zgodnie z wymaganiami standardu, w przeciwnym razie nie mógłby nawet dekodować własnych wyników kodowania, które bardzo często zawierają znaki nowej linii. Podczas korzystania z 'initWithBase64EncodedString: options:' wystarczy użyć opcji 'NSDataBase64DecodingIgnoreUnknownCharacters'. Większość dekoderów base64 innych firm domyślnie ignoruje znaki nowej linii, które można wyłączyć tylko za pomocą opcji. – Mecki

2

Po godzinach wysiłków związanych z badaniem online za pomocą tego posta, w końcu udało mi się to doskonale. Oto notatki z działającym kodem SWIFT najnowszej wersji. Mam nadzieję, że może komuś pomóc!

  1. uzyskał certyfikat w base64 zakodowany ciąg przekładkowo między głowicą i ogon tak (formatu PEM):

    -----BEGIN CERTIFICATE----- 
    -----END CERTIFICATE----- 
    
  2. strip nagłówka i ogon, taki jak

    // remove the header string 
    let offset = ("-----BEGIN CERTIFICATE-----").characters.count 
    let index = certStr.index(cerStr.startIndex, offsetBy: offset+1) 
    cerStr = cerStr.substring(from: index) 
    
    // remove the tail string 
    let tailWord = "-----END CERTIFICATE-----" 
    if let lowerBound = cerStr.range(of: tailWord)?.lowerBound { 
    cerStr = cerStr.substring(to: lowerBound) 
    } 
    
  3. dekodowanie łańcucha base64 do NSData:

    let data = NSData(base64Encoded: cerStr, 
        options:NSData.Base64DecodingOptions.ignoreUnknownCharacters)! 
    
  4. przekonwertować go z formatu NSData do SecCertificate:

    let cert = SecCertificateCreateWithData(kCFAllocatorDefault, data) 
    
  5. Teraz ten cert mogą być wykorzystane do porównania z certyfikat otrzymany od zaufania urlSession:

    certificateFromUrl = SecTrustGetCertificateAtIndex(...) 
    if cert == certificate { 
    } 
    
Powiązane problemy