使用NDK实现的Base64编/解码

Base64的编解码原理很简单,下面是编解码的部分核心代码:


编码:

/**
 * 编码
 * data       : byte数组
 * realLength : 数组长度
 * offset     : 偏移量
 * length     : 从偏移量开始截取的长度
 * flag       : 选项
 */
EncodeData *encode(const char *data, int realLength, int offset, int length, int flag) {
    /* 将字符串数据转换成ascii码,再将ascii码变成二进制表示,
     * 将二进制中每3个8位的数据转换成4个6位的数据,
     * 并且在6位的数据前加‘00’,最终变成4个8位,
     * 操作完的4个8位二进制再变成4个十进制数字,并根据数字对应的base64表获取编码,
     * 如果不满足3个8位,则在其后补0直至到3个8位,再分割成4个6位,其中补完的数据用'='表示*/

    /*android的base64库中,默认换行用/n,不用/r/n*/

    /* e.g
     * char          : 's'
     * binary        : 01110011
     * split         : 011100 | 11
     * complete 0    : 011100 | 110000 | 000000 | 000000
     * add 0 to head : 00011100 | 00110000 | 00000000 | 00000000
     * decimal       : 28 | 48 | 64(complete) | 64(complete)
     * encode        : c | w | = | =
     * result        : cw==
     * */

    EncodeData *encodeData = (EncodeData *) malloc(sizeof(EncodeData));
    Flags *flags = getFlags(flag);//根据选项值获取对应选项
    const char *table = flags->isUrlSafe ? ENCODE_WEB_TABLE : ENCODE_TABLE;
    char *p;//活动指针,用于循环取数据
    char temp1, temp2, temp3;//辅助取数

    int encodeLength = getEncodeLength(realLength, length, flags);

    encodeData->data = p = (char *) malloc(encodeLength * sizeof(char));
    int i;
    int count = LINE_GROUPS;

    int len = realLength;
    if (length != LEN_DEFAULT)
        len = length;

    len += offset;

    for (i = offset; i < len; i += 3) {

        bool isSecondCharEmpty = (i + 1 >= len);
        bool isThirdCharEmpty = (i + 2 >= len);

        //每次增长3个,若能进入循环,第一个字节肯定存在
        temp1 = data[i];
        temp2 = isSecondCharEmpty ? 0 : data[i + 1];
        temp3 = isThirdCharEmpty ? 0 : data[i + 2];

        //第一个转码取第一个字节的前6位,再通过0x3F(0011 1111)提取出
        *(p++) = table[(temp1 >> 2) & 0x3F];
        //第二个转码取第一个字节的7、8位(0x3F)和第二个字节的前4位(0x0F)
        *(p++) = table[((temp1 << 4) & 0x3F) + ((temp2 >> 4) & 0x0F)];
        //第三个转码取第二个字节的5、6、7、8位(0x3C)和第三个字节的前2位(0x03)
        //假设只有一个字节,那么必定可以转换出第一、二个转码,所以只需要判断第二个字节是否为空
        //若第二字节为空,则用‘=’(64)号代替
        if (isSecondCharEmpty && flags->isPadding)
            *(p++) = table[64];
        else if (!isSecondCharEmpty)
            *(p++) = table[(((temp2 << 2) & 0x3C) + ((temp3 >> 6) & 0x03))];
        //第四个转码直接取第三个字节的后6位即可
        if (isThirdCharEmpty && flags->isPadding)
            *(p++) = table[64];
        else if (!isThirdCharEmpty)
            *(p++) = table[(temp3 & 0x3F)];

        if (flags->isWrap && (--count) == 0) {
            if (flags->isCRLF) *(p++) = '\r';
            *(p++) = '\n';
            count = LINE_GROUPS;
        }
    }
    if (flags->isWrap) {
        if (flags->isCRLF) *(p++) = '\r';
        *p = '\n';//最后要加结束符号
    }

    free(flags);

    encodeData->length = encodeLength;
    return encodeData;
}

编码部分用unit test里简略的运行时间统计,速度大概比原来android方法20%左右


解码:

/**
 * 另一种方法解码,不预先计算解码长度
 */
