Java字符串存储字节数组

Jon*_*Jon 4 java string bytearray

我想存储一个包装在String对象中的字节数组.这是场景

  1. 用户输入密码.
  2. 使用getBytes()String方法获取该密码的字节.
  3. 它们的字节使用java的crypo包加密.
  4. 然后使用构造函数new String(bytes [])将这些字节转换为String
  5. 该String存储或以其他方式传递(未更改)
  6. 获取该String的字节,它们与编码的字节不同.

这是一段代码,描述了我在说什么.

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);
Run Code Online (Sandbox Code Playgroud)

其中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);
Run Code Online (Sandbox Code Playgroud)

但这没有帮助.

有任何想法吗?

Jon*_*han 17

将二进制数据存储在String对象中并不是一个好主意.你最好使用类似Base64编码的东西,它旨在将二进制数据转换为可打印的字符串,并且完全可逆.

事实上,我刚刚为Java找到了一个公共域base64编码器:http://iharder.sourceforge.net/current/java/base64/

  • 除非你绝对没有选择,否则将秘密存储在String对象(密码输入或解密输出)中也不是一个好主意.这是因为没有办法清除字符串 - 一旦它在内存中,字符串就不会被覆盖,直到内存被垃圾收集并且内存分配器决定重新分配该部分内存. (2认同)
  • String类本身的API文档说"String类表示字符串".再往下,它还说"一个字符串代表一个UTF-16格式的字符串......".基本上,它是一个用于存储字符数据的智能对象,您需要一个用于存储二进制数据的哑对象. (2认同)

Mr.*_* 安宇 11

有几个人指出这不是String(byte[])构造函数的正确使用.重要的是要记住,在Java中,a String由字符组成,恰好是16位,而不是8位,就像字节一样.你也忘记了字符编码.请记住,角色通常不是一个字节.

让我们一点一点地分解它:

String s = "test123";
byte[] a = s.getBytes();
Run Code Online (Sandbox Code Playgroud)

此时你的字节数组最有可能包含8个字节,如果你的系统默认字符编码Windows-1252iso-8859-1UTF-8.

byte[] b = env.encrypt(a);
Run Code Online (Sandbox Code Playgroud)

现在b包含一些看似随机的数据,具体取决于您的加密,甚至不保证一定的长度.许多加密引擎填充输入数据,以便输出匹配特定的块大小.

String t = new String(b);
Run Code Online (Sandbox Code Playgroud)

这是随机字节,并要求Java将它们解释为字符数据.这些字符可能显示为乱码,并且某些位序列对于每个编码都不是有效字符.Java尽职尽责并创建一系列16位字符.

byte[] c = t.getBytes();
Run Code Online (Sandbox Code Playgroud)

b根据编码的不同,这可能会也可能不会提供相同的字节数组.您在问题描述中声明您看到c长度为16个字节; 这可能是因为t中的垃圾在默认字符编码中转换不好.

byte[] d = env.decrypt(c);
Run Code Online (Sandbox Code Playgroud)

这不起作用,因为c不是您期望的数据,而是腐败.

解决方案:

  1. 只需将字节数组直接存储在数据库或任何地方.但是你仍然忘记了字符编码问题,在一秒钟内就更多了.
  2. 获取字节数组数据并使用Base64或十六进制数字对其进行编码并存储该字符串:

    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();
    
    Run Code Online (Sandbox Code Playgroud)

字符编码:

用户的密码可能不是ASCII,因此您的系统容易出错,因为您没有指定编码.

相比:

String s = "tést123";
byte[] a = s.getBytes();
byte[] b = env.encrypt(a);
Run Code Online (Sandbox Code Playgroud)

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

字节数组aUTF-8编码值与系统默认值不同(除非您的系统默认值为UTF-8).只要A)你是一致的并且B)你的编码可以代表你的数据的所有允许字符,你使用什么编码并不重要.您可能无法以系统默认编码存储中文文本.如果您的应用程序曾部署在多台计算机上,并且其中一台计算机具有不同的系统默认编码,则在一个系统上加密的密码将在另一台系统上变得乱码.

故事的道德:字符不是字节,字节不是字符.你必须记住你正在处理的是什么,以及如何在它们之间来回转换.