Giter Club home page Giter Club logo

core's Introduction

core

badge-license badge-latest-release

badge-kotlin badge-endians

badge-platform-android badge-platform-jvm badge-platform-js badge-platform-js-node badge-platform-wasm badge-platform-linux badge-platform-macos badge-platform-ios badge-platform-tvos badge-platform-watchos badge-platform-windows badge-support-android-native badge-support-apple-silicon badge-support-js-ir badge-support-linux-arm

Low level core cryptographic components for Kotlin Multiplatform

NOTE: For Jvm, Digest extends java.security.MessageDigest and Mac extends javax.crypto.Mac for interoperability.

Utilized by KotlinCrypto/hash and KotlinCrypto/MACs

Library Authors

Modules in core are intentionally single purpose and small such that you are able to include them in your APIs without having to import some massive crypto library. Consumers of your APIs can then import the higher level implementations to use with your library (giving them the choice of what algorithms they wish to use, importing only what they need).

This also means that as new higher level functions get implemented, you do not need to do anything.

/**
 * This feature of Foo requires a dependency if you wish to use it.
 * See: https://github.com/KotlinCrypto/hash
 * */
class FooFeatureA(digest: Digest) {
    // ...
}

Usage

Digest
// Using SHA256 from hash repo as an example
import org.kotlincrypto.hash.sha2.SHA256

fun main() {
    val digest = SHA256()
    val bytes = Random.Default.nextBytes(615)
    
    // Digest implements Algorithm
    println(digest.algorithm())
    
    // Digest implements Updatable
    digest.update(5.toByte())
    digest.update(bytes)
    digest.update(bytes, 10, 88)

    // Digest implements Resettable
    digest.reset()

    digest.update(bytes)

    // Digest implements Copyable
    val copy = digest.copy()

    val hash = digest.digest()
    val hash2 = copy.digest(bytes)
}
Mac
// Using SecureRandom from the secure-random repo as an example
import org.kotlincrypto.SecureRandom
// Using HmacSHA3_256 from the MACs repo as an example
import org.kotlincrypto.macs.hmac.sha3.HmacSHA3_256

fun main() {
    val key = SecureRandom().nextBytesOf(100)
    val mac = HmacSHA3_256(key)
    val bytes = Random.Default.nextBytes(615)

    // Mac implements Algorithm
    println(mac.algorithm())

    // Mac implements Updatable
    mac.update(5.toByte())
    mac.update(bytes)
    mac.update(bytes, 10, 88)

    // Mac implements Resettable
    mac.reset()

    mac.update(bytes)

    // Mac implements Copyable
    val copy = mac.copy()

    val hash = mac.doFinal()
    val hash2 = copy.doFinal(bytes)
}
Xof

XOFs (i.e. Extendable-Output Functions) were introduced with SHA3.

XOFs are very similar to Digest and Mac except that instead of calling digest() or doFinal(), which returns a fixed size ByteArray, their output size can be variable in length.

As such, KotlinCrypto takes the approach of making them distinctly different from those types, while implementing the same interfaces (Algorithm, Copyable, Resettable, Updatable).

Output for an Xof is done by reading, instead.

// Using SHAKE128 from hash repo as an example
import org.kotlincrypto.hash.sha3.SHAKE128

fun main() {
    val xof: Xof<SHAKE128> = SHAKE128.xOf()
    val bytes = Random.Default.nextBytes(615)

    // Xof implements Algorithm
    println(xof.algorithm())

    // Xof implements Updatable
    xof.update(5.toByte())
    xof.update(bytes)
    xof.update(bytes, 10, 88)

    // Xof implements Resettable
    xof.reset()

    xof.update(bytes)

    // Xof implements Copyable
    xof.copy()

    val out1 = ByteArray(100)
    val out2 = ByteArray(12345)

    // Use produces a Reader which auto-closes when your action finishes.
    // Reader is using a snapshot of the Xof state (thus the
    // optional argument to resetXof with a default of true).
    xof.use(resetXof = false) { read(out1, 0, out1.size); read(out2) }

    val out3 = ByteArray(out1.size)
    val out4 = ByteArray(out2.size)

    // Can also create a Reader that won't auto-close
    val reader = xof.reader(resetXof = false)
    reader.read(out3)
    reader.read(out4)
    reader.close()

    try {
        // The Reader has been closed and will throw
        // exception when trying to read from again.
        reader.use { read(out4) }
    } catch (e: IllegalStateException) {
        e.printStackTrace()
    }

    // Contents are the same because Reader uses
    // a snapshot of Xof, which was not updated
    // between production of Readers.
    assertContentEquals(out1 + out2, out3 + out4)

    // Still able to update Xof, independent of the production
    // and usage of Readers.
    xof.update(10.toByte())
    xof.use { read(out3); read(out4) }

    try {
        assertContentEquals(out1 + out2, out3 + out4)
        throw IllegalStateException()
    } catch (_: AssertionError) {
        // pass
    }
}

