iPhone의 NSString에 대한 AES 암호화


124

아무도 문자열을 암호화하고 암호화 된 데이터로 다른 문자열을 반환 할 수있는 올바른 방향으로 나를 가리킬 수 있습니까? (AES256 암호화를 시도해 왔습니다.) 두 개의 NSString 인스턴스를 사용하는 메서드를 작성하고 싶습니다. 하나는 암호화 할 메시지이고 다른 하나는 암호화 할 '암호'입니다. 생성해야 할 것 같습니다. 암호화 된 데이터와 함께 암호가 제공되는 경우 되돌릴 수있는 방식으로 암호가있는 암호화 키. 그런 다음 메서드는 암호화 된 데이터에서 생성 된 NSString을 반환해야합니다.

이 게시물의 첫 번째 댓글에 자세히 설명 된 기술을 시도했지만 지금까지 운이 없었습니다. Apple의 CryptoExercise 에는 확실히 뭔가가 있지만 이해할 수는 없습니다. 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가 iPhone의 Core OS의 일부 라고 보고합니다. 10.4와 10.5는 모두를 가지고 /usr/include/CommonCrypto있지만 10.5에는 man 페이지가 CCCryptor.3cc있고 10.4에는 그렇지 않으므로 YMMV.


편집 : 안전한 무손실 변환을 사용하여 암호화 된 데이터 바이트를 문자열 (원하는 경우)로 나타내는 Base64 인코딩 사용에 대한이 후속 질문 을 참조하십시오 .


1
감사. CommonCrypto는 iPhone의 Core OS의 일부이며 10.6도 실행 중입니다.
Boz

1
참조 된 코드가 위험 할 정도로 안전하지 않기 때문에 -1을했습니다. 대신 Rob Napier의 대답을보십시오. 그의 블로그 항목 " robnapier.net/aes-commoncrypto 가 이것이 왜 안전하지 않은지 정확히 자세히 설명합니다.
Erik Engheim 2014 년

1
이 솔루션은 제 경우에는 작동하지 않습니다. 디코딩 할 문자열이 있습니다 : U2FsdGVkX1 + MEhsbofUNj58m + 8tu9ifAKRiY / Zf8YIw = 그리고 키 : 3841b8485cd155d932a2d601b8cee2ec. 솔루션에서 키를 사용하여 문자열을 해독 할 수 없습니다. 감사합니다
조지

이 솔루션은 XCode7을 사용하는 El Capitan의 Cocoa 앱에서 작동하지 않습니다. ARC는 autorelease.
Volomike

@QuinnTaylor이 답변을 편집 할 수 있지만 적절하다고 판단되는대로 변경할 수있는 기회를 제공하고 싶었습니다. 여기에서 코드를 복구했습니다 . 또한 해당 코드 가 없으면 컴파일되지 않는다는 점을 지적 할 수 있습니다 . 그래서 XCode7을 사용하여 El Capitan의 Cocoa 응용 프로그램에서 작동하도록했습니다. 이제 내가하려는 것은 원시 데이터를 반환하는 대신 전송 중에 방해받지 않고 전송할 수 있도록이 데이터를 Base64Encode / Base64Decode하는 방법을 알아내는 것입니다.
Volomike

46

Jeff LaMarche의 블로그 에서 찾은 솔루션 과 몇 가지 힌트 를 사용하는 NSData 및 NSString에 대한 범주 모음을 모았습니다. 여기 Stack Overflow에서 Quinn Taylor의 .

범주를 사용하여 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 2012

Jeff LaMarche 링크가 끊어졌습니다
whyoz

35

@owlstead, "주어진 답변 중 하나의 암호화 된 보안 변종"에 대한 귀하의 요청과 관련하여 RNCryptor 를 참조하십시오 . 요청한 작업을 정확히 수행하도록 설계되었으며 여기에 나열된 코드의 문제에 대한 응답으로 구축되었습니다.

RNCryptor는 솔트와 함께 PBKDF2를 사용하고, 임의의 IV를 제공하고, HMAC (PBKDF2에서 자체 솔트로 생성됨)를 연결합니다. 동기 및 비동기 작업을 지원합니다.


