Created
April 19, 2021 21:56
-
-
Save jvz/14db0211cded5e757a687e041698284f to your computer and use it in GitHub Desktop.
Port of libsodium sealed boxes to Java using jnacl and jblake2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | |
<modelVersion>4.0.0</modelVersion> | |
<parent> | |
<groupId>org.jenkins-ci</groupId> | |
<artifactId>jenkins</artifactId> | |
<version>1.63</version> | |
<relativePath /> | |
</parent> | |
<artifactId>sealedbox4j</artifactId> | |
<version>${revision}${changelist}</version> | |
<packaging>jar</packaging> | |
<scm> | |
<connection>scm:git:git://github.com/jenkinsci/todo.git</connection> | |
<developerConnection>scm:git:git@github.com:jenkinsci/todo.git</developerConnection> | |
<url>https://github.com/jenkinsci/todo</url> | |
<tag>${scmTag}</tag> | |
</scm> | |
<name>Sealed Box</name> | |
<url>https://github.com/jenkinsci/todo</url> | |
<licenses> | |
<license> | |
<name>MIT License</name> | |
<url>http://opensource.org/licenses/MIT</url> | |
</license> | |
</licenses> | |
<properties> | |
<revision>1.0</revision> | |
<changelist>-SNAPSHOT</changelist> | |
<java.level>8</java.level> | |
</properties> | |
<dependencies> | |
<dependency> | |
<groupId>eu.neilalexander</groupId> | |
<artifactId>jnacl</artifactId> | |
<version>1.0.0</version> | |
</dependency> | |
<dependency> | |
<groupId>org.kocakosm</groupId> | |
<artifactId>jblake2</artifactId> | |
<version>0.4</version> | |
</dependency> | |
<dependency> | |
<groupId>junit</groupId> | |
<artifactId>junit</artifactId> | |
<scope>test</scope> | |
</dependency> | |
</dependencies> | |
<repositories> | |
<repository> | |
<id>repo.jenkins-ci.org</id> | |
<url>https://repo.jenkins-ci.org/public/</url> | |
</repository> | |
</repositories> | |
<pluginRepositories> | |
<pluginRepository> | |
<id>repo.jenkins-ci.org</id> | |
<url>https://repo.jenkins-ci.org/public/</url> | |
</pluginRepository> | |
</pluginRepositories> | |
</project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package io.jenkins.infra.sealedbox4j; | |
import org.kocakosm.jblake2.Blake2b; | |
import javax.crypto.AEADBadTagException; | |
import java.util.Arrays; | |
import static com.neilalexander.jnacl.crypto.curve25519.crypto_scalarmult_base; | |
import static com.neilalexander.jnacl.crypto.curve25519xsalsa20poly1305.*; | |
/** | |
* Sealed boxes are designed to anonymously send messages to a recipient given its public key. Only the recipient can | |
* decrypt these messages, using its private key. While the recipient can verify the integrity of the message, it cannot | |
* verify the identity of the sender. A message is encrypted using an ephemeral key pair, whose secret part is destroyed | |
* right after the encryption process. Without knowing the secret key used for a given message, the sender cannot | |
* decrypt its own message later. And without additional data, a message cannot be correlated with the identity of | |
* its sender. | |
* | |
* @see <a href="https://doc.libsodium.org/public-key_cryptography/sealed_boxes">Sealed boxes</a> | |
*/ | |
public class SealedBox { | |
private SealedBox() { | |
// utility class | |
} | |
/** | |
* Creates a sealed box encrypted to the recipient public key. | |
*/ | |
public static byte[] createSealedBox(byte[] recipientPublicKey, byte[] message) { | |
byte[] ephemeralPrivateKey = new byte[crypto_secretbox_SECRETKEYBYTES]; | |
byte[] ephemeralPublicKey = new byte[crypto_secretbox_PUBLICKEYBYTES]; | |
crypto_box_keypair(ephemeralPublicKey, ephemeralPrivateKey); | |
byte[] nonce = new Blake2b(crypto_secretbox_NONCEBYTES) | |
.update(ephemeralPublicKey) | |
.update(recipientPublicKey) | |
.digest(); | |
byte[] buf = new byte[message.length + crypto_secretbox_ZEROBYTES]; | |
System.arraycopy(message, 0, buf, crypto_secretbox_ZEROBYTES, message.length); | |
crypto_box(buf, buf, nonce, recipientPublicKey, ephemeralPrivateKey); | |
byte[] sealedBox = Arrays.copyOf(ephemeralPublicKey, buf.length + crypto_secretbox_BOXZEROBYTES); | |
System.arraycopy(buf, crypto_secretbox_BOXZEROBYTES, sealedBox, crypto_secretbox_PUBLICKEYBYTES, message.length + crypto_secretbox_BOXZEROBYTES); | |
return sealedBox; | |
} | |
/** | |
* Opens a sealed box encrypted to the keypair with the provided private key and returns its contents if valid | |
* or throws an exception if invalid. | |
*/ | |
public static byte[] openSealedBox(byte[] privateKey, byte[] sealedBox) throws AEADBadTagException { | |
byte[] ephemeralPublicKey = Arrays.copyOf(sealedBox, crypto_secretbox_PUBLICKEYBYTES); | |
byte[] publicKey = new byte[crypto_secretbox_PUBLICKEYBYTES]; | |
crypto_scalarmult_base(publicKey, privateKey); | |
byte[] nonce = new Blake2b(crypto_secretbox_NONCEBYTES) | |
.update(ephemeralPublicKey) | |
.update(publicKey) | |
.digest(); | |
byte[] buf = Arrays.copyOfRange(sealedBox, crypto_secretbox_BOXZEROBYTES, sealedBox.length); | |
if (crypto_box_open(buf, buf, nonce, ephemeralPublicKey, privateKey) == -1) { | |
throw new AEADBadTagException("Tag mismatch"); | |
} | |
return Arrays.copyOfRange(buf, crypto_secretbox_ZEROBYTES, buf.length); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package io.jenkins.infra.sealedbox4j; | |
import org.junit.Test; | |
import javax.crypto.AEADBadTagException; | |
import java.nio.charset.StandardCharsets; | |
import static com.neilalexander.jnacl.crypto.curve25519xsalsa20poly1305.crypto_box_keypair; | |
import static org.junit.Assert.*; | |
public class SealedBoxTest { | |
@Test | |
public void smokeTest() throws AEADBadTagException { | |
byte[] alicePrivateKey = new byte[32]; | |
byte[] alicePublicKey = new byte[32]; | |
crypto_box_keypair(alicePublicKey, alicePrivateKey); | |
byte[] message = "Hello, world!".getBytes(StandardCharsets.UTF_8); | |
byte[] sealedBox = SealedBox.createSealedBox(alicePublicKey, message); | |
byte[] contents = SealedBox.openSealedBox(alicePrivateKey, sealedBox); | |
assertArrayEquals(message, contents); | |
sealedBox[sealedBox.length - 1] ^= 42; | |
assertThrows(AEADBadTagException.class, () -> SealedBox.openSealedBox(alicePrivateKey, sealedBox)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment