iPhoneでのNSStringのAES暗号化


124

文字列を暗号化して、暗号化されたデータを含む別の文字列を返すことができるように、誰かが私を正しい方向に向けることができますか?(私はAES256暗号化を試してみました。)2つのNSStringインスタンスを取得するメソッドを書きたいと思います。1つは暗号化するメッセージで、もう1つはそれを暗号化する「パスコード」です-生成する必要があると思いますパスコードが暗号化されたデータとともに提供された場合に逆にすることができる方法で、パスコードを含む暗号化キー。次に、メソッドは暗号化されたデータから作成されたNSStringを返す必要があります。

私はこの投稿の最初のコメントで詳述されいる手法を試しましたが、今のところ運がありません。AppleのCryptoExerciseには確かに何かがありますが、それを理解することはできません... CCCryptへの参照をたくさん見ましたが、それを使用したすべてのケースで失敗しました。

暗号化された文字列を復号化できる必要もありますが、kCCEncrypt / kCCDecryptと同じくらい簡単であることを願っています。


1
安全なバージョンの回答を提供してくれたRob Napierから回答に賞金を差し上げていることに注意してください。
Maarten Bodewes 2012年

回答:


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にうまく変換されるとは限らないという事実を踏まえると、必要な機能をラップする2つのメソッドをフォワードとリバースで記述する方が便利かもしれません...

- (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のコアOSの一部であると報告しています。10.4と10.5にはがありますが/usr/include/CommonCrypto、10.5にはmanページがCCCryptor.3ccあり、10.4にはないので、YMMVです。


編集:安全でロスレスの変換を使用して、暗号化されたデータバイトを(必要に応じて)文字列として表現するためにBase64エンコーディングを使用することに関する次の質問を参照してください。


1
ありがとう。CommonCryptoはiPhoneのコアOSの一部であり、私も10.6を実行しています。
ボズ

1
参照されたコードは危険なほど安全でないため、-1にしました。代わりにロブネイピアの答えを見てください。彼のブログエントリ」robnapier.net/aes-commoncrypto詳細はまさに、なぜこれは危険です。
エリックEngheim

1
このソリューションは私の場合は機能しません。私はデコードしたい文字列を持っています:U2FsdGVkX1 + MEhsbofUNj58m + 8tu9ifAKRiY / Zf8YIw =そして私はキーを持っています:3841b8485cd155d932a2d601b8cee2ec。ソリューションでキーを使用して文字列を復号化できません。ありがとう
ジョージ

このソリューションは、XCode7を搭載したEl CapitanのCocoaアプリでは機能しません。ARCは禁止しautoreleaseます。
Volomike 2016年

@QuinnTaylorこの回答は編集できますが、必要に応じて変更できるようにしたいと考えています。 ここでコードを修復しました。また、あなたはそれなしでそれを指摘したいかもしれませんその適応コードがないとコンパイルできないことおくとよいでしょう。だから、私はそれをXCode7でEl Capitan上のCocoaアプリケーションで動作させるようにしました。今私がやろうとしているのは、このデータをBase64Encode / Base64Decodeして、生データを返すのではなく、転送中に妨害されることなく送信できるようにする方法を理解することです。
Volomike、2016年

46

私は、NSDataとNSStringのカテゴリのコレクションをまとめました。これは、Jeff LaMarcheのブログにあるソリューションとここでのスタックオーバーフローに関するQuinn Taylorのヒントを使用しています。

カテゴリを使用してNSDataを拡張してAES256暗号化を提供し、暗号化されたデータを文字列に安全にBASE64エンコードするNSStringの拡張も提供します。

文字列の暗号化の使用法を示す例を次に示します。

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、「与えられた答えの1つの暗号的に安全なバリアント」のリクエストについては、RNCryptorを参照してください。それはあなたが要求していることを正確に行うように設計されました(そしてここにリストされたコードの問題に応じて構築されました)。

RNCryptorはPBKDF2をソルトとともに使用し、ランダムIVを提供し、HMAC(これも独自のソルトを使用してPBKDF2から生成されます。同期および非同期操作をサポートします)をアタッチします。


興味深いコードで、おそらくポイントに値します。PBKDF2の反復数はいくつですか?また、HMACは何を計算しますか?暗号化されたデータだけを想定していますか?提供されているドキュメントでは簡単に見つけることができませんでした。
Maarten Bodewes 2012

詳細については、「ベストプラクティスのセキュリティ」をご覧ください。私はiOSで1万回の反復を推奨します(iPhone 4では約80ms)。そして、はい、HMACより暗号化します。私はおそらく今夜「データフォーマット」ページを調べて、それがv2.0で最新であることを確認します(メインドキュメントは最新ですが、データフォーマットページを改訂したかどうか思い出せません)。
Rob Napier

ええ、ええ、ドキュメントでラウンド数を見つけてコードを調べました。そこにクリーンアップ関数と個別のHMACおよび暗号化キーが表示されます。時間が許せば、明日はもっと深く見ていきます。次に、ポイントを割り当てます。
Maarten Bodewes 2012

5
NSDataに暗号化し、多くのBase64エンコーダーの1つを使用してそれを文字列に変換します。データから文字列へのエンコーダーなしでは、文字列から文字列に暗号化する方法はありません。
Rob Napier

1
@ジャック私の輸出コンプライアンス法に関する専門知識の欠如を非常にカラフルな言葉で述べた弁護士の助言により、私はもはや輸出コンプライアンス法についての助言をしません。弁護士と相談する必要があります。
ロブ・ネーピア

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

これらの2つの関数を、必要な@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する代わりにdataWithBytesNoCopyNSMutableDatawith dataWithLengthを割り当てmutableBytesてバイトポインタのプロパティを使用し、プロパティを設定してサイズを変更しlengthます。3.暗号化に直接文字列を使用することは非常に安全ではありません。PBKDF2によって作成されたものなどの派生キーを使用する必要があります。
zaph '19年

@zaph、私が変更を確認できるように、どこかにペーストビン/パスティーを実行できますか?ところで、上記のコードについては、動作させるために、クインテイラーから見たコードを単に変更しました。私はまだこのビジネスを学んでいるので、あなたの意見は私にとって非常に役に立ちます。
Volomike 2016年

このSOの回答を参照してください。エラー処理も最小限で、暗号化と復号化の両方を処理します。復号化の際にバッファを拡張する必要はありません。得られるものがほとんどない場合は、追加のコードに特化していないコードが少なくなります。nullを使用してキーを拡張する必要がある場合(これを行うべきではありません)、キーの可変バージョンを作成し、長さを設定しますkeyData.length = kCCKeySizeAES256;
zaph

PBKDF2を使用して文字列からキーを作成するには、このSOの回答を参照してください。
zaph 2016年

@Volomikeこれを使用する場合 、iTunes-Connectで「コンプライアンス情報のエクスポート(はい)」を選択する必要がありますか?
ジャック

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.