흥미로운 코드이며 아마도 그만한 가치가 있습니다. PBKDF2의 반복 횟수는 얼마이며 HMAC를 어떻게 계산합니까? 암호화 된 데이터 만 추측합니까? 제공된 문서에서 쉽게 찾을 수 없습니다.
Maarten Bodewes

자세한 내용은 "모범 사례 보안"을 참조하십시오. iOS에서는 10k 반복을 권장합니다 (iPhone 4에서는 ~ 80ms). 그리고 예, HMAC보다 암호화합니다. 오늘 밤 "데이터 형식"페이지를 검토하여 v2.0에서 최신 버전인지 확인합니다 (주 문서는 최신 상태이지만 데이터 형식 페이지를 수정했는지 기억할 수 없습니다).
Rob Napier

아, 네, 문서에서 라운드 수를 찾아서 코드를 살펴 보았습니다. 정리 기능과 별도의 HMAC 및 암호화 키가 있습니다. 시간이 허락한다면 내일 더 자세히 살펴 보도록하겠습니다. 그런 다음 포인트를 할당합니다.
Maarten Bodewes

5
NSData로 암호화하고 여러 Base64 인코더 중 하나를 사용하여이를 문자열로 변환합니다. 데이터-문자열 인코더없이 문자열에서 문자열로 암호화하는 방법은 없습니다.
Rob Napier

1
@Jack 제 변호사의 조언에 따라 (수출 준수 법에 대한 저의 부족함을 매우 다채로운 용어로 설명했습니다…), 더 이상 수출 준수 법에 대한 조언을하지 않습니다. 변호사와상의해야합니다.
Rob Napier

12

나는 그의 답변을 업데이트하기 위해 @QuinnTaylor에서 조금 기다렸지 만 그가 업데이트하지 않았기 때문에 여기에 답변이 좀 더 명확하고 XCode7에로드되는 방식 (아마도 그 이상)이 있습니다. Cocoa 응용 프로그램에서 이것을 사용했지만 iOS 응용 프로그램에서도 잘 작동합니다. ARC 오류가 없습니다.

AppDelegate.m 또는 AppDelegate.mm 파일의 @implementation 섹션 앞에 붙여 넣으십시오.

#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 클래스에이 두 함수를 붙여 넣습니다. 제 경우에는 AppDelegate.mm 또는 AppDelegate.m 파일에서 @implementation AppDelegate를 선택했습니다.

- (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하는 대신 with를 dataWithBytesNoCopy할당 하고 바이트 포인터에 속성을 사용한 다음 속성을 설정하여 크기를 조정 합니다. 3. 암호화에 문자열을 직접 사용하는 것은 매우 안전하지 않으므로 PBKDF2에서 생성 한 것과 같은 파생 키를 사용해야합니다. NSMutableDatadataWithLengthmutableByteslength
zaph

@zaph, 어딘가에서 pastebin / pastie를 수행하여 변경 사항을 볼 수 있습니까? BTW, 위의 코드에서 저는 Quinn Taylor에서 본 코드를 적용하여 작동하도록했습니다. 나는 여전히이 사업을 배우고 있으며 당신의 의견은 나에게 매우 유용 할 것입니다.
Volomike

SO 답변을 참조하면 오류 처리가 최소화되고 암호화와 암호 해독이 모두 처리됩니다. 복호화시 버퍼를 확장 할 필요가 없으며, 얻을 것이 거의 없을 때 추가로 전문화되지 않은 코드가 적습니다. null로 키를 확장하는 것이 필요한 경우 (이렇게하면 안 됨) 키의 변경 가능한 버전을 만들고 길이를 설정합니다 keyData.length = kCCKeySizeAES256;..
zaph

PBKDF2를 사용하여 문자열에서 키를 만드는 방법 은이 SO 답변 을 참조하십시오 .
zaph

@Volomike 이것을 사용하는 경우 iTunes-Connect 에서 수출 규정 준수 정보 (예) 를 선택해야 합니까?
Jack

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.