RC4加密算法是Ron Rivest在1987年设计出的密钥长度可变的加密算法族
,它是一种面向字节
操作的对称加密
算法,且属于对称密码算法中的序列密码(streamcipher,也称为流密码)。RC4算法采用的是
输出反馈(OFB,oupt-feedback)工作方式,该方式允许用一个
短的密钥产生一个相对
较长的密钥序列,并且它与分块加密算法(CBC,cipherblock chaining)不同,RC4算法不对明文数据进行分组(块),而是用密钥生成与明文一样长短的
密钥流对明文进行
异或`加密,加密得到的密文长度与明文一致。RC4算法特点:
据了解,RC4算法执行的速度相当快,它大约是分块密码算法DES的5倍,是3DES的15倍,且比高级算法AES也快很多。RC4的安全保证主要在于输入密钥的产生途径,只要在这方面不出漏洞,采用128bit
的密钥还是非常安全的。下图是RC4算法实现加解密一般流程:
前面说到,RC4算法是一种流密码
算法,它通过使用密钥(1~256字节)生成与明文一样长短的密钥流
对明文进行异或
加密,加密得到的密文长度与明文一致。RC4算法通过密钥调度算法(The key-scheduling algorithm,KSA)
和伪随机子密码生成算法(The pseudo-random generation algorithm,PRGA)
两个步骤来生成密钥流,然后再用密钥流与明文进行异或产生密文。KSA与PRGA介绍如下:
该算法的原理是利用一个密钥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]);
}
该算法是利用状态向量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;
// 置换向量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) 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);
}
为了进一步了解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.java
和H264DecodeThread.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~