无法解密使用 openssl AES_ctr128_encrypt 加密的文件
我在 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,但我试图避免它......
谢谢!
I have a file encrypted using the following code in 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);
I have to decrypt this file using java so I was using the code below to do it:
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();
The JAVA code doesn't give me any error but the output is not correct.
What am I doing wrong?
My main doubts are if i'm using the correct padding (also tried PKCS5Padding without success) and if the key and iv are correct (don't know what the function AES_set_encrypt_key really does...).
** EDIT **
I think I have an answer to my own question, but I still have some doubts.
CTR means counter mode. The function AES_ctr128_encrypt receives as parameters the actual counter (ecount) and the number of blocks used (num).
The file is being encrypted in blocks of 16 bytes, like this:
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);
}
the function init_ctr does this:
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;
}
This means that before every encryption/decryption the C code is resetting the counter and the ivec.
I am trying to decrypt the file as a whole in java. This probably means Java is using the counter correctly but the C code is not as it is resetting the counter at each block.
Is my investigation correct?
I have absolutely NO CONTROL over the C code that is calling openssl. Is there a way of doing the same in JAVA, i.e. resetting the counter at each block of 16? (The API only requests the key, algorithm, mode and IV)
My only other option is to use openssl via JNI but I was trying to avoid it...
Thank you!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我没有尝试过,但您应该能够有效地模拟 C 端所做的事情 - 分别解密每个 16 字节(= 128 位)块,并在两次调用之间重置密码。
请注意,仅对一个块使用 CTR 模式,初始化向量和计数器为零,这违背了 CTR 模式的目标 - 它比 ECB。
如果我没看错的话,您可以尝试使用 C 函数(或等效的 Java 版本)加密一些零块 - 这些应该每次都以相同的块形式出现。将此块与任何密文进行异或以恢复明文。
这相当于 128 位字母表上的凯撒密码(例如 16 字节块),块密码没有为简单的 128 位 XOR 密码增加任何安全性。猜测一个明文块(或者更一般地说,猜测正确位置的 128 位,不一定都在同一个块中)可以获取有效密钥,从而可以获取所有剩余的明文块。
I did not try it, but you should be able to effectively emulate what is done there on the C side - decrypt each 16-byte (=128 bit) block separately, and reset the cipher between two calls.
Please note that using CTR mode for just one block, with a zero initialization vector and counter, defeats the goal of CTR mode - it is worse than ECB.
If I see this right, you could try to encrypt some blocks of zeros with your C function (or the equivalent Java version) - these should come out as the same block each time. XOR this block with any ciphertext to get your plaintext back.
This is the equivalent to a Caesar cipher on a 128-bit alphabet (e.g. the 16-byte blocks), the block cipher adds no security here to a simple 128-bit XOR cipher. Guessing one block of plaintext (or more generally, guessing 128 bits at the right positions, not necessary all in the same block) allows getting the effective key, which allows getting all the remaining plaintext blocks.
您的加密密钥不同。
C 代码使用
0
到F
的 ASCII 字符代码,而 Javacode 使用实际字节0
到16
>。Your encryption keys are different.
The C code uses the ASCII character codes for
0
throughF
, whereas the Javacode uses the actual bytes0
through16
.该 C 代码存在许多严重问题:
init_ctr()
来解决此问题。if (!RAND_bytes(iv, 8)) { /* handle error */ }
。PKCS5_PBKDF2_HMAC_SHA1()
实现)等密钥派生函数从密码生成密钥。There are numerous serious problems with that C code:
init_ctr()
once only, prior to encrypting the first block.if (!RAND_bytes(iv, 8)) { /* handle error */ }
.PKCS5_PBKDF2_HMAC_SHA1()
).