whyoleg / cryptography-kotlin

Kotlin Multiplatform cryptography / crypto library
https://whyoleg.github.io/cryptography-kotlin/
Apache License 2.0
331 stars 20 forks source link

AES/GCM usage example #36

Closed morki closed 3 weeks ago

morki commented 3 months ago

Hi @whyoleg,

thank you very much for this awesome library. I am using snapshot version to convert my webpush library to multiplatform.

I transformed code from Java Security APIs without issues (using your new ECDH support), but one think I can't find a way to do is this last function:

internal fun encryptAesGcmNoPadding(key: ByteArray, nonce: ByteArray, payload: ByteArray): ByteArray {
    //return CryptographyProvider.Default
    //    .get(AES.GCM)
    //    .keyDecoder()
    //    .decodeFromBlocking(AES.Key.Format.RAW, key)
    //    .cipher()
    //    .encryptBlocking(nonce, payload)

    return Cipher.getInstance("AES/GCM/NoPadding").run {
        init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), GCMParameterSpec(128, nonce))
        doFinal(payload)
    }
}

Can you please help me?

whyoleg commented 3 months ago

Hey! There is no support for custom nonce for GCM on current moment. nonce will be autogenerated and prepended: input=plaintext, output=nonce+ciphertext+authTag. Do you really need to be able to provide non-random nonce during encryption? If it really is, I will add it there under Delicate API similar to how IV is provided in CTR mode. Or you can do a PR if you want to contribute :)

Just FYI: there are planned some small breaking changes for ECDH API for upcoming release, so just be aware :)

morki commented 3 months ago

Thank you very much for quick response :)

Do you really need to be able to provide non-random nonce during encryption?

Yes, it is in spec, this is code after testing conversion to your lib:

    /**
     * Generates the body of request to push service provider
     *
     * @param payload The message payload to be sent in the push notification.
     * @param p256dh The Base64-encoded P256DH key for authentication with the push service provider.
     * @param auth The Base64-encoded authentication secret for the push service provider.
     * @return The encrypted body of the web push message.
     */
    public fun getBody(payload: ByteArray, p256dh: ByteArray, auth: ByteArray): ByteArray {
        val userPublicKey = CryptographyProvider.Default
            .get(ECDH)
            .publicKeyDecoder(EC.Curve.P256)
            .decodeFromBlocking(EC.PublicKey.Format.RAW, p256dh)

        val auxKeyPair = CryptographyProvider.Default
            .get(ECDH)
            .keyPairGenerator(EC.Curve.P256)
            .generateKeyBlocking()

        val auxPublicKey = auxKeyPair.publicKey
            .encodeToBlocking(EC.PublicKey.Format.RAW)

        val secret = auxKeyPair.privateKey
            .sharedSecretDerivation()
            .deriveSharedSecretBlocking(userPublicKey)

        val salt = CryptographyRandom.nextBytes(16)
        val secretInfo = concatBytes(webPushInfo, p256dh, auxPublicKey)
        val derivedSecret = hkdfSha256(secret, auth, secretInfo, 32)
        val derivedKey = hkdfSha256(derivedSecret, salt, keyInfo, 16)
        val derivedNonce = hkdfSha256(derivedSecret, salt, nonceInfo, 12)
        val encryptedPayload = encryptAesGcmNoPadding(derivedKey, derivedNonce, payload + byteArrayOf(2))

        return concatBytes(
            salt,
            byteArrayOf(0, 0, 16, 0),
            byteArrayOf(auxPublicKey.size.toByte()),
            auxPublicKey,
            encryptedPayload,
        )
    }

If it really is, I will add it there under Delicate API similar to how IV is provided in CTR mode. Or you can do a PR if you want to contribute :)

I will look into option of PR, it should be ok for JVM, but maybe I will strugle with other providers, will see.

Just FYI: there are planned some small breaking changes for ECDH API for upcoming release, so just be aware :)

No problem, now I am just testing if everything is supported to do full rewrite (it will need more API refactorings tu support selection of providers)

morki commented 3 months ago

I am trying to import this project into IntelliJ IDEA to start working on the PR, but it always fails with:

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':testtool:client:transformCommonMainDependenciesMetadata'.
> java.util.zip.ZipException: invalid entry compressed size (expected 106 but got 105 bytes)

I am using Fedora 40 linux, any help would be much appreciated.

UPDATE: This is known issue https://youtrack.jetbrains.com/issue/KT-68248

UPDATE 2: I solved it using other JDK.

whyoleg commented 3 weeks ago

0.4.0 was just released!

It's now possible with AES-GCM to provide IV via encryptWithIv/decryptWithIv functions