<NME> CryptUtils.java
<BEF> package cgeo.geocaching.utils;

import android.text.Spannable;
import android.text.SpannableStringBuilder;

import androidx.annotation.NonNull;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.crypto.spec.SecretKeySpec;

public final class CryptUtils {
    public static String rot13(String text) {
        if (text == null) {
            return "";
    }

    private CryptUtils() {
        // utility class
    }

    private static class Rot13Encryption {
        private boolean plaintext = false;

        char getNextEncryptedCharacter(final char c) {
            int result = c;
            if (result == '[') {
                plaintext = true;
            } else if (result == ']') {
                plaintext = false;
            } else if (!plaintext) {
                final int capitalized = result & 32;
                result &= ~capitalized;
                result = (result >= 'A' && result <= 'Z' ? ((result - 'A' + 13) % 26 + 'A') : result)
                        | capitalized;
            }
            return (char) result;
        }
    }

    @NonNull
    public static String rot13(final String text) {
        if (text == null) {
            return StringUtils.EMPTY;
        }
        final StringBuilder result = new StringBuilder();
        final Rot13Encryption rot13 = new Rot13Encryption();

        final int length = text.length();
        for (int index = 0; index < length; index++) {
            final char c = text.charAt(index);
            result.append(rot13.getNextEncryptedCharacter(c));
        }
        return result.toString();
    }

    @NonNull
    public static String md5(final String text) {
        try {
            final MessageDigest digest = MessageDigest.getInstance("MD5");
            digest.update(text.getBytes(StandardCharsets.UTF_8), 0, text.length());
            return new BigInteger(1, digest.digest()).toString(16);
        } catch (NoSuchAlgorithmException e) {
            Log.e("CryptUtils.md5", e);
        }

        return StringUtils.EMPTY;
    }

    @NonNull
    public static byte[] hashHmac(final String text, final String salt) {
        try {
            final SecretKeySpec secretKeySpec = new SecretKeySpec(salt.getBytes(StandardCharsets.UTF_8), "HmacSHA1");
            final Mac mac = Mac.getInstance("HmacSHA1");
            mac.init(secretKeySpec);
            return mac.doFinal(text.getBytes(StandardCharsets.UTF_8));
        } catch (GeneralSecurityException e) {
            Log.e("CryptUtils.hashHmac", e);
            return EMPTY;
        }
    }

    @NonNull
    public static CharSequence rot13(final Spannable span) {
        // I needed to re-implement the rot13(String) encryption here because we must work on
        // a SpannableStringBuilder instead of the pure text and we must replace each character inline.
        // Otherwise we loose all the images, colors and so on...
        final SpannableStringBuilder buffer = new SpannableStringBuilder(span);
        final Rot13Encryption rot13 = new Rot13Encryption();

        final int length = span.length();
        for (int index = 0; index < length; index++) {
            final char c = span.charAt(index);
            buffer.replace(index, index + 1, String.valueOf(rot13.getNextEncryptedCharacter(c)));
        }
        return buffer;
    }

    @NonNull
    public static String base64Encode(final byte[] in) {
        final int iLen = in.length;
        final int oDataLen = (iLen * 4 + 2) / 3; // output length without padding
        final int oLen = ((iLen + 2) / 3) * 4; // output length including padding
        final char[] out = new char[oLen];
        int ip = 0;
        int op = 0;

        return buffer;
    }

}

<MSG> Moved methods to better fitting classes

<DFF> @@ -13,6 +13,32 @@ import javax.crypto.Mac;
 import javax.crypto.spec.SecretKeySpec;
 
 public final class CryptUtils {
+
+    private static char[] base64map1 = new char[64];
+    private static byte[] base64map2 = new byte[128];
+
+    static {
+        int i = 0;
+        for (char c = 'A'; c <= 'Z'; c++) {
+            base64map1[i++] = c;
+        }
+        for (char c = 'a'; c <= 'z'; c++) {
+            base64map1[i++] = c;
+        }
+        for (char c = '0'; c <= '9'; c++) {
+            base64map1[i++] = c;
+        }
+        base64map1[i++] = '+';
+        base64map1[i++] = '/';
+
+        for (i = 0; i < base64map2.length; i++) {
+            base64map2[i] = -1;
+        }
+        for (i = 0; i < 64; i++) {
+            base64map2[base64map1[i]] = (byte) i;
+        }
+    }
+
     public static String rot13(String text) {
         if (text == null) {
             return "";
@@ -111,4 +137,74 @@ public final class CryptUtils {
         return buffer;
     }
 
+    public static byte[] base64Decode(String text) {
+        char[] in = text.toCharArray();
+
+        int iLen = in.length;
+        if (iLen % 4 != 0) {
+            throw new IllegalArgumentException("Length of Base64 encoded input string is not a multiple of 4.");
+        }
+        while (iLen > 0 && in[iLen - 1] == '=') {
+            iLen--;
+        }
+        int oLen = (iLen * 3) / 4;
+        byte[] out = new byte[oLen];
+        int ip = 0;
+        int op = 0;
+        while (ip < iLen) {
+            int i0 = in[ip++];
+            int i1 = in[ip++];
+            int i2 = ip < iLen ? in[ip++] : 'A';
+            int i3 = ip < iLen ? in[ip++] : 'A';
+            if (i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127) {
+                throw new IllegalArgumentException("Illegal character in Base64 encoded data.");
+            }
+            int b0 = base64map2[i0];
+            int b1 = base64map2[i1];
+            int b2 = base64map2[i2];
+            int b3 = base64map2[i3];
+            if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0) {
+                throw new IllegalArgumentException("Illegal character in Base64 encoded data.");
+            }
+            int o0 = (b0 << 2) | (b1 >>> 4);
+            int o1 = ((b1 & 0xf) << 4) | (b2 >>> 2);
+            int o2 = ((b2 & 3) << 6) | b3;
+            out[op++] = (byte) o0;
+            if (op < oLen) {
+                out[op++] = (byte) o1;
+            }
+            if (op < oLen) {
+                out[op++] = (byte) o2;
+            }
+        }
+        return out;
+    }
+
+    public static String base64Encode(byte[] in) {
+        int iLen = in.length;
+        int oDataLen = (iLen * 4 + 2) / 3; // output length without padding
+        int oLen = ((iLen + 2) / 3) * 4; // output length including padding
+        char[] out = new char[oLen];
+        int ip = 0;
+        int op = 0;
+
+        while (ip < iLen) {
+            int i0 = in[ip++] & 0xff;
+            int i1 = ip < iLen ? in[ip++] & 0xff : 0;
+            int i2 = ip < iLen ? in[ip++] & 0xff : 0;
+            int o0 = i0 >>> 2;
+            int o1 = ((i0 & 3) << 4) | (i1 >>> 4);
+            int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
+            int o3 = i2 & 0x3F;
+            out[op++] = base64map1[o0];
+            out[op++] = base64map1[o1];
+            out[op] = op < oDataLen ? base64map1[o2] : '=';
+            op++;
+            out[op] = op < oDataLen ? base64map1[o3] : '=';
+            op++;
+        }
+
+        return new String(out);
+    }
+
 }

Moved methods to better fitting classes