使用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变为对称加密算法而编码表就是密匙呢?