0.4.0 • Published 10 months ago

@s77rt/react-native-sodium v0.4.0

Weekly downloads
-
License
MIT
Repository
github
Last release
10 months ago

@s77rt/react-native-sodium is a high-performance cryptography library written in C++ that uses the JavaScript Interface (JSI) to provide access to the Sodium API. Sodium is a modern, easy-to-use library for encryption, decryption, signatures, password hashing, and more.

Installation

npm install @s77rt/react-native-sodium

Usage

For detailed documentation checkout libsodium Documentation.

Initialization

import sodium from "@s77rt/react-native-sodium";

if (sodium.sodium_init() < 0) {
	throw new Error("Failed to initialize sodium!");
}

Generating random data

Random number

randombytes_random(): number;
const rnd = sodium.randombytes_random();
console.log("Random number:", rnd);

Random number within interval

randombytes_uniform(upperBound: number): number;
const upperBound = 100;
const rnd = sodium.randombytes_uniform(upperBound);
console.log("Random number:", rnd);

Random byte sequence

randombytes_buf(buf: ArrayBuffer, size: number): void;
const buf = new ArrayBuffer(8);
sodium.randombytes_buf(buf, buf.byteLength);
console.log("Random byte sequence:", new Uint8Array(buf));

Deterministic random byte sequence

randombytes_buf_deterministic(buf: ArrayBuffer, size: number, seed: ArrayBuffer): void;
const buf = new ArrayBuffer(8);
const seed = new ArrayBuffer(sodium.randombytes_SEEDBYTES);
new TextEncoder().encodeInto("Couscous", new Uint8Array(seed));
sodium.randombytes_buf_deterministic(buf, buf.byteLength, seed);
console.log("Deterministic random byte sequence:", new Uint8Array(buf));

Generator deallocation

randombytes_close(): number;
randombytes_close();

Generator reseeding

randombytes_stir(): void;
randombytes_stir();

Public-key cryptography

Authenticated encryption

crypto_box_keypair(pk: ArrayBuffer, sk: ArrayBuffer): number;
crypto_box_seed_keypair(pk: ArrayBuffer, sk: ArrayBuffer, seed: ArrayBuffer): number;
crypto_box_easy(c: ArrayBuffer, m: ArrayBuffer, mLen: number, n: ArrayBuffer, pk: ArrayBuffer, sk: ArrayBuffer): number;
crypto_box_open_easy(m: ArrayBuffer, c: ArrayBuffer, cLen: number, n: ArrayBuffer, pk: ArrayBuffer, sk: ArrayBuffer): number;
const alicePK = new ArrayBuffer(sodium.crypto_box_PUBLICKEYBYTES);
const aliceSK = new ArrayBuffer(sodium.crypto_box_SECRETKEYBYTES);
const bobPK = new ArrayBuffer(sodium.crypto_box_PUBLICKEYBYTES);
const bobSK = new ArrayBuffer(sodium.crypto_box_SECRETKEYBYTES);
const nonce = new ArrayBuffer(sodium.crypto_box_NONCEBYTES);
const message = new TextEncoder().encode("Fennec fox").buffer;
const messageL = message.byteLength;
const cipherL = sodium.crypto_box_MACBYTES + messageL;
const cipher = new ArrayBuffer(cipherL);
const decrypted = new ArrayBuffer(messageL);
sodium.crypto_box_keypair(alicePK, aliceSK);
sodium.crypto_box_keypair(bobPK, bobSK);
sodium.randombytes_buf(nonce, nonce.byteLength);
sodium.crypto_box_easy(cipher, message, messageL, nonce, bobPK, aliceSK);
sodium.crypto_box_open_easy(decrypted, cipher, cipherL, nonce, alicePK, bobSK);
console.log("Message:", new Uint8Array(message));
console.log("Cipher:", new Uint8Array(cipher));
console.log("Decrypted:", new Uint8Array(decrypted));

Sealed boxes

