Недопустимый размер ключа AES после расшифровки RSA

Я пытаюсь расшифровать сообщение, зашифрованное с использованием симметричного шифрования AES-128, а затем асимметричного шифрования RSA-1024 на сгенерированном симметричном ключе. Я получаю зашифрованный ключ AES и зашифрованное сообщение, извлекаю закрытый ключ из файла pfx, а затем приступаю к расшифровке симметричного ключа. После этого я пытаюсь расшифровать зашифрованное сообщение с помощью расшифрованного ключа AES.

Ниже приведен мой код:

  // Get the private key
  PrivateKey privateKey = (PrivateKey) keyStore.getKey(selectedAlias, "password".toCharArray());
  System.out.println("Key information " + privateKey.getAlgorithm() + " " + privateKey.getFormat());

  // Load aesSessionKey and encryptedMessage
  byte[] aesSessionKey = ...
  byte[] encryptedMessage = ...

  // RSA Decryption of Encrypted Symmetric AES key - 128 bits
  Cipher rsaCipher = Cipher.getInstance("RSA", "BC");
  rsaCipher.init(Cipher.UNWRAP_MODE, privateKey);
  Key decryptedKey = rsaCipher.unwrap(aesSessionKey, "AES", Cipher.SECRET_KEY);
  System.out.println("Decrypted Key Length: " + decryptedKey.getEncoded().length);

  SecretKeySpec decrypskeySpec = new SecretKeySpec(decryptedKey.getEncoded(), "AES");
  Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING", "BC");
  cipher.init(Cipher.DECRYPT_MODE, decryptedKey, new IvParameterSpec(new byte[16]));
  byte[] message = cipher.doFinal(encryptedMessage);
  System.out.println(new String(message, "UTF-8"));

Проблема в том, что размер расшифрованного ключа AES составляет 128 байт, а не 16 байт, как я ожидал. Я получаю следующее исключение:

Key information RSA PKCS#8
Decrypted Key Length: 128
java.security.InvalidKeyException: Key length not 128/192/256 bits.
at org.bouncycastle.jce.provider.JCEBlockCipher.engineInit(Unknown Source)
at javax.crypto.Cipher.init(DashoA13*..)
at javax.crypto.Cipher.init(DashoA13*..)
at com.simarks.services.PKCS12.run(PKCS12.java:74)
at com.simarks.services.PKCS12.main(PKCS12.java:34)

Я новичок в криптографии Java. Я проверил множество других вопросов и попробовал несколько разных подходов (например, использование DECRYPT_MODE вместо UNWRAP_MODE), но получаю ту же ошибку. Любая помощь будет высоко ценится.


EDIT: Клиентский код, шифрующий сообщение, таков:

