【问题标题】:Can't decrypt file encrypted using openssl AES_ctr128_encrypt无法解密使用 openssl AES_ctr128_encrypt 加密的文件
【发布时间】:2011-07-20 15:20:05
【问题描述】:

我有一个在 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,但我试图避免它......

谢谢!

【问题讨论】:

  • 请注意:您可以将初始化向量写成更短的byte[] iv = new byte[16]。这在 C 和 Java 中不应该具有相同的长度吗? (或者你甚至在你的 C 代码中使用它?)
  • 谢谢保罗。使用的 IV 是 ivec,它是声明的 iv 的扩展(在 init_ctr 中完成)。最后,对于这个例子,它也将有 16 个字节的长度,全为零。
  • 哎哟。这有效地使用了 CTR 函数来模拟 ECB 模式。请点击编写此 C 程序的人。
  • (实际上,不是在模拟 ECB,而是在 128 位字母表上模拟凯撒密码。请参阅我的答案。)

标签: java openssl aes


【解决方案1】:

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


请注意,仅对一个块使用 CTR 模式,初始化向量和计数器为零,这违背了 CTR 模式的目标 - 它比 ECB 更糟糕

如果我没看错,您可以尝试使用您的 C 函数(或等效的 Java 版本)加密一些零块 - 这些每次都应该作为相同的块出现。将此块与任何密文异或以获取您的明文。

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

【讨论】:

  • 保罗,你是完全正确的。我还没有测试过它,但是 C 代码执行密码的方式是完全错误的。我会尝试看看我能在两端做些什么来保证集成,但我绝对希望这是 AES 而不是某种凯撒密码。
  • 补充一下,SLaks 的回答也是正确的,有两个问题:错误的密钥和错误的文件加密方式。
【解决方案2】:

您的加密密钥不同。

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

【讨论】:

  • 谢谢,这是我的疑惑之一,但 unsigned char ckey[] = "0123456789ABCDEF";移植到 java 不应该是 new byte[]{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; ?正确的移植应该是什么?
  • 字节不是字符。您需要将字符转换为其 ASCII 值。例如,'0'0x30
  • 谢谢,我试试看效果如何
  • 进行了更改,键将是 {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70} JAVA,但文件仍未正确解密:(还有其他想法吗?可能还有其他我做错的事情......
  • 不知道。尝试查看调试器中的所有相关值。
【解决方案3】:

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

  • 如前所述,它正在重新初始化每个块上的计数器。这使得加密完全不安全。在加密第一个块之前,只需调用一次init_ctr() 即可解决此问题。
  • 它将 IV 静态设置为零。应随机生成新的 IV,例如 if (!RAND_bytes(iv, 8)) { /* handle error */ }
  • 代码似乎直接使用密码字符串作为密钥。相反,应使用 PBKDF2 等密钥派生函数(由 PKCS5_PBKDF2_HMAC_SHA1() 在 OpenSSL 中实现)从密码生成密钥。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-04-15
    • 1970-01-01
    • 2018-12-25
    • 1970-01-01
    • 2012-12-04
    • 2012-04-07
    • 2010-11-03
    • 2016-05-04
    相关资源
    最近更新 更多