加密解密基础问题:字节数组和字符串的相互转换

在加密时,一般加密算法和hash算法,它们操作的都是字节数组,对字节数组按照加密算法进行各种变换,运算,得到的结果也是字节数组。而我们一般是要求对字符串进行加密,所以就涉及到字符串String到 byte[] 的转换,这个很简单。同时在解密时,也涉及到字节数组byte[] 到 String 的转换。另外在对用户的密码进行hash加密之后,最终是要保存在数据库中,所以加密得到 byte[] 也要转换到 String.

1. String 到 byte[] 的转换很简单,因为String类有直接的函数:

    public byte[] getBytes(Charset charset) {
        if (charset == null) throw new NullPointerException();
        return StringCoding.encode(charset, value, 0, value.length);
    }

    /**
     * Encodes this {@code String} into a sequence of bytes using the
     * platform‘s default charset, storing the result into a new byte array.
     *
     * @return  The resultant byte array
     *
     * @since      JDK1.1
     */
    public byte[] getBytes() {
        return StringCoding.encode(value, 0, value.length);
    }

2. 但是,byte[] 到String 的转换却没有那么简单

其原因是,我们不能简单的使用使用String的函数:

     /**
     * Constructs a new {@code String} by decoding the specified array of bytes
     * using the platform‘s default charset.  The length of the new {@code
     * String} is a function of the charset, and hence may not be equal to the
     * length of the byte array.
     *
     * <p> The behavior of this constructor when the given bytes are not valid
     * in the default charset is unspecified.  The {@link
     * java.nio.charset.CharsetDecoder} class should be used when more control
     * over the decoding process is required.*/
    public String(byte bytes[]) {
        this(bytes, 0, bytes.length);
    }

   /**
     * Constructs a new {@code String} by decoding the specified array of
     * bytes using the specified {@linkplain java.nio.charset.Charset charset}.
     * The length of the new {@code String} is a function of the charset, and
     * hence may not be equal to the length of the byte array.
     *
     * <p> This method always replaces malformed-input and unmappable-character
     * sequences with this charset‘s default replacement string.  The {@link
     * java.nio.charset.CharsetDecoder} class should be used when more control
     * over the decoding process is required.*/
    public String(byte bytes[], Charset charset) {
        this(bytes, 0, bytes.length, charset);
    }

也就是不能使用 new String(byte); 也不能使用 new String(byte, charset).

为什么呢?

很简单因为, MD5, SHA-256, SHA-512 等等算法,它们是通过对byte[] 进行各种变换和运算,得到加密之后的byte[],那么这个加密之后的 byte[] 结果显然 就不会符合任何一种的编码方案,比如 utf-8, GBK等,因为加密的过程是任意对byte[]进行运算的。所以你用任何一种编码方案来解码 加密之后的 byte[] 结果,得到的都会是乱码

那么,我们该如何将加密的结果 byte[] 转换到String呢?

首先,我们要问一下,为什么要将加密得到的 byte[] 转换到 String ?

答案是因为一是要对加密的结果进行存储,比如存入数据库中,二是在单向不可逆的hash加密算法对密码加密时,我们需要判断用户登录的密码是否正确,那么就涉及到两个加密之后的byte[] 进行比较,看他们是否一致。两个 byte[] 进行比较,可以一次比较一个单字节,也可以一次比较多个字节。也可以转换成String, 然后比较两个String就行了。因为加密结果要进行存储,所以其实都是选择转换成String来进行比较的。

加密解密时,采用的byte[] 到 String 转换的方法都是将 byte[] 二进制利用16进制的char[]来表示,每一个 byte 是8个bit,每4个bit对应一个16进制字符。所以一个 byte 对应于两个 16进制字符:

public class HexUtil {
    private static final char[] DIGITS = {
            ‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘,
            ‘8‘, ‘9‘, ‘a‘, ‘b‘, ‘c‘, ‘d‘, ‘e‘, ‘f‘
    };

    public static String encodeToString(byte[] bytes) {
        char[] encodedChars = encode(bytes);
        return new String(encodedChars);
    }

