jerson / react-native-fast-rsa

RSA for react native made with golang for fast performance
https://www.npmjs.com/package/react-native-fast-rsa
MIT License
35 stars 11 forks source link

response on .decryptOAEP() padded with nonsense #56

Closed rmacd closed 11 months ago

rmacd commented 1 year ago

v2.3.6 RN 4.10.0

when provided an input, the output is unexpectedly longer (and incorrect), but decryption does not appear to fail. Cipher selection: RSA/ECB/OAEPWithSHA-256AndMGF1Padding

encrypted input (b64):

QK5pP83ZaaHF+HOr8XAZ6kXyFvSCQWEKlGgEdEQbCvpEa4rq57poO38+gBYXOtrsvTtxOKAbW96Fgybr
VRdrW95IkyqUN121FuiHBCkhSUNav188KYKqmZEj4Kf+V7h9pljtQrcxaVNp9nOSfGyO70v18ATOuSlU
EP0QiJVsynnuf7t+SQTNuSHqSjAnoQcu9/KsZFakfRSETg6Z1FuNeex3hkOhRl2K3fVhhZsQsEAW1KKr
P2ONHEtoSlV09CWpaTlx9nEnmNovh/1FAuHZ7+Nu2UqQKKT+rpa8kaij2Ks7Fb87uhZRnA8gU+APcMnH
Bfca+GHMvMX7jJQuOujSiA==

key (RSA-2048):

MIIEpAIBAAKCAQEAqINTkDRjJ3dtq28jbWJaUhIWN4OKaLj50D6V0bDkspXDgviLf2ZvEfmg6DlrplVl
4156lDojylFvpuNDEC9YItcuVD43095mvmho/LOypLcwx1RKN6etY5RYX1YZC0si9qsDMgElZUrTEwXe
EYaYLS5VG+uOixxa94f1seEy9usLd8ISMzcb0sEvurZTx6hBwE61R9slJTlL7paWvJ6vbnaUEDyo7uLN
qsyl0158wfHRFJJ9itA689Yk3cEZL50IBMpShmu7flqxXbKQGSu8Vcw1zLIrSruvUuUD+w2SZVY3Kj3W
9bQm+OW6DY2llB7/mTApo2yNoS3mS8hOeZhVIQIDAQABAoIBAAdqii+vQore3G5+TGiYusFgZuZoDVwZ
AfOKgZTyLEp4wVs+/YKsmyhHHHAD8Osn2H1Wb94cpe0WQuvtFgs9DiuwXStbdqEGKW7pUO+IKA1MO6MW
IFMNN9oJUg9WDnGbC62f7prRhpgL4Di22iRa0FEyuA+rwQD5zT4WF3fN4ayQrrmQaiLAYw0QvYZQDl7J
K4ocQbVjgB7cHmPjB8G/zFKZhHwgLrYwDEnL7mz5f2t2aMLrURovc1Opnwgk14SqdUGpJ1XMPE6TIzxa
TPdqncGBpwTTspr5iJqvriYW2GjAT72HIXy3t3BpBOJt4P5A7D0p9jTHJQRY+1QFgcr9Ec0CgYEA5J2H
sYV1HPKoolvYhvFYXkTLTqDn9rKnHJP4MxTTQcSstjCgzAuDN7TlrXo5csNC6t7AElO22wPWwiEL9dM/
O5VEQ0V/3+veABp6dIRh+TTKR6miNQ9UNrUjCdhrOW40FX23oEZIFMpstId8/yA81oXVenda//uxhHDe
1nJac/UCgYEAvLLDRNxPLr4ICQcfPiw/kplgUDSn0z0vIZLl9bQ3Q0pX9eoF5gqxORbViBmZzzfsz2oo
9LN3aaV9bUaarINmWTH0slm+vLk2b2eyCGPlulS9RhNgmrrCazdqssWu1yBMIxBdvRmxEP6GZdpROn01
4cuZDFi/zaQ+TXwiFzdLTP0CgYAEVukoeXwLnJ+O1Wd6yEIBKBUj3PIKQMHjTPu9HHwWF4Gfw7SJqv5G
pGxnqpZEk2hFxQyoTGaAKcZ90NrFQ8lDfEXbcQpIWdXQ8q+4XnrtnA7q5VFq6GuUzkNoAG+om2rprYU6
yZq8qkr98kRxI0+EUu4GcRWNHl30QaA5Odp1sQKBgQCK+e4vbUM0Xel1HLW6CMTZp/TznZRtVAa+Z37O
s+hvuvWFvNKTVxSnw1WJY7GQmNPk/38imns8aBI0xWdt32kmEFD0enysaozZCDprS4gK8BZm4iaoTxyZ
8rq26DmZX8Qznv4rJBzxM0SxB1YECewBXP5fxY2eW3U3hFFnX+Yp0QKBgQDbY41wFkswdz9BnzRF25eM
lBQnAjbXnO93FWWtexPXzLmqXJOt4lgF2YTv6YlosS5zrAGhaDHCZTiPrajqILeheS4RmYyzm6UvI2w6
QJ9NbxR/6plxTdCuhuL8JTBIakjRNAWWQa2l71rDvOeMvkh8ES00We9PRLTyG3iMruasEA==

