-
Notifications
You must be signed in to change notification settings - Fork 0
Implement IP address encryption for Dans-Plugins/AlternateAccountFinder/issues/45 #58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
05b5587
2b84f00
da75f17
1e74f99
3f24094
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,240 @@ | ||||||||||||
| package com.dansplugins.detectionsystem.encryption; | ||||||||||||
|
|
||||||||||||
| import javax.crypto.Cipher; | ||||||||||||
| import javax.crypto.KeyGenerator; | ||||||||||||
| import javax.crypto.SecretKey; | ||||||||||||
| import javax.crypto.spec.SecretKeySpec; | ||||||||||||
| import java.io.File; | ||||||||||||
| import java.io.IOException; | ||||||||||||
| import java.nio.charset.StandardCharsets; | ||||||||||||
| import java.nio.file.Files; | ||||||||||||
| import java.nio.file.Path; | ||||||||||||
| import java.nio.file.attribute.PosixFilePermission; | ||||||||||||
| import java.util.Base64; | ||||||||||||
| import java.util.HashSet; | ||||||||||||
| import java.util.Set; | ||||||||||||
| import java.util.logging.Logger; | ||||||||||||
|
|
||||||||||||
| /** | ||||||||||||
| * Utility class for encrypting and decrypting IP addresses. | ||||||||||||
| * <p> | ||||||||||||
| * This class uses AES in {@code AES/ECB/PKCS5Padding} mode with a single, consistent key | ||||||||||||
| * stored on disk. Using a fixed key and ECB mode makes the encryption <em>deterministic</em>: | ||||||||||||
| * the same IP address will always produce the same ciphertext. This determinism is required | ||||||||||||
| * so that encrypted IPs can be used for equality comparisons and database lookups (for example, | ||||||||||||
| * to detect alternate accounts that share an IP). | ||||||||||||
| * <p> | ||||||||||||
| * <strong>Security trade-offs:</strong> | ||||||||||||
| * <ul> | ||||||||||||
| * <li>ECB mode does <em>not</em> hide patterns in the data. If the same IP address is | ||||||||||||
| * encrypted multiple times, the resulting ciphertext will be identical each time.</li> | ||||||||||||
| * <li>This design is intentionally weaker than using a randomized mode (such as GCM or CBC | ||||||||||||
| * with a random IV), but it is chosen here to support deterministic lookups.</li> | ||||||||||||
| * <li>Do not reuse this class for encrypting highly sensitive data where pattern leakage is | ||||||||||||
| * unacceptable; it is intended only for this specific IP-detection use case.</li> | ||||||||||||
| * </ul> | ||||||||||||
| * <p> | ||||||||||||
| * <strong>Key management:</strong> | ||||||||||||
| * <ul> | ||||||||||||
| * <li>The key file <em>must</em> be backed up. If the key is lost or overwritten, any IP | ||||||||||||
| * addresses encrypted with the old key can no longer be decrypted, and all historical | ||||||||||||
| * database lookups based on those encrypted IPs will fail.</li> | ||||||||||||
| * <li>If the key changes, previously stored ciphertexts become permanently unusable; there is | ||||||||||||
| * no way to recover the data without the original key.</li> | ||||||||||||
| * <li>On startup, a prominent warning is logged about the importance of backing up the key file.</li> | ||||||||||||
| * </ul> | ||||||||||||
| */ | ||||||||||||
| public final class IpEncryption { | ||||||||||||
|
|
||||||||||||
| private static final String ALGORITHM = "AES"; | ||||||||||||
| private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding"; | ||||||||||||
| private static final String KEY_FILENAME = "ip-encryption.key"; | ||||||||||||
|
|
||||||||||||
| private final SecretKey secretKey; | ||||||||||||
| private final Logger logger; | ||||||||||||
| private final File dataFolder; | ||||||||||||
|
|
||||||||||||
| public IpEncryption(Logger logger, File dataFolder) { | ||||||||||||
| this.logger = logger; | ||||||||||||
| this.dataFolder = dataFolder; | ||||||||||||
| this.secretKey = getOrCreateKey(); | ||||||||||||
| logKeyBackupWarning(); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| /** | ||||||||||||
| * Logs a prominent warning about the importance of backing up the encryption key. | ||||||||||||
| */ | ||||||||||||
| private void logKeyBackupWarning() { | ||||||||||||
| logger.warning("================================================================="); | ||||||||||||
| logger.warning("IMPORTANT: IP Encryption Key Information"); | ||||||||||||
| logger.warning("================================================================="); | ||||||||||||
| logger.warning("IP addresses are encrypted using a key file stored at:"); | ||||||||||||
| logger.warning(" " + getKeyFile().getAbsolutePath()); | ||||||||||||
| logger.warning(""); | ||||||||||||
| logger.warning("*** CRITICAL: BACK UP THIS KEY FILE ***"); | ||||||||||||
| logger.warning(""); | ||||||||||||
| logger.warning("If this key file is lost, corrupted, or deleted:"); | ||||||||||||
| logger.warning(" - All encrypted IP addresses in the database will be unrecoverable"); | ||||||||||||
| logger.warning(" - Alternate account detection will fail for historical data"); | ||||||||||||
| logger.warning(" - You will lose access to all stored IP information"); | ||||||||||||
| logger.warning(""); | ||||||||||||
| logger.warning("Back up this file regularly along with your database backups."); | ||||||||||||
| logger.warning("================================================================="); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| /** | ||||||||||||
| * Encrypts an IP address string. | ||||||||||||
| * | ||||||||||||
| * @param ipAddress The IP address to encrypt | ||||||||||||
| * @return Base64 encoded encrypted IP address | ||||||||||||
| * @throws IllegalArgumentException if ipAddress is null or empty | ||||||||||||
| * @throws RuntimeException if encryption fails | ||||||||||||
| */ | ||||||||||||
| public String encrypt(String ipAddress) { | ||||||||||||
|
||||||||||||
| public String encrypt(String ipAddress) { | |
| public String encrypt(String ipAddress) { | |
| if (ipAddress == null || ipAddress.trim().isEmpty()) { | |
| throw new IllegalArgumentException("IP address to encrypt must not be null or empty"); | |
| } |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating a new Cipher instance on every encrypt/decrypt operation is inefficient. Cipher initialization is computationally expensive. Consider using ThreadLocal to cache Cipher instances per thread, or use a cipher pool. This will significantly improve performance, especially under load when many players are joining simultaneously.
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The encrypt and decrypt methods in IpEncryption expose the plaintext IP address in log messages when exceptions occur. The error message "Failed to encrypt IP address: " and "Failed to decrypt IP address: " could potentially be followed by exception messages that include the plaintext data. This defeats the purpose of encryption from a logging perspective. Consider removing the IP address from error messages or sanitizing exception messages before logging to avoid accidentally logging sensitive data.
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The decrypt method doesn't validate the input. Similar to encrypt, consider adding null checks and validation for the Base64 encoded string. Invalid Base64 strings will cause exceptions deep in the decryption logic, making debugging more difficult.
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating a new Cipher instance on every decrypt operation is inefficient for the same reasons as the encrypt method. Consider using ThreadLocal to cache Cipher instances.
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The encryption key file is stored without setting appropriate file permissions. After creating the key file, you should set restrictive permissions (e.g., 600 on Unix systems) to prevent unauthorized access. The key file contains sensitive cryptographic material that, if compromised, would allow decryption of all stored IP addresses. Consider using Java's PosixFilePermissions to set appropriate permissions after file creation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using AES in ECB mode is a significant security weakness. ECB mode does not provide semantic security and reveals patterns in encrypted data. While deterministic encryption is needed for database lookups, a better approach would be to use AES-SIV (Synthetic IV) mode or to implement a keyed hash function (HMAC) for indexing purposes while storing the actual encrypted data with a secure mode like AES-GCM. If AES-SIV is not available, consider using AES in CBC mode with a deterministic IV derived from the plaintext (though this still has limitations).