diff --git a/commafeed-server/src/main/java/com/commafeed/backend/feed/ImageProxyUrl.java b/commafeed-server/src/main/java/com/commafeed/backend/feed/ImageProxyUrl.java index 682cf22e..27feb785 100644 --- a/commafeed-server/src/main/java/com/commafeed/backend/feed/ImageProxyUrl.java +++ b/commafeed-server/src/main/java/com/commafeed/backend/feed/ImageProxyUrl.java @@ -1,37 +1,68 @@ package com.commafeed.backend.feed; -import org.apache.hc.client5.http.utils.Base64; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Base64; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; + +import com.google.common.primitives.Bytes; import lombok.experimental.UtilityClass; @UtilityClass 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_TAG_LENGTH = 128; + + static { + try { + KeyGenerator keyGen = KeyGenerator.getInstance("AES"); + keyGen.init(256, SECURE_RANDOM); + KEY = keyGen.generateKey(); + } catch (Exception e) { + throw new IllegalStateException("Failed to generate AES key", e); + } + } + public static String encode(String url) { - return Base64.encodeBase64String(rot13(url).getBytes()); + try { + byte[] iv = new byte[GCM_IV_LENGTH]; + SECURE_RANDOM.nextBytes(iv); + + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + cipher.init(Cipher.ENCRYPT_MODE, KEY, new GCMParameterSpec(GCM_TAG_LENGTH, iv)); + byte[] encrypted = cipher.doFinal(url.getBytes(StandardCharsets.UTF_8)); + + byte[] combined = Bytes.concat(iv, encrypted); + return Base64.getUrlEncoder().withoutPadding().encodeToString(combined); + } catch (Exception e) { + throw new IllegalStateException("Failed to encode URL", e); + } } public static String decode(String code) { - return rot13(new String(Base64.decodeBase64(code))); - } + try { + byte[] combined = Base64.getUrlDecoder().decode(code); - private static String rot13(String msg) { - StringBuilder message = new StringBuilder(); + byte[] iv = Arrays.copyOfRange(combined, 0, GCM_IV_LENGTH); + byte[] encrypted = Arrays.copyOfRange(combined, GCM_IV_LENGTH, combined.length); - for (char c : msg.toCharArray()) { - if (c >= 'a' && c <= 'm') { - c += 13; - } else if (c >= 'n' && c <= 'z') { - c -= 13; - } else if (c >= 'A' && c <= 'M') { - c += 13; - } else if (c >= 'N' && c <= 'Z') { - c -= 13; - } - message.append(c); + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + cipher.init(Cipher.DECRYPT_MODE, KEY, new GCMParameterSpec(GCM_TAG_LENGTH, iv)); + byte[] decrypted = cipher.doFinal(encrypted); + + return new String(decrypted, StandardCharsets.UTF_8); + } catch (Exception e) { + throw new IllegalStateException("Failed to decode URL", e); } - - return message.toString(); } }