Enkripsi AES untuk NSString di iPhone

124

Adakah yang bisa mengarahkan saya ke arah yang benar untuk dapat mengenkripsi string, mengembalikan string lain dengan data terenkripsi? (Saya sudah mencoba dengan enkripsi AES256.) Saya ingin menulis metode yang membutuhkan dua contoh NSString, satu adalah pesan untuk dienkripsi dan yang lainnya adalah 'kode sandi' untuk mengenkripsinya - saya kira saya harus membuat kunci enkripsi dengan kode sandi, dengan cara yang dapat dibalik jika kode sandi disertakan dengan data terenkripsi. Metode ini kemudian harus mengembalikan NSString yang dibuat dari data yang dienkripsi.

Saya sudah mencoba teknik rinci dalam komentar pertama di posting ini , tetapi sejauh ini saya tidak berhasil. CryptoExercise Apple pasti memiliki sesuatu, tetapi saya tidak dapat memahaminya ... Saya telah melihat banyak referensi ke CCCrypt , tetapi gagal dalam setiap kasus yang saya gunakan.

Saya juga harus bisa mendekripsi string terenkripsi, tapi saya harap itu sesederhana kCCEncrypt / kCCDecrypt.

Boz
sumber
1
Harap dicatat bahwa saya telah diberikan karunia untuk jawaban oleh Rob Napier yang telah memberikan aman versi jawabannya.
Maarten Bodewes

Jawaban:

126

Karena Anda belum memposting kode apa pun, sulit untuk mengetahui dengan tepat masalah yang Anda hadapi. Namun, posting blog yang Anda tautkan tampaknya berfungsi dengan cukup baik ... selain dari koma tambahan di setiap panggilan CCCrypt()yang menyebabkan kesalahan kompilasi.

Komentar selanjutnya pada posting itu menyertakan kode yang disesuaikan ini , yang berfungsi untuk saya, dan tampaknya sedikit lebih mudah. Jika Anda menyertakan kode mereka untuk kategori NSData, Anda dapat menulis sesuatu seperti ini: (Catatan: printf()Panggilan hanya untuk mendemonstrasikan status data di berbagai titik - dalam aplikasi nyata, tidak masuk akal untuk mencetak nilai seperti itu .)

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    NSString *key = @"my password";
    NSString *secret = @"text to encrypt";

    NSData *plain = [secret dataUsingEncoding:NSUTF8StringEncoding];
    NSData *cipher = [plain AES256EncryptWithKey:key];
    printf("%s\n", [[cipher description] UTF8String]);

    plain = [cipher AES256DecryptWithKey:key];
    printf("%s\n", [[plain description] UTF8String]);
    printf("%s\n", [[[NSString alloc] initWithData:plain encoding:NSUTF8StringEncoding] UTF8String]);

    [pool drain];
    return 0;
}

Dengan kode ini, dan fakta bahwa data terenkripsi tidak akan selalu diterjemahkan dengan baik ke dalam NSString, mungkin lebih mudah untuk menulis dua metode yang membungkus fungsionalitas yang Anda butuhkan, maju dan mundur ...

- (NSData*) encryptString:(NSString*)plaintext withKey:(NSString*)key {
    return [[plaintext dataUsingEncoding:NSUTF8StringEncoding] AES256EncryptWithKey:key];
}

- (NSString*) decryptData:(NSData*)ciphertext withKey:(NSString*)key {
    return [[[NSString alloc] initWithData:[ciphertext AES256DecryptWithKey:key]
                                  encoding:NSUTF8StringEncoding] autorelease];
}

Ini pasti berfungsi pada Snow Leopard, dan @Boz melaporkan bahwa CommonCrypto adalah bagian dari Core OS di iPhone. Baik 10.4 dan 10.5 memiliki /usr/include/CommonCrypto, meskipun 10.5 memiliki halaman manual CCCryptor.3ccdan 10.4 tidak, jadi YMMV.


EDIT: Lihat pertanyaan tindak lanjut ini tentang menggunakan pengkodean Base64 untuk mewakili byte data terenkripsi sebagai string (jika diinginkan) menggunakan konversi yang aman dan tanpa kehilangan.

