基于Java线程池的Kuznyechik算法应用研究

2018-12-18 03:22
泰山学院学报 2018年6期
关键词:加密算法线程队列

刘 聪

(德州学院 信息管理学院,山东 德州 253000)

分组密码“Kuznyechik”是俄罗斯密码标准算法集合(称为GOST算法)之一。该算法能够保证信息在传输过程中的机密性和完整性,它符合现代密码的要求,具有安全、高效率、易用和灵活等优点。适用于开发、运用到各种用途的现代信息系统中,保护数据的安全。为了提高Kuznyechik算法在实际应用中的加密速度,本文根据Kuznyechik算法的特点,与Java线程池技术结合,提出采用线程池技术并行加密Kuznyechik的方法。

1 Java线程池技术[1]

通常情况下,加密程序都是单线程运行的。为了提高程序的加密速度,可以设置多个线程去运行。Java语言对多线程提供了非常优秀的支持,基本的Java线程模型有Thread类、Runnable接口、Callable接口、Future接口等。

线程池就是在程序运行时创建一定数量的线程保存起来,当需要时,从线程池中取出一个线程。完成相应任务后再放回到线程池中。通过线程池,可以有效降低资源消耗、提高程序响应速度、提高线程的可管理性。根据线程池的工作原理,算法程序需要线程池管理器、工作线程、任务队列、数据加密实体几个部分。

2 Kuznyechik算法介绍[2]

Kuznyechik在俄罗斯联邦GOST R 34.12-2015的国家标准中定义,它有128比特的块大小和256比特的密钥长度。该算法是俄罗斯密码标准算法集合(称为GOST算法)之一。该算法可以在硬件和软件中实现,能够保证信息在传输过程中的机密性和完整性,它符合现代密码的要求。该标准适用于开发,运用到各种用途的现代信息系统。

该算法包括密钥编排和加密两部分,密钥编排算法是Feistel结构的,有左右两支,在密钥编排算法中,主密钥k为256比特,分为k1和k2,各128比特。首先k1与常数c1相加,将得到的结果经过s盒,L置换,然后与k2相加,得左支到k1′,之前k1的值变成k2′。密钥编排流程如图1所示:

图1 密钥编排流程图

经过8轮相同的运算,最后得到编排后的密钥k3和k4,然后以同样的方式生成k5和k6、k7和k8、k9和k10。其中L置换经过下面公式得到:

其中a是128位的,经过线性反馈移位寄存器转16拍,其中转换的定义如下:

其中ai是8位,a16是经过Ⅰ运算得到的,Ⅰ运算的定义如下:

I(a15,…,a0)=∇(148·Δ(a15)+32·Δ(a14)

+133·Δ(a13)+16·Δ(a12)+194·Δ(a11)

+192·Δ(a10)+1·Δ(a9)+251·Δ(a8)

+1·Δ(a7)+192·Δ(a6)+194·Δ(a5)+16·Δ(a4))

+133·Δ(a3)+32·Δ(a2)+148·Δ(a1)+1·Δ(a0))

I运算的输入是16个8位二进制数,输出为1个8位二进制数。表示将8位二进制数转换成有限域内的元素,▽是它的逆运算。经过有限域内的乘法运算,然后相加,最后转换成1个8位二进制输出。计算有限域乘法,首先要确定GF(2)上的8次不可约多项式。在该算法中,这个不可约多项式为

在加密算法中,明文块a为128比特,密钥k1,…,k10分别为128位,经过10轮运算,分别使用密钥k1,…,k10。加密算法定义如下:

具体加密流程如图2所示:

图2 加密流程图

3 基于Java线程池的Kuznyechik算法设计

Kuznyechik要进行10轮循环加密,因为根据算法的特点,事先进行密钥编排,得到10个轮密钥进行循环加密。根据加密算法的特点,本文设计了适用于Kuznyechik算法加密的线程池。首先创建任务实体,要加密数据的输入和输出保存到任务实体的属性中。

