文章目录

  • 1.RC4算法简介与原理
    • 1.1RC4算法工作原理
    • 1.2 RC4算法C实现
  • 2. 实战演练

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算法实现加解密一般流程:

1.1RC4算法工作原理

 前面说到,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密码流生成流程如下:

 从上图可知,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;// 置换向量Stemp = 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]; // 向量Schar T[256]; // 向量Tconst char *key_default = "jiangdongguo"; // 密钥// 第一步:初始化向量S、T。将key_default按字节循环填充到向量Tfor(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;// 置换向量Stemp = 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]; // 向量Schar T[256]; // 向量Tconst char *key_default = "jiangdongguo"; // 密钥// 第一步:初始化向量S、T。将key_default按字节循环填充到向量Tfor(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;// 置换向量Stemp = 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(rc4utilSHAREDsrc/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 <jni.h>
#include <android/log.h>
#include <cstring>#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);// 初始化向量Sinit_S();// 初始化密钥,即向量Tinit_key(key);// 置换状态向量Spremute_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);// 初始化向量Sinit_S();// 初始化密钥,即向量Tinit_key(key);// 置换状态向量Spremute_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按字节循环填充到向量Tfor(int i=0; i<256; i++) {T[i] = key[i % key_len];}} else {if(isDebug)LOG_I("使用默认密钥");// 将key_default按字节循环填充到向量Tint 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;// 置换向量Stemp = 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直播开发之旅(14):使用RC4算法加解密音视频流相关推荐

  1. Android直播开发之旅(25):使用AES算法加密多媒体文件(+RSA+MD5+Base64)

    文章目录 1. AES算法 1.1 AES加密过程 1.1.1 字节代替(SubBytes) 1.1.2 行移位(ShiftRows) 1.1.3 列混合(MixColumns) 1.1.4 加轮密钥 ...

  2. Android直播开发之旅(3):AAC编码格式分析与MP4文件封装(MediaCodec+MediaMuxer)

    Android直播开发之旅(3):AAC编码格式分析与MP4文件封装(MediaCodec+MediaMuxer) (码字不易,转载请声明出处:http://blog.csdn.net/andrexp ...

  3. Android直播开发之旅(17):使用FFmpeg提取MP4中的H264和AAC

    最近在开发中遇到了一个问题,即无法提取到MP4中H264流的关键帧进行处理,且保存到本地的AAC音频也无法正常播放.经过调试分析发现,这是由于解封装MP4得到的H264和AAC是ES流,它们缺失解码时 ...

  4. Android直播开发之旅(13):使用FFmpeg+OpenSL ES播放PCM音频

    文章目录 1. OpenSL ES原理 1.1 OpenSL ES核心API讲解 1.1.1 对象(Object)与接口(Interface) 1.1.2 [OpenSL ES的状态机制](https ...

  5. Android直播开发之旅(4):MP3编码格式分析与lame库编译封装

    转载请声明出处:http://blog.csdn.net/andrexpert/article/77683776 一.Mp3编码格式分析 MP3,全称MPEG Audio Layer3,是一种高效的计 ...

  6. Android直播开发之旅(9):OkCamera,Android 相机应用开发通用库

    OkCamera,Android 相机应用开发通用库 转载请声明出处:http://blog.csdn.net/andrexpert/article/details/79302576 明天就可以回家过 ...

  7. Android直播开发之旅(7):Android视频直播核心技术(架构)详解

    (转载请声明出处:http://blog.csdn.net/andrexpert/article/details/76919535) 一.直播架构解析 目前主流的直播架构中主要有两种方案,即流媒体转发 ...

  8. Android直播开发之旅(15):libjpeg库的编译移植与使用

    1. libjpeg介绍  libJPEG库是一款功能强大的JPEG图像处理开源库,它支持将图像数据压缩编码为JPEG格式和对原有的JPEG图像解压缩,Android系统底层处理图片压缩就是用得lib ...

  9. Android直播开发之旅(18):FFmpeg中滤镜(filter)的工作原理

    文章目录 1. 什么是滤镜 1.1 简单滤镜(滤镜链) 1.2 复杂滤镜(滤镜图) 2. 滤镜API介绍与使用 2.1 滤镜API介绍 2.1.1 结构体 2.1.2 功能函数 2.2 滤镜API的使 ...

  10. h5直播开发之旅总结

    前言 关于直播,有很多相关技术文章,这里不多说. 作为前端,我们比较关心我们所需要的. 直播的大致流程: APP端调用摄像头 -> 拍摄视频 -> 实时上传视频 -> 服务器端获取视 ...

最新文章

  1. Go 学习笔记(9)— 循环(for、for range访问数组、切片、字符串和通道、goto、continue、break)
  2. 第一天:数据库设计--access数据类型介绍
  3. ServletContext 与application的异同
  4. linux sed命令新文件名,linux中sed命令批量修改
  5. Linux下CMake简明教程(七)对库进行链接
  6. ubantu使用apt安装时出现: xxx is not found 的解决方法
  7. 欠122亿乐视能不能“真还”?数据拆解乐视债务账单
  8. JavaFX技巧20:有很多需要展示的地方吗? 使用画布!
  9. 解密Arm Neoverse V1 和 Neoverse N2 平台 为下一代基础设施带来计算变革
  10. GUI Design Studio 4 5 151 0原型设计工具的使用
  11. 2021年3月程序员工资统计,平均15189元,又涨了
  12. 【代码笔记】iOS-MBProgressHUD+MJ
  13. XP经典壁纸,多少人曾爱慕你年轻时的容颜
  14. 最全最新cpu显卡天梯图_电脑显卡天梯图2019排行榜——2019显卡CPU天梯图排行榜...
  15. php开发环境浏览器有哪些,ie内核浏览器有哪些
  16. 网易邮箱显示服务器返回错误,企业退信的常见问题?
  17. 嵌入式开发——物联网
  18. 开启memcached日志
  19. outlook配置文件添加服务器,Microsoft Outlook卡在加载配置文件?这里如何解决它
  20. 一个小程序走完诉讼全程,腾讯云加速推动“智慧法院”方案落地

热门文章

  1. 精彩回顾 | NDBC 2021华为参会回顾
  2. 硬件学习之滤波电容的阻抗特性
  3. 什么是带内管理 带外管理?
  4. 姚锦云:再论庄子传播思想与接受主体性:回应尹连根教授
  5. miner更换owner钱包地址
  6. Handler消息机制之深入理解Message.obtain()
  7. 新浪微博分享错误代码列表
  8. Excel实现行列转换的三种方式
  9. 删除linkinfo.dll
  10. 两个自然数互素(relatively prime)