Skip to content

Commit

Permalink
SecretBox: refactor box and boxOpen to accept nonce directly (#18)
Browse files Browse the repository at this point in the history
* refactor box and boxOpen to accept nonce parameter directly

* add functions to prepend and extract nonce

* move generateRandomBytesArray() to util and make it public

* remove functions to prepend and extract nonce
  • Loading branch information
muzzammilshahid authored Mar 2, 2024
1 parent 8e5e56f commit 15cd56f
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 73 deletions.
3 changes: 2 additions & 1 deletion src/main/java/io/xconn/cryptology/SealedBox.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ public static void seal(byte[] output, byte[] message, byte[] recipientPublicKey
byte[] nonce = createNonce(keyPair.getPublicKey(), recipientPublicKey);
byte[] sharedSecret = computeSharedSecret(recipientPublicKey, keyPair.getPrivateKey());

byte[] ciphertext = Util.encrypt(nonce, message, sharedSecret);
byte[] ciphertext = new byte[message.length + MAC_SIZE];
Util.encrypt(ciphertext, nonce, message, sharedSecret);

System.arraycopy(keyPair.getPublicKey(), 0, output, 0, keyPair.getPublicKey().length);
System.arraycopy(ciphertext, 0, output, keyPair.getPublicKey().length, ciphertext.length);
Expand Down
43 changes: 13 additions & 30 deletions src/main/java/io/xconn/cryptology/SecretBox.java
Original file line number Diff line number Diff line change
@@ -1,56 +1,39 @@
package io.xconn.cryptology;

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

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

public class SecretBox {

public static byte[] box(byte[] message, byte[] privateKey) {
byte[] output = new byte[message.length + MAC_SIZE + NONCE_SIZE];
box(output, message, privateKey);
public static byte[] box(byte[] nonce, byte[] message, byte[] privateKey) {
byte[] output = new byte[message.length + MAC_SIZE];
box(output, nonce, message, privateKey);

return output;
}

public static void box(byte[] output, byte[] message, byte[] privateKey) {
byte[] nonce = generateNonce();
byte[] cipherWithoutNonce = Util.encrypt(nonce, message, privateKey);

System.arraycopy(nonce, 0, output, 0, nonce.length);
System.arraycopy(cipherWithoutNonce, 0, output, nonce.length, cipherWithoutNonce.length);
public static void box(byte[] output, byte[] nonce, byte[] message, byte[] privateKey) {
Util.encrypt(output, nonce, message, privateKey);
}


public static byte[] boxOpen(byte[] ciphertext, byte[] privateKey) {
byte[] plainText = new byte[ciphertext.length - MAC_SIZE - NONCE_SIZE];
boxOpen(plainText, ciphertext, privateKey);
public static byte[] boxOpen(byte[] nonce, byte[] ciphertext, byte[] privateKey) {
byte[] plainText = new byte[ciphertext.length - MAC_SIZE];
boxOpen(plainText, nonce, ciphertext, privateKey);

return plainText;
}

public static void boxOpen(byte[] output, byte[] ciphertext, byte[] privateKey) {
byte[] nonce = Arrays.copyOfRange(ciphertext, 0, NONCE_SIZE);
byte[] message = Arrays.copyOfRange(ciphertext, NONCE_SIZE, ciphertext.length);

Util.decrypt(output, nonce, message, privateKey);
public static void boxOpen(byte[] output, byte[] nonce, byte[] ciphertext, byte[] privateKey) {
Util.decrypt(output, nonce, ciphertext, privateKey);
}

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

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

static byte[] generateRandomBytesArray(int size) {
byte[] randomBytes = new byte[size];
SecureRandom random = new SecureRandom();
random.nextBytes(randomBytes);
return randomBytes;
public static byte[] generateNonce() {
return Util.generateRandomBytesArray(NONCE_SIZE);
}
}
21 changes: 13 additions & 8 deletions src/main/java/io/xconn/cryptology/Util.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.xconn.cryptology;

import java.security.MessageDigest;
import java.security.SecureRandom;

import org.bouncycastle.crypto.engines.XSalsa20Engine;
import org.bouncycastle.crypto.macs.Poly1305;
Expand All @@ -12,7 +13,7 @@ public class Util {
public static final int NONCE_SIZE = 24;
public static final int MAC_SIZE = 16;

public static byte[] encrypt(byte[] nonce, byte[] message, byte[] secret) {
public static void encrypt(byte[] output, byte[] nonce, byte[] message, byte[] secret) {
checkLength(secret, SECRET_KEY_LEN);
checkLength(nonce, NONCE_SIZE);

Expand All @@ -22,15 +23,12 @@ public static byte[] encrypt(byte[] nonce, byte[] message, byte[] secret) {
cipher.init(true, new ParametersWithIV(new KeyParameter(secret), nonce));
byte[] subKey = new byte[SECRET_KEY_LEN];
cipher.processBytes(subKey, 0, SECRET_KEY_LEN, subKey, 0);
byte[] cipherText = new byte[message.length + mac.getMacSize()];
cipher.processBytes(message, 0, message.length, cipherText, mac.getMacSize());
cipher.processBytes(message, 0, message.length, output, mac.getMacSize());

// hash the ciphertext
mac.init(new KeyParameter(subKey));
mac.update(cipherText, mac.getMacSize(), message.length);
mac.doFinal(cipherText, 0);

return cipherText;
mac.update(output, mac.getMacSize(), message.length);
mac.doFinal(output, 0);
}

static void decrypt(byte[] output, byte[] nonce, byte[] ciphertext, byte[] secret) {
Expand Down Expand Up @@ -64,12 +62,19 @@ static void decrypt(byte[] output, byte[] nonce, byte[] ciphertext, byte[] secre
cipher.processBytes(ciphertext, mac.getMacSize(), output.length, output, 0);
}

static void checkLength(byte[] data, int size) {
public static void checkLength(byte[] data, int size) {
if (data == null)
throw new NullPointerException("Input array is null.");
else if (data.length != size) {
throw new IllegalArgumentException("Invalid array length: " + data.length +
". Length should be " + size);
}
}

public static byte[] generateRandomBytesArray(int size) {
byte[] randomBytes = new byte[size];
SecureRandom random = new SecureRandom();
random.nextBytes(randomBytes);
return randomBytes;
}
}
14 changes: 4 additions & 10 deletions src/test/java/io/xconn/cryptology/InteroperabilityTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,17 @@ public void secretBoxTest() {
TweetNaclFast.SecretBox box = new TweetNaclFast.SecretBox(privateKey);
byte[] ct = box.box(message, nonce);

byte[] ciphertext = new byte[nonce.length + ct.length];
System.arraycopy(nonce, 0, ciphertext, 0, nonce.length);
System.arraycopy(ct, 0, ciphertext, nonce.length, ct.length);

// decrypt using SecretBox
byte[] plainText = SecretBox.boxOpen(ciphertext, privateKey);
byte[] plainText = SecretBox.boxOpen(nonce, ct, privateKey);

assertArrayEquals(message, plainText);

// 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[] cipherText = SecretBox.box(nonce, message, privateKey);

// decrypt using TweetNaCl
byte[] decryptedMessage = box.open(encryptedMessage, nonce1);
byte[] decryptedMessage = box.open(cipherText, nonce);

assertArrayEquals(message, decryptedMessage);
}

Expand Down
46 changes: 23 additions & 23 deletions src/test/java/io/xconn/cryptology/SecretBoxTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

import static io.xconn.cryptology.SecretBox.box;
Expand All @@ -24,51 +22,53 @@ public static void setUp() {
@Test
public void testEncryptAndDecrypt() {
byte[] message = "Hello, World!".getBytes();
byte[] encrypted = box(message, secretKey);
byte[] decrypted = boxOpen(encrypted, secretKey);
byte[] nonce = SecretBox.generateNonce();

byte[] encrypted = box(nonce, message, secretKey);
byte[] decrypted = boxOpen(nonce, encrypted, secretKey);
assertArrayEquals(message, decrypted);
}

@Test
public void testEncryptAndDecryptOutput() {
byte[] message = "Hello, World!".getBytes();
byte[] encrypted = new byte[Util.NONCE_SIZE + Util.MAC_SIZE + message.length];
box(encrypted, message, secretKey);
byte[] nonce = SecretBox.generateNonce();

byte[] encrypted = new byte[Util.MAC_SIZE + message.length];
box(encrypted, nonce, message, secretKey);

byte[] decrypted = new byte[message.length];
boxOpen(decrypted, encrypted, secretKey);
boxOpen(decrypted, nonce, encrypted, secretKey);
assertArrayEquals(message, decrypted);
}

@Test
public void testEncryptAndDecryptWithInvalidMAC() {
byte[] message = "Hello, World!".getBytes();
byte[] encrypted = box(message, secretKey);
byte[] nonce = SecretBox.generateNonce();

byte[] encrypted = box(nonce, message, secretKey);
encrypted[encrypted.length - 1] ^= 0xFF; // Modify last byte
assertThrows(IllegalArgumentException.class, () -> boxOpen(encrypted, secretKey));
assertThrows(IllegalArgumentException.class, () -> boxOpen(nonce, encrypted, secretKey));
}

@Test
public void testEncryptAndDecryptWithInvalidNonce() {
byte[] message = "Hello, World!".getBytes();
byte[] encrypted = box(message, secretKey);
encrypted[0] ^= 0xFF; // Modify first byte
assertThrows(IllegalArgumentException.class, () -> boxOpen(encrypted, secretKey));
byte[] nonce = SecretBox.generateNonce();

byte[] encrypted = box(nonce, message, secretKey);
nonce[0] ^= 0xFF; // Modify first byte
assertThrows(IllegalArgumentException.class, () -> boxOpen(nonce, encrypted, secretKey));
}

@Test
public void testEncryptAndDecryptWithModifiedCiphertext() {
byte[] message = "Hello, World!".getBytes();
byte[] encrypted = box(message, secretKey);
encrypted[Util.NONCE_SIZE + 1] ^= 0xFF; // Modify the byte next to nonce
assertThrows(IllegalArgumentException.class, () -> boxOpen(encrypted, secretKey));
}
byte[] nonce = SecretBox.generateNonce();

@Test
public void testGenerateRandomBytesArray() {
int size = 32;
byte[] randomBytes = SecretBox.generateRandomBytesArray(size);

assertNotNull(randomBytes);
assertEquals(size, randomBytes.length);
byte[] encrypted = box(nonce, message, secretKey);
encrypted[Util.NONCE_SIZE + 1] ^= 0xFF; // Modify the byte next to nonce
assertThrows(IllegalArgumentException.class, () -> boxOpen(nonce, encrypted, secretKey));
}
}
14 changes: 13 additions & 1 deletion src/test/java/io/xconn/cryptology/UtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

import static io.xconn.cryptology.Util.checkLength;
Expand All @@ -28,11 +30,21 @@ void testEncryptDecrypt() {
byte[] message = "Hello, world!".getBytes();
byte[] nonce = SecretBox.generateNonce();

byte[] encryptedText = encrypt(nonce, message, secretKey);
byte[] encryptedText = new byte[message.length + Util.MAC_SIZE];
encrypt(encryptedText, nonce, message, secretKey);

byte[] decryptedText = new byte[message.length];
decrypt(decryptedText, nonce, encryptedText, secretKey);

assertArrayEquals(message, decryptedText);
}

@Test
public void testGenerateRandomBytesArray() {
int size = 32;
byte[] randomBytes = Util.generateRandomBytesArray(size);

assertNotNull(randomBytes);
assertEquals(size, randomBytes.length);
}
}

0 comments on commit 15cd56f

Please sign in to comment.