Skip to content

Commit

Permalink
replace libsodium with bouncy castle in SecretBox
Browse files Browse the repository at this point in the history
  • Loading branch information
muzzammilshahid committed Feb 24, 2024
1 parent 5b1b713 commit 0f1c292
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 53 deletions.
9 changes: 3 additions & 6 deletions autobahn/src/main/java/xbr/network/KeySeries.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.cbor.CBORFactory;

import org.libsodium.jni.SodiumConstants;
import org.libsodium.jni.crypto.Random;
import org.web3j.utils.Numeric;

import java.math.BigInteger;
Expand Down Expand Up @@ -94,8 +92,7 @@ byte[] getPrice() {
}

Map<String, Object> encrypt(Object payload) throws JsonProcessingException {
byte[] nonce = new Random().randomBytes(
SodiumConstants.XSALSA20_POLY1305_SECRETBOX_NONCEBYTES);
byte[] nonce = Util.generateRandomBytesArray(Util.NONCE_SIZE);

Map<String, Object> data = new HashMap<>();
data.put("id", mID);
Expand All @@ -112,8 +109,8 @@ byte[] encryptKey(byte[] keyID, byte[] buyerPubKey) {
}

private void onRotate() {
mID = new Random().randomBytes(16);
mKey = new Random().randomBytes(SodiumConstants.XSALSA20_POLY1305_SECRETBOX_KEYBYTES);
mID = Util.generateRandomBytesArray(16);
mKey = Util.generateRandomBytesArray(Util.SECRET_KEY_LEN);
mBox = new SecretBox(mKey);

Map<String, Object> data = new HashMap<>();
Expand Down
10 changes: 4 additions & 6 deletions autobahn/src/main/java/xbr/network/SimpleBuyer.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.cbor.CBORFactory;

import org.libsodium.jni.SodiumConstants;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.ECKeyPair;
import org.web3j.utils.Numeric;
Expand All @@ -29,15 +28,14 @@

import io.crossbar.autobahn.utils.ABLogger;
import io.crossbar.autobahn.utils.IABLogger;
import io.crossbar.autobahn.utils.Pair;
import io.crossbar.autobahn.wamp.Session;
import io.crossbar.autobahn.wamp.exceptions.ApplicationError;
import xbr.network.crypto.SealedBox;
import xbr.network.crypto.SecretBox;
import xbr.network.pojo.Quote;
import xbr.network.pojo.Receipt;

import static org.libsodium.jni.NaCl.sodium;

public class SimpleBuyer {

private static final IABLogger LOGGER = ABLogger.getLogger(SimpleBuyer.class.getName());
Expand Down Expand Up @@ -69,9 +67,9 @@ public SimpleBuyer(String marketMakerAddr, String buyerKey, BigInteger maxPrice)
mEthPublicKey = mECKey.getPublicKey().toByteArray();
mEthAddr = Numeric.hexStringToByteArray(Credentials.create(mECKey).getAddress());

mPrivateKey = new byte[SodiumConstants.SECRETKEY_BYTES];
mPublicKey = new byte[SodiumConstants.PUBLICKEY_BYTES];
sodium().crypto_box_keypair(mPublicKey, mPrivateKey);
Pair<byte[], byte[]> pubPriKeyPair = Util.generateX25519KeyPair();
mPublicKey = pubPriKeyPair.first;
mPrivateKey = pubPriKeyPair.second;

mMaxPrice = maxPrice;
mKeys = new HashMap<>();
Expand Down
3 changes: 1 addition & 2 deletions autobahn/src/main/java/xbr/network/SimpleSeller.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;

import org.libsodium.jni.crypto.Random;
import org.web3j.crypto.ECKeyPair;
import org.web3j.crypto.Keys;
import org.web3j.utils.Numeric;
Expand Down Expand Up @@ -93,7 +92,7 @@ public byte[] getPublicKey() {
private void onRotate(KeySeries series) {
mKeysMap.put(Numeric.toHexString(series.getID()), series);
long validFrom = Math.round(System.nanoTime() - 10 * Math.pow(10, 9));
byte[] signature = new Random().randomBytes(65);
byte[] signature = Util.generateRandomBytesArray(65);

List<Object> args = new ArrayList<>();
args.add(series.getID());
Expand Down
32 changes: 32 additions & 0 deletions autobahn/src/main/java/xbr/network/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@

package xbr.network;

import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.generators.X25519KeyPairGenerator;
import org.bouncycastle.crypto.params.X25519KeyGenerationParameters;
import org.bouncycastle.crypto.params.X25519PrivateKeyParameters;
import org.bouncycastle.crypto.params.X25519PublicKeyParameters;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
Expand All @@ -22,11 +27,17 @@

import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;

import io.crossbar.autobahn.utils.Pair;

public class Util {

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

public static BigInteger toXBR(int xbr) {
return BigInteger.valueOf(xbr).multiply(BigInteger.valueOf(10).pow(18));
}
Expand Down Expand Up @@ -169,4 +180,25 @@ static CompletableFuture<String> recoverEIP712Signer(int chainId, String verifyi

return future;
}

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

public static Pair<byte[], byte[]> generateX25519KeyPair() {
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 Pair<>(publicKeyParams.getEncoded(), privateKeyParams.getEncoded());
}
}
107 changes: 69 additions & 38 deletions autobahn/src/main/java/xbr/network/crypto/SecretBox.java
Original file line number Diff line number Diff line change
@@ -1,65 +1,96 @@
package xbr.network.crypto;

import org.libsodium.jni.crypto.Random;
import org.libsodium.jni.crypto.Util;
import org.libsodium.jni.encoders.Encoder;
import static xbr.network.Util.NONCE_SIZE;
import static xbr.network.Util.SECRET_KEY_LEN;
import static xbr.network.Util.generateRandomBytesArray;

import java.util.Arrays;

import static org.libsodium.jni.NaCl.sodium;
import static org.libsodium.jni.SodiumConstants.BOXZERO_BYTES;
import static org.libsodium.jni.SodiumConstants.XSALSA20_POLY1305_SECRETBOX_KEYBYTES;
import static org.libsodium.jni.SodiumConstants.XSALSA20_POLY1305_SECRETBOX_NONCEBYTES;
import static org.libsodium.jni.SodiumConstants.ZERO_BYTES;
import static org.libsodium.jni.crypto.Util.checkLength;
import static org.libsodium.jni.crypto.Util.isValid;
import static org.libsodium.jni.crypto.Util.removeZeros;
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 java.security.MessageDigest;
import java.util.Arrays;

public class SecretBox {

private byte[] mKey;
private Encoder mEncoder;
private final byte[] mKey;

public SecretBox(byte[] key) {
checkLength(key, XSALSA20_POLY1305_SECRETBOX_KEYBYTES);
mEncoder = Encoder.RAW;
mKey = key;
checkLength(key, SECRET_KEY_LEN);
mKey = Arrays.copyOf(key, key.length);
}

public byte[] encrypt(byte[] message) {
byte[] nonce = new Random().randomBytes(XSALSA20_POLY1305_SECRETBOX_NONCEBYTES);
byte[] nonce = generateRandomBytesArray(NONCE_SIZE);
return encrypt(nonce, message);
}

public byte[] encrypt(byte[] nonce, byte[] message) {
checkLength(nonce, XSALSA20_POLY1305_SECRETBOX_NONCEBYTES);
byte[] msg = org.libsodium.jni.crypto.Util.prependZeros(ZERO_BYTES, message);
byte[] ct = org.libsodium.jni.crypto.Util.zeros(msg.length);
isValid(sodium().crypto_secretbox_xsalsa20poly1305(ct, msg, msg.length,
nonce, mKey), "Encryption failed");
byte[] cipherWithoutNonce = removeZeros(BOXZERO_BYTES, ct);
public byte[] encrypt(byte[] nonce, byte[] plaintext) {
checkLength(nonce, NONCE_SIZE);

XSalsa20Engine xsalsa20 = new XSalsa20Engine();
Poly1305 poly1305 = new Poly1305();

xsalsa20.init(true, new ParametersWithIV(new KeyParameter(mKey), nonce));
byte[] subKey = new byte[SECRET_KEY_LEN];
xsalsa20.processBytes(subKey, 0, SECRET_KEY_LEN, subKey, 0);
byte[] cipherWithoutNonce = new byte[plaintext.length + poly1305.getMacSize()];
xsalsa20.processBytes(plaintext, 0, plaintext.length, cipherWithoutNonce, poly1305.getMacSize());

// hash ciphertext and prepend mac to ciphertext
poly1305.init(new KeyParameter(subKey));
poly1305.update(cipherWithoutNonce, poly1305.getMacSize(), plaintext.length);
poly1305.doFinal(cipherWithoutNonce, 0);

byte[] ciphertext = new byte[cipherWithoutNonce.length +
XSALSA20_POLY1305_SECRETBOX_NONCEBYTES];
NONCE_SIZE];
System.arraycopy(nonce, 0, ciphertext, 0, nonce.length);
System.arraycopy(cipherWithoutNonce, 0, ciphertext, nonce.length,
cipherWithoutNonce.length);
return ciphertext;
}

public byte[] decrypt(byte[] ciphertext) {
byte[] nonce = Arrays.copyOfRange(ciphertext, 0, XSALSA20_POLY1305_SECRETBOX_NONCEBYTES);
byte[] message = Arrays.copyOfRange(ciphertext, XSALSA20_POLY1305_SECRETBOX_NONCEBYTES,
byte[] nonce = Arrays.copyOfRange(ciphertext, 0, NONCE_SIZE);
byte[] message = Arrays.copyOfRange(ciphertext, NONCE_SIZE,
ciphertext.length);
return decrypt(nonce, message);
}

public byte[] decrypt(byte[] nonce, byte[] ciphertext) {
checkLength(nonce, XSALSA20_POLY1305_SECRETBOX_NONCEBYTES);
byte[] ct = org.libsodium.jni.crypto.Util.prependZeros(BOXZERO_BYTES, ciphertext);
byte[] message = Util.zeros(ct.length);
isValid(sodium().crypto_secretbox_xsalsa20poly1305_open(message, ct,
ct.length, nonce, mKey), "Decryption failed. Ciphertext failed verification");
return removeZeros(ZERO_BYTES, message);

private byte[] decrypt(byte[] nonce, byte[] ciphertext) {
checkLength(nonce, NONCE_SIZE);

XSalsa20Engine xsalsa20 = new XSalsa20Engine();
Poly1305 poly1305 = new Poly1305();

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

// hash ciphertext
poly1305.init(new KeyParameter(sk));
int len = Math.max(ciphertext.length - poly1305.getMacSize(), 0);
poly1305.update(ciphertext, poly1305.getMacSize(), len);
byte[] calculatedMAC = new byte[poly1305.getMacSize()];
poly1305.doFinal(calculatedMAC, 0);

// extract mac
final byte[] presentedMAC = new byte[poly1305.getMacSize()];
System.arraycopy(
ciphertext, 0, presentedMAC, 0, Math.min(ciphertext.length, poly1305.getMacSize()));

if (!MessageDigest.isEqual(calculatedMAC, presentedMAC)) {
throw new IllegalArgumentException("Invalid MAC");
}

byte[] plaintext = new byte[len];
xsalsa20.processBytes(ciphertext, poly1305.getMacSize(), plaintext.length, plaintext, 0);
return plaintext;
}

private void checkLength(byte[] data, int size) {
if (data == null || data.length != size)
throw new IllegalArgumentException("Invalid size: " + data.length);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,4 @@ public void testGetAuthMethod() {
CryptosignAuth auth = new CryptosignAuth(TestAuthID, TestPrivateKey);
assertEquals("cryptosign", auth.getAuthMethod());
}
}
}
32 changes: 32 additions & 0 deletions autobahn/src/test/java/xbr/network/UtilTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package xbr.network;

import static junit.framework.TestCase.assertEquals;
import static org.junit.Assert.assertNotNull;

import org.junit.Test;

import io.crossbar.autobahn.utils.Pair;

public class UtilTest {

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

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

@Test
public void testGenerateKeyPair() {
Pair<byte[], byte[]> keyPair = Util.generateX25519KeyPair();

assertNotNull(keyPair);
assertNotNull(keyPair.first);
assertNotNull(keyPair.second);

assertEquals(32, keyPair.first.length);
assertEquals(32, keyPair.second.length);
}
}
71 changes: 71 additions & 0 deletions autobahn/src/test/java/xbr/network/crypto/SecretBoxTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package xbr.network.crypto;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertThrows;
import static xbr.network.Util.NONCE_SIZE;
import static xbr.network.Util.generateRandomBytesArray;

import org.junit.Test;

public class SecretBoxTest {

@Test
public void testConstructor() {
// test with valid key
new SecretBox(new byte[32]);

// test with invalid key
assertThrows(IllegalArgumentException.class, () -> new SecretBox(new byte[16]));

// test with null key
assertThrows(NullPointerException.class, () -> new SecretBox(null));
}

@Test
public void testEncryptAndDecrypt() {
SecretBox secretBox = new SecretBox(new byte[32]);
byte[] message = "Hello, World!".getBytes();
byte[] encrypted = secretBox.encrypt(message);
byte[] decrypted = secretBox.decrypt(encrypted);
assertArrayEquals(message, decrypted);
}

@Test
public void testEncryptAndDecryptWithNonce() {
SecretBox secretBox = new SecretBox(new byte[32]);
byte[] nonce = generateRandomBytesArray(NONCE_SIZE);
byte[] message = "Hello, World!".getBytes();
byte[] encrypted = secretBox.encrypt(nonce, message);
byte[] decrypted = secretBox.decrypt(encrypted);
assertArrayEquals(message, decrypted);
}

@Test
public void testEncryptAndDecryptWithInvalidMAC() {
SecretBox secretBox = new SecretBox(new byte[32]);
byte[] message = "Hello, World!".getBytes();
byte[] encrypted = secretBox.encrypt(message);
encrypted[encrypted.length - 1] ^= 0xFF; // Modify last byte
assertThrows(IllegalArgumentException.class, () -> secretBox.decrypt(encrypted));
}

@Test
public void testEncryptAndDecryptWithInvalidNonce() {
SecretBox secretBox = new SecretBox(new byte[32]);
byte[] message = "Hello, World!".getBytes();
byte[] encrypted = secretBox.encrypt(message);
encrypted[0] ^= 0xFF; // Modify first byte
assertThrows(IllegalArgumentException.class, () -> secretBox.decrypt(encrypted));
}

@Test
public void testEncryptAndDecryptWithModifiedCiphertext() {
byte[] key = new byte[32];
SecretBox secretBox = new SecretBox(key);
byte[] message = "Hello, World!".getBytes();
byte[] encrypted = secretBox.encrypt(message);
encrypted[NONCE_SIZE + 1] ^= 0xFF; // Modify the byte next to nonce
assertThrows(IllegalArgumentException.class, () -> secretBox.decrypt(encrypted));
}

}

0 comments on commit 0f1c292

Please sign in to comment.