2013-04-27 89 views
2

我正在使用以下代碼來解密從android設備加密的文件。Java CipherInputStream解密行爲

private void mDecrypt_File(FileInputStream fin, String outFile) throws Exception { 
    FileOutputStream fout = new FileOutputStream(outFile); 

    byte[] iv = new byte[16]; 
    byte[] salt = new byte[16]; 
    byte[] len = new byte[8]; 
    byte[] FC_TAGBuffer = new byte[8]; 

    Cipher cipher = Cipher.getInstance(CIPHER_INSTANCE); 

    DataInputStream dis = new DataInputStream(fin); 

    dis.read(iv, 0, 16); 
    dis.read(salt, 0, 16); 

    Rfc2898DeriveBytes rfc = new Rfc2898DeriveBytes(DEFAULT_PASSWORD, salt, F_ITERATIONS); 
    SecretKey key = new SecretKeySpec(rfc.getBytes(32), "AES"); 

    //decryption code 
    cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); 
    CipherInputStream cIn = new CipherInputStream(dis, cipher); 

    cIn.read(len, 0, 8); 
    long lSize = getLong(len, 0); 

    cIn.read(FC_TAGBuffer, 0, 8); 

    byte[] tempFC_TAGBuffer = changeByteArray(FC_TAGBuffer, 0);//new byte[8];       

    BigInteger ulong = new BigInteger(1, tempFC_TAGBuffer); 

    if (!ulong.equals(FC_TAG)) { 
    Exception ex = new Exception("Tags are not equal"); 
    throw ex; 
    } 

    byte[] bytes = new byte[BUFFER_SIZE]; 
    //determine number of reads to process on the file       
    long numReads = lSize/BUFFER_SIZE; 
    // determine what is left of the file, after numReads     
    long slack = (long) lSize % BUFFER_SIZE; 

    int read = -1; 
    int value = 0; 
    int outValue = 0; 

    MessageDigest md = MessageDigest.getInstance("SHA-256"); 
    md.reset(); 
    // read the buffer_sized chunks   
    for (int i = 0; i < numReads; ++i) { 
    read = cIn.read(bytes, 0, bytes.length); 
    fout.write(bytes, 0, read); 
    md.update(bytes, 0, read); 
    value += read; 
    outValue += read; 
    } 
    // now read the slack     
    if (slack > 0) { 
    read = cIn.read(bytes, 0, (int) slack); 
    fout.write(bytes, 0, read); 
    md.update(bytes, 0, read); 
    value += read; 
    outValue += read; 
    } 
    fout.flush(); 
    fout.close(); 
    byte[] curHash = md.digest(); 

    byte[] oldHash = new byte[md.getDigestLength()]; 
    read = cIn.read(oldHash, 0, oldHash.length); 
    if (oldHash.length != read || (!CheckByteArrays(oldHash, curHash))) { 
    Exception ex = new Exception("File Corrupted!"); 
    throw ex; 
    } 
    if (outValue != lSize) { 
    Exception ex = new Exception("File Sizes don't match!"); 
    throw ex; 
    } 
} 

此代碼在Android上正常工作,但在Java桌面應用程序上表現奇怪。 我觀察到的是,在讀取CipherInputStream中的舊散列時,只有當要解密的數據的大小是32的倍數時,cIn才返回正確的散列值。例如,如果我加密的文本文件的長度爲32個字符或64/128/...),然後將下面的代碼

byte[] oldHash = new byte[md.getDigestLength()]; 
read = cIn.read(oldHash, 0, oldHash.length); 
if (oldHash.length != read || (!CheckByteArrays(oldHash, curHash))) { 
    Exception ex = new Exception("File Corrupted!"); 
    throw ex; 
} 

正確地計算oldHash,但如果我更改任何其他長度(不是32的倍數)的文本,然後將oldHash的最後幾個值變爲零。

我的觀察:

  1. 文字大小6點焦 - 尾隨零在oldHash - 6
  2. 文本大小13字符 - 尾隨零在oldHash - 13
  3. 文本大小20個字符 - 在oldHash尾隨零 - 4點
  4. 文本大小32點炭 - 尾隨在oldHash零 - 0 //正確結果
  5. 文本大小31點炭 - 尾隨零在oldHash - 1
  6. 文字大小64 char - oldHash中的拖尾零 - 0 //正確結果

請幫我理解此行爲。

+0

'read'方法不能保證填充你的字節數組。也許你只需要再讀一遍?您可以使用實用程序庫,例如['IOUtils.readFully'](http://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/IOUtils.html#readFully%28java。 io.InputStream,%20byte []%29)來包裝你的密碼輸入流。 – 2013-04-27 07:44:27

回答

4

同意DuncanJones,你的循環是一團糟。雖然您正確地檢查了read()方法的返回值,但循環迭代假定每個read()將返回最後一次讀取的BUFFER_SIZE個字節或「冗餘」字節。

如果您正確使用DataInputStream,那麼您的代碼將會非常好。例如,你包在一個DataInputStreamFileInputStreamfin但隨後使用這兩條線的錯誤方法:

dis.read(iv, 0, 16); 
dis.read(salt, 0, 16); 

相反,你應該使用readFully方法,如:

dis.readFully(iv); 
dis.readFully(salt); 

同樣,您將受益於用另一個DataInputStream封裝您的CipherInputStreamcIn,例如:

CipherInputStream cIn = new CipherInputStream(dis, cipher); 
DataInputStream dcIn = new DataInputStream(cIn); 

DataInputStream類已經有一個getLong方法,所以你可以只更換這些線路:

cIn.read(len, 0, 8); 
long lSize = getLong(len, 0); 

cIn.read(FC_TAGBuffer, 0, 8); 

long lSize = dcIn.getLong() 
dcIn.readFully(FC_TAGBuffer); 

,你會得到扔掉你的自產自銷getLong方法。現在,您可以使用dcIn.readFully(bytes)繼續讀取下一個lSize字節,並確保BUFFER_SIZE塊中的代碼更清晰,更短,更易於閱讀和更正。