CryptoAPI C ++与Java使用AES进行互操作 [英] CryptoAPI C++ interop with Java using AES

查看:399
本文介绍了CryptoAPI C ++与Java使用AES进行互操作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图在C ++中使用CryptoAPI加密,并使用SunJCE解密Java。我已经得到RSA密钥工作 - 并验证测试字符串。但是,我的AES密钥不工作 - 我得到 javax.crypto.BadPaddingException:给定最后块未正确填充

I am trying to encrypt in C++ using CryptoAPI and decrypt Java using SunJCE. I have gotten the RSA key to work -- and verified on a test string. However, my AES key is not working -- I get javax.crypto.BadPaddingException: Given final block not properly padded.

C ++加密:

// init and gen key
HCRYPTPROV provider;
CryptAcquireContext(&provider, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_VERIFYCONTEXT);

// Use symmetric key encryption
HCRYPTKEY sessionKey;
DWORD exportKeyLen;
CryptGenKey(provider, CALG_AES_128, CRYPT_EXPORTABLE, &sessionKey);

// Export key
BYTE exportKey[1024];
CryptExportKey(sessionKey, NULL, PLAINTEXTKEYBLOB, 0, exportKey, &exportKeyLen);

// skip PLAINTEXTKEYBLOB header
//      { uint8_t bType, uint8_t version, uint16_t reserved, uint32_t aiKey, uint32_t keySize }
DWORD keySize =  *((DWORD*)(exportKey + 8));
BYTE * rawKey = exportKey + 12;

// reverse bytes for java
for (unsigned i=0; i<keySize/2; i++) {
    BYTE temp = rawKey[i];
    rawKey[i] = rawKey[keySize-i-1];
    rawKey[keySize-i-1] = temp;
}

// Encrypt message
BYTE encryptedMessage[1024];
const char * message = "Decryption Works";
BYTE messageLen = (BYTE)strlen(message);
memcpy(encryptedMessage, message, messageLen);
DWORD encryptedMessageLen = messageLen;
CryptEncrypt(sessionKey, NULL, TRUE, 0, encryptedMessage, &encryptedMessageLen, sizeof(encryptedMessage));

// reverse bytes for java
for (unsigned i=0; i<encryptedMessageLen/2; i++) {
    BYTE temp = encryptedMessage[i];
    encryptedMessage[i] = encryptedMessage[encryptedMessageLen - i - 1];
    encryptedMessage[encryptedMessageLen - i - 1] = temp;
}

BYTE byteEncryptedMessageLen = (BYTE)encryptedMessageLen;
FILE * f = fopen("test.aes", "wb");
fwrite(rawKey, 1, keySize, f);
fwrite(&byteEncryptedMessageLen, 1, sizeof(byteEncryptedMessageLen), f);
fwrite(encryptedMessage, 1, encryptedMessageLen, f);
fclose(f);

// destroy session key
CryptDestroyKey(sessionKey);
CryptReleaseContext(provider, 0);

Java解密:

try
{
    FileInputStream in = new FileInputStream("test.aes");
    DataInputStream dataIn = new DataInputStream(in);

    // stream key and message
    byte[] rawKey = new byte[16];
    dataIn.read(rawKey);
    byte encryptedMessageLen = dataIn.readByte();
    byte[] encryptedMessage = new byte[encryptedMessageLen];
    dataIn.read(encryptedMessage);

    // use CBC/PKCS5PADDING, with 0 IV -- default for Microsoft Base Cryptographic Provider
    SecretKeySpec sessionKey = new SecretKeySpec(rawKey, "AES");
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
    cipher.init(Cipher.DECRYPT_MODE, sessionKey, new IvParameterSpec(new byte[16]));

    cipher.doFinal(encryptedMessage);
}
catch (Exception e) {
  e.printStackTrace();
}

在类似的例子中,我尝试了不颠倒密钥字节的排列并且不反转消息中的字节。如果我用java中的导入密钥加密和解密,我得到有效的结果。我也可以在C ++中专门加密和解密。

In a similar example I have tried permutations of not reversing the bytes of the key and not reversing bytes in the message. If I encrypt and decrypt with the imported key in java, I get valid results. I can also encrypt and decrypt exclusively in C++.

问题:


  1. 我使用CBC / PKCS5PADDING?这是 MS_ENH_RSA_AES_PROV 的默认值吗?

  2. 是零的IV确实是 MS_ENH_RSA_AES_PROV

  3. 有没有任何方法来诊断关键字行为的细节?

  4. 我想坚持使用标准Java而不是安装BouncyCastle,但是有什么不同之处,会使第三方包更好地工作吗?

  1. Should I use CBC/PKCS5PADDING? Is this the default for MS_ENH_RSA_AES_PROV?
  2. Is a zeroed IV indeed the default for MS_ENH_RSA_AES_PROV?
  3. Are there any ways to diagnose the specifics of how the key is behaving?
  4. I'd like to stick with standard Java packages instead of installing BouncyCastle, but are there any differences that would make a 3rd party package work better?


推荐答案

我必须做几件事才能正确得到信息:

