diff --git a/crypto/src/crypto/engines/BlowfishEngine.cs b/crypto/src/crypto/engines/BlowfishEngine.cs index 1152e3d78..23daec9f1 100644 --- a/crypto/src/crypto/engines/BlowfishEngine.cs +++ b/crypto/src/crypto/engines/BlowfishEngine.cs @@ -329,11 +329,15 @@ public void Init( bool forEncryption, ICipherParameters parameters) { - if (!(parameters is KeyParameter)) + if (!(parameters is BlowfishParameters) && !(parameters is KeyParameter)) throw new ArgumentException("invalid parameter passed to Blowfish init - " + Platform.GetTypeName(parameters)); + var blowfishParameters = parameters is BlowfishParameters ? + (BlowfishParameters)parameters : + new BlowfishParameters(((KeyParameter)parameters).GetKey(), extendedKey: false); + this.encrypting = forEncryption; - this.workingKey = ((KeyParameter)parameters).GetKey(); + this.workingKey = blowfishParameters.GetKey(); SetKey(this.workingKey); } @@ -441,11 +445,6 @@ private void ProcessTable( private void SetKey(byte[] key) { - if (key.Length < 4 || key.Length > 56) - { - throw new ArgumentException("key length must be in range 32 to 448 bits"); - } - /* * - comments are from _Applied Crypto_, Schneier, p338 * please be careful comparing the two, AC numbers the @@ -469,7 +468,7 @@ private void SetKey(byte[] key) * (up to P[17]). Repeatedly cycle through the key bits until the * entire P-array has been XOR-ed with the key bits */ - int keyLength = key.Length; + int keyLength = System.Math.Min(key.Length, P_SZ*4); int keyIndex = 0; for (int i=0; i < P_SZ; i++) diff --git a/crypto/src/crypto/parameters/BlowfishParameters.cs b/crypto/src/crypto/parameters/BlowfishParameters.cs new file mode 100644 index 000000000..b8b54ddb2 --- /dev/null +++ b/crypto/src/crypto/parameters/BlowfishParameters.cs @@ -0,0 +1,46 @@ +using System; +using Org.BouncyCastle.Crypto; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class BlowfishParameters + : KeyParameter + { + /** + * Blowfish takes a variable-length key, from 32 bits to 448 bits [1]. + * + * Some implementations like OpenSSL [2] and Nettle [3] do not restrict the key size. + * Other algorithms like bcrypt [4] assume that Blowfish supports keys up to 576 bits, + * which is the maximum size for which all bits of the key will be used + * to initialize the P box, assuming the designed 16 rounds. + * + * For interoperability, BlowfishParameters can be created with an extended key, + * using the `extendedKey` parameter. It is not restricted in length, + * as neither OpenSSL nor Nettle restricts it, but only the first 576 bits + * will be used if longer. + * + * [1] https://datatracker.ietf.org/doc/html/draft-schneier-blowfish-00 + * [2] https://github.com/openssl/openssl/blob/openssl-3.0/crypto/bf/bf_skey.c#L31 + * [3] https://github.com/gnutls/nettle/blob/nettle_3.8.1_release_20220727/blowfish.c#L386 + * [4] https://github.com/bcgit/bc-csharp/blob/release/v2.3/crypto/src/crypto/generators/BCrypt.cs#L587 + */ + private const int MinKeyLen = 4; + private const int MaxKeyLen = 56; + + public BlowfishParameters( + byte[] key, + bool extendedKey = false) + : base(key) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + if (key.Length < MinKeyLen || (!extendedKey && key.Length > MaxKeyLen)) + throw new ArgumentException($"key length must be in range {MinKeyLen * 8} to {MaxKeyLen * 8} bits"); + } + + public bool IsExtendedKey + { + get { return KeyLength > MaxKeyLen; } + } + } +} diff --git a/crypto/test/src/crypto/test/BlowfishTest.cs b/crypto/test/src/crypto/test/BlowfishTest.cs index dd12c2540..34d4c4170 100644 --- a/crypto/test/src/crypto/test/BlowfishTest.cs +++ b/crypto/test/src/crypto/test/BlowfishTest.cs @@ -27,7 +27,29 @@ public override string Name new BlockCipherVectorTest(4, new BlowfishEngine(), new KeyParameter(Hex.Decode("0123456789ABCDEF")), "1111111111111111", "61F9C3802281B096"), new BlockCipherVectorTest(5, new BlowfishEngine(), new KeyParameter(Hex.Decode("FEDCBA9876543210")), "0123456789ABCDEF", "0ACEAB0FC6A0A28D"), new BlockCipherVectorTest(6, new BlowfishEngine(), new KeyParameter(Hex.Decode("7CA110454A1A6E57")), "01A1D6D039776742", "59C68245EB05282B"), - new BlockCipherVectorTest(7, new BlowfishEngine(), new KeyParameter(Hex.Decode("0131D9619DC1376E")), "5CD54CA83DEF57DA", "B1B8CC0B250F09A0") + new BlockCipherVectorTest(7, new BlowfishEngine(), new KeyParameter(Hex.Decode("0131D9619DC1376E")), "5CD54CA83DEF57DA", "B1B8CC0B250F09A0"), + + // with BlowfishParameters + new BlockCipherVectorTest(10, new BlowfishEngine(), new BlowfishParameters(Hex.Decode("0000000000000000")), "0000000000000000", "4EF997456198DD78"), + new BlockCipherVectorTest(11, new BlowfishEngine(), new BlowfishParameters(Hex.Decode("FFFFFFFFFFFFFFFF")), "FFFFFFFFFFFFFFFF", "51866FD5B85ECB8A"), + new BlockCipherVectorTest(12, new BlowfishEngine(), new BlowfishParameters(Hex.Decode("3000000000000000")), "1000000000000001", "7D856F9A613063F2"), + new BlockCipherVectorTest(13, new BlowfishEngine(), new BlowfishParameters(Hex.Decode("1111111111111111")), "1111111111111111", "2466DD878B963C9D"), + new BlockCipherVectorTest(14, new BlowfishEngine(), new BlowfishParameters(Hex.Decode("0123456789ABCDEF")), "1111111111111111", "61F9C3802281B096"), + new BlockCipherVectorTest(15, new BlowfishEngine(), new BlowfishParameters(Hex.Decode("FEDCBA9876543210")), "0123456789ABCDEF", "0ACEAB0FC6A0A28D"), + new BlockCipherVectorTest(16, new BlowfishEngine(), new BlowfishParameters(Hex.Decode("7CA110454A1A6E57")), "01A1D6D039776742", "59C68245EB05282B"), + new BlockCipherVectorTest(17, new BlowfishEngine(), new BlowfishParameters(Hex.Decode("0131D9619DC1376E")), "5CD54CA83DEF57DA", "B1B8CC0B250F09A0"), + + // with BlowfishParameters and extended keys + new BlockCipherVectorTest(20, new BlowfishEngine(), new BlowfishParameters(Hex.Decode("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), extendedKey: true), "0000000000000000", "4ef997456198dd78"), + new BlockCipherVectorTest(21, new BlowfishEngine(), new BlowfishParameters(Hex.Decode("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), extendedKey: true), "0000000000000000", "4ef997456198dd78"), + new BlockCipherVectorTest(22, new BlowfishEngine(), new BlowfishParameters(Hex.Decode("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), extendedKey: true), "ffffffffffffffff", "51866fd5b85ecb8a"), + new BlockCipherVectorTest(23, new BlowfishEngine(), new BlowfishParameters(Hex.Decode("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), extendedKey: true), "ffffffffffffffff", "51866fd5b85ecb8a"), + new BlockCipherVectorTest(24, new BlowfishEngine(), new BlowfishParameters(Hex.Decode("111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"), extendedKey: true), "1111111111111111", "2466dd878b963c9d"), + new BlockCipherVectorTest(25, new BlowfishEngine(), new BlowfishParameters(Hex.Decode("11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"), extendedKey: true), "1111111111111111", "2466dd878b963c9d"), + new BlockCipherVectorTest(26, new BlowfishEngine(), new BlowfishParameters(Hex.Decode("300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), extendedKey: true), "1000000000000001", "6252d3fc90256722"), + new BlockCipherVectorTest(27, new BlowfishEngine(), new BlowfishParameters(Hex.Decode("30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), extendedKey: true), "1000000000000001", "6252d3fc90256722"), + new BlockCipherVectorTest(28, new BlowfishEngine(), new BlowfishParameters(Hex.Decode("4f8afc23a1daac522510982b41c9186081b2a00537e193d85d004013ce520cc77aeb3c7822668c425adf7a9af977ad0c380f471229dcc73478d6a560ce3bc730df05e975a6d06d4e"), extendedKey: true), "63038f81aff43d3e", "88ccd0c218b35b0b"), + new BlockCipherVectorTest(29, new BlowfishEngine(), new BlowfishParameters(Hex.Decode("4f8afc23a1daac522510982b41c9186081b2a00537e193d85d004013ce520cc77aeb3c7822668c425adf7a9af977ad0c380f471229dcc73478d6a560ce3bc730df05e975a6d06d4e9be8ca0e"), extendedKey: true), "63038f81aff43d3e", "88ccd0c218b35b0b"), }; public BlowfishTest() @@ -63,6 +85,27 @@ public void TestFunction() Assert.AreEqual("key length must be in range 32 to 448 bits", e.Message); } + // key range check -- new BlowfishParameters + try + { + blowfish.Init(true, new BlowfishParameters(new byte[1])); + Fail("no exception"); + } + catch (ArgumentException e) + { + Assert.AreEqual("key length must be in range 32 to 448 bits", e.Message); + } + + try + { + blowfish.Init(true, new BlowfishParameters(new byte[59])); + Fail("no exception"); + } + catch (ArgumentException e) + { + Assert.AreEqual("key length must be in range 32 to 448 bits", e.Message); + } + Assert.AreEqual(Name + ": Okay", resultText); } }