@amazon-devices/keplercrypto
The KeplerCrypto API provides functionality that allows you to perform typical cryptographic operations such as read, generate, and manage cryptographic keys using industry-standard algorithms.
Note: Running this API in the simulator is not supported.
Get started
Setup
- Add the following library dependency to the
dependencies
section of your package.json file.
"@amazon-devices/keplercrypto": " ~2.0,
Usage
In the source file in which you are using the KeplerCrypto functionality, import the data types that you need from the Turbo Module.
import {
AsymmetricAlgorithm,
AsymmetricKeyBuilder,
CbcContextBuilder,
CtrContextBuilder,
Crypto,
DigestAlgorithm,
EccCurve,
GcmContextBuilder,
KeyPurpose,
PrivateKey,
PublicKey,
SymmetricAlgorithm,
SymmetricKey,
} from '@amazon-devices/keplercrypto';
Instancing the Crypto
class is always the first step to make use of the
KeplerCrypto Turbo Module.
/**
* Create a Crypto object.
*/
const crypto = new Crypto();
The following example code is for explanatory purposes and is simplified for clarity. Most of the methods in the KeplerCrypto API throw an exception when an error occurs.
Wrap your calls in a try-catch block to gracefully handle any errors that occur.
try {
crypto.getRandom(length);
} catch (e) {
if (e instanceof InvalidArgumentError) {...}
else if (e instanceof SecurityError) {...}
}
Check the documentation the specific exceptions that any particular method throws.
ECDSA signature and verification
The following example code includes ECDSA keypair generation, message signing, and verification.
Step 1: Create an ECDSA key builder
// Create a new ECDSA key builder
const keyBuilder = crypto.makeEccKeyBuilder();
// Mark the key as exportable
keyBuilder.exportable = true;
// Set key purposes
keyBuilder.purposes = [KeyPurpose.SIGN, KeyPurpose.VERIFY];
Step 2: Generate a keypair
You can generate an ECDSA keypair or construct a private key.
To generate an ECDSA keypair, call buildGenerated()
.
// Generate the ECDSA keypair
const privateKey = await keyBuilder.buildGenerated();
// Get the public key
const publicKey = privateKey.getPublicKey();
To construct a private key from an existing DER string, call buildPrivateFromDer()
.
// Build private key from an existing DER string
const privateKey = await keyBuilder.buildPrivateFromDer(privateKeyDer);
const publicKey = privateKey.getPublicKey();
</viv>
Step 3: Create a signature context builder
const builder = crypto.makeSignatureContextBuilder();
builder.signingAlgorithm = crypto.getAsymmetricAlgorithmByName(AsymmetricAlgorithm.ECDSA);
Step 4: Sign the message and obtain the signature
// Build the signing context with the private key
const signer = builder.buildSigningContext(privateKey);
const message = 'This is a very important message';
const messageBuffer = new TextEncoder().encode(message).buffer;
// Sign the message
const signedMessage = await signer.sign(messageBuffer);
Step 5: Verify the message with the signature
// Build the verification context with the public key
const verifier = builder.buildVerificationContext(publicKey);
// Verify the message
const result = await verifier.verify(messageBuffer, signedMessage);
RSA signature and verification
The following example code includes RSA keypair generation, message signing, and verification.
Step 1: Create an RSA key builder
// Make an RSA key builder
const keybuilder = crypto.makeRsaKeyBuilder();
// Mark the key as exportable
keyBuilder.exportable = true;
// Set key purposes
keyBuilder.purposes = [KeyPurpose.SIGN, KeyPurpose.VERIFY];
Step 2: Generate a keypair
You can generate an RSA keypair or construct a private key.
To generate an RSA keypair, call buildGenerated()
.
// Generate the RSA keypair
const privateKey = await keyBuilder.buildGenerated();
// Get the public key
const publicKey = privateKey.getPublicKey();
To construct a private key from an existing DER string, call buildPrivateFromDer()
.
// Build private key from an existing DER string
const privateKey = await keyBuilder.buildPrivateFromDer(privateKeyDer);
const publicKey = privateKey.getPublicKey();
Step 3: Create a signature context builder and sign the message
Create a signature context builder by calling buildSigningContext()
and then sign the message with the RSA-PKCS1 v1.5 scheme to get the signature.
// Build the signing context with the private key
const builder = crypto.makeRsassaPkcs1ContextBuilder();
const digestAlgorithm = crypto.getDigestAlgorithmByName(DigestAlgorithm.SHA256);
builder.digestAlgorithm = digestAlgorithm;
const rsaPkcs1Signer = builder.buildSigningContext(privateKey);
const message = 'This is a very important message';
const messageBuffer = new TextEncoder().encode(message).buffer;
// Sign the message
const signedMessage = await rsaPkcs1Signer.sign(messageBuffer);
Step 4: Verify the message
Use the signature to verify the message by calling rsaPkcs1Verifier.verify()
which uses the RSA-PKCS1 v1.5 standard.
// Build the verification context with the public key
const rsaPkcs1Verifier = builder.buildVerificationContext(publicKey);
// Verify the message
const result = await rsaPkcs1Verifier.verify(messageBuffer, signedMessage);
Step 5: Sign the message and get the signature
To sign the message using RSA-PSS standard call rsaPssSigner.sign()
which
returns the signature of the signed message.
// The salt length should typically match the digest length to make sure proper padding and security.
// Using a different salt length might still work, but it is not recommended.
builder.saltLength = digestAlgorithm.size / 8;
// Build the signing context with the private key
const rsaPssSigner = builder.buildSigningContext(privateKey);
const message = 'This is a very important message';
const messageBuffer = new TextEncoder().encode(message).buffer;
// Sign the message
const signedMessage = await rsaPssSigner.sign(messageBuffer);
To verify the message, call rsaPssVerifier.verify()
and pass the signature of the message.
// Build the verification context with the public key
const rsaPssVerifier = builder.buildVerificationContext(publicKey);
// Verify the message
const result = await rsaPssVerifier.verify(messageBuffer, signedMessage);
HMAC signature and verification
The following example shows how to generate a secret key, export the key, sign the data, and verify it.
// Generate a secret key
const keyBuilder = crypto.makeSymmetricKeyBuilder();
keyBuilder.exportable = true;
keyBuilder.purposes = [KeyPurpose.ENCRYPT, KeyPurpose.DECRYPT];
keyBuilder.bits = 256;
const key = await keyBuilder.buildGenerated();
// Export the secure key
const rawKey = key.exportRaw();
const exportedKeyBuilder = crypto.makeSymmetricKeyBuilder();
exportedKeyBuilder.exportable = true;
exportedKeyBuilder.purposes = [KeyPurpose.ENCRYPT, KeyPurpose.DECRYPT];
const exportedKey = await exportedKeyBuilder.buildFromRaw(rawKey);
// Get the digest algorithm
const digestAlgorithm = crypto.getDigestAlgorithmByName(DigestAlgorithm.SHA256);
// Make an HMAC Context Builder with the secure key and the digest algorithm
const builder = crypto.makeHmacContextBuilder();
builder.key = key;
builder.digestAlgorithm = digestAlgorithm;
// Sign the data
const signContext = builder.build();
const tag = await signContext.sign(plaintext);
// Verify the data
const verifyContext = builder.build();
const tag = signContext.sign(plaintext);
const result1 = await verifyContext.verify(plaintext, tag);
// Try to verify with exported key
const exportedBuilder = crypto.makeHmacContextBuilder();
exportedBuilder.key = exportedKey;
exportedBuilder.digestAlgorithm(digestAlgorithm);
const exportedVerifyContext = exportedBuilder.build();
const result2 = await exportedVerifyContext.verify(plaintext, tag);
Use an AES CBC Block Cipher to encrypt a message
The following example shows how to generate an AES-256 key, export it, create an AES-256 CBC cipher context builder, and use it to sign a plaintext.
// Generate an AES-256 key
const keyBuilder = crypto.makeSymmetricKeyBuilder();
keyBuilder.bits = 256;
keyBuilder.exportable = true;
const purposes = [KeyPurpose.ENCRYPT, KeyPurpose.DECRYPT];
keyBuilder.purposes = purposes;
const key = await keyBuilder.buildGenerated();
// Export the AES key
const rawKey = key.exportRaw();
const exportedKeyBuilder = crypto.makeSymmetricKeyBuilder();
exportedKeyBuilder.exportable = true;
exportedKeyBuilder.purposes = purposes;
const exportedKey = await exportedKeyBuilder.buildFromRaw(rawKey);
// Make an AES-256 CBC cipher context builder
const builder = crypto.makeCbcCipherContextBuilder(crypto.getSymmetricAlgorithmByName(SymmetricAlgorithm.AES256));
// Set the proper IV
const iv = new Uint8Array(builder.ivSize).buffer;
builder.iv = iv;
// Set the key
builder.key = key;
// Encrypt the plaintext
const plaintext = 'try this string';
const plaintextBuffer = new TextEncoder().encode(plaintext).buffer;
const encryptionContext = builder.buildEncryptionContext();
const ciphertext = await encryptionContext.encrypt(plaintextBuffer);
// Decrypt the ciphertext
const decryptionContext = builder.buildDecryptionContext();
const decrypted = await decryptionContext.decrypt(ciphertext);
// Try to use the exported key and then decrypt the ciphertext with it
const exportedBuilder = crypto.makeCbcCipherContextBuilder(crypto.getSymmetricAlgorithmByName(SymmetricAlgorithm.AES256));
exportedBuilder.iv = iv;
exportedBuilder.key = exportedKey;
const exportedDecryptionContext = exportedBuilder.buildDecryptionContext();
const exportedDecrypted = await exportedDecryptionContext.decrypt(ciphertext);
Use an AES CTR Block Cipher to encrypt a message
The following example shows how to generate an AES-CTR key, export it, create an AES-256 CTR cipher context builder, and use it to sign a plaintext.
// Generate an AES-256 key
const purposes = [KeyPurpose.ENCRYPT, KeyPurpose.DECRYPT];
const keyBuilder = crypto.makeSymmetricKeyBuilder();
keyBuilder.exportable = true;
keyBuilder.purposes = purposes;
keyBuilder.bits = 256;
const key = await keyBuilder.buildGenerated();
// Export the AES key
const rawKey = key.exportRaw();
const exportedKeyBuilder = crypto.makeSymmetricKeyBuilder();
exportedKeyBuilder.exportable = true;
exportedKeyBuilder.purposes = purposes;
const exportedKey = await exportedKeyBuilder.buildFromRaw(rawKey);
// Make an AES CTR cipher context builder
const builder = crypto.makeCtrCipherContextBuilder(crypto.getSymmetricAlgorithmByName(SymmetricAlgorithm.AES256));
// Set a proper count and nonce
builder.count = 12;
builder.nonce = 12;
// Set the key
builder.key = key;
// Encrypt the plaintext
const plaintext = 'try this string';
const plaintextBuffer = new TextEncoder().encode(plaintext).buffer;
const encryptionContext = builder.buildEncryptionContext();
const ciphertext = await encryptionContext.encrypt(plaintextBuffer);
// Decrypt the ciphertext
const decryptionContext = builder.buildDecryptionContext();
const decrypted = await decryptionContext.decrypt(ciphertext);
const result1 = plaintext === new TextDecoder('utf-8').decode(decrypted);
// Use the exported key with the CTR cipher context builder
builder.key = exportedKey;
// Try to decrypt using the exported key
const exportedDecryptionContext = builder.buildDecryptionContext();
const exportedDecrypted = await exportedDecryptionContext.decrypt(ciphertext);
const result2 = plaintext === new TextDecoder('utf-8').decode(exportedDecrypted);
Use an AES GCM Block Cipher to encrypt a message
The following example shows how to generate an AES-256 GCM key, export it, create an AES-256 GCM cipher context builder, and use it to sign a plaintext.
// Generate an AES-256 key
const purposes = [KeyPurpose.ENCRYPT, KeyPurpose.DECRYPT];
const keyBuilder = crypto.makeSymmetricKeyBuilder();
keyBuilder.exportable = true;
keyBuilder.purposes = purposes;
keyBuilder.bits = 256;
const key = await keyBuilder.buildGenerated();
// Export the AES key
const rawKey = key.exportRaw();
const exportedKeyBuilder = crypto.makeSymmetricKeyBuilder();
exportedKeyBuilder.exportable = true;
exportedKeyBuilder.purposes = purposes;
const exportedKey = await exportedKeyBuilder.buildFromRaw(rawKey);
// Make an AES-256 GCM cipher context builder
const builder = crypto.makeGcmCipherContextBuilder(crypto.getSymmetricAlgorithmByName(SymmetricAlgorithm.AES256));
// Initialize the IV with a 4-byte identifier in order to use a randomized sequence
// of nonces. Alternatively, initialize it with a vector of size builder.getIvSize()
// to use an arbitrary nonce.
const iv = new Uint8Array([0xab, 0xcd, 0xef, 0x01]);
builder.iv = iv;
builder.key = key;
// Encrypt the plaintext
const plaintext = 'try this string';
const plaintextBuffer = new TextEncoder().encode(plaintext).buffer;
const encryptionContext = builder.buildEncryptionContext();
const ciphertext = await encryptionContext.encrypt(plaintextBuffer);
// Decrypt the ciphertext
const decryptionContext = builder.buildDecryptionContext();
const decrypted = await decryptionContext.decrypt(ciphertext);
// Use the exported key with the GCM cipher context builder
builder.key = exportedKey;
// Try to decrypt using the exported key
const exportedDecryptionContext = builder.buildDecryptionContext();
const exportedDecrypted = await exportedDecryptionContext.decrypt(ciphertext);
Use RSA-OAEP to encrypt a message
The following example shows how to generate an RSA-OAEP key, export it, create a context builder, and use it to sign a plaintext.
// Get the digest algorithm and the asymmetric algorithm, and define the key purposes
const sha = crypto.getDigestAlgorithmByName(DigestAlgorithm.SHA384);
const alg = crypto.getAsymmetricAlgorithmByName(AsymmetricAlgorithm.RSA_OAEP);
const purposes = [KeyPurpose.ENCRYPT, KeyPurpose.DECRYPT];
// Make an RsaOaepKeyBuilder
const keyBuilder = crypto.makeRsaOaepKeyBuilder();
keyBuilder.exportable = true;
keyBuilder.purposes = purposes;
keyBuilder.bits = 384;
// Generate a private key from the keybuilder
const privateKey = await keyBuilder.buildGenerated();
// Export the key for later test case
const rawPrivateKey = await privateKey.exportDer();
// Get the corresponding public key
const publicKey = privateKey.getPublicKey();
// Export the public key for later use
const rawPublicKey = await publickey.exportDer();
// Build the plaintext from a message
const plaintext = 'try this string';
const plaintextBuffer = new TextEncoder().encode(plaintext).buffer;
// Build an RSA-OAEP cipher context using alg and sha
const rsaOaepCipherContext = crypto.makeRsaOaepCipherContextBuilder(alg, sha);
// Build encryption context from RSA-OAEP cipher context with public key
const rsaOaepEncryptionContext = rsaOaepCipherContext.buildEncryptionContext(publickey);
// Encrypt with the encryption context
const ciphertext = await rsaOaepEncryptionContext.encrypt(plaintextBuffer);
// Build the decryption context from the cipher context
const rsaOaepDecryptionContext = rsaOaepCipherContext.buildDecryptionContext(privatekey);
// Decrypt with the decryption context
const decrypted = await rsaOaepDecryptionContext.decrypt(ciphertext);
// Ensure that the decrypted message is the same as the plaintext
const result1 = plaintext === new TextDecoder('utf-8').decode(decrypted);
// Use the exported key to build a private key and public key
const exportedPrivkey = await keyBuilder.buildPrivateFromDer(rawPrivateKey);
const exportedRsaOaepDecryptionContext = rsaOaepCipherContext.buildDecryptionContext(exportedPrivkey);
// Use the imported key to decrypt the ciphertext and ensure that the decrypted message is equal to the plaintext
const exportedDecrypted = await exportedRsaOaepDecryptionContext.decrypt(ciphertext);
const result2 = plaintext === new TextDecoder('utf-8').decode(exportedDecrypted);
// Use the imported public key to encrypt, the original private key to decrypt
// and ensure that decrypted message is equal to the plaintext
const exportedPublicKey = await keyBuilder.buildPublicFromDer(rawPublicKey);
const exportedRsaOaepEncryptionContext = rsaOaepCipherContext.buildEncryptionContext(exportedPublicKey);
const exportedCiphertext = await exportedRsaOaepEncryptionContext.encrypt(plaintextBuffer);
const exportedDecryptedAgain = await rsaOaepDecryptionContext.decrypt(exportedCiphertext);
const result3 = plaintext === new TextDecoder('utf-8').decode(exportedDecryptedAgain);
Create a message digest
The following code example calls getDigestAlgorithmByName()
to get the digest,
creates a digest context, encodes an example string, and gets a digest for the string.
const builder = crypto.makeDigestContextBuilder();
builder.digestAlgorithm = crypto.getDigestAlgorithmByName(DigestAlgorithm.SHA256);
const digestContext = builder.build();
const plaintext = 'try this string';
const plaintextBuffer = new TextEncoder().encode(plaintext).buffer;
const hash = await digestContext.digest(plaintextBuffer);
Key derivation using HKDF (HMAC Key Derivation Function)
const salt = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const info = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // Extra info from the key
// Create a secret (optional if you already have a secret with the DERIVE purpose)
const keyBuilder = crypto.makeSymmetricKeyBuilder();
keyBuilder.bits = 256; // arbitrary length
keyBuilder.exportable = false;
keyBuilder.purposes = [KeyPurpose.DERIVE]; // IMPORTANT: the secret must be able to derive
const secret = await keyBuilder.buildGenerated();
const algorithm = crypto.getDigestAlgorithmByName(DigestAlgorithm.SHA256);
// Create a context builder
const ctxBuilder = crypto.makeHkdfContextBuilder();
ctxBuilder.info = info;
ctxBuilder.salt = salt;
ctxBuilder.hashAlgorithm = algorithm;
// Create a derivation context
const ctx = ctxBuilder.buildKeyDerivationContext(secret);
// Derive a symmetric key
// IMPORTANT: the derived key must match the length of the algorithm being used
const generatedKeyLen = algorithm.size / 8;
const symmKey = ctx.deriveKey(generatedKeyLen);
// Derive a raw key instead
const rawKey = ctx.deriveBits(generatedKeyLen);
Key derivation using PBKDF2 (Password-Based Key Derivation Function 2)
const iterations = 10000; // Number of iterations, the biggest, the most secure but also the most performance drop.
// 10000 is a good value that does not deteriorate too much the performance
const salt = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const generatedKeyLen = 512;
// Create a context builder
const ctxBuilder = crypto.makePbkdf2ContextBuilder();
// Create a secret (optional if you already have a secret with the DERIVE purpose)
const keyBuilder = crypto.makeSymmetricKeyBuilder()
keyBuilder.bits = 256; // arbitrary length
keyBuilder.exportable = false;
keyBuilder.purposes = [KeyPurpose.DERIVE]; // IMPORTANT: the secret must be able to derive
const secret = await keyBuilder.buildGenerated();
const algorithm = crypto.getDigestAlgorithmByName(DigestAlgorithm.SHA256);
// Create a derivation context
ctxBuilder.iterations = iterations;
ctxBuilder.salt = salt;
ctxBuilder.hashAlgorithm = algorithm;
const ctx = buildKeyDerivationContext(secret);
// Derive a symmetric key
const symmKey = await ctx.deriveKey(generatedKeyLen);
// Derive a raw key instead
const rawKey = await ctx.deriveBits(generatedKeyLen);
Web Crypto API shim layer
The KeplerCrypto library provides a shim layer that implements the Web Crypto API. We refer to the existing Web Crypto API MDN documentation for the API methods and parameters.
Web Crypto usage example
The following example presents how to import and use the Web Crypto API shim layer.
Import the API
import {WebCrypto} from '@amazon-devices/keplercrypto';
Initialize the WebCrypto
object
const wc = new WebCrypto();
Optional: Expose the crypto
instance on globalThis
// Define globalThis if it doesn't exist
if (typeof globalThis === 'undefined') {
(window as any).globalThis = window;
}
globalThis.crypto = wc.crypto as any;
Call to Web Crypto APIs
The next code example generates an ECDH CryptoKeyPair
.
const nonExtractableKeyPair = (await wc.crypto.subtle.generateKey(
{
name: 'ECDH',
namedCurve: 'P-256',
},
false, // non-extractable
['deriveBits'],
)) as CryptoKeyPair;
Supported Web Crypto algorithms
This library is a work in progress and the Web Crypto implementation provides a subset of the whole W3C Web Crypto specification. More operations will be supported in future versions.
Note: deriveKey is not supported in the Web Crypto shim layer implementation.
For key derivation functionality, please use the standard KeplerCrypto API, as
shown in the above "Key derivation using HKDF" and "Key derivation using PBKDF2"
examples.
Supported key formats for Web Crypto
When importing and exporting keys with the Web Crypto API, the following formats are supported:
Key Type | Supported Import Formats | Supported Export Formats |
---|---|---|
AES | raw, jwk | raw, jwk |
HMAC | raw, jwk | raw, jwk |
ECDH | pkcs8, spki, jwk | pkcs8, spki, jwk |
Format descriptions:
- raw: Unformatted binary data, used for symmetric keys (AES or HMAC), or Elliptic Curve public keys.
- pkcs8: PKCS #8 format for RSA or Elliptic Curve private keys.
- spki: SubjectPublicKeyInfo format for RSA or Elliptic Curve public keys.
- jwk: JSON Web Key format (supported by all key types)
Example of importing a key:
// Import an AES key in raw format
const rawKey = new Uint8Array([...]); // Your key bytes
const importedKey = await wc.crypto.subtle.importKey(
"raw", // format
rawKey, // key data
{ name: "AES-GCM" }, // algorithm
false, // extractable
["encrypt", "decrypt"] // key usages
);
Web Crypto API Support Matrix
Algorithm Operations:
- ✓ = Supported
- ✗ = Not Supported
Algorithm | encrypt() | decrypt() | sign() | verify() | digest() | deriveKey() | importKey() | exportKey() | deriveBits() | generateKey() |
---|---|---|---|---|---|---|---|---|---|---|
RSASSA-PKCS1v1 | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
RSA-PSS | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
RSA-OAEP | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
ECDH | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ |
ECDSA | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
AES-CTR | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✗ | ✓ |
AES-CBC | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✗ | ✓ |
AES-GCM | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✗ | ✓ |
HMAC | ✗ | ✗ | ✓ | ✓ | ✗ | ✗ | ✓ | ✓ | ✗ | ✗ |
SHA-1 | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ |
SHA-256 | ✗ | ✗ | ✗ | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ |
SHA-384 | ✗ | ✗ | ✗ | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ |
SHA-512 | ✗ | ✗ | ✗ | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ |
HKDF | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
PBKDF2 | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
Overview of KeplerCrypto
The KeplerCrypto API provides functionality that allows you to perform typical cryptographic operations such as read, generate, and manage cryptographic keys using industry-standard algorithms.
Get started
The KeplerCrypto API is supplied as part of the Kepler SDK.
Install and setup KeplerCrypto
To add the API to your project, in the root directory of your app, open the package.json file and add the following dependency.
"dependencies": {
"@amazon-devices/keplercrypto": "*",
...
},
Use the KeplerCrypto API in your app
In the source file in which you are using the KeplerCrypto functionality, import the data types that you need from the Turbo Module.
import {
AsymmetricAlgorithm,
AsymmetricKeyBuilder,
CbcContextBuilder,
CtrContextBuilder,
Crypto,
DigestAlgorithm,
EccCurve,
GcmContextBuilder,
KeyPurpose,
PrivateKey,
PublicKey,
SymmetricAlgorithm,
SymmetricKey,
} from '@amazon-devices/keplercrypto';
Next, get a Crypto
instance.
/**
* Create a Crypto object.
*/
const crypto = new Crypto();
Instancing the Crypto
class is always the first step to make use of the
KeplerCrypto Turbo Module.
KeplerCrypto common use cases
The following example code is for explanatory purposes and is simplified for clarity. Most of the methods in the KeplerCrypto API throw an exception when an error occurs.
Wrap your calls in a try-catch block to gracefully handle any errors that occur. For example:
try {
crypto.getRandom(length);
} catch (e) {
if (e instanceof InvalidArgumentError) {...}
else if (e instanceof SecurityError) {...}
}
Check the documentation for any method that you call for the specific exceptions that it throws.
Note: Running this Turbo Module in the simulator is not supported.
ECDSA signature and verification
The following example code includes ECDSA keypair generation, message signing, and verification.
Step 1: Create an ECDSA key builder
// Create a new ECDSA key builder
const keyBuilder = crypto.makeEccKeyBuilder();
// Mark the key as exportable
keyBuilder.exportable = true;
// Set key purposes
keyBuilder.purposes = [KeyPurpose.SIGN, KeyPurpose.VERIFY];
Step 2: Generate a keypair
You can generate an ECDSA keypair or construct a private key.
To generate an ECDSA keypair, call buildGenerated()
.
// Generate the ECDSA keypair
const privateKey = await keyBuilder.buildGenerated();
// Get the public key
const publicKey = privateKey.getPublicKey();
To construct a private key from an existing DER string, call buildPrivateFromDer()
.
// Build private key from an existing DER string
const privateKey = await keyBuilder.buildPrivateFromDer(privateKeyDer);
const publicKey = privateKey.getPublicKey();
Step 3: Create a signature context builder
const builder = crypto.makeSignatureContextBuilder();
builder.signingAlgorithm = crypto.getAsymmetricAlgorithmByName(AsymmetricAlgorithm.ECDSA);
Step 4: Sign the message and obtain the signature
// Build the signing context with the private key
const signer = builder.buildSigningContext(privateKey);
const message = 'This is a very important message';
const messageBuffer = new TextEncoder().encode(message).buffer;
// Sign the message
const signedMessage = await signer.sign(messageBuffer);
Step 5: Verify the message with the signature
// Build the verification context with the public key
const verifier = builder.buildVerificationContext(publicKey);
// Verify the message
const result = await verifier.verify(messageBuffer, signedMessage);
RSA signature and verification
The following example code includes RSA keypair generation, message signing, and verification.
Step 1: Create an RSA key builder
// Make an RSA key builder
const keybuilder = crypto.makeRsaKeyBuilder();
// Mark the key as exportable
keyBuilder.exportable = true;
// Set key purposes
keyBuilder.purposes = [KeyPurpose.SIGN, KeyPurpose.VERIFY];
Step 2: Generate a keypair
You can generate an RSA keypair or construct a private key.
To generate an RSA keypair, call buildGenerated()
.
// Generate the RSA keypair
const privateKey = await keyBuilder.buildGenerated();
// Get the public key
const publicKey = privateKey.getPublicKey();
To construct a private key from an existing DER string, call buildPrivateFromDer()
.
// Build private key from an existing DER string
const privateKey = await keyBuilder.buildPrivateFromDer(privateKeyDer);
const publicKey = privateKey.getPublicKey();
Step 3: Create a signature context builder and sign the message
Create a signature context builder by calling buildSigningContext()
and then
sign the message with the RSA-PKCS1 v1.5 scheme to get the signature.
// Build the signing context with the private key
const builder = crypto.makeRsassaPkcs1ContextBuilder();
const digestAlgorithm = crypto.getDigestAlgorithmByName(DigestAlgorithm.SHA256);
builder.digestAlgorithm = digestAlgorithm;
const rsaPkcs1Signer = builder.buildSigningContext(privateKey);
const message = 'This is a very important message';
const messageBuffer = new TextEncoder().encode(message).buffer;
// Sign the message
const signedMessage = await rsaPkcs1Signer.sign(messageBuffer);
Step 4: Verify the message
Use the signature to verify the message by calling rsaPkcs1Verifier.verify()
which uses the RSA-PKCS1 v1.5 standard.
// Build the verification context with the public key
const rsaPkcs1Verifier = builder.buildVerificationContext(publicKey);
// Verify the message
const result = await rsaPkcs1Verifier.verify(messageBuffer, signedMessage);
Step 5: Sign the message and get the signature
To sign the message using RSA-PSS standard call rsaPssSigner.sign()
which
returns the signature of the signed message.
// The salt length should typically match the digest length to make sure proper padding and security.
// Using a different salt length might still work, but it is not recommended.
builder.saltLength = digestAlgorithm.size / 8;
// Build the signing context with the private key
const rsaPssSigner = builder.buildSigningContext(privateKey);
const message = 'This is a very important message';
const messageBuffer = new TextEncoder().encode(message).buffer;
// Sign the message
const signedMessage = await rsaPssSigner.sign(messageBuffer);
To verify the message, call rsaPssVerifier.verify()
and pass the signature of
the message.
// Build the verification context with the public key
const rsaPssVerifier = builder.buildVerificationContext(publicKey);
// Verify the message
const result = await rsaPssVerifier.verify(messageBuffer, signedMessage);
HMAC signature and verification
The following example shows how to generate a secret key, export the key, sign the data and verify it.
// Generate a secret key
const keyBuilder = crypto.makeSymmetricKeyBuilder();
keyBuilder.exportable = true;
keyBuilder.purposes = [KeyPurpose.ENCRYPT, KeyPurpose.DECRYPT];
keyBuilder.bits = 256;
const key = await keyBuilder.buildGenerated();
// Export the secret key
const rawKey = key.exportRaw();
const exportedKeyBuilder = crypto.makeSymmetricKeyBuilder();
exportedKeyBuilder.exportable = true;
exportedKeyBuilder.purposes = [KeyPurpose.ENCRYPT, KeyPurpose.DECRYPT];
const exportedKey = await exportedKeyBuilder.buildFromRaw(rawKey);
// Get the digest algorithm
const digestAlgorithm = crypto.getDigestAlgorithmByName(DigestAlgorithm.SHA256);
// Make an HMAC Context Builder with the secret key and the digest algorithm
const builder = crypto.makeHmacContextBuilder();
builder.key = key;
builder.digestAlgorithm = digestAlgorithm;
// Sign the data
const signContext = builder.build();
const tag = await signContext.sign(plaintext);
// Verify the data
const verifyContext = builder.build();
const tag = signContext.sign(plaintext);
const result1 = await verifyContext.verify(plaintext, tag);
// Try to verify with exported key
const exportedBuilder = crypto.makeHmacContextBuilder();
exportedBuilder.key = exportedKey;
exportedBuilder.digestAlgorithm(digestAlgorithm);
const exportedVerifyContext = exportedBuilder.build();
const result2 = await exportedVerifyContext.verify(plaintext, tag);
Use an AES CBC Block Cipher to encrypt a message
The following example shows how to generate an AES-256 key, export it, create an AES-256 CBC cipher context builder, and use it to encrypt a plaintext.
// Generate an AES-256 key
const keyBuilder = crypto.makeSymmetricKeyBuilder();
keyBuilder.bits = 256;
keyBuilder.exportable = true;
const purposes = [KeyPurpose.ENCRYPT, KeyPurpose.DECRYPT];
keyBuilder.purposes = purposes;
const key = await keyBuilder.buildGenerated();
// Export the AES key
const rawKey = key.exportRaw();
const exportedKeyBuilder = crypto.makeSymmetricKeyBuilder();
exportedKeyBuilder.exportable = true;
exportedKeyBuilder.purposes = purposes;
const exportedKey = await exportedKeyBuilder.buildFromRaw(rawKey);
// Make an AES-256 CBC cipher context builder
const builder = crypto.makeCbcCipherContextBuilder(crypto.getSymmetricAlgorithmByName(SymmetricAlgorithm.AES256));
// Set the proper IV
const iv = new Uint8Array(builder.ivSize).buffer;
builder.iv = iv;
// Set the key
builder.key = key;
// Encrypt the plaintext
const plaintext = 'try this string';
const plaintextBuffer = new TextEncoder().encode(plaintext).buffer;
const encryptionContext = builder.buildEncryptionContext();
const ciphertext = await encryptionContext.encrypt(plaintextBuffer);
// Decrypt the ciphertext
const decryptionContext = builder.buildDecryptionContext();
const decrypted = await decryptionContext.decrypt(ciphertext);
const result1 = plaintext === new TextDecoder('utf-8').decode(decrypted);
// Try to use the exported key and then decrypt the ciphertext with it
const exportedBuilder = crypto.makeCbcCipherContextBuilder(crypto.getSymmetricAlgorithmByName(SymmetricAlgorithm.AES256));
exportedBuilder.iv = iv;
exportedBuilder.key = exportedKey;
const exportedDecryptionContext = exportedBuilder.buildDecryptionContext();
const exportedDecrypted = await exportedDecryptionContext.decrypt(ciphertext);
Use an AES CTR Block Cipher to encrypt a message
The following example shows how to generate an AES-CTR key, export it, create an AES-256 CTR cipher context builder, and use it to encrypt a plaintext.
// Generate an AES-256 key
const purposes = [KeyPurpose.ENCRYPT, KeyPurpose.DECRYPT];
const keyBuilder = crypto.makeSymmetricKeyBuilder();
keyBuilder.exportable = true;
keyBuilder.purposes = purposes;
keyBuilder.bits = 256;
const key = await keyBuilder.buildGenerated();
// Export the AES key
const rawKey = key.exportRaw();
const exportedKeyBuilder = crypto.makeSymmetricKeyBuilder();
exportedKeyBuilder.exportable = true;
exportedKeyBuilder.purposes = purposes;
const exportedKey = await exportedKeyBuilder.buildFromRaw(rawKey);
// Make an AES CTR cipher context builder
const builder = crypto.makeCtrCipherContextBuilder(crypto.getSymmetricAlgorithmByName(SymmetricAlgorithm.AES256));
// Set a proper count and nonce
builder.count = 12;
builder.nonce = 12;
// Set the key
builder.key = key;
// Encrypt the plaintext
const plaintext = 'try this string';
const plaintextBuffer = new TextEncoder().encode(plaintext).buffer;
const encryptionContext = builder.buildEncryptionContext();
const ciphertext = await encryptionContext.encrypt(plaintextBuffer);
// Decrypt the ciphertext
const decryptionContext = builder.buildDecryptionContext();
const decrypted = await decryptionContext.decrypt(ciphertext);
const result1 = plaintext === new TextDecoder('utf-8').decode(decrypted);
// Use the exported key with the CTR cipher context builder
builder.key = exportedKey;
// Try to decrypt using the exported key
const exportedDecryptionContext = builder.buildDecryptionContext();
const exportedDecrypted = await exportedDecryptionContext.decrypt(ciphertext);
const result2 = plaintext === new TextDecoder('utf-8').decode(exportedDecrypted);
Use an AES GCM Block Cipher to encrypt a message
The following example shows how to generate an AES-256 GCM key, export it, create an AES-256 GCM cipher context builder, and use it to encrypt a plaintext.
// Generate an AES-256 key
const purposes = [KeyPurpose.ENCRYPT, KeyPurpose.DECRYPT];
const keyBuilder = crypto.makeSymmetricKeyBuilder();
keyBuilder.exportable = true;
keyBuilder.purposes = purposes;
keyBuilder.bits = 256;
const key = await keyBuilder.buildGenerated();
// Export the AES key
const rawKey = key.exportRaw();
const exportedKeyBuilder = crypto.makeSymmetricKeyBuilder();
exportedKeyBuilder.exportable = true;
exportedKeyBuilder.purposes = purposes;
const exportedKey = await exportedKeyBuilder.buildFromRaw(rawKey);
// Make an AES-256 GCM cipher context builder
const builder = crypto.makeGcmCipherContextBuilder(crypto.getSymmetricAlgorithmByName(SymmetricAlgorithm.AES256));
// Initialize the IV with a 4-byte identifier in order to use a randomized sequence
// of nonces. Alternatively, initialize it with a vector of size builder.getIvSize()
// to use an arbitrary nonce.
const iv = new Uint8Array([0xab, 0xcd, 0xef, 0x01]);
builder.iv = iv;
builder.key = key;
// Encrypt the plaintext
const plaintext = 'try this string';
const plaintextBuffer = new TextEncoder().encode(plaintext).buffer;
const encryptionContext = builder.buildEncryptionContext();
const ciphertext = await encryptionContext.encrypt(plaintextBuffer);
// Decrypt the ciphertext
const decryptionContext = builder.buildDecryptionContext();
const decrypted = await decryptionContext.decrypt(ciphertext);
// Use the exported key with the GCM cipher context builder
builder.key = exportedKey;
// Try to decrypt using the exported key
const exportedDecryptionContext = builder.buildDecryptionContext();
const exportedDecrypted = await exportedDecryptionContext.decrypt(ciphertext);
Use RSA-OAEP to encrypt a message
The following example shows how to generate an RSA-OAEP key, export it, create a context builder, and use it to encrypt a plaintext.
// Get the digest algorithm and the asymmetric algorithm, and define the key purposes
const sha = crypto.getDigestAlgorithmByName(DigestAlgorithm.SHA384);
const alg = crypto.getAsymmetricAlgorithmByName(AsymmetricAlgorithm.RSA_OAEP);
const purposes = [KeyPurpose.ENCRYPT, KeyPurpose.DECRYPT];
// Make an RsaOaepKeyBuilder
const keyBuilder = crypto.makeRsaOaepKeyBuilder();
keyBuilder.exportable = true;
keyBuilder.purposes = purposes;
keyBuilder.bits = 384;
// Generate a private key from the keybuilder
const privateKey = await keyBuilder.buildGenerated();
// Export the key for later test case
const rawPrivateKey = await privateKey.exportDer();
// Get the corresponding public key
const publicKey = privateKey.getPublicKey();
// Export the public key for later use
const rawPublicKey = await publickey.exportDer();
// Build the plaintext from a message
const plaintext = 'try this string';
const plaintextBuffer = new TextEncoder().encode(plaintext).buffer;
// Build an RSA-OAEP cipher context using alg and sha
const rsaOaepCipherContext = crypto.makeRsaOaepCipherContextBuilder(alg, sha);
// Build encryption context from RSA-OAEP cipher context with public key
const rsaOaepEncryptionContext = rsaOaepCipherContext.buildEncryptionContext(publickey);
// Encrypt with the encryption context
const ciphertext = await rsaOaepEncryptionContext.encrypt(plaintextBuffer);
// Build the decryption context from the cipher context
const rsaOaepDecryptionContext = rsaOaepCipherContext.buildDecryptionContext(privatekey);
// Decrypt with the decryption context
const decrypted = await rsaOaepDecryptionContext.decrypt(ciphertext);
// Ensure that the decrypted message is the same as the plaintext
const result1 = plaintext === new TextDecoder('utf-8').decode(decrypted);
// Use the exported key to build a private key and public key
const exportedPrivkey = await keyBuilder.buildPrivateFromDer(rawPrivateKey);
const exportedRsaOaepDecryptionContext = rsaOaepCipherContext.buildDecryptionContext(exportedPrivkey);
// Use the imported key to decrypt the ciphertext and ensure that the decrypted message is equal to the plaintext
const exportedDecrypted = await exportedRsaOaepDecryptionContext.decrypt(ciphertext);
const result2 = plaintext === new TextDecoder('utf-8').decode(exportedDecrypted);
// Use the imported public key to encrypt, the original private key to decrypt
// and ensure that decrypted message is equal to the plaintext
const exportedPublicKey = await keyBuilder.buildPublicFromDer(rawPublicKey);
const exportedRsaOaepEncryptionContext = rsaOaepCipherContext.buildEncryptionContext(exportedPublicKey);
const exportedCiphertext = await exportedRsaOaepEncryptionContext.encrypt(plaintextBuffer);
const exportedDecryptedAgain = await rsaOaepDecryptionContext.decrypt(exportedCiphertext);
const result3 = plaintext === new TextDecoder('utf-8').decode(exportedDecryptedAgain);
Create a message digest
The following code example calls getDigestAlgorithmByName()
to get the digest,
creates a digest context, encodes an example string, and gets a digest for the string.
const builder = crypto.makeDigestContextBuilder();
builder.digestAlgorithm = crypto.getDigestAlgorithmByName(DigestAlgorithm.SHA256);
const digestContext = builder.build();
const plaintext = 'try this string';
const plaintextBuffer = new TextEncoder().encode(plaintext).buffer;
const hash = await digestContext.digest(plaintextBuffer);
Key derivation using HKDF (HMAC Key Derivation Function)
const salt = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const info = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // Extra info from the key
// Create a secret (optional if you already have a secret with the DERIVE purpose)
const keyBuilder = crypto.makeSymmetricKeyBuilder();
keyBuilder.bits = 256; // arbitrary length
keyBuilder.exportable = false;
keyBuilder.purposes = [KeyPurpose.DERIVE]; // IMPORTANT: the secret must be able to derive
const secret = await keyBuilder.buildGenerated();
const algorithm = crypto.getDigestAlgorithmByName(DigestAlgorithm.SHA256);
// Create a context builder
const ctxBuilder = crypto.makeHkdfContextBuilder();
ctxBuilder.info = info;
ctxBuilder.salt = salt;
ctxBuilder.hashAlgorithm = algorithm;
// Create a derivation context
const ctx = ctxBuilder.buildKeyDerivationContext(secret);
// Derive a symmetric key
// IMPORTANT: the derived key must match the length of the algorithm being used
const generatedKeyLen = algorithm.size / 8;
const symmKey = ctx.deriveKey(generatedKeyLen);
// Derive a raw key instead
const rawKey = ctx.deriveBits(generatedKeyLen);
Key derivation using PBKDF2 (Password-Based Key Derivation Function 2)
const iterations = 10000; // Number of iterations. Higher values increase security but decrease performance.
// 10000 is a good value that does not deteriorate too much the performance
const salt = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const generatedKeyLen = 512;
// Create a context builder
const ctxBuilder = crypto.makePbkdf2ContextBuilder();
// Create a secret (optional if you already have a secret with the DERIVE purpose)
const keyBuilder = crypto.makeSymmetricKeyBuilder()
keyBuilder.bits = 256; // arbitrary length
keyBuilder.exportable = false;
keyBuilder.purposes = [KeyPurpose.DERIVE]; // IMPORTANT: the secret must be able to derive
const secret = await keyBuilder.buildGenerated();
const algorithm = crypto.getDigestAlgorithmByName(DigestAlgorithm.SHA256);
// Create a derivation context
ctxBuilder.iterations = iterations;
ctxBuilder.salt = salt;
ctxBuilder.hashAlgorithm = algorithm;
const ctx = buildKeyDerivationContext(secret);
// Derive a symmetric key
const symmKey = await ctx.deriveKey(generatedKeyLen);
// Derive a raw key instead
const rawKey = await ctx.deriveBits(generatedKeyLen);
Web Crypto API shim layer
The KeplerCrypto library provides a shim layer that implements the Web Crypto API. We refer to the existing Web Crypto API MDN documentation for the API methods and parameters.
Web Crypto usage example
The following example presents how to import and use the Web Crypto API shim layer:
Import the API
import {WebCrypto} from '@amazon-devices/keplercrypto';
Initialize the WebCrypto
object
const wc = new WebCrypto();
Optional: Expose the crypto
instance on globalThis
// Define globalThis if it doesn't exist
if (typeof globalThis === 'undefined') {
(window as any).globalThis = window;
}
globalThis.crypto = wc.crypto as any;
Call to Web Crypto APIs
The next code example generates an ECDH CryptoKeyPair
:
const nonExtractableKeyPair = (await wc.crypto.subtle.generateKey(
{
name: 'ECDH',
namedCurve: 'P-256',
},
false, // non-extractable
['deriveBits'],
)) as CryptoKeyPair;
Supported Web Crypto algorithms
This library is a work in progress and the Web Crypto implementation provides a subset of the whole W3C Web Crypto specification. More operations will be supported in future versions.
Supported key formats for Web Crypto
When importing and exporting keys with the Web Crypto API, the following formats are supported:
Key Type | Supported Import Formats | Supported Export Formats |
---|---|---|
AES | raw, jwk | raw, jwk |
HMAC | raw, jwk | raw, jwk |
ECDH | pkcs8, spki, jwk | pkcs8, spki, jwk |
ECDSA | pkcs8, spki, jwk | pkcs8, spki, jwk |
HKDF | raw | raw |
PBKDF2 | raw | raw |
RSASSA-PKCS1-v1_5 | pkcs8, spki, jwk | pkcs8, spki, jwk |
RSA-PSS | pkcs8, spki, jwk | pkcs8, spki, jwk |
RSA-OAEP | pkcs8, spki, jwk | pkcs8, spki, jwk |
Format descriptions:
- raw: Unformatted binary data, used for symmetric keys or Elliptic Curve public keys.
- pkcs8: PKCS #8 format for RSA or Elliptic Curve private keys.
- spki: SubjectPublicKeyInfo format for RSA or Elliptic Curve public keys.
- jwk: JSON Web Key format for symmetric keys or RSA and Elliptic Curve public/private keys.
Example of importing a key:
// Import an AES key in raw format
const rawKey = new Uint8Array([...]); // Your key bytes
const importedKey = await wc.crypto.subtle.importKey(
"raw", // format
rawKey, // key data
{ name: "AES-GCM" }, // algorithm
false, // extractable
["encrypt", "decrypt"] // key usages
);
Web Crypto API Support Matrix
Algorithm Operations:
- ✓ = Supported
- ✗ = Unsupported
- — = Not applicable
Algorithm | encrypt() | decrypt() | sign() | verify() | digest() | importKey() | exportKey() | generateKey() | deriveBits() | deriveKey() | wrapKey() | unwrapKey() |
---|---|---|---|---|---|---|---|---|---|---|---|---|
RSASSA-PKCS1v1 | — | — | ✓ | ✓ | — | ✓ | ✓ | ✓ | — | — | — | — |
RSA-PSS | — | — | ✓ | ✓ | — | ✓ | ✓ | ✓ | — | — | — | — |
RSA-OAEP | ✓ | ✓ | — | — | — | ✓ | ✓ | ✓ | — | — | ✗ | ✗ |
ECDH | — | — | — | — | — | ✓ | ✓ | ✓ | ✓ | ✓ | — | — |
ECDSA | — | — | ✓ | ✓ | — | ✓ | ✓ | ✓ | — | — | — | — |
AES-CTR | ✓ | ✓ | — | — | — | ✓ | ✓ | ✓ | — | — | ✗ | ✗ |
AES-CBC | ✓ | ✓ | — | — | — | ✓ | ✓ | ✓ | — | — | ✗ | ✗ |
AES-GCM | ✓ | ✓ | — | — | — | ✓ | ✓ | ✓ | — | — | ✗ | ✗ |
AES-KW | — | — | — | — | — | ✗ | ✗ | ✗ | — | — | ✗ | ✗ |
HMAC | — | — | ✓ | ✓ | — | ✓ | ✓ | ✓ | — | — | — | — |
HKDF | — | — | — | — | — | ✓ | ✓ | — | ✓ | ✓ | — | — |
PBKDF2 | — | — | — | — | — | ✓ | ✓ | — | ✓ | ✓ | — | — |
Ed25519 | — | — | ✗ | ✗ | — | ✗ | ✗ | ✗ | — | — | — | — |
X25519 | — | — | — | — | — | ✗ | ✗ | ✗ | ✗ | ✗ | — | — |
SHA-1 | — | — | — | — | ✗ | — | — | — | — | — | — | — |
SHA-256 | — | — | — | — | ✓ | — | — | — | — | — | — | — |
SHA-384 | — | — | — | — | ✓ | — | — | — | — | — | — | — |
SHA-512 | — | — | — | — | ✓ | — | — | — | — | — | — | — |
Modules
- Crypto
- index
- turbo-modules/__mocks__/KeplerCrypto
- turbo-modules/__mocks__/KeplerCrypto
- turbo-modules/__mocks__/KeplerCrypto
- turbo-modules/KeplerCrypto
- turbo-modules/KeplerCrypto
- webcrypto/Crypto
- webcrypto/Crypto
- webcrypto/errors
- webcrypto/errors
- webcrypto/SubtleCrypto
- webcrypto/SubtleCrypto
- webcrypto/types
- webcrypto/types
- webcrypto/WebCrypto
- webcrypto/WebCrypto
Last updated: Oct 02, 2025