crypto_box_seal(c: ArrayBuffer, m: ArrayBuffer, mLen: number, pk: ArrayBuffer): number;
crypto_box_seal_open(m: ArrayBuffer, c: ArrayBuffer, cLen: number, pk: ArrayBuffer, sk: ArrayBuffer): number;
const bobPK = new ArrayBuffer(sodium.crypto_box_PUBLICKEYBYTES);
const bobSK = new ArrayBuffer(sodium.crypto_box_SECRETKEYBYTES);
const message = new TextEncoder().encode("Fennec fox").buffer;
const messageL = message.byteLength;
const cipherL = sodium.crypto_box_SEALBYTES + messageL;
const cipher = new ArrayBuffer(cipherL);
const decrypted = new ArrayBuffer(messageL);
sodium.crypto_box_keypair(bobPK, bobSK);
sodium.crypto_box_seal(cipher, message, messageL, bobPK);
sodium.crypto_box_seal_open(decrypted, cipher, cipherL, bobPK, bobSK);
console.log("Message:", new Uint8Array(message));
console.log("Cipher:", new Uint8Array(cipher));
console.log("Decrypted:", new Uint8Array(decrypted));

Public-key signatures

crypto_sign_keypair(pk: ArrayBuffer, sk: ArrayBuffer): number;
crypto_sign_seed_keypair(pk: ArrayBuffer, sk: ArrayBuffer, seed: ArrayBuffer): number;
crypto_sign(sm: ArrayBuffer, smLenP: ArrayBuffer | null, m: ArrayBuffer, mLen: number, sk: ArrayBuffer): number;
crypto_sign_open(m: ArrayBuffer, mLenP: ArrayBuffer | null, sm: ArrayBuffer, smLen: number, pk: ArrayBuffer): number;
const bobPK = new ArrayBuffer(sodium.crypto_sign_PUBLICKEYBYTES);
const bobSK = new ArrayBuffer(sodium.crypto_sign_SECRETKEYBYTES);
const message = new TextEncoder().encode("I saw a fennec fox").buffer;
const messageL = message.byteLength;
const signedL = sodium.crypto_sign_BYTES + messageL;
const signed = new ArrayBuffer(signedL);
sodium.crypto_sign_keypair(bobPK, bobSK);
if (sodium.crypto_sign(signed, null, message, messageL, bobSK) !== 0) {
	throw new Error("Failed to sign the message!");
}
if (sodium.crypto_sign_open(null, null, signed, signedL, bobPK) !== 0) {
	throw new Error("Invalid signature!");
}
console.log("Message:", new Uint8Array(message));
console.log("Signed:", new Uint8Array(signed));

Hashing

Generic hashing

Single-part
crypto_generichash(output: ArrayBuffer, outputLen: number, input: ArrayBuffer, inputLen: number, key: ArrayBuffer | null, keyLen: number): number;
const output = new ArrayBuffer(sodium.crypto_generichash_BYTES);
const input = new TextEncoder().encode("Fennec fox").buffer;
const key = new ArrayBuffer(sodium.crypto_generichash_KEYBYTES);
sodium.crypto_generichash_keygen(key);
sodium.crypto_generichash(
	output,
	output.byteLength,
	input,
	input.byteLength,
	key,
	key.byteLength
);
console.log(
	"Key:",
	sodium.sodium_bin2hex(
		new ArrayBuffer(key.byteLength * 2 + 1),
		key.byteLength * 2 + 1,
		key,
		key.byteLength
	)
);
console.log(
	"Hash:",
	sodium.sodium_bin2hex(
		new ArrayBuffer(output.byteLength * 2 + 1),
		output.byteLength * 2 + 1,
		output,
		output.byteLength
	)
);
Multi-part
crypto_generichash_init(state: Record<string, never>, key: ArrayBuffer | null, keyLen: number, outputLen: number): number;
crypto_generichash_update(state: Record<string, never>, input: ArrayBuffer, inputLen: number): number;
crypto_generichash_final(state: Record<string, never>, output: ArrayBuffer, outputLen: number): number;
const output = new ArrayBuffer(sodium.crypto_generichash_BYTES);
const input1 = new TextEncoder().encode("Fennec ").buffer;
const input2 = new TextEncoder().encode("fox").buffer;
const key = new ArrayBuffer(sodium.crypto_generichash_KEYBYTES);
const state = {};
sodium.crypto_generichash_keygen(key);
sodium.crypto_generichash_init(state, key, key.byteLength, output.byteLength);
sodium.crypto_generichash_update(state, input1, input1.byteLength);
sodium.crypto_generichash_update(state, input2, input2.byteLength);
sodium.crypto_generichash_final(state, output, output.byteLength);
console.log(
	"Key:",
	sodium.sodium_bin2hex(
		new ArrayBuffer(key.byteLength * 2 + 1),
		key.byteLength * 2 + 1,
		key,
		key.byteLength
	)
);
console.log(
	"Hash:",
	sodium.sodium_bin2hex(
		new ArrayBuffer(output.byteLength * 2 + 1),
		output.byteLength * 2 + 1,
		output,
		output.byteLength
	)
);
Keygen
crypto_generichash_keygen(k: ArrayBuffer): void;
const k = new ArrayBuffer(sodium.crypto_generichash_KEYBYTES);
sodium.crypto_generichash_keygen(k);
console.log(
	"Key:",
	sodium.sodium_bin2hex(
		new ArrayBuffer(k.byteLength * 2 + 1),
		k.byteLength * 2 + 1,
		k,
		k.byteLength
	)
);

