diff --git a/QORTIUM-CHANGELOG.md b/QORTIUM-CHANGELOG.md index 717d08e4f..d31ea9c08 100644 --- a/QORTIUM-CHANGELOG.md +++ b/QORTIUM-CHANGELOG.md @@ -34,6 +34,10 @@ own chain. ## Change Entries +### 2026-05-28 - security: replace java.util.Random with SecureRandom (Q4 + Q5) + +Replaced predictable random number generation in QDN request IDs and online-account nonce setup with shared secure random generators. This makes peer-facing request identifiers and minting-related nonce starting points harder to predict without changing the surrounding transaction, block, or QDN behavior. + ### 2026-05-28 - security: validate backup name before SQL string interpolation Added a strict backup-name check before the repository builds its HSQLDB backup command. Current backup callers already use fixed safe names, but this prevents future code from passing names with quotes, path separators, or other unsafe characters into a database command or backup path. diff --git a/src/main/java/org/qortium/block/Block.java b/src/main/java/org/qortium/block/Block.java index e4ca61144..d9f4ea3fb 100644 --- a/src/main/java/org/qortium/block/Block.java +++ b/src/main/java/org/qortium/block/Block.java @@ -53,6 +53,7 @@ import java.math.BigInteger; import java.math.RoundingMode; import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.*; @@ -114,6 +115,7 @@ public static ValidationResult valueOf(int value) { // Other properties private static final Logger LOGGER = LogManager.getLogger(Block.class); + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); public static final int CURRENT_VERSION = 1; @@ -434,7 +436,7 @@ else if (isOnlineAccountsBlock(height)) { if (onlineAccounts.isEmpty()) { // new v5.1.0, don't fail (25 blocks before payout) when isSingleNodeTestnet == true if (Settings.getInstance().isSingleNodeTestnet()) { - Integer nonce = new Random().nextInt(500000); + Integer nonce = SECURE_RANDOM.nextInt(500000); byte[] timestampBytes = Longs.toByteArray(onlineAccountsTimestamp); byte[] signature = Ed25519Extras.signForAggregation(minter.getPrivateKey(), timestampBytes); byte[] publicKey = minter.getPublicKey(); diff --git a/src/main/java/org/qortium/controller/OnlineAccountsManager.java b/src/main/java/org/qortium/controller/OnlineAccountsManager.java index 287f17cac..da2509d9c 100644 --- a/src/main/java/org/qortium/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortium/controller/OnlineAccountsManager.java @@ -31,6 +31,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.security.SecureRandom; import java.util.*; import java.util.concurrent.*; import java.util.stream.Collectors; @@ -38,6 +39,7 @@ public class OnlineAccountsManager { private static final Logger LOGGER = LogManager.getLogger(OnlineAccountsManager.class); + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); // 'Current' as in 'now' @@ -187,7 +189,7 @@ public void ensureTestingAccountsOnline(PrivateKeyAccount... onlineAccounts) { byte[] signature = Ed25519Extras.signForAggregation(onlineAccount.getPrivateKey(), timestampBytes); byte[] publicKey = onlineAccount.getPublicKey(); - Integer nonce = new Random().nextInt(500000); + Integer nonce = SECURE_RANDOM.nextInt(500000); OnlineAccountData ourOnlineAccountData = new OnlineAccountData(onlineAccountsTimestamp, signature, publicKey, nonce); replacementAccounts.add(ourOnlineAccountData); diff --git a/src/main/java/org/qortium/controller/arbitrary/ArbitraryDataFileListManager.java b/src/main/java/org/qortium/controller/arbitrary/ArbitraryDataFileListManager.java index 05a9f7483..0efa63786 100644 --- a/src/main/java/org/qortium/controller/arbitrary/ArbitraryDataFileListManager.java +++ b/src/main/java/org/qortium/controller/arbitrary/ArbitraryDataFileListManager.java @@ -4,6 +4,7 @@ import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; +import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -12,7 +13,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Random; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; @@ -50,6 +50,7 @@ public class ArbitraryDataFileListManager { private static final Logger LOGGER = LogManager.getLogger(ArbitraryDataFileListManager.class); + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); private static ArbitraryDataFileListManager instance; @@ -419,7 +420,7 @@ public boolean fetchArbitraryDataFileList(ArbitraryTransactionData arbitraryTran // Assign random ID to this message int id; do { - id = new Random().nextInt(Integer.MAX_VALUE - 1) + 1; + id = SECURE_RANDOM.nextInt(Integer.MAX_VALUE - 1) + 1; // Put queue into map (keyed by message ID) so we can poll for a response // If putIfAbsent() doesn't return null, then this ID is already taken @@ -477,7 +478,7 @@ public boolean fetchArbitraryDataFileList(Peer peer, byte[] signature) { // Assign random ID to this message int id; do { - id = new Random().nextInt(Integer.MAX_VALUE - 1) + 1; + id = SECURE_RANDOM.nextInt(Integer.MAX_VALUE - 1) + 1; // Put queue into map (keyed by message ID) so we can poll for a response // If putIfAbsent() doesn't return null, then this ID is already taken diff --git a/src/main/java/org/qortium/controller/arbitrary/ArbitraryMetadataManager.java b/src/main/java/org/qortium/controller/arbitrary/ArbitraryMetadataManager.java index aaaa0ec2c..020047f72 100644 --- a/src/main/java/org/qortium/controller/arbitrary/ArbitraryMetadataManager.java +++ b/src/main/java/org/qortium/controller/arbitrary/ArbitraryMetadataManager.java @@ -24,6 +24,7 @@ import org.qortium.utils.Triple; import java.io.IOException; +import java.security.SecureRandom; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; @@ -37,6 +38,7 @@ public class ArbitraryMetadataManager { private static final Logger LOGGER = LogManager.getLogger(ArbitraryMetadataManager.class); + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); private static ArbitraryMetadataManager instance; @@ -286,7 +288,7 @@ private byte[] fetchArbitraryMetadata(byte[] signature, byte[] metadataHash, boo // Assign random ID to this message int id; do { - id = new Random().nextInt(Integer.MAX_VALUE - 1) + 1; + id = SECURE_RANDOM.nextInt(Integer.MAX_VALUE - 1) + 1; // Put queue into map (keyed by message ID) so we can poll for a response // If putIfAbsent() doesn't return null, then this ID is already taken