Skip to content

Instantly share code, notes, and snippets.

@jvz
Created April 19, 2021 21:56
Show Gist options
  • Save jvz/14db0211cded5e757a687e041698284f to your computer and use it in GitHub Desktop.
Save jvz/14db0211cded5e757a687e041698284f to your computer and use it in GitHub Desktop.
Port of libsodium sealed boxes to Java using jnacl and jblake2
<?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>
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);
}
}
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