2011-03-15 47 views
9

我想要使用以下代碼來實現某些數據必須加密和解密的高併發應用程序。所以我需要知道該代碼的哪一部分應該同步,以避免不可預知的問題。這個Java加密代碼線程安全嗎?

public class DesEncrypter { 
    Cipher ecipher; 
    Cipher dcipher; 

    // 8-byte Salt 
    byte[] salt = { 
     (byte)0xA9, (byte)0x9B, (byte)0xC8, (byte)0x32, 
     (byte)0x56, (byte)0x35, (byte)0xE3, (byte)0x03 
    }; 

    int iterationCount = 19; 

    DesEncrypter(String passPhrase) { 
     try { 
      // Create the key 
      KeySpec keySpec = new PBEKeySpec(passPhrase.toCharArray(), salt, iterationCount); 

      SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndDES").generateSecret(keySpec); 
      ecipher = Cipher.getInstance(key.getAlgorithm()); 
      dcipher = Cipher.getInstance(key.getAlgorithm()); 

      // Prepare the parameter to the ciphers 
      AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, iterationCount); 

      // Create the ciphers 
      ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec); 
      dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec); 
     } catch (...) 
    } 

    public String encrypt(String str) { 
     try { 
      // Encode the string into bytes using utf-8 
      byte[] utf8 = str.getBytes("UTF8"); 
      // Encrypt 
      byte[] enc = ecipher.doFinal(utf8); 
      // Encode bytes to base64 to get a string 
      return new sun.misc.BASE64Encoder().encode(enc); 

     } catch (...) 
    } 

    public String decrypt(String str) { 
     try { 
      // Decode base64 to get bytes 
      byte[] dec = new sun.misc.BASE64Decoder().decodeBuffer(str); 
      // Decrypt 
      byte[] utf8 = dcipher.doFinal(dec); 
      // Decode using utf-8 
      return new String(utf8, "UTF8"); 
     } catch (...) 
    } 
} 

如果我創建的加密(新密碼)和解密()方法每次調用,那麼我能避免併發問題,我只是不知道是否有大量的開銷在獲得一個新的每個調用的密碼實例。

public String encrypt(String str) { 
     try { 
      // Encode the string into bytes using utf-8 
      byte[] utf8 = str.getBytes("UTF8"); 
      // Encrypt 
      //new cipher instance 
      ecipher = Cipher.getInstance(key.getAlgorithm()); 

      byte[] enc = ecipher.doFinal(utf8); 
      // Encode bytes to base64 to get a string 
      return new sun.misc.BASE64Encoder().encode(enc); 

     } catch (...) 

回答

3

如果一次使用多個線程,則只需要線程安全。由於這個類的每個實例可能只會被一個線程使用,因此不需要擔心它是否是線程安全的。

在一個不相關的說明,有一個硬編碼鹽,nonce或IV是從來沒有一個好主意。

+0

不知道,謝謝。我計劃在嚴格的操作系統權限下將鹽和密碼短語存儲在單獨的文件中,這些權限僅適用於應用程序。 – user646584 2011-03-15 03:28:51

+0

使用恆定的鹽(即使它受到保護)首先會破壞鹽的使用。 – 2011-03-15 03:39:13

+0

你是說鹽應該在運行時隨機獲得?我很困惑,很明顯,一旦數據被加密後,密碼不能改變,那麼需要事先生成並存儲在安全文件中,鹽和密碼短語,密碼短語等? – user646584 2011-03-15 03:53:50

10

標準規則 - 除非的Javadoc國家明確的是,在Java庫一類是線程安全的,你應該假設它不是。

在這種特定情況:

  • 各種類不記錄爲線程安全的。
  • Cipher.getInstance(...)SecretKeyFactory.getInstance(...)方法記錄爲返回新對象;即不引用其他線程可能引用的現有對象。

    UPDATE - 該javadoc這樣說:

    「一個新的SecretKeyFactory對象封裝從返回支持指定算法的Provider SecretKeyFactorySpi實施。」

    此外,顯然source code確認一個新的對象被創建並返回。

總之,這意味着你的DesEncryptor類不是當前線程安全的,但你應該能夠使它線程安全的同步相關的操作(例如encodedecode),並沒有露出兩個密碼對象。如果使這些方法同步可能會造成瓶頸,則爲每個線程創建一個單獨的實例DesEncryptor

+0

對不起,在這樣一個老話題上發帖,但我懷疑SecretKeyFactory.getInstance()每次都返回一個新對象,因爲它的工廠。此外,我現在正在測試它,第一次調用需要2.5s,而每次後續調用都需要0s。 – andrewktmeikle 2013-09-16 14:26:36

+0

1)查看更新。2)第一次調用可能觸發類加載,並可能涉及從操作系統獲取「熵」的初始化。在某些情況下,這可能需要大量的實時數據。 – 2013-09-17 04:04:13

+0

夠公平,作爲一個單身人士tbh沒有什麼意義。爲什麼它只是第一次收集熵?當你調用getInstance時,肯定需要完成它嗎? – andrewktmeikle 2013-09-20 09:24:55

1

Cipher對象不會是線程安全的,因爲它保留了有關加密過程的內部狀態。這也適用於您的DesEncrypter類 - 除非您同步encodedecode方法,否則每個線程都需要使用自己的DesEncrypter實例。

+0

查看我上面的示例...爲什麼不爲每次調用encrypt()/ decrypt()創建一個新的Cipher實例? – user646584 2011-03-15 03:40:16

+2

是的,但在你的例子中,你使用實例變量'ecipher'和'dcipher'來存儲Cipher實例,所以如果兩個線程同時在同一個DesEncrypter實例上調用encrypt(),它們會互相打開除非你同步'encrypt()'調用)。爲了避免這種情況,你可以使'ecipher'和'dcipher'成爲局部變量(在encrypt()函數中)而不是實例變量,這樣每次調用encrypt()都會有自己的值。 – 2011-03-15 03:48:17