Mã hóa AES cho NSString trên iPhone


124

Bất cứ ai cũng có thể chỉ cho tôi đi đúng hướng để có thể mã hóa một chuỗi, trả lại một chuỗi khác với dữ liệu được mã hóa? . khóa mã hóa với mật mã, theo cách có thể đảo ngược nếu mật mã được cung cấp cùng với dữ liệu được mã hóa. Phương thức sau đó sẽ trả về một NSString được tạo từ dữ liệu được mã hóa.

Tôi đã thử kỹ thuật chi tiết trong bình luận đầu tiên về bài đăng này , nhưng cho đến nay tôi không gặp may mắn. CryptoExercise của Apple chắc chắn có thứ gì đó, nhưng tôi không thể hiểu được ... Tôi đã thấy rất nhiều tài liệu tham khảo về CCCrypt , nhưng nó đã thất bại trong mọi trường hợp tôi đã sử dụng nó.

Tôi cũng sẽ phải có thể giải mã một chuỗi được mã hóa, nhưng tôi hy vọng điều đó đơn giản như kCCEncrypt / kCCDecrypt.


1
Xin lưu ý rằng tôi đã đưa tiền thưởng cho câu trả lời của Rob Napier, người đã cung cấp phiên bản bảo mật của câu trả lời.
Maarten Bodewes

Câu trả lời:


126

Vì bạn chưa đăng bất kỳ mã nào, thật khó để biết chính xác vấn đề bạn gặp phải. Tuy nhiên, bài đăng blog mà bạn liên kết đến dường như hoạt động khá tốt ... ngoài dấu phẩy thừa trong mỗi cuộc gọi CCCrypt()gây ra lỗi biên dịch.

Một nhận xét sau đó về bài đăng đó bao gồm mã được điều chỉnh này, phù hợp với tôi và có vẻ đơn giản hơn một chút. Nếu bạn bao gồm mã của họ cho danh mục NSData, bạn có thể viết một cái gì đó như thế này: (Lưu ý: Các printf()cuộc gọi chỉ để thể hiện trạng thái của dữ liệu tại nhiều điểm khác nhau - trong một ứng dụng thực tế, sẽ không có ý nghĩa khi in các giá trị đó .)

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

Với mã này và thực tế là dữ liệu được mã hóa sẽ không phải lúc nào cũng dịch độc đáo thành NSString, có thể thuận tiện hơn khi viết hai phương thức bao hàm các chức năng bạn cần, tiến và lùi ...

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

Điều này chắc chắn hoạt động trên Snow Leopard và @Boz báo cáo rằng CommonCrypto là một phần của Hệ điều hành lõi trên iPhone. Cả 10,4 và 10,5 đều có /usr/include/CommonCrypto, mặc dù 10,5 có trang man cho CCCryptor.3ccvà 10,4 không có, vì vậy YMMV.


EDIT: Xem câu hỏi tiếp theo này về việc sử dụng mã hóa Base64 để biểu diễn các byte dữ liệu được mã hóa dưới dạng chuỗi (nếu muốn) bằng cách sử dụng các chuyển đổi an toàn, không mất dữ liệu.


1
Cảm ơn. CommonCrypto là một phần của HĐH lõi trên iPhone và tôi cũng đang chạy 10.6.
Boz

1
Tôi đã làm -1, bởi vì mã được tham chiếu là không an toàn nguy hiểm. Thay vào đó, hãy nhìn vào câu trả lời của Rob Napier. Bài viết trên blog của anh ấy " robnapier.net/aes-commoncrypto chi tiết chính xác lý do tại sao điều này không an toàn.
Erik Engheim

1
Giải pháp này không hoạt động trong trường hợp của tôi. Tôi có một chuỗi mà tôi muốn giải mã: U2FsdGVkX1 + MEhsbofUNj58m + 8tu9ifAKRiY / Zf8YIw = và tôi có khóa: 3841b8485cd155d932a2d601b8ce2. Tôi không thể giải mã chuỗi bằng cách sử dụng khóa với giải pháp của bạn. Cảm ơn
George

Giải pháp này không hoạt động trong ứng dụng Cacao trên El Capitan với XCode7. ARC cấm autorelease.
Volomike

@QuinnTaylor Tôi có thể chỉnh sửa câu trả lời này, nhưng muốn cho bạn cơ hội để thay đổi nó khi bạn thấy phù hợp. Tôi đã sửa mã của bạn ở đây . Ngoài ra, bạn có thể muốn chỉ ra rằng nếu không có mã được điều chỉnh đó, nó sẽ không được biên dịch. Vì vậy, tôi đã làm cho nó hoạt động trên ứng dụng Cacao trên El Capitan với XCode7. Bây giờ những gì tôi đang cố gắng làm là tìm ra cách Base64Encode / Base64Decode dữ liệu này để nó có thể truyền được mà không bị xáo trộn trong quá trình, thay vì trả về dữ liệu thô.
Volomike

46

Tôi đã tập hợp một bộ sưu tập các danh mục cho NSData và NSString, sử dụng các giải pháp được tìm thấy trên blog của Jeff LaMarchemột số gợi ý của Quinn Taylor tại đây trên Stack Overflow.

Nó sử dụng các danh mục để mở rộng NSData để cung cấp mã hóa AES256 và cũng cung cấp một phần mở rộng của NSString sang dữ liệu được mã hóa BASE64 một cách an toàn cho các chuỗi.

Dưới đây là một ví dụ để hiển thị cách sử dụng để mã hóa chuỗi:

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] );

Lấy mã nguồn đầy đủ ở đây:

https://gist.github.com/838614

Cảm ơn tất cả các gợi ý hữu ích!

- Michael


