Skip to content

Commit

Permalink
refactor: move util functions to relevant classes
Browse files Browse the repository at this point in the history
  • Loading branch information
muzzammilshahid committed Feb 29, 2024
1 parent f3f0c49 commit 7525c00
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 149 deletions.
43 changes: 35 additions & 8 deletions src/main/java/io/xconn/cryptology/SealedBox.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
package io.xconn.cryptology;

import java.security.SecureRandom;

import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.digests.Blake2bDigest;
import org.bouncycastle.crypto.engines.XSalsa20Engine;
import org.bouncycastle.crypto.generators.X25519KeyPairGenerator;
import org.bouncycastle.crypto.macs.Poly1305;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.crypto.params.X25519KeyGenerationParameters;
import org.bouncycastle.crypto.params.X25519PrivateKeyParameters;
import org.bouncycastle.crypto.params.X25519PublicKeyParameters;
import org.bouncycastle.math.ec.rfc7748.X25519;
import org.bouncycastle.util.Arrays;

import static io.xconn.cryptology.Util.MAC_SIZE;
import static io.xconn.cryptology.Util.PUBLIC_KEY_BYTES;
import static io.xconn.cryptology.Util.getX25519PublicKey;

public class SealedBox {
private static final byte[] HSALSA20_SEED = new byte[16];
public static final int NONCE_SIZE = 24;
public static final int PUBLIC_KEY_BYTES = 32;
public static final int Private_KEY_LEN = 32;
public static final int MAC_SIZE = 16;

public static byte[] seal(byte[] message, byte[] recipientPublicKey) {
byte[] cipherText = new byte[message.length + PUBLIC_KEY_BYTES + MAC_SIZE];
Expand All @@ -22,15 +29,15 @@ public static byte[] seal(byte[] message, byte[] recipientPublicKey) {
}

public static void seal(byte[] output, byte[] message, byte[] recipientPublicKey) {
KeyPair keyPair = Util.generateX25519KeyPair();
KeyPair keyPair = generateKeyPair();
byte[] nonce = createNonce(keyPair.getPublicKey(), recipientPublicKey);
byte[] sharedSecret = computeSharedSecret(recipientPublicKey, keyPair.getPrivateKey());

XSalsa20Engine cipher = new XSalsa20Engine();
ParametersWithIV params = new ParametersWithIV(new KeyParameter(sharedSecret), nonce);
cipher.init(true, params);

byte[] sk = new byte[Util.SECRET_KEY_LEN];
byte[] sk = new byte[Private_KEY_LEN];
cipher.processBytes(sk, 0, sk.length, sk, 0);

// encrypt the message
Expand All @@ -49,8 +56,28 @@ public static void seal(byte[] output, byte[] message, byte[] recipientPublicKey
System.arraycopy(ciphertext, 0, output, keyPair.getPublicKey().length + macBuf.length, ciphertext.length);
}

public static KeyPair generateKeyPair() {
SecureRandom random = new SecureRandom();
X25519KeyGenerationParameters params = new X25519KeyGenerationParameters(random);
X25519KeyPairGenerator generator = new X25519KeyPairGenerator();
generator.init(params);

AsymmetricCipherKeyPair keyPair = generator.generateKeyPair();

X25519PrivateKeyParameters privateKeyParams = (X25519PrivateKeyParameters) keyPair.getPrivate();
X25519PublicKeyParameters publicKeyParams = (X25519PublicKeyParameters) keyPair.getPublic();

return new KeyPair(publicKeyParams.getEncoded(), privateKeyParams.getEncoded());
}

public static byte[] getPublicKey(byte[] privateKeyRaw) {
X25519PrivateKeyParameters privateKey = new X25519PrivateKeyParameters(privateKeyRaw, 0);

return privateKey.generatePublicKey().getEncoded();
}

static byte[] createNonce(byte[] ephemeralPublicKey, byte[] recipientPublicKey) {
Blake2bDigest blake2b = new Blake2bDigest(Util.NONCE_SIZE * 8);
Blake2bDigest blake2b = new Blake2bDigest(NONCE_SIZE * 8);
byte[] nonce = new byte[blake2b.getDigestSize()];

blake2b.update(ephemeralPublicKey, 0, ephemeralPublicKey.length);
Expand Down Expand Up @@ -80,7 +107,7 @@ public static byte[] sealOpen(byte[] message, byte[] privateKey) {
public static void sealOpen(byte[] output, byte[] message, byte[] privateKey) {
byte[] ephemeralPublicKey = Arrays.copyOf(message, PUBLIC_KEY_BYTES);
byte[] ciphertext = Arrays.copyOfRange(message, PUBLIC_KEY_BYTES, message.length);
byte[] nonce = createNonce(ephemeralPublicKey, getX25519PublicKey(privateKey));
byte[] nonce = createNonce(ephemeralPublicKey, getPublicKey(privateKey));
byte[] sharedSecret = computeSharedSecret(ephemeralPublicKey, privateKey);

SecretBox.boxOpen(output, nonce, ciphertext, sharedSecret);
Expand Down
54 changes: 36 additions & 18 deletions src/main/java/io/xconn/cryptology/SecretBox.java
Original file line number Diff line number Diff line change
@@ -1,43 +1,46 @@
package io.xconn.cryptology;

import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Arrays;

import org.bouncycastle.crypto.engines.XSalsa20Engine;
import org.bouncycastle.crypto.macs.Poly1305;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;

import static io.xconn.cryptology.Util.MAC_SIZE;

public class SecretBox {

public static final int NONCE_SIZE = 24;
public static final int SECRET_KEY_LEN = 32;
public static final int MAC_SIZE = 16;

public static byte[] box(byte[] message, byte[] privateKey) {
checkLength(privateKey, Util.SECRET_KEY_LEN);
checkLength(privateKey, SECRET_KEY_LEN);

byte[] nonce = Util.generateRandomBytesArray(Util.NONCE_SIZE);
byte[] output = new byte[message.length + MAC_SIZE + Util.NONCE_SIZE];
byte[] nonce = generateNonce();
byte[] output = new byte[message.length + MAC_SIZE + NONCE_SIZE];
box(output, nonce, message, privateKey);

return output;
}

public static void box(byte[] output, byte[] message, byte[] privateKey) {
checkLength(privateKey, Util.SECRET_KEY_LEN);
checkLength(privateKey, SECRET_KEY_LEN);

byte[] nonce = Util.generateRandomBytesArray(Util.NONCE_SIZE);
byte[] nonce = generateNonce();
box(output, nonce, message, privateKey);
}

static void box(byte[] output, byte[] nonce, byte[] plaintext, byte[] privateKey) {
checkLength(nonce, Util.NONCE_SIZE);
checkLength(nonce, NONCE_SIZE);

XSalsa20Engine cipher = new XSalsa20Engine();
Poly1305 mac = new Poly1305();

cipher.init(true, new ParametersWithIV(new KeyParameter(privateKey), nonce));
byte[] subKey = new byte[Util.SECRET_KEY_LEN];
cipher.processBytes(subKey, 0, Util.SECRET_KEY_LEN, subKey, 0);
byte[] subKey = new byte[SECRET_KEY_LEN];
cipher.processBytes(subKey, 0, SECRET_KEY_LEN, subKey, 0);
byte[] cipherWithoutNonce = new byte[plaintext.length + mac.getMacSize()];
cipher.processBytes(plaintext, 0, plaintext.length, cipherWithoutNonce, mac.getMacSize());

Expand All @@ -52,10 +55,10 @@ static void box(byte[] output, byte[] nonce, byte[] plaintext, byte[] privateKey


public static byte[] boxOpen(byte[] ciphertext, byte[] privateKey) {
checkLength(privateKey, Util.SECRET_KEY_LEN);
checkLength(privateKey, SECRET_KEY_LEN);

byte[] nonce = Arrays.copyOfRange(ciphertext, 0, Util.NONCE_SIZE);
byte[] message = Arrays.copyOfRange(ciphertext, Util.NONCE_SIZE,
byte[] nonce = Arrays.copyOfRange(ciphertext, 0, NONCE_SIZE);
byte[] message = Arrays.copyOfRange(ciphertext, NONCE_SIZE,
ciphertext.length);
byte[] plainText = new byte[message.length - MAC_SIZE];
boxOpen(plainText, nonce, message, privateKey);
Expand All @@ -64,23 +67,23 @@ public static byte[] boxOpen(byte[] ciphertext, byte[] privateKey) {
}

public static void boxOpen(byte[] output, byte[] ciphertext, byte[] privateKey) {
checkLength(privateKey, Util.SECRET_KEY_LEN);
checkLength(privateKey, SECRET_KEY_LEN);

byte[] nonce = Arrays.copyOfRange(ciphertext, 0, Util.NONCE_SIZE);
byte[] message = Arrays.copyOfRange(ciphertext, Util.NONCE_SIZE,
byte[] nonce = Arrays.copyOfRange(ciphertext, 0, NONCE_SIZE);
byte[] message = Arrays.copyOfRange(ciphertext, NONCE_SIZE,
ciphertext.length);

boxOpen(output, nonce, message, privateKey);
}

static void boxOpen(byte[] output, byte[] nonce, byte[] ciphertext, byte[] privateKey) {
checkLength(nonce, Util.NONCE_SIZE);
checkLength(nonce, NONCE_SIZE);

XSalsa20Engine cipher = new XSalsa20Engine();
Poly1305 mac = new Poly1305();

cipher.init(false, new ParametersWithIV(new KeyParameter(privateKey), nonce));
byte[] sk = new byte[Util.SECRET_KEY_LEN];
byte[] sk = new byte[SECRET_KEY_LEN];
cipher.processBytes(sk, 0, sk.length, sk, 0);

// hash ciphertext
Expand All @@ -103,6 +106,21 @@ static void boxOpen(byte[] output, byte[] nonce, byte[] ciphertext, byte[] priva
cipher.processBytes(ciphertext, mac.getMacSize(), output.length, output, 0);
}

public static byte[] generateSecret() {
return generateRandomBytesArray(SECRET_KEY_LEN);
}

static byte[] generateNonce() {
return generateRandomBytesArray(NONCE_SIZE);
}

static byte[] generateRandomBytesArray(int size) {
byte[] randomBytes = new byte[size];
SecureRandom random = new SecureRandom();
random.nextBytes(randomBytes);
return randomBytes;
}

static void checkLength(byte[] data, int size) {
if (data == null)
throw new NullPointerException("Input array is null.");
Expand Down
43 changes: 0 additions & 43 deletions src/main/java/io/xconn/cryptology/Util.java

This file was deleted.

16 changes: 8 additions & 8 deletions src/test/java/io/xconn/cryptology/InteroperabilityTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class InteroperabilityTest {
@Test
public void secretBoxTest() {
byte[] message = "Hello, World!".getBytes();
byte[] nonce = Util.generateRandomBytesArray(Util.NONCE_SIZE);
byte[] nonce = SecretBox.generateNonce();

// encrypt using TweetNaCl
TweetNaclFast.SecretBox box = new TweetNaclFast.SecretBox(privateKey);
Expand All @@ -36,8 +36,8 @@ public void secretBoxTest() {
// encrypt using SecretBox
byte[] cipherText = SecretBox.box(message, privateKey);

byte[] nonce1 = Arrays.copyOfRange(cipherText, 0, Util.NONCE_SIZE);
byte[] encryptedMessage = Arrays.copyOfRange(cipherText, Util.NONCE_SIZE, cipherText.length);
byte[] nonce1 = Arrays.copyOfRange(cipherText, 0, SecretBox.NONCE_SIZE);
byte[] encryptedMessage = Arrays.copyOfRange(cipherText, SecretBox.NONCE_SIZE, cipherText.length);

// decrypt using TweetNaCl
byte[] decryptedMessage = box.open(encryptedMessage, nonce1);
Expand Down Expand Up @@ -79,21 +79,21 @@ static byte[] crypto_box_seal(byte[] clearText, byte[] receiverPubKey) throws Ge
byte[] ciphertext = box.box(clearText, nonce);
if (ciphertext == null) throw new GeneralSecurityException("could not create box");

byte[] sealedbox = new byte[ciphertext.length + Util.PUBLIC_KEY_BYTES];
byte[] sealedbox = new byte[ciphertext.length + SealedBox.PUBLIC_KEY_BYTES];
byte[] ephpubkey = ephkeypair.getPublicKey();

System.arraycopy(ephpubkey, 0, sealedbox, 0, Util.PUBLIC_KEY_BYTES);
System.arraycopy(ephpubkey, 0, sealedbox, 0, SealedBox.PUBLIC_KEY_BYTES);
System.arraycopy(ciphertext, 0, sealedbox, 32, ciphertext.length);

return sealedbox;
}

public static byte[] crypto_box_seal_open(byte[] c, byte[] pk, byte[] sk) throws GeneralSecurityException {
if (c.length < Util.PUBLIC_KEY_BYTES + Util.MAC_SIZE)
if (c.length < SealedBox.PUBLIC_KEY_BYTES + SealedBox.MAC_SIZE)
throw new IllegalArgumentException("Ciphertext too short");

byte[] pksender = Arrays.copyOfRange(c, 0, Util.PUBLIC_KEY_BYTES);
byte[] ciphertextwithmac = Arrays.copyOfRange(c, Util.PUBLIC_KEY_BYTES, c.length);
byte[] pksender = Arrays.copyOfRange(c, 0, SealedBox.PUBLIC_KEY_BYTES);
byte[] ciphertextwithmac = Arrays.copyOfRange(c, SealedBox.PUBLIC_KEY_BYTES, c.length);
byte[] nonce = SealedBox.createNonce(pksender, pk);

TweetNaclFast.Box box = new TweetNaclFast.Box(pksender, sk);
Expand Down
30 changes: 26 additions & 4 deletions src/test/java/io/xconn/cryptology/SealedBoxTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.xconn.cryptology;

import java.security.SecureRandom;

import org.bouncycastle.util.encoders.Hex;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
Expand All @@ -13,8 +15,6 @@
import static io.xconn.cryptology.SealedBox.createNonce;
import static io.xconn.cryptology.SealedBox.sealOpen;
import static io.xconn.cryptology.SealedBox.seal;
import static io.xconn.cryptology.Util.MAC_SIZE;
import static io.xconn.cryptology.Util.PUBLIC_KEY_BYTES;

public class SealedBoxTest {

Expand All @@ -40,7 +40,7 @@ public void testEncryptAndDecrypt() {
public void testEncryptAndDecryptOutput() {
String message = "Hello, world!";

byte[] encrypted = new byte[message.getBytes().length + PUBLIC_KEY_BYTES + MAC_SIZE];
byte[] encrypted = new byte[message.getBytes().length + SealedBox.PUBLIC_KEY_BYTES + SealedBox.MAC_SIZE];
seal(encrypted, message.getBytes(), publicKey);
byte[] decrypted = new byte[message.length()];
sealOpen(decrypted, encrypted, privateKey);
Expand All @@ -52,7 +52,7 @@ public void testEncryptAndDecryptOutput() {
void testCreateNonce() {
byte[] nonce = createNonce(new byte[32], new byte[32]);
assertNotNull(nonce);
assertEquals(Util.NONCE_SIZE, nonce.length);
assertEquals(SealedBox.NONCE_SIZE, nonce.length);
}

@Test
Expand All @@ -63,6 +63,28 @@ public void testComputeSharedSecret() {
assertArrayEquals(expectedSharedSecret, sharedSecret);
}

@Test
void testGetPublicKey() {
SecureRandom random = new SecureRandom();
byte[] privateKeyRaw = new byte[32];
random.nextBytes(privateKeyRaw);

byte[] publicKey = SealedBox.getPublicKey(privateKeyRaw);

assertNotNull(publicKey);
assertEquals(SealedBox.PUBLIC_KEY_BYTES, publicKey.length);
}

@Test
void testGenerateKeyPair() {
KeyPair keyPair = SealedBox.generateKeyPair();

assertNotNull(keyPair);
assertNotNull(keyPair.getPublicKey());
assertNotNull(keyPair.getPrivateKey());
assertEquals(SealedBox.PUBLIC_KEY_BYTES, keyPair.getPublicKey().length);
assertEquals(SealedBox.Private_KEY_LEN, keyPair.getPrivateKey().length);
}

@Test
public void testInvalidDecrypt() {
Expand Down
Loading

0 comments on commit 7525c00

Please sign in to comment.