I am writing a chat app with end-to-end encryption and have spent at least a day on this one problem. I am using react-native-rsa native and am having a problem with message decryption that I can't seem to find documented anywhere.
Upon screen load, a useEffect call is used, and this calls a function that registers a callback to the 'child_added' event on the correct Firebase child. The callback in turn runs a function that decrypts messages retrieved from Firebase.
There are two different ways in which this callback is run, as per the Firebase documentation. One way is upon the page load, where the callback is run once for each existing child in the chosen location of the Firebase tree. Then the function runs once each time a new child appears.
What makes this problem so confusing is that the decryptor only fails in the former case. When the screen loads, the initial runs of the function (with all correct data being passed in) causes the RSAKeychain.decrypt(text, keyTag); to throw an IllegalBlockSizeException with no other message provided. I understand that this kind of problem should not even be applicable to RSA encryption.
My code is as follows. First the use of useEffect is in place in the functional component, which causes Fire.get to be run once:
const ConvoScreen = ({route, navigation}) => {
const { recipientId, title } = route.params;
useEffect(() => {
Fire.get(recipientId, message => {
return setMessages((previous) => (GiftedChat.append(previous, message)))
});
return () => {
Fire.off(recipientId);
}
}, []);
...
Then, in Fire.get, the on 'child_added' event listener is added:
get = (fromId, callback) => {
this.getConvo(fromId).on('child_added', async (snapshot) => {
const callbackWith = await this.parse(snapshot);
callback(callbackWith);
});
};
This event triggers the parse function to be run. The catch block here is what's reached ONLY on the initial triggers of 'child_added' that run for the existing children in the tree:
parse = async message => {
try {
const {key: _id} = message;
const {user, timestamp, text, signature} = message.val();
const createdAt = new Date(timestamp);
// IllegalBlockSizeException thrown from native code on this line
// But only on the initial triggers of 'child_added' and not subsequent triggers
const decryptedText = await RSAKeychain.decrypt(text, keyTag);
return {
_id,
createdAt,
text: decryptedText,
user
};
} catch (err) {
console.log("PARSE ERROR ENCOUNTERED!");
console.log(err.message);
// The message is always "Error not specified."
}
};
When the exact same data gets passed into the snapshot variable, the decryption works. Any help or even suggestions for investigation would be greatly appreciated. I have exhausted my abilities.
Edit: I managed to get out some stack trace info:
LOG javax.crypto.IllegalBlockSizeException
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:519)
at javax.crypto.Cipher.doFinal(Cipher.java:1741)
at com.RNRSA.RSA.decrypt(RSA.java:148)
at com.RNRSA.RSA.decrypt(RSA.java:156)
at com.RNRSA.RNRSAKeychainModule$5.run(RNRSAKeychainModule.java:128)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
Caused by: android.security.KeyStoreException: Unknown error
at android.security.KeyStore.getKeyStoreException(KeyStore.java:697)
at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:224)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:506)
... 8 more