From 707d8d40416bdde73aa6f85735479c6479eb8721 Mon Sep 17 00:00:00 2001 From: Mino <1381835+Minoost@users.noreply.github.com> Date: Wed, 18 Mar 2020 12:44:50 +0900 Subject: [PATCH] WIP --- Dalamud.Bootstrap/Crypto/Blowfish.cs | 343 +++++++++++++++------------ 1 file changed, 195 insertions(+), 148 deletions(-) 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; + } } }