Criptografia AES para um NSString no iPhone

124

Alguém pode me apontar na direção certa para poder criptografar uma string, retornando outra string com os dados criptografados? (Eu tenho tentado com a criptografia AES256.) Quero escrever um método que use duas instâncias NSString, uma sendo a mensagem para criptografar e a outra sendo um 'código de acesso' para criptografá-la - desconfio que teria que gerar a chave de criptografia com a senha, de uma maneira que pode ser revertida se a senha for fornecida com os dados criptografados. O método deve retornar um NSString criado a partir dos dados criptografados.

Eu tentei a técnica detalhada no primeiro comentário deste post , mas não tive sorte até agora. O CryptoExercise da Apple certamente tem algo, mas eu não consigo entender ... Eu já vi muitas referências ao CCCrypt , mas falhou em todos os casos em que o usei.

Eu também teria que ser capaz de descriptografar uma string criptografada, mas espero que seja tão simples quanto o kCCEncrypt / kCCDecrypt.

Boz
fonte
1
Observe que dei uma recompensa a uma resposta de Rob Napier, que forneceu uma versão segura da resposta.
Maarten Bodewes

Respostas:

126

Como você não publicou nenhum código, é difícil saber exatamente quais problemas você está enfrentando. No entanto, a postagem do blog ao qual você vincula parece funcionar com bastante decência ... além da vírgula extra em cada chamada na CCCrypt()qual causou erros de compilação.

Um comentário posterior sobre esse post inclui esse código adaptado , que funciona para mim e parece um pouco mais direto. Se você incluir o código para a categoria NSData, poderá escrever algo assim: (Nota: as printf()chamadas são apenas para demonstrar o estado dos dados em vários pontos - em um aplicativo real, não faria sentido imprimir esses valores .)

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

Dado esse código, e o fato de os dados criptografados nem sempre serem traduzidos de maneira agradável em um NSString, pode ser mais conveniente escrever dois métodos que envolvam a funcionalidade de que você precisa, tanto para frente quanto para trás ...

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

Isso definitivamente funciona no Snow Leopard, e a @Boz relata que o CommonCrypto faz parte do Core OS no iPhone. Tanto o 10.4 quanto o 10.5 possuem /usr/include/CommonCrypto, embora 10.5 tenha uma página de manual CCCryptor.3cce 10.4 não, portanto o YMMV.


EDIT: Consulte esta pergunta de acompanhamento sobre o uso da codificação Base64 para representar bytes de dados criptografados como uma sequência (se desejar) usando conversões seguras e sem perdas.

Quinn Taylor
fonte
1
Obrigado. O CommonCrypto faz parte do Core OS no iPhone e também estou executando o 10.6.
Boz
1
Eu fiz -1, porque o código referenciado é perigosamente inseguro. Veja a resposta de Rob Napier. Sua entrada de blog" robnapier.net/aes-commoncrypto detalhes exatamente porque este é inseguro.
Erik Engheim
1
Esta solução não funciona no meu caso. Eu tenho uma sequência que eu quero decodificar: U2FsdGVkX1 + MEhsbofUNj58m + 8tu9ifAKRiY / Zf8YIw = e tenho a chave: 3841b8485cd155d932a2d601b8cee2ec. Não consigo descriptografar a string usando a chave da sua solução. Obrigado
George
Esta solução não funciona em um aplicativo Cocoa no El Capitan com o XCode7. O ARC proíbe o autorelease.
Volomike
@QuinnTaylor Posso editar esta resposta, mas queria dar a você a oportunidade de alterá-la como achar melhor. Eu consertei seu código aqui . Além disso, você pode apontar que sem esse código adaptado , ele não será compilado. Então, consegui que funcionasse em um aplicativo Cocoa no El Capitan com o XCode7. Agora, o que estou tentando fazer é descobrir como Base64Encode / Base64Decode esses dados para que sejam transmitidos sem serem incomodados durante o transporte, em vez de retornar dados brutos.
Volomike
46

Reuni uma coleção de categorias para o NSData e o NSString, que utiliza soluções encontradas no blog de Jeff LaMarche e algumas dicas de Quinn Taylor aqui no Stack Overflow.

Ele usa categorias para estender o NSData para fornecer criptografia AES256 e também oferece uma extensão de NSString para dados criptografados com codificação BASE64 com segurança para seqüências de caracteres.

Aqui está um exemplo para mostrar o uso para criptografar seqüências de caracteres:

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

Obtenha o código fonte completo aqui:

https://gist.github.com/838614

Obrigado por todas as dicas úteis!