public class encryptionData{

public String inputData;

public String outputData;

public String getInputData(){

return inputData;

}

public String getOutputData(){

return outputData;

}

public void setInputData(String inputData) {

this.inputData=inputData;

}

public void setOutputData(String outputData) {

this.outputData=outputData;

}

在主线程中,首先创建队列和工作线程,然后创建任务实体,通过set方法给实体的属性赋值,然后将加密数据对象实体加入到任务队列中,最后不断循环检测工作线程的队列是否为空,如果为空,说明数据已经加密完成,线程池销毁[3-4]。在主线程工作的同时,工作线程不断从任务队列中取出任务实体,获取要加密的内容,不断进行加密,加密结束存储密文,当工作线程将所有任务实体处理完后,队列为空,线程池销毁。具体运行流程如图3所示。

图3 Java线程池的kuznyechik算法流程图

4 算法实现

根据算法原理和加密流程,本文采用128bit的密钥,需要加密10轮。首先需要设计线程池管理类,该类内部成员包括线程池实体、任务队列和工作线程实体,线程池实体使用单例模式来设计,并加入同步锁保证线程池对象的唯一[5]。任务队列用来存放加密实体对象,工作线程不断从任务队列中取出任务实体,从任务实体中获取需要加密的明文,然后调用加密方法进行轮加密运算。以下是线程池管理类的主要代码:

public class ThreadPool{

private static ThreadPool instance=null;

public List<encryptionData> taskQueue = Collections.synchronizedList (new LinkedList<encryptionData>());//任务队列

//工作线程(真正执行任务的线程)

private WorkThread workQueue;

private ThreadPool(){

workQueue=new WorkThread();

}

public static synchronized ThreadPool getInstance(){

if(instance==null)

instance=new ThreadPool();

return instance;

}

public void addTask(encryptionData task){

//对任务队列的操作上锁

synchronized(taskQueue){

if(task!=null){

taskQueue.add(task);

taskQueue.notifyAll();

}

}

}

public void destory(){

workQueue.stopThread();

workQueue=null;

//对任务队列的操作上锁

synchronized(taskQueue){

taskQueue.clear();

}

}

工作线程的主要代码如下:

public void run(){

while(isRuning){

encryptionData temp=null;

//对任务队列的操作要上锁

synchronized(taskQueue){

//任务队列为空,等待新的任务加入

while(isRuning&&taskQueue.isEmpty()){

try{

taskQueue.wait(10);

}catch(InterruptedException e){

e.printStackTrace();

}

}

i( fisRuning)

temp=taskQueue.remove(0);

}

i(ftemp!=null){

isWaiting=false;

//执行加密函数

Encryption.encresult(temp.getInputData());

isWaiting=true;

}

}

在加密函数中,主要通过明文与密钥相加运算,然后通过S盒和L运算,得到密文,其中,S盒和L运算的主要代码如下:

进s盒运算,使用以下方法来实现,其中s盒的长度为256,返回的结果是s盒的输出。

public static char[] sbox(char x[]){

for(int i=0;i<16;i++){

a[i]=(char)x[i];

c[i]= (char) s[a[i]];

}

return c;

}

L运算主要进行了16次R运算,主要代码如下:

for(int i=0;i<16;i++){

char e= ltrans(k11);

for(int j=0;j<15;j++){

k11[j]=k11[j+1];

}

k11[15] =e;

}

其中,ltrans表示l运算。在l运算中,主要用到的是GF(2^8)有限域内乘法,这个域的最大值为256。有限域内的乘法使用以下的方法实现:

public static char multiply(char a, char b){

char temp[]={a,0x00,0x00,0x00,0x00,0x00,0x00,0x00};

char tempmultiply=0x00;

int i=0;

for(i=1;i< 8;i++){

temp[i] =XTIME(temp[i-1]);

}

tempmultiply=(char)((b&0x01)*a);

for(i=1;i<=7;i++){

tempmultiply^=(((b >> i)&0x01)*temp[i]);

}

return tempmultiply;

}

在二进制中,所有的数都能用0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80异或得到,任何一个数b和a进行相乘,都可以表示为b和0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80分别相乘,然后异或得到。只要计算出b和这几个数的乘积,一切结果都可以得到。XTIME方法的含义是求一个数与0x02的乘积,如果求一个数乘以2,一般是左移一位,本算法中,不可约多项式为p(x)=x8+x7+x6+x+1,如果最高位为1,再继续左移会超出最大值,这时,左移1位的同时要异或0xc3。XTIME的具体实现代码如下:

public static char XTIME(char x) {

int k=0x00;

if((x&0x80)!=0)

k=0xc3;

return(char)((((x << 1)^k))&0xff);

}

经过8次temp[i]=XTIME(temp[i-1])运算,temp数组中包含8个字符,分别为:a*0x01,a*0x02,a*0x04,a*0x08,a*0x10,a*0x20,a*0x40,a*0x80。然后经过8次tempmulti⁃ply^=(((b >> i)&0x01)*temp[i])运算,另一个乘数b首先进行1位右移,再和0x01与运算,然后分别和这8个字符相乘,把相乘的结果进行相加,得到a和b的乘积。

5 算法验证

本文使用Java语句,在eclipse平台进行测试,硬件采用Intel(R)CoreI-5(TM)i5-8400(主频2.8GHZ,6核)32G内存的台式机作为平台进行测试。测试用到了加速比,未采用线程池和采用线程池程序加密算法的运行时间分别记为τa和τb,经过测试,结果如下表1所示:

表1 加密验证结果

测试表明,在数据量比较少时,采用线程池比未采用线程池加密效率略高,但是随着数据量的增大,采用线程池明显比不采用线程池加密效率明显增高。随着数据量的增加,加速比逐渐变陡。下面是原始的测试数据,图4-图7:

图4 1k数据加密示意图

图5 10k数据加密示意图

图6 100k数据加密示意图

图7 1000k数据加密示意图

6 结束语

本文通过设计线程池,基于Java线程池技术,对Kuznyechik算法进行了改进,通过eclipse平台进行测试,测试结果表明,随着数据量的增大,能够明显增加加密的效率,缩短加密时间,为大数据环境下的加密奠定了基础。在未来的研究中,将通过GPU的并行计算,来进一步提高加密效率。

猜你喜欢
加密算法线程队列
基于DES加密算法的改进研究
基于C#线程实验探究
队列里的小秘密
基于多队列切换的SDN拥塞控制*
基于国产化环境的线程池模型研究与实现
线程池调度对服务器性能影响的研究*
DES加密算法的实现
基于整数矩阵乘法的图像加密算法
在队列里
丰田加速驶入自动驾驶队列