8000 Implement X25519Recipient by simao · Pull Request #63 · android-password-store/kage · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Implement X25519Recipient #63

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions src/kotlin/kage/crypto/x25519/X25519.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Copyright 2021 The kage Authors. All rights reserved. Use of this source code is governed by
* either an Apache 2.0 or MIT license at your discretion, that can be found in the LICENSE-APACHE
* or LICENSE-MIT files respectively.
*/
package kage.crypto.x25519

import org.bouncycastle.math.ec.rfc7748.X25519

public object X25519 {
public val BASEPOINT: ByteArray =
byteArrayOf(
9,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
)

public fun scalarMult(input: ByteArray, r: ByteArray): ByteArray {
val out = ByteArray(input.size)

X25519.scalarMult(input, 0, r, 0, out, 0)

return out
}
}
47 changes: 47 additions & 0 deletions src/kotlin/kage/crypto/x25519/X25519Recipient.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Copyright 2021 The kage Authors. All rights reserved. Use of this source code is governed by
* either an Apache 2.0 or MIT license at your discretion, that can be found in the LICENSE-APACHE
* or LICENSE-MIT files respectively.
*/
package kage.crypto.x25519

import at.favre.lib.crypto.HKDF
import java.security.SecureRandom
import kage.Recipient
import kage.crypto.chacha20.ChaCha20Poly1305
import kage.crypto.chacha20.ChaCha20Poly1305.CHACHA_20_POLY_1305_NONCE_LENGTH
import kage.format.AgeStanza
import kage.utils.encodeBase64

public class X25519Recipient(private val publicKey: ByteArray) : Recipient {

override fun wrap(fileKey: ByteArray): List<AgeStanza> {
val ephemeralSecret = ByteArray(EPHEMERAL_SECRET_LEN)
SecureRandom().nextBytes(ephemeralSecret)

val ephemeralShare = X25519.scalarMult(ephemeralSecret, X25519.BASEPOINT)

val salt = ephemeralShare.plus(publicKey)

val sharedSecret = X25519.scalarMult(ephemeralSecret, publicKey)

val hkdf = HKDF.fromHmacSha256()

val wrapingKey =
hkdf.extractAndExpand(salt, sharedSecret, X25519_INFO.toByteArray(), MAC_KEY_LENGTH)

val nonce = ByteArray(CHACHA_20_POLY_1305_NONCE_LENGTH)
val wrappedKey = ChaCha20Poly1305.encrypt(wrapingKey, nonce, fileKey)

val stanza = AgeStanza(X25519_STANZA_TYPE, listOf(ephemeralShare.encodeBase64()), wrappedKey)

return listOf(stanza)
}

internal companion object {
const val X25519_STANZA_TYPE = "X25519"
const val X25519_INFO = "age-encryption.org/v1/X25519"
const val MAC_KEY_LENGTH = 32 // bytes
const val EPHEMERAL_SECRET_LEN = 32 // bytes
}
}
20 changes: 19 additions & 1 deletion src/test/kotlin/AgeTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ package kage

import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.util.*
import kage.crypto.chacha20.ChaCha20Poly1305OutputStream
import kage.crypto.scrypt.ScryptRecipient
import kage.crypto.x25519.X25519Recipient
import org.bouncycastle.util.encoders.Hex
import org.junit.Test

// TODO: Write some integration tests using another implementation of `age`
Expand All @@ -27,7 +30,7 @@ class AgeTest {
}

@Test
fun testEncryptExactBlockSizeDoesNotThrow() {
fun testScryptEncryptExactBlockSizeDoesNotThrow() {
// Encrypt exactly 2 chunks
val i = ByteArray(ChaCha20Poly1305OutputStream.CHUNK_SIZE * 2)
i.fill("0".toByte())
Expand All @@ -40,4 +43,19 @@ class AgeTest {
// println(Base64.getEncoder().encodeToString(baos.toByteArray()))
// TODO: Test this better when `decrypt` is implemented
}

@Test
fun testX25519EncryptDoesNotThrow() {
val publicKey = Hex.decode("1292e55a1e907ddb45726667ab19b48efdf323732cbd31ade84ef2ec0eb0eb0b")

val recipients = listOf(X25519Recipient(publicKey))

val bais = ByteArrayInputStream("this is my file".toByteArray())
val baos = ByteArrayOutputStream()

Age.encrypt(recipients, bais, baos, generateArmor = false)

println(Base64.getEncoder().encodeToString(baos.toByteArray()))
// TODO: Test this better when `decrypt` is implemented
}
}
33 changes: 33 additions & 0 deletions src/test/kotlin/kage/crypto/x25519/X25519RecipientTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright 2021 The kage Authors. All rights reserved. Use of this source code is governed by
* either an Apache 2.0 or MIT license at your discretion, that can be found in the LICENSE-APACHE
* or LICENSE-MIT files respectively.
*/
package kage.kage.crypto.x25519

import java.security.SecureRandom
import kage.crypto.x25519.X25519Recipient
import kage.utils.decodeBase64
import kotlin.test.assertEquals
import org.junit.Test

class X25519RecipientTest {
@Test
fun testWrap() {
val publicKey = ByteArray(32)
SecureRandom().nextBytes(publicKey)

val recipient = X25519Recipient(publicKey)

val fileKey = ByteArray(32)

val stanza = recipient.wrap(fileKey).first()

val sharedSecret = stanza.args.first().decodeBase64()

assertEquals(X25519Recipient.EPHEMERAL_SECRET_LEN, sharedSecret.size)
assertEquals(X25519Recipient.X25519_STANZA_TYPE, stanza.type)

// TODO: Test this with `unwrap` when implemented
}
}
0