使用AES-128和密钥加密NSString

Tri*_*ert 2 security encryption cocoa nsstring

我有一个基本的笔记应用程序,我想让用户有加密或安全的笔记.我有一个用户界面,但是现在,我似乎无法加密.它要么返回一堆垃圾,要么根本没有.这是我用来/解密的:

- (BOOL) encryptWithAES128Key: (NSString *) key {
    // 'key' should be 16 bytes for AES128, will be null-padded otherwise
    char * keyPtr[kCCKeySizeAES128+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];

    // encrypts in-place, since this is a mutable data object
    size_t numBytesEncrypted = 0;
    CCCryptorStatus result = CCCrypt(kCCEncrypt, kCCAlgorithmAES128 , kCCOptionPKCS7Padding, 
                                     keyPtr, kCCKeySizeAES128,
                                     NULL /* initialization vector (optional) */, 
                                     [self mutableBytes], [self length], /* input */
                                     [self mutableBytes], [self length] + kCCBlockSizeAES128, /* output */
                                     &numBytesEncrypted);

    return (result == kCCSuccess);
}

- (NSMutableData *) decryptWithAES128Key: (NSString *) key {
    // 'key' should be 16 bytes for AES128, will be null-padded otherwise
    char * keyPtr[kCCKeySizeAES128+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];

    // encrypts in-place, since this is a mutable data object
    size_t bufferSize           = [self length] + kCCBlockSizeAES128;
    void* buffer                = malloc(bufferSize);

    size_t numBytesEncrypted = 0;
    CCCryptorStatus result = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, 
                                     keyPtr, kCCKeySizeAES128,
                                     NULL /* initialization vector (optional) */, 
                                     [self bytes], [self length], /* input */
                                     buffer, bufferSize, /* output */
                                     &numBytesEncrypted);   


    if(result == kCCSuccess || result == kCCParamError) {
        return [[NSMutableData dataWithBytesNoCopy:buffer length:numBytesEncrypted] retain];
    }

    return nil;
}
Run Code Online (Sandbox Code Playgroud)

有谁知道为什么这可能会出错?

编辑1: 我修改了我的en/decryption代码是一样的.以下是它现在的样子:

- (BOOL) encryptWithAES128Key: (NSString *) key {
    CCCryptorStatus ccStatus = kCCSuccess;
    // Symmetric crypto reference.
    CCCryptorRef thisEncipher = NULL;
    // Cipher Text container.
    NSData * cipherOrPlainText = nil;
    // Pointer to output buffer.
    uint8_t * bufferPtr = NULL;
    // Total size of the buffer.
    size_t bufferPtrSize = 0;
    // Remaining bytes to be performed on.
    size_t remainingBytes = 0;
    // Number of bytes moved to buffer.
    size_t movedBytes = 0;
    // Length of plainText buffer.
    size_t plainTextBufferSize = 0;
    // Placeholder for total written.
    size_t totalBytesWritten = 0;
    // A friendly helper pointer.
    uint8_t * ptr;

    // Initialization vector; dummy in this case 0's.
    uint8_t iv[kCCBlockSizeAES128];
    memset((void *) iv, 0x0, (size_t) sizeof(iv));
    plainTextBufferSize = [self length];

    ccStatus = CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, (const void *)[key UTF8String], kCCKeySizeAES128, (const void *)iv, &thisEncipher);

    // Calculate byte block alignment for all calls through to and including final.
    bufferPtrSize = CCCryptorGetOutputLength(thisEncipher, plainTextBufferSize, true);

    // Allocate buffer.
    bufferPtr = [self mutableBytes];

    // Zero out buffer.
    //memset((void *)bufferPtr, 0x0, bufferPtrSize);

    // Initialize some necessary book keeping.

    ptr = bufferPtr;

    // Set up initial size.
    remainingBytes = bufferPtrSize;

    // Actually perform the encryption or decryption.
    ccStatus = CCCryptorUpdate(thisEncipher, (const void *) [self bytes], plainTextBufferSize, ptr, remainingBytes, &movedBytes);

    ptr += movedBytes;
    remainingBytes -= movedBytes;
    totalBytesWritten += movedBytes;

    // Finalize everything to the output buffer.
    ccStatus = CCCryptorFinal(thisEncipher, ptr, remainingBytes, &movedBytes);

    cipherOrPlainText = [NSData dataWithBytes:(const void *)bufferPtr length:(NSUInteger)totalBytesWritten];
    NSLog(@"data: %@", cipherOrPlainText);

    NSLog(@"buffer: %s", bufferPtr);

    CCCryptorRelease(thisEncipher);
    thisEncipher = NULL;
    if(bufferPtr) free(bufferPtr);
}