DecodeData *decodeLikeAndroid(const char *data, int realLength, int offset, int length, int flag) {
    DecodeData *decodeData = (DecodeData *) malloc(sizeof(DecodeData));
    const int *table;
    if (flag & URL_SAFE)
        table = DECODE_WEB_TABLE;
    else
        table = DECODE_TABLE;
    int value;
    char c;

    int index = offset;
    int len = realLength;
    if (length != LEN_DEFAULT)
        len = length;
    len += offset;

    decodeData->data = (char *) malloc(sizeof(char) * len);
    int decodeLength = 0;
    int state = 0;//记录状态

    //最后还是用了android源码里的解决方法
    //因为解码数据中可能存在多种被无效数据(如头文件里解码表中的-1、-2)污染的情况
    //自己写过2种方法,效果不理想
    while (index < len) {
        if (state == 0) {
            while (index + 4 <= len && (value = (table[data[index]] << 18) |
                                                (table[data[index + 1]] << 12) |
                                                (table[data[index + 2]] << 6) |
                                                (table[data[index + 3]])) >= 0) {

                decodeData->data[decodeLength++] = (value >> 16);
                decodeData->data[decodeLength++] = (value >> 8);
                decodeData->data[decodeLength++] = value;

//                //第一个解码取第一个字节的第2~8位,第二个字节的第3~4位
//                decodeData->data[decodeLength++] = (temp1 << 2) + (temp2 >> 4);
//                //第二个解码取第二个字节的后4位,第三个字节的第2~6位
//                decodeData->data[decodeLength++] = (temp2 << 4) + (temp3 >> 2);
//                //第三个解码取第三个字节的后2位,第四个字节的第0~6位
//                decodeData->data[decodeLength++] = (temp3 << 6) + temp4;

                index += 4;
            }

            if (index >= len)
                break;
        }

        c = table[data[index++]];

        switch (state) {
            case 0:
                if (c < DECODE_EQUALS) {
                    value = c;
                    ++state;
                } else if (c != DECODE_SKIP)
                    return getErrorDecodeData(decodeData);
                break;

            case 1:
                if (c < DECODE_EQUALS) {
                    value = (value << 6) | c;
                    ++state;
                } else if (c != DECODE_SKIP)
                    return getErrorDecodeData(decodeData);
                break;

            case 2:
                if (c < DECODE_EQUALS) {
                    value = (value << 6) | c;
                    ++state;
                } else if (c == DECODE_EQUALS) {
                    decodeData->data[decodeLength++] = (value >> 4);
//                    //第一个解码取第一个字节的第2~8位,第二个字节的第3~4位
//                    decodeData->data[decodeLength++] = (temp1 << 2) + (temp2 >> 4);
                    state = 4;
                } else if (c != DECODE_SKIP)
                    return getErrorDecodeData(decodeData);
                break;

            case 3:
                if (c < DECODE_EQUALS) {
                    value = (value << 6) | c;

                    decodeData->data[decodeLength++] = (value >> 16);
                    decodeData->data[decodeLength++] = (value >> 8);
                    decodeData->data[decodeLength++] = value;
                    state = 0;
                } else if (c == DECODE_EQUALS) {
                    decodeData->data[decodeLength++] = (value >> 10);
                    decodeData->data[decodeLength++] = (value >> 2);

                    state = 5;
                } else if (c != DECODE_SKIP)
                    return getErrorDecodeData(decodeData);
                break;

            case 4:
                if (c == DECODE_EQUALS) {
                    ++state;
                } else if (c != DECODE_SKIP)
                    return getErrorDecodeData(decodeData);
                break;

            case 5:
                if (c != DECODE_SKIP)
                    return getErrorDecodeData(decodeData);
                break;
        }
    }

    //跳出while循环后

    switch (state) {
        case 0:
            break;
        case 1:
            return getErrorDecodeData(decodeData);
        case 2:
            decodeData->data[decodeLength++] = (value >> 4);
            break;
        case 3:
            decodeData->data[decodeLength++] = (value >> 10);
            decodeData->data[decodeLength++] = (value >> 2);
            break;
        case 4:
            return getErrorDecodeData(decodeData);
        case 5:
            break;
    }

    decodeData->length = decodeLength;
    return decodeData;
}



注意的是,解码部分还是用了android源码中Base64的解码方法,原因是在解码中会出现解码数据被无效字符污染的情况,暂时没想到好的思路。

