AES Encryption สำหรับ NSString บน iPhone


124

ใครช่วยชี้ทางที่ถูกต้องให้ฉันสามารถเข้ารหัสสตริงส่งคืนสตริงอื่นด้วยข้อมูลที่เข้ารหัสได้ไหม (ฉันได้ลองใช้การเข้ารหัส AES256 แล้ว) ฉันต้องการเขียนวิธีที่ใช้อินสแตนซ์ NSString สองอินสแตนซ์อันหนึ่งเป็นข้อความในการเข้ารหัสและอีกอันเป็น 'รหัสผ่าน' เพื่อเข้ารหัสด้วย - ฉันสงสัยว่าฉันต้องสร้าง คีย์การเข้ารหัสด้วยรหัสผ่านในลักษณะที่สามารถย้อนกลับได้หากรหัสนั้นมาพร้อมกับข้อมูลที่เข้ารหัส วิธีนี้ควรส่งคืน NSString ที่สร้างจากข้อมูลที่เข้ารหัส

ฉันได้ลองใช้เทคนิคที่มีรายละเอียดในความคิดเห็นแรกในโพสต์นี้แล้ว แต่ฉันไม่มีโชคเลย CryptoExerciseของ Apple มีบางอย่างแน่นอน แต่ฉันไม่สามารถเข้าใจได้ ... ฉันเห็นการอ้างอิงถึงCCCryptมากมาย แต่ก็ล้มเหลวในทุกกรณีที่ฉันเคยใช้

ฉันจะต้องสามารถถอดรหัสสตริงที่เข้ารหัสได้ แต่ฉันหวังว่ามันจะง่ายเหมือน kCCEncrypt / kCCDecrypt


1
โปรดทราบว่าฉันได้ให้คำตอบจาก Rob Napier ซึ่งได้ให้คำตอบในเวอร์ชันที่ปลอดภัย
Maarten Bodewes

คำตอบ:


126

เนื่องจากคุณไม่ได้โพสต์โค้ดใด ๆ จึงเป็นการยากที่จะทราบว่าคุณพบปัญหาใด อย่างไรก็ตามโพสต์บล็อกที่คุณลิงก์ไปดูเหมือนจะใช้งานได้ดี ... นอกเหนือจากเครื่องหมายจุลภาคพิเศษในการเรียกแต่ละครั้งCCCrypt()ซึ่งทำให้เกิดข้อผิดพลาดในการคอมไพล์

ความคิดเห็นในภายหลังในโพสต์นั้นรวมถึงโค้ดที่ดัดแปลงนี้ซึ่งเหมาะกับฉันและดูเหมือนจะตรงไปตรงมากว่าเล็กน้อย หากคุณใส่รหัสของพวกเขาสำหรับหมวดหมู่ NSData คุณสามารถเขียนสิ่งนี้ได้: (หมายเหตุ: การprintf()โทรใช้เพื่อแสดงสถานะของข้อมูลตามจุดต่างๆเท่านั้น - ในแอปพลิเคชันจริงมันไม่สมเหตุสมผลที่จะพิมพ์ค่าดังกล่าว .)

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

ด้วยรหัสนี้และความจริงที่ว่าข้อมูลที่เข้ารหัสจะไม่สามารถแปลเป็น NSString ได้อย่างสวยงามเสมอไปการเขียนสองวิธีที่ครอบคลุมฟังก์ชันที่คุณต้องการอาจสะดวกกว่าในการไปข้างหน้าและย้อนกลับ ...

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

สิ่งนี้ใช้ได้กับ Snow Leopard อย่างแน่นอนและ@Bozรายงานว่า CommonCrypto เป็นส่วนหนึ่งของ Core OS บน iPhone ทั้ง 10.4 และ 10.5 มี/usr/include/CommonCryptoแม้ว่า 10.5 จะมี man page สำหรับCCCryptor.3ccและ 10.4 ก็ไม่มีดังนั้น YMMV


แก้ไข:ดูคำถามติดตามผลเกี่ยวกับการใช้การเข้ารหัส Base64 เพื่อแสดงไบต์ข้อมูลที่เข้ารหัสเป็นสตริง (หากต้องการ) โดยใช้การแปลงที่ปลอดภัยและไม่สูญเสีย


1
ขอบคุณ CommonCrypto เป็นส่วนหนึ่งของ Core OS บน iPhone และฉันใช้งาน 10.6 ด้วย
Boz

1
ฉันได้ -1 เนื่องจากรหัสอ้างอิงนั้นไม่ปลอดภัยอย่างอันตราย มองไปที่คำตอบของ Rob Napier แทน รายการบล็อกของเขา " robnapier.net/aes-commoncrypto ให้รายละเอียดว่าเหตุใดจึงไม่ปลอดภัย
Erik Engheim

