diff --git a/src/Jackett/Jackett.csproj b/src/Jackett/Jackett.csproj
index 78732f22e..7e5476a0f 100644
--- a/src/Jackett/Jackett.csproj
+++ b/src/Jackett/Jackett.csproj
@@ -319,6 +319,7 @@
+
diff --git a/src/Jackett/Services/ProtectionService.cs b/src/Jackett/Services/ProtectionService.cs
index 710b4f154..58f0bfd7d 100644
--- a/src/Jackett/Services/ProtectionService.cs
+++ b/src/Jackett/Services/ProtectionService.cs
@@ -6,6 +6,7 @@ using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
+using Jackett.Utils;
namespace Jackett.Services
{
@@ -18,6 +19,7 @@ namespace Jackett.Services
public class ProtectionService : IProtectionService
{
DataProtectionScope PROTECTION_SCOPE = DataProtectionScope.LocalMachine;
+ private const string JACKETT_KEY = "JACKETT_KEY";
const string APPLICATION_KEY = "Dvz66r3n8vhTGip2/quiw5ISyM37f7L2iOdupzdKmzkvXGhAgQiWK+6F+4qpxjPVNks1qO7LdWuVqRlzgLzeW8mChC6JnBMUS1Fin4N2nS9lh4XPuCZ1che75xO92Nk2vyXUo9KSFG1hvEszAuLfG2Mcg1r0sVyVXd2gQDU/TbY=";
IServerService serverService;
@@ -34,6 +36,34 @@ namespace Jackett.Services
}
public string Protect(string plainText)
+ {
+ var jackettKey = Environment.GetEnvironmentVariable(JACKETT_KEY);
+
+ if (jackettKey == null)
+ {
+ return ProtectDefaultMethod(plainText);
+ }
+ else
+ {
+ return ProtectUsingKey(plainText, jackettKey);
+ }
+ }
+
+ public string UnProtect(string plainText)
+ {
+ var jackettKey = Environment.GetEnvironmentVariable(JACKETT_KEY);
+
+ if (jackettKey == null)
+ {
+ return UnProtectDefaultMethod(plainText);
+ }
+ else
+ {
+ return UnProtectUsingKey(plainText, jackettKey);
+ }
+ }
+
+ private string ProtectDefaultMethod(string plainText)
{
if (string.IsNullOrEmpty(plainText))
return string.Empty;
@@ -72,7 +102,7 @@ namespace Jackett.Services
return Convert.ToBase64String(protectedBytes);
}
- public string UnProtect(string plainText)
+ private string UnProtectDefaultMethod(string plainText)
{
if (string.IsNullOrEmpty(plainText))
return string.Empty;
@@ -111,6 +141,16 @@ namespace Jackett.Services
return Encoding.UTF8.GetString(unprotectedBytes);
}
+ private string ProtectUsingKey(string plainText, string key)
+ {
+ return StringCipher.Encrypt(plainText, key);
+ }
+
+ private string UnProtectUsingKey(string plainText, string key)
+ {
+ return StringCipher.Decrypt(plainText, key);
+ }
+
public void Protect(T obj)
{
var type = obj.GetType();
diff --git a/src/Jackett/Utils/StringCipher.cs b/src/Jackett/Utils/StringCipher.cs
new file mode 100644
index 000000000..17124f660
--- /dev/null
+++ b/src/Jackett/Utils/StringCipher.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Jackett.Utils
+{
+ public static class StringCipher
+ {
+ // This constant is used to determine the keysize of the encryption algorithm in bits.
+ // We divide this by 8 within the code below to get the equivalent number of bytes.
+ private const int Keysize = 256;
+
+ // This constant determines the number of iterations for the password bytes generation function.
+ private const int DerivationIterations = 1000;
+
+ public static string Encrypt(string plainText, string passPhrase)
+ {
+ // Salt and IV is randomly generated each time, but is preprended to encrypted cipher text
+ // so that the same Salt and IV values can be used when decrypting.
+ var saltStringBytes = Generate256BitsOfRandomEntropy();
+ var ivStringBytes = Generate256BitsOfRandomEntropy();
+ var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
+ using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
+ {
+ var keyBytes = password.GetBytes(Keysize / 8);
+ using (var symmetricKey = new RijndaelManaged())
+ {
+ symmetricKey.BlockSize = 256;
+ symmetricKey.Mode = CipherMode.CBC;
+ symmetricKey.Padding = PaddingMode.PKCS7;
+ using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
+ {
+ using (var memoryStream = new MemoryStream())
+ {
+ using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
+ {
+ cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
+ cryptoStream.FlushFinalBlock();
+ // Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes.
+ var cipherTextBytes = saltStringBytes;
+ cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
+ cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
+ memoryStream.Close();
+ cryptoStream.Close();
+ return Convert.ToBase64String(cipherTextBytes);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public static string Decrypt(string cipherText, string passPhrase)
+ {
+ // Get the complete stream of bytes that represent:
+ // [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText]
+ var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
+ // Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes.
+ var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
+ // Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes.
+ var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
+ // Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string.
+ var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray();
+
+ using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
+ {
+ var keyBytes = password.GetBytes(Keysize / 8);
+ using (var symmetricKey = new RijndaelManaged())
+ {
+ symmetricKey.BlockSize = 256;
+ symmetricKey.Mode = CipherMode.CBC;
+ symmetricKey.Padding = PaddingMode.PKCS7;
+ using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
+ {
+ using (var memoryStream = new MemoryStream(cipherTextBytes))
+ {
+ using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
+ {
+ var plainTextBytes = new byte[cipherTextBytes.Length];
+ var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
+ memoryStream.Close();
+ cryptoStream.Close();
+ return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static byte[] Generate256BitsOfRandomEntropy()
+ {
+ var randomBytes = new byte[32]; // 32 Bytes will give us 256 bits.
+ using (var rngCsp = new RNGCryptoServiceProvider())
+ {
+ // Fill the array with cryptographically secure random bytes.
+ rngCsp.GetBytes(randomBytes);
+ }
+ return randomBytes;
+ }
+ }
+}