结果倒是很有意思,我在单元测试里写了简单的统计运行时间,结果解码时间比android提供的Base64方法还要慢一些(大数据量、10000次左右的循环)。

想到好的思路再修改了


修改了解码拼接字符部分,现在在同一测试中的运行时间基本与android方法持平(最多快1%)


另外,代码里写了一个计算解码后数据长度的方法,适合统计数据源里没有污染情况下的解码长度:

/**
 * 计算解码后的数据长度
 */
int getDecodeLength(const char *data, int realLength, int offset, int length, Flags *flags) {

    char log[256];

    int decodeLength = 0;
    int tempLength = realLength;
    if (length != LEN_DEFAULT)
        tempLength = length;

    sprintf(log, "tempLength:%d", tempLength);
    LOGV(log);

    //当数据应该有'='存在时,数据长度少于4,则无法被解码
    //反之,不应该存在'='时,数据长度少于2,则同上
    if (flags->isPadding && tempLength < 4) {
        return -1;
    } else if (!flags->isPadding && tempLength < 2) {
        return -2;
    }

    //计算数据尾部的换行符占位数(若存在)
    int endSpace = 0;
    if (length == LEN_DEFAULT || (length + offset == realLength)) {
        if (flags->isCRLF) {
            if (data[realLength - 2] == '\r')
                endSpace = 2;
        } else if (data[realLength - 1] == '\n')
            endSpace = 1;
    }

    sprintf(log, "endSpace:%d", endSpace);
    LOGV(log);

    //计算换行符个数
    int enters = 0;
    if (flags->isWrap) {
        enters = tempLength / (flags->isCRLF ? 78 : 77);
    }

    sprintf(log, "enters:%d", enters);
    LOGV(log);

    tempLength -= endSpace;
    decodeLength = tempLength;

    int t = tempLength - enters;
    tempLength = t >= 0 ? t : tempLength;
    bool isRemainder = false;
    int equals = 0;//记录'='号个数(若存在)
    int remainder = 0;//记录余数(若存在)

    sprintf(log, "tempLength--:%d", tempLength);
    LOGV(log);

    /*
     * 多判断一层,存在padding时,解码数据不足4位且不能被4整除的throw exception,
     * 不存在padding时,最少解码数据是2位,数据不能被4整除时,若余数为1、2,则数据完整可被解码
     */

    if (flags->isPadding) {
        if (tempLength % 4 != 0) {
            return -3;
        }

        if (data[decodeLength - 2] == '=') {
            equals = 2;
            isRemainder = true;
        }
        else if (data[decodeLength - 1] == '=') {
            equals = 1;
            isRemainder = true;
        }

    } else {
        int d = tempLength % 4;
        if (d > 2) {
            return -4;
        } else if (d != 0) {
            remainder = d;
            isRemainder = true;
        }
    }

    if (isRemainder) {
        decodeLength -= enters;
        decodeLength += remainder;
        decodeLength = decodeLength * DECODE_CONST - 3;
        decodeLength += (3 - equals);
    } else {
        decodeLength -= enters;
        decodeLength = decodeLength * DECODE_CONST;
    }
    sprintf(log, "decodeLength--:%d", decodeLength);
    LOGV(log);
    return decodeLength;
}

获取编码后长度的方法也有,思路简单很多:

/**
 * 计算、获取编码长度
 */
int getEncodeLength(int realLength, int length, Flags *flags) {
    //编码后的长度计算:3×8=4×6->4×8

    int tempLength = realLength;
    if (length != LEN_DEFAULT)
        tempLength = length;

    int encodeLength = 0;
    if (realLength > 0) {
        int remainder = tempLength % 3;

        encodeLength = (remainder == 0 ? ((tempLength / 3) << 2) : (((tempLength / 3) + 1) << 2));
        if (flags->isWrap) {
            if (!flags->isCRLF) //编码后每76个字符插入'\n'换行
                encodeLength = encodeLength + 1 + encodeLength / LINE_CHARS;
            else //编码后每76个字符插入'\r\n'换行
                encodeLength = encodeLength + (1 + encodeLength / LINE_CHARS) * 2;
        }
        //没有padding,不需要在后面补'='
        if (!flags->isPadding && remainder > 0)
            encodeLength -= (3 - remainder);
    }

    return encodeLength;
}