Short-input hashing

Short hash
crypto_shorthash(output: ArrayBuffer, input: ArrayBuffer, inputLen: number, k: ArrayBuffer): number;
const output = new ArrayBuffer(sodium.crypto_shorthash_BYTES);
const input = new TextEncoder().encode("Fennec fox").buffer;
const k = new ArrayBuffer(sodium.crypto_shorthash_KEYBYTES);
sodium.crypto_shorthash_keygen(k);
sodium.crypto_shorthash(output, input, input.byteLength, k);
console.log(
	"Key:",
	sodium.sodium_bin2hex(
		new ArrayBuffer(k.byteLength * 2 + 1),
		k.byteLength * 2 + 1,
		k,
		k.byteLength
	)
);
console.log(
	"Hash:",
	sodium.sodium_bin2hex(
		new ArrayBuffer(output.byteLength * 2 + 1),
		output.byteLength * 2 + 1,
		output,
		output.byteLength
	)
);
Keygen
crypto_shorthash_keygen(k: ArrayBuffer): void;
const k = new ArrayBuffer(sodium.crypto_shorthash_KEYBYTES);
sodium.crypto_shorthash_keygen(k);
console.log(
	"Key:",
	sodium.sodium_bin2hex(
		new ArrayBuffer(k.byteLength * 2 + 1),
		k.byteLength * 2 + 1,
		k,
		k.byteLength
	)
);

Padding

Pad

sodium_pad(paddedBufLenP: ArrayBuffer, buf: ArrayBuffer, unpaddedBufLen: number, blockSize: number, maxBufLen: number): number;
const paddedBufLenP = new ArrayBuffer(8); // 8 bytes are needed to store a size_t number
const buf = new ArrayBuffer(64);
const message = new TextEncoder().encode("Fennec fox");
new Uint8Array(buf).set(message);
const unpaddedBufLen = message.byteLength;
const blockSize = 16;
sodium.sodium_pad(
	paddedBufLenP,
	buf,
	unpaddedBufLen,
	blockSize,
	buf.byteLength
);
const paddedBufLen = Number(new DataView(paddedBufLenP).getBigUint64(0, true)); // Safe as long as you are not working with a 9PB data
console.log("Padded buf:", new Uint8Array(buf.slice(0, paddedBufLen)));

Unpad

sodium_unpad(unpaddedBufLenP: ArrayBuffer, buf: ArrayBuffer, paddedBufLen: number, blockSize: number): number;
const unpaddedBufLenP = new ArrayBuffer(8); // 8 bytes are needed to store a size_t number
const buf = new Uint8Array([
	70, 101, 110, 110, 101, 99, 32, 102, 111, 120, 128, 0, 0, 0, 0, 0,
]).buffer;
const blockSize = 16;
sodium.sodium_unpad(unpaddedBufLenP, buf, buf.byteLength, blockSize);
const unpaddedBufLen = Number(
	new DataView(unpaddedBufLenP).getBigUint64(0, true) // Safe as long as you are not working with a 9PB data
);
console.log("Unpadded buf:", new Uint8Array(buf.slice(0, unpaddedBufLen)));

Helpers

Constant-time test for equality

sodium_memcmp(b1_: ArrayBuffer, b2_: ArrayBuffer, len: number): number;
const b1_ = new Uint8Array([7, 7, 1, 2]).buffer;
const b2_ = new Uint8Array([7, 7, 100, 200]).buffer;
console.log("isEqual:", sodium.sodium_memcmp(b1_, b2_, 2) === 0);

Hexadecimal encoding/decoding

