使用Java實作了Base64算法。和jdk自帶的Base64實作比較過,加密後的字元串完全相同,解密後也沒錯誤。效率沒測過,應該不怎麼樣。通過實作這個,明白了Base64的原理,是做大的收獲。
public class Base {
private static final char[] map = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
};
/**
* 編碼
* @param plaintextBytes 明文的比特數組
* @return 密文的比特數組
*/
public byte[] encode(byte[] plaintextBytes) {
StringBuilder builder = getBinaryStr(plaintextBytes);
String[] strings = getBinaryStrArray(builder);
return getByteArrayByMap(strings);
}
/**
* 将明文的比特數組轉換為二進制數的字元串
* @param plaintextBytes 明文的比特數組
* @return 二進制字元串
*/
private StringBuilder getBinaryStr(byte[] plaintextBytes) {
StringBuilder builder = new StringBuilder();
for (byte b : plaintextBytes) {
String s = Integer.toUnsignedString(b, 2);
if (b < 0) {
s = s.substring(24, 32);
} else {
int num = 8 - s.length();
s = "0".repeat(num) + s;
}
builder.append(s);
}
return builder;
}
/**
* 将二進制書的字元串按6個位元組一組進行分組
* @param builder 二進制書的字元串
* @return 字元串數組
*/
private String[] getBinaryStrArray(StringBuilder builder) {
int length = builder.length();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i != 0 && i % 6 == 0) {
sb.append(" ");
}
sb.append(builder.charAt(i));
}
String s = sb.toString();
return s.split(" ");
}
/**
* 将字元串數組中的元素變為整數,對照映射表,轉為對應字元的比特表示
* @param strings 二進制的字元串數組
* @return 比特數組
*/
private byte[] getByteArrayByMap(String[] strings) {
List<Byte> list = new ArrayList<>();
int len = strings.length;
for (int i = 0; i < len; i++) {
String s = strings[i];
String string;
if (i == len - 1) {
string = s + "0".repeat(6 - s.length());
} else {
string = s;
}
char c = map[Integer.parseInt(string, 2)];
list.add((byte) c);
if (i == len - 1 && !s.equals(string)) {
if (s.length() == 2) {
list.add((byte) '=');
list.add((byte) '=');
}
if (s.length() == 4) {
list.add((byte) '=');
}
}
}
int size = list.size();
byte[] bytes = new byte[size];
for (int i = 0; i < size; i++) {
bytes[i] = list.get(i);
}
return bytes;
}
/**
* 編碼。<br>
* 輸入字元串,輸出字元串<br>
* 使用UTF8進行編碼與解碼
* @param plaintext 明文
* @return 密文
*/
public String encodeToString(String plaintext) {
Charset utf8 = StandardCharsets.UTF_8;
byte[] plaintextBytes = plaintext.getBytes(utf8);
byte[] encode = encode(plaintextBytes);
return new String(encode, utf8);
}
// --------------------------------------解碼-----------------------------------------------------------
/**
* 解碼
* @param ciphertextBytes 密文的比特數組
* @return 明文的比特數組
*/
public byte[] decode(byte[] ciphertextBytes) {
StringBuilder builder = getByteStringWithoutEqual(ciphertextBytes);
StringBuilder sb = new StringBuilder();
int length = builder.length();
for (int i = 0; i < length; i++) {
char c = builder.charAt(i);
if (i != 0 && i % 8 == 0) {
sb.append(" ");
}
sb.append(c);
}
String[] strings = sb.toString().split(" ");
List<Byte> list = new ArrayList<>();
for (String s : strings) {
list.add((byte) Integer.parseInt(s, 2));
}
int size = list.size();
byte[] dest = new byte[size];
for (int i = 0; i < size; i++) {
dest[i] = list.get(i);
}
return dest;
}
private StringBuilder getByteStringWithoutEqual(byte[] ciphertextBytes) {
int length = ciphertextBytes.length;
if (length < 4) {
throw new RuntimeException("密文比特數組錯誤!");
}
int num = 0;
if (ciphertextBytes[length - 1] == 61) num++;
if (ciphertextBytes[length - 2] == 61) num++;
int index = length - 1 - num;
byte ciphertextByte = ciphertextBytes[index];
ciphertextBytes[index] = (byte) map[getIndexByByte(ciphertextByte) >> (num * 2)];
byte[] bytes = Arrays.copyOf(ciphertextBytes, length - num);
StringBuilder builder = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
byte b = bytes[i];
int indexByByte = getIndexByByte(b);
String s = Integer.toUnsignedString(indexByByte, 2);
if (i < bytes.length - 1) {
s = "0".repeat(6 - s.length()) + s;
} else {
s = "0".repeat(6 - num * 2 - s.length()) + s;
}
builder.append(s);
}
return builder;
}
private int getIndexByByte(byte b) {
if (b >= 65 && b <= 90) {
return (b - 65);
}
if (b >= 97 && b <= 122) {
return (b - 97) + 26;
}
if (b >= 48 && b <= 57) {
return (b - 48) + (26 + 26);
}
if (b == 43) return 62;
if (b == 47) return 63;
throw new RuntimeException("密文中含有Base64編碼後不應該存在的字元:" + b);
}
/**
* 解碼。<br>
* 輸入字元串,輸出字元串<br>
* 使用UTF8進行編碼與解碼
* @param ciphertext 密文
* @return 明文
*/
public String decodeToString(String ciphertext) {
Charset utf8 = StandardCharsets.UTF_8;
byte[] bytes = ciphertext.getBytes(utf8);
byte[] decode = decode(bytes);
return new String(decode, utf8);
}
}
在編寫的過程中出過錯,有時候加密去掉最後的=的最後一位與标準實作不一樣。找了很久才發現,在标準實作中最後那個二進制數是在末尾補0,我是在前面補0。我覺得前面補0更好,畢竟不改變最後一位數的大小,不知道是否有問題?
還有就是,如果自定義編碼表中字元的順序,是否可以把Base64變為對稱加密算法而編碼表就是密匙呢?