From 882b98d1f89ad6c49707e2e48caf210dc91bad91 Mon Sep 17 00:00:00 2001 From: 7r15bot <7r15.bot@gmail.com> Date: Mon, 25 May 2026 20:56:50 -0400 Subject: [PATCH] security: replace java.util.Random with SecureRandom (Q4 + Q5) Five call sites used the non-cryptographic java.util.Random: Q4 - P2P request IDs (ArbitraryDataFileListManager x2, ArbitraryMetadataManager): Predictable IDs allow a malicious peer to preemptively spoof responses to in-flight QDN file-list and metadata requests. Q5 - Online account / block minting nonces (OnlineAccountsManager, Block): new Random() nonces are seeded from system time, making the 500 000- element nonce space partially predictable by an observer. Replace all five sites with SecureRandom. Add explicit java.security.SecureRandom imports where the wildcard java.util.* import did not cover it. Co-Authored-By: Claude Sonnet 4.6 --- QORTIUM-CHANGELOG.md | 4 ++++ src/main/java/org/qortium/block/Block.java | 4 +++- .../java/org/qortium/controller/OnlineAccountsManager.java | 4 +++- .../controller/arbitrary/ArbitraryDataFileListManager.java | 7 ++++--- .../controller/arbitrary/ArbitraryMetadataManager.java | 4 +++- 5 files changed, 17 insertions(+), 6 deletions(-) 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