Encrypt websites with AES.

This ensures that nodes are storing unreadable files, outside of the context of Qortal. For public data, the decryption keys themselves are on-chain, included in the "secret" field of arbitrary transactions. When we introduce the concept of private data, we can simply exclude the secret key from the transaction so that only the owner can decrypt it.

When encrypting the file, I have added the 16 byte initialization vector as a prefix to the cyphertext, and it is then automatically extracted back out when decrypting. This gives us the option to encrypt more than one file with the same key, if we ever need it. Right now, we are using a unique key per file, so it's not actually needed, but it's good to have support.
This commit is contained in:
CalDescent
2021-07-17 14:51:39 +01:00
parent f599aa4852
commit f5b29bad33
5 changed files with 176 additions and 17 deletions

View File

@@ -3,6 +3,7 @@ package org.qortal.test;
import org.junit.Test;
import org.qortal.account.PrivateKeyAccount;
import org.qortal.block.BlockChain;
import org.qortal.crypto.AES;
import org.qortal.crypto.BouncyCastle25519;
import org.qortal.crypto.Crypto;
import org.qortal.test.common.Common;
@@ -10,7 +11,17 @@ import org.qortal.utils.Base58;
import static org.junit.Assert.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Random;
import org.bouncycastle.crypto.agreement.X25519Agreement;
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
@@ -20,6 +31,11 @@ import org.bouncycastle.crypto.params.X25519PublicKeyParameters;
import com.google.common.hash.HashCode;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
public class CryptoTests extends Common {
@Test
@@ -255,4 +271,68 @@ public class CryptoTests extends Common {
assertEquals(expectedProxyPrivateKey, Base58.encode(proxyPrivateKey));
}
@Test
public void testAESFileEncryption() throws NoSuchAlgorithmException, IOException, IllegalBlockSizeException,
InvalidKeyException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException {
// Create temporary directory and file paths
java.nio.file.Path tempDir = Files.createTempDirectory("qortal-tests");
String inputFilePath = tempDir.toString() + File.separator + "inputFile";
String outputFilePath = tempDir.toString() + File.separator + "outputFile";
String decryptedFilePath = tempDir.toString() + File.separator + "decryptedFile";
String reencryptedFilePath = tempDir.toString() + File.separator + "reencryptedFile";
// Generate some dummy data
byte[] randomBytes = new byte[1024];
new Random().nextBytes(randomBytes);
// Write it to the input file
FileOutputStream outputStream = new FileOutputStream(inputFilePath);
outputStream.write(randomBytes);
// Make sure only the input file exists
assertTrue(Files.exists(Paths.get(inputFilePath)));
assertFalse(Files.exists(Paths.get(outputFilePath)));
// Encrypt
SecretKey aesKey = AES.generateKey(256);
AES.encryptFile("AES", aesKey, inputFilePath, outputFilePath);
assertTrue(Files.exists(Paths.get(outputFilePath)));
byte[] encryptedBytes = Files.readAllBytes(Paths.get(outputFilePath));
// Delete the input file
Files.delete(Paths.get(inputFilePath));
assertFalse(Files.exists(Paths.get(inputFilePath)));
// Decrypt
String encryptedFilePath = outputFilePath;
assertFalse(Files.exists(Paths.get(decryptedFilePath)));
AES.decryptFile("AES", aesKey, encryptedFilePath, decryptedFilePath);
assertTrue(Files.exists(Paths.get(decryptedFilePath)));
// Delete the output file
Files.delete(Paths.get(outputFilePath));
assertFalse(Files.exists(Paths.get(outputFilePath)));
// Check that the decrypted file contents matches the original data
byte[] decryptedBytes = Files.readAllBytes(Paths.get(decryptedFilePath));
assertTrue(Arrays.equals(decryptedBytes, randomBytes));
assertEquals(1024, decryptedBytes.length);
// Write the original data back to the input file
outputStream = new FileOutputStream(inputFilePath);
outputStream.write(randomBytes);
// Now encrypt the data one more time using the same key
// This is to ensure the initialization vector produces a different result
AES.encryptFile("AES", aesKey, inputFilePath, reencryptedFilePath);
assertTrue(Files.exists(Paths.get(reencryptedFilePath)));
// Make sure the ciphertexts do not match
byte[] reencryptedBytes = Files.readAllBytes(Paths.get(reencryptedFilePath));
assertFalse(Arrays.equals(encryptedBytes, reencryptedBytes));
}
}