Quinn Taylor
sumber
1
Terima kasih. CommonCrypto adalah bagian dari Core OS di iPhone, dan saya juga menjalankan 10.6.
Boz
1
Saya melakukan -1, karena kode yang direferensikan sangat tidak aman. Lihat jawaban Rob Napier sebagai gantinya. Entri blognya " robnapier.net/aes-commoncrypto menjelaskan secara detail mengapa ini tidak aman.
Erik Engheim
1
Solusi ini tidak berfungsi dalam kasus saya. Saya memiliki string yang ingin saya pecahkan kode: U2FsdGVkX1 + MEhsbofUNj58m + 8tu9ifAKRiY / Zf8YIw = dan saya memiliki kuncinya: 3841b8485cd155d932a2d601b8cee2ec. Saya tidak dapat mendekripsi string menggunakan kunci dengan solusi Anda. Terima kasih
George
Solusi ini tidak berfungsi di aplikasi Cocoa di El Capitan dengan XCode7. ARC melarang autorelease.
Volomike
@QuinnTaylor Saya dapat mengedit jawaban ini, tetapi ingin memberi Anda kesempatan untuk mengubahnya sesuai keinginan Anda. Saya memperbaiki kode Anda di sini . Juga, Anda mungkin ingin menunjukkan bahwa tanpa kode yang disesuaikan itu, itu tidak akan bisa dikompilasi. Jadi, saya membuatnya bekerja pada aplikasi Kakao di El Capitan dengan XCode7. Sekarang yang saya coba lakukan adalah mencari cara bagaimana Base64Encode / Base64Decode data ini sehingga dapat ditransmisikan tanpa diganggu saat transit, daripada mengembalikan data mentah.
Volomike
46

Saya telah mengumpulkan koleksi kategori untuk NSData dan NSString yang menggunakan solusi yang ditemukan di blog Jeff LaMarche dan beberapa petunjuk oleh Quinn Taylor di sini di Stack Overflow.

Ini menggunakan kategori untuk memperluas NSData untuk menyediakan enkripsi AES256 dan juga menawarkan ekstensi NSString ke data terenkripsi BASE64 dengan aman ke string.

Berikut adalah contoh untuk menunjukkan penggunaan untuk mengenkripsi string:

NSString *plainString = @"This string will be encrypted";
NSString *key = @"YourEncryptionKey"; // should be provided by a user

NSLog( @"Original String: %@", plainString );

NSString *encryptedString = [plainString AES256EncryptWithKey:key];
NSLog( @"Encrypted String: %@", encryptedString );

NSLog( @"Decrypted String: %@", [encryptedString AES256DecryptWithKey:key] );

Dapatkan kode sumber lengkapnya di sini:

https://gist.github.com/838614

Terima kasih atas semua petunjuk yang berguna!

- Michael

Michael Thiel
sumber
NSString * key = @ "YourEncryptionKey"; // harus disediakan oleh pengguna. Bisakah kita membuat kunci 256-bit aman yang acak, bukan yang disediakan oleh pengguna.
Pranav Jaiswal
Tautan Jeff LaMarche rusak
whyoz
35

@owlead, terkait permintaan Anda untuk "varian yang aman secara kriptografis dari salah satu jawaban yang diberikan," silakan lihat RNCryptor . Itu dirancang untuk melakukan persis apa yang Anda minta (dan dibangun sebagai tanggapan atas masalah dengan kode yang tercantum di sini).

RNCryptor menggunakan PBKDF2 dengan garam, memberikan IV acak, dan memasang HMAC (juga dihasilkan dari PBKDF2 dengan garamnya sendiri. Ini mendukung operasi sinkron dan asinkron.

Rob Napier
sumber
Kode yang menarik, dan mungkin sepadan dengan poinnya. Berapa hitungan iterasi untuk PBKDF2 dan berapa HMAC yang Anda hitung? Saya menganggap hanya data yang dienkripsi? Saya tidak dapat menemukannya dengan mudah dalam dokumentasi yang disediakan.
Maarten Bodewes
Lihat "Keamanan praktik terbaik" untuk detailnya. Saya merekomendasikan 10k iterasi di iOS (~ 80ms di iPhone 4). Dan ya, encrypt-than-HMAC. Saya mungkin akan melihat-lihat halaman "Format data" malam ini untuk memastikannya up to date pada v2.0 (dokumen utama adalah yang terbaru, tetapi saya tidak ingat apakah saya merevisi halaman format data).
Rob Napier
Ah, ya, temukan jumlah putaran di dokumen dan lihat kodenya. Saya melihat fungsi pembersihan dan HMAC terpisah dan kunci enkripsi di sana. Jika waktu mengizinkan saya akan mencoba dan melihat lebih dalam besok. Lalu saya akan memberikan poin.
Maarten Bodewes
5
Enkripsi ke NSData, dan gunakan salah satu dari banyak pembuat enkode Base64 untuk mengubahnya menjadi string. Tidak ada cara untuk mengenkripsi dari string ke string tanpa encoder data-ke-string.
Rob Napier
1
@Jack Atas nasihat pengacara saya (yang menggambarkan kurangnya keahlian saya dalam hukum kepatuhan ekspor dengan istilah yang sangat berwarna…), saya tidak lagi memberikan nasihat tentang hukum kepatuhan ekspor. Anda harus berdiskusi dengan pengacara Anda.
Rob Napier
12

Saya menunggu sedikit di @QuinnTaylor untuk memperbarui jawabannya, tetapi karena dia tidak melakukannya, inilah jawabannya sedikit lebih jelas dan dengan cara yang akan dimuat di XCode7 (dan mungkin lebih besar). Saya menggunakan ini di aplikasi Cocoa, tetapi kemungkinan akan berfungsi dengan baik dengan aplikasi iOS juga. Tidak memiliki kesalahan ARC.

Tempelkan sebelum bagian @implementation mana pun di file AppDelegate.m atau AppDelegate.mm Anda.

#import <CommonCrypto/CommonCryptor.h>

@implementation NSData (AES256)

- (NSData *)AES256EncryptWithKey:(NSString *)key {
    // 'key' should be 32 bytes for AES256, will be null-padded otherwise
    char keyPtr[kCCKeySizeAES256+1]; // room for terminator (unused)
    bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)

    // fetch key data
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    NSUInteger dataLength = [self length];

    //See the doc: For block ciphers, the output size will always be less than or 
    //equal to the input size plus the size of one block.
    //That's why we need to add the size of one block here
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);

    size_t numBytesEncrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                     keyPtr, kCCKeySizeAES256,
                                     NULL /* initialization vector (optional) */,
                                     [self bytes], dataLength, /* input */
                                     buffer, bufferSize, /* output */
                                     &numBytesEncrypted);
    if (cryptStatus == kCCSuccess) {
        //the returned NSData takes ownership of the buffer and will free it on deallocation
        return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
    }

    free(buffer); //free the buffer;
    return nil;
}

