@symbiote-native/crypto 3.0.2 → 3.0.3
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 +62 -25
- package/build/core/aes/aes-native-module.d.ts +55 -0
- package/build/core/aes/aes-native-module.js +2 -0
- package/build/core/aes/aes-types.d.ts +36 -0
- package/build/core/aes/aes-types.js +7 -0
- package/build/core/aes/aes.d.ts +16 -0
- package/build/core/aes/aes.js +59 -0
- package/build/core/aes/index.d.ts +2 -0
- package/build/core/aes/index.js +2 -0
- package/build/core/index.d.ts +1 -0
- package/build/core/index.js +1 -0
- package/build-ngc/core/aes/aes-native-module.d.ts +55 -0
- package/build-ngc/core/aes/aes-native-module.js +3 -0
- package/build-ngc/core/aes/aes-native-module.js.map +1 -0
- package/build-ngc/core/aes/aes-types.d.ts +36 -0
- package/build-ngc/core/aes/aes-types.js +8 -0
- package/build-ngc/core/aes/aes-types.js.map +1 -0
- package/build-ngc/core/aes/aes.d.ts +16 -0
- package/build-ngc/core/aes/aes.js +60 -0
- package/build-ngc/core/aes/aes.js.map +1 -0
- package/build-ngc/core/aes/index.d.ts +2 -0
- package/build-ngc/core/aes/index.js +3 -0
- package/build-ngc/core/aes/index.js.map +1 -0
- package/build-ngc/core/index.d.ts +1 -0
- package/build-ngc/core/index.js +1 -0
- package/build-ngc/core/index.js.map +1 -1
- package/native-link.json +5 -0
- package/package.json +15 -15
- package/src/core/aes/aes-native-module.ts +101 -0
- package/src/core/aes/aes-types.ts +48 -0
- package/src/core/aes/aes.ts +142 -0
- package/src/core/aes/index.ts +17 -0
- package/src/core/index.ts +1 -0
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# @symbiote-native/crypto
|
|
2
2
|
|
|
3
3
|
A wrapper package for [SymbioteNative](../../README.md) that makes
|
|
4
|
-
[`expo-crypto`](https://github.com/expo/expo/tree/main/packages/expo-crypto)
|
|
5
|
-
secure random bytes, `randomUUID`, and string/buffer digest hashing
|
|
4
|
+
[`expo-crypto`](https://github.com/expo/expo/tree/main/packages/expo-crypto) - cryptographically
|
|
5
|
+
secure random bytes, `randomUUID`, and string/buffer digest hashing - usable from **every**
|
|
6
6
|
adapter, React, Vue, Svelte, Solid, and Angular. Like `@symbiote-native/local-auth`, every
|
|
7
7
|
function here is a plain sync/async call with no per-instance state or event stream, so there is
|
|
8
|
-
no hook/composable/service to wrap
|
|
9
|
-
`core`. AES encryption (`expo-crypto`'s `aes/` subfolder) is
|
|
8
|
+
no hook/composable/service to wrap - every adapter's entry point is a plain re-export of the same
|
|
9
|
+
`core`. AES-GCM encryption (`expo-crypto`'s `aes/` subfolder) is ported too.
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
@@ -22,39 +22,39 @@ npx @symbiote-native/cli new my-app --crypto
|
|
|
22
22
|
npx @symbiote-native/cli add --crypto
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
Either way: installs `@symbiote-native/crypto` and wires the native autolinking automatically
|
|
25
|
+
Either way: installs `@symbiote-native/crypto` and wires the native autolinking automatically - see
|
|
26
26
|
[`@symbiote-native/cli`](../cli).
|
|
27
27
|
|
|
28
28
|
<details>
|
|
29
|
-
<summary>Manual install (no CLI
|
|
29
|
+
<summary>Manual install (no CLI - installing and wiring native autolinking by hand)</summary>
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
32
|
npm install @symbiote-native/crypto
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
`expo-crypto` and `expo-modules-core` come along as regular dependencies, pinned to exact
|
|
36
|
-
versions
|
|
36
|
+
versions - never install either yourself, and never add the `expo` meta-package to your project
|
|
37
37
|
(it bundles its own Metro/Babel pipeline, which conflicts with this project's own).
|
|
38
38
|
|
|
39
39
|
## Required one-time step: native autolinking wiring
|
|
40
40
|
|
|
41
41
|
Unlike a plain RN native module, `expo-crypto`'s native code is discovered by
|
|
42
|
-
`expo-modules-autolinking`, not RN's own `react-native.config.cjs` mechanism
|
|
42
|
+
`expo-modules-autolinking`, not RN's own `react-native.config.cjs` mechanism - this needs wiring
|
|
43
43
|
into the native host app **once**, covering this package and every other `expo-modules-core`
|
|
44
44
|
package with zero further changes:
|
|
45
45
|
|
|
46
46
|
| Platform | Touches |
|
|
47
47
|
| -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
48
|
-
| iOS | `ios/Podfile`
|
|
49
|
-
| iOS | `AppDelegate.swift`
|
|
50
|
-
| Android | `settings.gradle` / `app/build.gradle`
|
|
51
|
-
| Android | `MainApplication.kt`
|
|
48
|
+
| iOS | `ios/Podfile` - add `use_expo_modules!` |
|
|
49
|
+
| iOS | `AppDelegate.swift` - Expo's runtime-bootstrap hook |
|
|
50
|
+
| Android | `settings.gradle` / `app/build.gradle` - resolve and include the Expo Gradle projects |
|
|
51
|
+
| Android | `MainApplication.kt` - Expo's bootstrap hook, plus a hand-written native-module name map (there's no `expo` meta-package here to auto-generate one) |
|
|
52
52
|
|
|
53
53
|
Full mechanics live in the `symbiote-expo-native-module` project skill. Reference
|
|
54
54
|
implementation: `examples/expo-react/ios/Podfile` and
|
|
55
55
|
`examples/expo-react/android/app/src/main/java/com/canaryexpo/MainApplication.kt`.
|
|
56
56
|
|
|
57
|
-
No platform permission strings are needed for this package
|
|
57
|
+
No platform permission strings are needed for this package - random-byte generation and
|
|
58
58
|
digest hashing touch no protected device capability.
|
|
59
59
|
|
|
60
60
|
</details>
|
|
@@ -67,11 +67,11 @@ src/core/ getRandomBytes / getRandomBytesAsync / getRandomValues / randomUUI
|
|
|
67
67
|
option/result types, and the CryptoError validation-error class.
|
|
68
68
|
native-module.ts resolves the native module via expo-modules-core's
|
|
69
69
|
requireNativeModule.
|
|
70
|
-
src/angular/ @symbiote-native/crypto/angular
|
|
70
|
+
src/angular/ @symbiote-native/crypto/angular - export * from '../core'
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
`./react`, `./vue`, `./svelte`, and `./solid` are `exports`-map aliases straight onto `src/core
|
|
74
|
-
|
|
73
|
+
`./react`, `./vue`, `./svelte`, and `./solid` are `exports`-map aliases straight onto `src/core/`:
|
|
74
|
+
no physical per-framework file, since there's nothing to subscribe to or clean up. `./angular`
|
|
75
75
|
stays a physical file/subpath since Angular ships through a separate `ngc`/AOT build (`build-ngc/`).
|
|
76
76
|
|
|
77
77
|
## Use it
|
|
@@ -245,9 +245,9 @@ export class CryptoScreen {
|
|
|
245
245
|
}
|
|
246
246
|
```
|
|
247
247
|
|
|
248
|
-
There's no per-instance service to `inject()` in the Angular case
|
|
248
|
+
There's no per-instance service to `inject()` in the Angular case - every function is a plain
|
|
249
249
|
free function off the core package, called straight from the component body (or the constructor,
|
|
250
|
-
on Angular). All six examples mirror the real canary demo screens
|
|
250
|
+
on Angular). All six examples mirror the real canary demo screens:
|
|
251
251
|
`examples/expo-react/screens/CryptoScreen.tsx`, `examples/expo-vue-sfc/screens/CryptoScreen.vue`,
|
|
252
252
|
`examples/expo-vue-tsx/screens/CryptoScreen.tsx`, `examples/expo-svelte/screens/CryptoScreen.svelte`,
|
|
253
253
|
`examples/expo-solid/screens/CryptoScreen.tsx`, `examples/expo-angular/src/screens/CryptoScreen.ts`.
|
|
@@ -271,13 +271,39 @@ digest(algorithm: CryptoDigestAlgorithm, data: BufferSource): Promise<ArrayBuffe
|
|
|
271
271
|
|
|
272
272
|
Plus `CryptoDigestAlgorithm` (`SHA1`/`SHA256`/`SHA384`/`SHA512`/`MD2`\*/`MD4`\*/`MD5`, \*iOS
|
|
273
273
|
only), `CryptoEncoding` (`HEX`/`BASE64`), `ICryptoDigestOptions`, `IDigest`, `ITypedArray` (and its
|
|
274
|
-
`IUintBasedTypedArray`/`IIntBasedTypedArray` halves), and the `CryptoError` validation-error class
|
|
275
|
-
|
|
274
|
+
`IUintBasedTypedArray`/`IIntBasedTypedArray` halves), and the `CryptoError` validation-error class,
|
|
275
|
+
ported from upstream's `Crypto.types.ts`/`Crypto.ts`, renamed with this repo's `I`-prefix
|
|
276
276
|
convention for exported types (`ts-js-best-practices`).
|
|
277
277
|
|
|
278
|
+
### AES-GCM
|
|
279
|
+
|
|
280
|
+
Ported from upstream's `aes/` subfolder onto a second native module, `ExpoCryptoAES`:
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
import {
|
|
284
|
+
AESEncryptionKey,
|
|
285
|
+
AESKeySize,
|
|
286
|
+
AESSealedData,
|
|
287
|
+
aesDecryptAsync,
|
|
288
|
+
aesEncryptAsync,
|
|
289
|
+
} from '@symbiote-native/crypto';
|
|
290
|
+
|
|
291
|
+
const key = await AESEncryptionKey.generate(AESKeySize.AES256);
|
|
292
|
+
const sealed = await aesEncryptAsync(plaintextBase64, key);
|
|
293
|
+
const plain = await aesDecryptAsync(sealed, key, { output: 'base64' });
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
`AESEncryptionKey` and `AESSealedData` extend the native classes: `generate`/`import`/`bytes`/
|
|
297
|
+
`encoded` on the key, `fromParts`/`fromCombined` plus `iv`/`tag`/`ciphertext`/`combined` on the
|
|
298
|
+
sealed data. A string input is base64. `ArrayBuffer` inputs become a `Uint8Array`, and binary
|
|
299
|
+
`nonce.bytes`/`additionalData` go to native as base64, as upstream does. `tagLength` is ignored on
|
|
300
|
+
Apple (always 16), and `AES192` is unsupported on web. Option and input types carry the
|
|
301
|
+
`I`-prefix: `IAesEncryptOptions`, `IAesDecryptOptions`, `IAesSealedDataConfig`, `IBinaryInput`,
|
|
302
|
+
`IGcmNonceParam`, `IGcmTagByteLength`.
|
|
303
|
+
|
|
278
304
|
```ts
|
|
279
305
|
import { digestStringAsync, randomUUID } from '@symbiote-native/crypto';
|
|
280
|
-
// or the framework-scoped entry points
|
|
306
|
+
// or the framework-scoped entry points - identical surface, re-exported verbatim:
|
|
281
307
|
import { randomUUID } from '@symbiote-native/crypto/react';
|
|
282
308
|
import { randomUUID } from '@symbiote-native/crypto/vue';
|
|
283
309
|
import { randomUUID } from '@symbiote-native/crypto/svelte';
|
|
@@ -288,21 +314,32 @@ import { randomUUID } from '@symbiote-native/crypto/angular';
|
|
|
288
314
|
## Notes
|
|
289
315
|
|
|
290
316
|
- `getRandomBytes`/`getRandomBytesAsync` validate `byteCount` is a number in `0`-`1024`
|
|
291
|
-
(inclusive), throwing a plain `TypeError` otherwise, and floor a fractional count
|
|
317
|
+
(inclusive), throwing a plain `TypeError` otherwise, and floor a fractional count - matching
|
|
292
318
|
upstream exactly. This port skips upstream's `__DEV__`/remote-debugger `Math.random` fallback:
|
|
293
319
|
that's a React Native debugging-tool concern, not applicable to this package's native-call
|
|
294
320
|
path.
|
|
295
321
|
- `digestStringAsync` validates `algorithm`/`data`/`options.encoding` and throws `CryptoError` (a
|
|
296
|
-
`TypeError` subclass, `code: 'ERR_CRYPTO'`) on an invalid value
|
|
322
|
+
`TypeError` subclass, `code: 'ERR_CRYPTO'`) on an invalid value - mirroring upstream's own
|
|
297
323
|
`CryptoError`.
|
|
298
324
|
- `digest` prefers a native `digestAsync` when present; otherwise it allocates a fixed-size
|
|
299
325
|
output buffer (sized via the `digestLengths` lookup table) and calls the sync native `digest`.
|
|
300
326
|
|
|
327
|
+
## Common questions
|
|
328
|
+
|
|
329
|
+
- **`digestStringAsync` is `undefined`.** The native module did not load: rebuild after install.
|
|
330
|
+
- **`randomUUID` returns a promise.** Reported after a native binary update; restart and rebuild.
|
|
331
|
+
- **Which algorithms?** Prefer SHA-256 / SHA-512; SHA-1 is not recommended for security use.
|
|
332
|
+
- **`undefined` under Jest.** The native module is absent in tests; inject a fake.
|
|
333
|
+
|
|
334
|
+
Sources: [expo/expo#18215](https://github.com/expo/expo/issues/18215),
|
|
335
|
+
[expo/expo#24021](https://github.com/expo/expo/issues/24021),
|
|
336
|
+
[expo/expo#6512](https://github.com/expo/expo/issues/6512).
|
|
337
|
+
|
|
301
338
|
## Test it
|
|
302
339
|
|
|
303
|
-
No Fabric/Descriptor angle at all
|
|
340
|
+
No Fabric/Descriptor angle at all - every function here is a pure sync/async-function surface,
|
|
304
341
|
never a view or per-instance state. Tests inject a fake native-module object in place of the
|
|
305
|
-
real `requireNativeModule` resolution (`src/core/crypto.test.ts`, `vitest`)
|
|
342
|
+
real `requireNativeModule` resolution (`src/core/crypto.test.ts`, `src/core/aes/aes.test.ts`, `vitest`) - no
|
|
306
343
|
`installFabric()`, no ViewConfig. Native rendering itself is verified on-device (see the parent
|
|
307
344
|
[README](../../README.md) for the project's testing model).
|
|
308
345
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { NativeModule } from 'expo-modules-core';
|
|
2
|
+
import type { IAesDecryptOptions, IAesEncryptOptions, IAesSealedDataConfig, IBinaryInput, IGcmTagByteLength, AESKeySize } from './aes-types';
|
|
3
|
+
type IEncoding = 'hex' | 'base64';
|
|
4
|
+
declare class EncryptionKey {
|
|
5
|
+
static generate(size?: AESKeySize): Promise<EncryptionKey>;
|
|
6
|
+
static import(bytes: Uint8Array): Promise<EncryptionKey>;
|
|
7
|
+
static import(hexString: string, encoding: IEncoding): Promise<EncryptionKey>;
|
|
8
|
+
/** Key size in bits */
|
|
9
|
+
size: AESKeySize;
|
|
10
|
+
bytes(): Promise<Uint8Array>;
|
|
11
|
+
encoded(encoding: IEncoding): Promise<string>;
|
|
12
|
+
}
|
|
13
|
+
declare class SealedData {
|
|
14
|
+
static fromCombined(combined: IBinaryInput, config?: IAesSealedDataConfig): SealedData;
|
|
15
|
+
static fromParts(iv: IBinaryInput, ciphertext: IBinaryInput, tag: IBinaryInput): SealedData;
|
|
16
|
+
static fromParts(iv: IBinaryInput, ciphertextWithTag: IBinaryInput, tagLength?: number): SealedData;
|
|
17
|
+
ciphertext(options: {
|
|
18
|
+
includeTag?: boolean;
|
|
19
|
+
encoding: 'base64';
|
|
20
|
+
}): Promise<string>;
|
|
21
|
+
ciphertext(options?: {
|
|
22
|
+
includeTag?: boolean;
|
|
23
|
+
encoding?: 'bytes';
|
|
24
|
+
}): Promise<Uint8Array>;
|
|
25
|
+
ciphertext(options: {
|
|
26
|
+
includeTag?: boolean;
|
|
27
|
+
encoding?: 'base64' | 'bytes';
|
|
28
|
+
}): Promise<string | Uint8Array>;
|
|
29
|
+
iv(encoding?: 'bytes'): Promise<Uint8Array>;
|
|
30
|
+
iv(encoding: 'base64'): Promise<string>;
|
|
31
|
+
iv(encoding?: 'bytes' | 'base64'): Promise<string | Uint8Array>;
|
|
32
|
+
tag(encoding?: 'bytes'): Promise<Uint8Array>;
|
|
33
|
+
tag(encoding: 'base64'): Promise<string>;
|
|
34
|
+
tag(encoding?: 'bytes' | 'base64'): Promise<string | Uint8Array>;
|
|
35
|
+
combined(encoding?: 'bytes'): Promise<Uint8Array>;
|
|
36
|
+
combined(encoding: 'base64'): Promise<string>;
|
|
37
|
+
combined(encoding?: 'bytes' | 'base64'): Promise<string | Uint8Array>;
|
|
38
|
+
/** IV + ciphertext + tag, in bytes */
|
|
39
|
+
readonly combinedSize: number;
|
|
40
|
+
readonly ivSize: number;
|
|
41
|
+
readonly tagSize: IGcmTagByteLength;
|
|
42
|
+
}
|
|
43
|
+
export type INativeAesEncryptOptions = Omit<IAesEncryptOptions, 'nonce'> & {
|
|
44
|
+
nonce?: number | IBinaryInput | undefined;
|
|
45
|
+
};
|
|
46
|
+
declare class NativeAesCryptoModule extends NativeModule {
|
|
47
|
+
EncryptionKey: typeof EncryptionKey;
|
|
48
|
+
SealedData: typeof SealedData;
|
|
49
|
+
generateKey(size?: AESKeySize): Promise<EncryptionKey>;
|
|
50
|
+
importKey(keyInput: string | Uint8Array, encoding?: IEncoding): Promise<EncryptionKey>;
|
|
51
|
+
encryptAsync(plaintext: IBinaryInput, key: EncryptionKey, options?: INativeAesEncryptOptions): Promise<SealedData>;
|
|
52
|
+
decryptAsync(sealedData: SealedData, key: EncryptionKey, options?: IAesDecryptOptions): Promise<string | Uint8Array>;
|
|
53
|
+
}
|
|
54
|
+
export declare const expoCryptoAes: NativeAesCryptoModule;
|
|
55
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type IBinaryInput = string | Uint8Array | ArrayBuffer;
|
|
2
|
+
export declare enum AESKeySize {
|
|
3
|
+
AES128 = 128,
|
|
4
|
+
AES192 = 192,
|
|
5
|
+
AES256 = 256
|
|
6
|
+
}
|
|
7
|
+
export type IGcmTagByteLength = 16 | 15 | 14 | 13 | 12 | 8 | 4;
|
|
8
|
+
export type IAesSealedDataConfig = {
|
|
9
|
+
/** @default 12 */
|
|
10
|
+
ivLength: number;
|
|
11
|
+
/** @default 16 */
|
|
12
|
+
tagLength: IGcmTagByteLength;
|
|
13
|
+
};
|
|
14
|
+
export type IAesDecryptOptions = {
|
|
15
|
+
/** @default 'bytes' */
|
|
16
|
+
output?: 'bytes' | 'base64';
|
|
17
|
+
additionalData?: IBinaryInput;
|
|
18
|
+
};
|
|
19
|
+
export type IBase64DecryptOptions = IAesDecryptOptions & {
|
|
20
|
+
output: 'base64';
|
|
21
|
+
};
|
|
22
|
+
export type IBytesDecryptOptions = IAesDecryptOptions & {
|
|
23
|
+
output?: 'bytes';
|
|
24
|
+
};
|
|
25
|
+
export type IGcmNonceParam = {
|
|
26
|
+
/** Byte length of the nonce to generate. @default 12 */
|
|
27
|
+
length: number;
|
|
28
|
+
} | {
|
|
29
|
+
bytes: IBinaryInput;
|
|
30
|
+
};
|
|
31
|
+
export type IAesEncryptOptions = {
|
|
32
|
+
/** @default { length: 12 } */
|
|
33
|
+
nonce?: IGcmNonceParam;
|
|
34
|
+
tagLength?: IGcmTagByteLength;
|
|
35
|
+
additionalData?: IBinaryInput;
|
|
36
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export var AESKeySize;
|
|
2
|
+
(function (AESKeySize) {
|
|
3
|
+
AESKeySize[AESKeySize["AES128"] = 128] = "AES128";
|
|
4
|
+
// Unsupported on web
|
|
5
|
+
AESKeySize[AESKeySize["AES192"] = 192] = "AES192";
|
|
6
|
+
AESKeySize[AESKeySize["AES256"] = 256] = "AES256";
|
|
7
|
+
})(AESKeySize || (AESKeySize = {}));
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { expoCryptoAes } from './aes-native-module';
|
|
2
|
+
import type { IAesDecryptOptions, IAesEncryptOptions, IAesSealedDataConfig, IBase64DecryptOptions, IBinaryInput, IBytesDecryptOptions } from './aes-types';
|
|
3
|
+
export declare class AESEncryptionKey extends expoCryptoAes.EncryptionKey {
|
|
4
|
+
}
|
|
5
|
+
export declare class AESSealedData extends expoCryptoAes.SealedData {
|
|
6
|
+
/** `tag` is a separate tag, or the tag length when the ciphertext already carries it */
|
|
7
|
+
static fromParts(iv: IBinaryInput, ciphertext: IBinaryInput, tag: IBinaryInput): AESSealedData;
|
|
8
|
+
static fromParts(iv: IBinaryInput, ciphertextWithTag: IBinaryInput, tagLength?: number): AESSealedData;
|
|
9
|
+
/** `combined` holds IV, ciphertext and tag back to back */
|
|
10
|
+
static fromCombined(combined: IBinaryInput, config?: IAesSealedDataConfig): AESSealedData;
|
|
11
|
+
}
|
|
12
|
+
/** AES-GCM encryption. A string plaintext must be base64-encoded */
|
|
13
|
+
export declare function aesEncryptAsync(plaintext: IBinaryInput, key: AESEncryptionKey, options?: IAesEncryptOptions): Promise<AESSealedData>;
|
|
14
|
+
export declare function aesDecryptAsync(sealedData: AESSealedData, key: AESEncryptionKey, options: IBase64DecryptOptions): Promise<string>;
|
|
15
|
+
export declare function aesDecryptAsync(sealedData: AESSealedData, key: AESEncryptionKey, options?: IBytesDecryptOptions): Promise<Uint8Array>;
|
|
16
|
+
export declare function aesDecryptAsync(sealedData: AESSealedData, key: AESEncryptionKey, options?: IAesDecryptOptions): Promise<string | Uint8Array>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { expoCryptoAes, } from './aes-native-module.js';
|
|
2
|
+
// Native has no ArrayBuffer support, and encodes binary options as base64 strings
|
|
3
|
+
function convertBinaryInput(input, useBase64 = false) {
|
|
4
|
+
const bytes = input instanceof ArrayBuffer ? new Uint8Array(input) : input;
|
|
5
|
+
if (typeof bytes !== 'string' && useBase64) {
|
|
6
|
+
return uint8ArrayToBase64(bytes);
|
|
7
|
+
}
|
|
8
|
+
return bytes;
|
|
9
|
+
}
|
|
10
|
+
function uint8ArrayToBase64(bytes) {
|
|
11
|
+
let binaryString = '';
|
|
12
|
+
for (const byte of bytes) {
|
|
13
|
+
binaryString += String.fromCharCode(byte);
|
|
14
|
+
}
|
|
15
|
+
return btoa(binaryString);
|
|
16
|
+
}
|
|
17
|
+
export class AESEncryptionKey extends expoCryptoAes.EncryptionKey {
|
|
18
|
+
}
|
|
19
|
+
export class AESSealedData extends expoCryptoAes.SealedData {
|
|
20
|
+
static fromParts(iv, ciphertext, tag) {
|
|
21
|
+
const processedIv = convertBinaryInput(iv);
|
|
22
|
+
const processedCiphertext = convertBinaryInput(ciphertext);
|
|
23
|
+
if (tag === undefined || typeof tag === 'number') {
|
|
24
|
+
return expoCryptoAes.SealedData.fromParts(processedIv, processedCiphertext, tag);
|
|
25
|
+
}
|
|
26
|
+
return expoCryptoAes.SealedData.fromParts(processedIv, processedCiphertext, convertBinaryInput(tag));
|
|
27
|
+
}
|
|
28
|
+
/** `combined` holds IV, ciphertext and tag back to back */
|
|
29
|
+
static fromCombined(combined, config) {
|
|
30
|
+
return expoCryptoAes.SealedData.fromCombined(convertBinaryInput(combined), config);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** AES-GCM encryption. A string plaintext must be base64-encoded */
|
|
34
|
+
export function aesEncryptAsync(plaintext, key, options = {}) {
|
|
35
|
+
const { nonce, additionalData, ...rest } = options;
|
|
36
|
+
let nativeOptions = { ...rest };
|
|
37
|
+
if (nonce) {
|
|
38
|
+
nativeOptions = {
|
|
39
|
+
...nativeOptions,
|
|
40
|
+
nonce: 'bytes' in nonce ? convertBinaryInput(nonce.bytes, true) : nonce.length,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (additionalData) {
|
|
44
|
+
nativeOptions = {
|
|
45
|
+
...nativeOptions,
|
|
46
|
+
additionalData: convertBinaryInput(additionalData, true),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return expoCryptoAes.encryptAsync(convertBinaryInput(plaintext), key, nativeOptions);
|
|
50
|
+
}
|
|
51
|
+
export function aesDecryptAsync(sealedData, key, options = {}) {
|
|
52
|
+
const { additionalData, ...rest } = options;
|
|
53
|
+
return expoCryptoAes.decryptAsync(sealedData, key, {
|
|
54
|
+
...rest,
|
|
55
|
+
additionalData: additionalData
|
|
56
|
+
? convertBinaryInput(additionalData, true)
|
|
57
|
+
: undefined,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { AESEncryptionKey, AESSealedData, aesEncryptAsync, aesDecryptAsync, } from './aes';
|
|
2
|
+
export { AESKeySize, type IAesDecryptOptions, type IAesEncryptOptions, type IAesSealedDataConfig, type IBase64DecryptOptions, type IBinaryInput, type IBytesDecryptOptions, type IGcmNonceParam, type IGcmTagByteLength, } from './aes-types';
|
package/build/core/index.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { CryptoError, getRandomBytes, getRandomBytesAsync, getRandomValues, randomUUID, digestStringAsync, digest, } from './crypto';
|
|
2
|
+
export * from './aes';
|
|
2
3
|
export { CryptoDigestAlgorithm, CryptoEncoding, type ICryptoDigestOptions, type IDigest, type IUintBasedTypedArray, type IIntBasedTypedArray, type ITypedArray, } from './types';
|
package/build/core/index.js
CHANGED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { NativeModule } from 'expo-modules-core';
|
|
2
|
+
import type { IAesDecryptOptions, IAesEncryptOptions, IAesSealedDataConfig, IBinaryInput, IGcmTagByteLength, AESKeySize } from './aes-types';
|
|
3
|
+
type IEncoding = 'hex' | 'base64';
|
|
4
|
+
declare class EncryptionKey {
|
|
5
|
+
static generate(size?: AESKeySize): Promise<EncryptionKey>;
|
|
6
|
+
static import(bytes: Uint8Array): Promise<EncryptionKey>;
|
|
7
|
+
static import(hexString: string, encoding: IEncoding): Promise<EncryptionKey>;
|
|
8
|
+
/** Key size in bits */
|
|
9
|
+
size: AESKeySize;
|
|
10
|
+
bytes(): Promise<Uint8Array>;
|
|
11
|
+
encoded(encoding: IEncoding): Promise<string>;
|
|
12
|
+
}
|
|
13
|
+
declare class SealedData {
|
|
14
|
+
static fromCombined(combined: IBinaryInput, config?: IAesSealedDataConfig): SealedData;
|
|
15
|
+
static fromParts(iv: IBinaryInput, ciphertext: IBinaryInput, tag: IBinaryInput): SealedData;
|
|
16
|
+
static fromParts(iv: IBinaryInput, ciphertextWithTag: IBinaryInput, tagLength?: number): SealedData;
|
|
17
|
+
ciphertext(options: {
|
|
18
|
+
includeTag?: boolean;
|
|
19
|
+
encoding: 'base64';
|
|
20
|
+
}): Promise<string>;
|
|
21
|
+
ciphertext(options?: {
|
|
22
|
+
includeTag?: boolean;
|
|
23
|
+
encoding?: 'bytes';
|
|
24
|
+
}): Promise<Uint8Array>;
|
|
25
|
+
ciphertext(options: {
|
|
26
|
+
includeTag?: boolean;
|
|
27
|
+
encoding?: 'base64' | 'bytes';
|
|
28
|
+
}): Promise<string | Uint8Array>;
|
|
29
|
+
iv(encoding?: 'bytes'): Promise<Uint8Array>;
|
|
30
|
+
iv(encoding: 'base64'): Promise<string>;
|
|
31
|
+
iv(encoding?: 'bytes' | 'base64'): Promise<string | Uint8Array>;
|
|
32
|
+
tag(encoding?: 'bytes'): Promise<Uint8Array>;
|
|
33
|
+
tag(encoding: 'base64'): Promise<string>;
|
|
34
|
+
tag(encoding?: 'bytes' | 'base64'): Promise<string | Uint8Array>;
|
|
35
|
+
combined(encoding?: 'bytes'): Promise<Uint8Array>;
|
|
36
|
+
combined(encoding: 'base64'): Promise<string>;
|
|
37
|
+
combined(encoding?: 'bytes' | 'base64'): Promise<string | Uint8Array>;
|
|
38
|
+
/** IV + ciphertext + tag, in bytes */
|
|
39
|
+
readonly combinedSize: number;
|
|
40
|
+
readonly ivSize: number;
|
|
41
|
+
readonly tagSize: IGcmTagByteLength;
|
|
42
|
+
}
|
|
43
|
+
export type INativeAesEncryptOptions = Omit<IAesEncryptOptions, 'nonce'> & {
|
|
44
|
+
nonce?: number | IBinaryInput | undefined;
|
|
45
|
+
};
|
|
46
|
+
declare class NativeAesCryptoModule extends NativeModule {
|
|
47
|
+
EncryptionKey: typeof EncryptionKey;
|
|
48
|
+
SealedData: typeof SealedData;
|
|
49
|
+
generateKey(size?: AESKeySize): Promise<EncryptionKey>;
|
|
50
|
+
importKey(keyInput: string | Uint8Array, encoding?: IEncoding): Promise<EncryptionKey>;
|
|
51
|
+
encryptAsync(plaintext: IBinaryInput, key: EncryptionKey, options?: INativeAesEncryptOptions): Promise<SealedData>;
|
|
52
|
+
decryptAsync(sealedData: SealedData, key: EncryptionKey, options?: IAesDecryptOptions): Promise<string | Uint8Array>;
|
|
53
|
+
}
|
|
54
|
+
export declare const expoCryptoAes: NativeAesCryptoModule;
|
|
55
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aes-native-module.js","sourceRoot":"","sources":["../../../src/core/aes/aes-native-module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAmGtE,MAAM,CAAC,MAAM,aAAa,GACxB,mBAAmB,CAAwB,eAAe,CAAC,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type IBinaryInput = string | Uint8Array | ArrayBuffer;
|
|
2
|
+
export declare enum AESKeySize {
|
|
3
|
+
AES128 = 128,
|
|
4
|
+
AES192 = 192,
|
|
5
|
+
AES256 = 256
|
|
6
|
+
}
|
|
7
|
+
export type IGcmTagByteLength = 16 | 15 | 14 | 13 | 12 | 8 | 4;
|
|
8
|
+
export type IAesSealedDataConfig = {
|
|
9
|
+
/** @default 12 */
|
|
10
|
+
ivLength: number;
|
|
11
|
+
/** @default 16 */
|
|
12
|
+
tagLength: IGcmTagByteLength;
|
|
13
|
+
};
|
|
14
|
+
export type IAesDecryptOptions = {
|
|
15
|
+
/** @default 'bytes' */
|
|
16
|
+
output?: 'bytes' | 'base64';
|
|
17
|
+
additionalData?: IBinaryInput;
|
|
18
|
+
};
|
|
19
|
+
export type IBase64DecryptOptions = IAesDecryptOptions & {
|
|
20
|
+
output: 'base64';
|
|
21
|
+
};
|
|
22
|
+
export type IBytesDecryptOptions = IAesDecryptOptions & {
|
|
23
|
+
output?: 'bytes';
|
|
24
|
+
};
|
|
25
|
+
export type IGcmNonceParam = {
|
|
26
|
+
/** Byte length of the nonce to generate. @default 12 */
|
|
27
|
+
length: number;
|
|
28
|
+
} | {
|
|
29
|
+
bytes: IBinaryInput;
|
|
30
|
+
};
|
|
31
|
+
export type IAesEncryptOptions = {
|
|
32
|
+
/** @default { length: 12 } */
|
|
33
|
+
nonce?: IGcmNonceParam;
|
|
34
|
+
tagLength?: IGcmTagByteLength;
|
|
35
|
+
additionalData?: IBinaryInput;
|
|
36
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export var AESKeySize;
|
|
2
|
+
(function (AESKeySize) {
|
|
3
|
+
AESKeySize[AESKeySize["AES128"] = 128] = "AES128";
|
|
4
|
+
// Unsupported on web
|
|
5
|
+
AESKeySize[AESKeySize["AES192"] = 192] = "AES192";
|
|
6
|
+
AESKeySize[AESKeySize["AES256"] = 256] = "AES256";
|
|
7
|
+
})(AESKeySize || (AESKeySize = {}));
|
|
8
|
+
//# sourceMappingURL=aes-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aes-types.js","sourceRoot":"","sources":["../../../src/core/aes/aes-types.ts"],"names":[],"mappings":"AAGA,MAAM,CAAN,IAAY,UAKX;AALD,WAAY,UAAU;IACpB,iDAAY,CAAA;IACZ,qBAAqB;IACrB,iDAAY,CAAA;IACZ,iDAAY,CAAA;AACd,CAAC,EALW,UAAU,KAAV,UAAU,QAKrB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { expoCryptoAes } from './aes-native-module';
|
|
2
|
+
import type { IAesDecryptOptions, IAesEncryptOptions, IAesSealedDataConfig, IBase64DecryptOptions, IBinaryInput, IBytesDecryptOptions } from './aes-types';
|
|
3
|
+
export declare class AESEncryptionKey extends expoCryptoAes.EncryptionKey {
|
|
4
|
+
}
|
|
5
|
+
export declare class AESSealedData extends expoCryptoAes.SealedData {
|
|
6
|
+
/** `tag` is a separate tag, or the tag length when the ciphertext already carries it */
|
|
7
|
+
static fromParts(iv: IBinaryInput, ciphertext: IBinaryInput, tag: IBinaryInput): AESSealedData;
|
|
8
|
+
static fromParts(iv: IBinaryInput, ciphertextWithTag: IBinaryInput, tagLength?: number): AESSealedData;
|
|
9
|
+
/** `combined` holds IV, ciphertext and tag back to back */
|
|
10
|
+
static fromCombined(combined: IBinaryInput, config?: IAesSealedDataConfig): AESSealedData;
|
|
11
|
+
}
|
|
12
|
+
/** AES-GCM encryption. A string plaintext must be base64-encoded */
|
|
13
|
+
export declare function aesEncryptAsync(plaintext: IBinaryInput, key: AESEncryptionKey, options?: IAesEncryptOptions): Promise<AESSealedData>;
|
|
14
|
+
export declare function aesDecryptAsync(sealedData: AESSealedData, key: AESEncryptionKey, options: IBase64DecryptOptions): Promise<string>;
|
|
15
|
+
export declare function aesDecryptAsync(sealedData: AESSealedData, key: AESEncryptionKey, options?: IBytesDecryptOptions): Promise<Uint8Array>;
|
|
16
|
+
export declare function aesDecryptAsync(sealedData: AESSealedData, key: AESEncryptionKey, options?: IAesDecryptOptions): Promise<string | Uint8Array>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { expoCryptoAes, } from './aes-native-module';
|
|
2
|
+
// Native has no ArrayBuffer support, and encodes binary options as base64 strings
|
|
3
|
+
function convertBinaryInput(input, useBase64 = false) {
|
|
4
|
+
const bytes = input instanceof ArrayBuffer ? new Uint8Array(input) : input;
|
|
5
|
+
if (typeof bytes !== 'string' && useBase64) {
|
|
6
|
+
return uint8ArrayToBase64(bytes);
|
|
7
|
+
}
|
|
8
|
+
return bytes;
|
|
9
|
+
}
|
|
10
|
+
function uint8ArrayToBase64(bytes) {
|
|
11
|
+
let binaryString = '';
|
|
12
|
+
for (const byte of bytes) {
|
|
13
|
+
binaryString += String.fromCharCode(byte);
|
|
14
|
+
}
|
|
15
|
+
return btoa(binaryString);
|
|
16
|
+
}
|
|
17
|
+
export class AESEncryptionKey extends expoCryptoAes.EncryptionKey {
|
|
18
|
+
}
|
|
19
|
+
export class AESSealedData extends expoCryptoAes.SealedData {
|
|
20
|
+
static fromParts(iv, ciphertext, tag) {
|
|
21
|
+
const processedIv = convertBinaryInput(iv);
|
|
22
|
+
const processedCiphertext = convertBinaryInput(ciphertext);
|
|
23
|
+
if (tag === undefined || typeof tag === 'number') {
|
|
24
|
+
return expoCryptoAes.SealedData.fromParts(processedIv, processedCiphertext, tag);
|
|
25
|
+
}
|
|
26
|
+
return expoCryptoAes.SealedData.fromParts(processedIv, processedCiphertext, convertBinaryInput(tag));
|
|
27
|
+
}
|
|
28
|
+
/** `combined` holds IV, ciphertext and tag back to back */
|
|
29
|
+
static fromCombined(combined, config) {
|
|
30
|
+
return expoCryptoAes.SealedData.fromCombined(convertBinaryInput(combined), config);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** AES-GCM encryption. A string plaintext must be base64-encoded */
|
|
34
|
+
export function aesEncryptAsync(plaintext, key, options = {}) {
|
|
35
|
+
const { nonce, additionalData, ...rest } = options;
|
|
36
|
+
let nativeOptions = { ...rest };
|
|
37
|
+
if (nonce) {
|
|
38
|
+
nativeOptions = {
|
|
39
|
+
...nativeOptions,
|
|
40
|
+
nonce: 'bytes' in nonce ? convertBinaryInput(nonce.bytes, true) : nonce.length,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (additionalData) {
|
|
44
|
+
nativeOptions = {
|
|
45
|
+
...nativeOptions,
|
|
46
|
+
additionalData: convertBinaryInput(additionalData, true),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return expoCryptoAes.encryptAsync(convertBinaryInput(plaintext), key, nativeOptions);
|
|
50
|
+
}
|
|
51
|
+
export function aesDecryptAsync(sealedData, key, options = {}) {
|
|
52
|
+
const { additionalData, ...rest } = options;
|
|
53
|
+
return expoCryptoAes.decryptAsync(sealedData, key, {
|
|
54
|
+
...rest,
|
|
55
|
+
additionalData: additionalData
|
|
56
|
+
? convertBinaryInput(additionalData, true)
|
|
57
|
+
: undefined,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=aes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aes.js","sourceRoot":"","sources":["../../../src/core/aes/aes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { AESEncryptionKey, AESSealedData, aesEncryptAsync, aesDecryptAsync, } from './aes';
|
|
2
|
+
export { AESKeySize, type IAesDecryptOptions, type IAesEncryptOptions, type IAesSealedDataConfig, type IBase64DecryptOptions, type IBinaryInput, type IBytesDecryptOptions, type IGcmNonceParam, type IGcmTagByteLength, } from './aes-types';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/aes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,eAAe,GAChB,MAAM,OAAO,CAAC;AACf,OAAO,EACL,UAAU,GASX,MAAM,aAAa,CAAC"}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { CryptoError, getRandomBytes, getRandomBytesAsync, getRandomValues, randomUUID, digestStringAsync, digest, } from './crypto';
|
|
2
|
+
export * from './aes';
|
|
2
3
|
export { CryptoDigestAlgorithm, CryptoEncoding, type ICryptoDigestOptions, type IDigest, type IUintBasedTypedArray, type IIntBasedTypedArray, type ITypedArray, } from './types';
|
package/build-ngc/core/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,UAAU,EACV,iBAAiB,EACjB,MAAM,GACP,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,qBAAqB,EACrB,cAAc,GAMf,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,UAAU,EACV,iBAAiB,EACjB,MAAM,GACP,MAAM,UAAU,CAAC;AAClB,cAAc,OAAO,CAAC;AACtB,OAAO,EACL,qBAAqB,EACrB,cAAc,GAMf,MAAM,SAAS,CAAC"}
|
package/native-link.json
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
"importPath": "expo.modules.crypto.CryptoModule",
|
|
7
7
|
"className": "CryptoModule",
|
|
8
8
|
"nativeName": "ExpoCrypto"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"importPath": "expo.modules.crypto.aes.AesCryptoModule",
|
|
12
|
+
"className": "AesCryptoModule",
|
|
13
|
+
"nativeName": "ExpoCryptoAES"
|
|
9
14
|
}
|
|
10
15
|
]
|
|
11
16
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/crypto",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.3",
|
|
4
4
|
"description": "expo-crypto wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, Svelte, Solid, and Angular adapters. Cryptographically secure random bytes, randomUUID, and string/buffer digest hashing (SHA-1/256/384/512, MD2/MD4/MD5).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
72
|
"expo-crypto": "57.0.1",
|
|
73
|
-
"expo-modules-core": "57.0.
|
|
73
|
+
"expo-modules-core": "57.0.20"
|
|
74
74
|
},
|
|
75
75
|
"peerDependencies": {
|
|
76
76
|
"@angular/core": ">=20",
|
|
@@ -80,12 +80,12 @@
|
|
|
80
80
|
"solid-js": ">=1.9.0",
|
|
81
81
|
"svelte": ">=5.56.0",
|
|
82
82
|
"vue": ">=3.5.0",
|
|
83
|
-
"@symbiote-native/angular": "^3.
|
|
84
|
-
"@symbiote-native/engine": "^1.
|
|
85
|
-
"@symbiote-native/react": "^3.0
|
|
86
|
-
"@symbiote-native/solid": "^3.0
|
|
87
|
-
"@symbiote-native/svelte": "^3.0
|
|
88
|
-
"@symbiote-native/vue": "^3.0
|
|
83
|
+
"@symbiote-native/angular": "^3.2.0",
|
|
84
|
+
"@symbiote-native/engine": "^1.5.0",
|
|
85
|
+
"@symbiote-native/react": "^3.2.0",
|
|
86
|
+
"@symbiote-native/solid": "^3.1.0",
|
|
87
|
+
"@symbiote-native/svelte": "^3.1.0",
|
|
88
|
+
"@symbiote-native/vue": "^3.2.0"
|
|
89
89
|
},
|
|
90
90
|
"peerDependenciesMeta": {
|
|
91
91
|
"@symbiote-native/angular": {
|
|
@@ -133,13 +133,13 @@
|
|
|
133
133
|
"solid-js": "^1.9.14",
|
|
134
134
|
"svelte": "^5.56.0",
|
|
135
135
|
"typescript": "~6.0.0",
|
|
136
|
-
"@symbiote-native/angular": "3.
|
|
137
|
-
"@symbiote-native/engine": "1.
|
|
138
|
-
"@symbiote-native/react": "3.0
|
|
139
|
-
"@symbiote-native/solid": "3.0
|
|
140
|
-
"@symbiote-native/svelte": "3.0
|
|
141
|
-
"@symbiote-native/test-utils": "0.4.
|
|
142
|
-
"@symbiote-native/vue": "3.0
|
|
136
|
+
"@symbiote-native/angular": "3.2.0",
|
|
137
|
+
"@symbiote-native/engine": "1.5.0",
|
|
138
|
+
"@symbiote-native/react": "3.2.0",
|
|
139
|
+
"@symbiote-native/solid": "3.1.0",
|
|
140
|
+
"@symbiote-native/svelte": "3.1.0",
|
|
141
|
+
"@symbiote-native/test-utils": "0.4.6",
|
|
142
|
+
"@symbiote-native/vue": "3.2.0"
|
|
143
143
|
},
|
|
144
144
|
"scripts": {
|
|
145
145
|
"typecheck": "tsc --build",
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { NativeModule, requireNativeModule } from 'expo-modules-core';
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
IAesDecryptOptions,
|
|
5
|
+
IAesEncryptOptions,
|
|
6
|
+
IAesSealedDataConfig,
|
|
7
|
+
IBinaryInput,
|
|
8
|
+
IGcmTagByteLength,
|
|
9
|
+
AESKeySize,
|
|
10
|
+
} from './aes-types';
|
|
11
|
+
|
|
12
|
+
type IEncoding = 'hex' | 'base64';
|
|
13
|
+
|
|
14
|
+
declare class EncryptionKey {
|
|
15
|
+
static generate(size?: AESKeySize): Promise<EncryptionKey>;
|
|
16
|
+
static import(bytes: Uint8Array): Promise<EncryptionKey>;
|
|
17
|
+
static import(hexString: string, encoding: IEncoding): Promise<EncryptionKey>;
|
|
18
|
+
|
|
19
|
+
/** Key size in bits */
|
|
20
|
+
size: AESKeySize;
|
|
21
|
+
|
|
22
|
+
bytes(): Promise<Uint8Array>;
|
|
23
|
+
encoded(encoding: IEncoding): Promise<string>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
declare class SealedData {
|
|
27
|
+
static fromCombined(
|
|
28
|
+
combined: IBinaryInput,
|
|
29
|
+
config?: IAesSealedDataConfig,
|
|
30
|
+
): SealedData;
|
|
31
|
+
static fromParts(
|
|
32
|
+
iv: IBinaryInput,
|
|
33
|
+
ciphertext: IBinaryInput,
|
|
34
|
+
tag: IBinaryInput,
|
|
35
|
+
): SealedData;
|
|
36
|
+
static fromParts(
|
|
37
|
+
iv: IBinaryInput,
|
|
38
|
+
ciphertextWithTag: IBinaryInput,
|
|
39
|
+
tagLength?: number,
|
|
40
|
+
): SealedData;
|
|
41
|
+
|
|
42
|
+
ciphertext(options: {
|
|
43
|
+
includeTag?: boolean;
|
|
44
|
+
encoding: 'base64';
|
|
45
|
+
}): Promise<string>;
|
|
46
|
+
ciphertext(options?: {
|
|
47
|
+
includeTag?: boolean;
|
|
48
|
+
encoding?: 'bytes';
|
|
49
|
+
}): Promise<Uint8Array>;
|
|
50
|
+
ciphertext(options: {
|
|
51
|
+
includeTag?: boolean;
|
|
52
|
+
encoding?: 'base64' | 'bytes';
|
|
53
|
+
}): Promise<string | Uint8Array>;
|
|
54
|
+
|
|
55
|
+
iv(encoding?: 'bytes'): Promise<Uint8Array>;
|
|
56
|
+
iv(encoding: 'base64'): Promise<string>;
|
|
57
|
+
iv(encoding?: 'bytes' | 'base64'): Promise<string | Uint8Array>;
|
|
58
|
+
|
|
59
|
+
tag(encoding?: 'bytes'): Promise<Uint8Array>;
|
|
60
|
+
tag(encoding: 'base64'): Promise<string>;
|
|
61
|
+
tag(encoding?: 'bytes' | 'base64'): Promise<string | Uint8Array>;
|
|
62
|
+
|
|
63
|
+
combined(encoding?: 'bytes'): Promise<Uint8Array>;
|
|
64
|
+
combined(encoding: 'base64'): Promise<string>;
|
|
65
|
+
combined(encoding?: 'bytes' | 'base64'): Promise<string | Uint8Array>;
|
|
66
|
+
|
|
67
|
+
/** IV + ciphertext + tag, in bytes */
|
|
68
|
+
readonly combinedSize: number;
|
|
69
|
+
readonly ivSize: number;
|
|
70
|
+
readonly tagSize: IGcmTagByteLength;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Native takes the nonce as a bare length (to generate) or as bytes
|
|
74
|
+
export type INativeAesEncryptOptions = Omit<IAesEncryptOptions, 'nonce'> & {
|
|
75
|
+
nonce?: number | IBinaryInput | undefined;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
declare class NativeAesCryptoModule extends NativeModule {
|
|
79
|
+
EncryptionKey: typeof EncryptionKey;
|
|
80
|
+
SealedData: typeof SealedData;
|
|
81
|
+
|
|
82
|
+
generateKey(size?: AESKeySize): Promise<EncryptionKey>;
|
|
83
|
+
importKey(
|
|
84
|
+
keyInput: string | Uint8Array,
|
|
85
|
+
encoding?: IEncoding,
|
|
86
|
+
): Promise<EncryptionKey>;
|
|
87
|
+
|
|
88
|
+
encryptAsync(
|
|
89
|
+
plaintext: IBinaryInput,
|
|
90
|
+
key: EncryptionKey,
|
|
91
|
+
options?: INativeAesEncryptOptions,
|
|
92
|
+
): Promise<SealedData>;
|
|
93
|
+
decryptAsync(
|
|
94
|
+
sealedData: SealedData,
|
|
95
|
+
key: EncryptionKey,
|
|
96
|
+
options?: IAesDecryptOptions,
|
|
97
|
+
): Promise<string | Uint8Array>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const expoCryptoAes =
|
|
101
|
+
requireNativeModule<NativeAesCryptoModule>('ExpoCryptoAES');
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// A string input must be base64-encoded
|
|
2
|
+
export type IBinaryInput = string | Uint8Array | ArrayBuffer;
|
|
3
|
+
|
|
4
|
+
export enum AESKeySize {
|
|
5
|
+
AES128 = 128,
|
|
6
|
+
// Unsupported on web
|
|
7
|
+
AES192 = 192,
|
|
8
|
+
AES256 = 256,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// GCM tag length in bytes: 16 is the default and the only value Apple supports for encryption
|
|
12
|
+
export type IGcmTagByteLength = 16 | 15 | 14 | 13 | 12 | 8 | 4;
|
|
13
|
+
|
|
14
|
+
export type IAesSealedDataConfig = {
|
|
15
|
+
/** @default 12 */
|
|
16
|
+
ivLength: number;
|
|
17
|
+
/** @default 16 */
|
|
18
|
+
tagLength: IGcmTagByteLength;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type IAesDecryptOptions = {
|
|
22
|
+
/** @default 'bytes' */
|
|
23
|
+
output?: 'bytes' | 'base64';
|
|
24
|
+
// GCM additional authenticated data, base64 when a string
|
|
25
|
+
additionalData?: IBinaryInput;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type IBase64DecryptOptions = IAesDecryptOptions & { output: 'base64' };
|
|
29
|
+
|
|
30
|
+
export type IBytesDecryptOptions = IAesDecryptOptions & { output?: 'bytes' };
|
|
31
|
+
|
|
32
|
+
export type IGcmNonceParam =
|
|
33
|
+
| {
|
|
34
|
+
/** Byte length of the nonce to generate. @default 12 */
|
|
35
|
+
length: number;
|
|
36
|
+
}
|
|
37
|
+
| {
|
|
38
|
+
bytes: IBinaryInput;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type IAesEncryptOptions = {
|
|
42
|
+
/** @default { length: 12 } */
|
|
43
|
+
nonce?: IGcmNonceParam;
|
|
44
|
+
// Ignored on Apple, where the tag is always 16 bytes
|
|
45
|
+
tagLength?: IGcmTagByteLength;
|
|
46
|
+
// GCM additional authenticated data, base64 when a string
|
|
47
|
+
additionalData?: IBinaryInput;
|
|
48
|
+
};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
expoCryptoAes,
|
|
3
|
+
type INativeAesEncryptOptions,
|
|
4
|
+
} from './aes-native-module';
|
|
5
|
+
import type {
|
|
6
|
+
IAesDecryptOptions,
|
|
7
|
+
IAesEncryptOptions,
|
|
8
|
+
IAesSealedDataConfig,
|
|
9
|
+
IBase64DecryptOptions,
|
|
10
|
+
IBinaryInput,
|
|
11
|
+
IBytesDecryptOptions,
|
|
12
|
+
} from './aes-types';
|
|
13
|
+
|
|
14
|
+
// Hermes global, absent from the ES2022 lib
|
|
15
|
+
declare function btoa(data: string): string;
|
|
16
|
+
|
|
17
|
+
// Native has no ArrayBuffer support, and encodes binary options as base64 strings
|
|
18
|
+
function convertBinaryInput(
|
|
19
|
+
input: IBinaryInput,
|
|
20
|
+
useBase64 = false,
|
|
21
|
+
): IBinaryInput {
|
|
22
|
+
const bytes = input instanceof ArrayBuffer ? new Uint8Array(input) : input;
|
|
23
|
+
if (typeof bytes !== 'string' && useBase64) {
|
|
24
|
+
return uint8ArrayToBase64(bytes);
|
|
25
|
+
}
|
|
26
|
+
return bytes;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function uint8ArrayToBase64(bytes: Uint8Array): string {
|
|
30
|
+
let binaryString = '';
|
|
31
|
+
for (const byte of bytes) {
|
|
32
|
+
binaryString += String.fromCharCode(byte);
|
|
33
|
+
}
|
|
34
|
+
return btoa(binaryString);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class AESEncryptionKey extends expoCryptoAes.EncryptionKey {}
|
|
38
|
+
|
|
39
|
+
export class AESSealedData extends expoCryptoAes.SealedData {
|
|
40
|
+
/** `tag` is a separate tag, or the tag length when the ciphertext already carries it */
|
|
41
|
+
static override fromParts(
|
|
42
|
+
iv: IBinaryInput,
|
|
43
|
+
ciphertext: IBinaryInput,
|
|
44
|
+
tag: IBinaryInput,
|
|
45
|
+
): AESSealedData;
|
|
46
|
+
static override fromParts(
|
|
47
|
+
iv: IBinaryInput,
|
|
48
|
+
ciphertextWithTag: IBinaryInput,
|
|
49
|
+
tagLength?: number,
|
|
50
|
+
): AESSealedData;
|
|
51
|
+
|
|
52
|
+
static override fromParts(
|
|
53
|
+
iv: IBinaryInput,
|
|
54
|
+
ciphertext: IBinaryInput,
|
|
55
|
+
tag?: IBinaryInput | number,
|
|
56
|
+
): AESSealedData {
|
|
57
|
+
const processedIv = convertBinaryInput(iv);
|
|
58
|
+
const processedCiphertext = convertBinaryInput(ciphertext);
|
|
59
|
+
|
|
60
|
+
if (tag === undefined || typeof tag === 'number') {
|
|
61
|
+
return expoCryptoAes.SealedData.fromParts(
|
|
62
|
+
processedIv,
|
|
63
|
+
processedCiphertext,
|
|
64
|
+
tag,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return expoCryptoAes.SealedData.fromParts(
|
|
68
|
+
processedIv,
|
|
69
|
+
processedCiphertext,
|
|
70
|
+
convertBinaryInput(tag),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** `combined` holds IV, ciphertext and tag back to back */
|
|
75
|
+
static override fromCombined(
|
|
76
|
+
combined: IBinaryInput,
|
|
77
|
+
config?: IAesSealedDataConfig,
|
|
78
|
+
): AESSealedData {
|
|
79
|
+
return expoCryptoAes.SealedData.fromCombined(
|
|
80
|
+
convertBinaryInput(combined),
|
|
81
|
+
config,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** AES-GCM encryption. A string plaintext must be base64-encoded */
|
|
87
|
+
export function aesEncryptAsync(
|
|
88
|
+
plaintext: IBinaryInput,
|
|
89
|
+
key: AESEncryptionKey,
|
|
90
|
+
options: IAesEncryptOptions = {},
|
|
91
|
+
): Promise<AESSealedData> {
|
|
92
|
+
const { nonce, additionalData, ...rest } = options;
|
|
93
|
+
let nativeOptions: INativeAesEncryptOptions = { ...rest };
|
|
94
|
+
if (nonce) {
|
|
95
|
+
nativeOptions = {
|
|
96
|
+
...nativeOptions,
|
|
97
|
+
nonce:
|
|
98
|
+
'bytes' in nonce ? convertBinaryInput(nonce.bytes, true) : nonce.length,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (additionalData) {
|
|
102
|
+
nativeOptions = {
|
|
103
|
+
...nativeOptions,
|
|
104
|
+
additionalData: convertBinaryInput(additionalData, true),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return expoCryptoAes.encryptAsync(
|
|
108
|
+
convertBinaryInput(plaintext),
|
|
109
|
+
key,
|
|
110
|
+
nativeOptions,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function aesDecryptAsync(
|
|
115
|
+
sealedData: AESSealedData,
|
|
116
|
+
key: AESEncryptionKey,
|
|
117
|
+
options: IBase64DecryptOptions,
|
|
118
|
+
): Promise<string>;
|
|
119
|
+
export function aesDecryptAsync(
|
|
120
|
+
sealedData: AESSealedData,
|
|
121
|
+
key: AESEncryptionKey,
|
|
122
|
+
options?: IBytesDecryptOptions,
|
|
123
|
+
): Promise<Uint8Array>;
|
|
124
|
+
export function aesDecryptAsync(
|
|
125
|
+
sealedData: AESSealedData,
|
|
126
|
+
key: AESEncryptionKey,
|
|
127
|
+
options?: IAesDecryptOptions,
|
|
128
|
+
): Promise<string | Uint8Array>;
|
|
129
|
+
|
|
130
|
+
export function aesDecryptAsync(
|
|
131
|
+
sealedData: AESSealedData,
|
|
132
|
+
key: AESEncryptionKey,
|
|
133
|
+
options: IAesDecryptOptions = {},
|
|
134
|
+
): Promise<string | Uint8Array> {
|
|
135
|
+
const { additionalData, ...rest } = options;
|
|
136
|
+
return expoCryptoAes.decryptAsync(sealedData, key, {
|
|
137
|
+
...rest,
|
|
138
|
+
additionalData: additionalData
|
|
139
|
+
? convertBinaryInput(additionalData, true)
|
|
140
|
+
: undefined,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export {
|
|
2
|
+
AESEncryptionKey,
|
|
3
|
+
AESSealedData,
|
|
4
|
+
aesEncryptAsync,
|
|
5
|
+
aesDecryptAsync,
|
|
6
|
+
} from './aes';
|
|
7
|
+
export {
|
|
8
|
+
AESKeySize,
|
|
9
|
+
type IAesDecryptOptions,
|
|
10
|
+
type IAesEncryptOptions,
|
|
11
|
+
type IAesSealedDataConfig,
|
|
12
|
+
type IBase64DecryptOptions,
|
|
13
|
+
type IBinaryInput,
|
|
14
|
+
type IBytesDecryptOptions,
|
|
15
|
+
type IGcmNonceParam,
|
|
16
|
+
type IGcmTagByteLength,
|
|
17
|
+
} from './aes-types';
|