1
วิธีนี้ใช้ไม่ได้ในกรณีของฉัน ฉันมีสตริงที่ฉันต้องการถอดรหัส: U2FsdGVkX1 + MEhsbofUNj58m + 8tu9ifAKRiY / Zf8YIw = และฉันมีคีย์: 3841b8485cd155d932a2d601b8cee2ec ฉันไม่สามารถถอดรหัสสตริงโดยใช้คีย์กับโซลูชันของคุณ ขอบคุณ
George

โซลูชันนี้ใช้ไม่ได้กับแอป Cocoa บน El Capitan ด้วย XCode7 ARC ห้ามautorelease.
Volomike

@QuinnTaylor ฉันสามารถแก้ไขคำตอบนี้ได้ แต่ต้องการให้คุณมีโอกาสเปลี่ยนแปลงตามที่เห็นสมควร ฉันซ่อมรหัสของคุณที่นี่ นอกจากนี้คุณอาจต้องการชี้ให้เห็นว่าหากไม่มีโค้ดที่ดัดแปลงนั้นจะไม่รวบรวม ดังนั้นฉันจึงทำงานกับแอปพลิเคชัน Cocoa บน El Capitan ด้วย XCode7 ตอนนี้สิ่งที่ฉันพยายามทำคือหาวิธี Base64Encode / Base64 ถอดรหัสข้อมูลนี้เพื่อให้สามารถส่งผ่านได้โดยไม่ถูกรบกวนระหว่างการส่งแทนที่จะส่งคืนข้อมูลดิบ
Volomike

46

ฉันได้รวบรวมหมวดหมู่สำหรับ NSData และ NSString ซึ่งใช้โซลูชันที่พบในบล็อกของ Jeff LaMarcheและคำแนะนำบางอย่างของ Quinn Taylor ที่นี่ใน Stack Overflow

ใช้หมวดหมู่เพื่อขยาย NSData เพื่อให้การเข้ารหัส AES256 และยังมีส่วนขยายของ NSString เป็น BASE64 ที่เข้ารหัสข้อมูลที่เข้ารหัสอย่างปลอดภัยไปยังสตริง

นี่คือตัวอย่างเพื่อแสดงการใช้งานสำหรับการเข้ารหัสสตริง:

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

รับซอร์สโค้ดฉบับเต็มที่นี่:

https://gist.github.com/838614

ขอบคุณสำหรับคำแนะนำที่เป็นประโยชน์ทั้งหมด!

- ไมเคิล


NSString * key = @ "YourEncryptionKey"; // ควรจัดเตรียมโดยผู้ใช้เราสามารถสร้างคีย์ 256 บิตแบบสุ่มที่ปลอดภัยแทนคีย์ที่ผู้ใช้ให้มาได้หรือไม่
Pranav Jaiswal

ลิงก์ Jeff LaMarche เสีย
whyoz

35

@owlstead เกี่ยวกับคำขอของคุณสำหรับ "ตัวแปรที่เชื่อถือได้เข้ารหัสของหนึ่งในคำตอบที่ได้รับ" โปรดดูRNCryptor ได้รับการออกแบบมาเพื่อทำสิ่งที่คุณร้องขอ (และสร้างขึ้นเพื่อตอบสนองต่อปัญหาเกี่ยวกับรหัสที่ระบุไว้ที่นี่)

