@avieldr/react-native-rsa 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +453 -0
- package/Rsa.podspec +23 -0
- package/android/build.gradle +69 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/rsa/RsaModule.kt +129 -0
- package/android/src/main/java/com/rsa/RsaPackage.kt +33 -0
- package/android/src/main/java/com/rsa/core/ASN1Utils.kt +201 -0
- package/android/src/main/java/com/rsa/core/Algorithms.kt +126 -0
- package/android/src/main/java/com/rsa/core/KeyUtils.kt +83 -0
- package/android/src/main/java/com/rsa/core/RSACipher.kt +71 -0
- package/android/src/main/java/com/rsa/core/RSAKeyGenerator.kt +125 -0
- package/android/src/main/java/com/rsa/core/RSASigner.kt +70 -0
- package/ios/ASN1Utils.swift +225 -0
- package/ios/Algorithms.swift +89 -0
- package/ios/KeyUtils.swift +125 -0
- package/ios/RSACipher.swift +77 -0
- package/ios/RSAKeyGenerator.swift +164 -0
- package/ios/RSASigner.swift +101 -0
- package/ios/Rsa.h +61 -0
- package/ios/Rsa.mm +216 -0
- package/lib/module/NativeRsa.js +16 -0
- package/lib/module/NativeRsa.js.map +1 -0
- package/lib/module/constants.js +24 -0
- package/lib/module/constants.js.map +1 -0
- package/lib/module/encoding.js +116 -0
- package/lib/module/encoding.js.map +1 -0
- package/lib/module/errors.js +135 -0
- package/lib/module/errors.js.map +1 -0
- package/lib/module/index.js +232 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/keyInfo.js +286 -0
- package/lib/module/keyInfo.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeRsa.d.ts +32 -0
- package/lib/typescript/src/NativeRsa.d.ts.map +1 -0
- package/lib/typescript/src/constants.d.ts +21 -0
- package/lib/typescript/src/constants.d.ts.map +1 -0
- package/lib/typescript/src/encoding.d.ts +30 -0
- package/lib/typescript/src/encoding.d.ts.map +1 -0
- package/lib/typescript/src/errors.d.ts +47 -0
- package/lib/typescript/src/errors.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +122 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/keyInfo.d.ts +7 -0
- package/lib/typescript/src/keyInfo.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +63 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +133 -0
- package/src/NativeRsa.ts +59 -0
- package/src/constants.ts +25 -0
- package/src/encoding.ts +139 -0
- package/src/errors.ts +206 -0
- package/src/index.ts +305 -0
- package/src/keyInfo.ts +334 -0
- package/src/types.ts +85 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aviel Drori
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
# @avieldr/react-native-rsa
|
|
2
|
+
|
|
3
|
+
High-performance native RSA cryptography for React Native. Uses platform-native crypto libraries (Android `KeyPairGenerator`, iOS `Security` framework) for fast, secure operations.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Native RSA key generation** on both iOS and Android
|
|
8
|
+
- **Encrypt/Decrypt** with OAEP or PKCS#1 padding
|
|
9
|
+
- **Sign/Verify** with PSS or PKCS#1 padding
|
|
10
|
+
- **Key sizes**: 1024, 2048, 4096 bit
|
|
11
|
+
- **Hash algorithms**: SHA-1, SHA-256, SHA-384, SHA-512
|
|
12
|
+
- **Output formats**: PKCS#1 and PKCS#8 (private key), SPKI/X.509 (public key)
|
|
13
|
+
- **Key format conversion** between PKCS#1 and PKCS#8
|
|
14
|
+
- **Public key extraction** from private key
|
|
15
|
+
- **Key validation** (JS-only, no bridge call)
|
|
16
|
+
- **Turbo Module** (New Architecture required)
|
|
17
|
+
|
|
18
|
+
## Why This Package?
|
|
19
|
+
|
|
20
|
+
### Performance
|
|
21
|
+
|
|
22
|
+
Uses platform-native crypto APIs (`KeyPairGenerator` on Android, `Security.framework` on iOS) instead of JavaScript implementations or slow bridge calls:
|
|
23
|
+
|
|
24
|
+
| Operation | Native (this package) |
|
|
25
|
+
| --------------- | --------------------- |
|
|
26
|
+
| 2048-bit keygen | ~200ms |
|
|
27
|
+
| Encrypt/Decrypt | < 10ms |
|
|
28
|
+
| Sign/Verify | < 10ms |
|
|
29
|
+
|
|
30
|
+
> Note: Pure JS implementations are significantly less efficient for RSA operations.
|
|
31
|
+
|
|
32
|
+
### Security
|
|
33
|
+
|
|
34
|
+
- Uses **OS-hardened cryptographic APIs** — battle-tested implementations maintained by Apple and Google
|
|
35
|
+
- No bundled crypto libraries that could become outdated or vulnerable
|
|
36
|
+
- Supports modern padding schemes (OAEP, PSS) recommended by security standards
|
|
37
|
+
|
|
38
|
+
### Lightweight
|
|
39
|
+
|
|
40
|
+
Zero runtime dependencies — only peer dependencies on React and React Native.
|
|
41
|
+
|
|
42
|
+
### Flexibility
|
|
43
|
+
|
|
44
|
+
- Multiple **padding modes**: OAEP/PKCS#1 for encryption, PSS/PKCS#1 for signatures
|
|
45
|
+
- Multiple **hash algorithms**: SHA-1, SHA-256, SHA-384, SHA-512
|
|
46
|
+
- Multiple **key formats**: PKCS#1 and PKCS#8, with conversion between them
|
|
47
|
+
- Full **TypeScript support** with typed errors and options
|
|
48
|
+
|
|
49
|
+
### Compatibility
|
|
50
|
+
|
|
51
|
+
- Requires **New Architecture** (Turbo Modules) — React Native **0.71+**
|
|
52
|
+
- Android **API 24+** (Android 7.0)
|
|
53
|
+
- iOS **13.4+**
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
npm install @avieldr/react-native-rsa
|
|
59
|
+
# or
|
|
60
|
+
yarn add @avieldr/react-native-rsa
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
For iOS:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
cd ios && pod install
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
> **💡 Tip:** Check out the [`example/`](./example) app in this repository for complete working demonstrations of all features, including key generation, encryption, signing, and error handling.
|
|
70
|
+
|
|
71
|
+
## Quick Start
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import RSA, { base64ToUtf8 } from '@avieldr/react-native-rsa';
|
|
75
|
+
|
|
76
|
+
// Generate a key pair
|
|
77
|
+
const { publicKey, privateKey } = await RSA.generateKeyPair(2048);
|
|
78
|
+
|
|
79
|
+
// Encrypt and decrypt
|
|
80
|
+
const encrypted = await RSA.encrypt('Hello, World!', publicKey);
|
|
81
|
+
const decryptedBase64 = await RSA.decrypt(encrypted, privateKey);
|
|
82
|
+
const decrypted = base64ToUtf8(decryptedBase64); // "Hello, World!"
|
|
83
|
+
|
|
84
|
+
// Sign and verify
|
|
85
|
+
const signature = await RSA.sign('Message to sign', privateKey);
|
|
86
|
+
const isValid = await RSA.verify('Message to sign', signature, publicKey); // true
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## API
|
|
90
|
+
|
|
91
|
+
### `generateKeyPair(keySize?, options?)`
|
|
92
|
+
|
|
93
|
+
Generate an RSA key pair using native platform crypto.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import RSA from '@avieldr/react-native-rsa';
|
|
97
|
+
|
|
98
|
+
const { publicKey, privateKey } = await RSA.generateKeyPair(2048);
|
|
99
|
+
|
|
100
|
+
// With PKCS#8 format
|
|
101
|
+
const { publicKey, privateKey } = await RSA.generateKeyPair(2048, {
|
|
102
|
+
format: 'pkcs8',
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
| Parameter | Type | Default | Description |
|
|
107
|
+
| ---------------- | -------------------- | --------- | --------------------------------------- |
|
|
108
|
+
| `keySize` | `number` | `2048` | RSA key size in bits (1024, 2048, 4096) |
|
|
109
|
+
| `options.format` | `'pkcs1' \| 'pkcs8'` | `'pkcs1'` | Private key output format |
|
|
110
|
+
|
|
111
|
+
**Returns:** `Promise<RSAKeyPair>`
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
interface RSAKeyPair {
|
|
115
|
+
publicKey: string; // PEM (SPKI/X.509): -----BEGIN PUBLIC KEY-----
|
|
116
|
+
privateKey: string; // PEM (PKCS#1): -----BEGIN RSA PRIVATE KEY-----
|
|
117
|
+
// or (PKCS#8): -----BEGIN PRIVATE KEY-----
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
### `encrypt(data, publicKeyPEM, options?)`
|
|
124
|
+
|
|
125
|
+
Encrypt data with an RSA public key.
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// Basic encryption (UTF-8 text)
|
|
129
|
+
const encrypted = await RSA.encrypt('Hello, World!', publicKey);
|
|
130
|
+
|
|
131
|
+
// With options
|
|
132
|
+
const encrypted = await RSA.encrypt('Hello, World!', publicKey, {
|
|
133
|
+
padding: 'oaep', // or 'pkcs1'
|
|
134
|
+
hash: 'sha256', // or 'sha1', 'sha384', 'sha512'
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Binary data (already base64-encoded)
|
|
138
|
+
const encrypted = await RSA.encrypt(binaryDataBase64, publicKey, {
|
|
139
|
+
encoding: 'base64',
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
| Parameter | Type | Default | Description |
|
|
144
|
+
| ------------------ | -------------------- | ---------- | ---------------------------------------- |
|
|
145
|
+
| `data` | `string` | — | Data to encrypt (UTF-8 string or base64) |
|
|
146
|
+
| `publicKeyPEM` | `string` | — | Public key in SPKI PEM format |
|
|
147
|
+
| `options.padding` | `'oaep' \| 'pkcs1'` | `'oaep'` | Padding mode (OAEP recommended) |
|
|
148
|
+
| `options.hash` | `HashAlgorithm` | `'sha256'` | Hash algorithm (used with OAEP) |
|
|
149
|
+
| `options.encoding` | `'utf8' \| 'base64'` | `'utf8'` | How to interpret the input string |
|
|
150
|
+
|
|
151
|
+
**Returns:** `Promise<string>` — Base64-encoded ciphertext
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
### `decrypt(encrypted, privateKeyPEM, options?)`
|
|
156
|
+
|
|
157
|
+
Decrypt ciphertext with an RSA private key.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import RSA, { base64ToUtf8 } from '@avieldr/react-native-rsa';
|
|
161
|
+
|
|
162
|
+
const decryptedBase64 = await RSA.decrypt(encrypted, privateKey);
|
|
163
|
+
const plaintext = base64ToUtf8(decryptedBase64); // Convert back to UTF-8
|
|
164
|
+
|
|
165
|
+
// With options (must match encryption options)
|
|
166
|
+
const decryptedBase64 = await RSA.decrypt(encrypted, privateKey, {
|
|
167
|
+
padding: 'oaep',
|
|
168
|
+
hash: 'sha256',
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
| Parameter | Type | Default | Description |
|
|
173
|
+
| ----------------- | ------------------- | ---------- | ----------------------------------------- |
|
|
174
|
+
| `encrypted` | `string` | — | Base64-encoded ciphertext |
|
|
175
|
+
| `privateKeyPEM` | `string` | — | Private key in PEM format (PKCS#1/PKCS#8) |
|
|
176
|
+
| `options.padding` | `'oaep' \| 'pkcs1'` | `'oaep'` | Padding mode (must match encryption) |
|
|
177
|
+
| `options.hash` | `HashAlgorithm` | `'sha256'` | Hash algorithm (must match encryption) |
|
|
178
|
+
|
|
179
|
+
**Returns:** `Promise<string>` — Base64-encoded plaintext (use `base64ToUtf8()` to convert)
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
### `sign(data, privateKeyPEM, options?)`
|
|
184
|
+
|
|
185
|
+
Sign data with an RSA private key.
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
const signature = await RSA.sign('Message to sign', privateKey);
|
|
189
|
+
|
|
190
|
+
// With options
|
|
191
|
+
const signature = await RSA.sign('Message to sign', privateKey, {
|
|
192
|
+
padding: 'pss', // or 'pkcs1'
|
|
193
|
+
hash: 'sha256',
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
| Parameter | Type | Default | Description |
|
|
198
|
+
| ------------------ | -------------------- | ---------- | ----------------------------------------- |
|
|
199
|
+
| `data` | `string` | — | Data to sign (UTF-8 string or base64) |
|
|
200
|
+
| `privateKeyPEM` | `string` | — | Private key in PEM format (PKCS#1/PKCS#8) |
|
|
201
|
+
| `options.padding` | `'pss' \| 'pkcs1'` | `'pss'` | Padding mode (PSS recommended) |
|
|
202
|
+
| `options.hash` | `HashAlgorithm` | `'sha256'` | Hash algorithm |
|
|
203
|
+
| `options.encoding` | `'utf8' \| 'base64'` | `'utf8'` | How to interpret the input string |
|
|
204
|
+
|
|
205
|
+
**Returns:** `Promise<string>` — Base64-encoded signature
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
### `verify(data, signature, publicKeyPEM, options?)`
|
|
210
|
+
|
|
211
|
+
Verify a signature against data using an RSA public key.
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
const isValid = await RSA.verify('Message to sign', signature, publicKey);
|
|
215
|
+
// true if signature is valid, false otherwise
|
|
216
|
+
|
|
217
|
+
// With options (must match signing options)
|
|
218
|
+
const isValid = await RSA.verify('Message to sign', signature, publicKey, {
|
|
219
|
+
padding: 'pss',
|
|
220
|
+
hash: 'sha256',
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
| Parameter | Type | Default | Description |
|
|
225
|
+
| ------------------ | -------------------- | ---------- | ----------------------------------- |
|
|
226
|
+
| `data` | `string` | — | Original data that was signed |
|
|
227
|
+
| `signature` | `string` | — | Base64-encoded signature |
|
|
228
|
+
| `publicKeyPEM` | `string` | — | Public key in SPKI PEM format |
|
|
229
|
+
| `options.padding` | `'pss' \| 'pkcs1'` | `'pss'` | Padding mode (must match signing) |
|
|
230
|
+
| `options.hash` | `HashAlgorithm` | `'sha256'` | Hash algorithm (must match signing) |
|
|
231
|
+
| `options.encoding` | `'utf8' \| 'base64'` | `'utf8'` | How to interpret the input string |
|
|
232
|
+
|
|
233
|
+
**Returns:** `Promise<boolean>` — `true` if valid, `false` otherwise
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
### `getPublicKeyFromPrivate(privateKeyPEM)`
|
|
238
|
+
|
|
239
|
+
Extract the public key from an RSA private key.
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
const publicKey = await RSA.getPublicKeyFromPrivate(privateKey);
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Accepts both PKCS#1 and PKCS#8 private key formats.
|
|
246
|
+
|
|
247
|
+
**Returns:** `Promise<string>` — Public key in SPKI PEM format
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
### `convertPrivateKey(pem, targetFormat)`
|
|
252
|
+
|
|
253
|
+
Convert a private key between PKCS#1 and PKCS#8 formats.
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
// Convert PKCS#1 to PKCS#8
|
|
257
|
+
const pkcs8Key = await RSA.convertPrivateKey(pkcs1Key, 'pkcs8');
|
|
258
|
+
|
|
259
|
+
// Convert PKCS#8 to PKCS#1
|
|
260
|
+
const pkcs1Key = await RSA.convertPrivateKey(pkcs8Key, 'pkcs1');
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
| Parameter | Type | Description |
|
|
264
|
+
| -------------- | -------------------- | ---------------------------- |
|
|
265
|
+
| `pem` | `string` | Private key in PEM format |
|
|
266
|
+
| `targetFormat` | `'pkcs1' \| 'pkcs8'` | Target format for conversion |
|
|
267
|
+
|
|
268
|
+
**Returns:** `Promise<string>` — Private key in the target format
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
### `getKeyInfo(keyString)`
|
|
273
|
+
|
|
274
|
+
Analyze a PEM key string and return metadata. Runs entirely in JS — no native bridge call.
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import { getKeyInfo } from '@avieldr/react-native-rsa';
|
|
278
|
+
|
|
279
|
+
const info = getKeyInfo(privateKey);
|
|
280
|
+
// {
|
|
281
|
+
// isValid: true,
|
|
282
|
+
// format: 'pkcs1',
|
|
283
|
+
// keyType: 'private',
|
|
284
|
+
// pemLineCount: 13,
|
|
285
|
+
// derByteLength: 609,
|
|
286
|
+
// errors: []
|
|
287
|
+
// }
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Returns:** `RSAKeyInfo`
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
interface RSAKeyInfo {
|
|
294
|
+
isValid: boolean;
|
|
295
|
+
format: 'pkcs1' | 'pkcs8' | 'public' | 'unknown';
|
|
296
|
+
keyType: 'private' | 'public' | 'unknown';
|
|
297
|
+
pemLineCount: number;
|
|
298
|
+
derByteLength: number;
|
|
299
|
+
errors: string[];
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Encoding Utilities
|
|
306
|
+
|
|
307
|
+
The library provides pure-JS encoding utilities that work in all React Native JS engines.
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
import { utf8ToBase64, base64ToUtf8 } from '@avieldr/react-native-rsa';
|
|
311
|
+
|
|
312
|
+
// Encode UTF-8 text to base64
|
|
313
|
+
const encoded = utf8ToBase64('Hello, 世界! 🎉');
|
|
314
|
+
|
|
315
|
+
// Decode base64 back to UTF-8
|
|
316
|
+
const decoded = base64ToUtf8(encoded);
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
These are useful for:
|
|
320
|
+
|
|
321
|
+
- Converting decrypted data back to readable text
|
|
322
|
+
- Preparing binary data for encryption
|
|
323
|
+
- Handling Unicode and emoji correctly
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Error Handling
|
|
328
|
+
|
|
329
|
+
The library throws `RsaError` for invalid inputs and native failures with specific error codes:
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
import RSA, { RsaError } from '@avieldr/react-native-rsa';
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
await RSA.encrypt('data', 'invalid-key');
|
|
336
|
+
} catch (error) {
|
|
337
|
+
if (error instanceof RsaError) {
|
|
338
|
+
console.log(error.code); // 'INVALID_KEY'
|
|
339
|
+
console.log(error.message); // Detailed error message
|
|
340
|
+
console.log(error.cause); // Original error (if from native layer)
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Validation Errors
|
|
346
|
+
|
|
347
|
+
These are thrown **before** calling native code when inputs are invalid:
|
|
348
|
+
|
|
349
|
+
| Error Code | Description |
|
|
350
|
+
| ------------------ | ----------------------------------------- |
|
|
351
|
+
| `INVALID_INPUT` | Required parameter is missing or empty |
|
|
352
|
+
| `INVALID_KEY` | Key format is wrong or key type mismatch |
|
|
353
|
+
| `INVALID_KEY_SIZE` | Unsupported key size (not 1024/2048/4096) |
|
|
354
|
+
| `INVALID_PADDING` | Unknown padding mode |
|
|
355
|
+
| `INVALID_HASH` | Unknown hash algorithm |
|
|
356
|
+
| `INVALID_FORMAT` | Unknown key format |
|
|
357
|
+
| `INVALID_ENCODING` | Unknown encoding type |
|
|
358
|
+
|
|
359
|
+
### Native Operation Errors
|
|
360
|
+
|
|
361
|
+
These are thrown when the platform crypto operation fails:
|
|
362
|
+
|
|
363
|
+
| Error Code | Description |
|
|
364
|
+
| ----------------------- | ---------------------------------------------- |
|
|
365
|
+
| `KEY_GENERATION_FAILED` | Native key generation failed |
|
|
366
|
+
| `KEY_EXTRACTION_FAILED` | Failed to extract public key from private key |
|
|
367
|
+
| `KEY_CONVERSION_FAILED` | Failed to convert key between formats |
|
|
368
|
+
| `ENCRYPTION_FAILED` | Encryption failed (e.g., data too large) |
|
|
369
|
+
| `DECRYPTION_FAILED` | Decryption failed (e.g., wrong key or padding) |
|
|
370
|
+
| `SIGNING_FAILED` | Signing operation failed |
|
|
371
|
+
| `VERIFICATION_FAILED` | Signature verification failed |
|
|
372
|
+
|
|
373
|
+
### TypeScript Support
|
|
374
|
+
|
|
375
|
+
The error code type is exported for TypeScript users:
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
import type { RsaErrorCode } from '@avieldr/react-native-rsa';
|
|
379
|
+
|
|
380
|
+
function handleError(code: RsaErrorCode) {
|
|
381
|
+
switch (code) {
|
|
382
|
+
case 'INVALID_KEY':
|
|
383
|
+
// Handle invalid key
|
|
384
|
+
break;
|
|
385
|
+
case 'DECRYPTION_FAILED':
|
|
386
|
+
// Handle decryption failure
|
|
387
|
+
break;
|
|
388
|
+
// ...
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## Types
|
|
396
|
+
|
|
397
|
+
All types are exported for TypeScript users:
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
import type {
|
|
401
|
+
RSAKeyPair,
|
|
402
|
+
RSAKeyInfo,
|
|
403
|
+
GenerateKeyPairOptions,
|
|
404
|
+
KeyFormat,
|
|
405
|
+
EncryptOptions,
|
|
406
|
+
DecryptOptions,
|
|
407
|
+
SignOptions,
|
|
408
|
+
VerifyOptions,
|
|
409
|
+
EncryptionPadding,
|
|
410
|
+
SignaturePadding,
|
|
411
|
+
HashAlgorithm,
|
|
412
|
+
InputEncoding,
|
|
413
|
+
RsaErrorCode,
|
|
414
|
+
} from '@avieldr/react-native-rsa';
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
| Type | Values |
|
|
418
|
+
| ------------------- | -------------------------------------------- |
|
|
419
|
+
| `KeyFormat` | `'pkcs1' \| 'pkcs8'` |
|
|
420
|
+
| `RsaErrorCode` | See [Error Handling](#error-handling) |
|
|
421
|
+
| `EncryptionPadding` | `'oaep' \| 'pkcs1'` |
|
|
422
|
+
| `SignaturePadding` | `'pss' \| 'pkcs1'` |
|
|
423
|
+
| `HashAlgorithm` | `'sha1' \| 'sha256' \| 'sha384' \| 'sha512'` |
|
|
424
|
+
| `InputEncoding` | `'utf8' \| 'base64'` |
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## Benchmarks
|
|
429
|
+
|
|
430
|
+
Typical key generation times on modern devices:
|
|
431
|
+
|
|
432
|
+
| Key Size | Time |
|
|
433
|
+
| -------- | -------- |
|
|
434
|
+
| 1024-bit | ~50ms |
|
|
435
|
+
| 2048-bit | ~200ms |
|
|
436
|
+
| 4096-bit | ~2,000ms |
|
|
437
|
+
|
|
438
|
+
_Times vary by device. Measured on mid-range Android and iPhone devices._
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## Security Recommendations
|
|
443
|
+
|
|
444
|
+
- **Use OAEP padding** for encryption (default) — PKCS#1 v1.5 is vulnerable to padding oracle attacks
|
|
445
|
+
- **Use PSS padding** for signatures (default) — more secure than PKCS#1 v1.5
|
|
446
|
+
- **Use SHA-256 or higher** — SHA-1 is deprecated for new applications
|
|
447
|
+
- **Use 2048-bit keys minimum** — 1024-bit is considered weak
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
## License
|
|
452
|
+
|
|
453
|
+
MIT
|
package/Rsa.podspec
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "Rsa"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
+
s.source = { :git => "https://github.com/avieldr/react-native-rsa-turbo.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
|
|
17
|
+
s.private_header_files = "ios/**/*.h"
|
|
18
|
+
|
|
19
|
+
s.swift_version = "5.0"
|
|
20
|
+
s.frameworks = "Security"
|
|
21
|
+
|
|
22
|
+
install_modules_dependencies(s)
|
|
23
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.Rsa = [
|
|
3
|
+
kotlinVersion: "2.0.21",
|
|
4
|
+
minSdkVersion: 24,
|
|
5
|
+
compileSdkVersion: 36,
|
|
6
|
+
targetSdkVersion: 36
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
ext.getExtOrDefault = { prop ->
|
|
10
|
+
if (rootProject.ext.has(prop)) {
|
|
11
|
+
return rootProject.ext.get(prop)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return Rsa[prop]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
repositories {
|
|
18
|
+
google()
|
|
19
|
+
mavenCentral()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
dependencies {
|
|
23
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
24
|
+
// noinspection DifferentKotlinGradleVersion
|
|
25
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
apply plugin: "com.android.library"
|
|
31
|
+
apply plugin: "kotlin-android"
|
|
32
|
+
|
|
33
|
+
apply plugin: "com.facebook.react"
|
|
34
|
+
|
|
35
|
+
android {
|
|
36
|
+
namespace "com.rsa"
|
|
37
|
+
|
|
38
|
+
compileSdkVersion getExtOrDefault("compileSdkVersion")
|
|
39
|
+
|
|
40
|
+
defaultConfig {
|
|
41
|
+
minSdkVersion getExtOrDefault("minSdkVersion")
|
|
42
|
+
targetSdkVersion getExtOrDefault("targetSdkVersion")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
buildFeatures {
|
|
46
|
+
buildConfig true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
buildTypes {
|
|
50
|
+
release {
|
|
51
|
+
minifyEnabled false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
lint {
|
|
56
|
+
disable "GradleCompatible"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
compileOptions {
|
|
60
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
61
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
dependencies {
|
|
66
|
+
implementation "com.facebook.react:react-android"
|
|
67
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"
|
|
68
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
|
|
69
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
package com.rsa
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.Promise
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.rsa.core.RSACipher
|
|
7
|
+
import com.rsa.core.RSAKeyGenerator
|
|
8
|
+
import com.rsa.core.RSASigner
|
|
9
|
+
import kotlinx.coroutines.CoroutineScope
|
|
10
|
+
import kotlinx.coroutines.Dispatchers
|
|
11
|
+
import kotlinx.coroutines.SupervisorJob
|
|
12
|
+
import kotlinx.coroutines.cancel
|
|
13
|
+
import kotlinx.coroutines.launch
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* React Native TurboModule bridging RSA operations to JavaScript.
|
|
17
|
+
*
|
|
18
|
+
* Each method launches a coroutine on Dispatchers.Default (background thread pool)
|
|
19
|
+
* to keep crypto operations off the main thread, and resolves/rejects the JS promise.
|
|
20
|
+
*
|
|
21
|
+
* Dispatches to:
|
|
22
|
+
* - [RSAKeyGenerator] for key generation, public key extraction, and format conversion
|
|
23
|
+
* - [RSACipher] for encrypt/decrypt
|
|
24
|
+
* - [RSASigner] for sign/verify
|
|
25
|
+
*/
|
|
26
|
+
class RsaModule(reactContext: ReactApplicationContext) :
|
|
27
|
+
NativeRsaSpec(reactContext) {
|
|
28
|
+
|
|
29
|
+
private val supervisorJob = SupervisorJob()
|
|
30
|
+
private val moduleScope = CoroutineScope(Dispatchers.Default + supervisorJob)
|
|
31
|
+
private val keyGenerator = RSAKeyGenerator.getInstance()
|
|
32
|
+
|
|
33
|
+
override fun invalidate() {
|
|
34
|
+
supervisorJob.cancel("RsaModule invalidated")
|
|
35
|
+
super.invalidate()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// --- Key Generation ---
|
|
39
|
+
|
|
40
|
+
override fun generateKeyPair(keySize: Double, format: String, promise: Promise) {
|
|
41
|
+
moduleScope.launch {
|
|
42
|
+
try {
|
|
43
|
+
val result = keyGenerator.generateKeyPair(keySize.toInt(), format)
|
|
44
|
+
val map = Arguments.createMap()
|
|
45
|
+
map.putString("publicKey", result.publicKey)
|
|
46
|
+
map.putString("privateKey", result.privateKey)
|
|
47
|
+
promise.resolve(map)
|
|
48
|
+
} catch (e: Exception) {
|
|
49
|
+
promise.reject("RSAKeyGenerationError", e.message, e)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
override fun getPublicKeyFromPrivate(privateKeyPEM: String, promise: Promise) {
|
|
55
|
+
moduleScope.launch {
|
|
56
|
+
try {
|
|
57
|
+
val publicKeyPEM = keyGenerator.getPublicKeyFromPrivate(privateKeyPEM)
|
|
58
|
+
promise.resolve(publicKeyPEM)
|
|
59
|
+
} catch (e: Exception) {
|
|
60
|
+
promise.reject("RSAKeyExtractionError", e.message, e)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// --- Encrypt / Decrypt ---
|
|
66
|
+
|
|
67
|
+
override fun encrypt(dataBase64: String, publicKeyPEM: String, padding: String, hash: String, promise: Promise) {
|
|
68
|
+
moduleScope.launch {
|
|
69
|
+
try {
|
|
70
|
+
val result = RSACipher.encrypt(dataBase64, publicKeyPEM, padding, hash)
|
|
71
|
+
promise.resolve(result)
|
|
72
|
+
} catch (e: Exception) {
|
|
73
|
+
promise.reject("RSAEncryptError", e.message, e)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
override fun decrypt(dataBase64: String, privateKeyPEM: String, padding: String, hash: String, promise: Promise) {
|
|
79
|
+
moduleScope.launch {
|
|
80
|
+
try {
|
|
81
|
+
val result = RSACipher.decrypt(dataBase64, privateKeyPEM, padding, hash)
|
|
82
|
+
promise.resolve(result)
|
|
83
|
+
} catch (e: Exception) {
|
|
84
|
+
promise.reject("RSADecryptError", e.message, e)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// --- Sign / Verify ---
|
|
90
|
+
|
|
91
|
+
override fun sign(dataBase64: String, privateKeyPEM: String, padding: String, hash: String, promise: Promise) {
|
|
92
|
+
moduleScope.launch {
|
|
93
|
+
try {
|
|
94
|
+
val result = RSASigner.sign(dataBase64, privateKeyPEM, padding, hash)
|
|
95
|
+
promise.resolve(result)
|
|
96
|
+
} catch (e: Exception) {
|
|
97
|
+
promise.reject("RSASignError", e.message, e)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
override fun verify(dataBase64: String, signatureBase64: String, publicKeyPEM: String, padding: String, hash: String, promise: Promise) {
|
|
103
|
+
moduleScope.launch {
|
|
104
|
+
try {
|
|
105
|
+
val result = RSASigner.verify(dataBase64, signatureBase64, publicKeyPEM, padding, hash)
|
|
106
|
+
promise.resolve(result)
|
|
107
|
+
} catch (e: Exception) {
|
|
108
|
+
promise.reject("RSAVerifyError", e.message, e)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// --- Key Format Conversion ---
|
|
114
|
+
|
|
115
|
+
override fun convertPrivateKey(pem: String, targetFormat: String, promise: Promise) {
|
|
116
|
+
moduleScope.launch {
|
|
117
|
+
try {
|
|
118
|
+
val result = keyGenerator.convertPrivateKey(pem, targetFormat)
|
|
119
|
+
promise.resolve(result)
|
|
120
|
+
} catch (e: Exception) {
|
|
121
|
+
promise.reject("RSAConvertKeyError", e.message, e)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
companion object {
|
|
127
|
+
const val NAME = NativeRsaSpec.NAME
|
|
128
|
+
}
|
|
129
|
+
}
|