Crittografia dei dati in Cocoa, decodifica in PHP (e viceversa)

La situazione che sto cercando di risolvere: nella mia app Cocoa, ho bisogno di crittografare una string con un cifrario simmetrico, POSTla in PHP, e fare in modo che lo script decodifichi i dati. Il process deve funzionare al contrario per restituire una risposta (codifiche PHP, decodifiche Cocoa).

Mi manca qualcosa perché anche se riesco a far sì che sia la chiave che il vector di initialization (iv) siano gli stessi sia in PHP che in Cocoa, la decodifica non funziona mai quando un'app invia i suoi dati codificati all'altro. Entrambi funzionano bene codificando / decodificando i propri dati (verificato per assicurarsi che non ci fosse un problema PEBKAC a portta di mano). Ho il sospetto che ci sia un problema di imbottitura da qualche parte, semplicemente non lo vedo.

La mia app cocoa codifica utilizzando SSCrypto (che è solo un pratico dandy wrapper per le funzioni OpenSSL). Il codice è Blowfish, la modalità è CBC. (perdona le perdite di memory, il codice è stato spogliato per l'essenziale)

NSData *secretText = [@"secretTextToEncode" dataUsingEncoding:NSUTF8StringEncoding]; NSData *symmetricKey = [@"ThisIsMyKey" dataUsingEncoding:NSUTF8StringEncoding]; unsigned char *input = (unsigned char *)[secretText bytes]; unsigned char *outbuf; int outlen, templen, inlen; inlen = [secretText length]; unsigned char evp_key[EVP_MAX_KEY_LENGTH] = {"\0"}; int cipherMaxIVLength = EVP_MAX_IV_LENGTH; EVP_CIPHER_CTX cCtx; const EVP_CIPHER *cipher = EVP_bf_cbc(); cipherMaxIVLength = EVP_CIPHER_iv_length( cipher ); unsigned char iv[cipherMaxIVLength]; EVP_BytesToKey(cipher, EVP_md5(), NULL, [symmetricKey bytes], [symmetricKey length], 1, evp_key, iv); NSData *initVector = [NSData dataWithBytes:iv length:cipherMaxIVLength]; EVP_CIPHER_CTX_init(&cCtx); if (!EVP_EncryptInit_ex(&cCtx, cipher, NULL, evp_key, iv)) { EVP_CIPHER_CTX_cleanup(&cCtx); return nil; } int ctx_CipherKeyLength = EVP_CIPHER_CTX_key_length( &cCtx ); EVP_CIPHER_CTX_set_key_length(&cCtx, ctx_CipherKeyLength); outbuf = (unsigned char *)calloc(inlen + EVP_CIPHER_CTX_block_size(&cCtx), sizeof(unsigned char)); if (!EVP_EncryptUpdate(&cCtx, outbuf, &outlen, input, inlen)){ EVP_CIPHER_CTX_cleanup(&cCtx); return nil; } if (!EVP_EncryptFinal(&cCtx, outbuf + outlen, &templen)){ EVP_CIPHER_CTX_cleanup(&cCtx); return nil; } outlen += templen; EVP_CIPHER_CTX_cleanup(&cCtx); NSData *cipherText = [NSData dataWithBytes:outbuf length:outlen]; NSString *base64String = [cipherText encodeBase64WithNewlines:NO]; NSString *iv = [initVector encodeBase64WithNewlines:NO]; 

base64String e iv vengono quindi postati su PHP che tenta di decodificarlo:

 <?php import_request_variables( "p", "p_" ); if( $p_data != "" && $p_iv != "" ) { $encodedData = base64_decode( $p_data, true ); $iv = base64_decode( $p_iv, true ); $td = mcrypt_module_open( MCRYPT_BLOWFISH, '', MCRYPT_MODE_CBC, '' ); $keySize = mcrypt_enc_get_key_size( $td ); $key = substr( md5( "ThisIsMyKey" ), 0, $keySize ); $decodedData = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $encodedData, MCRYPT_MODE_CBC, $iv ); mcrypt_module_close( $td ); echo "decoded: " . $decodedData; } ?> 

decodedData è sempre senza senso.

Ho provato a invertire il process, inviando l'output codificato da PHP a Cocoa ma EVP_DecryptFinal () non riesce, che è quello che mi fa credere che ci sia un problema di riempimento NULL da qualche parte. Ho letto e riletto i documenti PHP e OpenSSL, ma ora tutto si confonde e non ho idee da provare.

Penso che il tuo problema sia che il metodo di derivare la chiave di crittografia grezza dalla string chiave è diverso sui due lati. La function php md5 () restituisce una string esadecimale, ad esempio 'a476c3 …' che si riduce alla dimensione della chiave, mentre EVP_BytesToKey () è una routine hash abbastanza complessa che restituisce una string di byte non elaborata. Potrebbe, con i parametri forniti, semplificare fino a un hash MD5 non elaborato, ma non lo so. Ad each modo, sarà diverso dall'hash di php.