expected output (b64):

lhbURqJZxnDN8W7X6G3GB2msDEP0wJlwnKSu1l/Ddxc=

can be checked via:

cat data | base64 -d | openssl pkeyutl -inkey key \
    -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -decrypt | base64

error seen when calling:

RSA.decryptOAEP(input, '', Hash.SHA256, privateKey)
        .then((res: string) => {
            console.debug(`res_length:${res.length}`);
            console.debug(`res_b64:${Buffer.from(res).toString('base64')}`);
        });

gives output:

 DEBUG  res_length:23
 DEBUG  res_b64:wpYW1IbColnGsM2xbteobcaHacKsDEP0gJmwwpzCpMKu1p/Dtxc=

but at byte level, the outputs are very similar:

01 [correct]: 96 16 d4 46 a2 59 c6 70 cd f1 6e d7 e8 6d c6 07 69 ac 0c 43 f4 c0 99 70 9c a4 ae d6 5f c3 77 17
02 [incorrect]: c2 96 16 d4 86 c2 a2 59 c6 b0 cd b1 6e d7 a8 6d c6 87 69 c2 ac 0c 43 f4 80 99 b0 c2 9c c2 a4 c2 ae d6 9f c3 b7 17

(byte-diff in cyberchef)

wondering if it's an encoding issue creeping in somewhere?

rmacd commented 1 year ago

Update

Have run via the native rsa-mobile library (decrypt_oaep_test.go) and am not able to reproduce; the output from the test is correct ... so this may very well be nothing to do with the library itself. Will go back through with a toothcomb tomorrow.

