1. Analyze the data

import java.util.Base64;

/ / encryption
final String s = Base64.getEncoder().encodeToString("Aston 123DD11".getBytes(StandardCharsets.UTF_8));
/ / decryption
final byte[] decode = Base64.getDecoder().decode(s.getBytes(StandardCharsets.UTF_8));
Copy the code
  • This analysis is onlyTable 1: The Base 64 Alphabet(including+./), so will skipnewline.linemaxRelevant part

2. Analyze the rules

The encoding process represents 24-bit groups of input bits as output strings of 4 encoded characters. Proceeding from left to right, a 24-bit input group is formed by concatenating 3 8-bit input groups. These 24 bits are then treated as 4 concatenated 6-bit groups, each of which is translated into a single character in the base 64 alphabet.

The encoding process represents a 24-bit input bit as an output string of four encoded characters. From left to right, connect three 8-bit input groups to form a 24-bit input group. The 24 bits are then processed into four concatenated six-bit groups, each of which is translated into a character in the base 64 alphabet.

  • case
            +--first octet--+-second octet--+--third octet--+
            |7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|
            +-----------+---+-------+-------+---+-----------+
            |5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0|
            +--1.index--+--2.index--+--3.index--+--4.index--+
Copy the code

3. Trace source code

3.1 Encryption Process

3.1.1 to createEncoder

public static Encoder getEncoder(a) {
     return Encoder.RFC4648;
}

static final Encoder RFC4648 = new Encoder(false.null, -1.true);

/ * * *@paramIsURL Whether to use "URL and file name security" Base 64 Alphabet * Table 1: The Base 64 Alphabet * Table 2: The "URL and Filename safe" Base 64 Alphabet *@paramNewline & Linemax Multipurpose Internet Mail Extension (MIME)[4] is often used as a Base64 reference for newline or non-alphabetic characters without regard to the consequences. * Multipurpose Internet Mail Extensions (MIME) [4] is often used as a reference for base64 without considering the consequences for line-wrapping or non-alphabet characters. *@paramDoPadding In some cases, the use of padding ("=") in the underlying encoded data is not required or used. In general, when the assumption is that the size of the transmitted data cannot be determined, padding is required to generate the correct decoded data. * Implementations must include the appropriate padding character encoding data at the end, unless otherwise expressly stated by referring to the specification of this document. * In some circumstances, the use of padding ("=") in base-encoded data is not required or used. In the general case, when assumptions about the size of transported data cannot be made, padding is required to yield correct decoded data. * Implementations MUST include appropriate pad characters at the end of encoded data unless the specification referring to this document explicitly states otherwise. */
private Encoder(boolean isURL, byte[] newline, int linemax, boolean doPadding) {
    this.isURL = isURL;
    this.newline = newline;
    this.linemax = linemax;
    this.doPadding = doPadding;
}
Copy the code

3.1.2 Method entry

/ * * *@paramSRC Specifies the byte array of the parameter data@returnBase64 encrypted byte array */
public byte[] encode(byte[] src) {
    // Calculate how long the encrypted data should be
    int len = outLength(src.length);          // dst array size
    byte[] dst = new byte[len];
    // Compute the Base64 value and place it in DST
    int ret = encode0(src, 0, src.length, dst);
    if(ret ! = dst.length)return Arrays.copyOf(dst, ret);
    return dst;
}
Copy the code

3.1.3 Calculate the length of encrypted data

int len = outLength(src.length); // dst array size

/ * * *@paramSrclen Specifies the length of the byte array *@returnThe length of encrypted data */
private final int outLength(int srclen) {
    int len = 0;
    if (doPadding) {
        // Take the if branch
        len = 4 * ((srclen + 2) / 3);
    } else {
        int n = srclen % 3;
        len = 4 * (srclen / 3) + (n == 0 ? 0 : n + 1);
    }
    // The if branch will not go
    if (linemax > 0)                                  // line separators
        len += (len - 1) / linemax * newline.length;
    return len;
}
Copy the code

DoPadding must be true in this environment

The length after encryption can be obtained from 2. Analysis rules:

  • (srclen + 2): There are 0 to 2 digits at the end=Fill it. Let’s fill two of them in as many cases as possible to make sure we divide by three by one
  • ((srclen + 2) / 3): A group of three arrays
    • Calculate how many images there are2. Analyze the rulesIn the+--first octet--+-second octet--+--third octet--+
  • 4 * ((srclen + 2) / 3): Calculates how many indexes there are. This is the encrypted length
    • like2. Analyze the rules, a group has four indexes+--1.index--+--2.index--+--3.index--+--4.index--+

Here’s an example:

  • Length 0 -> length 0
  • Length 1 -> length 4
  • Length 2 -> length 4
  • Length 3 -> length 4
  • Length 4 -> length 8
  • Length 5 -> length 8
  • Length 6 -> length 8
  • Length 7 -> length 12

