2009-08-18 52 views
3

我想存儲一個包裝在String對象中的字節數組。這裏是場景存儲字節數組的Java字符串

  1. 用戶輸入密碼。
  2. 該密碼的字節是使用getBytes()String方法獲取的。
  3. 使用java的crypo軟件包對它們的字節進行了加密。然後
  4. 這些字節被使用構造新的字符串(字節[])轉換成字符串
  5. 該字符串被存儲或以其它方式圍繞(不變)通過獲得
  6. 該字符串的字節和它們不同於編碼的字節。

下面是描述我在說什麼的一段代碼。

String s = "test123"; 
byte[] a = s.getBytes(); 
byte[] b = env.encrypt(a); 
String t = new String(b); 
byte[] c = t.getBytes(); 
byte[] d = env.decrypt(c); 

凡env.encrypt()和env.decrypt()做加密和解密。我遇到的問題是,b數組的長度爲8,c數組的長度爲16,我認爲它們是平等的。這裏發生了什麼?我試圖修改代碼如下

String s = "test123"; 
Charset charset = Charset.getDefaultCharset(); 
byte[] a = s.getBytes(charset); 
byte[] b = env.encrypt(a); 
String t = new String(b, charset); 
byte[] c = t.getBytes(charset); 
byte[] d = env.decrypt(c); 

但這並沒有幫助。

任何想法?

+0

代碼示例中的'env'是什麼? – 2009-08-18 19:25:28

回答

16

將二進制數據存儲在String對象中不是一個好主意。你最好使用諸如Base64編碼之類的東西,它旨在將二進制數據轉換爲可打印的字符串,並且是完全可逆的。

其實,我剛剛發現一個Java的公共領域的base64編碼:http://iharder.sourceforge.net/current/java/base64/

+0

+1取得密碼,加密它,轉換爲base64字符串(建議使用Apache Commons Codec的最後一位)。 – skaffman 2009-08-18 19:26:53

+2

除非你完全沒有選擇,否則在String對象中存儲祕密(密碼輸入或解密輸出)也不是一個好主意。這是因爲沒有辦法清除一個字符串 - 一旦它存在內存中,一個字符串不會被覆蓋,直到內存被垃圾回收並且內存分配器決定重新分配該內存段。 – atk 2009-08-18 19:49:19

+0

小心解釋爲什麼將二進制數據存儲在字符串obj中是個壞主意?我並不是說我不同意,但通常證明你的主張是個好主意。 – 2012-05-18 00:22:09

0

我沒有給你一個明確的答案,但如果我是這方面的工作,我會打印出字符串或字節在每一步,並比較他們看看發生了什麼。此外,b擁有env.encrypt的返回值,但c是.getBytes的返回值,所以在這種情況下,您可以將蘋果與桔子進行比較。

3

這有點濫用了String(byte [])構造函數和相關的方法。

這將使用某些編碼,並與其他人一起失敗。據推測,您的平臺的默認編碼是其中一個失敗的編碼。

您應該使用類似Commons Code c的東西來將這些字節轉換爲十六進制或base64。

另外你爲什麼要加密密碼,而不是用鹽對它們進行散列呢?

4

在這兩種情況下,您都使用操作系統默認的非Unicode字符集(這取決於區域設置)。如果您將字符串從一個系統傳遞到另一個系統,它們可能會有不同的區域設置,因此會有不同的默認字符集。你需要使用一個明確的字符集來做你想做的事情;例如ISO-8859-1。

更好的是,不要做轉換,直接傳遞byte[]數組。

2

這不會正常工作。將一個字節存儲爲一個字符串只適用於ascii集(以及其他一些)。如果您需要將加密結果存儲爲字符串,那麼將字節轉換爲十六進制然後將其放入字符串中呢?這將工作。

我建議你只保留密碼字節。沒有真正的理由將它存儲爲字符串(除非你想看看什麼人的密碼)。

