无法解密使用openssl AES_ctr128_encrypt加密的文件

时间:2011-07-20 15:20:05

标签: java openssl aes

我在c:

中使用以下代码加密了文件
unsigned char ckey[] =  "0123456789ABCDEF"; 
unsigned char iv[8] = {0};
AES_set_encrypt_key(ckey, 128, &key);
AES_ctr128_encrypt(indata, outdata, 16, &key, aesstate.ivec, aesstate.ecount, &aesstate.num);

我必须使用java解密此文件,所以我使用下面的代码来执行此操作:

private static final byte[] encryptionKey = new byte[]{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };

byte[] iv = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

IvParameterSpec ips = new IvParameterSpec(iv);
Cipher aesCipher = Cipher.getInstance("AES/CTR/NoPadding");
SecretKeySpec aeskeySpec = new SecretKeySpec(encryptionKey, "AES");
aesCipher.init(Cipher.DECRYPT_MODE, aeskeySpec, ips);
FileInputStream is = new FileInputStream(in);
CipherOutputStream os = new CipherOutputStream(new FileOutputStream(out), aesCipher);       
copy(is, os);       
os.close();

JAVA代码没有给我任何错误,但输出不正确。

我做错了什么?

我的主要疑问是,如果我使用正确的填充(也尝试了PKCS5Padding但没有成功),如果密钥和iv是正确的(不知道AES_set_encrypt_key真正做什么......)。

**编辑**

我想我对自己的问题有了答案,但我仍然有些疑惑。

CTR表示计数器模式。 AES_ctr128_encrypt函数接收实际计数器(ecount)和使用的块数(num)作为参数。

该文件以16字节为单位加密,如下所示:

for(int i = 0; i < length; i+=16)
{
   // .. buffer processing here
   init_ctr(&aesstate, iv); //Counter call
   AES_ctr128_encrypt(indata, outdata, 16, &key, aesstate.ivec, aesstate.ecount, &aesstate.num);
}

函数init_ctr执行此操作:

int init_ctr(struct ctr_state *state, const unsigned char iv[8])
{
    state->num = 0;
    memset(state->ecount, 0, 16);
    memset(state->ivec + 8, 0, 8);
    memcpy(state->ivec, iv, 8);
    return 0;
}

这意味着在每次加密/解密之前,C代码都会重置计数器和ivec。

我试图在java中解密整个文件。这可能意味着Java正确使用计数器,但C代码不是因为它在每个块重置计数器。

我的调查是否正确?

我完全没有控制调用openssl的C代码。有没有办法在JAVA中做同样的事情,即在每个16块重置计数器? (API仅请求密钥,算法,模式和IV)

我唯一的另一个选择是通过JNI使用openssl,但我试图避免它......

谢谢!

3 个答案:

答案 0 :(得分:4)

我没有尝试过,但你应该能够有效地模拟在C端做的事情 - 分别解密每个16字节(= 128位)的块,并在两次调用之间重置密码。


请注意,使用CTR模式只有一个块,初始化矢量和计数器为零,无法实现CTR模式的目标 - ECB 更差。

如果我看到这一点,你可以尝试使用你的C函数(或等效的Java版本)加密一些零块 - 这些应该每次都作为相同的块出现。使用任何密文对此块进行异或,以恢复您的明文。

这相当于128位字母表上的凯撒密码(例如16字节的块),块密码在这里没有为简单的128位XOR密码增加安全性。猜测一个明文块(或者更一般地说,在正确的位置猜测128位,而不是在同一个块中都是必需的)允许获得有效密钥,这允许获得所有剩余的明文块。

答案 1 :(得分:2)

您的加密密钥不同。

C代码使用0F的ASCII字符代码,而Javacode使用实际字节016

答案 2 :(得分:0)

C代码存在许多严重问题:

  • 如前所述,它正在重新启动每个街区的计数器。这使得加密完全不安全。这可以通过在加密第一个块之前仅调用init_ctr()一次来修复。
  • 将IV静态设置为零。应该随机生成新的IV,例如if (!RAND_bytes(iv, 8)) { /* handle error */ }
  • 代码似乎是直接使用密码字符串作为密钥。相反,应使用密钥派生函数(如PBKDF2(由PKCS5_PBKDF2_HMAC_SHA1()在OpenSSL中实现)从密码生成密钥。