Get Started

The best way to keep KotlinCrypto dependencies up to date is by using the version-catalog. Alternatively, see below.

// build.gradle.kts
dependencies {
    val core = "0.5.1"
    implementation("org.kotlincrypto.core:digest:$core")
    implementation("org.kotlincrypto.core:mac:$core")
    implementation("org.kotlincrypto.core:xof:$core")
}
// build.gradle
dependencies {
    def core = "0.5.1"
    implementation "org.kotlincrypto.core:digest:$core"
    implementation "org.kotlincrypto.core:mac:$core"
    implementation "org.kotlincrypto.core:xof:$core"
}

core's People

Contributors

05nelsonm avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

core's Issues

Android API 23 and below `javax.crypto.Mac.doFinal()` does not call `engineReset()`

Android API 23 and below never calls engineReset() when doFinal() is invoked.

API 23:

    public final byte[] doFinal() throws IllegalStateException {
        if (!isInitMac) {
            throw new IllegalStateException();
        }
        return getSpi().engineDoFinal();
    }

API 24:

    public final byte[] doFinal() throws IllegalStateException {
        chooseFirstProvider();
        if (initialized == false) {
            throw new IllegalStateException("MAC not initialized");
        }
        byte[] mac = spi.engineDoFinal();
        spi.engineReset();
        return mac;
    }

Add `XofAlgorithm` interface

Currently, Xof is implemented where it accepts any Algorithm. There is no distinction between what can, and cannot be an Xof.

Add

public interface XofAlgorithm: Algorithm

And then modify Xof to

public sealed class Xof<A: XofAlgorithm> {
    // ...
}

So that implementations can do

public sealed class SHAKEDigest: Digest, XofAlgorithm {
    // ...
}

This way, only SHAKEDigest (or KMAC) can be wrapped in XofFactory.XofDelegate.

`Digest` performance issues

Current implementation of Digest requires that when chunking, array copying be done into the buffer, then calling of compress to pass the buffer. This is very counterproductive. The caller is already passing in the ByteArray, why copy it?

There should be another compress function that takes ByteArray and bufOffs as arguments such that when Digest.update is called, the ByteArray can be passed directly w/o the need to array copy.

This is a bit tricky to implement, but can definitely be done w/o breaking compatibility.

  1. Add to Digest
    protected open fun compress(input: ByteArray, offset: Int) {
        throw NotImplementedError()
    }
  2. From DigestDelegate, if calling the added compress function throws NotImplementedError, fallback to using the old compress method (indicative of the inheritor not yet implementing the new method).
  3. Deprecate the old compress method and inform implementors to simply call compress(buffer, 0) to pass it to their override of the new protected open function compress method.

Fix package names

๐Ÿ˜ข

See 05nelsonm/encoding#124

All modules in core have the same package name, so the same issue that Craig is running into with Java9 Module split packages would occur for anyone using JPMS where core is a direct or transitive dependency, too...

This would be a major breaking change.

Android API 23 and below does not like `Mac.init` with a null key

06-06 15:52:15.751  3156  3175 E TestRunner: java.security.InvalidKeyException: key == null
06-06 15:52:15.751  3156  3175 E TestRunner: 	at javax.crypto.Mac.init(Mac.java:321)
06-06 15:52:15.751  3156  3175 E TestRunner: 	at org.kotlincrypto.core.Mac.<init>(Mac.kt:60)
06-06 15:52:15.751  3156  3175 E TestRunner: 	at org.kotlincrypto.macs.Hmac.<init>(Hmac.kt:57)
06-06 15:52:15.751  3156  3175 E TestRunner: 	at org.kotlincrypto.macs.Hmac.<init>(Hmac.kt:45)
06-06 15:52:15.751  3156  3175 E TestRunner: 	at org.kotlincrypto.macs.HmacSHA3_512.<init>(HmacSHA3_512.kt:36)

Android API 23 and below seem to be affected.

As we're installing our own engine with the engineInit overridden and no-op'd, would be better to send a static key than use null in order to avoid any potential issues. It does absolutely nothing but set Mac.isInitialized to true.

`KC_ANDROID_SDK_INT` returns `0` from `androidUnitTest` source sets

Need to include a check for the runtime environment

if (
    System.getProperty("java.runtime.name")
        ?.contains("android", ignoreCase = true) != true
) {
    // Not Android runtime
    return@lazy null
}

Note that the only thing this results in is reset being called twice during android unit tests.

`wasmJS` target support

