Мне пришлось сделать несколько вещей, чтобы правильно получить сообщение:
- Явно установить
KP_MODE
на CRYPT_MODE_CBC
и KP_IV
на 0
- Использовать
NoPadding
в Java для расшифровки - Не инвертировать байты для ключа или сообщения
С точки зрения диагностики проблемы самый полезный советдолжен был установить NoPadding в Java, который предотвращает 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();
}