- (NSData *)AES256DecryptWithKey:(NSString *)key {
    // 'key' should be 32 bytes for AES256, will be null-padded otherwise
    char keyPtr[kCCKeySizeAES256+1]; // room for terminator (unused)
    bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)

    // fetch key data
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    NSUInteger dataLength = [self length];

    //See the doc: For block ciphers, the output size will always be less than or 
    //equal to the input size plus the size of one block.
    //That's why we need to add the size of one block here
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);

    size_t numBytesDecrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                     keyPtr, kCCKeySizeAES256,
                                     NULL /* initialization vector (optional) */,
                                     [self bytes], dataLength, /* input */
                                     buffer, bufferSize, /* output */
                                     &numBytesDecrypted);

    if (cryptStatus == kCCSuccess) {
        //the returned NSData takes ownership of the buffer and will free it on deallocation
        return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
    }

    free(buffer); //free the buffer;
    return nil;
}

@end

Tempelkan kedua fungsi ini di kelas @implementation yang Anda inginkan. Dalam kasus saya, saya memilih @implementation AppDelegate di file AppDelegate.mm atau AppDelegate.m saya.

- (NSString *) encryptString:(NSString*)plaintext withKey:(NSString*)key {
    NSData *data = [[plaintext dataUsingEncoding:NSUTF8StringEncoding] AES256EncryptWithKey:key];
    return [data base64EncodedStringWithOptions:kNilOptions];
}

- (NSString *) decryptString:(NSString *)ciphertext withKey:(NSString*)key {
    NSData *data = [[NSData alloc] initWithBase64EncodedString:ciphertext options:kNilOptions];
    return [[NSString alloc] initWithData:[data AES256DecryptWithKey:key] encoding:NSUTF8StringEncoding];
}
Volomike
sumber
Catatan: 1. Saat mendekripsi ukuran keluaran akan lebih kecil dari ukuran masukan bila ada padding (PKCS # 7). Tidak ada alasan untuk meningkatkan bufferSize, cukup gunakan ukuran data terenkripsi. 2. Daripada malloc'ing buffer dan kemudian dataWithBytesNoCopyalokasikan NSMutableDatadengan dataWithLengthdan gunakan mutableBytesproperti untuk pointer byte dan kemudian ubah ukurannya dengan menyetel lengthpropertinya. 3. Menggunakan string secara langsung untuk enkripsi sangat tidak aman, kunci turunan harus digunakan seperti yang dibuat oleh PBKDF2.
zaf
@zaph, dapatkah Anda melakukan pastebin / pastie di suatu tempat agar saya dapat melihat perubahannya? BTW, pada kode di atas, saya hanya mengadaptasi kode yang saya lihat dari Quinn Taylor agar bisa berfungsi. Saya masih mempelajari bisnis ini sambil berjalan, dan masukan Anda akan sangat berguna bagi saya.
Volomike
Lihat jawaban SO ini dan bahkan memiliki penanganan kesalahan minimum dan menangani enkripsi dan dekripsi. Tidak perlu memperluas buffer pada dekripsi, hanya lebih sedikit kode yang tidak mengkhususkan diri dengan tambahan jika ada sedikit yang bisa diperoleh. Dalam hal memperluas kunci dengan nulls diinginkan (yang tidak boleh dilakukan) hanya membuat versi bisa berubah dari kunci dan mengatur panjang: keyData.length = kCCKeySizeAES256;.
zaf
Lihat jawaban SO ini untuk menggunakan PBKDF2 untuk membuat kunci dari string.
zaf
@Volomike Jika saya menggunakan ini, lalu haruskah saya memilih Export Compliance Information (YES) di iTunes-Connect?
Jack