Debugging Concurrency Issues in Objective‑C‑RSA Library on iOS: Keychain Tag Collisions and Solutions
This article examines how the widely used Objective‑C‑RSA library can cause RSA encryption failures on iOS when multiple threads concurrently add, delete, and query keychain entries using the same tag, leading to error‑25300 and data corruption, and proposes practical fixes.
RSA is a reliable asymmetric encryption algorithm that is extensively used in software development. The article shares a bug‑investigation experience from a 58 iOS app project, focusing on the Objective‑C‑RSA open‑source library, which many third‑party SDKs (e.g., AMap, Kuaishou, Tencent, etc.) also depend on.
During heavy concurrent usage, generating different RSA public keys with the library caused a >30% chance of key conflicts, resulting in empty encryption results or data that could not be decrypted. The root cause was identified as a thread‑unsafe combination of Keychain APIs (SecItemDelete, SecItemAdd, SecItemCopyMatching) when used together in multiple threads.
The specific error observed was -25300 ("The specified item could not be found in the keychain"), which occurs when one thread reads a keychain entry that another thread has just deleted.
Apple’s documentation states that while most Keychain functions are thread‑safe, they should not be called concurrently from multiple operations; calls must be serialized or confined to a single thread.
Investigation of the + [RSA addPublicKey:] implementation revealed that it uses a constant tag RSAUtil_PubKey for all keychain operations. When multiple SDKs use the same tag, concurrent reads and writes lead to dirty reads and corrupted RSA data.
To verify the hypothesis, the team wrote a shell script that searches for the string RSAUtil_PubKey in compiled .framework and .a libraries. Sample output shows many popular SDKs containing the tag:
search_liba() {
dir=$1
for lib in $(find $dir -type f -name "*.a");
do
cnt=$(strings $lib | grep $keyword -wc)
if [[ $cnt -gt 0 ]]; then
echo "$lib -> $keyword($cnt)"
fi
done
}
search_framework() {
dir=$1
for lib in $(find $dir -type d -name "*.framework");
do
lib_name=${lib##*/}
lib_name_without_ext=${lib_name%.framework}
lib_full_name=$lib/$lib_name_without_ext
if [[ -e "$lib_full_name" ]]; then
cnt=$(strings $lib_full_name | grep $keyword -wc)
if [[ $cnt -gt 0 ]]; then
echo "$lib_full_name -> $keyword($cnt)"
fi
fi
done
}The script discovered that SDKs such as AMapFoundationKit, AnyThinkSDK, EAccountApiSDK, GT3Captcha, KSAdSDK, and many others embed the default tag, confirming the risk of cross‑SDK conflicts.
The recommended fix is to replace the constant tag with a unique identifier (e.g., a UUID) for each invocation of + [RSA addPublicKey:] , ensuring that keychain entries are isolated per thread and per SDK. Additionally, developers should serialize keychain operations or avoid calling the RSA encryption method from multiple threads simultaneously.
In summary, the concurrency‑induced dirty reads in the Objective‑C‑RSA library cause RSA encryption failures, and the solution lies in using unique tags, improving thread safety, and adding thorough concurrent test cases.
58 Tech
Official tech channel of 58, a platform for tech innovation, sharing, and communication.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.