diff --git a/Dalamud.Bootstrap/Crypto/Blowfish.cs b/Dalamud.Bootstrap/Crypto/Blowfish.cs
index eba6d1277..56db5fb99 100644
--- a/Dalamud.Bootstrap/Crypto/Blowfish.cs
+++ b/Dalamud.Bootstrap/Crypto/Blowfish.cs
@@ -4,7 +4,146 @@ using System.Runtime.CompilerServices;
namespace Dalamud.Bootstrap.Crypto
{
- internal unsafe struct BlowfishState
+ ///
+ /// A class that implements Blowfish algorithm.
+ ///
+ ///
+ /// Sole purpose of this class is that to produce the value barely enough to be compatible with the algorithm seen in FINAL FANTASY XIV: A Relam Reborn.
+ /// Therefore, codes are not audited by security experts nor I don't believe that this is a correct implementation.
+ /// Heck, it even uses ECB block cipher mode! (because that's what FFXIV uses)
+ ///
+ /// Please, don't try to use this in production.
+ ///
+ internal sealed class Blowfish
+ {
+ private BlowfishState m_state;
+
+ ///
+ /// Initializes a new instance of the Blowfish class.
+ ///
+ ///
+ /// This function also calculates P-array and S-boxes from the given key which is most expensive operation in the blowfish algorithm.
+ ///
+ /// A secret key. The length of the key must be between 32 and 448 bits.
+ /// The length of the key is either too short or too long.
+ public Blowfish(ReadOnlySpan key)
+ {
+ if (!CheckKeyLength(key.Length))
+ {
+ var message = "The length of the secret key is either too short or too long.";
+ throw new ArgumentException(message, nameof(key));
+ }
+
+ m_state = new BlowfishState(key);
+ }
+
+ ///
+ /// Checks if given key size is supported.
+ ///
+ /// A key size in bytes.
+ /// Returns true if supported, false otherwise.
+ private static bool CheckKeyLength(int length) => length switch
+ {
+ // valid size: 32~448 bits
+ _ when length >= 4 && length <= 56 => true,
+ _ => false,
+ };
+
+ private static bool CheckBufferLength(int length) => length switch
+ {
+ _ when length % 8 == 0 => true,
+ _ => false,
+ };
+
+ public void EncryptInPlace(Span buffer)
+ {
+ // TODO: this is shit
+ throw new NotImplementedException();
+ }
+
+ public void DecryptInPlace(Span buffer)
+ {
+ if (CheckBufferLength(buffer.Length))
+ {
+ throw new ArgumentException("TODO: buffer length", nameof(buffer));
+ }
+
+ unsafe
+ {
+ fixed (byte* pBuffer = buffer)
+ {
+ for (byte* it = pBuffer; it < pBuffer + buffer.Length; it += 8)
+ {
+ DecryptBlockInPlace(it, it);
+ }
+ }
+ }
+ }
+
+ private unsafe void EncryptBlockInPlace(byte* input, byte* output)
+ {
+ var inputBlock = (uint*)input;
+ var outputBlock = (uint*)output;
+
+ var xl = inputBlock[0];
+ var xr = inputBlock[1];
+
+ // will be elided by JIT
+ if (!BitConverter.IsLittleEndian)
+ {
+ xl = BinaryPrimitives.ReverseEndianness(xl);
+ xr = BinaryPrimitives.ReverseEndianness(xr);
+ }
+
+ (xl, xr) = m_state.EncryptBlock(xl, xr);
+
+ // will be elided by JIT
+ if (!BitConverter.IsLittleEndian)
+ {
+ xl = BinaryPrimitives.ReverseEndianness(xl);
+ xr = BinaryPrimitives.ReverseEndianness(xr);
+ }
+
+ outputBlock[0] = xl;
+ outputBlock[1] = xr;
+ }
+
+ private unsafe void DecryptBlockInPlace(byte* input, byte* output)
+ {
+ var inputBlock = (uint*)input;
+ var outputBlock = (uint*)output;
+
+ var xl = inputBlock[0];
+ var xr = inputBlock[1];
+
+ // will be elided by JIT
+ if (!BitConverter.IsLittleEndian)
+ {
+ xl = BinaryPrimitives.ReverseEndianness(xl);
+ xr = BinaryPrimitives.ReverseEndianness(xr);
+ }
+
+ (xl, xr) = m_state.DecryptBlock(xl, xr);
+
+ // will be elided by JIT
+ if (!BitConverter.IsLittleEndian)
+ {
+ xl = BinaryPrimitives.ReverseEndianness(xl);
+ xr = BinaryPrimitives.ReverseEndianness(xr);
+ }
+
+ outputBlock[0] = xl;
+ outputBlock[1] = xr;
+ }
+ }
+
+ ///
+ /// Contains internal state of Blowfish algorithm. (P-box and S-boxes)
+ ///
+ ///
+ /// This struct is even more unsafe than Blowfish because it doesn't have a boundary check at all. (It assumes Blowfish class will)
+ ///
+ internal struct BlowfishState
{
// References:
// https://www.schneier.com/academic/archives/1994/09/description_of_a_new.html
@@ -166,13 +305,15 @@ namespace Dalamud.Bootstrap.Crypto
0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F, 0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6
};
- private fixed uint m_p[PSize];
- private fixed uint m_s0[SSize];
- private fixed uint m_s1[SSize];
- private fixed uint m_s2[SSize];
- private fixed uint m_s3[SSize];
+ // The reason why it's implemented as a value type is that fixed keyword is only available in this type.
+ // We don't want the compiler to produce a boundary check for every iteration.
+ private unsafe fixed uint m_p[PSize];
+ private unsafe fixed uint m_s0[SSize];
+ private unsafe fixed uint m_s1[SSize];
+ private unsafe fixed uint m_s2[SSize];
+ private unsafe fixed uint m_s3[SSize];
- public BlowfishState(ReadOnlySpan key)
+ public unsafe BlowfishState(ReadOnlySpan key)
{
// initializes P-array and S-boxes to initial values.
fixed (uint* pSrc = PInit)
@@ -207,17 +348,7 @@ namespace Dalamud.Bootstrap.Crypto
InitKey(key);
}
-
- // private void CheckKeyLength(ReadOnlySpan key)
- // {
- // // Supported key sizes: 32–448 bits
- // // https://en.wikipedia.org/wiki/Blowfish_(cipher)#The_algorithm
- // if (key.Length < 4 || key.Length > 56)
- // {
- // throw new ArgumentException("Key length must be between from 32 to 448 bits.", nameof(key));
- // }
- // }
-
+
///
/// Encrypts a block.
///
@@ -225,24 +356,27 @@ namespace Dalamud.Bootstrap.Crypto
/// A right side of the block.
public (uint, uint) EncryptBlock(uint xl, uint xr)
{
- // https://en.wikipedia.org/wiki/Feistel_cipher#Construction_details
- for (var i = 0; i < Rounds; i += 2)
+ unsafe
{
- xl ^= m_p[i];
- xr ^= Round(xl);
- xr ^= m_p[i + 1];
- xl ^= Round(xr);
+ // https://en.wikipedia.org/wiki/Feistel_cipher#Construction_details
+ for (var i = 0; i < Rounds; i += 2)
+ {
+ xl ^= m_p[i];
+ xr ^= Round(xl);
+ xr ^= m_p[i + 1];
+ xl ^= Round(xr);
+ }
+
+ xl ^= m_p[16];
+ xr ^= m_p[17];
+
+ // swap(L, R)
+ var temp = xl;
+ xl = xr;
+ xr = temp;
+
+ return (xl, xr);
}
-
- xl ^= m_p[16];
- xr ^= m_p[17];
-
- // swap(L, R)
- var temp = xl;
- xl = xr;
- xr = temp;
-
- return (xl, xr);
}
///
@@ -252,24 +386,27 @@ namespace Dalamud.Bootstrap.Crypto
/// A right side of the blick.
public (uint, uint) DecryptBlock(uint xl, uint xr)
{
- // https://en.wikipedia.org/wiki/Feistel_cipher#Construction_details
- for (var i = Rounds; i > 0; i -= 2)
+ unsafe
{
- xl ^= m_p[i + 1];
- xr ^= Round(xr);
- xr ^= m_p[i];
- xr ^= Round(xr);
+ // https://en.wikipedia.org/wiki/Feistel_cipher#Construction_details
+ for (var i = Rounds; i > 0; i -= 2)
+ {
+ xl ^= m_p[i + 1];
+ xr ^= Round(xr);
+ xr ^= m_p[i];
+ xr ^= Round(xr);
+ }
+
+ xl ^= m_p[1];
+ xr ^= m_p[0];
+
+ // swap(L, R);
+ var temp = xl;
+ xl = xr;
+ xr = temp;
+
+ return (xl, xr);
}
-
- xl ^= m_p[1];
- xr ^= m_p[0];
-
- // swap(L, R);
- var temp = xl;
- xl = xr;
- xr = temp;
-
- return (xl, xr);
}
///
@@ -278,6 +415,7 @@ namespace Dalamud.Bootstrap.Crypto
/// A setup key.
private void InitKey(ReadOnlySpan key)
{
+ unsafe
{
var keyPos = 0;
@@ -303,6 +441,7 @@ namespace Dalamud.Bootstrap.Crypto
}
}
+ unsafe
{
var xl = 0u;
var xr = 0u;
@@ -352,107 +491,15 @@ namespace Dalamud.Bootstrap.Crypto
[MethodImpl(MethodImplOptions.AggressiveInlining /* | MethodImplOptions.AggressiveOptimization */)]
private uint Round(uint x)
{
- return unchecked(
- ((m_s0[x >> 24] + m_s1[(byte)(x >> 16)]) ^ m_s2[(byte)(x >> 8)]) + m_s3[(byte)x]
- );
- }
- }
-
- internal sealed class Blowfish
- {
- private BlowfishState m_state;
-
- ///
- /// Initializes a new instance of the Blowfish class.
- ///
- ///
- /// This function also calculates P-array and S-boxes from the given key. This is most expensive operation in blowfish algorithm.
- ///
- /// A secret key used for blowfish. Key length must be between 32 and 448 bits.
- /// Length of the key is either too short or too long.
- public Blowfish(ReadOnlySpan key)
- {
- m_state = new BlowfishState(key);
- }
-
- public void EncryptInPlace(Span buffer)
- {
- // TODO: this is shit
- }
-
- public void DecryptInPlace(Span buffer)
- {
- if (buffer.Length % 8 != 0)
- {
- throw new ArgumentException("TODO: buffer length", nameof(buffer));
- }
-
unsafe
{
- fixed (byte* pBuffer = buffer)
- {
- for (byte* it = pBuffer; it < pBuffer + buffer.Length; it += 8)
- {
- DecryptBlockInPlace(it, it);
- }
- }
+ // NOTE: this is still sub-optimal
+ return unchecked
+ (
+ ((m_s0[x >> 24] + m_s1[(byte)(x >> 16)]) ^ m_s2[(byte)(x >> 8)]) + m_s3[(byte)x]
+ );
}
- }
-
- private unsafe void EncryptBlockInPlace(byte* input, byte* output)
- {
- var inputBlock = (uint*)input;
- var outputBlock = (uint*)output;
-
- var xl = inputBlock[0];
- var xr = inputBlock[1];
-
- // will be elided by JIT
- if (!BitConverter.IsLittleEndian)
- {
- xl = BinaryPrimitives.ReverseEndianness(xl);
- xr = BinaryPrimitives.ReverseEndianness(xr);
- }
-
- (xl, xr) = m_state.EncryptBlock(xl, xr);
-
- // will be elided by JIT
- if (!BitConverter.IsLittleEndian)
- {
- xl = BinaryPrimitives.ReverseEndianness(xl);
- xr = BinaryPrimitives.ReverseEndianness(xr);
- }
-
- outputBlock[0] = xl;
- outputBlock[1] = xr;
- }
-
- private unsafe void DecryptBlockInPlace(byte* input, byte* output)
- {
- var inputBlock = (uint*)input;
- var outputBlock = (uint*)output;
-
- var xl = inputBlock[0];
- var xr = inputBlock[1];
-
- // will be elided by JIT
- if (!BitConverter.IsLittleEndian)
- {
- xl = BinaryPrimitives.ReverseEndianness(xl);
- xr = BinaryPrimitives.ReverseEndianness(xr);
- }
-
- (xl, xr) = m_state.DecryptBlock(xl, xr);
-
- // will be elided by JIT
- if (!BitConverter.IsLittleEndian)
- {
- xl = BinaryPrimitives.ReverseEndianness(xl);
- xr = BinaryPrimitives.ReverseEndianness(xr);
- }
-
- outputBlock[0] = xl;
- outputBlock[1] = xr;
+
}
}
}