加密解密基础问题:字节数组和(16进制)字符串的相互转换
在加密時(shí),一般加密算法和hash算法,它們操作的都是字節(jié)數(shù)組,對(duì)字節(jié)數(shù)組按照加密算法進(jìn)行各種變換,運(yùn)算,得到的結(jié)果也是字節(jié)數(shù)組。而我們一般是要求對(duì)字符串進(jìn)行加密,所以就涉及到字符串String到 byte[] 的轉(zhuǎn)換,這個(gè)很簡(jiǎn)單。同時(shí)在解密時(shí),也涉及到字節(jié)數(shù)組byte[] 到 String 的轉(zhuǎn)換。另外在對(duì)用戶的密碼進(jìn)行hash加密之后,最終是要保存在數(shù)據(jù)庫中,所以加密得到 byte[] 也要轉(zhuǎn)換到 String.
1. String 到 byte[] 的轉(zhuǎn)換很簡(jiǎn)單,因?yàn)镾tring類有直接的函數(shù):
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 的轉(zhuǎn)換卻沒有那么簡(jiǎn)單
其原因是,我們不能簡(jiǎn)單的使用使用String的函數(shù):
/*** 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).
為什么呢?
很簡(jiǎn)單因?yàn)?#xff0c; MD5, SHA-256, SHA-512 等等算法,它們是通過對(duì)byte[] 進(jìn)行各種變換和運(yùn)算,得到加密之后的byte[],那么這個(gè)加密之后的 byte[] 結(jié)果顯然 就不會(huì)符合任何一種的編碼方案,比如 utf-8, GBK等,因?yàn)榧用艿倪^程是任意對(duì)byte[]進(jìn)行運(yùn)算的。所以你用任何一種編碼方案來解碼 加密之后的 byte[] 結(jié)果,得到的都會(huì)是亂碼。
那么,我們?cè)撊绾螌⒓用艿慕Y(jié)果 byte[] 轉(zhuǎn)換到String呢?
首先,我們要問一下,為什么要將加密得到的 byte[] 轉(zhuǎn)換到 String ?
答案是因?yàn)橐皇且獙?duì)加密的結(jié)果進(jìn)行存儲(chǔ),比如存入數(shù)據(jù)庫中,二是在單向不可逆的hash加密算法對(duì)密碼加密時(shí),我們需要判斷用戶登錄的密碼是否正確,那么就涉及到兩個(gè)加密之后的byte[] 進(jìn)行比較,看他們是否一致。兩個(gè) byte[] 進(jìn)行比較,可以一次比較一個(gè)單字節(jié),也可以一次比較多個(gè)字節(jié)。也可以轉(zhuǎn)換成String, 然后比較兩個(gè)String就行了。因?yàn)榧用芙Y(jié)果要進(jìn)行存儲(chǔ),所以其實(shí)都是選擇轉(zhuǎn)換成String來進(jìn)行比較的。
加密解密時(shí),采用的byte[] 到 String 轉(zhuǎn)換的方法都是將 byte[] 二進(jìn)制利用16進(jìn)制的char[]來表示,每一個(gè) byte 是8個(gè)bit,每4個(gè)bit對(duì)應(yīng)一個(gè)16進(jìn)制字符。所以一個(gè) byte 對(duì)應(yīng)于兩個(gè) 16進(jìn)制字符:
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進(jìn)制表達(dá)方式是使用 0-9 abcdef 這16個(gè)數(shù)字和字母來表示 0-15 這16個(gè)數(shù)字的。而顯然我們?cè)赟tring轉(zhuǎn)化時(shí),可以用字符 '0' 來表示 數(shù)字0, 可以用 '1' 來表示 1,可以用 'f' 來表示15.
所以上面我們看到16進(jìn)制使用 "0-9abcdef' 16個(gè)字符來表示 0-15 這個(gè)16個(gè)數(shù)字。主要的轉(zhuǎn)換過程是 public static char[] encode(byte[] data)函數(shù):
?int l = data.length;? char[] out = new char[l << 1]; 這兩句是初始化一個(gè) char[] 數(shù)組,其數(shù)組的大小是 byte[] 參數(shù)大小的兩倍,因?yàn)槊恳粋€(gè)byte[] 轉(zhuǎn)換到到2位16進(jìn)制的char[]。
(0xF0 & data[i]) >>> 4 表示先使用0xF0 & data[i], 去除了低4位上的值(其實(shí)這一步是多余的),然后右移4位,得到byte[] 數(shù)組中 第 i 個(gè) byte 的 高 4位,然后通過 DIGITS[] 數(shù)組,得到高4為對(duì)應(yīng)的字符;
DIGITS[0x0F & data[i]] 表示先使用 0x0F & data[i], 去除了高4位上的值,也就得到了低4為代表的大小,然后通過 DIGITS[] 數(shù)組,得到低4為對(duì)應(yīng)的字符;
通過這種方式,就可以講 byte[] 數(shù)組轉(zhuǎn)換成16進(jìn)制字符表示的 char[]。最后 new String(encodedChars); 得到String類型的結(jié)果.
所以最后的String是由:'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' 這16個(gè)字符組成的String, 不含有任何其的字母。比如不會(huì)g,h,jklmn.....等等。
3. 反向轉(zhuǎn)換:String 到 byte[]
上面我們實(shí)現(xiàn)了 byte[] 到 String 的轉(zhuǎn)換,編碼方案使用的是16進(jìn)制編碼。那么如何進(jìn)行反向解碼呢?也就是將16進(jìn)制編碼的String轉(zhuǎn)換成原來的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進(jìn)制編碼的String轉(zhuǎn)換成原來的byte[],第一步是將 String 類型轉(zhuǎn)換到 char[] 數(shù)組,也就是將 "10ae4f" 轉(zhuǎn)換成 ['1','0','a','e','4','f'],然后將沒兩個(gè)相連的 char 轉(zhuǎn)化成一個(gè) byte. 顯然 char[] 數(shù)組的大小必須是偶數(shù)的。
byte[] out = new byte[len >> 1]; byte[] 結(jié)果是 char[] 大小的一半大。
toDigit(data[j], j) << 4 表示:toDigit() 將一個(gè)字符轉(zhuǎn)換成16進(jìn)制的int大小,也就是將 '0' 轉(zhuǎn)換成數(shù)字0,將'f' 轉(zhuǎn)換成數(shù)字 f, 然后左移4位,成為byte[]的高4位;
f = f | toDigit(data[j], j); 表示先得到字符對(duì)應(yīng)的數(shù)字,然后做為低4位,和高4為合并(使用 | 操作符)為一個(gè)完整的8位byte.
out[i] = (byte) (f & 0xFF); 只保留8位,將多余高位去掉。
其實(shí)就是上面的反向過程而已。
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);} }結(jié)果:
str.getBytes()=[B@19e0bfd 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進(jìn)制的編碼將hash加密之后的 byte[] 轉(zhuǎn)換成 16進(jìn)制的字符串。
我們看得到的結(jié)果:37a9715fecb5e2f9812d4a02570636e3d5fe476fc67ac34bc824d6a8f835635d
全部由'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' 這16個(gè)字符組成。不含其他任何字符。
上面我們也有Base64的編碼方案:
Base64.encodeToString(str.getBytes())
它其實(shí)是使用 a-z, A-Z, 0-9, /, + 這64個(gè)字符來進(jìn)行編碼的,0-63分別對(duì)應(yīng)用前面的64個(gè)字符來表示。
其編碼結(jié)果的特點(diǎn)是:末尾可能有1個(gè)或者2個(gè) = :
Pz8/aGVsbG8vc2FzZXdyZWRmZGQ+Pj4uIEhlbGxvIOS4lueVjO+8gQ==
其原因是,Base64編碼算法是每次處理byte[]數(shù)組中三個(gè)連續(xù)的byte,那么就有可能 byte[] 數(shù)組不是3的整數(shù)倍,那么余數(shù)就有可能是1,或者2,所以就分別使用 一個(gè) = 和兩個(gè) = 來進(jìn)行填充。
所以:
Base64的編碼其特點(diǎn)就是可能末尾有一個(gè)或者兩個(gè)=,可能含有 / 和 + 字符。
16進(jìn)制編碼的特點(diǎn)是全部由'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' 這16個(gè)字符組成,不含其他字母。
加密算法都是對(duì)byte[]進(jìn)行變換和運(yùn)算。
有 String 轉(zhuǎn)換得到的 byte[] 就一定可以使用原來的編碼方案轉(zhuǎn)換成原來的 String,
但是加密的結(jié)果 byte[] 卻不能用任何字符編碼方案得到String, 一般使用16進(jìn)制編碼成String,然后進(jìn)行存儲(chǔ)或者比較。
轉(zhuǎn)載于:https://www.cnblogs.com/digdeep/p/4627813.html
總結(jié)
以上是生活随笔為你收集整理的加密解密基础问题:字节数组和(16进制)字符串的相互转换的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 操作系统之文件管理:6、文件的基本操作(
- 下一篇: /var/lib/mlocate/mlo