I had to do several things to get the message correctly:


  1. 明确设置 KP_MODE CRYPT_MODE_CBC KP_IV 0

  2. 不要使用 NoPadding 反转键或消息的字节

  1. Explicitly set KP_MODE to CRYPT_MODE_CBC, and KP_IV to 0
  2. Use NoPadding in Java decryption
  3. Don't reverse the bytes for the key or the message

在诊断问题方面,最有用的建议是在Java中设置NoPadding这可以防止 BadPaddingException 。这使我可以看到结果 - 即使是错误的。

In terms of diagnosing the problem the most useful piece of advice was to set NoPadding in Java which prevents the BadPaddingException. This allowed me to see the results -- even if wrong.

奇怪的是,RSA Java / CryptoAPI互操作解决方案要求消息完全字节反转,以便使用Java,但是AES不认为密钥或消息是字节颠倒的。

Strangely, the RSA Java/CryptoAPI interop solution requires the message to be completely byte reversed in order to work with Java, but AES does not expect the key or the message to be byte reversed.

CryptSetKeyParam不允许我使用ZERO_PADDING,但是当查看解密的字节时,清除CryptoAPI填充的未使用字节数。例如,在块大小为16的情况下,如果最后一个块仅使用9个字节,则剩余的5个字节获得值0x05。这是否存在潜在的安全漏洞?我应该用随机字节填充所有其他字节,并且只使用最后一个字节来表示使用多少填充?

CryptSetKeyParam would not let me use ZERO_PADDING, but when looking at the decrypted bytes, it is clear that CryptoAPI fills with the number of unused bytes. For instance, with a block size of 16, if the last block only uses 9 bytes, then the remaining 5 bytes get the value of 0x05. Does this present a potential security leak? Should I pad all other bytes with random bytes and use only the last byte to signify how much padding is used?

工作代码(使用最后一个字节的CryptoAPI约定为了简化,检查Crypt的返回值已被删除):

The working code (using the CryptoAPI convention of last byte being pad count) is below (checking of return values from Crypt have been removed for simplicity):

// init and gen key
HCRYPTPROV provider;
CryptAcquireContext(&provider, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_VERIFYCONTEXT);

// Use symmetric key encryption
HCRYPTKEY sessionKey;
DWORD exportKeyLen;
BYTE iv[32];
memset(iv, 0, sizeof(iv));
DWORD padding = PKCS5_PADDING;
DWORD mode = CRYPT_MODE_CBC;
CryptGenKey(provider, CALG_AES_128, CRYPT_EXPORTABLE, &sessionKey);
CryptSetKeyParam(sessionKey, KP_IV, iv, 0);
CryptSetKeyParam(sessionKey, KP_PADDING, (BYTE*)&padding, 0);
CryptSetKeyParam(sessionKey, KP_MODE, (BYTE*)&mode, 0);

// Export key
BYTE exportKey[1024];
CryptExportKey(sessionKey, NULL, PLAINTEXTKEYBLOB, 0, exportKey, &exportKeyLen);

// skip PLAINTEXTKEYBLOB header
//      { uint8_t bType, uint8_t version, uint16_t reserved, uint32_t aiKey, uint32_t keySize }
DWORD keySize =  *((DWORD*)(exportKey + 8));
BYTE * rawKey = exportKey + 12;

// Encrypt message
BYTE encryptedMessage[1024];
const char * message = "Decryption Works -- using multiple blocks";
BYTE messageLen = (BYTE)strlen(message);
memcpy(encryptedMessage, message, messageLen);
DWORD encryptedMessageLen = messageLen;
CryptEncrypt(sessionKey, NULL, TRUE, 0, encryptedMessage, &encryptedMessageLen, sizeof(encryptedMessage));

BYTE byteEncryptedMessageLen = (BYTE)encryptedMessageLen;
FILE * f = fopen("test.aes", "wb");
fwrite(rawKey, 1, keySize, f);
fwrite(&byteEncryptedMessageLen, 1, sizeof(byteEncryptedMessageLen), f);
fwrite(encryptedMessage, 1, encryptedMessageLen, f);
fclose(f);

// destroy session key
CryptDestroyKey(sessionKey);
CryptReleaseContext(provider, 0);

Java解密:

try
{
    FileInputStream in = new FileInputStream("test.aes");
    DataInputStream dataIn = new DataInputStream(in);

    // stream key and message
    byte[] rawKey = new byte[16];
    dataIn.read(rawKey);
    byte encryptedMessageLen = dataIn.readByte();
    byte[] encryptedMessage = new byte[encryptedMessageLen];
    dataIn.read(encryptedMessage);

    // use CBC/NoPadding, with 0 IV -- (each message is creating it's own session key, so zero IV is ok)
    SecretKeySpec sessionKey = new SecretKeySpec(rawKey, "AES");
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
    cipher.init(Cipher.DECRYPT_MODE, sessionKey, new IvParameterSpec(new byte[16]));

    byte[] decryptedBlocks = cipher.doFinal(encryptedMessage);

    // check versus expected message
    byte[] expectedBytes = "Decryption Works -- using multiple blocks".getBytes();
    Assert.assertTrue("Incorrect Message" + new String(message), Arrays.equals(message, expectedBytes));
}
catch (Exception e) {
  e.printStackTrace();
}

这篇关于CryptoAPI C ++与Java使用AES进行互操作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