RNCryptor ใช้ PBKDF2 กับเกลือให้ IV แบบสุ่มและแนบ HMAC (สร้างจาก PBKDF2 ด้วยเกลือของตัวเองด้วยสนับสนุนการทำงานแบบซิงโครนัสและอะซิงโครนัส


รหัสที่น่าสนใจและอาจคุ้มค่ากับคะแนน การนับซ้ำสำหรับ PBKDF2 เป็นเท่าใดและคุณคำนวณ HMAC ด้วยอะไร ฉันคิดว่าเป็นเพียงข้อมูลที่เข้ารหัส? ฉันไม่สามารถค้นหาสิ่งนั้นได้อย่างง่ายดายในเอกสารที่ให้มา
Maarten Bodewes

ดูรายละเอียดใน "แนวทางปฏิบัติที่ดีที่สุดในการรักษาความปลอดภัย" ฉันแนะนำการทำซ้ำ 10k บน iOS (~ 80ms บน iPhone 4) และใช่เข้ารหัสมากกว่า HMAC ฉันอาจจะดูหน้า "รูปแบบข้อมูล" คืนนี้เพื่อให้แน่ใจว่าเป็นเวอร์ชันล่าสุดในเวอร์ชัน 2.0 (เอกสารหลักเป็นเวอร์ชันล่าสุด แต่ฉันจำไม่ได้ว่าฉันแก้ไขหน้ารูปแบบข้อมูลหรือไม่)
Rob Napier

ใช่พบจำนวนรอบในเอกสารและดูรหัส ฉันเห็นฟังก์ชันล้างข้อมูลและแยก HMAC และคีย์การเข้ารหัสอยู่ในนั้น ถ้าเวลาเอื้ออำนวยฉันจะลองดูให้ลึกขึ้นในวันพรุ่งนี้ จากนั้นฉันจะกำหนดคะแนน
Maarten Bodewes

5
เข้ารหัสเป็น NSData และใช้หนึ่งในตัวเข้ารหัส Base64 จำนวนมากเพื่อแปลงเป็นสตริง ไม่มีวิธีเข้ารหัสจากสตริงเป็นสตริงโดยไม่มีตัวเข้ารหัสข้อมูลเป็นสตริง
Rob Napier

1
@ แจ็คตามคำแนะนำของทนายความของฉัน (ซึ่งอธิบายว่าฉันไม่มีความเชี่ยวชาญในกฎหมายการปฏิบัติตามกฎระเบียบการส่งออกในแง่ที่มีสีสันมาก…) ฉันไม่ได้ให้คำแนะนำเกี่ยวกับกฎหมายการปฏิบัติตามกฎระเบียบการส่งออกอีกต่อไป คุณจะต้องปรึกษากับทนายความของคุณ
Rob Napier

12

ฉันรอ @QuinnTaylor เล็กน้อยเพื่ออัปเดตคำตอบของเขา แต่เนื่องจากเขาไม่ทำนี่คือคำตอบที่ชัดเจนขึ้นเล็กน้อยและในลักษณะที่จะโหลดบน XCode7 (และอาจจะมากกว่า) ฉันใช้สิ่งนี้ในแอปพลิเคชัน Cocoa แต่ก็น่าจะใช้ได้กับแอปพลิเคชัน iOS เช่นกัน ไม่มีข้อผิดพลาด ARC

วางก่อนส่วน @implementation ในไฟล์ AppDelegate.m หรือ AppDelegate.mm

#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

วางฟังก์ชันทั้งสองนี้ในคลาส @implementation ที่คุณต้องการ ในกรณีของฉันฉันเลือก @implementation AppDelegate ในไฟล์ AppDelegate.mm หรือ AppDelegate.m

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

หมายเหตุ: 1. เมื่อถอดรหัสขนาดเอาต์พุตจะน้อยกว่าขนาดอินพุตเมื่อมีช่องว่างภายใน (PKCS # 7) ไม่มีเหตุผลที่จะเพิ่มขนาด bufferSize เพียงแค่ใช้ขนาดข้อมูลที่เข้ารหัส 2. แทนที่จะเป็น malloc'ing บัฟเฟอร์แล้วdataWithBytesNoCopyเพียงแค่จัดสรรNSMutableDataกับdataWithLengthและใช้mutableBytesคุณสมบัติสำหรับตัวชี้ไบต์จากนั้นปรับขนาดโดยการตั้งค่าเป็นlengthคุณสมบัติ 3. การใช้สตริงโดยตรงสำหรับการเข้ารหัสนั้นไม่ปลอดภัยมากควรใช้คีย์ที่ได้รับเช่นสร้างโดย PBKDF2
zaph

@zaph คุณสามารถทำ pastebin / pastie ที่ไหนสักแห่งเพื่อให้ฉันเห็นการเปลี่ยนแปลงได้หรือไม่? BTW ตามโค้ดด้านบนฉันแค่ปรับโค้ดที่ฉันเห็นจาก Quinn Taylor เพื่อให้มันใช้งานได้ ฉันยังคงเรียนรู้ธุรกิจนี้อยู่ตลอดเวลาและข้อมูลของคุณจะมีประโยชน์มากสำหรับฉัน
Volomike

ดูคำตอบ SOนี้และยังมีการจัดการข้อผิดพลาดขั้นต่ำและจัดการทั้งการเข้ารหัสและการถอดรหัส ไม่จำเป็นต้อง จำกัด ขอบเขตของบัฟเฟอร์ในการถอดรหัสมันเป็นเพียงรหัสที่น้อยกว่าซึ่งไม่ได้เชี่ยวชาญในการเพิ่มเติมหากมีน้อยที่จะได้รับ ในกรณีที่การขยายสำคัญกับ nulls เป็นที่ต้องการ (ที่ไม่ควรทำ) keyData.length = kCCKeySizeAES256;เพียงแค่สร้างรุ่นที่ไม่แน่นอนของคีย์และการตั้งค่าความยาว:
zaph

ดูคำตอบ SOนี้สำหรับการใช้ PBKDF2 เพื่อสร้างคีย์จากสตริง
zaph

@Volomike หากฉันใช้สิ่งนี้ฉันควรเลือกส่งออกข้อมูลการปฏิบัติตามข้อกำหนด (ใช่) บน iTunes-Connect หรือไม่
แจ็ค

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.