Rewrites encryption
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
Adds an optional real encryption mode, which encrypts pages using AES, without saving plaintext. Re-implements the old magic encryption in non-real encryption mode. Fixes incorrect key generation for use in the substitution cipher and the gene cipher. Removes the option for saving books as txt. Adds tests for all encryption methods. Saves all necessary decryption data when storing encrypted books. Removes the old book updating code.
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -20,7 +20,7 @@
|
||||
|
||||
<description>A continuation of the original Books Without Borders</description>
|
||||
<properties>
|
||||
<java.version>16</java.version>
|
||||
<java.version>17</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
|
@@ -59,8 +59,11 @@ public class CommandDecrypt implements TabExecutor {
|
||||
}
|
||||
|
||||
//Decrypt the book normally
|
||||
String key = EncryptionHelper.getNumberKeyFromStringKey(arguments[0]);
|
||||
ItemStack book = EncryptionHelper.loadEncryptedBook(player, key, true);
|
||||
ItemStack book = EncryptionHelper.loadEncryptedBook(player, arguments[0], true, false);
|
||||
if (book == null) {
|
||||
book = EncryptionHelper.loadEncryptedBookLegacy(player, arguments[0], true);
|
||||
}
|
||||
|
||||
if (book != null) {
|
||||
InventoryHelper.setHeldWrittenBook(player, book);
|
||||
stringFormatter.displaySuccessMessage(player, Translatable.SUCCESS_DECRYPTED);
|
||||
@@ -98,7 +101,10 @@ public class CommandDecrypt implements TabExecutor {
|
||||
|
||||
if (!key.equalsIgnoreCase("")) {
|
||||
//Decrypt the book
|
||||
ItemStack book = EncryptionHelper.loadEncryptedBook(player, key, false);
|
||||
ItemStack book = EncryptionHelper.loadEncryptedBook(player, key, false, true);
|
||||
if (book == null) {
|
||||
book = EncryptionHelper.loadEncryptedBookLegacy(player, key, false);
|
||||
}
|
||||
if (book != null) {
|
||||
InventoryHelper.setHeldWrittenBook(player, book);
|
||||
stringFormatter.displaySuccessMessage(player, Translatable.SUCCESS_AUTO_DECRYPTED);
|
||||
|
@@ -1,12 +1,15 @@
|
||||
package net.knarcraft.bookswithoutborders.command;
|
||||
|
||||
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
|
||||
import net.knarcraft.bookswithoutborders.config.BwBCommand;
|
||||
import net.knarcraft.bookswithoutborders.config.Translatable;
|
||||
import net.knarcraft.bookswithoutborders.gui.PagedBookIndex;
|
||||
import net.knarcraft.bookswithoutborders.state.BookDirectory;
|
||||
import net.knarcraft.bookswithoutborders.utility.BookFileHelper;
|
||||
import net.knarcraft.bookswithoutborders.utility.BookHelper;
|
||||
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
|
||||
import net.knarcraft.bookswithoutborders.utility.TabCompletionTypeHelper;
|
||||
import net.knarcraft.knarlib.formatting.StringFormatter;
|
||||
import net.knarcraft.knarlib.util.TabCompletionHelper;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@@ -26,7 +29,7 @@ public class CommandDelete implements TabExecutor {
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
|
||||
@NotNull String[] arguments) {
|
||||
if (!(sender instanceof Player)) {
|
||||
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!");
|
||||
BooksWithoutBorders.getStringFormatter().displayErrorMessage(sender, Translatable.ERROR_PLAYER_ONLY);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -42,20 +45,16 @@ public class CommandDelete implements TabExecutor {
|
||||
* @return <p>True if the book was deleted successfully</p>
|
||||
*/
|
||||
protected boolean deleteBook(@NotNull CommandSender sender, @NotNull String[] arguments, boolean deletePublic) {
|
||||
String command = deletePublic ? "deletepublicbook" : "deletebook";
|
||||
String command = deletePublic ? BwBCommand.DELETE_PUBLIC_BOOK.toString().toLowerCase() :
|
||||
BwBCommand.DELETE_BOOK.toString().toLowerCase();
|
||||
if (PagedBookIndex.displayPage(arguments, sender, deletePublic, command)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (arguments.length < 1) {
|
||||
BooksWithoutBorders.sendErrorMessage(sender, "Incorrect number of arguments for this command!");
|
||||
return false;
|
||||
}
|
||||
|
||||
//Delete the file
|
||||
List<String> availableBooks = BooksWithoutBorders.getAvailableBooks(sender, deletePublic);
|
||||
if (availableBooks.isEmpty()) {
|
||||
BooksWithoutBorders.sendErrorMessage(sender, "No files available to delete!");
|
||||
BooksWithoutBorders.getStringFormatter().displayErrorMessage(sender, Translatable.ERROR_DELETE_EMPTY);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -73,6 +72,7 @@ public class CommandDelete implements TabExecutor {
|
||||
* @param isPublic <p>Whether the book to delete is public or not</p>
|
||||
*/
|
||||
public void performBookDeletion(@NotNull CommandSender sender, @NotNull String fileName, @NotNull Boolean isPublic) {
|
||||
StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter();
|
||||
//If the file name is an index of the load list, load the book
|
||||
try {
|
||||
int loadListIndex = Integer.parseInt(fileName);
|
||||
@@ -90,19 +90,20 @@ public class CommandDelete implements TabExecutor {
|
||||
|
||||
//Send message if no such file could be found
|
||||
if (file == null) {
|
||||
BooksWithoutBorders.sendErrorMessage(sender, "Incorrect file name!");
|
||||
stringFormatter.displayErrorMessage(sender, Translatable.ERROR_INCORRECT_FILE_NAME);
|
||||
return;
|
||||
}
|
||||
|
||||
//Try to delete the file
|
||||
try {
|
||||
if (file.delete()) {
|
||||
BooksWithoutBorders.sendSuccessMessage(sender, "\"" + fileName + "\" deleted successfully");
|
||||
stringFormatter.displaySuccessMessage(sender,
|
||||
stringFormatter.replacePlaceholder(Translatable.SUCCESS_DELETED, "{file}", fileName));
|
||||
} else {
|
||||
BooksWithoutBorders.sendErrorMessage(sender, "Deletion failed without an exception!");
|
||||
stringFormatter.displayErrorMessage(sender, Translatable.ERROR_DELETE_FAILED_SILENT);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
BooksWithoutBorders.sendErrorMessage(sender, "Deletion failed!");
|
||||
stringFormatter.displayErrorMessage(sender, Translatable.ERROR_DELETE_FAILED_EXCEPTION);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -13,12 +13,14 @@ import java.util.List;
|
||||
public class CommandDeletePublic extends CommandDelete implements TabExecutor {
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] arguments) {
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
|
||||
@NotNull String[] arguments) {
|
||||
return deleteBook(sender, arguments, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] arguments) {
|
||||
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
|
||||
@NotNull String[] arguments) {
|
||||
return doTabCompletion(sender, arguments, true);
|
||||
}
|
||||
|
||||
|
@@ -1,10 +1,12 @@
|
||||
package net.knarcraft.bookswithoutborders.command;
|
||||
|
||||
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
|
||||
import net.knarcraft.bookswithoutborders.state.EncryptionStyle;
|
||||
import net.knarcraft.bookswithoutborders.config.Translatable;
|
||||
import net.knarcraft.bookswithoutborders.encryption.EncryptionStyle;
|
||||
import net.knarcraft.bookswithoutborders.state.ItemSlot;
|
||||
import net.knarcraft.bookswithoutborders.utility.EncryptionHelper;
|
||||
import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
|
||||
import net.knarcraft.knarlib.formatting.StringFormatter;
|
||||
import net.knarcraft.knarlib.util.TabCompletionHelper;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@@ -26,11 +28,19 @@ public class CommandEncrypt implements TabExecutor {
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
|
||||
@NotNull String[] arguments) {
|
||||
if (performPreChecks(sender, arguments, 1, "You must specify a key to encrypt a book!") == null) {
|
||||
StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter();
|
||||
if (performPreChecks(sender, arguments, 1,
|
||||
stringFormatter.getUnFormattedColoredMessage(Translatable.ERROR_ENCRYPT_NO_KEY)) == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EncryptionStyle encryptionStyle = arguments.length == 2 ? EncryptionStyle.getFromString(arguments[1]) : EncryptionStyle.SUBSTITUTION;
|
||||
|
||||
// AES is the only reliable method for retaining the plaintext
|
||||
if (BooksWithoutBorders.getConfiguration().useRealEncryption()) {
|
||||
encryptionStyle = EncryptionStyle.AES;
|
||||
}
|
||||
|
||||
return encryptBook(encryptionStyle, (Player) sender, arguments[0], "");
|
||||
}
|
||||
|
||||
@@ -137,6 +147,9 @@ public class CommandEncrypt implements TabExecutor {
|
||||
if (argumentsCount == 1) {
|
||||
return List.of("<password>");
|
||||
} else if (argumentsCount == 2) {
|
||||
if (BooksWithoutBorders.getConfiguration().useRealEncryption()) {
|
||||
return List.of();
|
||||
}
|
||||
return TabCompletionHelper.filterMatchingStartsWith(encryptionStyles, args[1]);
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package net.knarcraft.bookswithoutborders.command;
|
||||
|
||||
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
|
||||
import net.knarcraft.bookswithoutborders.state.EncryptionStyle;
|
||||
import net.knarcraft.bookswithoutborders.encryption.EncryptionStyle;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabExecutor;
|
||||
|
@@ -145,11 +145,7 @@ public class CommandSave implements TabExecutor {
|
||||
}
|
||||
|
||||
try {
|
||||
if (config.getUseYml()) {
|
||||
BookToFromTextHelper.bookToYml(savePath, fileName, book);
|
||||
} else {
|
||||
BookToFromTextHelper.bookToTXT(savePath, fileName, book);
|
||||
}
|
||||
BookToFromTextHelper.bookToYml(savePath, fileName, book);
|
||||
|
||||
//Update the relevant book list
|
||||
BooksWithoutBorders.updateBooks(player, saveToPublicFolder);
|
||||
|
@@ -38,11 +38,11 @@ public class BooksWithoutBordersConfig {
|
||||
private boolean authorOnlyCopy;
|
||||
private boolean authorOnlyUnsign;
|
||||
private boolean authorOnlySave;
|
||||
private boolean useYml;
|
||||
private boolean adminDecrypt;
|
||||
private boolean formatBooks;
|
||||
private boolean changeGenerationOnCopy;
|
||||
private boolean enableBookshelfPeeking;
|
||||
private boolean useRealEncryption;
|
||||
|
||||
private final Translator translator;
|
||||
private EconomyManager economyManager;
|
||||
@@ -153,15 +153,6 @@ public class BooksWithoutBordersConfig {
|
||||
return this.enableBookshelfPeeking;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether to use YML, not TXT, for saving books
|
||||
*
|
||||
* @return <p>Whether to use YML for saving books</p>
|
||||
*/
|
||||
public boolean getUseYml() {
|
||||
return this.useYml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether admins should be able to decrypt books without a password, and decrypt all group encrypted books
|
||||
*
|
||||
@@ -285,6 +276,15 @@ public class BooksWithoutBordersConfig {
|
||||
return (this.bookPriceType != null && this.bookPriceQuantity > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether to use real encryption for encrypted books
|
||||
*
|
||||
* @return <p>True if real encryption should be used</p>
|
||||
*/
|
||||
public boolean useRealEncryption() {
|
||||
return this.useRealEncryption;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path used to store encrypted books
|
||||
*
|
||||
@@ -301,7 +301,6 @@ public class BooksWithoutBordersConfig {
|
||||
public void saveConfigValues() {
|
||||
Logger logger = BooksWithoutBorders.getInstance().getLogger();
|
||||
Configuration config = BooksWithoutBorders.getInstance().getConfig();
|
||||
config.set(ConfigOption.USE_YAML.getConfigNode(), this.useYml);
|
||||
config.set(ConfigOption.MAX_DUPLICATES.getConfigNode(), this.bookDuplicateLimit);
|
||||
config.set(ConfigOption.TITLE_AUTHOR_SEPARATOR.getConfigNode(), this.titleAuthorSeparator);
|
||||
config.set(ConfigOption.LORE_LINE_SEPARATOR.getConfigNode(), this.loreSeparator);
|
||||
@@ -309,6 +308,7 @@ public class BooksWithoutBordersConfig {
|
||||
config.set(ConfigOption.MESSAGE_FOR_NEW_PLAYERS.getConfigNode(), this.welcomeMessage);
|
||||
config.set(ConfigOption.FORMAT_AFTER_SIGNING.getConfigNode(), this.formatBooks);
|
||||
config.set(ConfigOption.ENABLE_BOOKSHELF_PEEKING.getConfigNode(), this.enableBookshelfPeeking);
|
||||
config.set(ConfigOption.USE_REAL_ENCRYPTION.getConfigNode(), this.useRealEncryption);
|
||||
|
||||
String itemTypeNode = ConfigOption.PRICE_ITEM_TYPE.getConfigNode();
|
||||
if (this.bookPriceType != null) {
|
||||
@@ -354,7 +354,6 @@ public class BooksWithoutBordersConfig {
|
||||
BooksWithoutBorders.getInstance().reloadConfig();
|
||||
Configuration config = BooksWithoutBorders.getInstance().getConfig();
|
||||
try {
|
||||
this.useYml = getBoolean(config, ConfigOption.USE_YAML);
|
||||
this.bookDuplicateLimit = config.getInt(ConfigOption.MAX_DUPLICATES.getConfigNode(),
|
||||
(Integer) ConfigOption.MAX_DUPLICATES.getDefaultValue());
|
||||
this.titleAuthorSeparator = getString(config, ConfigOption.TITLE_AUTHOR_SEPARATOR);
|
||||
@@ -368,6 +367,7 @@ public class BooksWithoutBordersConfig {
|
||||
this.formatBooks = getBoolean(config, ConfigOption.FORMAT_AFTER_SIGNING);
|
||||
this.changeGenerationOnCopy = getBoolean(config, ConfigOption.CHANGE_GENERATION_ON_COPY);
|
||||
this.enableBookshelfPeeking = getBoolean(config, ConfigOption.ENABLE_BOOKSHELF_PEEKING);
|
||||
this.useRealEncryption = getBoolean(config, ConfigOption.USE_REAL_ENCRYPTION);
|
||||
String language = config.getString("language", "en");
|
||||
this.translator.loadLanguages(BooksWithoutBorders.getInstance().getDataFolder(), "en", language);
|
||||
|
||||
|
@@ -7,11 +7,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
*/
|
||||
public enum ConfigOption {
|
||||
|
||||
/**
|
||||
* Whether YAML should be used to store books instead of simple text files
|
||||
*/
|
||||
USE_YAML("Options.Save_Books_in_Yaml_Format", true),
|
||||
|
||||
/**
|
||||
* The max duplicates of a book that can be saved
|
||||
*/
|
||||
@@ -81,6 +76,11 @@ public enum ConfigOption {
|
||||
* Whether hitting a bookshelf should display information about the contained books
|
||||
*/
|
||||
ENABLE_BOOKSHELF_PEEKING("Options.Enable_Book_Peeking", true),
|
||||
|
||||
/**
|
||||
* Whether to use real AES encryption instead of storing garbled book text, while the full plaintext is stored in a file
|
||||
*/
|
||||
USE_REAL_ENCRYPTION("Options.Use_Real_Encryption", false),
|
||||
;
|
||||
|
||||
private final String configNode;
|
||||
|
@@ -22,6 +22,11 @@ public enum Permission {
|
||||
*/
|
||||
BYPASS_AUTHOR_ONLY_COPY("bypassAuthorOnlyCopy"),
|
||||
|
||||
/**
|
||||
* Does nothing by itself, but its child nodes allow decrypting group encrypted books for the specified group
|
||||
*/
|
||||
DECRYPT("decrypt"),
|
||||
|
||||
;
|
||||
|
||||
private final @NotNull String node;
|
||||
|
@@ -28,6 +28,11 @@ public enum Translatable implements TranslatableMessage {
|
||||
*/
|
||||
SUCCESS_AUTO_DECRYPTED,
|
||||
|
||||
/**
|
||||
* The success message displayed when a book is successfully deleted
|
||||
*/
|
||||
SUCCESS_DELETED,
|
||||
|
||||
/**
|
||||
* The error to display when the console attempts to run a player-only command
|
||||
*/
|
||||
@@ -103,6 +108,31 @@ public enum Translatable implements TranslatableMessage {
|
||||
*/
|
||||
ERROR_ENCRYPTED_BOOK_UNKNOWN,
|
||||
|
||||
/**
|
||||
* The error displayed when trying to delete a book while the book folder is empty or missing
|
||||
*/
|
||||
ERROR_DELETE_EMPTY,
|
||||
|
||||
/**
|
||||
* The error displayed when given the name of a book that does not exist
|
||||
*/
|
||||
ERROR_INCORRECT_FILE_NAME,
|
||||
|
||||
/**
|
||||
* The error displayed when a file failed to be deleted, without throwing an exception
|
||||
*/
|
||||
ERROR_DELETE_FAILED_SILENT,
|
||||
|
||||
/**
|
||||
* The error displayed when a file failed to be deleted, after throwing an exception
|
||||
*/
|
||||
ERROR_DELETE_FAILED_EXCEPTION,
|
||||
|
||||
/**
|
||||
* The error displayed when trying to encrypt a book without supplying a key
|
||||
*/
|
||||
ERROR_ENCRYPT_NO_KEY,
|
||||
|
||||
/**
|
||||
* The header displayed before printing all commands
|
||||
*/
|
||||
|
@@ -29,33 +29,58 @@ import java.util.logging.Level;
|
||||
*
|
||||
* @author EpicKnarvik97
|
||||
*/
|
||||
public class AES {
|
||||
public class AES implements Encryptor {
|
||||
|
||||
//TODO: Generate salt for each installation, and figure out what to to with the iv parameter
|
||||
private final IvParameterSpec ivParameterSpec;
|
||||
private final @NotNull IvParameterSpec ivParameterSpec;
|
||||
private final byte[] passwordSalt;
|
||||
private final @NotNull String password;
|
||||
|
||||
/**
|
||||
* Instantiates a new AES encryptor
|
||||
*
|
||||
* @param initializationVector <p>The initialization vector to use for CBC</p>
|
||||
* @param passwordSalt <p>The password salt to use</p>
|
||||
* @param aesConfiguration <p>The AES configuration to use</p>
|
||||
*/
|
||||
public AES(byte[] initializationVector, byte[] passwordSalt) {
|
||||
this.ivParameterSpec = new IvParameterSpec(initializationVector);
|
||||
this.passwordSalt = passwordSalt;
|
||||
public AES(@NotNull AESConfiguration aesConfiguration) {
|
||||
this.ivParameterSpec = new IvParameterSpec(aesConfiguration.iv());
|
||||
this.passwordSalt = aesConfiguration.salt();
|
||||
this.password = aesConfiguration.key();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String encryptText(@NotNull String input) {
|
||||
return encryptDecryptText(input, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String decryptText(@NotNull String input) {
|
||||
return encryptDecryptText(input, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a 16-byte initialization vector
|
||||
*
|
||||
* @return <p>An initialization vector</p>
|
||||
*/
|
||||
public static byte[] generateIV() {
|
||||
byte[] initializationVector = new byte[16];
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
secureRandom.nextBytes(initializationVector);
|
||||
return initializationVector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts or decrypts the given text
|
||||
*
|
||||
* @param input <p>The input to encrypt or decrypt</p>
|
||||
* @param password <p>The password to use for key generation</p>
|
||||
* @param encrypt <p>Whether to encrypt or decrypt the input</p>
|
||||
* <p>Note: The same IV and salt must be used during instantiation in order to decrypt an encrypted message.</p>
|
||||
*
|
||||
* @param input <p>The input to encrypt or decrypt</p>
|
||||
* @param encrypt <p>Whether to encrypt or decrypt the input</p>
|
||||
* @return <p>The encrypted/decrypted input, or null if anything went wrong</p>
|
||||
*/
|
||||
@Nullable
|
||||
public String encryptDecryptText(@NotNull String input, @NotNull String password, boolean encrypt) {
|
||||
private String encryptDecryptText(@NotNull String input, boolean encrypt) {
|
||||
//Make a key from the password
|
||||
SecretKeySpec secretKeySpec = getKeyFromPassword(password);
|
||||
//Get cipher instance
|
||||
@@ -80,24 +105,15 @@ public class AES {
|
||||
try {
|
||||
byte[] output = aes.doFinal(getInputBytes(input, encrypt));
|
||||
return createResult(output, encrypt);
|
||||
} catch (IllegalBlockSizeException | BadPaddingException exception) {
|
||||
BooksWithoutBorders.log(Level.SEVERE, "Invalid AES block size or padding");
|
||||
} catch (IllegalBlockSizeException exception) {
|
||||
BooksWithoutBorders.log(Level.SEVERE, "Invalid AES block size during finalization");
|
||||
return null;
|
||||
} catch (BadPaddingException exception) {
|
||||
BooksWithoutBorders.log(Level.SEVERE, "Invalid AES padding during finalization");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a 16-byte initialization vector
|
||||
*
|
||||
* @return <p>An initialization vector</p>
|
||||
*/
|
||||
public static byte[] generateIV() {
|
||||
byte[] initializationVector = new byte[16];
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
secureRandom.nextBytes(initializationVector);
|
||||
return initializationVector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the input string into bytes
|
||||
*
|
||||
@@ -139,8 +155,11 @@ public class AES {
|
||||
Cipher aes;
|
||||
try {
|
||||
aes = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException exception) {
|
||||
BooksWithoutBorders.log(Level.SEVERE, "Invalid AES algorithm or padding");
|
||||
} catch (NoSuchAlgorithmException exception) {
|
||||
BooksWithoutBorders.log(Level.SEVERE, "Invalid AES algorithm during Cipher generation");
|
||||
return null;
|
||||
} catch (NoSuchPaddingException exception) {
|
||||
BooksWithoutBorders.log(Level.SEVERE, "Invalid AES padding during Cipher generation");
|
||||
return null;
|
||||
}
|
||||
return aes;
|
||||
|
@@ -0,0 +1,24 @@
|
||||
package net.knarcraft.bookswithoutborders.encryption;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A configuration for AES encryption
|
||||
*
|
||||
* @param iv <p>The initialization vector</p>
|
||||
* @param salt <p>The encryption salt</p>
|
||||
* @param key <p>The encryption key</p>
|
||||
*/
|
||||
public record AESConfiguration(byte @NotNull [] iv, byte @NotNull [] salt, @NotNull String key) {
|
||||
|
||||
/**
|
||||
* Generates a new AES configuration with randomized IV and salt
|
||||
*
|
||||
* @param key <p>The encryption key to use</p>
|
||||
* @return <p>The new AES configuration</p>
|
||||
*/
|
||||
public static AESConfiguration getNewConfiguration(@NotNull String key) {
|
||||
return new AESConfiguration(AES.generateIV(), AES.generateIV(), key);
|
||||
}
|
||||
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package net.knarcraft.bookswithoutborders.state;
|
||||
package net.knarcraft.bookswithoutborders.encryption;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -7,9 +7,30 @@ import org.jetbrains.annotations.NotNull;
|
||||
*/
|
||||
public enum EncryptionStyle {
|
||||
|
||||
/**
|
||||
* Possibly lossy encryption using DNA codons
|
||||
*/
|
||||
DNA("dna"),
|
||||
|
||||
/**
|
||||
* A simple cipher using the key to substitute one character for another
|
||||
*/
|
||||
SUBSTITUTION("substitution"),
|
||||
|
||||
/**
|
||||
* A military-grade encryption cypher
|
||||
*/
|
||||
AES("aes"),
|
||||
|
||||
/**
|
||||
* An unbreakable encryption method assuming the key is completely random and never used more than once, ever
|
||||
*/
|
||||
ONE_TIME_PAD("onetimepad"),
|
||||
|
||||
/**
|
||||
* Just a way of using magic text to make text illegible
|
||||
*/
|
||||
MAGIC("magic"),
|
||||
;
|
||||
|
||||
private final String name;
|
@@ -0,0 +1,29 @@
|
||||
package net.knarcraft.bookswithoutborders.encryption;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* An interface describing a style of encryption
|
||||
*/
|
||||
public interface Encryptor {
|
||||
|
||||
/**
|
||||
* Encrypts the given plaintext
|
||||
*
|
||||
* @param input <p>The input to encrypt</p>
|
||||
* @return <p>The resulting cypher text, or null if unsuccessful</p>
|
||||
*/
|
||||
@Nullable
|
||||
String encryptText(@NotNull String input);
|
||||
|
||||
/**
|
||||
* Decrypts the given cypher text
|
||||
*
|
||||
* @param input <p>The cypher text to decrypt</p>
|
||||
* @return <p>The resulting plaintext, or null if unsuccessful</p>
|
||||
*/
|
||||
@Nullable
|
||||
String decryptText(@NotNull String input);
|
||||
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
package net.knarcraft.bookswithoutborders.encryption;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -12,11 +13,11 @@ import java.util.Random;
|
||||
* <p>Not sure where this was gotten from, but it does exist at
|
||||
* <a href="https://crypto.stackexchange.com/questions/11614/how-do-i-test-my-encryption-absolute-amateur">Stack Exchange</a>.</p>
|
||||
*/
|
||||
public class GenenCrypt {
|
||||
public class GenenCrypt implements Encryptor {
|
||||
|
||||
private final Random ranGen;
|
||||
private final String[] bases;
|
||||
private final String[] charList;
|
||||
private final String[] availableCharacters;
|
||||
private final HashMap<String, String[]> codonTable;
|
||||
private final HashMap<String, String> decryptTable;
|
||||
private final String key;
|
||||
@@ -62,32 +63,40 @@ public class GenenCrypt {
|
||||
// 10 digits
|
||||
// space, newline, and tab
|
||||
// the symbols . , ? " ! @ # $ % ^ & * ( ) - + = / _ \ : ; < >
|
||||
charList = new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q",
|
||||
availableCharacters = new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q",
|
||||
"R", "S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", " ",
|
||||
"\t", "\n", ".", ",", "?", "\"", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "-", "+", "=", "/",
|
||||
"_", "\\", ":", ";", "<", ">", "|"};
|
||||
|
||||
// define the codon table to encode text
|
||||
codonTable = new HashMap<>();
|
||||
for (int i = 0; i < charList.length; i++) {
|
||||
for (int i = 0; i < availableCharacters.length; i++) {
|
||||
String[] tempArray = new String[]{shuffledCodonList.get(4 * i), shuffledCodonList.get(4 * i + 1),
|
||||
shuffledCodonList.get(4 * i + 2), shuffledCodonList.get(4 * i + 3)};
|
||||
//System.out.println(i);
|
||||
codonTable.put(charList[i], tempArray);
|
||||
codonTable.put(availableCharacters[i], tempArray);
|
||||
}
|
||||
|
||||
// define the decryption table
|
||||
decryptTable = new HashMap<>();
|
||||
for (int i = 0; i < codonTable.size(); i++) {
|
||||
String s = charList[i];
|
||||
String s = availableCharacters[i];
|
||||
String[] sa = codonTable.get(s);
|
||||
decryptTable.put(sa[0], s);
|
||||
decryptTable.put(sa[1], s);
|
||||
decryptTable.put(sa[2], s);
|
||||
decryptTable.put(sa[3], s);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String encryptText(@NotNull String input) {
|
||||
return encrypt(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String decryptText(@NotNull String input) {
|
||||
return decrypt(input);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,7 +105,7 @@ public class GenenCrypt {
|
||||
public void printCodonTable() {
|
||||
// print the codon table
|
||||
for (int i = 0; i < codonTable.size(); i++) {
|
||||
String s = charList[i];
|
||||
String s = availableCharacters[i];
|
||||
String[] sa = codonTable.get(s);
|
||||
switch (s) {
|
||||
case "\t" ->
|
||||
@@ -117,7 +126,7 @@ public class GenenCrypt {
|
||||
* @return <p>The encrypted input</p>
|
||||
*/
|
||||
@NotNull
|
||||
public String encrypt(@NotNull String input) {
|
||||
private String encrypt(@NotNull String input) {
|
||||
StringBuilder output = new StringBuilder();
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
// insert junk bases
|
||||
@@ -151,7 +160,7 @@ public class GenenCrypt {
|
||||
* @return <p>The decrypted input</p>
|
||||
*/
|
||||
@NotNull
|
||||
public String decrypt(@NotNull String input) {
|
||||
private String decrypt(@NotNull String input) {
|
||||
StringBuilder output = new StringBuilder();
|
||||
int keyCount = 0;
|
||||
int junk = key.charAt(0);
|
||||
|
@@ -0,0 +1,22 @@
|
||||
package net.knarcraft.bookswithoutborders.encryption;
|
||||
|
||||
import net.knarcraft.bookswithoutborders.utility.BookFormatter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* So-called "Magic" encryption which simply makes the contents unreadable
|
||||
*/
|
||||
public class Magic implements Encryptor {
|
||||
|
||||
@Override
|
||||
public @Nullable String encryptText(@NotNull String input) {
|
||||
return "§k" + BookFormatter.stripColor(input.replace("§", ""));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String decryptText(@NotNull String input) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
package net.knarcraft.bookswithoutborders.encryption;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* A one-time pad implementation
|
||||
*/
|
||||
public class OneTimePad implements Encryptor {
|
||||
|
||||
private final @NotNull String key;
|
||||
|
||||
/**
|
||||
* Instantiates a new one-time pad
|
||||
*
|
||||
* @param key <p>The key to use</p>
|
||||
*/
|
||||
public OneTimePad(@NotNull String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String encryptText(@NotNull String input) {
|
||||
return oneTimePad(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String decryptText(@NotNull String input) {
|
||||
return oneTimePad(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts/decrypts the input using a one-time pad
|
||||
*
|
||||
* <p>The one time pad encryption is very secure, and encryption works just like decryption, but is vulnerable if
|
||||
* the same key is used more than once.</p>
|
||||
*
|
||||
* @param input <p>The input to encrypt/decrypt</p>
|
||||
* @return <p>The encrypted/decrypted output</p>
|
||||
*/
|
||||
@NotNull
|
||||
public String oneTimePad(@NotNull String input) {
|
||||
String longKey;
|
||||
try {
|
||||
final MessageDigest digest = MessageDigest.getInstance("SHA3-256");
|
||||
final byte[] hashBytes = digest.digest(key.getBytes(StandardCharsets.UTF_8));
|
||||
longKey = Base64.getEncoder().encodeToString(hashBytes);
|
||||
} catch (NoSuchAlgorithmException exception) {
|
||||
longKey = key;
|
||||
}
|
||||
|
||||
StringBuilder output = new StringBuilder();
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
output.append((char) (input.charAt(i) ^ longKey.charAt(i % longKey.length())));
|
||||
}
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
package net.knarcraft.bookswithoutborders.encryption;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.StringTokenizer;
|
||||
@@ -8,90 +9,89 @@ import java.util.StringTokenizer;
|
||||
/**
|
||||
* A simple substitution cipher
|
||||
*/
|
||||
public class SubstitutionCipher {
|
||||
public class SubstitutionCipher implements Encryptor {
|
||||
|
||||
public SubstitutionCipher() {
|
||||
private final @NotNull String key;
|
||||
|
||||
/**
|
||||
* Instantiates a new substitution cipher
|
||||
*
|
||||
* @param key <p>The key to use</p>
|
||||
*/
|
||||
public SubstitutionCipher(@NotNull String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
// encrypts a string using a substitution cipher.
|
||||
// the substitution is made harder to crack by
|
||||
// using a string for the key, it is converted
|
||||
// a series of offsets that each character in the
|
||||
// original message is offset by
|
||||
@NotNull
|
||||
public String encrypt(@NotNull String in, @NotNull String key) {
|
||||
StringBuilder output = new StringBuilder();
|
||||
if (!key.isEmpty()) {
|
||||
StringTokenizer tokenizer = new StringTokenizer(key, ", "); // tokenizes the key
|
||||
// converts each number in the key to an integer and adds to an array
|
||||
int[] offsetArray = new int[tokenizer.countTokens()];
|
||||
for (int i = 0; i < offsetArray.length; i++) {
|
||||
String nt = tokenizer.nextToken();
|
||||
@Override
|
||||
public @Nullable String encryptText(@NotNull String input) {
|
||||
return encryptDecrypt(input, true);
|
||||
}
|
||||
|
||||
try {
|
||||
offsetArray[i] = Integer.parseInt(nt);
|
||||
} catch (NumberFormatException e) {
|
||||
BigInteger big = new BigInteger(nt);
|
||||
offsetArray[i] = Math.abs(big.intValue());
|
||||
}
|
||||
@Override
|
||||
public @Nullable String decryptText(@NotNull String input) {
|
||||
return encryptDecrypt(input, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts or decrypts a string using a substitution cipher
|
||||
*
|
||||
* <p>The substitution is made harder to crack by using a string for the key, it is converted a series of offsets
|
||||
* that each character in the original message is offset by.</p>
|
||||
*
|
||||
* @param input <p>The input to encrypt/decrypt</p>
|
||||
* @param encrypt <p>Whether to encrypt or decrypt the input</p>
|
||||
* @return <p>The encryption output</p>
|
||||
*/
|
||||
@NotNull
|
||||
private String encryptDecrypt(@NotNull String input, boolean encrypt) {
|
||||
StringBuilder output = new StringBuilder();
|
||||
if (this.key.isBlank()) {
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
// converts each number in the key to an integer and adds to an array
|
||||
int[] offsetArray = getOffsetArray(this.key);
|
||||
|
||||
int offsetPosition = 0;
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
// encrypts the letter and adds to the output string
|
||||
if (encrypt) {
|
||||
output.append((char) (input.charAt(i) + offsetArray[offsetPosition]));
|
||||
} else {
|
||||
output.append((char) (input.charAt(i) - offsetArray[offsetPosition]));
|
||||
}
|
||||
|
||||
int offsetPosition = 0;
|
||||
for (int i = 0; i < in.length(); i++) {
|
||||
output.append((char) (in.charAt(i) + offsetArray[offsetPosition])); //encrypts the letter and adds to the output string
|
||||
// uses the next offset in the key, goes back to first offset if at end of list
|
||||
if (offsetPosition < offsetArray.length - 1) {
|
||||
offsetPosition++;
|
||||
} else {
|
||||
offsetPosition = 0;
|
||||
}
|
||||
// uses the next offset in the key, goes back to first offset if at end of list
|
||||
if (offsetPosition < offsetArray.length - 1) {
|
||||
offsetPosition++;
|
||||
} else {
|
||||
offsetPosition = 0;
|
||||
}
|
||||
}
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
// decrypts a string using the same substitution method,
|
||||
// but in reverse. Could probably be combined into one
|
||||
// method with a flag for encryption / decryption, but
|
||||
// I'm lazy.
|
||||
@SuppressWarnings("unused")
|
||||
@NotNull
|
||||
public String decrypt(@NotNull String in, @NotNull String key) {
|
||||
StringBuilder output = new StringBuilder();
|
||||
if (!key.isEmpty()) {
|
||||
StringTokenizer tokenizer = new StringTokenizer(key, ", "); // tokenizes the key
|
||||
// converts each number in the key to an integer and adds to an array
|
||||
int[] offsetArray = new int[tokenizer.countTokens()];
|
||||
for (int i = 0; i < offsetArray.length; i++) {
|
||||
offsetArray[i] = Integer.parseInt(tokenizer.nextToken());
|
||||
}
|
||||
int offsetPosition = 0;
|
||||
for (int i = 0; i < in.length(); i++) {
|
||||
output.append((char) (in.charAt(i) - offsetArray[offsetPosition])); //encrypts the letter and adds to the output string
|
||||
// uses the next offset in the key, goes back to first offset if at end of list
|
||||
if (offsetPosition < offsetArray.length - 1) {
|
||||
offsetPosition++;
|
||||
} else {
|
||||
offsetPosition = 0;
|
||||
}
|
||||
/**
|
||||
* Tokenizes a key and generates an offset array for substitution
|
||||
*
|
||||
* @param key <p>The key to make an offset array for</p>
|
||||
* @return <p>The offset array</p>
|
||||
*/
|
||||
private int[] getOffsetArray(@NotNull String key) {
|
||||
StringTokenizer tokenizer = new StringTokenizer(key, ", "); // tokenizes the key
|
||||
// converts each number in the key to an integer and adds to an array
|
||||
int[] offsetArray = new int[tokenizer.countTokens()];
|
||||
for (int i = 0; i < offsetArray.length; i++) {
|
||||
String nextToken = tokenizer.nextToken();
|
||||
|
||||
try {
|
||||
offsetArray[i] = Integer.parseInt(nextToken);
|
||||
} catch (NumberFormatException e) {
|
||||
BigInteger big = new BigInteger(nextToken);
|
||||
offsetArray[i] = Math.abs(big.intValue());
|
||||
}
|
||||
}
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
|
||||
// the one time pad encryption is very secure, and
|
||||
// encryption works just like decryption, but is
|
||||
// vulnerable if the same key is used more than once.
|
||||
@SuppressWarnings("unused")
|
||||
@NotNull
|
||||
public String oneTimePad(@NotNull String in, @NotNull String key) {
|
||||
StringBuilder output = new StringBuilder();
|
||||
for (int i = 0; i < in.length(); i++) {
|
||||
output.append((char) (in.charAt(i) ^ key.charAt(i % key.length())));
|
||||
}
|
||||
return output.toString();
|
||||
return offsetArray;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -12,7 +12,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -89,19 +88,10 @@ public class BookshelfHandler {
|
||||
String loreKey = key + ".lore";
|
||||
|
||||
String title = bookshelfSection.getString(titleKey, null);
|
||||
List<String> loreStrings = new ArrayList<>();
|
||||
List<?> lore = bookshelfSection.getList(loreKey);
|
||||
if (lore == null) {
|
||||
throw new IllegalArgumentException("Lore is missing from bookshelf data!");
|
||||
}
|
||||
lore.forEach((item) -> {
|
||||
if (item instanceof String) {
|
||||
loreStrings.add((String) item);
|
||||
}
|
||||
});
|
||||
List<String> lore = bookshelfSection.getStringList(loreKey);
|
||||
|
||||
if (title != null) {
|
||||
registerBookshelf(new Bookshelf(bookshelfLocation, title, loreStrings));
|
||||
registerBookshelf(new Bookshelf(bookshelfLocation, title, lore));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,23 +2,14 @@ package net.knarcraft.bookswithoutborders.listener;
|
||||
|
||||
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
|
||||
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
|
||||
import net.knarcraft.bookswithoutborders.state.BookDirectory;
|
||||
import net.knarcraft.bookswithoutborders.utility.BookHelper;
|
||||
import net.knarcraft.bookswithoutborders.utility.BookLoader;
|
||||
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
|
||||
import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerItemHeldEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
import org.bukkit.inventory.meta.BookMeta;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.logging.Level;
|
||||
@@ -30,31 +21,6 @@ public class PlayerEventListener implements Listener {
|
||||
|
||||
private final BooksWithoutBorders booksWithoutBorders = BooksWithoutBorders.getInstance();
|
||||
|
||||
@EventHandler
|
||||
public void onHold(@NotNull PlayerItemHeldEvent event) {
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = event.getPlayer();
|
||||
int selectedSlot = event.getNewSlot();
|
||||
PlayerInventory playerInventory = player.getInventory();
|
||||
ItemStack selectedItem = playerInventory.getItem(selectedSlot);
|
||||
|
||||
//Ignore irrelevant items
|
||||
if (selectedItem == null || selectedItem.getType() != Material.WRITTEN_BOOK) {
|
||||
return;
|
||||
}
|
||||
|
||||
ItemMeta itemMetadata = selectedItem.getItemMeta();
|
||||
if (itemMetadata == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Update the book the user is viewing
|
||||
updateBookInHand(player, itemMetadata, true);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(@NotNull PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
@@ -79,22 +45,6 @@ public class PlayerEventListener implements Listener {
|
||||
sendMessage = giveBookToNewPlayer(bookName, player, sendMessage);
|
||||
}
|
||||
}
|
||||
|
||||
//Updates any books in either hand
|
||||
ItemStack mainHandItem = InventoryHelper.getHeldItem(player, true);
|
||||
ItemStack offHandItem = InventoryHelper.getHeldItem(player, false);
|
||||
if (mainHandItem.getType() == Material.WRITTEN_BOOK) {
|
||||
ItemMeta itemMetadata = mainHandItem.getItemMeta();
|
||||
if (itemMetadata != null) {
|
||||
updateBookInHand(player, itemMetadata, true);
|
||||
}
|
||||
}
|
||||
if (offHandItem.getType() == Material.WRITTEN_BOOK) {
|
||||
ItemMeta itemMetadata = offHandItem.getItemMeta();
|
||||
if (itemMetadata != null) {
|
||||
updateBookInHand(player, itemMetadata, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,69 +75,4 @@ public class PlayerEventListener implements Listener {
|
||||
return sendMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a book in one of the player's hands
|
||||
*
|
||||
* @param player <p>The player to update</p>
|
||||
* @param itemMetadata <p>Information about the held book</p>
|
||||
* @param mainHand <p>Whether to update the book in the player's main hand</p>
|
||||
*/
|
||||
private void updateBookInHand(@NotNull Player player, @NotNull ItemMeta itemMetadata, boolean mainHand) {
|
||||
PlayerInventory playerInventory = player.getInventory();
|
||||
ItemStack updatedBook = updateBook(player, (BookMeta) itemMetadata);
|
||||
if (updatedBook != null) {
|
||||
if (mainHand) {
|
||||
playerInventory.setItemInMainHand(updatedBook);
|
||||
} else {
|
||||
playerInventory.setItemInOffHand(updatedBook);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates old books to a newer format
|
||||
*
|
||||
* @param player <p>The player holding the book</p>
|
||||
* @param oldBook <p>Metadata about the held book</p>
|
||||
* @return <p>An updated book</p>
|
||||
*/
|
||||
@Nullable
|
||||
public ItemStack updateBook(@NotNull Player player, @NotNull BookMeta oldBook) {
|
||||
//handles hacked title-less books
|
||||
if (oldBook.getTitle() == null || oldBook.getTitle().length() < 3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (oldBook.getTitle().substring(oldBook.getTitle().length() - 3).equalsIgnoreCase("[U]")) {
|
||||
String fileName;
|
||||
|
||||
if (oldBook.getAuthor() != null && oldBook.getAuthor().equalsIgnoreCase("unknown")) {
|
||||
//Unknown author is ignored
|
||||
fileName = oldBook.getTitle();
|
||||
} else {
|
||||
fileName = oldBook.getTitle() + BooksWithoutBorders.getConfiguration().getTitleAuthorSeparator() + oldBook.getAuthor();
|
||||
}
|
||||
|
||||
String playerFolderPath = BookHelper.getBookDirectoryPathString(BookDirectory.PLAYER, player);
|
||||
String publicFolderPath = BookHelper.getBookDirectoryPathString(BookDirectory.PUBLIC, player);
|
||||
|
||||
String[] possiblePaths = new String[]{
|
||||
publicFolderPath + fileName + ".yml",
|
||||
publicFolderPath + fileName + ".txt",
|
||||
playerFolderPath + fileName + ".yml",
|
||||
playerFolderPath + fileName + ".txt"
|
||||
};
|
||||
|
||||
for (String path : possiblePaths) {
|
||||
File file = new File(path);
|
||||
if (file.isFile()) {
|
||||
return BookLoader.loadBook(player, fileName, "true", "player");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -2,7 +2,9 @@ package net.knarcraft.bookswithoutborders.listener;
|
||||
|
||||
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
|
||||
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
|
||||
import net.knarcraft.bookswithoutborders.state.EncryptionStyle;
|
||||
import net.knarcraft.bookswithoutborders.config.Permission;
|
||||
import net.knarcraft.bookswithoutborders.encryption.EncryptionStyle;
|
||||
import net.knarcraft.bookswithoutborders.state.BookDirectory;
|
||||
import net.knarcraft.bookswithoutborders.utility.BookFileHelper;
|
||||
import net.knarcraft.bookswithoutborders.utility.BookFormatter;
|
||||
import net.knarcraft.bookswithoutborders.utility.BookLoader;
|
||||
@@ -55,10 +57,11 @@ public class SignEventListener implements Listener {
|
||||
event.setLine(0, ChatColor.DARK_GREEN + "[BwB]");
|
||||
|
||||
//Check if the sign is of a valid type
|
||||
if (!((lines[1].equalsIgnoreCase("[Encrypt]") || lines[1].equalsIgnoreCase("[Decrypt]") ||
|
||||
lines[1].equalsIgnoreCase("[Give]")) && !lines[2].trim().isEmpty())) {
|
||||
if ((!lines[1].equalsIgnoreCase("[Encrypt]") && !lines[1].equalsIgnoreCase("[Decrypt]") &&
|
||||
!lines[1].equalsIgnoreCase("[Give]")) || lines[2].trim().isEmpty()) {
|
||||
//Mark the second line as invalid
|
||||
event.setLine(1, ChatColor.DARK_RED + lines[1]);
|
||||
player.sendMessage("Invalid sign!");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -79,11 +82,6 @@ public class SignEventListener implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onClick(@NotNull PlayerInteractEvent event) {
|
||||
if (event.getClickedBlock() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Material clickedBlockType = event.getClickedBlock().getType();
|
||||
Player player = event.getPlayer();
|
||||
PlayerInventory playerInventory = player.getInventory();
|
||||
EquipmentSlot hand = event.getHand();
|
||||
@@ -96,15 +94,18 @@ public class SignEventListener implements Listener {
|
||||
}
|
||||
Material heldItemType = heldItem.getType();
|
||||
|
||||
if (event.getAction() == Action.RIGHT_CLICK_BLOCK && (Tag.SIGNS.isTagged(clickedBlockType) ||
|
||||
Tag.WALL_SIGNS.isTagged(clickedBlockType))) {
|
||||
event.setUseItemInHand(Event.Result.DENY);
|
||||
if (event.getClickedBlock() != null && (event.getAction() == Action.RIGHT_CLICK_BLOCK &&
|
||||
(Tag.SIGNS.isTagged(event.getClickedBlock().getType()) ||
|
||||
Tag.WALL_SIGNS.isTagged(event.getClickedBlock().getType())))) {
|
||||
//The player right-clicked a sign
|
||||
Sign sign = (Sign) event.getClickedBlock().getState();
|
||||
if (!signLineEquals(sign, 0, "[BwB]", ChatColor.DARK_GREEN)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.setUseItemInHand(Event.Result.DENY);
|
||||
event.setCancelled(true);
|
||||
|
||||
if (signLineEquals(sign, 1, "[Encrypt]", ChatColor.DARK_BLUE)) {
|
||||
encryptHeldBookUsingSign(sign, heldItemType, player, hand);
|
||||
} else if (signLineEquals(sign, 1, "[Decrypt]", ChatColor.DARK_BLUE)) {
|
||||
@@ -120,6 +121,7 @@ public class SignEventListener implements Listener {
|
||||
}
|
||||
} else if (heldItemType == Material.WRITTEN_BOOK && (event.getAction() == Action.LEFT_CLICK_AIR
|
||||
|| event.getAction() == Action.LEFT_CLICK_BLOCK)) {
|
||||
|
||||
BookMeta oldBook = (BookMeta) heldItem.getItemMeta();
|
||||
if (oldBook == null) {
|
||||
return;
|
||||
@@ -147,7 +149,11 @@ public class SignEventListener implements Listener {
|
||||
String lineText = BookFormatter.stripColor(sign.getSide(Side.FRONT).getLine(2));
|
||||
String key = EncryptionHelper.getNumberKeyFromStringKey(lineText);
|
||||
|
||||
ItemStack book = EncryptionHelper.loadEncryptedBook(player, key, false);
|
||||
ItemStack book = EncryptionHelper.loadEncryptedBook(player, key, false, false);
|
||||
if (book == null) {
|
||||
book = EncryptionHelper.loadEncryptedBookLegacy(player, key, false);
|
||||
}
|
||||
|
||||
if (book != null) {
|
||||
player.getInventory().setItem(hand, book);
|
||||
player.sendMessage(ChatColor.GREEN + "Book decrypted!");
|
||||
@@ -256,25 +262,32 @@ public class SignEventListener implements Listener {
|
||||
String groupName = oldBook.getLore().get(0).substring(3).split(" encrypted")[0];
|
||||
|
||||
//Permission check
|
||||
if (!player.hasPermission("bookswithoutborders.decrypt." + groupName) &&
|
||||
!(config.getAdminDecrypt() && player.hasPermission("bookswithoutborders.admin"))) {
|
||||
if (!player.hasPermission(Permission.DECRYPT + "." + groupName) &&
|
||||
!(config.getAdminDecrypt() && player.hasPermission(Permission.ADMIN.toString()))) {
|
||||
BooksWithoutBorders.sendErrorMessage(player, "You are not allowed to decrypt that book");
|
||||
return;
|
||||
}
|
||||
|
||||
String encryptedFolder = BooksWithoutBorders.getConfiguration().getEncryptedBookPath();
|
||||
String fileName = oldBook.getTitle() + config.getTitleAuthorSeparator() + oldBook.getAuthor();
|
||||
|
||||
String encryptionFile = InputCleaningHelper.cleanString(groupName) + config.getSlash() + fileName + ".yml";
|
||||
|
||||
File file = new File(BooksWithoutBorders.getConfiguration().getEncryptedBookPath() + encryptionFile);
|
||||
if (!file.isFile()) {
|
||||
file = new File(config.getBookFolder() + fileName + ".txt");
|
||||
if (!file.isFile()) {
|
||||
return;
|
||||
File file = BookFileHelper.findBookFile(encryptedFolder + InputCleaningHelper.cleanString(groupName) +
|
||||
config.getSlash(), oldBook);
|
||||
if (file == null) {
|
||||
file = BookFileHelper.findBookFile(encryptedFolder, oldBook);
|
||||
if (file == null) {
|
||||
file = BookFileHelper.findBookFile(config.bookFolder, oldBook);
|
||||
if (file == null) {
|
||||
BooksWithoutBorders.sendErrorMessage(player, "Unable to find encrypted book");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
newBook = BookLoader.loadBook(player, fileName, "true", groupName, heldItem.getAmount());
|
||||
|
||||
newBook = BookLoader.loadBook(player, fileName, "true", BookDirectory.ENCRYPTED, groupName, heldItem.getAmount());
|
||||
|
||||
if (newBook == null) {
|
||||
BooksWithoutBorders.sendErrorMessage(player, "Unable to load the unencrypted book!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -3,7 +3,10 @@ package net.knarcraft.bookswithoutborders.utility;
|
||||
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
|
||||
import net.knarcraft.bookswithoutborders.config.Translatable;
|
||||
import net.knarcraft.bookswithoutborders.state.BookDirectory;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.meta.BookMeta;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -236,21 +239,60 @@ public final class BookFileHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extension from the given path
|
||||
* Attempts to find the correct book file
|
||||
*
|
||||
* @param path <p>The path to get the extension from</p>
|
||||
* @return <p>The extension of the input</p>
|
||||
* @param folder <p>The folder the book is in</p>
|
||||
* @param bookMeta <p>The book meta of the book to find</p>
|
||||
* @return <p>The book's file, or null if not found</p>
|
||||
*/
|
||||
@NotNull
|
||||
public static String getExtensionFromPath(@NotNull String path) {
|
||||
int dotIndex = path.lastIndexOf(".");
|
||||
if (dotIndex > 0) {
|
||||
String separator = BooksWithoutBorders.getConfiguration().getTitleAuthorSeparator();
|
||||
if (path.lastIndexOf(separator) < dotIndex && (path.length() - dotIndex == 4)) {
|
||||
return path.substring((path.length() - dotIndex) + 1);
|
||||
}
|
||||
@Nullable
|
||||
public static File findBookFile(@NotNull String folder, @NotNull BookMeta bookMeta) {
|
||||
String separator = BooksWithoutBorders.getConfiguration().getTitleAuthorSeparator();
|
||||
String fileName = bookMeta.getTitle() + separator + bookMeta.getAuthor();
|
||||
return findBookFile(folder, fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to find the correct book file
|
||||
*
|
||||
* @param folder <p>The folder the book is in</p>
|
||||
* @param fileName <p>The name of the book to find</p>
|
||||
* @return <p>The book's file, or null if not found</p>
|
||||
*/
|
||||
@Nullable
|
||||
public static File findBookFile(@NotNull String folder, @NotNull String fileName) {
|
||||
fileName = InputCleaningHelper.cleanString(fileName);
|
||||
File file = new File(folder, fileName + ".yml");
|
||||
if (file.exists()) {
|
||||
return getBookFile(file.getAbsolutePath());
|
||||
}
|
||||
file = new File(folder, fileName.replace(" ", "_") + ".yml");
|
||||
if (file.exists()) {
|
||||
return getBookFile(file.getAbsolutePath());
|
||||
}
|
||||
file = new File(folder, fileName.replace(" ", "_") + ".txt");
|
||||
if (file.exists()) {
|
||||
return getBookFile(file.getAbsolutePath());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces an author name with a player UUID if matched
|
||||
*
|
||||
* @param fileName <p>The filename to replace the author of</p>
|
||||
* @return <p>The filename, or the filename with the author replaced with UUID</p>
|
||||
*/
|
||||
public static String replaceAuthorWithUUID(@NotNull String fileName) {
|
||||
String userName = BookFormatter.stripColor(getBookAuthorFromPath(fileName));
|
||||
|
||||
Player player = Bukkit.getPlayer(userName);
|
||||
if (player != null) {
|
||||
return userName.replace(userName, player.getUniqueId().toString());
|
||||
} else {
|
||||
return userName;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -3,7 +3,6 @@ package net.knarcraft.bookswithoutborders.utility;
|
||||
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
|
||||
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
|
||||
import net.knarcraft.bookswithoutborders.state.BookDirectory;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -15,6 +14,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* A helper class for loading books from files
|
||||
@@ -57,11 +57,28 @@ public final class BookLoader {
|
||||
BooksWithoutBorders.sendErrorMessage(sender, "Unrecognized book directory!");
|
||||
return null;
|
||||
}
|
||||
return loadBook(sender, fileName, isSigned, bookDirectory, directory, numCopies);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given book
|
||||
*
|
||||
* @param sender <p>The command sender trying to load the book</p>
|
||||
* @param fileName <p>The index or file name of the book to load</p>
|
||||
* @param isSigned <p>Whether to load the book as signed, and not unsigned</p>
|
||||
* @param bookDirectory <p>The type of directory to save in</p>
|
||||
* @param directory <p>The directory to save the book in</p>
|
||||
* @param numCopies <p>The number of copies to load</p>
|
||||
* @return <p>The loaded book</p>
|
||||
*/
|
||||
@Nullable
|
||||
public static ItemStack loadBook(@NotNull CommandSender sender, @NotNull String fileName, @NotNull String isSigned,
|
||||
@NotNull BookDirectory bookDirectory, @NotNull String directory, int numCopies) {
|
||||
//Find the filename if a book index is given
|
||||
try {
|
||||
int bookIndex = Integer.parseInt(fileName);
|
||||
List<String> availableFiles = BooksWithoutBorders.getAvailableBooks(sender, bookDirectory == BookDirectory.PUBLIC);
|
||||
List<String> availableFiles = BooksWithoutBorders.getAvailableBooks(sender,
|
||||
bookDirectory == BookDirectory.PUBLIC);
|
||||
if (bookIndex <= availableFiles.size()) {
|
||||
fileName = availableFiles.get(Integer.parseInt(fileName) - 1);
|
||||
}
|
||||
@@ -74,20 +91,10 @@ public final class BookLoader {
|
||||
File file = getFullPath(sender, fileName, bookDirectory, directory);
|
||||
if (file == null) {
|
||||
//Try converting the username to UUID
|
||||
String separator = config.getTitleAuthorSeparator();
|
||||
String userName = BookFileHelper.getBookAuthorFromPath(fileName);
|
||||
String title = BookFileHelper.getBookTitleFromPath(fileName);
|
||||
String extension = BookFileHelper.getExtensionFromPath(fileName);
|
||||
|
||||
Player player = Bukkit.getPlayer(userName);
|
||||
if (player != null) {
|
||||
userName = userName.replace(BookFormatter.stripColor(userName), player.getUniqueId().toString());
|
||||
file = getFullPath(sender, title + separator + userName + extension, bookDirectory, directory);
|
||||
if (file == null) {
|
||||
BooksWithoutBorders.sendErrorMessage(sender, "Incorrect file name!");
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
String replaced = BookFileHelper.replaceAuthorWithUUID(fileName);
|
||||
file = getFullPath(sender, replaced, bookDirectory, directory);
|
||||
if (file == null) {
|
||||
BooksWithoutBorders.sendErrorMessage(sender, "Incorrect file name!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -152,12 +159,17 @@ public final class BookLoader {
|
||||
private static File getFullPath(@NotNull CommandSender sender, @NotNull String fileName,
|
||||
@NotNull BookDirectory bookDirectory, @NotNull String directory) {
|
||||
BooksWithoutBordersConfig config = BooksWithoutBorders.getConfiguration();
|
||||
File file;
|
||||
File file = null;
|
||||
String slash = config.getSlash();
|
||||
if (bookDirectory == BookDirectory.ENCRYPTED) {
|
||||
file = BookFileHelper.getBookFile(config.getEncryptedBookPath() + directory + slash + fileName);
|
||||
file = BookFileHelper.findBookFile(config.getEncryptedBookPath() + directory + slash, fileName);
|
||||
} else {
|
||||
file = BookFileHelper.getBookFile(BookHelper.getBookDirectoryPathString(bookDirectory, sender) + fileName);
|
||||
String folder = BookHelper.getBookDirectoryPathString(bookDirectory, sender);
|
||||
if (folder != null) {
|
||||
file = BookFileHelper.findBookFile(folder, fileName);
|
||||
} else {
|
||||
BooksWithoutBorders.log(Level.WARNING, "Unknown directory " + bookDirectory);
|
||||
}
|
||||
}
|
||||
if (file == null || !file.isFile()) {
|
||||
return null;
|
||||
|
@@ -2,6 +2,8 @@ package net.knarcraft.bookswithoutborders.utility;
|
||||
|
||||
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
|
||||
import net.knarcraft.bookswithoutborders.config.Translatable;
|
||||
import net.knarcraft.bookswithoutborders.encryption.AESConfiguration;
|
||||
import net.knarcraft.bookswithoutborders.encryption.EncryptionStyle;
|
||||
import net.knarcraft.knarlib.util.FileHelper;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
@@ -12,10 +14,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
@@ -39,7 +38,55 @@ public final class BookToFromTextHelper {
|
||||
* @throws IOException <p>If unable to save the book</p>
|
||||
*/
|
||||
public static void bookToYml(@NotNull String path, @NotNull String fileName, @NotNull BookMeta bookMetadata) throws IOException {
|
||||
FileConfiguration bookYml = YamlConfiguration.loadConfiguration(new File(path, "blank"));
|
||||
FileConfiguration bookYml = getBookConfiguration(bookMetadata);
|
||||
bookYml.save(path + fileName + ".yml");
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves an encrypted book's contents to a .yml file
|
||||
*
|
||||
* @param path <p>The path of the folder to save to. Must end with a slash</p>
|
||||
* @param fileName <p>The name of the file to load to</p>
|
||||
* @param bookMetadata <p>Metadata about the book to save</p>
|
||||
* @throws IOException <p>If unable to save the book</p>
|
||||
*/
|
||||
public static void encryptedBookToYml(@NotNull String path, @NotNull String fileName, @NotNull BookMeta bookMetadata,
|
||||
@NotNull EncryptionStyle encryptionStyle, @NotNull String encryptionKey,
|
||||
@Nullable AESConfiguration aesConfiguration) throws IOException {
|
||||
FileConfiguration bookYml = getBookConfiguration(bookMetadata);
|
||||
|
||||
bookYml.set("Encryption.Style", encryptionStyle.toString());
|
||||
bookYml.set("Encryption.Key", encryptionKey);
|
||||
if (encryptionStyle == EncryptionStyle.AES) {
|
||||
if (aesConfiguration == null) {
|
||||
throw new IOException("Attempted to save AES encrypted book without supplying a configuration!");
|
||||
}
|
||||
bookYml.set("Encryption.AES.IV", EncryptionHelper.bytesToHex(aesConfiguration.iv()));
|
||||
bookYml.set("Encryption.AES.Salt", EncryptionHelper.bytesToHex(aesConfiguration.salt()));
|
||||
}
|
||||
|
||||
List<String> encryptedPages = EncryptionHelper.encryptDecryptBookPages(bookMetadata, encryptionStyle,
|
||||
aesConfiguration, encryptionKey, true);
|
||||
if (encryptedPages == null || encryptedPages.isEmpty()) {
|
||||
throw new IOException("Book encryption failed!");
|
||||
}
|
||||
bookYml.set("Encryption.Data", encryptedPages);
|
||||
|
||||
// Make sure the plaintext cannot simply be seen in the file
|
||||
if (BooksWithoutBorders.getConfiguration().useRealEncryption()) {
|
||||
bookYml.set("Pages", null);
|
||||
}
|
||||
|
||||
bookYml.save(path + fileName + ".yml");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a file configuration containing a book's information
|
||||
*
|
||||
* @param bookMetadata <p>Metadata about the book to save</p>
|
||||
*/
|
||||
private static FileConfiguration getBookConfiguration(@NotNull BookMeta bookMetadata) {
|
||||
FileConfiguration bookYml = YamlConfiguration.loadConfiguration(new File("", "blank"));
|
||||
|
||||
if (bookMetadata.hasTitle()) {
|
||||
bookYml.set("Title", bookMetadata.getTitle());
|
||||
@@ -59,7 +106,7 @@ public final class BookToFromTextHelper {
|
||||
bookYml.set("Lore", bookMetadata.getLore());
|
||||
}
|
||||
|
||||
bookYml.save(path + fileName + ".yml");
|
||||
return bookYml;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,30 +128,67 @@ public final class BookToFromTextHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a book's contents to a text file
|
||||
* Loads a book from a .yml file
|
||||
*
|
||||
* @param folderPath <p>The folder path to save to. Must end with a slash</p>
|
||||
* @param fileName <p>The name of the file to save to</p>
|
||||
* @param bookMetadata <p>Metadata about the book to save</p>
|
||||
* @throws IOException <p>If unable to save the book</p>
|
||||
* @param file <p>The path of the file to load</p>
|
||||
* @param bookMetadata <p>Metadata which will be altered with the book's contents</p>
|
||||
* @param userKey <p>The user-supplied decryption key</p>
|
||||
* @param forceDecrypt <p>Whether to use the saved key for decryption, ignoring the supplied key</p>
|
||||
* @return <p>Metadata for the loaded book</p>
|
||||
*/
|
||||
public static void bookToTXT(@NotNull String folderPath, @NotNull String fileName, @NotNull BookMeta bookMetadata) throws IOException {
|
||||
FileWriter fileWriter = new FileWriter(folderPath + fileName + ".txt", StandardCharsets.UTF_8);
|
||||
PrintWriter printWriter = new PrintWriter(fileWriter);
|
||||
List<String> pages = bookMetadata.getPages();
|
||||
@Nullable
|
||||
public static BookMeta encryptedBookFromYml(@NotNull File file, @NotNull BookMeta bookMetadata, @NotNull String userKey, boolean forceDecrypt) {
|
||||
BookMeta meta;
|
||||
|
||||
BookMeta.Generation generation = bookMetadata.getGeneration();
|
||||
if (generation == null) {
|
||||
generation = BookMeta.Generation.ORIGINAL;
|
||||
try {
|
||||
meta = bookFromYml(file, bookMetadata);
|
||||
if (meta == null) {
|
||||
return null;
|
||||
}
|
||||
} catch (IllegalArgumentException exception) {
|
||||
return null;
|
||||
}
|
||||
String generationString = ":" + generation.name();
|
||||
|
||||
//Save each page of the book as a text line
|
||||
printWriter.println("[Book]" + generationString);
|
||||
for (String page : pages) {
|
||||
printWriter.println(page);
|
||||
// If the plaintext is stored in the file, don't bother with real decryption
|
||||
if (!meta.getPages().isEmpty()) {
|
||||
return meta;
|
||||
}
|
||||
printWriter.close();
|
||||
|
||||
FileConfiguration bookYml = YamlConfiguration.loadConfiguration(file);
|
||||
userKey = EncryptionHelper.sha256(userKey);
|
||||
String realKey = bookYml.getString("Encryption.Key", "");
|
||||
if (forceDecrypt) {
|
||||
userKey = realKey;
|
||||
}
|
||||
if (!userKey.equals(realKey)) {
|
||||
BooksWithoutBorders.log(Level.INFO, "Supplied key: " + userKey + " does not match real key: " + realKey);
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> data = bookYml.getStringList("Encryption.Data");
|
||||
if (data.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
EncryptionStyle encryptionStyle = EncryptionStyle.getFromString(bookYml.getString("Encryption.Style",
|
||||
EncryptionStyle.SUBSTITUTION.toString()));
|
||||
|
||||
AESConfiguration aesConfiguration = null;
|
||||
if (encryptionStyle == EncryptionStyle.AES) {
|
||||
byte[] iv = EncryptionHelper.hexStringToByteArray(bookYml.getString("Encryption.AES.IV", ""));
|
||||
byte[] salt = EncryptionHelper.hexStringToByteArray(bookYml.getString("Encryption.AES.Salt", ""));
|
||||
aesConfiguration = new AESConfiguration(iv, salt, userKey);
|
||||
}
|
||||
|
||||
meta.setPages(data);
|
||||
List<String> decryptedPages = EncryptionHelper.encryptDecryptBookPages(meta, encryptionStyle,
|
||||
aesConfiguration, userKey, false);
|
||||
if (decryptedPages != null && !decryptedPages.isEmpty()) {
|
||||
meta.setPages(decryptedPages);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return meta;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -3,9 +3,13 @@ package net.knarcraft.bookswithoutborders.utility;
|
||||
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
|
||||
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
|
||||
import net.knarcraft.bookswithoutborders.encryption.AES;
|
||||
import net.knarcraft.bookswithoutborders.encryption.AESConfiguration;
|
||||
import net.knarcraft.bookswithoutborders.encryption.EncryptionStyle;
|
||||
import net.knarcraft.bookswithoutborders.encryption.Encryptor;
|
||||
import net.knarcraft.bookswithoutborders.encryption.GenenCrypt;
|
||||
import net.knarcraft.bookswithoutborders.encryption.Magic;
|
||||
import net.knarcraft.bookswithoutborders.encryption.OneTimePad;
|
||||
import net.knarcraft.bookswithoutborders.encryption.SubstitutionCipher;
|
||||
import net.knarcraft.bookswithoutborders.state.EncryptionStyle;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -16,7 +20,11 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
@@ -28,6 +36,8 @@ import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.clea
|
||||
*/
|
||||
public final class EncryptionHelper {
|
||||
|
||||
private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
|
||||
|
||||
private EncryptionHelper() {
|
||||
}
|
||||
|
||||
@@ -35,55 +45,79 @@ public final class EncryptionHelper {
|
||||
* Transforms a string key/password into its numerical values
|
||||
*
|
||||
* @param key <p>The key to transform</p>
|
||||
* @return <p>The numbers representing the key's characters</p>
|
||||
* @return <p>A comma-separated string of the numbers representing the key's characters</p>
|
||||
*/
|
||||
@NotNull
|
||||
public static String getNumberKeyFromStringKey(@NotNull String key) {
|
||||
StringBuilder integerKey = new StringBuilder();
|
||||
for (int x = 0; x < key.length(); x++) {
|
||||
integerKey.append(Character.getNumericValue(Character.codePointAt(key, x)));
|
||||
StringBuilder integerKey = new StringBuilder(String.valueOf(Character.codePointAt(key, 0)));
|
||||
for (int x = 1; x < key.length(); x++) {
|
||||
integerKey.append(", ").append(Character.codePointAt(key, x));
|
||||
}
|
||||
return integerKey.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs sha256 hashing on the input string
|
||||
*
|
||||
* @param input <p>The input to hash</p>
|
||||
* @return <p>The hashed input</p>
|
||||
*/
|
||||
@NotNull
|
||||
public static String sha256(@NotNull String input) {
|
||||
String hashed;
|
||||
try {
|
||||
final MessageDigest digest = MessageDigest.getInstance("SHA3-256");
|
||||
final byte[] hashBytes = digest.digest(input.getBytes(StandardCharsets.UTF_8));
|
||||
hashed = Base64.getEncoder().encodeToString(hashBytes);
|
||||
} catch (NoSuchAlgorithmException exception) {
|
||||
hashed = input;
|
||||
}
|
||||
return hashed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts the pages of a book
|
||||
*
|
||||
* @param book <p>The book to encrypt</p>
|
||||
* @param style <p>The encryption style to use</p>
|
||||
* @param integerKey <p>The encryption key to use</p>
|
||||
* @param player <p>The player trying to encrypt a book</p>
|
||||
* @param book <p>The book to encrypt</p>
|
||||
* @param style <p>The encryption style to use</p>
|
||||
* @param aesConfiguration <p>The AES configuration to use, if encrypting using AES</p>
|
||||
* @param key <p>The encryption key to use</p>
|
||||
* @param encrypt <p>Whether to perform an encryption or a decryption</p>
|
||||
* @return <p>The pages of the book in encrypted form</p>
|
||||
*/
|
||||
@Nullable
|
||||
public static List<String> encryptBookPages(@NotNull BookMeta book, @NotNull EncryptionStyle style,
|
||||
@NotNull String integerKey, @NotNull Player player) {
|
||||
public static List<String> encryptDecryptBookPages(@NotNull BookMeta book, @NotNull EncryptionStyle style,
|
||||
@Nullable AESConfiguration aesConfiguration, @NotNull String key,
|
||||
boolean encrypt) {
|
||||
Encryptor encryptor = switch (style) {
|
||||
case DNA -> new GenenCrypt(EncryptionHelper.getNumberKeyFromStringKey(key));
|
||||
case SUBSTITUTION -> new SubstitutionCipher(EncryptionHelper.getNumberKeyFromStringKey(key));
|
||||
case AES -> {
|
||||
if (aesConfiguration == null) {
|
||||
throw new IllegalArgumentException("Attempted to perform AES encryption without a valid AES configuration");
|
||||
} else {
|
||||
yield new AES(aesConfiguration);
|
||||
}
|
||||
}
|
||||
case ONE_TIME_PAD -> new OneTimePad(key);
|
||||
case MAGIC -> new Magic();
|
||||
};
|
||||
|
||||
List<String> encryptedPages = new ArrayList<>();
|
||||
//Scramble the book's contents
|
||||
if (style == EncryptionStyle.DNA) {
|
||||
//Encrypt the pages using gene-based encryption
|
||||
GenenCrypt gc = new GenenCrypt(integerKey);
|
||||
for (int x = 0; x < book.getPages().size(); x++) {
|
||||
encryptedPages.add(gc.encrypt(book.getPage(x + 1)));
|
||||
for (int x = 0; x < book.getPages().size(); x++) {
|
||||
String text = book.getPage(x + 1);
|
||||
String output;
|
||||
if (encrypt) {
|
||||
output = encryptor.encryptText(text);
|
||||
} else {
|
||||
output = encryptor.decryptText(text);
|
||||
}
|
||||
return encryptedPages;
|
||||
} else if (style == EncryptionStyle.SUBSTITUTION) {
|
||||
//Encrypt the pages using a substitution cipher
|
||||
SubstitutionCipher sc = new SubstitutionCipher();
|
||||
for (int x = 0; x < book.getPages().size(); x++) {
|
||||
encryptedPages.add(sc.encrypt(book.getPage(x + 1), integerKey));
|
||||
if (output == null || output.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return encryptedPages;
|
||||
} else if (style == EncryptionStyle.AES) {
|
||||
AES aes = new AES(AES.generateIV(), AES.generateIV());
|
||||
for (int x = 0; x < book.getPages().size(); x++) {
|
||||
encryptedPages.add(aes.encryptDecryptText(book.getPage(x + 1), integerKey, true));
|
||||
}
|
||||
return encryptedPages;
|
||||
} else {
|
||||
BooksWithoutBorders.sendErrorMessage(player, "Invalid encryption style encountered!");
|
||||
return null;
|
||||
encryptedPages.add(output);
|
||||
}
|
||||
return encryptedPages;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,9 +148,6 @@ public final class EncryptionHelper {
|
||||
@Nullable
|
||||
public static ItemStack encryptBook(Player player, boolean mainHand, @NotNull String key,
|
||||
@NotNull EncryptionStyle style, @NotNull String groupName) {
|
||||
//converts user supplied key into integer form
|
||||
String integerKey = EncryptionHelper.getNumberKeyFromStringKey(key);
|
||||
|
||||
BookMeta book = InventoryHelper.getHeldBookMetadata(player, mainHand);
|
||||
if (book == null) {
|
||||
BooksWithoutBorders.sendErrorMessage(player, "Unable to get metadata from the held book!");
|
||||
@@ -128,14 +159,18 @@ public final class EncryptionHelper {
|
||||
return null;
|
||||
}
|
||||
|
||||
String hashedKey = sha256(key);
|
||||
AESConfiguration configuration = AESConfiguration.getNewConfiguration(hashedKey);
|
||||
|
||||
//Save the book's un-encrypted contents to a file
|
||||
BookMeta newMetadata = saveBookPlaintext(groupName, player, book, integerKey);
|
||||
BookMeta newMetadata = saveBookPlaintext(groupName, player, book, style, hashedKey, configuration);
|
||||
if (newMetadata == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//Get the encrypted pages
|
||||
List<String> encryptedPages = EncryptionHelper.encryptBookPages(book, style, integerKey, player);
|
||||
List<String> encryptedPages = EncryptionHelper.encryptDecryptBookPages(book, style, configuration, hashedKey,
|
||||
true);
|
||||
if (encryptedPages == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -179,19 +214,22 @@ public final class EncryptionHelper {
|
||||
/**
|
||||
* Saves a book's plain text to a file
|
||||
*
|
||||
* @param groupName <p>The group who's allowed to decrypt the book, or ""</p>
|
||||
* @param player <p>The player trying to encrypt the book</p>
|
||||
* @param book <p>The book to encrypt</p>
|
||||
* @param integerKey <p>The key used to encrypt the book</p>
|
||||
* @param groupName <p>The group who's allowed to decrypt the book, or ""</p>
|
||||
* @param player <p>The player trying to encrypt the book</p>
|
||||
* @param book <p>The book to encrypt</p>
|
||||
* @param encryptionStyle <p>The encryption style used for the book</p>
|
||||
* @param key <p>The key used to encrypt the book</p>
|
||||
* @param aesConfiguration <p>The AES configuration to use, if encrypting using AES</p>
|
||||
* @return <p>The new metadata for the book, or null if it could not be saved</p>
|
||||
*/
|
||||
@Nullable
|
||||
private static BookMeta saveBookPlaintext(@NotNull String groupName, @NotNull Player player,
|
||||
@NotNull BookMeta book, @NotNull String integerKey) {
|
||||
@NotNull BookMeta book, @NotNull EncryptionStyle encryptionStyle,
|
||||
@NotNull String key, @NotNull AESConfiguration aesConfiguration) {
|
||||
BookMeta newMetadata = book;
|
||||
boolean wasSaved;
|
||||
if (groupName.trim().isEmpty()) {
|
||||
wasSaved = saveEncryptedBook(player, book, integerKey);
|
||||
wasSaved = saveEncryptedBook(player, book, encryptionStyle, key, aesConfiguration);
|
||||
} else {
|
||||
newMetadata = saveEncryptedBookForGroup(player, book, groupName);
|
||||
wasSaved = newMetadata != null;
|
||||
@@ -209,10 +247,12 @@ public final class EncryptionHelper {
|
||||
* @param player <p>The player trying to load the book</p>
|
||||
* @param key <p>The encryption key/password for decryption</p>
|
||||
* @param deleteEncryptedFile <p>Whether to delete the plaintext file after decryption is finished</p>
|
||||
* @param forceDecrypt <p>Whether to force decryption using the stored key</p>
|
||||
* @return <p>The loaded book, or null if no book could be loaded</p>
|
||||
*/
|
||||
@Nullable
|
||||
public static ItemStack loadEncryptedBook(@NotNull Player player, @NotNull String key, boolean deleteEncryptedFile) {
|
||||
public static ItemStack loadEncryptedBook(@NotNull Player player, @NotNull String key, boolean deleteEncryptedFile,
|
||||
boolean forceDecrypt) {
|
||||
ItemStack heldBook = InventoryHelper.getHeldBook(player, true);
|
||||
BookMeta bookMetadata = (BookMeta) heldBook.getItemMeta();
|
||||
String path = BooksWithoutBorders.getConfiguration().getEncryptedBookPath();
|
||||
@@ -221,20 +261,24 @@ public final class EncryptionHelper {
|
||||
return null;
|
||||
}
|
||||
|
||||
String fileName = "[" + key + "]" + BookHelper.getBookFile(bookMetadata, player, true);
|
||||
String fileName = BookHelper.getBookFile(bookMetadata, player, true);
|
||||
fileName = cleanString(fileName);
|
||||
|
||||
|
||||
File file = new File(path + fileName + ".yml");
|
||||
if (!file.isFile()) {
|
||||
file = new File(path + fileName + ".txt");
|
||||
|
||||
if (!file.isFile()) {
|
||||
BooksWithoutBorders.sendErrorMessage(player, "Incorrect decryption key!");
|
||||
BooksWithoutBorders.sendErrorMessage(player, "Book not found!");
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
bookMetadata = BookToFromTextHelper.bookFromFile(file, bookMetadata);
|
||||
bookMetadata = BookToFromTextHelper.encryptedBookFromYml(file, bookMetadata, key, forceDecrypt);
|
||||
if (bookMetadata == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
BooksWithoutBorders.sendErrorMessage(player, "Decryption failed!");
|
||||
return null;
|
||||
@@ -259,6 +303,104 @@ public final class EncryptionHelper {
|
||||
return newBook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an encrypted book
|
||||
*
|
||||
* @param player <p>The player trying to load the book</p>
|
||||
* @param key <p>The encryption key/password for decryption</p>
|
||||
* @param deleteEncryptedFile <p>Whether to delete the plaintext file after decryption is finished</p>
|
||||
* @return <p>The loaded book, or null if no book could be loaded</p>
|
||||
*/
|
||||
@Nullable
|
||||
public static ItemStack loadEncryptedBookLegacy(@NotNull Player player, @NotNull String key, boolean deleteEncryptedFile) {
|
||||
BooksWithoutBorders.sendErrorMessage(player, "Attempting legacy decryption");
|
||||
ItemStack heldBook = InventoryHelper.getHeldBook(player, true);
|
||||
BookMeta bookMetadata = (BookMeta) heldBook.getItemMeta();
|
||||
String path = BooksWithoutBorders.getConfiguration().getEncryptedBookPath();
|
||||
|
||||
if (bookMetadata == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder integerKey = new StringBuilder();
|
||||
for (int x = 0; x < key.length(); x++) {
|
||||
integerKey.append(Character.getNumericValue(Character.codePointAt(key, x)));
|
||||
}
|
||||
|
||||
String fileName = "[" + integerKey + "]" + BookHelper.getBookFile(bookMetadata, player, true);
|
||||
fileName = cleanString(fileName).replace(" ", "_");
|
||||
|
||||
File file = new File(path + fileName + ".yml");
|
||||
if (!file.isFile()) {
|
||||
file = new File(path + fileName + ".txt");
|
||||
|
||||
if (!file.isFile()) {
|
||||
BooksWithoutBorders.sendErrorMessage(player, "Incorrect decryption key!");
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
bookMetadata = BookToFromTextHelper.bookFromFile(file, bookMetadata);
|
||||
if (bookMetadata == null) {
|
||||
BooksWithoutBorders.sendErrorMessage(player, "Decryption failed!");
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
BooksWithoutBorders.sendErrorMessage(player, "Decryption failed!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (deleteEncryptedFile) {
|
||||
Logger logger = BooksWithoutBorders.getInstance().getLogger();
|
||||
try {
|
||||
if (!file.delete()) {
|
||||
logger.log(Level.SEVERE, "Book encryption data failed to delete upon decryption!\n" +
|
||||
"File location:" + file.getPath());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Book encryption data failed to delete upon decryption!\nFile location:" + file.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
ItemStack newBook = new ItemStack(Material.WRITTEN_BOOK);
|
||||
newBook.setItemMeta(bookMetadata);
|
||||
newBook.setAmount(InventoryHelper.getHeldBook(player, true).getAmount());
|
||||
return newBook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a byte array to a hexadecimal string
|
||||
*
|
||||
* @param bytes <p>The bytes to convert</p>
|
||||
* @return <p>The resulting hexadecimal string</p>
|
||||
*/
|
||||
public static String bytesToHex(byte[] bytes) {
|
||||
byte[] hexChars = new byte[bytes.length * 2];
|
||||
for (int j = 0; j < bytes.length; j++) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
|
||||
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
|
||||
}
|
||||
return new String(hexChars, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string of hexadecimals to a byte array
|
||||
*
|
||||
* @param input <p>The hexadecimal input to parse</p>
|
||||
* @return <p>The resulting byte array</p>
|
||||
*/
|
||||
public static byte[] hexStringToByteArray(@NotNull String input) {
|
||||
int len = input.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(input.charAt(i), 16) << 4) +
|
||||
Character.digit(input.charAt(i + 1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves an encrypted book to be decryptable for the given group
|
||||
*
|
||||
@@ -299,8 +441,7 @@ public final class EncryptionHelper {
|
||||
bookMetadata.setLore(newLore);
|
||||
|
||||
//Save file
|
||||
File file = (BooksWithoutBorders.getConfiguration().getUseYml()) ? new File(path + fileName + ".yml") :
|
||||
new File(path + fileName + ".txt");
|
||||
File file = new File(path + fileName + ".yml");
|
||||
if (!file.isFile()) {
|
||||
try {
|
||||
BookToFromTextHelper.bookToYml(path, fileName, bookMetadata);
|
||||
@@ -316,27 +457,30 @@ public final class EncryptionHelper {
|
||||
/**
|
||||
* Saves an encrypted book to be decryptable for the given user
|
||||
*
|
||||
* @param player <p>The player encrypting the book</p>
|
||||
* @param bookMetaData <p>Metadata for the book to encrypt</p>
|
||||
* @param key <p>The key to use for encryption</p>
|
||||
* @param player <p>The player encrypting the book</p>
|
||||
* @param bookMetaData <p>Metadata for the book to encrypt</p>
|
||||
* @param encryptionStyle <p>The style of encryption used</p>
|
||||
* @param key <p>The key to use for encryption</p>
|
||||
* @param aesConfiguration <p>The AES configuration to use if encrypting with AES</p>
|
||||
* @return <p>The new encrypted metadata for the book, or null if encryption failed</p>
|
||||
*/
|
||||
@NotNull
|
||||
private static Boolean saveEncryptedBook(@NotNull Player player, @NotNull BookMeta bookMetaData, @NotNull String key) {
|
||||
private static Boolean saveEncryptedBook(@NotNull Player player, @NotNull BookMeta bookMetaData,
|
||||
@NotNull EncryptionStyle encryptionStyle, @NotNull String key,
|
||||
@Nullable AESConfiguration aesConfiguration) {
|
||||
String path = BooksWithoutBorders.getConfiguration().getEncryptedBookPath();
|
||||
|
||||
String fileName = "[" + key + "]" + BookHelper.getBookFile(bookMetaData, player, true);
|
||||
String fileName = BookHelper.getBookFile(bookMetaData, player, true);
|
||||
fileName = cleanString(fileName);
|
||||
|
||||
//cancels saving if file is already encrypted
|
||||
File file = (BooksWithoutBorders.getConfiguration().getUseYml()) ? new File(path + fileName + ".yml") :
|
||||
new File(path + fileName + ".txt");
|
||||
File file = new File(path + fileName + ".yml");
|
||||
if (file.isFile()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
BookToFromTextHelper.bookToYml(path, fileName, bookMetaData);
|
||||
BookToFromTextHelper.encryptedBookToYml(path, fileName, bookMetaData, encryptionStyle, key, aesConfiguration);
|
||||
} catch (IOException exception) {
|
||||
BooksWithoutBorders.sendErrorMessage(player, "Encryption failed!");
|
||||
return false;
|
||||
|
@@ -1,8 +1,6 @@
|
||||
Options:
|
||||
# The language to use. Only "en" is built-in, but custom languages can be added
|
||||
Language: "en"
|
||||
# Whether to use YAML for saved books instead of just storing them as text
|
||||
Save_Books_in_Yaml_Format: true
|
||||
# The maximum number of duplicates of a saved book allowed
|
||||
Max_Number_of_Duplicates: 5
|
||||
# The separator used to separate the book title and the book author. While this is a ',' by default for backwards
|
||||
@@ -34,4 +32,8 @@ Options:
|
||||
# vanilla behavior where a copy of a copy cannot be copied further.
|
||||
Change_Generation_On_Copy: false
|
||||
# Whether to enable hitting a chiseled bookshelf while sneaking to see the shelf's contents.
|
||||
Enable_Book_Peeking: true
|
||||
Enable_Book_Peeking: true
|
||||
# Whether to use true AES encryption when encrypting and decrypting books. While the hashed password used for
|
||||
# encryption is still stored in the book file, the real contents of the book are not. Admin decrypt can be used to
|
||||
# peek at books, if an admin gets a hold of one, but only the encrypted AES cypher text is stored in the book.
|
||||
Use_Real_Encryption: false
|
@@ -4,6 +4,7 @@ en:
|
||||
SUCCESS_CLEARED: "Book cleared!"
|
||||
SUCCESS_DECRYPTED: "Book decrypted!"
|
||||
SUCCESS_AUTO_DECRYPTED: "Book auto-decrypted!"
|
||||
SUCCESS_DELETED: "\"{file}\" deleted successfully"
|
||||
ERROR_PLAYER_ONLY: "This command can only be used by a player!"
|
||||
ERROR_NOT_HOLDING_WRITTEN_BOOK: "You must be holding a written book to {action} it!"
|
||||
ERROR_NOT_HOLDING_WRITABLE_BOOK: "You must be holding a writable book to {action} it!"
|
||||
@@ -21,6 +22,11 @@ en:
|
||||
ERROR_DECRYPT_FAILED: "Failed to decrypt book!"
|
||||
ERROR_ENCRYPTED_DIRECTORY_EMPTY_OR_MISSING: "Could not find any encrypted files!"
|
||||
ERROR_ENCRYPTED_BOOK_UNKNOWN: "No matching encrypted book found!"
|
||||
ERROR_DELETE_EMPTY: "No files available to delete!"
|
||||
ERROR_INCORRECT_FILE_NAME: "Incorrect file name!"
|
||||
ERROR_DELETE_FAILED_SILENT: "Deletion failed without an exception!"
|
||||
ERROR_DELETE_FAILED_EXCEPTION: "Deletion failed!"
|
||||
ERROR_ENCRYPT_NO_KEY: "You must specify a key to encrypt a book!"
|
||||
NEUTRAL_COMMANDS_HEADER: |
|
||||
&e[] denote optional parameters
|
||||
<> denote required parameters
|
||||
|
@@ -4,6 +4,7 @@ import net.knarcraft.bookswithoutborders.encryption.GenenCrypt;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
public class GenenCryptTest {
|
||||
|
||||
@@ -11,8 +12,11 @@ public class GenenCryptTest {
|
||||
public void encryptDecryptTest() {
|
||||
GenenCrypt gc = new GenenCrypt("Another Key");
|
||||
gc.printCodonTable();
|
||||
String encrypted = gc.encrypt("Hello World!");
|
||||
assertEquals("HELLO WORLD!", gc.decrypt(encrypted));
|
||||
String encrypted = gc.encryptText("Hello World!");
|
||||
|
||||
assertNotNull(encrypted);
|
||||
|
||||
assertEquals("HELLO WORLD!", gc.decryptText(encrypted));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -3,22 +3,31 @@ package net.knarcraft.bookswithoutborders.encryption;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
|
||||
public class AESTest {
|
||||
|
||||
@Test
|
||||
public void encryptDecryptTest() {
|
||||
String plainText = "A lot of text";
|
||||
String password = "abc123";
|
||||
String plainText = "Flåklypa";
|
||||
String password = "TqOZdpY9RjjjVE9JjCWVecUYObv5MYidByrpI3cxjoY=";
|
||||
|
||||
AES aes = new AES(AES.generateIV(), AES.generateIV());
|
||||
System.out.println("Plaintext: " + plainText);
|
||||
System.out.println("Encryption password: " + password);
|
||||
|
||||
String encrypted = aes.encryptDecryptText(plainText, password, true);
|
||||
assertNotSame(encrypted, plainText);
|
||||
assertNotNull(encrypted);
|
||||
String decrypted = aes.encryptDecryptText(encrypted, password, false);
|
||||
AESConfiguration configuration = new AESConfiguration(new byte[]{-85, 103, -82, 71, 119, 28, 73, -75, -81, 102, -127, -125, -8, -75, 81, -111},
|
||||
new byte[]{(byte) 104, -42, 63, 31, -120, -2, 14, -119, 35, 122, 109, -64, 122, 117, 33, -85}, password);
|
||||
AES aes = new AES(configuration);
|
||||
|
||||
String cypherText = aes.encryptText(plainText);
|
||||
System.out.println("Cypher text: " + cypherText);
|
||||
|
||||
assertNotNull(cypherText);
|
||||
assertNotEquals(plainText, cypherText);
|
||||
|
||||
String decrypted = aes.decryptText(cypherText);
|
||||
System.out.println("Decrypted: " + decrypted);
|
||||
assertEquals(plainText, decrypted);
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,28 @@
|
||||
package net.knarcraft.bookswithoutborders.encryption;
|
||||
|
||||
import net.knarcraft.bookswithoutborders.utility.EncryptionHelper;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
|
||||
public class GenenCryptTest {
|
||||
|
||||
@Test
|
||||
public void encryptDecryptTest() {
|
||||
String encryptionKey = EncryptionHelper.getNumberKeyFromStringKey("My secret password!");
|
||||
String plaintext = "Very secret &4colored&r message.";
|
||||
GenenCrypt genenCrypt = new GenenCrypt(encryptionKey);
|
||||
|
||||
String cypherText = genenCrypt.encryptText(plaintext);
|
||||
|
||||
assertNotNull(cypherText);
|
||||
assertNotSame(cypherText, plaintext);
|
||||
|
||||
String decrypted = genenCrypt.decryptText(cypherText);
|
||||
|
||||
assertEquals(plaintext.toUpperCase(), decrypted);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
package net.knarcraft.bookswithoutborders.encryption;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
|
||||
public class OneTimePadTest {
|
||||
|
||||
@Test
|
||||
public void oneTimePadTest() {
|
||||
String plaintext = "Very secret text that should be kept secret";
|
||||
String key = "Very secret key!";
|
||||
|
||||
OneTimePad oneTimePad = new OneTimePad(key);
|
||||
String cypherText = oneTimePad.encryptText(plaintext);
|
||||
|
||||
assertNotNull(cypherText);
|
||||
assertNotSame(plaintext, cypherText);
|
||||
|
||||
String decrypted = oneTimePad.decryptText(cypherText);
|
||||
assertEquals(plaintext, decrypted);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
package net.knarcraft.bookswithoutborders.encryption;
|
||||
|
||||
import net.knarcraft.bookswithoutborders.utility.EncryptionHelper;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
|
||||
public class SubstitutionCipherTest {
|
||||
|
||||
@Test
|
||||
public void encryptDecryptTest() {
|
||||
String plaintext = "Very secret text that should be kept secret";
|
||||
String integerKey = EncryptionHelper.getNumberKeyFromStringKey("Very secret key!");
|
||||
SubstitutionCipher substitutionCipher = new SubstitutionCipher(integerKey);
|
||||
String cypherText = substitutionCipher.encryptText(plaintext);
|
||||
|
||||
assertNotNull(cypherText);
|
||||
assertNotSame(plaintext, cypherText);
|
||||
|
||||
String decrypted = substitutionCipher.decryptText(cypherText);
|
||||
assertEquals(plaintext, decrypted);
|
||||
}
|
||||
|
||||
}
|
@@ -10,7 +10,7 @@ public class EncryptionHelperTest {
|
||||
@Test
|
||||
public void getNumberKeyFromStringKey() {
|
||||
String numberKey = EncryptionHelper.getNumberKeyFromStringKey("hello");
|
||||
assertEquals("1714212124", numberKey);
|
||||
assertEquals("104, 101, 108, 108, 111", numberKey);
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user