- (NSMutableData *) decryptWithAES128Key: (NSString *) key {    
    CCCryptorStatus ccStatus = kCCSuccess;
    // Symmetric crypto reference.
    CCCryptorRef thisEncipher = NULL;
    // Cipher Text container.
    NSData * cipherOrPlainText = nil;
    // Pointer to output buffer.
    uint8_t * bufferPtr = NULL;
    // Total size of the buffer.
    size_t bufferPtrSize = 0;
    // Remaining bytes to be performed on.
    size_t remainingBytes = 0;
    // Number of bytes moved to buffer.
    size_t movedBytes = 0;
    // Length of plainText buffer.
    size_t plainTextBufferSize = 0;
    // Placeholder for total written.
    size_t totalBytesWritten = 0;
    // A friendly helper pointer.
    uint8_t * ptr;

    // Initialization vector; dummy in this case 0's.
    uint8_t iv[kCCBlockSizeAES128];
    memset((void *) iv, 0x0, (size_t) sizeof(iv));
    plainTextBufferSize = [self length];

    ccStatus = CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, (const void *)[key UTF8String], kCCKeySizeAES128, (const void *)iv, &thisEncipher);

    // Calculate byte block alignment for all calls through to and including final.
    bufferPtrSize = CCCryptorGetOutputLength(thisEncipher, plainTextBufferSize, true);

    // Allocate buffer.
    bufferPtr = malloc( bufferPtrSize * sizeof(uint8_t) );

    // Zero out buffer.
    memset((void *)bufferPtr, 0x0, bufferPtrSize);

    // Initialize some necessary book keeping.

    ptr = bufferPtr;

    // Set up initial size.
    remainingBytes = bufferPtrSize;

    // Actually perform the encryption or decryption.
    ccStatus = CCCryptorUpdate(thisEncipher, (const void *) [self bytes], plainTextBufferSize, ptr, remainingBytes, &movedBytes);

    ptr += movedBytes;
    remainingBytes -= movedBytes;
    totalBytesWritten += movedBytes;

    // Finalize everything to the output buffer.
    ccStatus = CCCryptorFinal(thisEncipher, ptr, remainingBytes, &movedBytes);

    cipherOrPlainText = [NSData dataWithBytes:(const void *)bufferPtr length:(NSUInteger)totalBytesWritten];
    NSLog(@"data: %@", cipherOrPlainText);

    NSLog(@"buffer: %s", bufferPtr);

    CCCryptorRelease(thisEncipher);
    thisEncipher = NULL;
    if(bufferPtr) free(bufferPtr);

    return [NSMutableData dataWithData:cipherOrPlainText];
}
Run Code Online (Sandbox Code Playgroud)

这段代码有点用.如果我使用密码'1234567890123456'加密此字符串:

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>dict</key>
    <dict>
        <key>device</key>
        <string>Tristan's Magical Macbook of Death</string>
        <key>text</key>
        <string>e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxMDM4XGNvY29hc3VicnRm
MzYwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEhlbHZldGljYTt9Cntc
Y29sb3J0Ymw7XHJlZDI1NVxncmVlbjI1NVxibHVlMjU1O30KXHBhcmRcdHg1NjBc
dHgxMTIwXHR4MTY4MFx0eDIyNDBcdHgyODAwXHR4MzM2MFx0eDM5MjBcdHg0NDgw
XHR4NTA0MFx0eDU2MDBcdHg2MTYwXHR4NjcyMFxxbFxxbmF0dXJhbFxwYXJkaXJu
YXR1cmFsCgpcZjBcZnMyNCBcY2YwIFx1bCBcdWxjMCBCTEFILn0=
</string>
        <key>title</key>
        <string>Welcome to Notepaddy!</string>
        <key>uuid</key>
        <string>5yvghz9n4ukgefnbx0qa2xne3nxeebcmcvpci9j5lwpncul1asftdayjv8a</string>
    </dict>
    <key>text</key>
    <string>e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxMDM4XGNvY29hc3VicnRm
MzYwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEhlbHZldGljYTt9Cntc
Y29sb3J0Ymw7XHJlZDI1NVxncmVlbjI1NVxibHVlMjU1O30KXHBhcmRcdHg1NjBc
dHgxMTIwXHR4MTY4MFx0eDIyNDBcdHgyODAwXHR4MzM2MFx0eDM5MjBcdHg0NDgw
XHR4NTA0MFx0eDU2MDBcdHg2MTYwXHR4NjcyMFxxbFxxbmF0dXJhbFxwYXJkaXJu
YXR1cmFsCgpcZjBcZnMyNCBcY2YwIFx1bCBcdWxjMCBCTEFILn0=
</string>
    <key>title</key>
    <string>Welcome to Notepaddy!</string>
    <key>uuid</key>
    <string>5yvghz9n4ukgefnbx0qa2xne3nxeebcmcvpci9j5lwpncul1asftdayjv8a</string>
</dict>
</plist>
Run Code Online (Sandbox Code Playgroud)

我得到了相同的文字,但整个文本</plist>都丢失了,而且</dict>被切断了.解密和打印结果字符串时,使用密码"0987654321123456"或任何其他密码加密时会给我完全垃圾,或者在复制到密码字段时与上面相同.

tc.*_*tc. 5

两个版本都有相同的问题:您告诉CommonCrypto写入缓冲区的末尾,然后忽略结果.

第一个版本:

[self mutableBytes], [self length] + kCCBlockSizeAES128, /* output */
Run Code Online (Sandbox Code Playgroud)

第二个版本:

// Calculate byte block alignment for all calls through to and including final.
bufferPtrSize = CCCryptorGetOutputLength(thisEncipher, plainTextBufferSize, true);

// Allocate buffer.
bufferPtr = [self mutableBytes];
Run Code Online (Sandbox Code Playgroud)

那是不对的.你没有分配任何东西.你告诉它把bufferPtrSize字节写入一个大小的缓冲区[self length]!

你想做更像这样的事情(如果你真的想要就地加密):

// Calculate byte block alignment for all calls through to and including final.
bufferPtrSize = CCCryptorGetOutputLength(thisEncipher, plainTextBufferSize, true);
// Increase my size if necessary:
if (bufferPtrSize > self.length) {
  self.length = bufferPtrSize;
}
Run Code Online (Sandbox Code Playgroud)

我也不确定为什么加密是就地而解密不是; 如果有的话,后者更容易做到.

您的第二个版本还有其他问题:

  • 你释放了你没有分配的东西: if(bufferPtr) free(bufferPtr);
  • 你可能会读过字符串的结尾: (const void *)[key UTF8String], kCCKeySizeAES128

额外的加密问题:

  • 密钥应该是固定大小的并且具有相当大的熵.天真地将字符串转换为字节并不是一个好的密钥(对于一个,长度超过16字节的密钥被有效地截断).你能做的最少就是散列它.您可能还想迭代哈希,或者只使用PBKDF2(诚然,我还没有找到PBKDF2的规范/测试向量...)
  • 你几乎肯定也想使用随机IV(参见SecRandomCopyBytes).

附录:

您看到截断结果的原因是因为您返回截断的答案(使用PKCS7填充,加密结果始终大于原始数据).机会(大约255/256)是最后一个密文块被错误地填充(因为你给了CCryptor截断的数据),所以ccStatus说发生了一个错误但是你忽略了这个并且无论如何都返回了结果.这是非常糟糕的做法.(另外,你真的想使用带CBC的MAC来避免填充oracle安全漏洞.)

编辑:

一些似乎工作的代码看起来像这样(完成测试用例):

笔记:

  • 实际上并没有在iOS上测试过(虽然非iOS代码应该在iOS上运行;它只是SecRandomCopyBytes是一个稍微好一点的界面,但在OS X上不可用).
  • read()循环可能是正确的,但未经过全面测试.
  • 密文以IV为前缀.这是"教科书"方法,但使密文更大.
  • 没有身份验证,因此此代码可以充当填充oracle.
  • 不支持AES-192或AES-256.添加起来并不困难(您只需要打开密钥长度并适当选择算法).
  • 密钥被指定为NSData,因此您需要执行类似的操作[string dataUsingEncoding:NSUTF8StringEncoding].对于奖励积分,请通过CC_SHA256运行并获取前16个输出字节.
  • 没有就地操作.我认为这不值得.

.

#include <Foundation/Foundation.h>
#include <CommonCrypto/CommonCryptor.h>

#if TARGET_OS_IPHONE
#include <Security/SecRandom.h>
#else
#include <fcntl.h>
#include <unistd.h>
#endif

@interface NSData(AES)
- (NSData*) encryptedDataUsingAESKey: (NSData *) key;
- (NSData*) decryptedDataUsingAESKey: (NSData *) key;
@end
@implementation NSData(AES)