具体的代码已上传到github:https://github.com/ViTess/Android-Base64

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java Native Interface(JNI)是Java平台的一项技术,它允许Java代码与本地代码(如C、C++)进行交互。在这种情况下,我们可以使用JNI来实现Base64加密和解密。 以下是一个示例代码,演示如何使用JNI实现Base64加密和解密: 1.创建一个名为"Base64.c"的C文件,其中包含以下代码: #include<jni.h> #include<stdlib.h> #include<stdio.h> #include<errno.h> #include<string.h> #include<android/log.h> #include "android/bitmap.h" #include "android/log.h" #include "libjpeg/jpeglib.h" #include "com_example_bitmaptest_utils_CImageUtils.h" //Base64加密函数 JNIEXPORT jstring JNICALL Java_com_example_bitmaptest_utils_CImageUtils_encrypt(JNIEnv *env, jobject obj, jstring str) { const char *src = (*env)->GetStringUTFChars(env, str, NULL); if(src == NULL){ return NULL; } jsize len = (*env)->GetStringUTFLength(env, str); int mod = len % 3; int new_len = len + (mod == 0 ? 0 : (3 - mod)); unsigned char *input = (unsigned char *)malloc(new_len); memset(input, 0, new_len); memcpy(input, src, len); int i = 0, j = 0; unsigned char *output = (unsigned char *)malloc(new_len * 4 / 3); memset(output, 0, new_len * 4 / 3); while(i < new_len){ int c = input[i++] << 16 | input[i++] << 8 | input[i++]; output[j++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[c >> 18]; output[j++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(c >> 12) & 0x3f]; output[j++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(c >> 6) & 0x3f]; output[j++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[c & 0x3f]; } if(mod > 0){ output[new_len * 4 / 3 - 1] = '='; } if(mod == 1){ output[new_len * 4 / 3 - 2] = '='; } (*env)->ReleaseStringUTFChars(env, str, src); jstring result = (*env)->NewStringUTF(env, output); free(input); free(output); return result; } //Base64解密函数 JNIEXPORT jstring JNICALL Java_com_example_bitmaptest_utils_CImageUtils_decrypt(JNIEnv *env, jobject obj, jstring str) { const char *src = (*env)->GetStringUTFChars(env, str, NULL); if(src == NULL){ return NULL; } jsize len = (*env)->GetStringUTFLength(env, str); int new_len = len / 4 * 3; if(src[len - 1] == '='){ new_len--; } if(src[len - 2] == '='){ new_len--; } unsigned char *input = (unsigned char *)malloc(len); memset(input, 0, len); memcpy(input, src, len); int i = 0, j = 0; unsigned char *output = (unsigned char *)malloc(new_len); memset(output, 0, new_len); while(i < len){ unsigned char c[4]; int k = 0; while(k < 4 && i < len){ if(input[i] != '\n' && input[i] != '\r'){ c[k++] = input[i++]; }else{ i++; } } if(k == 4){ output[j++] = (c[0] << 2) | (c[1] >> 4); output[j++] = (c[1] << 4) | (c[2] >> 2); output[j++] = (c[2] << 6) | c[3]; } } (*env)->ReleaseStringUTFChars(env, str, src); jstring result = (*env)->NewStringUTF(env, output); free(input); free(output); return result; } 2.使用javac译CImageUtils.java文件,生成CImageUtils.class文件。 3.使用javah命令生成.h头文件,命令为:javah -classpath . com.example.bitmaptest.utils.CImageUtils 4.将生成的CImageUtils.h文件拷贝到C文件所在目录下,并在C文件中包含该头文件。 5.使用ndk-build命令译C文件,生成动态库文件。 6.将生成的动态库文件拷贝到Android项目的libs目录下。 7.在Java代码中调用JNI函数,例如: public class MainActivity extends AppCompatActivity { static { System.loadLibrary("base64"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String str = "hello, world"; String encryptStr = CImageUtils.encrypt(str); Log.i("MainActivity", "encryptStr: " + encryptStr); String decryptStr = CImageUtils.decrypt(encryptStr); Log.i("MainActivity", "decryptStr: " + decryptStr); } } 通过以上步骤,我们就可以在Android项目中使用JNI实现Base64加密和解密。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值