天天看點

Java實作Base64加密算法

使用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變為對稱加密算法而編碼表就是密匙呢?