Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
npci-cl-custom-base64 CustomBase64.java
15 kB · 326 lines
Java
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327package org.npci.upi.security.pinactivitycomponent;
import java.io.UnsupportedEncodingException;import java.util.Arrays;
/** * Clean, compilable reconstruction of the obfuscated {@code OooOO0O} class found in * {@code cl.jar} (JADX could not decompile the encoder's {@code encodeBytes} body). * * Behavior was reverse-engineered from the original {@code .class} files and verified * bit-for-bit against the original jar (which itself fails the JVM verifier and can only * be run with {@code -noverify}). This implementation passes verification and produces * byte-identical output to the original for every input / flag combination tested. * * Options bitmask (the original uses a non-standard flag layout): * 1. {@link #NO_PADDING} (1) - drop the {@code '='} padding chars * 2. {@link #NO_NEWLINES} (2) - do not insert line breaks * 3. {@link #CRLF} (4) - use {@code \r\n} instead of {@code \n} (only with newlines) * 4. {@link #URL_SAFE} (8) - use {@code -} / {@code _} instead of {@code +} / {@code /} */public abstract class CustomBase64 {
public static final int NO_PADDING = 1; public static final int NO_NEWLINES = 2; public static final int CRLF = 4; public static final int URL_SAFE = 8;
/** Mirror of the original static flag (always on); guards a post-encode length assertion. */ public static final boolean ASSERTIONS_ENABLED = true;
/* ------------------------------------------------------------------ */ /* Shared state for the streaming encoder / decoder. */ /* ------------------------------------------------------------------ */ public static abstract class Base64State { /** Output buffer. */ public byte[] buffer; /** Number of valid bytes written into {@link #buffer}. */ public int position; }
/* ------------------------------------------------------------------ */ /* Encoder */ /* ------------------------------------------------------------------ */ public static class Base64Encoder extends Base64State { public static final byte[] STANDARD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes(); public static final byte[] URL_SAFE_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".getBytes();
public final byte[] alphabet; /** Up to two input bytes carried across a non-flushing call. */ public final byte[] pendingBytes = new byte[2]; /** Number of bytes currently held in {@link #pendingBytes} (0..2). */ public int pendingCount; /** Groups emitted on the current line; resets to 0 after each line break. */ public int lineLength; public final boolean doPadding; public final boolean doNewline; public final boolean useCrlf;
public Base64Encoder(int flags, byte[] buffer) { this.buffer = buffer; this.doPadding = (flags & NO_PADDING) == 0; this.doNewline = (flags & NO_NEWLINES) == 0; this.useCrlf = (flags & CRLF) != 0; this.alphabet = (flags & URL_SAFE) == 0 ? STANDARD_ALPHABET : URL_SAFE_ALPHABET; this.lineLength = this.doNewline ? 19 : -1; }
/** Emits a line separator when the current line is full (19 groups = 76 chars). */ private void endGroup() { if (!doNewline) return; if (--lineLength == 0) { separator(); lineLength = 19; } }
private void separator() { if (useCrlf) buffer[position++] = '\r'; buffer[position++] = '\n'; }
/** Emits one full 3-byte -> 4-char group from a 24-bit accumulator. */ private void emitGroup(int acc) { buffer[position ] = alphabet[(acc >> 18) & 63]; buffer[position + 1] = alphabet[(acc >> 12) & 63]; buffer[position + 2] = alphabet[(acc >> 6) & 63]; buffer[position + 3] = alphabet[acc & 63]; position += 4; endGroup(); }
public boolean encodeBytes(byte[] src, int off, int len, boolean flush) { // NOTE: like the decoder, the original resets the output write index to 0 on every // call (it carries only pending bytes + lineLength, not the write position). The // public API is unaffected (always one flush=true call on a fresh encoder). position = 0; int end = off + len; int i = off;
// Complete a group from bytes pending after the previous (non-flushing) call. if (pendingCount == 2) { if (i < end) { int acc = ((pendingBytes[0] & 0xff) << 16) | ((pendingBytes[1] & 0xff) << 8) | (src[i++] & 0xff); emitGroup(acc); pendingCount = 0; } } else if (pendingCount == 1) { if (i + 1 < end) { int acc = ((pendingBytes[0] & 0xff) << 16) | ((src[i++] & 0xff) << 8) | (src[i++] & 0xff); emitGroup(acc); pendingCount = 0; } }
// Main loop: consume whole 3-byte groups. while (end - i >= 3) { int acc = ((src[i++] & 0xff) << 16) | ((src[i++] & 0xff) << 8) | (src[i++] & 0xff); emitGroup(acc); }
int rem = end - i; if (!flush) { for (int k = 0; k < rem; k++) pendingBytes[pendingCount + k] = src[i + k]; pendingCount += rem; return true; }
// Flush: emit the final partial group. `left` is the number of leftover bytes // (pending bytes carried in + new bytes that did not form a full group). int left = pendingCount + rem; if (pendingCount == 1 && rem == 1) { // Original quirk: with exactly one pending byte and one new byte it emits them // REVERSED ([new, pending]). Reproduced for byte-identical behaviour. The public // one-shot API never reaches this (it never carries pending bytes). int b0 = src[i] & 0xff, b1 = pendingBytes[0] & 0xff; buffer[position++] = alphabet[(b0 >> 2) & 63]; buffer[position++] = alphabet[((b0 << 4) | (b1 >> 4)) & 63]; buffer[position++] = alphabet[(b1 << 2) & 63]; if (doPadding) buffer[position++] = '='; } else if (left == 1) { int b0 = (pendingCount == 1 ? pendingBytes[0] : src[i]) & 0xff; buffer[position++] = alphabet[(b0 >> 2) & 63]; buffer[position++] = alphabet[(b0 << 4) & 63]; if (doPadding) { buffer[position++] = '='; buffer[position++] = '='; } } else if (left == 2) { int b0, b1; if (pendingCount == 2) { b0 = pendingBytes[0] & 0xff; b1 = pendingBytes[1] & 0xff; } else { b0 = src[i] & 0xff; b1 = src[i + 1] & 0xff; } buffer[position++] = alphabet[(b0 >> 2) & 63]; buffer[position++] = alphabet[((b0 << 4) | (b1 >> 4)) & 63]; buffer[position++] = alphabet[(b1 << 2) & 63]; if (doPadding) buffer[position++] = '='; } pendingCount = 0;
// Trailing line separator (only if the current line has any output). if (doNewline && position > 0 && (left != 0 || lineLength != 19)) { separator(); } return true; } }
/* ------------------------------------------------------------------ */ /* Decoder */ /* ------------------------------------------------------------------ */ public static class Base64Decoder extends Base64State { private static final byte[] STANDARD_ALPHABET_BYTES = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes(); private static final byte[] URL_SAFE_ALPHABET_BYTES = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".getBytes();
public static final int[] STANDARD_DECODE_TABLE = buildTable(STANDARD_ALPHABET_BYTES); public static final int[] URL_SAFE_DECODE_TABLE = buildTable(URL_SAFE_ALPHABET_BYTES);
private static int[] buildTable(byte[] alphabet) { int[] t = new int[256]; Arrays.fill(t, -1); // -1 -> ignore this input char (whitespace, etc.) for (int v = 0; v < 64; v++) t[alphabet[v] & 0xff] = v; t['='] = -2; // -2 -> padding terminator return t; }
public int state; public int accumulator; public final int[] decodeTable;
public Base64Decoder(int flags, byte[] buffer) { this.buffer = buffer; this.decodeTable = (flags & URL_SAFE) == 0 ? STANDARD_DECODE_TABLE : URL_SAFE_DECODE_TABLE; this.state = 0; this.accumulator = 0; }
public boolean decodeBytes(byte[] src, int off, int len, boolean flush) { int st = state; if (st == 6) return false; int end = off + len; int acc = accumulator; byte[] out = buffer; int[] tab = decodeTable; // NOTE: the original resets the output write index to 0 on every call (it carries // only `state`/`accumulator`, not `position`). Multi-call streaming therefore loses // earlier bytes; the public API never uses it that way (always one flush=true call). int pos = 0;
for (int idx = off; idx < end; idx++) { int v = tab[src[idx] & 0xff]; switch (st) { case 0: if (v >= 0) { acc = v; st = 1; } else if (v != -1) { state = 6; return false; } break; case 1: if (v >= 0) { acc = (acc << 6) | v; st = 2; } else if (v != -1) { state = 6; return false; } break; case 2: if (v >= 0) { acc = (acc << 6) | v; st = 3; } else if (v == -2) { out[pos++] = (byte) (acc >> 4); st = 4; } else if (v != -1) { state = 6; return false; } break; case 3: if (v >= 0) { acc = (acc << 6) | v; out[pos + 2] = (byte) acc; out[pos + 1] = (byte) (acc >> 8); out[pos ] = (byte) (acc >> 16); pos += 3; st = 0; } else if (v == -2) { out[pos + 1] = (byte) (acc >> 2); out[pos ] = (byte) (acc >> 10); pos += 2; st = 5; } else if (v != -1) { state = 6; return false; } break; case 4: if (v == -2) { st = 5; } else if (v != -1) { state = 6; return false; } break; case 5: if (v != -1) { state = 6; return false; } break; default: state = 6; return false; } }
if (!flush) { state = st; accumulator = acc; position = pos; return true; }
if (st == 1) { state = 6; return false; } if (st == 2) { out[pos++] = (byte) (acc >> 4); } else if (st == 3) { out[pos++] = (byte) (acc >> 10); out[pos++] = (byte) (acc >> 2); } else if (st == 4) { state = 6; return false; }
state = st; position = pos; return true; } }
/* ------------------------------------------------------------------ */ /* Public API (mirrors the original's static entry points). */ /* ------------------------------------------------------------------ */
/** Replaces {@code +} with {@code -} and {@code /} with {@code _}. */ public static String toUrlSafe(String s) { return s.replace("+", "-").replace("/", "_"); }
public static byte[] decodeString(String s, int flags) { return decodeBytes(s.getBytes(), flags); }
public static byte[] decodeBytes(byte[] src, int flags) { return decodeBytesRange(src, 0, src.length, flags); }
public static byte[] decodeBytesRange(byte[] src, int off, int len, int flags) { Base64Decoder d = new Base64Decoder(flags, new byte[(len * 3) / 4]); if (!d.decodeBytes(src, off, len, true)) throw new IllegalArgumentException("bad base-64"); if (d.position == d.buffer.length) return d.buffer; byte[] trim = new byte[d.position]; System.arraycopy(d.buffer, 0, trim, 0, d.position); return trim; }
public static byte[] encodeBytes(byte[] src, int flags) { return encodeBytesRange(src, 0, src.length, flags); }
public static byte[] encodeBytesRange(byte[] src, int off, int len, int flags) { Base64Encoder e = new Base64Encoder(flags, null); boolean pad = e.doPadding; int base = (len / 3) * 4; int size; int mod = len % 3; if (pad) { size = (mod > 0) ? base + 4 : base; } else { size = (mod == 1) ? base + 2 : (mod == 2) ? base + 3 : base; } if (e.doNewline && len > 0) { size += (((len - 1) / 57) + 1) * (e.useCrlf ? 2 : 1); } e.buffer = new byte[size]; e.encodeBytes(src, off, len, true); if (!ASSERTIONS_ENABLED || e.position == size) return e.buffer; throw new AssertionError("encoded " + e.position + " != expected " + size); }
public static String encodeToString(byte[] src, int flags) { try { return new String(encodeBytes(src, flags), "US-ASCII"); } catch (UnsupportedEncodingException ex) { throw new AssertionError(ex); } }}