2014-11-15 14 views
5

Sto lavorando su un'app di crittografia/decrittografia dei file. Sto usando un semplice file .txt per il test. Quando seleziono il file dall'app e scelgo di crittografarlo, l'intero file viene crittografato. Tuttavia, quando decritto solo una parte dei dati del file viene decifrato. Per qualche motivo i primi 16 byte/caratteri non vengono decifrati.android cipher non decrittografa i primi 16 byte/caratteri di dati crittografati

contenuti test_file.txt: "This sentence is used to check file encryption/decryption results."

risultato crittografia: "¾mÁSTÐÿT:Y­„"O¤]ÞPÕµß~ëqrÈb×ßq²¨†ldµJ,O|56\e^-’@þûÝû"

risultato decrittazione: "£ÿÒÜÑàh]VÄþ„- used to check file encryption/decryption results."

Non ci sono errori nel logcat.

Cosa sto sbagliando?

metodo per crittografare il file:

public void encryptFile(String password, String filePath) { 
    byte[] encryptedFileData = null; 
    byte[] fileData = null; 

    try { 
     fileData = readFile(filePath);//method provided below 

     // 64 bit salt for testing only 
     byte[] salt = "goodsalt".getBytes("UTF-8"); 
     SecretKey key = generateKey(password.toCharArray(), salt);//method provided below 

     byte[] keyData = key.getEncoded(); 
     SecretKeySpec sKeySpec = new SecretKeySpec(keyData, "AES"); 
     Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 
     cipher.init(Cipher.ENCRYPT_MODE, sKeySpec); 

     encryptedFileData = cipher.doFinal(fileData); 

     saveData(encryptedFileData, filePath);//method provided below 
    } 
    catch (Exception e) { 
     e.printStackTrace(); 
    } 
} 

metodo per leggere il contenuto del file:

public byte[] readFile(String filePath) { 
    byte[] fileData; 
    File file = new File(filePath); 
    int size = (int) file.length(); 
    fileData = new byte[size]; 

    try { 
     BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file)); 
     inputStream.read(fileData); 
     inputStream.close(); 
    } 
    catch (FileNotFoundException e) { 
     e.printStackTrace(); 
    } 
    catch (IOException e) { 
     e.printStackTrace(); 
    } 

    return fileData; 
} 

metodo per generare la chiave segreta:

private SecretKey generateKey(char[] password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException { 
    // Number of PBKDF2 hardening rounds to use. Larger values increase computation time. You 
    // should select a value that causes computation to take >100ms. 
    final int iterations = 1000; 

    // Generate a 256-bit key 
    final int outputKeyLength = 256; 

    SecretKeyFactory secretKeyFactory; 

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 
     // Use compatibility key factory -- only uses lower 8-bits of passphrase chars 
     secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1And8bit"); 
    } 
    else { 
     // Traditional key factory. Will use lower 8-bits of passphrase chars on 
     // older Android versions (API level 18 and lower) and all available bits 
     // on KitKat and newer (API level 19 and higher). 
     secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); 
    } 

    KeySpec keySpec = new PBEKeySpec(password, salt, iterations, outputKeyLength); 

    return secretKeyFactory.generateSecret(keySpec); 
} 

Metodo di salvare dati crittografati/decodificato il file:

private void saveData(byte[] newFileData, String filePath) { 
    File file = new File(filePath); 

    try { 
     BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file)); 

     outputStream.write(newFileData); 
     outputStream.flush(); 
     outputStream.close(); 
    } 
    catch (IOException e) { 
     e.printStackTrace(); 
    } 
} 

metodo per decodificare il file:

public void decryptFile(String password, String filePath) { 
    byte[] decryptedFileData = null; 
    byte[] fileData = null; 

    try { 
     fileData = readFile(filePath); 

     byte[] salt = "goodsalt".getBytes("UTF-8");//generateSalt(); 
     SecretKey key = generateKey(password.toCharArray(), salt); 

     byte[] keyData = key.getEncoded(); 
     SecretKeySpec sKeySpec = new SecretKeySpec(keyData, "AES"); 
     Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 
     cipher.init(Cipher.DECRYPT_MODE, sKeySpec); 

     decryptedFileData = cipher.doFinal(fileData); 

     saveData(decryptedFileData, filePath); 
    } 
    catch (Exception e) { 
     e.printStackTrace(); 
    } 
} 

Questa riga di codice codifica il file:

//simple password for testing only 
encryptor.encryptFile("password", "storage/emulated/0/Download/test_file.txt"); 

Questa linea decodifica il file di:

encryptor.decryptFile("password", "storage/emulated/0/Download/test_file.txt"); 

Edit: Grazie a DarkSquirrel42 e Oncaphillis. Voi ragazzi siete fantastici!

L'aggiunta di questa riga di codice alle funzioni di crittografia e decrittografia ha risolto il problema.

//note: the initialization vector (IV) must be 16 bytes in this case 
//so, if a user password is being used to create it, measures must 
//be taken to ensure proper IV length; random iv is best and should be 
//stored, possibly alongside the encrypted data 
IvParameterSpec ivSpec = new IvParameterSpec(password.getBytes("UTF-8")); 

e poi,

cipher.init(Cipher.XXXXXXX_MODE, sKeySpec, ivSpec); 
+0

Non utilizzare un IV prevedibile, ma generarne uno casuale e anteporre il testo cifrato. –

risposta

5

il problema ha a che fare con la modalità del cifrario di funzionamento ... CBC, o la modalità block chaining cifrario

in generale CBC è semplice .. prendi qualunque sia l'uscita del tuo blocco precedente di encryiption e xor quello sull'input corrente prima di crittarlo

per il primo blocco abbiamo ovviamente un problema ... c'è nessun blocco precedente ... quindi introduciamo qualcosa chiamato IV ... un vettore di inizializzazione ... un blocco ength di byte casuali ...

ora ... come potete immaginare, avrete bisogno della stessa IV quando vuoi decifrare ...

dal momento che non si salva che, l'implementazione AES vi darà un IV a caso ogni volta ...

quindi non si hanno tutte le informazioni per decifrare blocco 1 ... che è il primo 16 byte in caso di AES ...

quando si gestiscono dati in modalità CBC, è sempre una buona scelta per anteporre semplicemente l'IV usato alla propria uscita di cypertext ... l'IV sarà solo casuale ... non è un segreto. ..

2

Come @ ÐarkSquirrel42 indica già la routine di en/decrytion per CBC sembra interpretare i primi 16 byte come vettore di inizializzazione. Questo ha funzionato per me:

 // got to be random 
     byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 
     IvParameterSpec ivspec = new IvParameterSpec(iv); 
     cipher.init(Cipher.XXXXX_MODE, sKeySpec,ivspec); 
+0

Un IV fisso o prevedibile non deve essere utilizzato con [AES-CBC] (http://crypto.stackexchange.com/questions/12977/aes-cbc-and-iv-encrypting-multiple-blocks-of-a-file -with-the-same-iv) soprattutto se la chiave non cambia. –

+0

@ArtjomB. è per questo che ho commentato "// deve essere casuale" – Oncaphillis

+2

Ma deve essere casuale ogni volta, non solo una volta. I commenti sono solo questo, commenti. Il codice dovrebbe riflettere le tue intenzioni. –

Problemi correlati