AES-256-CBC-PKCS5Padding用c语言实现,并支持Android手机的调用

决定今天记录下这一刻,折腾了4天终于给自己有了一个满意的答复了。

首先声明一下,以下所写的并不是深入研究AES算法,而是本人结合网络上高人写的文章,朋友的帮忙总结出了一套自认为更安全的支持Android的加密方式(不是原生的)。

公司给的需求是将密码和请求的URL地址采用AES加密,在网上找到了一位高人写的Object-c、C#、Java、Android都通用的版本,这里给上链接地址,通用AES加密版本,同时也直接附上附件,点击这里下载通用版本,在这里向原作者表示敬意!

可能大家都觉得奇怪,有现成的不用非要自己折腾用C语言来实现?这里我就要说下我的想法了,大家都知道Android中可以直接调用封装好的方法进行加密,但是这有个弊端,就是使用的密钥必须存储在Java代码中,大家都知道Java代码的安全性并不好,哪怕你打包混淆过别人都有办法看到里面的代码。后来我就放弃了把密钥(以下简称key)放在Java代码中的想法了。这个问题向我的好友@leepood也请教过,他给了我一些思路,然后我总结出了两套方案:1、key存储在c中,然后打包成so文件给Android调用;2、key存储在c中,Android端传递明文给c,然后通过c来加密并返回密文。后来决定使用第二种方案!这里要感谢下我的好友leepood。

然后就是接下来近4天的折腾了。参考了网上各种代码,c语言早忘光了(其实就是上学的时候没学好),看到什么char[],char* 脑袋立马就大了,但是没办法,还得硬着头皮去看。前前后后我算了下,至少尝试了网上提供的5套代码,没有一个是符合要求的,也就是快放弃的时候看到了pudn网站上提供的这套代码,没有积分的可以点击pudn-AES下载,这套代码注释很详细,以为看到希望了我经过一天多时间的测试发现不是代码的问题,而是这套代码完全不符合我的要求。下面我附上代码:

#include<stdio.h>
#include<string.h>

//xtime用于混合列变换
#define xtime(x) ((x<<1)^(((x>>7)&1)*0x1b))

//密钥的长度为128bit,轮数Nr为10
int Nk=4;
int Nr=10;
unsigned char RoundKey[240];//轮密钥
unsigned char Key[32];//主密钥

//S-盒
int sbox[256]= 
{
    0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
	0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
	0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
	0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
	0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
	0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
	0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
	0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
	0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
	0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
	0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
	0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
	0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
	0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
	0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
	0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16
};

int Rcon[255]= 
{
	0x8d,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36,0x6c,0xd8,0xab,0x4d,0x9a,
	0x2f,0x5e,0xbc,0x63,0xc6,0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91,0x39,
	0x72,0xe4,0xd3,0xbd,0x61,0xc2,0x9f,0x25,0x4a,0x94,0x33,0x66,0xcc,0x83,0x1d,0x3a,
	0x74,0xe8,0xcb,0x8d,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36,0x6c,0xd8,
	0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6,0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,
	0xc5,0x91,0x39,0x72,0xe4,0xd3,0xbd,0x61,0xc2,0x9f,0x25,0x4a,0x94,0x33,0x66,0xcc,
	0x83,0x1d,0x3a,0x74,0xe8,0xcb,0x8d,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,
	0x36,0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6,0x97,0x35,0x6a,0xd4,0xb3,
	0x7d,0xfa,0xef,0xc5,0x91,0x39,0x72,0xe4,0xd3,0xbd,0x61,0xc2,0x9f,0x25,0x4a,0x94,
	0x33,0x66,0xcc,0x83,0x1d,0x3a,0x74,0xe8,0xcb,0x8d,0x01,0x02,0x04,0x08,0x10,0x20,
	0x40,0x80,0x1b,0x36,0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6,0x97,0x35,
	0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91,0x39,0x72,0xe4,0xd3,0xbd,0x61,0xc2,0x9f,
	0x25,0x4a,0x94,0x33,0x66,0xcc,0x83,0x1d,0x3a,0x74,0xe8,0xcb,0x8d,0x01,0x02,0x04,
	0x08,0x10,0x20,0x40,0x80,0x1b,0x36,0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,
	0xc6,0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91,0x39,0x72,0xe4,0xd3,0xbd,
	0x61,0xc2,0x9f,0x25,0x4a,0x94,0x33,0x66,0xcc,0x83,0x1d,0x3a,0x74,0xe8,0xcb
};

