Completed random support functions

This commit is contained in:
Ugljesa Jovanovic 2020-10-03 18:16:26 +02:00
parent cb17d75deb
commit 0431f27e3e
No known key found for this signature in database
GPG Key ID: 178E6DFCECCB0E0F
10 changed files with 430 additions and 290 deletions

275
README.md
View File

@ -3,279 +3,10 @@
# Kotlin Multiplatform Crypto Library
#Note:
### Next stable release will be published after public release of Kotlin 1.4, until then API will change significantly
Kotlin Multiplatform Crypto is a library for various cryptographic applications.
The library comes in two flavors `multiplatform-crypto` and `multiplatform-crypto-delegated`. This project also provides
direct libsodium bindings under `multiplatform-crypto-libsodium-bindings`.
* `multiplatform-crypto` contains pure kotlin implementations, is not reviewed, should be considered unsafe and only
for prototyping or experimentation purposes.
* `multiplatform-crypto-delegated` relies on platform specific implementations, mostly libsodium, but care should still be taken that the kotlin code is not reviewed or proven safe.
APIs of both variants are identical.
* `multiplatform-crypto-libsodium-bindings` is a generated bindings library using `kotlin-multiplatform-libsodium-generator`
* Under HEAVY development at the moment
### Table of contents
1. [Supported platforms](#supported-platforms-by-variant)
2. [API](#api)
3. TODO
## Supported platforms by variant
|Platform|Pure variant| Delegated variant|
|--------|------------|------------------|
|Linux X86 64| :heavy_check_mark: | :heavy_check_mark: |
|Linux Arm 64| :heavy_check_mark: | :heavy_check_mark: |
|Linux Arm 32| :heavy_check_mark: | :x: |
|macOS X86 64| :heavy_check_mark: | :heavy_check_mark: |
|iOS x86 64 | :heavy_check_mark: | :heavy_check_mark: |
|iOS Arm 64 | :heavy_check_mark: | :heavy_check_mark: |
|iOS Arm 32 | :heavy_check_mark: | :heavy_check_mark: |
|watchOS X86 32 | :heavy_check_mark: | :heavy_check_mark: |
|watchOS Arm 64(_32) | :heavy_check_mark: | :heavy_check_mark: |
|watchos Arm 32 | :heavy_check_mark: | :heavy_check_mark: |
|tvOS X86 64 | :heavy_check_mark: | :heavy_check_mark: |
|tvOS Arm 64 | :heavy_check_mark: | :heavy_check_mark: |
|minGW X86 64| :heavy_check_mark: | :heavy_check_mark: |
|minGW X86 32| :x: | :x: |
## Sample project
The library includes sample project that shows usage on different platforms
- NOTE: Currently only linux, macOs and windows are included.
## Notes & Roadmap
**The API will move fast and break often until v1.0**
Next steps:
- Expand API (ECC, Signing ...)
## Should I use this in production?
**NO.**
The library is under HEAVY development.
## Why?
This is an experimental implementation, mostly for expanding personal understanding of cryptography.
It's not peer reviewed, not guaranteed to be bug free, and not guaranteed to be secure.
## API for Pure and Delegated flavourd
### Hashing functions
* Blake2b
* SHA512
* SHA256
### Key Derivation
* Argon2
### Authenticated symmetric encryption (AEAD)
* XChaCha20-Poly1305
### Delegated flavor dependancy table
The following table describes which library is used for particular cryptographic primitive
| Primitive | JVM | JS | Native |
| ----------|-----|----|--------|
| Blake2b | LazySodium | libsodium.js | libsodium |
| SHA256 | LazySodium | libsodium.js | libsodium |
| SHA512 | LazySodium | libsodium.js | libsodium |
| XChaCha20-Poly1305 | LazySodium | libsodium.js | libsodium |
## Integration
NOTE: Latest version of the library is built with Kotlin 1.4-M2 and therefore only SNAPSHOT variant is available. Next
stable version will be released when Kotlin 1.4. is released
#### Gradle
Kotlin
```kotlin
implementation("com.ionspin.kotlin:multiplatform-crypto:0.1.0")
or
implementation("com.ionspin.kotlin:multiplatform-crypto-delegated:0.1.0")
```
#### Snapshot builds
```kotlin
repositories {
maven {
url = uri("https://oss.sonatype.org/content/repositories/snapshots")
}
}
implementation("com.ionspin.kotlin:multiplatform-crypto:0.1.0-SNAPSHOT")
```
## Usage
### Helper functions
All API take `UByteArray` as message/key/nonce/etc parameter. For convenience when working with strings we provide
`String.enocdeToUbyteArray()` extensions function, and `UByteArray.toHexString` extension function.
More convenience functions will be added.
### Hashes
Hashes are provided in two versions, "stateless", usually the companion object of the hash,
which takes the data to be hashed in one go, and "updatable" which can be fed data in chunks.
#### Blake2b
You can use Blake 2b in two modes
##### Stateless version
You need to deliver the complete data that is to be hashed in one go
```kotlin
val input = "abc"
val result = Crypto.Blake2b.stateless(input.encodeToUByteArray())
```
Result is returned as a `UByteArray`
##### Updatable instance version
You can create an instance and feed the data by using `update(input : UByteArray)` call. Once all data is supplied,
you should call `digest()`.
If you want to use Blake2b with a key, you should supply it when creating the `Blake2b` instance.
```kotlin
val test = "abc"
val key = "key"
val blake2b = Crypto.Blake2b.updateable(key.encodeToUByteArray())
blake2b.update(test.encodeToUByteArray())
val result = blake2b.digest().toHexString()
```
After digest is called, the instance is reset and can be reused (Keep in mind key stays the same for the particular instance).
#### SHA2 (SHA256 and SHA512)
##### Stateless version
You need to deliver the complete data that is to be hashed in one go. You can either provide the `UByteArray` as input
or `String`. Result is always returned as `UByteArray` (At least in verision 0.0.1)
```kotlin
val input = "abc"
val result = Crypto.Sha256.stateless(input.encodeToUByteArray())
```
```kotlin
val input ="abc"
val result = Crypto.Sha512.stateless(input.encodeToUByteArray())
```
Result is returned as a `UByteArray`
##### Updateable version
Or you can use the updatable instance version
```kotlin
val sha256 = Crypto.Sha256.updateable()
sha256.update("abc".encodeToUByteArray())
val result = sha256.digest()
```
```kotlin
val sha512 = Crypto.Sha512.updateable()
sha512.update("abc".encodeToUByteArray())
val result = sha512.digest()
```
### Key derivation
#### Argon2
NOTE: This implementation is tested against KAT generated by reference Argon2 implementation, which does not follow
specification completely. See this issue https://github.com/P-H-C/phc-winner-argon2/issues/183
```kotlin
val argon2Instance = Argon2(
password = "Password",
salt = "RandomSalt",
parallelism = 8,
tagLength = 64U,
requestedMemorySize = 256U, //4GB
numberOfIterations = 4U,
key = "",
associatedData = "",
argonType = ArgonType.Argon2id
)
val tag = argon2Instance.derive()
val tagString = tag.map { it.toString(16).padStart(2, '0') }.joinToString(separator = "")
val expectedTagString = "c255e3e94305817d5e09a7c771e574e3a81cc78fef5da4a9644b6df0" +
"0ba1c9b424e3dd0ce7e600b1269b14c84430708186a8a60403e1bfbda935991592b9ff37"
println("Tag: ${tagString}")
assertEquals(tagString, expectedTagString)
```
### Symmetric encryption (OUTDATED, won't be exposed in next release, no counterpart in delegated flavor - 0.1.1)
#### AES
Aes is available with CBC and CTR mode through `AesCbc` and `AesCtr` classes/objects.
Similarly to hashes you can either use stateless or updateable version.
Initialization vector, or counter states are chosen by the SDK automaticaly, and returned alongside encrypted data
##### Stateless AesCbc and AesCtr
AesCtr
```kotlin
val keyString = "4278b840fb44aaa757c1bf04acbe1a3e"
val key = AesKey.Aes128Key(keyString)
val plainText = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"
val encryptedDataAndInitializationVector = AesCtr.encrypt(key, plainText.hexStringToUByteArray())
val decrypted = AesCtr.decrypt(
key,
encryptedDataAndInitializationVector.encryptedData,
encryptedDataAndInitializationVector.initialCounter
)
plainText == decrypted.toHexString()
```
AesCbc
```kotlin
val keyString = "4278b840fb44aaa757c1bf04acbe1a3e"
val key = AesKey.Aes128Key(keyString)
val plainText = "3c888bbbb1a8eb9f3e9b87acaad986c466e2f7071c83083b8a557971918850e5"
val encryptedDataAndInitializationVector = AesCbc.encrypt(key, plainText.hexStringToUByteArray())
val decrypted = AesCbc.decrypt(
key,
encryptedDataAndInitializationVector.encryptedData,
encryptedDataAndInitializationVector.initilizationVector
)
plainText == decrypted.toHexString()
```
## Libsodium bindings
* Under development
This repository contains two crypto related projects:
1. Libsodium bindings for Kotiln Multiplatform
2. Pure/Delegated kotlin multiplatform crypto library written from scratch in pure form.

View File

@ -25,7 +25,7 @@ object Versions {
val kotlinBigNumVersion = "0.1.6-1.4.0-rc-SNAPSHOT"
val lazySodium = "4.2.6"
val lazySodium = "4.3.1-SNAPSHOT"
val jna = "5.5.0"
val kotlinPoet = "1.6.0"
@ -81,7 +81,10 @@ object Deps {
val kotlinPoet = "com.squareup:kotlinpoet:${Versions.kotlinPoet}"
object Delegated {
val lazysodium = "com.goterl.lazycode:lazysodium-java:${Versions.lazySodium}"
// Temporary until reported lazysodium issues are fixed. My snapshot build with
// And cause I registered com.ionspin.kotlin as maven central package root now I have to use
// that even though this is pure java library. :)
val lazysodium = "com.ionspin.kotlin:lazysodium-java:${Versions.lazySodium}"
val jna = "net.java.dev.jna:jna:${Versions.jna}"
}
}

View File

@ -0,0 +1,285 @@
# Kotlin Multiplatform Crypto Library
Kotlin Multiplatform Crypto is a library for various cryptographic applications.
The library comes in two flavors `multiplatform-crypto` and `multiplatform-crypto-delegated`.
* `multiplatform-crypto` contains pure kotlin implementations, is not reviewed, should be considered unsafe and only
for prototyping or experimentation purposes.
* `multiplatform-crypto-delegated` relies on platform specific implementations, mostly libsodium, but care should still be taken that the kotlin code is not reviewed or proven safe.
APIs of both variants are identical.
### Table of contents
1. [Supported platforms](#supported-platforms-by-variant)
2. [API](#api)
3. TODO
## Supported platforms by variant
|Platform|Pure variant| Delegated variant|
|--------|------------|------------------|
|Linux X86 64| :heavy_check_mark: | :heavy_check_mark: |
|Linux Arm 64| :heavy_check_mark: | :heavy_check_mark: |
|Linux Arm 32| :heavy_check_mark: | :x: |
|macOS X86 64| :heavy_check_mark: | :heavy_check_mark: |
|iOS x86 64 | :heavy_check_mark: | :heavy_check_mark: |
|iOS Arm 64 | :heavy_check_mark: | :heavy_check_mark: |
|iOS Arm 32 | :heavy_check_mark: | :heavy_check_mark: |
|watchOS X86 32 | :heavy_check_mark: | :heavy_check_mark: |
|watchOS Arm 64(_32) | :heavy_check_mark: | :heavy_check_mark: |
|watchos Arm 32 | :heavy_check_mark: | :heavy_check_mark: |
|tvOS X86 64 | :heavy_check_mark: | :heavy_check_mark: |
|tvOS Arm 64 | :heavy_check_mark: | :heavy_check_mark: |
|minGW X86 64| :heavy_check_mark: | :heavy_check_mark: |
|minGW X86 32| :x: | :x: |
## Sample project
The library includes sample project that shows usage on different platforms
- NOTE: Currently only linux, macOs and windows are included.
## Notes & Roadmap
**The API will move fast and break often until v1.0**
Next steps:
- Expand API (ECC, Signing ...)
## Should I use this in production?
**NO.**
The library is under HEAVY development. Until development is done it will not be reviewed and therefore it shouldn't be used.
Contributions are still welcome!
## Why?
This is an experimental implementation, mostly for expanding personal understanding of cryptography.
It's not peer reviewed, not guaranteed to be bug free, and not guaranteed to be secure.
## API for Pure and Delegated flavours
### Hashing functions
* Blake2b
* SHA512
* SHA256
### Key Derivation
* Argon2
### Authenticated symmetric encryption (AEAD)
* XChaCha20-Poly1305
### Delegated flavor dependancy table
The following table describes which library is used for particular cryptographic primitive
| Primitive | JVM | JS | Native |
| ----------|-----|----|--------|
| Blake2b | LazySodium | libsodium.js | libsodium |
| SHA256 | LazySodium | libsodium.js | libsodium |
| SHA512 | LazySodium | libsodium.js | libsodium |
| XChaCha20-Poly1305 | LazySodium | libsodium.js | libsodium |
## Integration
NOTE: Latest version of the library is built with Kotlin 1.4-M2 and therefore only SNAPSHOT variant is available. Next
stable version will be released when Kotlin 1.4. is released
#### Gradle
Kotlin
```kotlin
implementation("com.ionspin.kotlin:multiplatform-crypto:0.1.0")
or
implementation("com.ionspin.kotlin:multiplatform-crypto-delegated:0.1.0")
```
#### Snapshot builds
```kotlin
repositories {
maven {
url = uri("https://oss.sonatype.org/content/repositories/snapshots")
}
}
implementation("com.ionspin.kotlin:multiplatform-crypto:0.1.0-SNAPSHOT")
```
## Usage
### Helper functions
All API take `UByteArray` as message/key/nonce/etc parameter. For convenience when working with strings we provide
`String.enocdeToUbyteArray()` extensions function, and `UByteArray.toHexString` extension function.
More convenience functions will be added.
### Hashes
Hashes are provided in two versions, "stateless", usually the companion object of the hash,
which takes the data to be hashed in one go, and "updatable" which can be fed data in chunks.
#### Blake2b
You can use Blake 2b in two modes
##### Stateless version
You need to deliver the complete data that is to be hashed in one go
```kotlin
val input = "abc"
val result = Crypto.Blake2b.stateless(input.encodeToUByteArray())
```
Result is returned as a `UByteArray`
##### Updatable instance version
You can create an instance and feed the data by using `update(input : UByteArray)` call. Once all data is supplied,
you should call `digest()`.
If you want to use Blake2b with a key, you should supply it when creating the `Blake2b` instance.
```kotlin
val test = "abc"
val key = "key"
val blake2b = Crypto.Blake2b.updateable(key.encodeToUByteArray())
blake2b.update(test.encodeToUByteArray())
val result = blake2b.digest().toHexString()
```
After digest is called, the instance is reset and can be reused (Keep in mind key stays the same for the particular instance).
#### SHA2 (SHA256 and SHA512)
##### Stateless version
You need to deliver the complete data that is to be hashed in one go. You can either provide the `UByteArray` as input
or `String`. Result is always returned as `UByteArray` (At least in verision 0.0.1)
```kotlin
val input = "abc"
val result = Crypto.Sha256.stateless(input.encodeToUByteArray())
```
```kotlin
val input ="abc"
val result = Crypto.Sha512.stateless(input.encodeToUByteArray())
```
Result is returned as a `UByteArray`
##### Updateable version
Or you can use the updatable instance version
```kotlin
val sha256 = Crypto.Sha256.updateable()
sha256.update("abc".encodeToUByteArray())
val result = sha256.digest()
```
```kotlin
val sha512 = Crypto.Sha512.updateable()
sha512.update("abc".encodeToUByteArray())
val result = sha512.digest()
```
### Key derivation
#### Argon2
NOTE: This implementation is tested against KAT generated by reference Argon2 implementation, which does not follow
specification completely. See this issue https://github.com/P-H-C/phc-winner-argon2/issues/183
```kotlin
val argon2Instance = Argon2(
password = "Password",
salt = "RandomSalt",
parallelism = 8,
tagLength = 64U,
requestedMemorySize = 256U, //4GB
numberOfIterations = 4U,
key = "",
associatedData = "",
argonType = ArgonType.Argon2id
)
val tag = argon2Instance.derive()
val tagString = tag.map { it.toString(16).padStart(2, '0') }.joinToString(separator = "")
val expectedTagString = "c255e3e94305817d5e09a7c771e574e3a81cc78fef5da4a9644b6df0" +
"0ba1c9b424e3dd0ce7e600b1269b14c84430708186a8a60403e1bfbda935991592b9ff37"
println("Tag: ${tagString}")
assertEquals(tagString, expectedTagString)
```
### Symmetric encryption (OUTDATED, won't be exposed in next release, no counterpart in delegated flavor - 0.1.1)
#### AES
Aes is available with CBC and CTR mode through `AesCbc` and `AesCtr` classes/objects.
Similarly to hashes you can either use stateless or updateable version.
Initialization vector, or counter states are chosen by the SDK automaticaly, and returned alongside encrypted data
##### Stateless AesCbc and AesCtr
AesCtr
```kotlin
val keyString = "4278b840fb44aaa757c1bf04acbe1a3e"
val key = AesKey.Aes128Key(keyString)
val plainText = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"
val encryptedDataAndInitializationVector = AesCtr.encrypt(key, plainText.hexStringToUByteArray())
val decrypted = AesCtr.decrypt(
key,
encryptedDataAndInitializationVector.encryptedData,
encryptedDataAndInitializationVector.initialCounter
)
plainText == decrypted.toHexString()
```
AesCbc
```kotlin
val keyString = "4278b840fb44aaa757c1bf04acbe1a3e"
val key = AesKey.Aes128Key(keyString)
val plainText = "3c888bbbb1a8eb9f3e9b87acaad986c466e2f7071c83083b8a557971918850e5"
val encryptedDataAndInitializationVector = AesCbc.encrypt(key, plainText.hexStringToUByteArray())
val decrypted = AesCbc.decrypt(
key,
encryptedDataAndInitializationVector.encryptedData,
encryptedDataAndInitializationVector.initilizationVector
)
plainText == decrypted.toHexString()
```
## Libsodium bindings
* Under development

View File

@ -47,6 +47,9 @@ repositories {
mavenCentral()
jcenter()
maven("https://dl.bintray.com/terl/lazysodium-maven")
maven {
url = uri("https://oss.sonatype.org/content/repositories/snapshots")
}
}
group = ReleaseInfo.group

View File

@ -5,6 +5,8 @@ package com.ionspin.kotlin.crypto.util
* ugljesa.jovanovic@ionspin.com
* on 27-Sep-2020
*/
val randombytes_SEEDBYTES = 32
expect object LibsodiumRandom {
/**

View File

@ -0,0 +1,56 @@
package com.ionspin.kotlin.crypto.util
import com.ionspin.kotlin.crypto.LibsodiumInitializer
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 03-Oct-2020
*/
class LibsodiumRandomTest {
@Test
fun testRandom() {
//This is just a sanity test, it should fail on occasion though, with probability of 1/2^32
LibsodiumInitializer.initializeWithCallback {
val random = LibsodiumRandom.random()
assertTrue { random != 0U }
}
}
@Test
fun testRandomUniform() {
//This is just a sanity test, it should fail on occasion though, with probability of 1/2^31
LibsodiumInitializer.initializeWithCallback {
val random = LibsodiumRandom.uniform(UInt.MAX_VALUE / 2U)
assertTrue { random != 0U && random < (UInt.MAX_VALUE / 2U)}
}
}
@Test
fun testRandomBuffer() {
//This is just a sanity test, it should fail on occasion though, with probability of 1/2^52
LibsodiumInitializer.initializeWithCallback {
val result = LibsodiumRandom.buf(20)
val lowProbability = UByteArray(20) { 0U }
assertFalse { result.contentEquals(lowProbability) }
}
}
@Test
fun testRandomBufferDeterministic() {
//This is just a sanity test, it should fail on occasion though, with probability of 1/2^52
LibsodiumInitializer.initializeWithCallback {
val seed = UByteArray(randombytes_SEEDBYTES) { 1U }
val result = LibsodiumRandom.bufDeterministic(20, seed)
val lowProbability = UByteArray(20) { 0U }
assertFalse { result.contentEquals(lowProbability) }
val secondResult = LibsodiumRandom.bufDeterministic(20, seed)
assertTrue { result.contentEquals(secondResult) }
}
}
}

View File

@ -207,9 +207,18 @@ interface JsSodiumInterface {
fun from_hex(data : String): Uint8Array
fun from_string(data : String): Uint8Array
// ---- > ---- Random ---- < -----
fun randombytes_buf(length: UInt) : Uint8Array
fun randombytes_buf_deterministic(length: UInt, seed : Uint8Array) : Uint8Array
fun randombytes_random() : UInt
fun randombytes_uniform(upper_bound: UInt) : UInt
// ---- Utils end ----
}

View File

@ -0,0 +1,48 @@
package com.ionspin.kotlin.crypto.util
import com.ionspin.kotlin.crypto.getSodium
import ext.libsodium.com.ionspin.kotlin.crypto.toUByteArray
import ext.libsodium.com.ionspin.kotlin.crypto.toUInt8Array
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 27-Sep-2020
*/
actual object LibsodiumRandom {
/**
* The randombytes_buf() function fills size bytes starting at buf with an unpredictable sequence of bytes.
*/
actual fun buf(size: Int): UByteArray {
return getSodium().randombytes_buf(size).toUByteArray()
}
/**
* The randombytes_buf_deterministic function stores size bytes into buf indistinguishable from random bytes without knowing seed.
* For a given seed, this function will always output the same sequence. size can be up to 2^31 (~8GB) because we use kotlin arrays
* and they are limited by Int primitive type
* seed is randombytes_SEEDBYTES bytes long.
* This function is mainly useful for writing tests, and was introduced in libsodium 1.0.12. Under the hood, it uses the ChaCha20 stream cipher.
*
*/
actual fun bufDeterministic(size: Int, seed: UByteArray): UByteArray {
return getSodium().randombytes_buf_deterministic(size.toUInt(), seed.toUInt8Array()).toUByteArray()
}
/**
* The randombytes_random() function returns an unpredictable value between 0 and 0xffffffff (included).
*/
actual fun random(): UInt {
return getSodium().randombytes_random()
}
/**
* The randombytes_uniform() function returns an unpredictable value between 0 and upper_bound (excluded). Unlike r
* andombytes_random() % upper_bound, it guarantees a uniform distribution of the possible output values even when
* upper_bound is not a power of 2. Note that an upper_bound < 2 leaves only a single element to be chosen, namely 0
*/
actual fun uniform(upperBound: UInt): UInt {
return getSodium().randombytes_uniform(upperBound)
}
}

View File

@ -27,7 +27,7 @@ actual object LibsodiumRandom {
*/
actual fun bufDeterministic(size: Int, seed: UByteArray): UByteArray {
val result = ByteArray(size)
sodium.randombytes_buf(result, size, seed.asByteArray())
sodium.randombytes_buf_deterministic(result, size, seed.asByteArray())
return result.asUByteArray()
}
@ -36,7 +36,8 @@ actual object LibsodiumRandom {
*/
actual fun random(): UInt {
//Broken in lazysodium-java https://github.com/terl/lazysodium-java/issues/86
TODO()
//Using temporary forked and fixed build until pull request is accepted in original repo
return sodium.randombytes_random().toUInt()
}
@ -48,7 +49,8 @@ actual object LibsodiumRandom {
*/
actual fun uniform(upperBound: UInt): UInt {
//Broken in lazysodium-java https://github.com/terl/lazysodium-java/issues/86
TODO("not implemented yet")
//Using temporary fixed build until pull request is accepted
return sodium.randombytes_uniform(upperBound.toInt()).toUInt()
}
}

View File

@ -1,15 +1,16 @@
|Function name| Implemented |
|-------------|-------------|
| add | |
| memcmp | |
| memzero | |
| memcmp | :heavy_check_mark: |
| memzero | :heavy_check_mark: |
| output_formats | |
| pad | |
| unpad | |
| pad | :heavy_check_mark: |
| unpad | :heavy_check_mark: |
| symbols | |
| to_base64 | |
| to_hex | |
| to_string | |
| to_base64 | :heavy_check_mark: |
| to_hex | :heavy_check_mark: |
| from_base64 | :heavy_check_mark: |
| from_hex | :heavy_check_mark: |
| crypto_aead_chacha20poly1305_decrypt | :heavy_check_mark: |
| crypto_aead_chacha20poly1305_decrypt_detached | :heavy_check_mark: |
| crypto_aead_chacha20poly1305_encrypt | :heavy_check_mark: |
@ -138,12 +139,12 @@
| crypto_stream_xchacha20_keygen | not present in LazySodium Android |
| crypto_stream_xchacha20_xor | not present in LazySodium Android|
| crypto_stream_xchacha20_xor_ic | not present in LazySodium Android |
| randombytes_buf | |
| randombytes_buf_deterministic | |
| randombytes_close | |
| randombytes_random | |
| randombytes_stir | |
| randombytes_uniform | |
| randombytes_buf | :heavy_check_mark: |
| randombytes_buf_deterministic | :heavy_check_mark: |
| randombytes_close | not present in LazySodium |
| randombytes_random | :heavy_check_mark: |
| randombytes_stir | not present in LazySodium |
| randombytes_uniform | :heavy_check_mark: |
| sodium_version_string | |
| SODIUM_LIBRARY_VERSION_MAJOR | |
| SODIUM_LIBRARY_VERSION_MINOR | |