    public static char[] encode(byte[] data) {
        int l = data.length;
        char[] out = new char[l << 1];
        // two characters form the hex value.
        for (int i = 0, j = 0; i < l; i++) {
            out[j++] = DIGITS[(0xF0 & data[i]) >>> 4];
            out[j++] = DIGITS[0x0F & data[i]];
        }
        return out;
    }

我们知道16进制表达方式是使用 0-9 abcdef 这16个数字和字母来表示 0-15 这16个数字的。而显然我们在String转化时,可以用字符 ‘0‘ 来表示 数字0, 可以用 ‘1‘ 来表示 1,可以用 ‘f‘ 来表示15.

所以上面我们看到16进制使用 "0-9abcdef‘ 16个字符来表示 0-15 这个16个数字。主要的转换过程是 public static char[] encode(byte[] data)函数:

int l = data.length;  char[] out = new char[l << 1]; 这两句是初始化一个 char[] 数组,其数组的大小是 byte[] 参数大小的两倍,因为每一个byte[] 转换到到2位16进制的char[]。

(0xF0 & data[i]) >>> 4 表示先使用0xF0 & data[i], 去除了低4位上的值(其实这一步是多余的),然后右移4位,得到byte[] 数组中 第 i 个 byte 的 高 4位,然后通过 DIGITS[] 数组,得到高4为对应的字符;

DIGITS[0x0F & data[i]] 表示先使用 0x0F & data[i], 去除了高4位上的值,也就得到了低4为代表的大小,然后通过 DIGITS[] 数组,得到低4为对应的字符;

通过这种方式,就可以讲 byte[] 数组转换成16进制字符表示的 char[]。最后 new String(encodedChars); 得到String类型的结果.

所以最后的String是由:‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘, ‘a‘, ‘b‘, ‘c‘, ‘d‘, ‘e‘, ‘f‘ 这16个字符组成的String, 不含有任何其的字母。比如不会g,h,jklmn.....等等。

3. 反向转换:String 到 byte[]

上面我们实现了 byte[] 到 String 的转换,编码方案使用的是16进制编码。那么如何进行反向解码呢?也就是将16进制编码的String转换成原来的byte[]呢?