//*in指向输入的明文,*out指向输出的密文,*key指向密钥
int aes_encrypt(const unsigned char *in,unsigned char *out,const unsigned char *key)
{
    int i,j,round=0;
    unsigned char temp[4]={0,0,0,0};
	unsigned char k;
    unsigned char state[4][4];

    //密钥扩展算法
    strcpy(RoundKey,key);//复制
    strcat(RoundKey,temp);//添加
    i=strlen(RoundKey)/4;

	//轮密钥有(分组长度)*(Nr+1)bit,是由密钥经过一个扩展算法完成的
	//密钥扩展算法中包括两个函数RotWord和SubWord
	while(i<(4*(Nr+1)))
    {
        //先将原始密钥保存
		for(j=0;j<4;j++)
        {
            temp[j]=RoundKey[(i-1)*4+j];
        }
        if(i%Nk==0)
        {
            //RotWord()是对输入的四个字节进行循环左移
            //即RotWord(a0,a1,a2,a3)=(a1,a2,a3,a0)
            {
				k=temp[0];
				temp[0]=temp[1];
				temp[1]=temp[2];
				temp[2]=temp[3];
				temp[3]=k;
            }

            //SubWord()对输入的四个字节分别使用S-盒替换操作 
            //Rcon[i]=(Rc[i],'00','00','00')
            {
                temp[0]=sbox[temp[0]];
                temp[1]=sbox[temp[1]];
                temp[2]=sbox[temp[2]];
                temp[3]=sbox[temp[3]];
            }
			temp[0]=temp[0]^Rcon[i/Nk];
        }
        else if(Nk>6&&i%Nk==4)
        {
            //Subword()
            {
                temp[0]=sbox[temp[0]];
                temp[1]=sbox[temp[1]];
                temp[2]=sbox[temp[2]];
                temp[3]=sbox[temp[3]];
            }
        }
		//扩展密钥的最前面Nk个字是直接由输入的密钥填充的
		//后面的每个字RoundKey[i]是由前面的字RoundKey[i-1]与Nk个位置前的RoundKey[Nk-1]进行异或得到的
		RoundKey[i*4+0]=RoundKey[(i-Nk)*4+0]^temp[0];
		RoundKey[i*4+1]=RoundKey[(i-Nk)*4+1]^temp[1];
		RoundKey[i*4+2]=RoundKey[(i-Nk)*4+2]^temp[2];
		RoundKey[i*4+3]=RoundKey[(i-Nk)*4+3]^temp[3];
        i++;
    }

	//将state初始化为明文M
    for(i=0;i<4;i++)
    {
        for(j=0;j<4;j++)
        {
            state[j][i]=in[i*4+j];
        }
    }

    //将轮密钥与state异或,AddRoundKey()
    for(i=0;i<4;i++)
    {
        for(j=0;j<4;j++)
        {
            state[j][i]^=RoundKey[round*16+i*4+j];
        }
    }

    //对前Nr-1轮中的每一轮用S-盒进行一次替换操作(SubBytes);
	//对替换的结果state做行移位操作(ShiftRows);
	//再对state做列混合变换(MixColums);
	//然后做与轮密钥异或操作(AddRoundKey)
	for(round=1;round<Nr;round++)
    {
        //SubBytes
		//字节替换操作使用一个S-盒对state的每个字节都进行独立的替换
        for(i=0;i<4;i++)
        {
            for(j=0;j<4;j++)
            {
				state[i][j]=sbox[state[i][j]];
            }
        }

        //ShiftRows
		//state的第一行保持不动
        //第二行循环左移一个字节
		k=state[1][0];
        state[1][0]=state[1][1];
        state[1][1]=state[1][2];
        state[1][2]=state[1][3];
        state[1][3]=k;
		//第三行循环左移两个字节
        k=state[2][0];
        state[2][0]=state[2][2];
        state[2][2]=k;
        k=state[2][1];
        state[2][1]=state[2][3];
        state[2][3]=k;
		//第四行循环左移三个字节
        k=state[3][0];
        state[3][0]=state[3][3];
        state[3][3]=state[3][2];
        state[3][2]=state[3][1];
        state[3][1]=k;

        //MixColumns
		//列混合变换对state中的每列进行独立操作
		//它把每一个列都看成GF(2^8)中的一个多项式s(x),在于GF(2^8)上的固定多项式a(x)={03}x^3+{01}x^2+{01}x+{02}进行x^4+1的乘法运算
        for(i=0;i<4;i++)
        {    
            k=state[0][i];
			temp[0]=state[0][i]^state[1][i]^state[2][i]^state[3][i];
			temp[1]=state[0][i]^state[1][i];
			temp[1]=xtime(temp[1]);
			state[0][i]^=temp[1]^temp[0];
			temp[1]=state[1][i]^state[2][i];
			temp[1]=xtime(temp[1]);
			state[1][i]^=temp[1]^temp[0];
			temp[1]=state[2][i]^state[3][i];
			temp[1]=xtime(temp[1]);
			state[2][i]^=temp[1]^temp[0];
			temp[1]=state[3][i]^k;
			temp[1]=xtime(temp[1]);
			state[3][i]^=temp[1]^temp[0];
        }

        //AddRoundKey
		//将轮密钥与state异或
        for(i=0;i<4;i++)
        {
            for(j=0;j<4;j++)
            {
                state[j][i]^=RoundKey[round*16+i*4+j];
            }
        }
    }

    //最后一轮加密算法
    //没有MixColumns
    //SubBytes
	//字节替换操作使用一个S-盒对state的每个字节都进行独立的替换
	for(i=0;i<4;i++)
	{
		for(j=0;j<4;j++)
		{
			state[i][j]=sbox[state[i][j]];
		}
	}

	//ShiftRows
	//state的第一行保持不动
	//第二行循环左移一个字节
    k=state[1][0];
    state[1][0]=state[1][1];
    state[1][1]=state[1][2];
    state[1][2]=state[1][3];
    state[1][3]=k;
	//第三行循环左移两个字节
    k=state[2][0];
    state[2][0]=state[2][2];
    state[2][2]=k;
	k=state[2][1];
    state[2][1]=state[2][3];
    state[2][3]=k;
	//第四行循环左移三个字节
    k=state[3][0];
    state[3][0]=state[3][3];
    state[3][3]=state[3][2];
    state[3][2]=state[3][1];
    state[3][1]=k;

    //AddRoundKey
	//将轮密钥与state异或
    for(i=0;i<4;i++)
    {
         for(j=0;j<4;j++)
         {
              state[j][i]^=RoundKey[Nr*16+i*4+j];
		 }
    }

    //将state里的最终结果复制到*out指向的区域
    for(i=0;i<4;i++)
    {
        for(j=0;j<4;j++)
        {
            out[i*4+j]=state[j][i];
        }
    }
	return i;
}

//主函数
void main()
{
    int i;
    unsigned char Key[17]={0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10,0x00};//密钥
    unsigned char M[16]={0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f};//明文
    unsigned char out[16];

    aes_encrypt(M,out,Key);//AES加密过程

	printf("密钥:\n");
    for(i=0;i<strlen(Key);i++)
        printf("%02x ",Key[i]);
    printf("\n");
	printf("明文:\n");
    for(i=0;i<Nk*4;i++)
        printf("%02x ",M[i]);
    printf("\n");
	printf("加密后的密文:\n");
    for(i=0;i<Nk*4;i++)
        printf("%02x ",out[i]);
    printf("\n");

}

为了能稍微看懂些这套代码,至少是为了能修改代码适应我的需求吧,无意中搜索到了这篇文章,这里是文章的地址,感兴趣的也可以下载,我这里也提供下载地址,点击AES加密介绍。由于目前C#端和IOS端采用的都是AES256并且是PKCS5Padding填充的方式,上面默认提供的是AES128方式的加密

根据上面的说明,修改代码Nk=8, Nr=14。然后测试代码,并对比Java原生的加密方式返回的字节流,一样的!!!Oh my god!皇天不负有心人啊!但高兴的还是太早了,输入的数据是16字节的还好,超过或不足16字节就是个坑啊!然后又看到上面pdf文章中写的这么一句话:

尼玛,这可怎么办!又是各种baidu,google,然后就看到了这篇本以为又遇到救星的文章,这里是文章地址。按文章中的说明:如果不足16字节的话我填充了0x06,我擦,还真可以。然后就是巨坑了,文章中说如果是16的倍数就填充0X16,而且也没说明如果大于16而不是16的倍数的情况。我就按他说的填充0X16!!!!怎么测试都跟Android端的字节流对不上。后来又换了几套代码还是不行!只能加密一组,后面填充16字节的数据怎么加密的都对不上。这套代码就这样废弃了,感兴趣的可以试下,代码应该是没问题的,后来发现(也就是我接下来要说的)填充的并不是0X16,而是0x10(十进制16),作者坑人不带商量的。后来找了一堆资料,有人说可以直接使用openssl中提供的AES加密方法,但是openssl这个开源c库太大了,我想单独把AES加密部分抠出来不太现实,关联的文件太多了,而且就一个小小的AES加密没必要用这么大的库。

又有放弃的念头了,然后就无所事事,萎靡不振,头昏脑胀的加了一个openssl的qq群,没抱任何希望的在里面发了条求助信息,过了一会一位好心的大哥回复了我,然后一番胡侃之后,在今天早上他给了我他写的代码。在这里真要感谢下这位大哥了!研究了下他的代码,然后他也指导了一番,终于在今天大功告成了!

大神提供的代码在这里下载。这套原生的支持输入3组(每组16个字节)明文数据就输出三组加密后的数据。而且没有填充功能,需要自己手动填充。说到填充这里需要补充几点知识,也是从网上看到的。如下:

算法/模式/填充                        16字节加密后数据长度         不满16字节加密后长度
AES/CBC/NoPadding                               16                                       不支持
AES/CBC/PKCS5Padding                        32                                          16
AES/CBC/ISO10126Padding                  32                                           16
AES/CFB/NoPadding                               16                                  原始数据长度
AES/CFB/PKCS5Padding                        32                                           16
AES/CFB/ISO10126Padding                  32                                            16
AES/ECB/NoPadding                               16                                         不支持
AES/ECB/PKCS5Padding                        32                                            16
AES/ECB/ISO10126Padding                   32                                           16
AES/OFB/NoPadding                               16                                   原始数据长度
AES/OFB/PKCS5Padding                        32                                            16
AES/OFB/ISO10126Padding                  32                                             16
AES/PCBC/NoPadding                             16                                        不支持
AES/PCBC/PKCS5Padding                      32                                             16
AES/PCBC/ISO10126Padding                32                                             16

可以看到,在原始数据长度为16的整数倍时,假如原始数据长度等于16*n,则使用NoPadding时加密后数据长度等于16*n,其它情况下加密数据长度等于16*(n+1)。在不足16的整数倍的情况下,假如原始数据长度等于16*n+m[其中m小于16],除了NoPadding填充之外的任何方式,加密数据长度都等于16*(n+1);NoPadding填充情况下,CBC、ECB和PCBC三种模式是不支持的,CFB、OFB两种模式下则加密数据长度等于原始数据长度。

说到填充在这里也要说明下,不能被网络上复制来复制去的文章忽悠了。

1、如果输入的数据不足16个字节,需要补齐(填充)到16个字节,比如:10个字节就补齐6个6,11个字节就补齐5个5,以此类推;
2、如果输入的数据是16的整数倍个字节,需要在数据后面填充16个0x10(也可以是10进制的16);
3、如果输入的数据大于16且不是16的倍数,你需要把字符串补齐到16位,比如:如果少4位,就补充4个4, 如果少5位就补充5个5,少n位,补充n个n。

结合大神提供的代码和他的指点,然后修改了他的源码,自己写了一套适合在Android端使用的加密方式,可以输入任何长度的数据都可以返回。

源码下载。

转载请注明出处:http://www.longdw.com/aes-256-cbc-c-android-pkcs5padding/

《AES-256-CBC-PKCS5Padding用c语言实现,并支持Android手机的调用》上有7条评论

  1. 楼主,padding 的代码有 bug。
    按照你的说法,不足 16 字节情况下,需将要补的字节数量填充到填充位,例如 11 个字节的要填充 5 个 0x05。
    但你的代码写死了填充 6, input[i] = 0x06 ,与 java 输出的结果也不符….

评论已关闭。