PBYTE          pInputData = NULL;
DWORD          dwInputSize = 0;
PBYTE          pCertData = NULL;
DWORD          dwCertSize = 0;
PCCERT_CONTEXT pCertContext = NULL;
HCRYPTPROV     hCryptProv = NULL;
HCRYPTKEY      hPublicKey = NULL;
HCRYPTKEY      hSessionKey = NULL;
BYTE           InitializationVector[ 32 ] = { 0 };
DWORD          PKCS5Padding = PKCS5_PADDING;
DWORD          CBCMode = CRYPT_MODE_CBC;
PSIMPLEBLOB    pKeyBlob = NULL;
DWORD          dwBlobSize = 0;
DWORD          dwKeySize = 0;
PBYTE          pEncryptedData = NULL;
DWORD          dwEncryptedDataSize = 0;
HRESULT        hr = S_FALSE;

 if( FAILED( hr = ReadBinaryFile( InputFile, &pInputData, &dwInputSize ) ) ) goto EncryptExit;
 if( FAILED( hr = ReadBinaryFile( CertFile, &pCertData, &dwCertSize ) ) ) goto EncryptExit;
 if( ( pCertContext = CertCreateCertificateContext( PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, pCertData, dwCertSize ) ) == NULL ) goto EncryptExit;
 if( !CryptAcquireContext( &hCryptProv, NULL, GetMsAesProviderName(), PROV_RSA_AES, 0 ) ) goto EncryptExit;
 if( !CryptImportPublicKeyInfo( hCryptProv, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, &pCertContext->pCertInfo->SubjectPublicKeyInfo, &hPublicKey ) ) goto EncryptExit;

 if( !CryptGenKey( hCryptProv, AlgId, CRYPT_EXPORTABLE, &hSessionKey ) ) goto EncryptExit;
 if( !CryptSetKeyParam( hSessionKey, KP_IV, InitializationVector, 0 ) ) goto EncryptExit;
 if( !CryptSetKeyParam( hSessionKey, KP_PADDING, (PBYTE)&PKCS5Padding, 0 ) ) goto EncryptExit;
 if( !CryptSetKeyParam( hSessionKey, KP_MODE, (PBYTE)&CBCMode, 0 ) ) goto EncryptExit;

 if( !CryptExportKey( hSessionKey, hPublicKey, SIMPLEBLOB, 0, NULL, &dwBlobSize ) ) goto EncryptExit;
 if( ( pKeyBlob = (PSIMPLEBLOB)malloc( dwBlobSize ) ) == NULL ) { hr = E_OUTOFMEMORY; goto EncryptExit; }
 if( !CryptExportKey( hSessionKey, hPublicKey, SIMPLEBLOB, 0, (PBYTE)pKeyBlob, &dwBlobSize ) ) goto EncryptExit;
 dwKeySize = dwBlobSize - sizeof( BLOBHEADER ) - sizeof( ALG_ID );

 dwEncryptedDataSize = dwInputSize;
 if( !CryptEncrypt( hSessionKey, NULL, TRUE, 0, NULL, &dwEncryptedDataSize, 0 ) ) goto EncryptExit;
 if( ( pEncryptedData = (PBYTE)malloc( dwEncryptedDataSize ) ) == NULL ) { hr = E_OUTOFMEMORY; goto EncryptExit; }
 CopyMemory( pEncryptedData, pInputData, dwInputSize );
 if( !CryptEncrypt( hSessionKey, NULL, TRUE, 0, pEncryptedData, &dwInputSize, dwEncryptedDataSize ) ) goto EncryptExit;

 if( FAILED( hr = WriteBinaryFile( OutputFile, pEncryptedData, dwInputSize ) ) ) goto EncryptExit;

 hr = WriteBinaryFile( KeyFile, pKeyBlob->Key, dwKeySize );

EncryptExit:

person Eduardo Sanchez-Ros    schedule 18.06.2013    source источник
comment
Не могли бы вы попробовать расшифровать с помощью Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC") вместо использования значений по умолчанию для "RSA"? Кстати, для такого рода функций вы также можете попробовать поставщиков Oracle по умолчанию (поэтому попробуйте также удалить аргумент "BC". Также распечатайте тип возвращаемого ключа.   -  person Maarten Bodewes    schedule 19.06.2013
comment
Использование безопасного заполнения, предпочтительно OAEP, а не PKCS#1v1.5, необходимо для безопасности RSA. Вы действительно должны исправить прокладку.   -  person CodesInChaos    schedule 21.06.2013


Ответы (3)


AES использует 128-битный битный ключ, длина которого составляет 16 байтов, так как один байт соответствует восьми битам.

1024-битный RSA шифрует 1024 бита (или 128 байтов) данных. Вы всегда должны использовать дополнение с RSA, так как это повышает безопасность и позволяет вам шифровать произвольный объем данных.

Также обратите внимание, что 1024-битный RSA довольно слаб против атак грубой силы. Минимум считается 2048 бит.

Кроме того, режим CBC со статическим IV просто напрашивается на неприятности.