Any plan or roadmap for wasmJS support? It is experimental, but I am interested in it. Thanks for your work!

`Mac.update` for `nonJvm` should validate `offset` and `len`

nonJvm source set for Mac is not validating arguments before calling engine.update. Nothing is affected by this bug as currently, all input is directed to a Digest for Hmac and is checked there, but a check should be performed for posterity's sake.

`javax.crypto.Mac.init` re-initializes the `Mac` with a new key

Found that if you call javax.crypto.Mac.init, it re-initializes and resets the Mac.

// This passes...
        val mac = Mac.getInstance("HmacSHA256")
        val key = ByteArray(50) { it.toByte() }
        val keySpec = SecretKeySpec(key, mac.algorithm)
        mac.init(keySpec)
        mac.update(key)
        val b1 = mac.doFinal()
        mac.init(SecretKeySpec(ByteArray(25) { it.toByte() }, mac.algorithm))
        mac.update(key)
        val b2 = mac.doFinal()
        try {
            assertContentEquals(b1, b2)
            throw IllegalStateException()
        } catch (_: AssertionError) {
            // pass
        }

This is related to #41 because use of a Provider as a work around to android's horrible APIs forcing us into a provider type API architecture (awful). Whenever init is called, a new MacSpi should be produced, but we can't do that.

  1. KotlinCrypto is not using a Provider like infra, it's direct usage only (i.e. HmacSHA256(key) initializes the Mac so you never get IllegalStateException).
  2. Nobody should ever call init on a Mac in Java after it's been initialized, they should use a new instance of javax.crypto.Mac (which is why the provider architecture is awful because it allows this type of functionality as you are forced into a lazy init API). So, if someone is using KotlinCrypto, they know it's already initialized. My concern is any java libraries that consumers might pass an org.kotlincrypto.Mac which may clone and then call javax.crypto.Mac.init.

Either the current org.kotlincrypto.Mac API needs to be broken to follow Java, or after engineInit is called for the first time (org.kotlincrypto.Mac init block calls init with a blank key so that javax.crypto.Mac.initialized gets set to true), throw an InvalidKeyException. It must be either or, as if someone tries to re-initialize the javax.crypto.Mac with a different key and current behavior is simply that engineInit is no-op'd, they are under the assumption that the new key is being used when in fact it is not, so any output would be incorrect.

Add `Digest` and `Mac` wrappers for JVM

For Digest and Mac jvm source sets, add a static function that will allow people to easily "wrap" what could be retrieved from getInstance

// org.kotlincrypto.core.Mac [JVM]
public actual abstract class Mac protected constructor(
    // ...
) : // ... {

    // ...

    public companion object {
        @JvmStatic
        @Throws(IllegalArgumentException::class, NoSuchAlgorithmException::class)
        fun from(algorithm: String, key: Key): org.kotlincrypto.core.Mac {
            return MacJvmWrapper(getInstance(algorithm), key)
        }

        @JvmStatic
        @Throws(IllegalArgumentException::class)
        fun from(mac: javax.crypto.Mac): org.kotlincrypto.core.Mac {
            if (mac is org.kotlincrypto.core.Mac) return mac

            // MacJvmWrapper MUST check if the javax.crypto.Mac is initialized or not
            // and throw an illegal argument exception if unable to do so
            return MacJvmWrapper(mac, null)
        }
    }
}
// org.kotlincrypto.core.Digest [JVM]
public actual abstract class Digest protected constructor(
    // ...
): // ... {

    // ...

    public companion object {
        @JvmStatic
        @Throws(NoSuchAlgorithmException::class)
        fun from(algorithm: String): org.kotlincrypto.core.Digest {
            return DigestJvmWrapper(getinstance(algorithm))
        }

        @JvmStatic
        fun from(digest: MessageDigest): org.kotlincrypto.core.Digest {
            if (digest is org.kotlincrypto.core.Digest) return digest

            return DigestJvmWrapper(digest)
        }
    }
}

This is already being done in hash and MACs libs for multiplatform implementations in order to test against Java.

Add `XOF` abstraction

FIPS PUB 202 introduced Extendable-Output Functions which are distinctly different than a Digest or a Mac. As such, in order to implement SHAKE128, SHAKE256, CSHAKE128, CSHAKE256, KMAC128, and KMAC256, a new abstraction is needed which provides such functionality.

Publication signing configuration not needed for `SNAPSHOT`s

Currently, all modules that are to be published configure the SigningExtension to set useGpgCmd(). Artifact signing is not necessary for -SNAPSHOT versions.

Wrap plugin configuration around an if check

if (!version.toString().endsWith("-SNAPSHOT")) {
    extensions.configure<SigningExtension>("signing") {
        useGpgCmd()
    }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.