- (NSData*) encryptedDataUsingAESKey: (NSData *) key {
        uint8_t iv[kCCBlockSizeAES128];
#if TARGET_OS_IPHONE
        if (0 != SecRandomCopyBytes(kSecRandomDefault, sizeof(iv), iv))
        {
                return nil;
        }
#else
        {
                int fd = open("/dev/urandom", O_RDONLY);
                if (fd < 0) { return nil; }
                ssize_t bytesRead;
                for (uint8_t * p = iv; (bytesRead = read(fd,p,iv+sizeof(iv)-p)); p += (size_t)bytesRead) {
                        // 0 means EOF.
                        if (bytesRead == 0) { close(fd); return nil; }
                        // -1, EINTR means we got a system call before any data could be read.
                        // Pretend we read 0 bytes (since we already handled EOF).
                        if (bytesRead < 0 && errno == EINTR) { bytesRead = 0; }
                        // Other errors are real errors.
                        if (bytesRead < 0) { close(fd); return nil; }
                }
                close(fd);
        }
#endif
        size_t retSize = 0;
        CCCryptorStatus result = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, 
                                     [key bytes], [key length],
                                     iv,
                                     [self bytes], [self length],
                                     NULL, 0,
                                     &retSize);
        if (result != kCCBufferTooSmall) { return nil; }

        // Prefix the data with the IV (the textbook method).
        // This requires adding sizeof(iv) in a few places later; oh well.
        void * retPtr = malloc(retSize+sizeof(iv));
        if (!retPtr) { return nil; }

        // Copy the IV.
        memcpy(retPtr, iv, sizeof(iv));

        result = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, 
                                     [key bytes], [key length],
                                     iv,
                                     [self bytes], [self length],
                                     retPtr+sizeof(iv),retSize,
                                     &retSize);
        if (result != kCCSuccess) { free(retPtr); return nil; }

        NSData * ret = [NSData dataWithBytesNoCopy:retPtr length:retSize+sizeof(iv)];
        // Does +[NSData dataWithBytesNoCopy:length:] free if allocation of the NSData fails?
        // Assume it does.
        if (!ret) { free(retPtr); return nil; }
        return ret;
}

- (NSData*) decryptedDataUsingAESKey: (NSData *) key {
        const uint8_t * p = [self bytes];
        size_t length = [self length];
        if (length < kCCBlockSizeAES128) { return nil; }

        size_t retSize = 0;
        CCCryptorStatus result = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, 
                                     [key bytes], [key length],
                                     p,
                                     p+kCCBlockSizeAES128, length-kCCBlockSizeAES128,
                                     NULL, 0,
                                     &retSize);
        if (result != kCCBufferTooSmall) { return nil; }

        void * retPtr = malloc(retSize);
        if (!retPtr) { return nil; }

        result = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, 
                                     [key bytes], [key length],
                                     p,
                                     p+kCCBlockSizeAES128, length-kCCBlockSizeAES128,
                                     retPtr, retSize,
                                     &retSize);
        if (result != kCCSuccess) { free(retPtr); return nil; }

        NSData * ret = [NSData dataWithBytesNoCopy:retPtr length:retSize];
        // Does +[NSData dataWithBytesNoCopy:length:] free if allocation of the NSData fails?
        // Assume it does.
        if (!ret) { free(retPtr); return nil; }
        return ret;
}

@end

void test(NSData * data, NSData * key)
{
        NSLog(@"%@, %@", data, key);
        NSData * enc = [data encryptedDataUsingAESKey:key];
        NSLog(@"%@", enc);
        NSData * dec = [enc decryptedDataUsingAESKey:key];
        NSLog(@"%@", dec);
        NSLog((data == dec || [data isEqual:dec]) ? @"pass" : @"FAIL");
}

int main()
{
#define d(x) [NSData dataWithBytesNoCopy:("" x) length:sizeof("" x)-1 freeWhenDone:0]
        [NSAutoreleasePool new];
        NSData * key = d("0123456789abcdef");
        test([NSData data], key);
        test(d(""), key);
        test(d("a"), key);
        test(d("0123456789abcde"), key);
        test(d("0123456789abcdef"), key);
        test(d("0123456789abcdef0"), key);
        test(d("0123456789abcdef0123456789abcde"), key);
        test(d("0123456789abcdef0123456789abcdef"), key);
        test(d("0123456789abcdef0123456789abcdef0"), key);
}
Run Code Online (Sandbox Code Playgroud)