3.1.4 Calculating the Base64 value

int ret = encode0(src, 0, src.length, dst);

private int encode0(byte[] src, int off, int end, byte[] dst) {
    // Select toBase64 and use 'Table 1: The Base64 Alphabet' (including '+', '/')
    char[] base64 = isURL ? toBase64URL : toBase64;
    int sp = off;
    // Select * from '3' where '3 '=' 3'
    // If the offset is not zero, recalibrate the length
    int slen = (end - off) / 3 * 3;
    int sl = off + slen;
    // The if branch will not go
    if (linemax > 0 && slen  > linemax / 4 * 3)
        slen = linemax / 4 * 3;
    int dp = 0;
    while (sp < sl) {
        int sl0 = Math.min(sp + slen, sl);
        // The for loop evaluates, takes three values at a time, and concatenates them as described in [2. Parsing rule]
        // Move the first value 16 bits to the left, the second value 8 bits to the left, and the third value remains in place. The 24-bit data is stored in bits
        for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) {
            int bits = (src[sp0++] & 0xff) < <16 |
                       (src[sp0++] & 0xff) < <8 |
                       (src[sp0++] & 0xff);
            //' &0x3f ': '0011_1111',' and 'it zeros all but the lower six bits
            // Assign the first index: convert the high 6(24-18) bits to a byte assignment
            // Assign to the second index: convert the lower 6 bits of the upper 12(24-12) bits to a byte assignment
            // Assign the third index: convert the lower 6 bits of the upper 18(24-6) bits to a byte assignment
            // Assign the fourth index: convert the lower 6 bits of bits to a byte assignment
            // select base64String from base64String
            dst[dp0++] = (byte)base64[(bits >>> 18) & 0x3f];
            dst[dp0++] = (byte)base64[(bits >>> 12) & 0x3f];
            dst[dp0++] = (byte)base64[(bits >>> 6)  & 0x3f];
            dst[dp0++] = (byte)base64[bits & 0x3f];
        }
        int dlen = (sl0 - sp) / 3 * 4;
        dp += dlen;
        sp = sl0;
        if (dlen == linemax && sp < end) {
            for (byteb : newline){ dst[dp++] = b; }}}// If the byte is not divided by a whole 3, then the loop will process the remaining byte
    if (sp < end) {               // 1 or 2 leftover bytes
        // Get the first byte of the remaining bits
        int b0 = src[sp++] & 0xff;
        //[1.index] use the first six bits
        dst[dp++] = (byte)base64[b0 >> 2];
        // Check if there is only one tyte at the end, if, else
        if (sp == end) {
            //[2.index] use the following two bits to spell four zeros
            // Add '=' to the last two bytes in base64
            // There are two bits: 00, 01, 11
            // If A base64 string ends with '==', the first character can only be 'A', 'g', 'w'.
            dst[dp++] = (byte)base64[(b0 << 4) & 0x3f];
            if (doPadding) {
                dst[dp++] = '=';
                dst[dp++] = '='; }}else {
            // Get the last byte of the remaining bits
            int b1 = src[sp++] & 0xff;
            //[2.index] add 4 bits after last byte with 2 bits after first byte
            dst[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)];
            //[3.index] concatenates two zeros with '4 bits after last byte'
            // Add '=' to the last byte in base64
            // If a base64 string ends in '=', the preceding character can also be traced, but it is too many to be listed
            dst[dp++] = (byte)base64[(b1 << 2) & 0x3f];
            if (doPadding) {
                dst[dp++] = '='; }}}return dp;
}
Copy the code

3.2 Decryption Process

3.2.1 createDecoder

public static Decoder getDecoder(a) {
     return Decoder.RFC4648;
}

static final Decoder RFC4648 = new Decoder(false.false);

private Decoder(boolean isURL, boolean isMIME) {
    this.isURL = isURL;
    this.isMIME = isMIME;
}
Copy the code

3.2.2 Method entry

/ * * *@paramSRC Specifies the byte array * of the Base64 data@returnA decrypted byte array */
public byte[] decode(byte[] src) {
    // Calculate how long the decrypted data should be
    byte[] dst = new byte[outLength(src, 0, src.length)];
    // Decrypt Base64 and place it in DST
    int ret = decode0(src, 0, src.length, dst);
    if(ret ! = dst.length) { dst = Arrays.copyOf(dst, ret); }return dst;
}
Copy the code