in decrypt_oaep_test.go:19 (see https://github.com/rmacd/jerson-rsa-mobile/commit/e781c0f376b4d89f1bdbd5ddac2af5036e5f0eaf)

var testCipher_gh56 = `QK5pP83ZaaHF+HOr8XAZ6kXyFvSCQWEKlGgEdEQbCvpEa4rq57poO38+gBYXOtrsvTtxOKAbW96FgybrVRdrW95IkyqUN121FuiHBCkhSUNav188KYKqmZEj4Kf+V7h9pljtQrcxaVNp9nOSfGyO70v18ATOuSlUEP0QiJVsynnuf7t+SQTNuSHqSjAnoQcu9/KsZFakfRSETg6Z1FuNeex3hkOhRl2K3fVhhZsQsEAW1KKrP2ONHEtoSlV09CWpaTlx9nEnmNovh/1FAuHZ7+Nu2UqQKKT+rpa8kaij2Ks7Fb87uhZRnA8gU+APcMnHBfca+GHMvMX7jJQuOujSiA==`
var testKey_gh56 = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAqINTkDRjJ3dtq28jbWJaUhIWN4OKaLj50D6V0bDkspXDgviLf2ZvEfmg6DlrplVl4156lDojylFvpuNDEC9YItcuVD43095mvmho/LOypLcwx1RKN6etY5RYX1YZC0si9qsDMgElZUrTEwXeEYaYLS5VG+uOixxa94f1seEy9usLd8ISMzcb0sEvurZTx6hBwE61R9slJTlL7paWvJ6vbnaUEDyo7uLNqsyl0158wfHRFJJ9itA689Yk3cEZL50IBMpShmu7flqxXbKQGSu8Vcw1zLIrSruvUuUD+w2SZVY3Kj3W9bQm+OW6DY2llB7/mTApo2yNoS3mS8hOeZhVIQIDAQABAoIBAAdqii+vQore3G5+TGiYusFgZuZoDVwZAfOKgZTyLEp4wVs+/YKsmyhHHHAD8Osn2H1Wb94cpe0WQuvtFgs9DiuwXStbdqEGKW7pUO+IKA1MO6MWIFMNN9oJUg9WDnGbC62f7prRhpgL4Di22iRa0FEyuA+rwQD5zT4WF3fN4ayQrrmQaiLAYw0QvYZQDl7JK4ocQbVjgB7cHmPjB8G/zFKZhHwgLrYwDEnL7mz5f2t2aMLrURovc1Opnwgk14SqdUGpJ1XMPE6TIzxaTPdqncGBpwTTspr5iJqvriYW2GjAT72HIXy3t3BpBOJt4P5A7D0p9jTHJQRY+1QFgcr9Ec0CgYEA5J2HsYV1HPKoolvYhvFYXkTLTqDn9rKnHJP4MxTTQcSstjCgzAuDN7TlrXo5csNC6t7AElO22wPWwiEL9dM/O5VEQ0V/3+veABp6dIRh+TTKR6miNQ9UNrUjCdhrOW40FX23oEZIFMpstId8/yA81oXVenda//uxhHDe1nJac/UCgYEAvLLDRNxPLr4ICQcfPiw/kplgUDSn0z0vIZLl9bQ3Q0pX9eoF5gqxORbViBmZzzfsz2oo9LN3aaV9bUaarINmWTH0slm+vLk2b2eyCGPlulS9RhNgmrrCazdqssWu1yBMIxBdvRmxEP6GZdpROn014cuZDFi/zaQ+TXwiFzdLTP0CgYAEVukoeXwLnJ+O1Wd6yEIBKBUj3PIKQMHjTPu9HHwWF4Gfw7SJqv5GpGxnqpZEk2hFxQyoTGaAKcZ90NrFQ8lDfEXbcQpIWdXQ8q+4XnrtnA7q5VFq6GuUzkNoAG+om2rprYU6yZq8qkr98kRxI0+EUu4GcRWNHl30QaA5Odp1sQKBgQCK+e4vbUM0Xel1HLW6CMTZp/TznZRtVAa+Z37Os+hvuvWFvNKTVxSnw1WJY7GQmNPk/38imns8aBI0xWdt32kmEFD0enysaozZCDprS4gK8BZm4iaoTxyZ8rq26DmZX8Qznv4rJBzxM0SxB1YECewBXP5fxY2eW3U3hFFnX+Yp0QKBgQDbY41wFkswdz9BnzRF25eMlBQnAjbXnO93FWWtexPXzLmqXJOt4lgF2YTv6YlosS5zrAGhaDHCZTiPrajqILeheS4RmYyzm6UvI2w6QJ9NbxR/6plxTdCuhuL8JTBIakjRNAWWQa2l71rDvOeMvkh8ES00We9PRLTyG3iMruasEA==\n-----END RSA PRIVATE KEY-----"

func TestFastRSA_DecryptOAEP_02(t *testing.T) {
    instance := NewFastRSA()
    output, err := instance.DecryptOAEP(testCipher_gh56, "", "sha256", testKey_gh56)
    if err != nil {
        t.Fatal(err)
    }
    fmt.Printf("%x\n", output)
}

output:

=== RUN   TestFastRSA_DecryptOAEP_02
9616d446a259c670cdf16ed7e86dc60769ac0c43f4c099709ca4aed65fc37717
--- PASS: TestFastRSA_DecryptOAEP_02 (0.00s)
PASS

This is the CORRECT output (ie matches what we get out of openssl) - so it seems like the error is occurring somewhere between the native library and me accessing the value of the string in RN.

jerson commented 1 year ago

Hi, could you try enabling JSI and then running the decrypt


RSA.useJSI = true;

RSA.decryptOAEP(input, '', Hash.SHA256, privateKey)
        .then((res: string) => {
            console.debug(`res_length:${res.length}`);
            console.debug(`res_b64:${Buffer.from(res).toString('base64')}`);
        });

which library are you using to support Buffer.from() some libraries have different implementation depending on debug or non debug mode (because of JSI)

rmacd commented 1 year ago

Hmm, sadly same output when JSI is enabled.

Regarding the Buffer library ... the length of res remains incorrect at 23 rather than 32 (prior to it being fed to Buffer)

 LOG  [FastRSA] (decryptOAEP) JSI install: Installed
 DEBUG  res_length:23
 DEBUG  res_b64:wpYW1IbColnGsM2xbteobcaHacKsDEP0gJmwwpzCpMKu1p/Dtxc=

reinstalling pods:

% pod install --repo-update
Auto-linking React Native modules for target: RNCAsyncStorage, RNScreens, RNVectorIcons, react-native-aes, react-native-background-actions, react-native-encrypted-storage, react-native-fast-rsa, react-native-netinfo, react-native-render-html, react-native-safe-area-context, and react-native-version-number
[Codegen] Generating ./build/generated/ios/React-Codegen.podspec.json
Updating local specs repositories
Analyzing dependencies
Fetching podspec for `DoubleConversion` from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`
[Codegen] Found FBReactNativeSpec
Fetching podspec for `RCT-Folly` from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`
Fetching podspec for `boost` from `../node_modules/react-native/third-party-podspecs/boost.podspec`
Fetching podspec for `glog` from `../node_modules/react-native/third-party-podspecs/glog.podspec`
Fetching podspec for `hermes-engine` from `../node_modules/react-native/sdks/hermes/hermes-engine.podspec`
Downloading dependencies
Installing CocoaAsyncSocket (7.6.5)
Installing DoubleConversion (1.1.6)
Installing FBLazyVector (0.70.1)
Installing FBReactNativeSpec (0.70.1)
Installing Flipper (0.125.0)
Installing Flipper-Boost-iOSX (1.76.0.1.11)
Installing Flipper-DoubleConversion (3.2.0.1)
Installing Flipper-Fmt (7.1.7)
Installing Flipper-Folly (2.6.10)
Installing Flipper-Glog (0.5.0.5)
Installing Flipper-PeerTalk (0.0.4)
Installing Flipper-RSocket (1.4.3)
Installing FlipperKit (0.125.0)
Installing OpenSSL-Universal (1.1.1100)
Installing RCT-Folly (2021.07.22.00)
Installing RCTRequired (0.70.1)
Installing RCTTypeSafety (0.70.1)
Installing RNCAsyncStorage (1.17.10)
Installing RNScreens (3.17.0)
Installing RNVectorIcons (9.2.0)
Installing React (0.70.1)
Installing React-Codegen (0.70.1)
Installing React-Core (0.70.1)
Installing React-CoreModules (0.70.1)
Installing React-RCTActionSheet (0.70.1)
Installing React-RCTAnimation (0.70.1)
Installing React-RCTBlob (0.70.1)
Installing React-RCTImage (0.70.1)
Installing React-RCTLinking (0.70.1)
Installing React-RCTNetwork (0.70.1)
Installing React-RCTSettings (0.70.1)
Installing React-RCTText (0.70.1)
Installing React-RCTVibration (0.70.1)
Installing React-bridging (0.70.1)
Installing React-callinvoker (0.70.1)
Installing React-cxxreact (0.70.1)
Installing React-hermes (0.70.1)
Installing React-jsi (0.70.1)
Installing React-jsiexecutor (0.70.1)
Installing React-jsinspector (0.70.1)
Installing React-logger (0.70.1)
Installing React-perflogger (0.70.1)
Installing React-runtimeexecutor (0.70.1)
Installing ReactCommon (0.70.1)
Installing SocketRocket (0.6.0)
Installing Yoga (1.14.0)
Installing YogaKit (1.18.1)
Installing boost (1.76.0)
Installing fmt (6.2.1)
Installing glog (0.3.5)
Installing hermes-engine (0.70.1)
Installing libevent (2.1.12)
Installing react-native-aes (2.0.0)
Installing react-native-background-actions (2.6.7)
Installing react-native-encrypted-storage (4.0.2)
Installing react-native-fast-rsa (2.3.6)
Installing react-native-netinfo (9.3.1)
Installing react-native-render-html (6.3.4)
Installing react-native-safe-area-context (4.3.3)
Installing react-native-version-number (0.3.6)
Generating Pods project
Setting REACT_NATIVE build settings
Setting CLANG_CXX_LANGUAGE_STANDARD to c++17

Have created a standalone TS RN project that reproduces the error: https://github.com/rmacd/rn-test-fastrsa

rmacd commented 1 year ago

update:

breaking on FastRSA.mm:24, the value of response is as we expect:

(lldb) x -c 32 `(char*)(*response).message + 24`
0x282d7ddd8: 96 16 d4 46 a2 59 c6 70 cd f1 6e d7 e8 6d c6 07  ...F.Y.p..n..m..
0x282d7dde8: 69 ac 0c 43 f4 c0 99 70 9c a4 ae d6 5f c3 77 17  i..C...p...._.w.

value of bytesResult is also the same:

(lldb) x -c 32 `bytesResult + 24`
0x282d23c98: 96 16 d4 46 a2 59 c6 70 cd f1 6e d7 e8 6d c6 07  ...F.Y.p..n..m..
0x282d23ca8: 69 ac 0c 43 f4 c0 99 70 9c a4 ae d6 5f c3 77 17  i..C...p...._.w.

as is the final NSArray result object:

(lldb) po result
<__NSArrayM 0x283614300>(12,0,0,0,0,0,6,0,8,0,4,0,6,0,0,0,4,0,0,0,32,0,0,0,
150,22,212,70,162,89,198,112,205,241,110,215,232,109,198,7,105,172,12,67,244,
192,153,112,156,164,174,214,95,195,119,23,0,0,0,0)

[value after dec->hex is the same]

so I'm at least satisfied the issue is happening after resolve() has been called

rmacd commented 1 year ago

update: following instructions at https://partiellkorrekt.de/2021/02/05/spy-on-the-react-native-messagequeue-in-style/ and running spy() on messages coming back over RN Bridge indicates response is correct:

 LOG  Native -> JS: <callback for FastRSA.call>([12,0,0,0,0,0,6,0,8,0,4,0,6,0,0,0,4,0,0,0,32,0,0,0,150,22,212,70,162,89,198,112,205,241,110,215,232,109,198,7,105,172,12,67,244,192,153,112,156,164,174,214,95,195,119,23,0,0,0,0])

so it seems issue is happening after response has been returned from RN Bridge

rmacd commented 1 year ago

OK have found problem now.

line 341 decryptOAEP(), returns response via _stringResponse(result)

in turn passes via .getRootAsStringResponse(string)

Input to this is correct, output is incorrect where input is arbitrary unicode (doesn't happen with plain text / alphanumeric).

input is mangled by this function, will look to see how this can be fixed; in meantime, is there any reason why response was being returned via cast-like routine?