react-native-security-suite 0.9.21 → 1.0.0-rc.1
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/README.md +233 -65
- package/android/build.gradle +11 -0
- package/android/gradle.properties +1 -1
- package/android/src/main/java/com/securitysuite/CryptoConfig.java +158 -0
- package/android/src/main/java/com/securitysuite/CryptoUtils.java +152 -0
- package/android/src/main/java/com/securitysuite/EcdhKeyStore.java +60 -0
- package/android/src/main/java/com/securitysuite/HeaderSanitizer.java +75 -0
- package/android/src/main/java/com/securitysuite/JWSGenerator.java +237 -32
- package/android/src/main/java/com/securitysuite/JwsFetchPayload.java +81 -0
- package/android/src/main/java/com/securitysuite/Obfuscation.java +57 -0
- package/android/src/main/java/com/securitysuite/SecureStorageNative.java +211 -0
- package/android/src/main/java/com/securitysuite/SecureView.java +2 -10
- package/android/src/main/java/com/securitysuite/SecureWindowHelper.java +30 -0
- package/android/src/main/java/com/securitysuite/SecuritySuiteModule.java +310 -102
- package/android/src/main/java/com/securitysuite/Sslpinning.java +219 -106
- package/android/src/main/java/com/securitysuite/security/AppIntegrityChecker.java +133 -0
- package/android/src/main/java/com/securitysuite/security/EmulatorDetector.java +145 -0
- package/android/src/main/java/com/securitysuite/security/RuntimeDetector.java +234 -0
- package/android/src/test/java/com/securitysuite/JWSGeneratorTest.java +153 -0
- package/android/src/test/java/com/securitysuite/SecureStorageNativeTest.java +37 -0
- package/ios/CryptoConfig.swift +124 -0
- package/ios/JWSGenerator.swift +288 -0
- package/ios/JWSGeneratorTests.swift +168 -0
- package/ios/KeychainHelper.swift +104 -0
- package/ios/Obfuscation.swift +42 -0
- package/ios/SecureStorageNative.swift +84 -0
- package/ios/Security/AppIntegrityChecker.swift +85 -0
- package/ios/Security/EmulatorDetector.swift +45 -0
- package/ios/Security/RuntimeDetector.swift +107 -0
- package/ios/SecuritySuite.mm +28 -4
- package/ios/SecuritySuite.swift +407 -131
- package/ios/SslPinning.swift +242 -263
- package/lib/commonjs/clipboard/index.js +3 -0
- package/lib/commonjs/clipboard/index.js.map +1 -0
- package/lib/commonjs/crypto/index.js +39 -0
- package/lib/commonjs/crypto/index.js.map +1 -0
- package/lib/commonjs/device/index.js +40 -0
- package/lib/commonjs/device/index.js.map +1 -0
- package/lib/commonjs/errors.js +62 -0
- package/lib/commonjs/errors.js.map +1 -0
- package/lib/commonjs/index.js +220 -151
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/integrity/index.js +40 -0
- package/lib/commonjs/integrity/index.js.map +1 -0
- package/lib/commonjs/jws.js +141 -0
- package/lib/commonjs/jws.js.map +1 -0
- package/lib/commonjs/legacy/cryptoOptions.js +20 -0
- package/lib/commonjs/legacy/cryptoOptions.js.map +1 -0
- package/lib/commonjs/native/bridge.js +23 -0
- package/lib/commonjs/native/bridge.js.map +1 -0
- package/lib/commonjs/network/index.js +3 -0
- package/lib/commonjs/network/index.js.map +1 -0
- package/lib/commonjs/risk/score.js +36 -0
- package/lib/commonjs/risk/score.js.map +1 -0
- package/lib/commonjs/runtime/index.js +31 -0
- package/lib/commonjs/runtime/index.js.map +1 -0
- package/lib/commonjs/screen/index.js +13 -0
- package/lib/commonjs/screen/index.js.map +1 -0
- package/lib/commonjs/securitySuite/index.js +42 -0
- package/lib/commonjs/securitySuite/index.js.map +1 -0
- package/lib/commonjs/storage/index.js +3 -0
- package/lib/commonjs/storage/index.js.map +1 -0
- package/lib/commonjs/types/detection.js +2 -0
- package/lib/commonjs/types/detection.js.map +1 -0
- package/lib/module/clipboard/index.js +3 -0
- package/lib/module/clipboard/index.js.map +1 -0
- package/lib/module/crypto/index.js +35 -0
- package/lib/module/crypto/index.js.map +1 -0
- package/lib/module/device/index.js +36 -0
- package/lib/module/device/index.js.map +1 -0
- package/lib/module/errors.js +55 -0
- package/lib/module/errors.js.map +1 -0
- package/lib/module/index.js +147 -148
- package/lib/module/index.js.map +1 -1
- package/lib/module/integrity/index.js +36 -0
- package/lib/module/integrity/index.js.map +1 -0
- package/lib/module/jws.js +127 -0
- package/lib/module/jws.js.map +1 -0
- package/lib/module/legacy/cryptoOptions.js +16 -0
- package/lib/module/legacy/cryptoOptions.js.map +1 -0
- package/lib/module/native/bridge.js +19 -0
- package/lib/module/native/bridge.js.map +1 -0
- package/lib/module/network/index.js +3 -0
- package/lib/module/network/index.js.map +1 -0
- package/lib/module/risk/score.js +32 -0
- package/lib/module/risk/score.js.map +1 -0
- package/lib/module/runtime/index.js +27 -0
- package/lib/module/runtime/index.js.map +1 -0
- package/lib/module/screen/index.js +5 -0
- package/lib/module/screen/index.js.map +1 -0
- package/lib/module/securitySuite/index.js +38 -0
- package/lib/module/securitySuite/index.js.map +1 -0
- package/lib/module/storage/index.js +3 -0
- package/lib/module/storage/index.js.map +1 -0
- package/lib/module/types/detection.js +2 -0
- package/lib/module/types/detection.js.map +1 -0
- package/lib/typescript/commonjs/docs/api-v1-proposal.d.ts +215 -0
- package/lib/typescript/commonjs/docs/api-v1-proposal.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/SecureView.d.ts +1 -1
- package/lib/typescript/commonjs/src/SecureView.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/clipboard/index.d.ts +2 -0
- package/lib/typescript/commonjs/src/clipboard/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/crypto/index.d.ts +15 -0
- package/lib/typescript/commonjs/src/crypto/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/device/index.d.ts +11 -0
- package/lib/typescript/commonjs/src/device/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/errors.d.ts +17 -0
- package/lib/typescript/commonjs/src/errors.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/helpers.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts +77 -24
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/integrity/index.d.ts +6 -0
- package/lib/typescript/commonjs/src/integrity/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/jws.d.ts +44 -0
- package/lib/typescript/commonjs/src/jws.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/legacy/cryptoOptions.d.ts +35 -0
- package/lib/typescript/commonjs/src/legacy/cryptoOptions.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/native/bridge.d.ts +12 -0
- package/lib/typescript/commonjs/src/native/bridge.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/network/index.d.ts +2 -0
- package/lib/typescript/commonjs/src/network/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/risk/score.d.ts +12 -0
- package/lib/typescript/commonjs/src/risk/score.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/runtime/index.d.ts +6 -0
- package/lib/typescript/commonjs/src/runtime/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/screen/index.d.ts +3 -0
- package/lib/typescript/commonjs/src/screen/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/securitySuite/index.d.ts +6 -0
- package/lib/typescript/commonjs/src/securitySuite/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/storage/index.d.ts +2 -0
- package/lib/typescript/commonjs/src/storage/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/types/detection.d.ts +41 -0
- package/lib/typescript/commonjs/src/types/detection.d.ts.map +1 -0
- package/lib/typescript/module/docs/api-v1-proposal.d.ts +215 -0
- package/lib/typescript/module/docs/api-v1-proposal.d.ts.map +1 -0
- package/lib/typescript/module/src/SecureView.d.ts +1 -1
- package/lib/typescript/module/src/SecureView.d.ts.map +1 -1
- package/lib/typescript/module/src/clipboard/index.d.ts +2 -0
- package/lib/typescript/module/src/clipboard/index.d.ts.map +1 -0
- package/lib/typescript/module/src/crypto/index.d.ts +15 -0
- package/lib/typescript/module/src/crypto/index.d.ts.map +1 -0
- package/lib/typescript/module/src/device/index.d.ts +11 -0
- package/lib/typescript/module/src/device/index.d.ts.map +1 -0
- package/lib/typescript/module/src/errors.d.ts +17 -0
- package/lib/typescript/module/src/errors.d.ts.map +1 -0
- package/lib/typescript/module/src/helpers.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts +77 -24
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/integrity/index.d.ts +6 -0
- package/lib/typescript/module/src/integrity/index.d.ts.map +1 -0
- package/lib/typescript/module/src/jws.d.ts +44 -0
- package/lib/typescript/module/src/jws.d.ts.map +1 -0
- package/lib/typescript/module/src/legacy/cryptoOptions.d.ts +35 -0
- package/lib/typescript/module/src/legacy/cryptoOptions.d.ts.map +1 -0
- package/lib/typescript/module/src/native/bridge.d.ts +12 -0
- package/lib/typescript/module/src/native/bridge.d.ts.map +1 -0
- package/lib/typescript/module/src/network/index.d.ts +2 -0
- package/lib/typescript/module/src/network/index.d.ts.map +1 -0
- package/lib/typescript/module/src/risk/score.d.ts +12 -0
- package/lib/typescript/module/src/risk/score.d.ts.map +1 -0
- package/lib/typescript/module/src/runtime/index.d.ts +6 -0
- package/lib/typescript/module/src/runtime/index.d.ts.map +1 -0
- package/lib/typescript/module/src/screen/index.d.ts +3 -0
- package/lib/typescript/module/src/screen/index.d.ts.map +1 -0
- package/lib/typescript/module/src/securitySuite/index.d.ts +6 -0
- package/lib/typescript/module/src/securitySuite/index.d.ts.map +1 -0
- package/lib/typescript/module/src/storage/index.d.ts +2 -0
- package/lib/typescript/module/src/storage/index.d.ts.map +1 -0
- package/lib/typescript/module/src/types/detection.d.ts +41 -0
- package/lib/typescript/module/src/types/detection.d.ts.map +1 -0
- package/package.json +2 -4
- package/src/clipboard/index.ts +1 -0
- package/src/crypto/index.ts +49 -0
- package/src/device/index.ts +47 -0
- package/src/errors.ts +84 -0
- package/src/index.tsx +293 -195
- package/src/integrity/index.ts +46 -0
- package/src/jws.ts +213 -0
- package/src/legacy/cryptoOptions.ts +49 -0
- package/src/native/bridge.ts +37 -0
- package/src/network/index.ts +1 -0
- package/src/risk/score.ts +49 -0
- package/src/runtime/index.ts +43 -0
- package/src/screen/index.ts +2 -0
- package/src/securitySuite/index.ts +45 -0
- package/src/storage/index.ts +1 -0
- package/src/types/detection.ts +46 -0
- package/android/src/main/java/com/securitysuite/StorageEncryption.java +0 -52
- package/ios/StorageEncryption.swift +0 -89
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](https://www.npmjs.com/package/react-native-security-suite)
|
|
6
6
|
|
|
7
|
-
**Comprehensive security solutions for React Native applications**
|
|
7
|
+
**Comprehensive security solutions for React Native applications** — Protect your mobile apps with root/jailbreak detection, SSL certificate pinning, RFC 7515 JWS request signing, hardware-backed secure storage, X25519 key exchange, screenshot protection, and network monitoring.
|
|
8
8
|
|
|
9
9
|
<div style="display: flex; flex-direction: row; justify-content: center; align-items: center; gap: 20px;">
|
|
10
10
|
<img src="https://raw.githubusercontent.com/mohamadnavabi/react-native-security-suite/master/pulse.gif" alt="iOS Pulse Network Monitor" width="200" />
|
|
@@ -23,17 +23,18 @@
|
|
|
23
23
|
|
|
24
24
|
### Data Security & Encryption
|
|
25
25
|
|
|
26
|
-
- **
|
|
27
|
-
- **
|
|
28
|
-
- **
|
|
29
|
-
- **
|
|
26
|
+
- **Secure Storage**: Hardware-backed encrypted storage (Keychain on iOS, EncryptedSharedPreferences on Android)
|
|
27
|
+
- **Diffie-Hellman / X25519 Key Exchange**: Configurable key agreement with `CryptoOptions`
|
|
28
|
+
- **Shared-Key Encryption**: AES-GCM encrypt/decrypt using a derived shared secret
|
|
29
|
+
- **Obfuscation**: Local string obfuscation with an explicit secret (not for credentials at rest)
|
|
30
30
|
|
|
31
31
|
### Network Security & Monitoring
|
|
32
32
|
|
|
33
|
+
- **JWS Request Signing (RFC 7515)**: HMAC-signed compact JWS tokens for `fetch` requests (HS256/HS384/HS512)
|
|
34
|
+
- **SSL Certificate Pinning**: Pin SPKI SHA-256 hashes with domain allowlists
|
|
33
35
|
- **Network Logger**: Built-in request/response logging
|
|
34
36
|
- **Android Chucker Integration**: Advanced network debugging
|
|
35
37
|
- **iOS Pulse Integration**: Network monitoring for iOS
|
|
36
|
-
- **SSL Pinning with Custom Certificates**: Enhanced security for API calls
|
|
37
38
|
|
|
38
39
|
## 📱 Supported Platforms
|
|
39
40
|
|
|
@@ -61,6 +62,34 @@ npm install react-native-security-suite
|
|
|
61
62
|
cd ios && pod install
|
|
62
63
|
```
|
|
63
64
|
|
|
65
|
+
## 📁 Project Structure
|
|
66
|
+
|
|
67
|
+
The public API is exported from `src/index.tsx`. JWS validation and normalization live in a dedicated module so TypeScript and native code share the same contract.
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
src/
|
|
71
|
+
├── index.tsx # Public API (fetch, crypto, SecureStorage, SecureView)
|
|
72
|
+
├── jws.ts # JWS types, validation, payload normalization
|
|
73
|
+
├── SecureView.tsx # Screenshot-protected view component
|
|
74
|
+
└── helpers.ts # Internal utilities
|
|
75
|
+
|
|
76
|
+
android/src/main/java/com/securitysuite/
|
|
77
|
+
├── SecureStorageNative.java # EncryptedSharedPreferences + Android Keystore
|
|
78
|
+
├── JWSGenerator.java # RFC 7515 compact JWS (sign + verify)
|
|
79
|
+
├── JwsFetchPayload.java # Default fetch signing payload builder
|
|
80
|
+
├── SecuritySuiteModule.java
|
|
81
|
+
└── Sslpinning.java # fetch + SSL pinning + JWS header injection
|
|
82
|
+
|
|
83
|
+
ios/
|
|
84
|
+
├── SecureStorageNative.swift # Keychain-backed secure storage
|
|
85
|
+
├── KeychainHelper.swift # Keychain read/write/query helpers
|
|
86
|
+
├── JWSGenerator.swift # RFC 7515 compact JWS (sign + verify)
|
|
87
|
+
├── SecuritySuite.swift # Native module + fetch
|
|
88
|
+
└── SslPinning.swift
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**JWS flow:** JavaScript validates options in `src/jws.ts` (secret, algorithm, headers, payload shape), normalizes the payload to the exact UTF-8 signing string, then calls native `generateJWS` on Android/iOS. For `fetch`, native code builds the signing payload when `jws.payload` is omitted, signs it, and attaches the compact token to the request header.
|
|
92
|
+
|
|
64
93
|
## 📖 Usage Examples
|
|
65
94
|
|
|
66
95
|
### 1. Root/Jailbreak Detection
|
|
@@ -106,33 +135,22 @@ const SensitiveScreen = () => {
|
|
|
106
135
|
};
|
|
107
136
|
```
|
|
108
137
|
|
|
109
|
-
### 3.
|
|
138
|
+
### 3. Obfuscation (local only)
|
|
110
139
|
|
|
111
|
-
|
|
140
|
+
Obfuscation requires an explicit secret and is intended for non-sensitive local encoding — not for credentials, tokens, or PII at rest. Use `SecureStorage` for persisted secrets.
|
|
112
141
|
|
|
113
142
|
```javascript
|
|
114
|
-
import {
|
|
115
|
-
|
|
116
|
-
const handleEncryption = async () => {
|
|
117
|
-
// Soft encryption (faster, less secure)
|
|
118
|
-
const softEncrypted = await encrypt('Sensitive data', false);
|
|
119
|
-
console.log('Soft encrypted:', softEncrypted);
|
|
120
|
-
|
|
121
|
-
const softDecrypted = await decrypt(softEncrypted, false);
|
|
122
|
-
console.log('Soft decrypted:', softDecrypted);
|
|
123
|
-
|
|
124
|
-
// Hard encryption (slower, more secure)
|
|
125
|
-
const hardEncrypted = await encrypt('Highly sensitive data', true);
|
|
126
|
-
console.log('Hard encrypted:', hardEncrypted);
|
|
143
|
+
import { obfuscate, deobfuscate } from 'react-native-security-suite';
|
|
127
144
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
};
|
|
145
|
+
const encoded = await obfuscate('local-cache-value', 'app-specific-secret');
|
|
146
|
+
const decoded = await deobfuscate(encoded, 'app-specific-secret');
|
|
131
147
|
```
|
|
132
148
|
|
|
149
|
+
> `encrypt()` / `decrypt()` remain available but are deprecated. They now require an explicit `secretKey` and should be replaced with `obfuscate()` / `deobfuscate()` or `SecureStorage`.
|
|
150
|
+
|
|
133
151
|
### 4. Secure Storage
|
|
134
152
|
|
|
135
|
-
Store sensitive data
|
|
153
|
+
Store sensitive data in hardware-backed encrypted storage. On **iOS**, values are stored in the Keychain (`kSecClassGenericPassword`). On **Android**, values are encrypted with **EncryptedSharedPreferences** backed by Android Keystore (`AES256_GCM`).
|
|
136
154
|
|
|
137
155
|
```javascript
|
|
138
156
|
import { SecureStorage } from 'react-native-security-suite';
|
|
@@ -152,24 +170,39 @@ const handleSecureStorage = async () => {
|
|
|
152
170
|
})
|
|
153
171
|
);
|
|
154
172
|
|
|
155
|
-
// Retrieve
|
|
173
|
+
// Retrieve data
|
|
156
174
|
const token = await SecureStorage.getItem('userToken');
|
|
157
175
|
const credentials = await SecureStorage.getItem('userCredentials');
|
|
176
|
+
const allKeys = await SecureStorage.getAllKeys();
|
|
158
177
|
|
|
159
178
|
console.log('Retrieved token:', token);
|
|
160
179
|
console.log('Retrieved credentials:', JSON.parse(credentials));
|
|
180
|
+
console.log('Stored keys:', allKeys);
|
|
161
181
|
|
|
162
182
|
// Remove sensitive data
|
|
163
183
|
await SecureStorage.removeItem('userToken');
|
|
184
|
+
|
|
185
|
+
// Clear all secure storage entries
|
|
186
|
+
await SecureStorage.clear();
|
|
164
187
|
} catch (error) {
|
|
165
188
|
console.error('Secure storage error:', error);
|
|
166
189
|
}
|
|
167
190
|
};
|
|
168
191
|
```
|
|
169
192
|
|
|
170
|
-
|
|
193
|
+
#### Security Guarantee
|
|
194
|
+
|
|
195
|
+
`SecureStorage` no longer uses AsyncStorage or any JavaScript-side persistence. All keys and values are:
|
|
171
196
|
|
|
172
|
-
|
|
197
|
+
- **Encrypted at rest** — AES-GCM (Android) / Keychain-protected blobs (iOS)
|
|
198
|
+
- **Hardware-backed where available** — Android Keystore master key; iOS Keychain with `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`
|
|
199
|
+
- **System-managed keys** — No hardcoded salts or encryption keys in app code; the OS generates and protects master keys
|
|
200
|
+
|
|
201
|
+
If a native read/write fails (for example, KeyStore initialization errors on Android), the Promise rejects with a clear `Secure storage operation failed` error.
|
|
202
|
+
|
|
203
|
+
### 5. Diffie-Hellman / X25519 Key Exchange
|
|
204
|
+
|
|
205
|
+
Implement secure key exchange with optional `CryptoOptions` (defaults: X25519 key agreement, AES-256-GCM, HMAC-SHA-512):
|
|
173
206
|
|
|
174
207
|
```javascript
|
|
175
208
|
import {
|
|
@@ -179,33 +212,124 @@ import {
|
|
|
179
212
|
decryptBySharedKey,
|
|
180
213
|
} from 'react-native-security-suite';
|
|
181
214
|
|
|
215
|
+
const cryptoOptions = {
|
|
216
|
+
keyAgreementAlgorithm: 'X25519',
|
|
217
|
+
keyType: 'OKP',
|
|
218
|
+
encryptionKeyAlgorithm: 'AES-256',
|
|
219
|
+
hmacAlgorithm: 'HMAC-SHA-512',
|
|
220
|
+
cipher: 'AES-GCM',
|
|
221
|
+
};
|
|
222
|
+
|
|
182
223
|
const handleKeyExchange = async () => {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const clientPublicKey = await getPublicKey();
|
|
186
|
-
console.log('Client public key:', clientPublicKey);
|
|
224
|
+
const clientPublicKey = await getPublicKey();
|
|
225
|
+
const serverPublicKey = 'SERVER_PUBLIC_KEY_FROM_API';
|
|
187
226
|
|
|
188
|
-
|
|
189
|
-
|
|
227
|
+
await getSharedKey(serverPublicKey, cryptoOptions);
|
|
228
|
+
|
|
229
|
+
const encryptedMessage = await encryptBySharedKey('Secret message', cryptoOptions);
|
|
230
|
+
const decryptedMessage = await decryptBySharedKey(encryptedMessage, cryptoOptions);
|
|
231
|
+
};
|
|
232
|
+
```
|
|
190
233
|
|
|
191
|
-
|
|
192
|
-
const sharedKey = await getSharedKey(serverPublicKey);
|
|
193
|
-
console.log('Shared key generated:', sharedKey);
|
|
234
|
+
### 6. JWS Generation (RFC 7515)
|
|
194
235
|
|
|
195
|
-
|
|
196
|
-
const encryptedMessage = await encryptBySharedKey('Secret message');
|
|
197
|
-
console.log('Encrypted message:', encryptedMessage);
|
|
236
|
+
Generate [RFC 7515](https://datatracker.ietf.org/doc/html/rfc7515) compact JWS tokens. Supported algorithms: **HS256**, **HS384**, **HS512**. An explicit `secret` is always required.
|
|
198
237
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
238
|
+
**How it works**
|
|
239
|
+
|
|
240
|
+
1. TypeScript (`src/jws.ts`) validates `secret`, `algorithm`, and custom headers.
|
|
241
|
+
2. The payload is normalized to the exact UTF-8 string used for signing (objects/arrays are `JSON.stringify`'d; `undefined`/`null`/`''` become an empty payload).
|
|
242
|
+
3. Native code builds sorted protected headers (always including `alg`), base64url-encodes header and payload, signs `base64url(header).base64url(payload)` with HMAC, and returns the compact token.
|
|
243
|
+
|
|
244
|
+
**Empty payload** (`undefined`, `null`, or `''`) produces three segments with an empty middle segment:
|
|
245
|
+
|
|
246
|
+
```javascript
|
|
247
|
+
import { generateJWS } from 'react-native-security-suite';
|
|
248
|
+
|
|
249
|
+
const jws = await generateJWS({
|
|
250
|
+
algorithm: 'HS256',
|
|
251
|
+
secret: 'my-temporary-secret',
|
|
252
|
+
headers: { kid: 'key-1' },
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// compact form: <protectedHeader>.<payload>.<signature>
|
|
256
|
+
// jws.split('.').length === 3
|
|
257
|
+
// jws.split('.')[1] === '' // empty payload segment
|
|
206
258
|
```
|
|
207
259
|
|
|
208
|
-
|
|
260
|
+
**JSON payload:**
|
|
261
|
+
|
|
262
|
+
```javascript
|
|
263
|
+
const jws = await generateJWS({
|
|
264
|
+
algorithm: 'HS512',
|
|
265
|
+
secret: 'my-temporary-secret',
|
|
266
|
+
payload: { amount: 1000, currency: 'USD' },
|
|
267
|
+
headers: { kid: 'key-1', request_id: 'req-123', typ: 'JWS' },
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Header rules:** keys must match `^[a-zA-Z][a-zA-Z0-9_-]*$`; values must be JSON primitives (`string`, `number`, `boolean`, `null`). String values must be printable ASCII (`0x20`–`0x7E`). If both `algorithm` and `headers.alg` are set, they must match.
|
|
272
|
+
|
|
273
|
+
> **Security note:** HS* JWS on mobile uses a client-side shared secret. It helps with request integrity when combined with TLS, but it is not proof against a fully compromised client.
|
|
274
|
+
|
|
275
|
+
### 7. Fetch Request Signing with JWS
|
|
276
|
+
|
|
277
|
+
Pass a `jws` object on `fetch` options. The signed token is sent as an HTTP header (default: `X-Request-Signature`).
|
|
278
|
+
|
|
279
|
+
```javascript
|
|
280
|
+
import { fetch } from 'react-native-security-suite';
|
|
281
|
+
|
|
282
|
+
await fetch('https://api.example.com/payments', {
|
|
283
|
+
method: 'POST',
|
|
284
|
+
headers: { 'Content-Type': 'application/json' },
|
|
285
|
+
body: { amount: 1000 },
|
|
286
|
+
jws: {
|
|
287
|
+
algorithm: 'HS256',
|
|
288
|
+
secret: 'temporary-session-secret',
|
|
289
|
+
headers: {
|
|
290
|
+
kid: 'key-1',
|
|
291
|
+
request_id: 'req-123',
|
|
292
|
+
timestamp: Date.now(),
|
|
293
|
+
nonce: 'unique-nonce-per-request',
|
|
294
|
+
},
|
|
295
|
+
headerName: 'X-Request-Signature',
|
|
296
|
+
detached: false,
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Default signing payload** (when `jws.payload` is omitted): native code builds a sorted JSON object from:
|
|
302
|
+
|
|
303
|
+
| Field | Source |
|
|
304
|
+
|-------|--------|
|
|
305
|
+
| `method` | HTTP method (uppercased) |
|
|
306
|
+
| `path` | URL path (`/` if empty) |
|
|
307
|
+
| `query` | Query string (if present) |
|
|
308
|
+
| `bodyHash` | Base64url SHA-256 of request body (if body present) |
|
|
309
|
+
| `timestamp`, `nonce`, `request_id` | Copied from `jws.headers` when provided |
|
|
310
|
+
|
|
311
|
+
**Explicit fetch payload:** when you set `jws.payload`, it must already be a string (use `JSON.stringify` for objects). For fetch, object payloads are not auto-serialized — only `generateJWS` accepts object payloads directly.
|
|
312
|
+
|
|
313
|
+
```javascript
|
|
314
|
+
jws: {
|
|
315
|
+
secret: 'temporary-session-secret',
|
|
316
|
+
payload: JSON.stringify({ amount: 1000, currency: 'USD' }),
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**Detached fetch signing** — set `jws.detached: true` to send `header..signature` while the raw payload remains in the request body:
|
|
321
|
+
|
|
322
|
+
```javascript
|
|
323
|
+
jws: {
|
|
324
|
+
secret: 'temporary-session-secret',
|
|
325
|
+
detached: true,
|
|
326
|
+
headers: { kid: 'key-1', request_id: 'req-123' },
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Migration from legacy options:** `options.keyId`, `options.requestId`, and top-level `options.secret` are deprecated. Use `options.jws` with `secret`, `headers.kid`, and `headers.request_id` instead. Legacy signing always used detached HS256 and the `X-JWS-Signature` header.
|
|
331
|
+
|
|
332
|
+
### 8. SSL Certificate Pinning
|
|
209
333
|
|
|
210
334
|
Secure your API communications with certificate pinning:
|
|
211
335
|
|
|
@@ -241,7 +365,7 @@ const secureApiCall = async () => {
|
|
|
241
365
|
};
|
|
242
366
|
```
|
|
243
367
|
|
|
244
|
-
###
|
|
368
|
+
### 9. Network Monitoring & Debugging
|
|
245
369
|
|
|
246
370
|
Monitor network requests in development:
|
|
247
371
|
|
|
@@ -272,37 +396,81 @@ const monitoredRequest = async () => {
|
|
|
272
396
|
|
|
273
397
|
### Security Detection
|
|
274
398
|
|
|
275
|
-
- `deviceHasSecurityRisk()`
|
|
399
|
+
- `deviceHasSecurityRisk()` — Detect rooted/jailbroken devices
|
|
276
400
|
|
|
277
401
|
### Encryption & Storage
|
|
278
402
|
|
|
279
|
-
- `
|
|
280
|
-
- `
|
|
281
|
-
- `SecureStorage` -
|
|
403
|
+
- `obfuscate(input, secret)` — Local obfuscation with explicit secret
|
|
404
|
+
- `deobfuscate(input, secret)` — Reverse `obfuscate`
|
|
405
|
+
- `SecureStorage` — Hardware-backed encrypted storage (`setItem`, `getItem`, `removeItem`, `getAllKeys`, `clear`, `multiGet`, `multiSet`, `multiRemove`)
|
|
406
|
+
- `encrypt(text, hardEncryption?, secretKey?)` — **deprecated**; requires `secretKey`
|
|
407
|
+
- `decrypt(encryptedText, hardEncryption?, secretKey?)` — **deprecated**; requires `secretKey`
|
|
282
408
|
|
|
283
409
|
### Key Exchange
|
|
284
410
|
|
|
285
|
-
- `getPublicKey()`
|
|
286
|
-
- `getSharedKey(serverPublicKey)`
|
|
287
|
-
- `encryptBySharedKey(text)` -
|
|
288
|
-
- `decryptBySharedKey(encryptedText)` -
|
|
411
|
+
- `getPublicKey()` — Generate client public key (JWK)
|
|
412
|
+
- `getSharedKey(serverPublicKey, options?)` — Derive shared secret; accepts `CryptoOptions`
|
|
413
|
+
- `encryptBySharedKey(text, options?)` — AES-GCM encrypt with derived key
|
|
414
|
+
- `decryptBySharedKey(encryptedText, options?)` — AES-GCM decrypt with derived key
|
|
415
|
+
|
|
416
|
+
#### `CryptoOptions`
|
|
417
|
+
|
|
418
|
+
| Option | Default | Description |
|
|
419
|
+
|--------|---------|-------------|
|
|
420
|
+
| `keyAgreementAlgorithm` | `'X25519'` | Key agreement (e.g. `X25519`, `ECDH`) |
|
|
421
|
+
| `keyType` | `'OKP'` | JWK key type (`OKP`, `EC`) |
|
|
422
|
+
| `encryptionKeyAlgorithm` | `'AES-256'` | Symmetric key algorithm |
|
|
423
|
+
| `hmacAlgorithm` | `'HMAC-SHA-512'` | HMAC for signing and key derivation |
|
|
424
|
+
| `cipher` | `'AES-GCM'` | AEAD cipher mode |
|
|
425
|
+
| `tagLength` | `128` | GCM authentication tag length (bits) |
|
|
426
|
+
| `ivLength` | `12` | GCM IV length (bytes) |
|
|
427
|
+
|
|
428
|
+
### JWS
|
|
429
|
+
|
|
430
|
+
- `generateJWS(options: GenerateJWSOptions)` — Generate compact or detached JWS
|
|
431
|
+
|
|
432
|
+
#### `GenerateJWSOptions`
|
|
433
|
+
|
|
434
|
+
| Field | Required | Description |
|
|
435
|
+
|-------|----------|-------------|
|
|
436
|
+
| `secret` | yes | Non-empty HMAC secret |
|
|
437
|
+
| `algorithm` | no | `HS256` (default), `HS384`, or `HS512` |
|
|
438
|
+
| `payload` | no | String, object, array, number, boolean, `null`, or `undefined` |
|
|
439
|
+
| `headers` | no | Custom protected headers (`kid`, `request_id`, etc.) |
|
|
440
|
+
|
|
441
|
+
#### `JwsFetchOptions` (on `fetch` options)
|
|
442
|
+
|
|
443
|
+
| Field | Required | Description |
|
|
444
|
+
|-------|----------|-------------|
|
|
445
|
+
| `secret` | yes | Non-empty HMAC secret |
|
|
446
|
+
| `algorithm` | no | `HS256` (default), `HS384`, or `HS512` |
|
|
447
|
+
| `headers` | no | Protected headers; `timestamp`/`nonce`/`request_id` also feed the default payload |
|
|
448
|
+
| `payload` | no | Explicit signing string; omit to use the default fetch payload |
|
|
449
|
+
| `detached` | no | Detached compact form (default: `false`) |
|
|
450
|
+
| `headerName` | no | Request header name (default: `X-Request-Signature`) |
|
|
451
|
+
|
|
452
|
+
Exported types: `JwsAlgorithm`, `JwsPayload`, `JwsHeaders`, `JwsHeaderValue`, `GenerateJWSOptions`, `JwsFetchOptions`.
|
|
289
453
|
|
|
290
454
|
### Network Security
|
|
291
455
|
|
|
292
|
-
- `fetch(url, options, loggerEnabled?)`
|
|
456
|
+
- `fetch(url, options, loggerEnabled?)` — Secure fetch with SSL pinning and optional JWS signing
|
|
457
|
+
|
|
458
|
+
`fetch` `options` also accepts `certificates` + `validDomains` for SSL pinning, and deprecated top-level `keyId`, `requestId`, `secret` for legacy JWS.
|
|
293
459
|
|
|
294
460
|
### UI Components
|
|
295
461
|
|
|
296
|
-
- `SecureView`
|
|
462
|
+
- `SecureView` — Screenshot-protected view component
|
|
297
463
|
|
|
298
464
|
## 🛡️ Security Best Practices
|
|
299
465
|
|
|
300
|
-
1. **Always validate certificates**
|
|
301
|
-
2. **Detect compromised devices**
|
|
302
|
-
3. **
|
|
303
|
-
4. **Protect sensitive UI**
|
|
304
|
-
5. **
|
|
305
|
-
6. **
|
|
466
|
+
1. **Always validate certificates** — Use SSL pinning for production APIs
|
|
467
|
+
2. **Detect compromised devices** — Check for root/jailbreak before sensitive operations
|
|
468
|
+
3. **Store secrets in SecureStorage** — Use hardware-backed storage for tokens and credentials
|
|
469
|
+
4. **Protect sensitive UI** — Wrap sensitive content in `SecureView`
|
|
470
|
+
5. **Sign requests with JWS** — Include `timestamp`, `nonce`, and `request_id` in `jws.headers` for replay protection; never embed long-lived secrets in the app binary
|
|
471
|
+
6. **Use detached JWS for body signing** — When the request body is the signed payload, set `jws.detached: true` so the body is not duplicated inside the token
|
|
472
|
+
7. **Monitor network traffic** — Use built-in logging for debugging only; disable in production
|
|
473
|
+
8. **Rotate session secrets** — Treat `jws.secret` as a short-lived session or request-scoped value from your backend
|
|
306
474
|
|
|
307
475
|
## 🐛 Troubleshooting
|
|
308
476
|
|
package/android/build.gradle
CHANGED
|
@@ -76,6 +76,12 @@ android {
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
testOptions {
|
|
80
|
+
unitTests.all {
|
|
81
|
+
it.useJUnit()
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
79
85
|
lintOptions {
|
|
80
86
|
disable "GradleCompatible"
|
|
81
87
|
}
|
|
@@ -90,6 +96,7 @@ android {
|
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
buildFeatures {
|
|
99
|
+
buildConfig true
|
|
93
100
|
dataBinding true
|
|
94
101
|
}
|
|
95
102
|
}
|
|
@@ -111,5 +118,9 @@ dependencies {
|
|
|
111
118
|
implementation "com.squareup.okhttp3:okhttp:4.12.0"
|
|
112
119
|
implementation "com.scottyab:rootbeer-lib:0.1.0"
|
|
113
120
|
implementation "com.github.chuckerteam.chucker:library:4.1.0"
|
|
121
|
+
implementation "androidx.security:security-crypto:1.1.0-alpha06"
|
|
122
|
+
|
|
123
|
+
testImplementation "junit:junit:4.13.2"
|
|
124
|
+
testImplementation "com.facebook.react:react-native:+"
|
|
114
125
|
}
|
|
115
126
|
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
package com.securitysuite;
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReadableMap;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Configurable cryptographic parameters with secure defaults and whitelist validation.
|
|
7
|
+
*/
|
|
8
|
+
public final class CryptoConfig {
|
|
9
|
+
public static final String DEFAULT_KEY_AGREEMENT = "ECDH";
|
|
10
|
+
public static final String DEFAULT_KEY_FACTORY = "EC";
|
|
11
|
+
public static final String DEFAULT_ENCRYPTION_KEY_ALGORITHM = "AES";
|
|
12
|
+
public static final String DEFAULT_HMAC_KEY_ALGORITHM = "HmacSHA256";
|
|
13
|
+
public static final String DEFAULT_CIPHER_TRANSFORMATION = "AES/GCM/NoPadding";
|
|
14
|
+
public static final int DEFAULT_GCM_TAG_LENGTH = 128;
|
|
15
|
+
public static final int DEFAULT_GCM_IV_LENGTH = 12;
|
|
16
|
+
|
|
17
|
+
public final String keyAgreementAlgorithm;
|
|
18
|
+
public final String keyFactoryAlgorithm;
|
|
19
|
+
public final String encryptionKeyAlgorithm;
|
|
20
|
+
public final String hmacKeyAlgorithm;
|
|
21
|
+
public final String cipherTransformation;
|
|
22
|
+
public final int gcmTagLength;
|
|
23
|
+
public final int gcmIvLength;
|
|
24
|
+
|
|
25
|
+
private CryptoConfig(
|
|
26
|
+
String keyAgreementAlgorithm,
|
|
27
|
+
String keyFactoryAlgorithm,
|
|
28
|
+
String encryptionKeyAlgorithm,
|
|
29
|
+
String hmacKeyAlgorithm,
|
|
30
|
+
String cipherTransformation,
|
|
31
|
+
int gcmTagLength,
|
|
32
|
+
int gcmIvLength
|
|
33
|
+
) {
|
|
34
|
+
this.keyAgreementAlgorithm = keyAgreementAlgorithm;
|
|
35
|
+
this.keyFactoryAlgorithm = keyFactoryAlgorithm;
|
|
36
|
+
this.encryptionKeyAlgorithm = encryptionKeyAlgorithm;
|
|
37
|
+
this.hmacKeyAlgorithm = hmacKeyAlgorithm;
|
|
38
|
+
this.cipherTransformation = cipherTransformation;
|
|
39
|
+
this.gcmTagLength = gcmTagLength;
|
|
40
|
+
this.gcmIvLength = gcmIvLength;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public static CryptoConfig defaults() {
|
|
44
|
+
return new CryptoConfig(
|
|
45
|
+
DEFAULT_KEY_AGREEMENT,
|
|
46
|
+
DEFAULT_KEY_FACTORY,
|
|
47
|
+
DEFAULT_ENCRYPTION_KEY_ALGORITHM,
|
|
48
|
+
DEFAULT_HMAC_KEY_ALGORITHM,
|
|
49
|
+
DEFAULT_CIPHER_TRANSFORMATION,
|
|
50
|
+
DEFAULT_GCM_TAG_LENGTH,
|
|
51
|
+
DEFAULT_GCM_IV_LENGTH
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public static CryptoConfig fromReadableMap(ReadableMap options) {
|
|
56
|
+
if (options == null) {
|
|
57
|
+
return defaults();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return new CryptoConfig(
|
|
61
|
+
validateKeyAgreement(readString(options, "keyAgreementAlgorithm", DEFAULT_KEY_AGREEMENT)),
|
|
62
|
+
validateKeyFactory(readString(options, "keyFactoryAlgorithm", DEFAULT_KEY_FACTORY)),
|
|
63
|
+
validateEncryptionKeyAlgorithm(
|
|
64
|
+
readString(options, "encryptionKeyAlgorithm", DEFAULT_ENCRYPTION_KEY_ALGORITHM)
|
|
65
|
+
),
|
|
66
|
+
validateHmacKeyAlgorithm(readString(options, "hmacKeyAlgorithm", DEFAULT_HMAC_KEY_ALGORITHM)),
|
|
67
|
+
validateCipherTransformation(
|
|
68
|
+
readString(options, "cipherTransformation", DEFAULT_CIPHER_TRANSFORMATION)
|
|
69
|
+
),
|
|
70
|
+
options.hasKey("gcmTagLength") ? options.getInt("gcmTagLength") : DEFAULT_GCM_TAG_LENGTH,
|
|
71
|
+
options.hasKey("gcmIvLength") ? options.getInt("gcmIvLength") : DEFAULT_GCM_IV_LENGTH
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public CryptoConfig merge(ReadableMap options) {
|
|
76
|
+
return options == null ? this : fromReadableMap(mergeMaps(this, options));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private static ReadableMap mergeMaps(CryptoConfig base, ReadableMap overrides) {
|
|
80
|
+
com.facebook.react.bridge.WritableMap map = com.facebook.react.bridge.Arguments.createMap();
|
|
81
|
+
map.putString("keyAgreementAlgorithm", base.keyAgreementAlgorithm);
|
|
82
|
+
map.putString("keyFactoryAlgorithm", base.keyFactoryAlgorithm);
|
|
83
|
+
map.putString("encryptionKeyAlgorithm", base.encryptionKeyAlgorithm);
|
|
84
|
+
map.putString("hmacKeyAlgorithm", base.hmacKeyAlgorithm);
|
|
85
|
+
map.putString("cipherTransformation", base.cipherTransformation);
|
|
86
|
+
map.putInt("gcmTagLength", base.gcmTagLength);
|
|
87
|
+
map.putInt("gcmIvLength", base.gcmIvLength);
|
|
88
|
+
|
|
89
|
+
if (overrides.hasKey("keyAgreementAlgorithm")) {
|
|
90
|
+
map.putString("keyAgreementAlgorithm", overrides.getString("keyAgreementAlgorithm"));
|
|
91
|
+
}
|
|
92
|
+
if (overrides.hasKey("keyFactoryAlgorithm")) {
|
|
93
|
+
map.putString("keyFactoryAlgorithm", overrides.getString("keyFactoryAlgorithm"));
|
|
94
|
+
}
|
|
95
|
+
if (overrides.hasKey("encryptionKeyAlgorithm")) {
|
|
96
|
+
map.putString("encryptionKeyAlgorithm", overrides.getString("encryptionKeyAlgorithm"));
|
|
97
|
+
}
|
|
98
|
+
if (overrides.hasKey("hmacKeyAlgorithm")) {
|
|
99
|
+
map.putString("hmacKeyAlgorithm", overrides.getString("hmacKeyAlgorithm"));
|
|
100
|
+
}
|
|
101
|
+
if (overrides.hasKey("cipherTransformation")) {
|
|
102
|
+
map.putString("cipherTransformation", overrides.getString("cipherTransformation"));
|
|
103
|
+
}
|
|
104
|
+
if (overrides.hasKey("gcmTagLength")) {
|
|
105
|
+
map.putInt("gcmTagLength", overrides.getInt("gcmTagLength"));
|
|
106
|
+
}
|
|
107
|
+
if (overrides.hasKey("gcmIvLength")) {
|
|
108
|
+
map.putInt("gcmIvLength", overrides.getInt("gcmIvLength"));
|
|
109
|
+
}
|
|
110
|
+
return map;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private static String readString(ReadableMap map, String key, String defaultValue) {
|
|
114
|
+
if (map.hasKey(key) && map.getString(key) != null) {
|
|
115
|
+
return map.getString(key).trim();
|
|
116
|
+
}
|
|
117
|
+
return defaultValue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private static String validateKeyAgreement(String algorithm) {
|
|
121
|
+
if ("ECDH".equals(algorithm)) {
|
|
122
|
+
return algorithm;
|
|
123
|
+
}
|
|
124
|
+
throw new IllegalArgumentException("Unsupported keyAgreementAlgorithm: " + algorithm);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private static String validateKeyFactory(String algorithm) {
|
|
128
|
+
if ("EC".equals(algorithm)) {
|
|
129
|
+
return algorithm;
|
|
130
|
+
}
|
|
131
|
+
throw new IllegalArgumentException("Unsupported keyFactoryAlgorithm: " + algorithm);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private static String validateEncryptionKeyAlgorithm(String algorithm) {
|
|
135
|
+
if ("AES".equals(algorithm)) {
|
|
136
|
+
return algorithm;
|
|
137
|
+
}
|
|
138
|
+
throw new IllegalArgumentException("Unsupported encryptionKeyAlgorithm: " + algorithm);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private static String validateHmacKeyAlgorithm(String algorithm) {
|
|
142
|
+
switch (algorithm) {
|
|
143
|
+
case "HmacSHA256":
|
|
144
|
+
case "HmacSHA384":
|
|
145
|
+
case "HmacSHA512":
|
|
146
|
+
return algorithm;
|
|
147
|
+
default:
|
|
148
|
+
throw new IllegalArgumentException("Unsupported hmacKeyAlgorithm: " + algorithm);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private static String validateCipherTransformation(String transformation) {
|
|
153
|
+
if ("AES/GCM/NoPadding".equals(transformation)) {
|
|
154
|
+
return transformation;
|
|
155
|
+
}
|
|
156
|
+
throw new IllegalArgumentException("Unsupported cipherTransformation: " + transformation);
|
|
157
|
+
}
|
|
158
|
+
}
|