Se cambi il php in md5 ("ThisIsMyKey", TRUE), questo ti darà un raw hash md5. Sul lato del cocoa delle cose, SSCrypto + getMD5ForData: il metodo dovrebbe generare lo stesso per la stessa string (problemi di codifica del text a parte).

Modifica 1: se la string php e i dati Cocoa vengono printingti in modo identico, sono ancora diversi a livello di byte. La string php è codificata in esadecimale (cioè consiste solo di caratteri 0-9 e af) mentre i dati del cocoa sono i byte non elaborati (sebbene NSData stampi in modo utile una string con codifica esadecimale dei suoi contenuti quando NSLoggato). È comunque necessario aggiungere il secondo parametro TRUE alla function mp5 () di php per get la string di byte non elaborata.

Modifica 2: OpenSSL 1.1.0c ha modificato l'algorithm del digest] (La crittografia / decrittografia non funziona bene tra due versioni di openssl diverse ) utilizzata in alcuni componenti interni. Precedentemente, è stato utilizzato MD5 e 1.1.0 è passato a SHA256. EVP_BytesToKey attenzione che il cambiamento non influisce su di te in EVP_BytesToKey e comandi come openssl enc .

Ho capito il mio problema. La risposta breve: la chiave utilizzata era di diverse lunghezze in Cocoa e PHP. La lunga risposta …

La mia richiesta originale era l'uso di Blowfish / CBC, che è un codice a lunghezza variabile da 16 byte a 56. Scendendo dall'idea di Boaz che la chiave fosse in qualche modo la colpa, sono passato a TripleDES per il codice poiché utilizza una lunghezza di chiave fissa di 24 byte. Fu allora che notai un problema: la chiave restituita da Cocoa / EVP_BytesToKey () era lunga 24 byte, ma il valore restituito da md5 () con la mia chiave era solo 16.

La soluzione al problema era di fare in modo che PHP creasse una chiave nello stesso modo in cui EVP_BytesToKey fa fino a quando la lunghezza dell'output fosse alless (cipherKeyLength + cipherIVLength). Il seguente PHP fa proprio questo (ignorando qualsiasi salt o count iterations)

 $cipher = MCRYPT_TRIPLEDES; $cipherMode = MCRYPT_MODE_CBC; $keySize = mcrypt_get_key_size( $cipher, $cipherMode ); $ivSize = mcrypt_get_iv_size( $cipher, $cipherMode ); $rawKey = "ThisIsMyKey"; $genKeyData = ''; do { $genKeyData = $genKeyData.md5( $genKeyData.$rawKey, true ); } while( strlen( $genKeyData ) < ($keySize + $ivSize) ); $generatedKey = substr( $genKeyData, 0, $keySize ); $generatedIV = substr( $genKeyData, $keySize, $ivSize ); $output = mcrypt_decrypt( $cipher, $generatedKey, $encodedData, $cipherMode, $generatedIV ); echo "output (hex)" . bin2hex($output); 

Nota che molto probabilmente sarà il riempimento PKCS # 5 alla fine di quell'output. Controlla i commenti qui http://us3.php.net/manual/en/ref.mcrypt.php per pkcs5_pad e pkcs5_unpad per aggiungere e rimuovere detto padding.

Fondamentalmente, prendi il valore md5 della chiave e se questo non è abbastanza lungo, aggiungi la chiave al risultato md5 e md5 quella string di nuovo. Lavare, risciacquare, ripetere. La pagina man di EVP_BytesToKey () spiega cosa sta facendo in realtà e mostra where mettere i valori salt, se necessario. Anche questo metodo di rigenerazione della chiave rigenera correttamente il vector di initialization (iv), quindi non è necessario passarlo.

Ma che mi dici di Blowfish?

EVP_BytesToKey() restituisce la chiave più piccola ansible per una cifra in quanto non accetta un context in base al quale basare una dimensione della chiave. Quindi la dimensione predefinita è tutto ciò che ottieni, che per Blowfish è di 16 byte. mcrypt_get_key_size() , d'altra parte, restituisce la dimensione della chiave più grande ansible. Quindi le seguenti righe nel mio codice originale:

 $keySize = mcrypt_enc_get_key_size( $td ); $key = substr( md5( "ThisIsMyKey" ), 0, $keySize ); 

restituirebbe sempre una chiave di 32 caratteri perché $ keySize è impostato su 56. Modifica del codice sopra a:

 $cipher = MCRYPT_BLOWFISH; $cipherMode = MCRYPT_MODE_CBC; $keySize = 16; 

consente a blowfish di decodificare correttamente, ma praticamente rovina il vantaggio di una chiave a lunghezza variabile. Per riassumere, EVP_BytesToKey() è rotto quando si tratta di cifrari a lunghezza variabile. È necessario creare una chiave / iv in modo diverso quando si utilizza una cifratura a chiave variabile. Non ci sono voluto molto perché 3DES functionrà per quello di cui ho bisogno.