person ntoskrnl    schedule 19.06.2013
comment
Этот ответ представляет собой просто набор утверждений о ключах AES и шифровании RSA. Как это решает исходную проблему? - person Duncan Jones; 19.06.2013
comment
Я думаю, что в первом абзаце достаточно ясно сказано, в чем проблема. Второй абзац объясняет, как решить проблему. Два последних абзаца указывают на другие криптографические проблемы в коде. - person ntoskrnl; 19.06.2013
comment
Не уверен, что ты пытаешься сказать @ntoskrnl. Я знаю, что ключ AES, который я расшифровываю, должен иметь длину 128 бит (16 байтов), но вместо этого я получаю 128-байтовый ключ. Я понимаю вашу точку зрения о слабости RSA-1024. - person Eduardo Sanchez-Ros; 19.06.2013
comment
Вы получаете 128 байт, потому что именно столько данных шифрует 1024-битный RSA. Если вы хотите зашифровать другой объем данных, вы должны использовать заполнение. - person ntoskrnl; 19.06.2013

В отличие от многих других провайдеров, провайдер BouncyCastle не использует заполнение по умолчанию, когда вы указываете алгоритм "RSA". Согласно часто задаваемым вопросам, провайдер BC сопоставит "RSA" с "RSA/NONE/NoPadding".

В результате ваши расшифрованные данные по-прежнему имеют заполнение и, таким образом, имеют длину 128 байтов. Вам необходимо определить, какое заполнение использовалось при шифровании вашего ключа, и убедитесь, что вы указали это явно при создании экземпляра Cipher. Например:

Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC")

Как owlstead упомянутый в комментариях, вы также можете рассмотреть возможность использования стандартных поставщиков Oracle для этого кода, если хотите — кажется, что BouncyCastle не нужен.

person Duncan Jones    schedule 19.06.2013
comment
Я пытался использовать Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC"), но получаю следующее исключение для rsaCipher.doFinal: Key information RSA PKCS#8 javax.crypto.BadPaddingException: unknown block type at org.bouncycastle.jce.provider.JCERSACipher.engineDoFinal(Unknown Source) at javax.crypto.Cipher.doFinal(Cipher.java:1978) at com.simarks.services.PKCS12.run(PKCS12.java:73) at com.simarks.services.PKCS12.main(PKCS12.java:33) - person Eduardo Sanchez-Ros; 19.06.2013
comment
Также я пробовал со стандартными поставщиками Oracle и получаю это исключение: Key information RSA PKCS#8 javax.crypto.BadPaddingException: Data must start with zero at sun.security.rsa.RSAPadding.unpadV15(RSAPadding.java:325) at sun.security.rsa.RSAPadding.unpad(RSAPadding.java:272) at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:357) at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:383) at javax.crypto.Cipher.doFinal(Cipher.java:1978) at com.simarks.services.PKCS12.run(PKCS12.java:71) at com.simarks.services.PKCS12.main(PKCS12.java:31) - person Eduardo Sanchez-Ros; 19.06.2013
comment
@spaniard Похоже, заполнение PKCS # 1 не использовалось в исходном шифровании. У вас есть исходный код, который зашифровал ключ AES? Если нет, то какое лучшее описание того, как ключ был зашифрован, вам дали? - person Duncan Jones; 19.06.2013
comment
Просто добавил код клиента в вопрос. Надеюсь это поможет решить проблему - person Eduardo Sanchez-Ros; 19.06.2013

После некоторых исследований я решил эту проблему, поменяв местами байты aesSessionKey:

org.apache.commons.lang.ArrayUtils.reverse(encryptedSessionKey);

По-видимому, структуры CryptoAPI обычно представляют данные в порядке с обратным порядком байтов, но Java (и .NET) использует порядок байтов с прямым порядком байтов. Вот несколько ссылок, где я нашел решение:

person Eduardo Sanchez-Ros    schedule 20.06.2013