11

有幾個人指出,這是不是一個正確使用String(byte[])構造。重要的是要記住,在Java中,String由字符組成,字符恰好是16位,而不是8位,如字節所示。你也忘記了字符編碼。請記住,一個字符通常不是一個字節。

讓我們有點把它分解一下:

String s = "test123"; 
byte[] a = s.getBytes(); 

此時你的字節數組最有可能包含8個字節,如果你的系統的默認字符編碼是Windows-1252iso-8859-1UTF-8

byte[] b = env.encrypt(a); 

現在b包含取決於你加密了一些看似隨機的數據,甚至不保證是一定的長度。許多加密引擎填充輸入數據,以便輸出與特定的塊大小匹配。

String t = new String(b); 

這是帶您的隨機字節並要求Java將它們解釋爲字符數據。這些字符可能顯示爲亂碼,並且某些位序列對於每種編碼都不是有效字符。 Java盡職盡責並創建一系列16位字符。

byte[] c = t.getBytes(); 

這可以或可以不給你相同的字節數組作爲b,這取決於編碼。您在問題描述中聲明,您看到c長度爲16個字節;這可能是因爲t中的垃圾在默認字符編碼中不能很好地轉換。

byte[] d = env.decrypt(c); 

這不起作用,因爲c不是您期望它的數據,而是已損壞。

解決方案:

  1. 只是字節數組直接存儲在數據庫中或其它地方。然而,你仍然忘記了字符編碼問題,更多的在一秒鐘內。
  2. 採取字節數組數據,並使用基數64或作爲十六進制數字編碼,並存儲該字符串:

    byte[] cypherBytes = env.encrypt(getBytes(plainText)); 
    StringBuffer cypherText = new StringBuffer(cypherBytes.length * 2); 
    for (byte b : cypherBytes) { 
        String hex = String.format("%02X", b); //$NON-NLS-1$ 
        cypherText.append(hex); 
    } 
    return cypherText.toString(); 
    

文字編碼:

用戶的口令可以不是ASCII,因此你的系統容易出問題,因爲你沒有指定編碼。

比較:

String s = "tést123"; 
byte[] a = s.getBytes(); 
byte[] b = env.encrypt(a); 

String s = "tést123"; 
byte[] a = s.getBytes("UTF-8"); 
byte[] b = env.encrypt(a); 

的字節數組a不會有與UTF-8編碼相同的值與系統默認的(除非你的系統默認爲UTF-8)。只要A)你是一致的,B)你的編碼可以代表你的數據的所有允許字符。您可能無法將中文文本存儲在系統默認編碼中。如果您的應用程序曾部署在多臺計算機上,並且其中一個計算機具有不同的系統默認編碼,則在一個系統上加密的密碼在另一個系統上會變得亂碼。

道德故事: 字符不是字節,字節也不是字符。你必須記住你正在處理的是什麼以及如何在它們之間來回轉換。

+0

感謝您提供豐富的答案。我目前被第三方糟糕的類型選擇所阻止;我需要通過他們的系統傳遞一個字符串,我將在稍後回到相同的JVM中,並且我需要使用字節。加密的大小非常緊湊,我希望避免Base64編碼。我希望有一個字符集說「每一位模式是有效的」。 – 2014-09-05 20:51:06

+0

@CoryKendall你在混合概念。許多字符編碼都有「每一位模式都是有效的」。但在Java字符串中只有UTF-16。 – 2014-09-05 21:07:36

+0

啊,我看到了,所以我不能將任何位模式轉換爲ISO-8859-1字符串,並返回字節而不會看到變化?哪幾個會回答這個問題? – 2014-09-05 21:09:13

1

實現一個StringWrapper類,其構造函數接受一個String arg並將其轉換爲byte []。使用「ISO-8859-1」編碼來確保每個字符只會是8位而不是16位。然後,您顯然可以使用編碼/解碼方法來操縱這些字節。