3.2.3 Calculate how long the decrypted data should be

    / * * *@paramSRC Specifies the byte array * of the Base64 data@paramSp offset *@paramSl The length of base64 is *@returnA decrypted byte array */
    private int outLength(byte[] src, int sp, int sl) {
    / / select fromBase64
    int[] base64 = isURL ? fromBase64URL : fromBase64;
    int paddings = 0;
    int len = sl - sp;
    // The length is 0
    if (len == 0)
        return 0;
    'Table 1: The Base 64 Alphabet' contains at least one set of 3 bytes
    if (len < 2) {
        if (isMIME && base64[0] = = -1)
            return 0;
        throw new IllegalArgumentException(
            "Input byte[] should at least have 2 bytes for base64 bytes");
    }
    if (isMIME) {
        // scan all bytes to fill out all non-alphabet. a performance
        // trade-off of pre-scan or Arrays.copyOf
        int n = 0;
        while (sp < sl) {
            int b = src[sp++] & 0xff;
            if (b == '=') {
                len -= (sl - sp + 1);
                break;
            }
            if ((b = base64[b]) == -1)
                n++;
        }
        len -= n;
    } else {
        // Not a MIME rule, so else branch
        // Determine and calculate whether the base64 string has a '=' padding at the end
        if (src[sl - 1] = ='=') {
            paddings++;
            if (src[sl - 2] = ='=') paddings++; }}According to [2. Analysis rule], the length of encrypted data must be a multiple of 4
    // I think: judge here (len & 0x3)! = 0, to be compatible with and clean up possible dirty data
    // Or MIME rules
    if (paddings == 0 && (len & 0x3) !=  0)
        paddings = 4 - (len & 0x3);
    return 3 * ((len + 3) / 4) - paddings;
}

/** * Lookup table for decoding unicode characters drawn from the * "Base64 Alphabet" (as specified in Table 1 of RFC 2045) into * their 6-bit positive integer equivalents. Characters that * are not in the Base64 alphabet but fall within The bounds of * the array are encoded to -1. * * a lookup table that decodes unicode characters taken from the "Base64 alphabet" (as described in table 1 of rfc2045) into equivalent 6-bit positive integers. * Characters that are not in the Base64 alphabet but are in the range of the array are encoded as -1. * /
private static final int[] fromBase64 = new int[256];
static {
    Arrays.fill(fromBase64, -1);
    for (int i = 0; i < Encoder.toBase64.length; i++)
        fromBase64[Encoder.toBase64[i]] = i;
    fromBase64['='] = -2;
}
Copy the code

3.2.4 decryption Base64

private int decode0(byte[] src, int sp, int sl, byte[] dst) {
    / / select fromBase64
    int[] base64 = isURL ? fromBase64URL : fromBase64;
    int dp = 0;
    int bits = 0;
    // Prepare the offset of the initial bit operation, which is also the condition of the loop assignment
    int shiftto = 18;       // pos of first byte of 4-byte atom
    while (sp < sl) {
        int b = src[sp++] & 0xff;
        // End check
        if ((b = base64[b]) < 0) {
            // The '-' position is assigned to -2, which is used here as a judgment
            if (b == -2) {         // padding byte '='
                // = shiftto==18 unnecessary padding
                // x= shiftto==12 a dangling single x
                // x to be handled together with non-padding case
                // xx= shiftto==6&&sp==sl missing last =
                // xx=y shiftto==6 last is not =
                // Determine if the end does not conform to the specification according to the rule
                if (shiftto == 6&& (sp == sl || src[sp++] ! ='=') ||
                    shiftto == 18) {
                    throw new IllegalArgumentException(
                        "Input byte array has wrong 4-byte ending unit");
                }
                // Then we jump out of the while and do the final processing
                break;
            }
            if (isMIME)    // skip if for rfc2045
                continue;
            else
                throw new IllegalArgumentException(
                    "Illegal base64 character " +
                    Integer.toString(src[sp - 1].16));
        }
        // Set b to the left by an offset of -6 to the next index
        // When shiftto<0 is executed exactly 4 times, it restores an encrypted set of bits
        bits |= (b << shiftto);
        shiftto -= 6;
        if (shiftto < 0) {
            // Indicates that the execution retrieves a complete set of bits
            //first octet sets the first 8-bit complement to bits
            dst[dp++] = (byte)(bits >> 16);
            dst[dp++] = (byte)(bits >>  8);
            dst[dp++] = (byte)(bits);
            // The group of data is processed and reassigned to the initial state
            shiftto = 18;
            bits = 0; }}// At the end of the text
    // reached end of byte array or hit padding '=' characters.
    if (shiftto == 6) {
        dst[dp++] = (byte)(bits >> 16);
    } else if (shiftto == 0) {
        dst[dp++] = (byte)(bits >> 16);
        dst[dp++] = (byte)(bits >>  8);
    } else if (shiftto == 12) {
        // dangling single "x", incorrectly encoded.
        throw new IllegalArgumentException(
            "Last unit does not have enough valid bits");
    }
    // If it is not MIME, the rest will not be done.
    // anything left is invalid, if is not MIME.
    // if MIME, ignore all non-base64 character
    while (sp < sl) {
        if (isMIME && base64[src[sp++]] < 0)
            continue;
        throw new IllegalArgumentException(
            "Input byte array has incorrect ending byte at " + sp);
    }
    return dp;
}
Copy the code