Skip to content

Instantly share code, notes, and snippets.

@Myrkie
Created July 1, 2025 08:00
Show Gist options
  • Save Myrkie/05359014e9db1092bf26951f75be1755 to your computer and use it in GitHub Desktop.
Save Myrkie/05359014e9db1092bf26951f75be1755 to your computer and use it in GitHub Desktop.
Csharp implementation for decrypting and encrypting FTPRush RushSite.xml passwords
using System.Text;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Engines;
namespace ExtractRushPass
{
public static class FtpRushCrypto
{
private static readonly byte[] Secret = "th3m3ugaysshit9"u8.ToArray();
private static readonly byte[] IvInit = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
// Derived keys and IVs - computed once at class init
private static readonly byte[] Key; // 32 bytes
private static readonly byte[] Iv; // 8 bytes
private static readonly byte[] IvShort; // 8 bytes
static FtpRushCrypto()
{
var ripemd256 = new RipeMD256Digest();
ripemd256.BlockUpdate(Secret, 0, Secret.Length);
Key = new byte[32];
ripemd256.DoFinal(Key, 0);
var blowfishEngine = new BlowfishEngine();
blowfishEngine.Init(true, new KeyParameter(Key));
Iv = new byte[8];
blowfishEngine.ProcessBlock(IvInit, 0, Iv, 0);
IvShort = new byte[8];
blowfishEngine.ProcessBlock(Iv, 0, IvShort, 0);
}
/// <summary>
/// Encrypts plainPassword to FTPRush encrypted hex password string.
/// Returns uppercase hex string.
/// </summary>
public static string EncryptPassword(string plainPassword)
{
ArgumentNullException.ThrowIfNull(plainPassword);
byte[] passBytes = Encoding.ASCII.GetBytes(plainPassword);
int ctLength = passBytes.Length;
switch (ctLength)
{
case 0:
return "";
case < 8:
{
// Short password XOR with IvShort
byte[] ct = new byte[ctLength];
for (int i = 0; i < ctLength; i++)
ct[i] = (byte)(passBytes[i] ^ IvShort[i]);
return Convert.ToHexString(ct);
}
default:
{
// Long password encryption with custom CBC
int fullBlocksLen = ctLength - ctLength % 8;
int modulus = ctLength % 8;
byte[] cbcIv = new byte[8];
Array.Copy(Iv, cbcIv, 8);
var blowfishEngine = new BlowfishEngine();
blowfishEngine.Init(true, new KeyParameter(Key));
byte[] ct = new byte[ctLength];
// Encrypt full blocks in custom CBC mode
for (int blockIndex = 0; blockIndex < fullBlocksLen; blockIndex += 8)
{
byte[] ivOld = new byte[8];
Array.Copy(cbcIv, ivOld, 8);
for (int i = 0; i < 8; i++)
{
cbcIv[i] ^= passBytes[blockIndex + i];
}
blowfishEngine.ProcessBlock(cbcIv, 0, ct, blockIndex);
for (int i = 0; i < 8; i++)
{
cbcIv[i] = (byte)(ivOld[i] ^ ct[blockIndex + i]);
}
}
if (modulus <= 0) return Convert.ToHexString(ct);
{
byte[] ivModulus = new byte[8];
blowfishEngine.ProcessBlock(cbcIv, 0, ivModulus, 0);
// XOR the last partial bytes with ivModulus
for (int i = 0; i < modulus; i++)
{
ct[fullBlocksLen + i] = (byte)(passBytes[fullBlocksLen + i] ^ ivModulus[i]);
}
}
return Convert.ToHexString(ct);
}
}
}
/// <summary>
/// Decrypts an FTPRush encrypted hex password string.
/// Returns plaintext string.
/// </summary>
public static string DecryptPassword(string encryptedHex)
{
ArgumentNullException.ThrowIfNull(encryptedHex);
if (encryptedHex.Length % 2 != 0)
throw new ArgumentException("Invalid encrypted hex string length.");
int ctLength = encryptedHex.Length / 2;
byte[] ptBytes = Convert.FromHexString(encryptedHex);
switch (ctLength)
{
case 0:
return "";
case < 8:
{
// Short password XOR with IvShort
byte[] pt = new byte[ctLength];
for (int i = 0; i < ctLength; i++)
pt[i] = (byte)(ptBytes[i] ^ IvShort[i]);
return Encoding.ASCII.GetString(pt);
}
default:
{
// Long password decryption with custom CBC
int fullBlocksLen = ctLength - ctLength % 8;
int modulus = ctLength % 8;
byte[] cbcIv = new byte[8];
Array.Copy(Iv, cbcIv, 8);
var blowfishEngine = new BlowfishEngine();
blowfishEngine.Init(false, new KeyParameter(Key));
byte[] pt = new byte[ctLength];
// Decrypt full blocks with custom CBC
for (int blockIndex = 0; blockIndex < fullBlocksLen; blockIndex += 8)
{
byte[] tmp = new byte[8];
blowfishEngine.ProcessBlock(ptBytes, blockIndex, tmp, 0);
for (int i = 0; i < 8; i++)
{
pt[blockIndex + i] = (byte)(tmp[i] ^ cbcIv[i]);
}
for (int i = 0; i < 8; i++)
{
cbcIv[i] ^= ptBytes[blockIndex + i];
}
}
if (modulus <= 0) return Encoding.ASCII.GetString(pt);
{
byte[] ivModulus = new byte[8];
blowfishEngine.Init(true, new KeyParameter(Key));
blowfishEngine.ProcessBlock(cbcIv, 0, ivModulus, 0);
// XOR the last partial bytes with ivModulus
for (int i = 0; i < modulus; i++)
{
pt[fullBlocksLen + i] = (byte)(ptBytes[fullBlocksLen + i] ^ ivModulus[i]);
}
}
return Encoding.ASCII.GetString(pt);
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment