话不多说,直接上代码。下面.Net代码返回的是GvACpmgPkvPrRgt5afp82w==
using System;
using System.Security.Cryptography;
using System.Text;
protected static string EncryptToBase64String()
{
string encryptStr = "12345678", string strKey = "1234567812345678123456781234567812345678";
byte[] keyArray = UTF8Encoding.UTF8.GetBytes(strKey);
byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(encryptStr);
RijndaelManaged rDel = new RijndaelManaged();
rDel.Key = keyArray;
rDel.Mode = CipherMode.ECB;
rDel.Padding = PaddingMode.PKCS7;
ICryptoTransform cTransform = rDel.CreateEncryptor();
byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
return Convert.ToBase64String(resultArray, 0, resultArray.Length);
}
在AES在线解密 AES在线加密 Aes online hex 十六进制密钥 - The X 在线工具 ,选择GB2312/ECB/PKCS7/输出BASE64,显示也是GvACpmgPkvPrRgt5afp82w==
新建test.c,代码如下
#include <tomcrypt.h>
void dump_bytes(unsigned char *in, unsigned long len)
{
unsigned long idx;
for(idx=0; idx<len; idx++)
printf("%02hhX", *(in+idx));
}
int main(int argc, char *argv[]) {
symmetric_key sk;
const unsigned char *strIn = "12345678";
const unsigned char *strKey = "12345678123456781234567812345678";
unsigned char strOut[16];
unsigned char strTest[16];
unsigned char strBase64[256];
zeromem(strOut, sizeof(strOut));
rijndael_setup(strKey, 32, 0, &sk);
rijndael_ecb_encrypt(strIn, strOut, &sk);
unsigned long nLen = sizeof(strBase64);
base64_encode(strOut, 16, strBase64, &nLen);
printf(strBase64);
printf("\n");
printf("%d", nLen);
printf("\n");
rijndael_ecb_decrypt(strOut, strTest, &sk);
printf(strTest);
}
执行
gcc -I ../src/headers -L ../ -o test test.c -ltomcrypt
验证的结果却是tuE8VyU8IDK8IkBzNPeneQ==
阅读了AES的加密方式,AES能加密的数据,都是采用标准长度,也称为块。
在AES标准规范中,分组长度只能是128位,也就是说,每个分组为16个字节(每个字节8位)。
AES | 密钥长度 | 分组长度 | 加密轮数 |
---|---|---|---|
AES-128 | 128 | 128 | 10 |
AES-192 | 192 | 128 | 11 |
AES-256 | 256 | 128 | 14 |
这是因为如果明文不是128位(16字节)的则需要进行填充,需要将明文补充到16个字节整数倍的长度。在我们进行加解密时需要采用同样的填充方式,否则无法解密成功。填充模式有:No Padding、PKCS5 Padding、PKCS7 Padding、ISO10126 Padding、Ansix923 Padding、Zero Padding等等。
1、No Padding
不需要填充,但是明文必须是16个字节的整数倍。如果不是,那么就需要使用者自己去实现填充。除了这种模式外,其他的填充模式如果已经是16个字节的数据的话,会在填充一个16字节的数据。
2、PKCS5 Padding(C#上不支持此方式)
在明文的末尾进行填充,填充的数据时当前和16个字节相差的数量。
这里明文长度明显不足16个字节,那么就需要进行填充。明文长度为14,则填充字节数为2,16进制为02
明文:70 61 73 73 77 6f 72 64 54 65 78 74 43 61
填充后:70 61 73 73 77 6f 72 64 54 65 78 74 43 61 02 02
3、PKCS7 Padding
在明文的末尾进行填充,填充的数据时当前和16个字节相差的数量。
这里明文长度为14,则填充字节数为2,16进制为02
明文:70 61 73 73 77 6f 72 64 54 65 78 74 43 61
填充后:70 61 73 73 77 6f 72 64 54 65 78 74 43 61 02 02
4、ISO10126 Padding
在明文的末尾进行填充,当前和16个字节相差的数量填写在最后,其余字节填充随机数。
当前和16个字节相差的数量写在最后,所以填充后倒数第二字节填充随机数,最后一个字节是与16个字节相差的数量。
明文:70 61 73 73 77 6f 72 64 54 65 78 74 43 61
填充后:70 61 73 73 77 6f 72 64 54 65 78 74 43 61 01 02
5、Ansix923 Padding
最后一个字节填充字节序列的长度,其余字节均填充数字0。
明文:70 61 73 73 77 6f 72 64 54 65 78 74 43 61
填充后:70 61 73 73 77 6f 72 64 54 65 78 74 43 61 00 02
6、Zero padding
后面补0,缺多少就补多少个0
明文:70 61 73 73 77 6f 72 64 54 65 78 74 43 61
填充后:明文:70 61 73 73 77 6f 72 64 54 65 78 74 43 61 00 00
除了No padding这种填充模式,其余的填充模式,如果已经是16个字节的数据,会在填充一个16字节的数据.
明文:70 61 73 73 77 6f 72 64 54 65 78 74 43 61 73 65
填充后:{70 61 73 73 77 6f 72 64 54 65 78 74 43 61 73 65 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00}
除了No padding这种填充模式,其余的填充模式,如果已经是16个字节的数据,会在填充一个16字节的数据.
这个非常关键。
由于OpenSSL默认的填充方式是PKCS7 Padding,我们首先测试PKCS7 Padding填充,12345678一共占用了8个字符,剩下的8个字符全部填充为16-8=8。
#include <tomcrypt.h>
void dump_bytes(unsigned char *in, unsigned long len)
{
unsigned long idx;
for(idx=0; idx<len; idx++)
printf("%02hhX", *(in+idx));
}
int main(int argc, char *argv[]) {
symmetric_key sk;
char strIn[16] = "12345678";
for(int i = 8;i<16;i++){
strIn[i] = 8;
}
for(int i = 0;i<16;i++){
printf("%d is %d\n", i, strIn[i]);
}
printf("\n");
const unsigned char *strKey = "12345678123456781234567812345678";
unsigned char strOut[16];
unsigned char strTest[32];
unsigned char strBase64[256];
zeromem(strOut, sizeof(strOut));
rijndael_setup(strKey, 32, 0, &sk);
rijndael_ecb_encrypt(strIn, strOut, &sk);
unsigned long nLen = sizeof(strBase64);
base64_encode(strOut, 16, strBase64, &nLen);
printf(strBase64);
printf("\n");
printf("%d", nLen);
printf("\n");
rijndael_ecb_decrypt(strOut, strTest, &sk);
printf(strTest);
}
这时候编译的结果就跟OpenSSL的结果一样了。
至此,困扰多时的不同平台的加密不一致的问题也得到了解决。
吐槽一下,感觉这些国外的程序员真的很无语。。
作为一个AES加密算法,对于其他调用这算法的程序员来说不需要操心那么多吧。。
参数传入字符串、字符串长度、密码、密码长度、加密方式(ECB/CBC等等)、填充方式、IV,传几个参数进去,然后丢出来一个字符串结果回来不香吗。。
LibTomCrypt已经很好了,大家试试WInCrypt来实现一段AES加密看看。。百度搜索一天的资料,都不一定能写的出来能跟OpenSSL匹配的代码。
还是VisualFreeBasic比较适合我们国人的程序员,嘎嘎,每个函数都写的那个掏心窝子的舒服。
必须给勇芳赞一个。