static random fields should not be used with graalvm as they are seeded at compile time and thus not random anymore

This commit is contained in:
Athou
2026-06-13 12:03:50 +02:00
parent 1388fba8e4
commit 00d7be735a
3 changed files with 29 additions and 16 deletions

View File

@@ -4,6 +4,7 @@ import jakarta.enterprise.event.Observes;
import jakarta.inject.Singleton; import jakarta.inject.Singleton;
import com.commafeed.backend.feed.FeedRefreshEngine; import com.commafeed.backend.feed.FeedRefreshEngine;
import com.commafeed.backend.feed.ImageProxyUrl;
import com.commafeed.backend.task.TaskScheduler; import com.commafeed.backend.task.TaskScheduler;
import com.commafeed.security.password.PasswordConstraintValidator; import com.commafeed.security.password.PasswordConstraintValidator;
@@ -26,6 +27,10 @@ public class CommaFeedApplication {
PasswordConstraintValidator.setMinimumPasswordLength(config.users().minimumPasswordLength()); PasswordConstraintValidator.setMinimumPasswordLength(config.users().minimumPasswordLength());
if (config.imageProxyEnabled()) {
ImageProxyUrl.generateKey();
}
feedRefreshEngine.start(); feedRefreshEngine.start();
taskScheduler.start(); taskScheduler.start();
} }

View File

@@ -1,14 +1,15 @@
package com.commafeed.backend.feed; package com.commafeed.backend.feed;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.lang3.RandomUtils;
import com.google.common.primitives.Bytes; import com.google.common.primitives.Bytes;
@@ -17,28 +18,25 @@ import lombok.experimental.UtilityClass;
@UtilityClass @UtilityClass
public class ImageProxyUrl { public class ImageProxyUrl {
private static final SecretKey KEY;
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
private static final int GCM_IV_LENGTH = 12; private static final int GCM_IV_LENGTH = 12;
private static final int GCM_TAG_LENGTH = 128; private static final int GCM_TAG_LENGTH = 128;
static { private static SecretKey key;
try {
KeyGenerator keyGen = KeyGenerator.getInstance("AES"); public static void generateKey() {
keyGen.init(256, SECURE_RANDOM); key = new SecretKeySpec(RandomUtils.secure().randomBytes(32), "AES");
KEY = keyGen.generateKey();
} catch (Exception e) {
throw new IllegalStateException("Failed to generate AES key", e);
}
} }
public static String encode(String url) { public static String encode(String url) {
if (key == null) {
throw new IllegalStateException("Key not initialized");
}
try { try {
byte[] iv = new byte[GCM_IV_LENGTH]; byte[] iv = RandomUtils.secure().randomBytes(GCM_IV_LENGTH);
SECURE_RANDOM.nextBytes(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, KEY, new GCMParameterSpec(GCM_TAG_LENGTH, iv)); cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(GCM_TAG_LENGTH, iv));
byte[] encrypted = cipher.doFinal(url.getBytes(StandardCharsets.UTF_8)); byte[] encrypted = cipher.doFinal(url.getBytes(StandardCharsets.UTF_8));
byte[] combined = Bytes.concat(iv, encrypted); byte[] combined = Bytes.concat(iv, encrypted);
@@ -49,6 +47,10 @@ public class ImageProxyUrl {
} }
public static String decode(String code) { public static String decode(String code) {
if (key == null) {
throw new IllegalStateException("Key not initialized");
}
try { try {
byte[] combined = Base64.getUrlDecoder().decode(code); byte[] combined = Base64.getUrlDecoder().decode(code);
@@ -56,7 +58,7 @@ public class ImageProxyUrl {
byte[] encrypted = Arrays.copyOfRange(combined, GCM_IV_LENGTH, combined.length); byte[] encrypted = Arrays.copyOfRange(combined, GCM_IV_LENGTH, combined.length);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, KEY, new GCMParameterSpec(GCM_TAG_LENGTH, iv)); cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(GCM_TAG_LENGTH, iv));
byte[] decrypted = cipher.doFinal(encrypted); byte[] decrypted = cipher.doFinal(encrypted);
return new String(decrypted, StandardCharsets.UTF_8); return new String(decrypted, StandardCharsets.UTF_8);

View File

@@ -1,12 +1,18 @@
package com.commafeed.backend.feed; package com.commafeed.backend.feed;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;
class ImageProxyUrlTest { class ImageProxyUrlTest {
@BeforeAll
static void init() {
ImageProxyUrl.generateKey();
}
@ParameterizedTest @ParameterizedTest
@ValueSource( @ValueSource(
strings = { strings = {