sodium_bin2hex(hex: ArrayBuffer, hexMaxLen: number, bin: ArrayBuffer, binLen: number): string;
sodium_hex2bin(bin: ArrayBuffer, binMaxLen: number, hex: string, hexLen: number, ignore: string | null, binLen: ArrayBuffer, hexEnd: ArrayBuffer | null): number;
const bin = new Uint8Array([0, 255, 0, 255]).buffer;
console.log(
	"Hex:",
	sodium.sodium_bin2hex(
		new ArrayBuffer(bin.byteLength * 2 + 1), // Each byte is encoded into two characters, plus one for the null character
		bin.byteLength * 2 + 1,
		bin,
		bin.byteLength
	)
);
const hex = "00ff00ff";
const bin = new ArrayBuffer(hex.length / 2); // Every two characters fit into a single byte
sodium.sodium_hex2bin(
	bin,
	bin.byteLength,
	hex,
	hex.length,
	null,
	new ArrayBuffer(8), // 8 bytes are needed to store a size_t number, not used in this example
	null
);
console.log("Binary:", new Uint8Array(bin));

Base64 encoding/decoding

sodium_base64_encoded_len(binLen: number, variant: number): number;
sodium_bin2base64(b64: ArrayBuffer, b64MaxLen: number, bin: ArrayBuffer, binLen: number, variant: number): string;
sodium_base642bin(bin: ArrayBuffer, binMaxLen: number, b64: string, b64Len: number, ignore: string | null, binLen: ArrayBuffer, b64End: ArrayBuffer | null, variant: number): number;
const variant = sodium.sodium_base64_VARIANT_ORIGINAL;
const bin = new Uint8Array([0, 255, 0, 255]).buffer;
const b64Len = sodium.sodium_base64_encoded_len(bin.byteLength, variant);
console.log(
	"Base64:",
	sodium.sodium_bin2base64(
		new ArrayBuffer(b64Len),
		b64Len,
		bin,
		bin.byteLength,
		variant
	)
);
const variant = sodium.sodium_base64_VARIANT_ORIGINAL;
const b64 = "AP8A/w==";
const bin = new ArrayBuffer(Math.ceil((b64.length / 4) * 3)); // Bin will take at most (b64.length / 4) * 3 bytes. Use binLen to get the exact length
const binLen = new ArrayBuffer(8); // 8 bytes are needed to store a size_t number
sodium.sodium_base642bin(
	bin,
	bin.byteLength,
	b64,
	b64.length,
	null,
	binLen,
	null,
	variant
);
const binLenAsNumber = Number(new DataView(binLen).getBigUint64(0, true)); // Safe as long as you are not working with a 9PB data
console.log("Binary:", new Uint8Array(bin.slice(0, binLenAsNumber)));

Large numbers arithmetic operations

sodium_increment(n: ArrayBuffer, nLen: number): void;
sodium_add(a: ArrayBuffer, b: ArrayBuffer, len: number): void;
sodium_sub(a: ArrayBuffer, b: ArrayBuffer, len: number): void;
sodium_compare(b1_: ArrayBuffer, b2_: ArrayBuffer, len: number): number;
const a = new Uint8Array(16).fill(255, 0, 10).buffer; // a=1208925819614629174706175
const b = new Uint8Array(16).fill(255, 0, 10).buffer; // b=1208925819614629174706175
console.log("Comparison", sodium.sodium_compare(a, b, a.byteLength));
sodium.sodium_increment(a, a.byteLength);
console.log("Comparison", sodium.sodium_compare(a, b, a.byteLength));
sodium.sodium_sub(a, b, a.byteLength);
console.log("Comparison", sodium.sodium_compare(a, b, a.byteLength));

Testing for all zeros

sodium_is_zero(n: ArrayBuffer, nLen: number): number;
const n = new ArrayBuffer(8);
console.log("isZero", sodium.sodium_is_zero(n, n.byteLength) === 1);

Clearing the stack

sodium_stackzero(len: number): void;
sodium.sodium_stackzero(4);

FAQ

Q: Why functions that take char* parameters sometimes use ArrayBuffer and other times use string?

A: If the input is encoding-dependant an ArrayBuffer is used and it's the responsibility of the user to choose the desired encoding. You can use TextEncoder to generate an array buffer with UTF-8 encoding. On the other hand if the input is guaranteed to be representable in ASCII then it will be interpreted as ASCII and a string is used.

If the input is meant to be written into then an ArrayBuffer must be used since strings are immutable.

Q: Why are some libsodium functions not implemented?

A: This library aims to provide a 1:1 libsodium compatibility however functions are implemented progressively and per priority and needs. Feel free to submit an issue for prioritization.

PRs are welcome!

License

MIT

0.4.0

10 months ago

0.3.0

10 months ago

0.2.1

10 months ago

0.2.0

11 months ago

0.1.2

11 months ago

0.1.1

11 months ago