-- Michael

Michael Thiel
fonte
NSString * key = @ "YourEncryptionKey"; // deve ser fornecida por um usuário Podemos gerar uma chave aleatória segura de 256 bits, em vez de uma fornecida pelo usuário.
Pranav Jaiswal
O link de Jeff LaMarche está quebrado
whyoz
35

@owlstead, sobre sua solicitação de "uma variante criptograficamente segura de uma das respostas fornecidas", consulte RNCryptor . Ele foi projetado para fazer exatamente o que você está solicitando (e foi construído em resposta aos problemas com o código listado aqui).

O RNCryptor usa PBKDF2 com sal, fornece um IV aleatório e anexa HMAC (também gerado a partir de PBKDF2 com seu próprio sal. Ele suporta operação síncrona e assíncrona.

Rob Napier
fonte
Código interessante, e provavelmente vale a pena. Qual é a contagem de iterações para o PBKDF2 e sobre o que você calcula o HMAC? Presumo apenas os dados criptografados? Não consegui encontrar isso facilmente na documentação fornecida.
Maarten Bodewes
Veja "Segurança das melhores práticas" para obter detalhes. Eu recomendo 10k iterações no iOS (~ 80ms em um iPhone 4). E sim, criptografar que HMAC. Provavelmente examinarei a página "Formato dos dados" esta noite para garantir que esteja atualizada na v2.0 (os principais documentos estão atualizados, mas não me lembro se revisei a página do formato dos dados).
9138 Rob Napier
Ah, sim, localizei o número de rodadas nos documentos e procurei o código. Vejo funções de limpeza e separo HMAC e chaves de criptografia lá. Se o tempo permitir, tentarei dar uma olhada mais profunda amanhã. Então eu vou atribuir os pontos.
Maarten Bodewes
5
Criptografe para o NSData e use um dos muitos codificadores Base64 para convertê-lo em uma seqüência de caracteres. Não há como criptografar de uma string para uma string sem um codificador de dados para string.
9133 Rob Napier #
1
@ Jack Sob orientação do meu advogado (que descreveu minha falta de conhecimento em leis de conformidade de exportação em termos extremamente coloridos ...), não dou mais conselhos sobre leis de conformidade de exportação. Você precisará discutir com seu advogado.
Rob Napier
12

Eu esperei um pouco no @QuinnTaylor para atualizar sua resposta, mas como ele não o fez, aqui está a resposta um pouco mais clara e de uma maneira que será carregada no XCode7 (e talvez maior). Usei isso em um aplicativo Cocoa, mas provavelmente funcionará bem com um aplicativo iOS também. Não possui erros de ARC.

Cole antes de qualquer seção @implementation no seu arquivo AppDelegate.m ou 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

Cole essas duas funções na classe @implementation desejada. No meu caso, escolhi @implementation AppDelegate no meu arquivo AppDelegate.mm ou 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];
}
Volomike
fonte
Nota: 1. Ao descriptografar, o tamanho da saída será menor que o tamanho da entrada quando houver preenchimento (PKCS # 7). Não há motivo para aumentar o tamanho do buffer, basta usar o tamanho dos dados criptografados. 2. Em vez de alocar um buffer e, em seguida, dataWithBytesNoCopyapenas aloque um NSMutableDatacom dataWithLengthe use a mutableBytespropriedade para o ponteiro de bytes e, em seguida, redimensione definindo sua lengthpropriedade. 3. O uso direto de strings para uma criptografia é muito inseguro, uma chave derivada deve ser usada como a criada pelo PBKDF2.
Zaph
@ zaph, você pode fazer um pastebin / pastie em algum lugar para que eu possa ver as alterações? BTW, no código acima, eu apenas adaptei o código que vi de Quinn Taylor para fazê-lo funcionar. Ainda estou aprendendo esse negócio, e sua opinião será muito útil para mim.
Volomike 19/01
Veja esta resposta SO e ele ainda possui manipulação mínima de erros e lida com criptografia e descriptografia. Não há necessidade de estender o buffer na descriptografia, é apenas menos código que não é especializado em um adicional se houver pouco a ganhar. No caso estendendo a chave com valores nulos é desejada (que não deve ser feito) apenas criar uma versão mutável da chave e definir a duração: keyData.length = kCCKeySizeAES256;.
Zaph
Consulte esta resposta do SO para usar o PBKDF2 para criar uma chave a partir de uma sequência.
Zaph
@Volomike Se eu usar isso, devo selecionar Exportar informações de conformidade (SIM) no iTunes-Connect?
27418 Jack