Android直播开发之旅(14):使用RC4算法加解密音视频流

1. RC4算法简介与原理

 RC4加密算法是Ron Rivest在1987年设计出的密钥长度可变的加密算法族,它是一种面向字节操作的对称加密算法,且属于对称密码算法中的序列密码(streamcipher,也称为流密码)。RC4算法采用的是输出反馈(OFB,oupt-feedback)工作方式,该方式允许用一个短的密钥产生一个相对较长的密钥序列,并且它与分块加密算法(CBC,cipherblock chaining)不同,RC4算法不对明文数据进行分组(块),而是用密钥生成与明文一样长短的密钥流对明文进行异或`加密,加密得到的密文长度与明文一致。RC4算法特点:

  • (1) 简洁易于实现、加密速度快;
  • (2) 密钥长度可变,一般用256个字节,安全性较高;

 据了解,RC4算法执行的速度相当快,它大约是分块密码算法DES的5倍,是3DES的15倍,且比高级算法AES也快很多。RC4的安全保证主要在于输入密钥的产生途径,只要在这方面不出漏洞,采用128bit的密钥还是非常安全的。下图是RC4算法实现加解密一般流程:

Android直播开发之旅(14):使用RC4算法加解密音视频流_第1张图片

1.1 RC4算法工作原理

 前面说到,RC4算法是一种流密码算法,它通过使用密钥(1~256字节)生成与明文一样长短的密钥流对明文进行异或加密,加密得到的密文长度与明文一致。RC4算法通过密钥调度算法(The key-scheduling algorithm,KSA)伪随机子密码生成算法(The pseudo-random generation algorithm,PRGA)两个步骤来生成密钥流,然后再用密钥流与明文进行异或产生密文。KSA与PRGA介绍如下:

  • 密钥调度算法(KSA)

 该算法的原理是利用一个密钥key对状态向量S作置换,通过对向量S中的数重新排列得到将来用于生成密钥流的种子。其中,这个密钥是我们自行设定的,存储在临时向量T中,通常长度为1~256个字节。算法C伪代码描述如下:

for(i=0; i<256; i++) {
    S[i] = i;
    T[i] = key[i%keyLen];
}
for(i=0; i<256; i++) {
    j = (j+S[i]+T[j]) % 256;
    swap(S[i], S[j]);
}
  • 伪随机子密码生成算法(PRGA)

 该算法是利用状态向量S来产生任意长度的密钥流,并且密钥流的长度受明文的影响,即与明文的长度保持一致。算法C伪代码描述如下:

i=j=k=0;
while(k < plainTextLen) {
    i = (i + 1) % 256;
    j = (j + S[i]) % 256;
    swap(S[i], S[j]);
    keyStream[k++] = S[(S[i] + S[j]) % 256];
}

RC4密码流生成流程如下:

Android直播开发之旅(14):使用RC4算法加解密音视频流_第2张图片

 从上图可知,RC4算法加/解密可分为如下步骤:

(1) 初始化向量S和向量T;

 向量S和向量T的长度为256(字节),每个单元占一个字节。其中,向量S又称为状态向量,是密钥流生成的种子1,通常初始值直接赋予0~255即可;向量T又称为临时向量,是密钥流生成的种子2,通常我们将密钥(我们自己设定的密码,长度为1-256字节,通常选择16字节=128bits即可)直接赋值给向量T,如果密钥长度小于256,则循环将密钥赋值到向量T。

// 密钥
char[] key_default = "jiangdongguo";
char S[256]; 
char T[256];
// 初始化向量S
for(int i=0; i<256; i++) {
    S[i] = i;
}
// 初始化向量T
int key_len = strlen(key_default);
for(int i=0; i<256; i++) {
    T[i] = key_default[i % key_len];
}

(2) 将向量S进行置换;

 将向量S进行置换的目的是打乱初始种子1,使得状态向量S具有随机性。置换的规则是从第0个字节开始,执行256次,保证每个字节都得到处理。

char temp;
int j = 0;
for (int i = 0; i < 256; i++) {
    j = (j + S[i] + T[i]) % 256;
    temp = S[i];
    S[i] = S[j];
    S[j] = temp;
}

(3) 产生密钥流;

 密钥流就是根据种子1(向量S)、种子2(向量T)处理得到的字节流,其长度和明文的长度是相等的。假如明文的长度为100字节,那么密钥流的长度也为100字节。

int i=0, j=0;
int index=0, t = 0;
char temp = 0;
// 密钥流
char * keyStream = (char *) malloc(len * sizeof(char));
memset(keyStream, 0, len * sizeof(char));
while (textLen --) {
    i = (i + 1) % 256;
    j = (j + S[i]) % 256;
    // 置换向量S
    temp = S[i];
    S[i] = S[j];
    S[j] = temp;
    // 生成密钥流
    t = (S[i] + S[j]) % 256;
    keyStream[index] = S[t];
    index ++;
}

(4) 加密或解密;

 无论加密还是解密,我们只需要将明文/密文使用相同的密钥流对每个字节进行异或操作即可。

// 加密
char * cryptText = (char *) malloc(len * sizeof(char));
memset(cryptText, 0, len * sizeof(char));
for(int i = 0; i< len; i++) {
    cryptText[i] = char(keyStream[i] ^ plainText[i]);
}
// 解密
char * plainText = (char *) malloc(len * sizeof(char));
memset(plainText, 0, len * sizeof(char));
for(int i = 0; i< len; i++) {
    plainText[i] = char(keyStream[i] ^ cryptText[i]);
}
1.2 RC4算法C实现

(1) RC4加密

void rc4_encypt(char *plainText, int len) {
    char S[256]; // 向量S
	char T[256]; // 向量T
	const char *key_default = "jiangdongguo"; // 密钥
    // 第一步:初始化向量S、T。将key_default按字节循环填充到向量T
    for(int i=0; i<256; i++) {
        S[i] = i;
    }
    int key_len = strlen(key_default);
    for(int i=0; i<256; i++) {
        T[i] = key_default[i % key_len];
    }
    // 第二步:置换向量S。打乱向量S,保证每个字节都得到处理
    char temp;
    int j = 0;
    for (int i = 0; i < 256; i++) {
        j = (j + S[i] + T[i]) % 256;
        temp = S[i];
        S[i] = S[j];
        S[j] = temp;
    }
    // 第三步:生成密钥流。密钥流的长度=要加密数据的长度
    int i=0, j=0;
    int index=0, t = 0;
    char temp = 0;
    char * keyStream = (char *) malloc(len * sizeof(char));
    memset(keyStream, 0, len * sizeof(char));
    int textLen = len;
    while (textLen --) {
        i = (i + 1) % 256;
        j = (j + S[i]) % 256;
        // 置换向量S
        temp = S[i];
        S[i] = S[j];
        S[j] = temp;
        // 生成密钥流
        t = (S[i] + S[j]) % 256;
        keyStream[index] = S[t];
        index ++;
    }
    // 第四步:加密。使用密钥流对原始数据流进行异或操作
    char * cryptText = (char *) malloc(len * sizeof(char));
    memset(cryptText, 0, len * sizeof(char));
    for(int i = 0; i< len; i++) {
        cryptText[i] = char(keyStream[i] ^ plainText[i]);
    }
    memcpy(plainText, cryptText, len);
    free(cryptText);
    free(keyStream);
}

(2) RC4解密

void rc4_decypt(char *cryptText, int len ) {
    char S[256]; // 向量S
	char T[256]; // 向量T
	const char *key_default = "jiangdongguo"; // 密钥
    // 第一步:初始化向量S、T。将key_default按字节循环填充到向量T
    for(int i=0; i<256; i++) {
        S[i] = i;
    }
    int key_len = strlen(key_default);
    for(int i=0; i<256; i++) {
        T[i] = key_default[i % key_len];
    }
    // 第二步:置换向量S。打乱向量S,保证每个字节都得到处理
    char temp;
    int j = 0;
    for (int i = 0; i < 256; i++) {
        j = (j + S[i] + T[i]) % 256;
        temp = S[i];
        S[i] = S[j];
        S[j] = temp;
    }
    // 第三步:生成密钥流。密钥流的长度=要加密数据的长度
    int i=0, j=0;
    int index=0, t = 0;
    char temp = 0;
    char * keyStream = (char *) malloc(len * sizeof(char));
    memset(keyStream, 0, len * sizeof(char));
    int textLen = len;
    while (textLen --) {
        i = (i + 1) % 256;
        j = (j + S[i]) % 256;
        // 置换向量S
        temp = S[i];
        S[i] = S[j];
        S[j] = temp;
        // 生成密钥流
        t = (S[i] + S[j]) % 256;
        keyStream[index] = S[t];
        index ++;
    }
    // 第四步:加密。使用相同的密钥流对加密数据流进行异或操作
    char * plainText = (char *) malloc(len * sizeof(char));
    memset(plainText, 0, len * sizeof(char));
    for(int i = 0; i< len; i++) {
        plainText[i] = char(keyStream[i] ^ cryptText[i]);
    }
    memcpy(cryptText, plainText, len);
    free(plainText);
    free(keyStream);
}

2. 实战演练

 为了进一步了解RC4算法,这里将通过NDK/JNI技术将算法实现写到so中,然后再在Android应用Java层调用native方法实现对采集的摄像头视频流进行实时加解密。首先,我们看下AS工程的CmakeList.txt配置文件,它定义了如何编译C/C++文件,并将相关库链接到so库。

cmake_minimum_required(VERSION 3.4.1)

set(CMAKE_VERBOSE_MAKEFILE  on)
# 指定so输出路径
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/libs)
# 添加自定义库rc4util
add_library(
        rc4util
        SHARED
        src/main/cpp/NativeRC4.cpp
        )
# 查找系统库
find_library(log-lib log)
find_library(android-lib android)
# 链接所有库到rc4util
target_link_libraries(
        rc4util

        ${log-lib}
        ${android-lib}
)

 其次,在Java层定义RC4加解密native方法,位于main目录下的RC4Utils.java文件中。

package com.jiangdg.natives;

import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;

import java.io.UnsupportedEncodingException;

/***
 *  使用RC4算法加密流数据
 *
 * @author Jiangdg
 * @since 2019-08-20 10:40
 * */
public class RC4Utils {

    static {
        System.loadLibrary("rc4util");
    }

    /** 加密,使用默认密钥
     *
     * @param plainText 字符串明文
     * @return 字符串密文
     * */
    public static String rc4EncryptText(String plainText) {
        if(TextUtils.isEmpty(plainText)) {
            return null;
        }
        byte[] plainData = plainText.getBytes();
        nativeRc4Encrypt(null, plainData, 0, plainData.length);
        return Base64.encodeToString(plainData, Base64.DEFAULT);
    }

    /**
     *  解密,使用默认密钥
     *
     * @param cipherText 字符串密文
     * @return 字符串明文
     * */
    public static String rc4DecryptText(String cipherText){
        if(TextUtils.isEmpty(cipherText)) {
            return null;
        }
        byte[] cipherData = Base64.decode(cipherText, Base64.DEFAULT);
        nativeRC4Decrypt(null, cipherData, 0, cipherData.length);
        return new String(cipherData);
    }

    /**
     *  加密,使用默认密钥
     *
     * @param plain 明文(字节数组)
     * @return 密文
     * */
    public static int rc4EncryptData(byte[] plain, int start, int len) {
        return nativeRc4Encrypt(null, plain, start , len);
    }

    /**
     *  解密,使用默认密钥
     *
     * @param cipher 密文(字节数组)
     * @return 明文
     * */
    public static int rc4DecryptData(byte[] cipher, int start, int len) {
        return nativeRC4Decrypt(null, cipher, start , len);
    }

    public native static int nativeRc4Encrypt(String key, byte[] plainText, int start, int len);

    public native static int nativeRC4Decrypt(String key, byte[] cipherText, int start, int len);
}

 第三,编写RC4加解密算法的native实现,位于cpp目录下的NativeRC4.cpp文件中。

// RC4加解密native实现
//
// Created by Jiangdg on 2019/8/20.
//
#include 
#include 
#include 

#define TAG "NativeRC4"
#define LOG_I(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)

void init_S();
void init_key(const char *key);
void premute_S();
void create_key_stream(char *keyStream, int lenBytes);

char S[256]; // 向量S
char T[256]; // 向量T
bool isDebug = true;
const char *key_default = "jiangdongguo"; // 默认密钥


extern "C"
JNIEXPORT jint JNICALL
Java_com_jiangdg_natives_RC4Utils_nativeRc4Encrypt(JNIEnv *env, jclass type, jstring key_,
                                                   jbyteArray plainText_, jint start, jint len) {
    const char *key = NULL;
    if(key_ != NULL) {
        key = env->GetStringUTFChars(key_, 0);
    }
    if(plainText_ == NULL) {
        if(isDebug)
            LOG_I("plain text can not be null");
        return -1;
    }
    jbyte *plainText = env->GetByteArrayElements(plainText_, JNI_FALSE);
    if(isDebug)
        LOG_I("encrypt plainText = %s", plainText);
    // 初始化向量S
    init_S();

    // 初始化密钥,即向量T
    init_key(key);

    // 置换状态向量S
    premute_S();

    // 生成密钥流,长度与明文长度一样
    char * keyStream = (char *) malloc(len * sizeof(char));
    memset(keyStream, 0, len * sizeof(char));
    create_key_stream(keyStream, len);

    // 使用密钥流对明文加密(异或处理)
    char * cryptText = (char *) malloc(len * sizeof(char));
    memset(cryptText, 0, len * sizeof(char));
    for(int i = 0; i< len; i++) {
        cryptText[i] = keyStream[i] ^ plainText[i+start];
    }
    if(isDebug)
        LOG_I("encrypt cryptText = %s /////len=%d", cryptText, strlen(cryptText));
    memcpy(plainText+start, cryptText, len);
    free(cryptText);
    free(keyStream);
    if(isDebug)
        LOG_I("encrypt text over");
    if(key_ != NULL) {
        env->ReleaseStringUTFChars(key_, key);
    }
    env->ReleaseByteArrayElements(plainText_, plainText, 0);
    return 0;
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_jiangdg_natives_RC4Utils_nativeRC4Decrypt(JNIEnv *env, jclass type, jstring key_,
                                                   jbyteArray cipherText_, jint start, jint len) {
    const char *key = NULL;
    if(key_ != NULL) {
        key = env->GetStringUTFChars(key_, 0);
    }
    if(cipherText_ == NULL) {
        if(isDebug)
            LOG_I("cipher text can not be null");
        return -1;
    }
    jbyte *cipherText = env->GetByteArrayElements(cipherText_, JNI_FALSE);
    // 初始化向量S
    init_S();

    // 初始化密钥,即向量T
    init_key(key);

    // 置换状态向量S
    premute_S();

    // 生成密钥流,长度与密文长度一样
    char * keyStream = (char *) malloc(len * sizeof(char));
    memset(keyStream, 0, len * sizeof(char));
    create_key_stream(keyStream, len);

    // 使用密钥流对密文解密(异或处理)
    char * plainText = (char *) malloc(len * sizeof(char));
    memset(plainText, 0, len * sizeof(char));
    for(int i = 0; i< len; i++) {
        plainText[i] = keyStream[i] ^ cipherText[i+start];
    }
    if(isDebug)
        LOG_I("decrypt plainText = %s", plainText);
    memcpy(cipherText+start, plainText, len);
    free(plainText);
    free(keyStream);
    if(isDebug)
        LOG_I("decrypt text over");
    if(key_ != NULL) {
        env->ReleaseStringUTFChars(key_, key);
    }
    env->ReleaseByteArrayElements(cipherText_, cipherText, 0);
    return 0;
}


void init_S() {
    for(int i=0; i<256; i++) {
        S[i] = i;
    }
}

void init_key(const char *key) {
    if(key) {
        if(isDebug)
            LOG_I("使用用户输入密钥");
        int key_len = strlen(key);
        // 将key按字节循环填充到向量T
        for(int i=0; i<256; i++) {
            T[i] = key[i % key_len];
        }
    } else {
        if(isDebug)
            LOG_I("使用默认密钥");
        // 将key_default按字节循环填充到向量T
        int key_len = strlen(key_default);
        for(int i=0; i<256; i++) {
            T[i] = key_default[i % key_len];
        }
    }
}


void premute_S() {
    char temp;
    int j = 0;
    // 打乱向量S,保证每个字节都得到处理
    for (int i = 0; i < 256; i++) {
        j = (j + S[i] + T[i]) % 256;
        temp = S[i];
        S[i] = S[j];
        S[j] = temp;
    }
}


void create_key_stream(char *keyStream, int textLen) {
    int i=0, j=0;
    int index=0, t = 0;
    char temp = 0;
    while (textLen --) {
        i = (i + 1) % 256;
        j = (j + S[i]) % 256;
        // 置换向量S
        temp = S[i];
        S[i] = S[j];
        S[j] = temp;
        // 生成密钥流
        t = (S[i] + S[j]) % 256;
        keyStream[index] = S[t];
        index ++;
    }
}

 最后,就是对Camera采集的视频流进行编码加密,渲染显示时解密解码。其中,考虑到再解码H264数据时,受每帧的起始码和NALU头的影响,因此,在加密时我们只对I帧或B/P帧的有效负载进行加密。具体的实现位于Java层main目录下的H264EncodeThread.javaH264DecodeThread.java文件中,这里只给出加密部分和解密部分代码。

// H264EncodeThread.java加密部分
// 判断关键帧和非关键帧,加密
byte[] ppsSps = new byte[0];
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputIndex;
do {
    outputIndex = mEncodeCodec.dequeueOutputBuffer(bufferInfo, TIMESOUTUS);
    if(outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    } else if(outputIndex >= 0) {
        ByteBuffer outputBuffer = mEncodeCodec.getOutputBuffer(outputIndex);
        if(outputBuffer != null) {
            byte[] outData = new byte[bufferInfo.size];
            outputBuffer.position(bufferInfo.offset);
            outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
            outputBuffer.get(outData);
            int countStartHeader = 5;
            int naluType = outputBuffer.get(4) & 0x1F;

            if(naluType == 7 || naluType == 8) {
                ppsSps = outData;
            } else if(naluType == 5) {
                // 加密关键帧
                RC4Utils.rc4EncryptData(outData, countStartHeader, 
                                        outData.length-countStartHeader);

                byte[] iframeData = new byte[ppsSps.length + bufferInfo.size];
                System.arraycopy(ppsSps, 0 , iframeData, 0, ppsSps.length);
                System.arraycopy(outData, 0, iframeData, ppsSps.length, outData.length);
                outData = iframeData;
            } else if(naluType == 1){
                // 加密非关键帧
                RC4Utils.rc4EncryptData(outData, countStartHeader, outData.length-
                                        countStartHeader);
            }
        }
        mEncodeCodec.releaseOutputBuffer(outputIndex, false);
    }
}while (outputIndex >= 0 && !isExit);
 
// H264DecodeThread.java解密部分
// 判断关键帧和非关键帧,解密后,再解码
int inputIndex = mDecodeCodec.dequeueInputBuffer(TIMEOUTUS);
if(inputIndex >= 0) {
    ByteBuffer inputBuffer = mDecodeCodec.getInputBuffer(inputIndex);
    byte[] h264 = new byte[0];
    if(inputBuffer != null) {
        try {
            h264 = (byte[]) mQueue.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if(h264.length > 0) {
            int type = h264[4] & 0x1F;
            int countStartHeader = 5;
            // 解密关键帧或非关键帧
            if(type == 5 || type == 1]) {
                RC4Utils.rc4DecryptData(h264, countStartHeader, h264.length-countStartHeader);
            }
            inputBuffer.clear();
            inputBuffer.put(h264);
        }
    }
    mDecodeCodec.queueInputBuffer(inputIndex, 0, h264.length, System.nanoTime()/1000, 0);
}

效果演示:

github源码地址:RC4Encrypt,如果觉得有用或有疑问,欢迎star 或 Issues~

你可能感兴趣的:(Android视频直播)