Java расшифровывает зашифрованные данные RSA ArrayIndexOutOfBoundsException: слишком много данных для блока RSA
Я шифрую некоторые данные на веб-сервере Phoenix:
private_key = ExPublicKey.load!("private.pem")
token = %{username: user.username, mobile_phone: user.mobile_phone, email: user.email}
payload = Poison.encode!(token)
{:ok, signature} = ExPublicKey.encrypt_private(payload, private_key)
И расшифровываем его на клиенте Java (фактически Android) следующим образом:
try {
byte[] keyBytes = Base64.decode(Constants.RSA_PUBLIC_KEY.getBytes(), Base64.DEFAULT);
X509EncodedKeySpec encodedKeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(encodedKeySpec) ;
Cipher cipher = Cipher.getInstance("RSA") ;
cipher.init(Cipher.DECRYPT_MODE, publicKey) ;
//
Log.e(DEBUG_TAG, jwt) ; // received token
String payload = new String(Base64.decode(jwt, Base64.DEFAULT), "UTF-8") ; // java does UTF16, elixir does UTF8
Log.e(DEBUG_TAG, payload) ; // base64 decoded token
byte[] cipherText = cipher.doFinal(payload.getBytes("UTF-8")) ; // decrypt
String token = new String(Base64.decode(cipherText, Base64.URL_SAFE), "UTF-8") ; // cipher text is urlencoded
Log.e(DEBUG_TAG, token) ;
return null ;
} catch (Exception e) {
e.printStackTrace();
}
Нет никаких исключений на стороне Феникса, но попытка расшифровать токен на Java приводит к исключению:
java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block
at com.android.org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi.engineDoFinal(CipherSpi.java:459)
at javax.crypto.Cipher.doFinal(Cipher.java:1502
Если входное значение слишком велико для модуля RSA, это должно было привести к ошибке на веб-сервере. Поэтому мне интересно, что на самом деле не так.
ОБНОВЛЕНИЕ: кажется, что была проблема с библиотекой. Выходные данные, создаваемые подписанием дайджеста SHA256 для некоторых данных, возвращают 344 байта, тогда как предполагается, что для используемой длины ключа он должен быть 256 байтов. Вернулся к использованию Эрланга public_key
модуль и теперь он работает нормально.
3 ответа
Непонятна реальная цель, и это усложняет ситуацию, но если вы пытаетесь выдать веб-токены JSON, то, как вам кажется, ваша реализация совершенно неверна
JWT имеет цифровую подпись, а не шифруется
зашифровать секретным ключом!= цифровая подпись
вы "расшифровываете" весь токен вместо проверки подписи, которая должна быть последней частью веб-токена JSON, как это
hhhh.pppp.ssss
,
@zaph описал ошибку, но она не произойдет, если вы используете цифровую подпись. Невозможно исправить ваш код, поэтому подумайте о его повторной реализации
Подписание - это не то же самое, что шифрование с использованием закрытого ключа. Хотя оба будут использовать модульное возведение в степень с подписью частного экспонента, а шифрование использует разные методы заполнения. Больше информации здесь. По сути, вы не должны рассматривать хеширование и подписывание как отдельные операции: хеширование является частью генерации и проверки подписи.
Однако причина неудачи вашего кода в другом: подпись, вероятно, закодирована с использованием base64. Base64 сгенерирует выходной размер ceiling(256/3)×4
, Это, конечно, равняется 344 символам / байтам. Таким образом, сначала вам нужно будет декодировать результат, прежде чем расшифровать его.
Решением этой проблемы является использование гибридного шифрования. А именно, это включает использование RSA для асимметричного шифрования симметричного ключа.
Произвольно сгенерируйте ключ симметричного шифрования (скажем, AES) и зашифруйте его с помощью открытого текста. Затем зашифруйте симметричный ключ с помощью RSA. Передайте как симметрично зашифрованный текст, так и асимметрично зашифрованный симметричный ключ.
Затем получатель может расшифровать блок RSA, который выдаст симметричный ключ, позволяющий расшифровать симметрично зашифрованный текст.
Это может быть показано более формально следующим образом. Пусть MM будет открытым текстом, KAESKAES будет случайно выбранным ключом AES, а KPuKPu будет открытым ключом RSA получателя, который у вас уже есть.
C1=EAES(M,KAES)
C1=EAES(M,KAES)
C2=ERSA(KAES,KPu)
C2=ERSA(KAES,KPu)
Then, send both C1C1 and C2C2.
Пусть KPrKPr будет личным ключом RSA получателя. Получатель может затем восстановить MM как
KAES=DRSA(C2,KPr)
KAES=DRSA(C2,KPr)
M=DAES(C1,KAES)
M=DAES(C1,KAES)
(Чтобы разрешить потоковое дешифрование или большие сообщения, вы обычно отправляете сначала C2C2, а затем (намного больший) C1C1.)