NSString * key = @ "YourEncryptKey"; // phải được cung cấp bởi người dùng Chúng ta có thể tạo khóa 256 bit an toàn ngẫu nhiên, thay vì khóa do người dùng cung cấp.
Pranav Jaiswal

Liên kết Jeff LaMarche bị hỏng
whyoz

35

@owlstead, liên quan đến yêu cầu của bạn về "một biến thể bảo mật bằng mật mã của một trong những câu trả lời đã cho", vui lòng xem RNCryptor . Nó được thiết kế để thực hiện chính xác những gì bạn yêu cầu (và được xây dựng để đáp ứng với các vấn đề với mã được liệt kê ở đây).

RNCryptor sử dụng PBKDF2 với muối, cung cấp IV ngẫu nhiên và đính kèm HMAC (cũng được tạo từ PBKDF2 bằng muối của chính nó. Nó hỗ trợ hoạt động đồng bộ và không đồng bộ.


Mã thú vị, và có lẽ giá trị điểm. Số lần lặp cho PBKDF2 là bao nhiêu và bạn tính toán được HMAC là bao nhiêu? Tôi đoán chỉ là dữ liệu được mã hóa? Tôi không thể tìm thấy điều đó một cách dễ dàng trong tài liệu được cung cấp.
Maarten Bodewes

Nhìn vào "Bảo mật thực hành tốt nhất" để biết chi tiết. Tôi khuyên dùng 10k lần lặp trên iOS (~ 80ms trên iPhone 4). Và vâng, mã hóa-hơn-HMAC. Có lẽ tôi sẽ xem qua trang "Định dạng dữ liệu" tối nay để đảm bảo cập nhật trên v2.0 (các tài liệu chính được cập nhật, nhưng tôi không thể nhớ nếu tôi đã sửa đổi trang định dạng dữ liệu).
Rob Napier

Ah, yeah, đã tìm thấy số vòng trong tài liệu và xem mã. Tôi thấy các chức năng dọn dẹp và các khóa mã hóa và mã hóa riêng biệt trong đó. Nếu thời gian cho phép tôi sẽ cố gắng và nhìn sâu hơn vào ngày mai. Sau đó, tôi sẽ chỉ định điểm.
Maarten Bodewes

5
Mã hóa cho NSData và sử dụng một trong nhiều bộ mã hóa Base64 để chuyển đổi chuỗi đó thành chuỗi. Không có cách nào để mã hóa từ chuỗi thành chuỗi mà không có bộ mã hóa dữ liệu thành chuỗi.
Rob Napier

1
@Jack Theo lời khuyên của luật sư của tôi (người mô tả sự thiếu chuyên môn của tôi về luật tuân thủ xuất khẩu theo thuật ngữ cực kỳ sặc sỡ), tôi không còn đưa ra lời khuyên về luật tuân thủ xuất khẩu. Bạn sẽ cần thảo luận với luật sư của bạn.
Rob Napier

12

Tôi đã đợi một chút trên @QuinnTaylor để cập nhật câu trả lời của anh ấy, nhưng vì anh ấy đã không làm, đây là câu trả lời rõ ràng hơn một chút và theo cách mà nó sẽ tải trên XCode7 (và có lẽ lớn hơn). Tôi đã sử dụng điều này trong một ứng dụng Cacao, nhưng nó cũng có thể hoạt động tốt với ứng dụng iOS. Không có lỗi ARC.

Dán trước bất kỳ phần @imcellenceation nào trong tệp AppDelegate.m hoặc AppDelegate.mm của bạn.

#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

Dán hai hàm này vào lớp @im THỰCation mà bạn mong muốn. Trong trường hợp của tôi, tôi đã chọn @imâyation AppDelegate trong tệp AppDelegate.mm hoặc AppDelegate.m của tôi.

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

Lưu ý: 1. Khi giải mã kích thước đầu ra sẽ nhỏ hơn kích thước đầu vào khi có phần đệm (PKCS # 7). Không có lý do để tăng kích thước bộ đệm, chỉ cần sử dụng kích thước dữ liệu được mã hóa. 2. Thay vì malloc'ing một bộ đệm và sau đó dataWithBytesNoCopychỉ phân bổ một NSMutableDatavới dataWithLengthvà sử dụng mutableBytestài sản cho con trỏ byte và sau đó chỉ cần thay đổi kích thước bằng cách thiết lập nó lengthsở hữu. 3. Sử dụng chuỗi trực tiếp để mã hóa rất không an toàn, nên sử dụng khóa dẫn xuất như được tạo bởi PBKDF2.
zaph

@zaph, bạn có thể làm pastebin / pastie ở đâu đó để tôi có thể thấy những thay đổi không? BTW, ở đoạn mã trên, tôi chỉ điều chỉnh mã tôi thấy từ Quinn Taylor để làm cho nó hoạt động. Tôi vẫn đang học kinh doanh này khi tôi đi, và đầu vào của bạn sẽ rất hữu ích cho tôi.
Volomike

Xem câu trả lời SO này và nó thậm chí còn xử lý lỗi tối thiểu và xử lý cả mã hóa và giải mã. Không cần phải mở rộng bộ đệm khi giải mã, nó chỉ là ít mã không chuyên với một bổ sung nếu khi có ít để đạt được. Trong trường hợp mở rộng khóa bằng null là mong muốn (không nên thực hiện), chỉ cần tạo một phiên bản có thể thay đổi của khóa và đặt độ dài : keyData.length = kCCKeySizeAES256;.
zaph

Xem câu trả lời SO này để sử dụng PBKDF2 để tạo khóa từ chuỗi.
zaph

@Volomike Nếu tôi sử dụng điều này, thì tôi có nên chọn Xuất thông tin tuân thủ (CÓ) trên iTunes-Connect không?
Jack

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.