使用react-native-sodium与PHP sodium扩展时crypto_box_seal_open()解密失败问题排查
Let's break down the possible issues that could be causing your crypto_box_seal_open() decryption failure — I've run into similar cross-platform libsodium headaches before, so here's what to check step by step:
1. You're Missing the Critical Encryption Step in PHP
Looking at your PHP code, there's no sign of calling sodium_crypto_box_seal() to actually encrypt the notification content! This is a huge red flag. You need to:
- Convert the client's uploaded public key to binary (if the client sent it as base64, which it should)
- Encrypt your plaintext notification with that public key
- Encode the encrypted binary as base64 (since APNs payloads can't handle raw binary) and inject it into the push message
Here's a corrected PHP snippet:
// Assume $clientPublicKey is the base64-encoded key from your iOS app $binaryPublicKey = base64_decode($clientPublicKey); $plaintextContent = json_encode(['alert' => 'Your secure notification content']); // Encrypt using libsodium's seal mode $encryptedBinary = sodium_crypto_box_seal($plaintextContent, $binaryPublicKey); $encryptedBase64 = base64_encode($encryptedBinary); // Build the push message with the encrypted content $msg = CloudMessage::fromArray( [ "apns" => [ "payload" => [ "aps" => [ "sound" => 'default', "content-available" => 1 // Enable silent processing if needed ], "encrypted_content" => $encryptedBase64 // Use a custom field instead of notification.body ], ] ] ); sendMulticast($msg, $apn_tokens);
2. Your React Native Decoding Logic is Broken
Your current client code has two major encoding mistakes:
let note = JSON.parse(notification.notification.body.toString()) note = btoa(note)
- If PHP sends a base64 ciphertext,
JSON.parse()will fail (base64 isn't valid JSON) btoa(note)will turn an object into"[object Object]"— this is nothing like your actual encrypted data
Fix it by directly decoding the base64 ciphertext to binary data (which react-native-sodium expects):
const handleIncomingNotification = async (notification) => { // Don't forget async! try { // Grab the base64-encrypted content from the custom payload field const encryptedBase64 = notification.data.encrypted_content; // Decode base64 to Uint8Array (react-native-sodium's expected format) const ciphertext = Sodium.from_base64(encryptedBase64, Sodium.base64_variants.ORIGINAL); // Decrypt using your app's own public and private keys const plaintextBinary = await Sodium.crypto_box_seal_open(ciphertext, public_key, private_key); // Convert binary back to a readable string const decryptedMessage = Sodium.to_string(plaintextBinary); console.log('Decrypted content:', decryptedMessage); } catch(e) { console.log('Decryption failed:', e); } };
3. Key Format Mismatches Between Platforms
libsodium functions are strict about binary data — any encoding mismatch will break decryption:
- When uploading the public key: Always send it as base64 from React Native, not raw binary (HTTP can corrupt raw bytes). Update your key upload code:
const base64Pk = Sodium.to_base64(pk, Sodium.base64_variants.ORIGINAL); await sendToServer(base64Pk); - When storing keys locally: If you save
public_key/private_keyto AsyncStorage, store them as base64, then decode back to binary before use:// Example: Loading keys from storage const savedBase64Pk = await AsyncStorage.getItem('device_public_key'); const public_key = Sodium.from_base64(savedBase64Pk, Sodium.base64_variants.ORIGINAL); const savedBase64Sk = await AsyncStorage.getItem('device_private_key'); const private_key = Sodium.from_base64(savedBase64Sk, Sodium.base64_variants.ORIGINAL);
4. APNs Payload Gotchas
- Size limits: Standard APNs payloads are capped at 2KB. If your encrypted content is too large, it'll get truncated — keep plaintext messages short.
- Avoid
notification.body: Thenotificationfield is meant for user-visible text. Use a custom payload field (likeencrypted_contentin the PHP example above) to avoid unintended formatting or truncation by APNs.
5. Debugging Tips
Since the error comes directly from libsodium's native crypto_box_seal_open call, the issue is almost certainly data/key corruption. Add logging to verify:
- In PHP: Log the base64 public key you received and the base64 ciphertext you sent.
- In React Native: Log the base64 ciphertext you receive, plus base64 versions of your stored keys. Compare these to PHP's logs to ensure no data was altered in transit.
内容的提问来源于stack exchange,提问作者UnknownFrequency

