我必须做几件事来正确地得到消息:将KP_MODE明确设置为CRYPT_MODE_CBC ,将KP_MODE设置为0
在Java解密中使用NoPadding
不要反转密钥或消息的字节
在诊断问题方面,最有用的建议是在Java中设置NoPadding以防止BadPaddingException 。 这让我看到了结果 - 即使是错误的。
奇怪的是,RSA Java / CryptoAPI互操作解决方案要求消息完全按字节反转才能使用Java,但AES不希望密钥或消息是字节反转的。
CryptSetKeyParam不会让我使用ZERO_PADDING,但是当查看解密的字节时,很明显CryptoAPI会填充未使用的字节数。 例如,如果块大小为16,如果最后一个块仅使用9个字节,则剩余的5个字节将获得0x05的值。 这是否存在潜在的安全漏洞? 我应该用随机字节填充所有其他字节,并仅使用最后一个字节来表示使用了多少填充?
下面是工作代码(使用最后一个字节的填充计数的CryptoAPI约定)(为了简单起见,已经删除了来自Crypt的返回值的检查):// 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();
}