    /**
     * Converts the specified Hex-encoded String into a raw byte array.  This is a
     * convenience method that merely delegates to {@link #decode(char[])} using the
     * argument‘s hex.toCharArray() value.
     *
     * @param hex a Hex-encoded String.
     * @return A byte array containing binary data decoded from the supplied String‘s char array.
     */
    public static byte[] decode(String hex) {
        return decode(hex.toCharArray());
    }
    /**
     * Converts an array of characters representing hexidecimal values into an
     * array of bytes of those same values. The returned array will be half the
     * length of the passed array, as it takes two characters to represent any
     * given byte. An exception is thrown if the passed char array has an odd
     * number of elements.
     *
     * @param data An array of characters containing hexidecimal digits
     * @return A byte array containing binary data decoded from
     *         the supplied char array.
     * @throws IllegalArgumentException if an odd number or illegal of characters
     *                                  is supplied
     */
    public static byte[] decode(char[] data) throws IllegalArgumentException {
        int len = data.length;
        if ((len & 0x01) != 0) {
            throw new IllegalArgumentException("Odd number of characters.");
        }
        byte[] out = new byte[len >> 1];
        // two characters form the hex value.
        for (int i = 0, j = 0; j < len; i++) {
            int f = toDigit(data[j], j) << 4;
            j++;
            f = f | toDigit(data[j], j);
            j++;
            out[i] = (byte) (f & 0xFF);
        }
        return out;
    }    protected static int toDigit(char ch, int index) throws IllegalArgumentException {        int digit = Character.digit(ch, 16);        if (digit == -1) {            throw new IllegalArgumentException("Illegal hexadecimal charcter " + ch + " at index " + index);        }        return digit;    }

要将16进制编码的String转换成原来的byte[],第一步是将 String 类型转换到 char[] 数组,也就是将 "10ae4f" 转换成 [‘1‘,‘0‘,‘a‘,‘e‘,‘4‘,‘f‘],然后将没两个相连的 char 转化成一个 byte. 显然 char[] 数组的大小必须是偶数的。

byte[] out = new byte[len >> 1]; byte[] 结果是 char[] 大小的一半大。

toDigit(data[j], j) << 4 表示:toDigit() 将一个字符转换成16进制的int大小,也就是将 ‘0‘ 转换成数字0,将‘f‘ 转换成数字 f, 然后左移4位,成为byte[]的高4位;

f = f | toDigit(data[j], j); 表示先得到字符对应的数字,然后做为低4位,和高4为合并(使用 | 操作符)为一个完整的8位byte.

out[i] = (byte) (f & 0xFF); 只保留8位,将多余高位去掉。

其实就是上面的反向过程而已。

4. 例子

public class EncodeTest {
    public static void main(String[] args){
        String str = "???hello/sasewredfdd>>>. Hello 世界!";
        System.out.println("str.getBytes()=" + str.getBytes());
        System.out.println("Base64=" + Base64.encodeToString(str.getBytes()));

        String hexStr = HexUtil.encodeToString(str.getBytes());    //str.getBytes(Charset.forName("utf-8"));

        System.out.println("hexStr=" + hexStr);
        String orignalStr = new String(str.getBytes());     //new String(str.getBytes(), Charset.forName("utf-8"));
        System.out.println("orignalStr=" + orignalStr);
        String str2 = new String(HexUtil.decode(hexStr));
        System.out.println("str2=" + str2);
        System.out.println(str.equals(str2));

        String sha = new SimpleHash("sha-256", str, "11d23ccf28fc1e8cbab8fea97f101fc1d", 2).toString();
        System.out.println("sha=" + sha);
    }
}

结果:

str.getBytes()=[[email protected]
Base64=Pz8/aGVsbG8vc2FzZXdyZWRmZGQ+Pj4uIEhlbGxvIOS4lueVjO+8gQ==
hexStr=3f3f3f68656c6c6f2f73617365777265646664643e3e3e2e2048656c6c6f20e4b896e7958cefbc81
orignalStr=???hello/sasewredfdd>>>. Hello 世界!
str2=???hello/sasewredfdd>>>. Hello 世界!
true
sha=37a9715fecb5e2f9812d4a02570636e3d5fe476fc67ac34bc824d6a8f835635d

最后的 new SimpleHash("sha-256", str, "11d23ccf28fc1e8cbab8fea97f101fc1d", 2).toString() ,其 .toString() 方法就是使用的 16进制的编码将hash加密之后的 byte[] 转换成 16进制的字符串。

我们看得到的结果:37a9715fecb5e2f9812d4a02570636e3d5fe476fc67ac34bc824d6a8f835635d

全部由‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘, ‘a‘, ‘b‘, ‘c‘, ‘d‘, ‘e‘, ‘f‘ 这16个字符组成。不含其他任何字符。

上面我们也有Base64的编码方案:

Base64.encodeToString(str.getBytes())

它其实是使用 a-z, A-Z, 0-9, /, + 这64个字符来进行编码的,0-63分别对应用前面的64个字符来表示。

其编码结果的特点是:末尾可能有1个或者2个 = :

Pz8/aGVsbG8vc2FzZXdyZWRmZGQ+Pj4uIEhlbGxvIOS4lueVjO+8gQ==

其原因是,Base64编码算法是每次处理byte[]数组中三个连续的byte,那么就有可能 byte[] 数组不是3的整数倍,那么余数就有可能是1,或者2,所以就分别使用 一个 = 和两个 = 来进行填充。

所以:

Base64的编码其特点就是可能末尾有一个或者两个=,可能含有 / 和 + 字符。

16进制编码的特点是全部由‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘, ‘a‘, ‘b‘, ‘c‘, ‘d‘, ‘e‘, ‘f‘ 这16个字符组成,不含其他字母。

加密算法都是对byte[]进行变换和运算。

有 String 转换得到的 byte[] 就一定可以使用原来的编码方案转换成原来的 String,

但是加密的结果 byte[] 却不能用任何字符编码方案得到String, 一般使用16进制编码成String.

时间: 2024-10-03 14:24:41

加密解密基础问题:字节数组和字符串的相互转换的相关文章

密码学——Java 加密解密基础

Java  加密解密基础 密码学是研究编制密码和破译密码的技术科学.研究密码变化的客观规律,应用于编制密码以保守通信秘密的,称为编码学:应用于破译密码以获取通信情报的,称为破译学,总称密码学. 密码学常用术语 明文: 待加密数据. 密文: 明文经过加密后数据. 加密: 将明文转换为密文的过程. 加密算法: 将明文转换为密文的转换算法. 加密密钥: 通过加密算法进行加密操作的密钥. 解密: 将密文转换为铭文的过程. 解密算法: 将密文转换为明文的转换算法. 解密密钥: 通过解密短发进行解密操作的密

Linux之加密解密基础、openssl及CA基础应用

加密解密基础简介 数据在网络中传输过程中要保证三个要点: (1)数据的完整性:防止数据在传输过程中遭到未授权用户的破坏或篡改 (2)数据的机密性:防止文件数据泄漏给未授权用户从而让其利 (3)数据的可用性:保证授权用户能按需访问存取文件数据 因此加密技术是最常用的安全保密手段,利用技术手段把重要的数据变为乱码(加密)传送,到达目的地后再用相同或不同的手段还原(解密).加密技术包括两个元素:算法和密钥.算法是将普通的信息或者可以理解的信息与一串数字(密钥)结合,产生不可理解的密文的步骤,密钥是用来

加密解密基础及私有CA的实现-2015092801

1. 加密解密简述 2.加密算法 3.加密解密步骤 4.使用openssl自建私有CA 加密解密基础: 由于互联网的数据传输基本上都是明文的(如ftp.http.telnet等),而往往有些数据在互联网传输对于传输方来讲,确实涉及隐私或是一些敏感的内容不想让互联网上其他人看到,因此,引入了加密的机制. 最早起的加密,采用移位的方式对数据进行加密,如数据数"abcd",其对应的在互联网上传输的内容是"efgh".后来的加密方式是采用"密码对照本",

加密解密基础及openssl构建私有CA初步

方今社会,互联网的普及给我们的生活带来了极大的便利,但任何事物都有其两面性:窗户打开了,阳光和新鲜的空气进来了,苍蝇也进来了.如何在利用互联网带来便利的同时,又尽量规避其风险,保护自己网络通信中信息的安全及私密,成为了一个热门话题,下面本文将主要探讨两个方面的问题,不当之处还请各位前辈不吝斧正. (一)加密解密基础 首先要搞清楚的一个问题是,我们平时的网络通信存在哪些风险呢?目前互联网上常见的攻击方式有两种:主动攻击和被动攻击.主动攻击包括报文伪装.重复.消息篡改.拒绝服务等:被动攻击主要是监听

加密解密基础

现代网络通信中网络安全是至关重要,安全的最基本的当然就是加密与解密了,今天跟大家分享一下加密与解密的基础. 安全的目标: 保密性:confidentiality 完整性:integrity 可用性:availability 攻击类型: 威胁保密性的攻击:窃听.通信量分析: 威胁完整性的攻击:更改.伪装.重放.否认: 威胁可用性的攻击:拒绝服务(DoS): 解决方案: 技术方面:加密和解密: 传统加密方法:替代加密方法.置换加密方法 现代加密方法:现代块加密方法 服务方面:用于抵御攻击的服务,也即

字符串到字节数组和字节数组到字符串的转换(编码和解码问题)

/* * String(byte[] bytes, String charsetName):通过指定的字符集解码字节数组 * byte[] getBytes(String charsetName):使用指定的字符集合把字符串编码为字节数组 * * 编码:把看得懂的变成看不懂的 * String -- byte[] * * 解码:把看不懂的变成看得懂的 * byte[] -- String * * 举例:谍战片(发电报,接电报) * * 码表:小本子 *         字符    数值 * *

linux加密解密基础、PKI及SSL、创建私有CA

1.加密解密基础:          数据在网络中传输过程中要保证三个要点: (1)数据的完整性:防止数据在传输过程中遭到未授权用户的破坏或篡改.          (2)数据的机密性:防止文件数据泄漏给未授权用户从而让其利用          (3)数据的可用性:保证授权用户能按需访问存取文件数据 2.常见的加密技术: 对称加密  公钥加密  单向加密          (1)对称加密:加密解密使用同一个密钥,将原始数据分割成固定大小的块,逐个进行加密 加密算法:               

加密解密基础问题:字节数组和(16进制)字符串的相互转换(转)

在加密时,一般加密算法和hash算法,它们操作的都是字节数组,对字节数组按照加密算法进行各种变换,运算,得到的结果也是字节数组.而我们一般是要求对字符串进行加密,所以就涉及到字符串String到 byte[] 的转换,这个很简单.同时在解密时,也涉及到字节数组byte[] 到 String 的转换.另外在对用户的密码进行hash加密之后,最终是要保存在数据库中,所以加密得到 byte[] 也要转换到 String. 1. String 到 byte[] 的转换很简单,因为String类有直接的函

字节数组与字符串(字符数组)的转换操作

1.默认编码方式转换: (1)string(char[])转换为byte[] byte[] byteArr = System.Text.Encoding.Default.GetBytes(char[]); byte[] byteArr = System.Text.Encoding.Default.GetBytes(string); byte[] byteArr = System.Text.Encoding.Default.GetBytes(char[], startindex, count);