/// <summary> /// Run four bytes through keyed S-boxes and apply MDS matrix. /// </summary> private static DWord F32(DWord x, IList <DWord> k32, int keyLen) { if (keyLen >= 256) { x.B0 = (byte)(P8X8[P04, x.B0] ^ k32[3].B0); x.B1 = (byte)(P8X8[P14, x.B1] ^ k32[3].B1); x.B2 = (byte)(P8X8[P24, x.B2] ^ k32[3].B2); x.B3 = (byte)(P8X8[P34, x.B3] ^ k32[3].B3); } if (keyLen >= 192) { x.B0 = (byte)(P8X8[P03, x.B0] ^ k32[2].B0); x.B1 = (byte)(P8X8[P13, x.B1] ^ k32[2].B1); x.B2 = (byte)(P8X8[P23, x.B2] ^ k32[2].B2); x.B3 = (byte)(P8X8[P33, x.B3] ^ k32[2].B3); } if (keyLen >= 128) { x = MdsTable[0, P8X8[P01, P8X8[P02, x.B0] ^ k32[1].B0] ^ k32[0].B0] ^ MdsTable[1, P8X8[P11, P8X8[P12, x.B1] ^ k32[1].B1] ^ k32[0].B1] ^ MdsTable[2, P8X8[P21, P8X8[P22, x.B2] ^ k32[1].B2] ^ k32[0].B2] ^ MdsTable[3, P8X8[P31, P8X8[P32, x.B3] ^ k32[1].B3] ^ k32[0].B3]; } return(x); }
/// <summary> /// Initialize the Twofish key schedule from key32 /// </summary> private void ReKey() { BuildMds(); // built only first time it is accessed var k32E = new DWord[_key.Length / 2]; var k32O = new DWord[_key.Length / 2]; // even/odd key dwords var k64Cnt = _key.Length / 2; for (var i = 0; i < k64Cnt; i++) { // split into even/odd key dwords k32E[i] = _key[2 * i]; k32O[i] = _key[2 * i + 1]; _sBoxKeys[k64Cnt - 1 - i] = ReedSolomonMdsEncode(k32E[i], k32O[i]); // compute S-box keys using (12,8) Reed-Solomon code over GF(256) } const int subkeyCnt = RoundSubkeys + 2 * Rounds; var keyLen = _key.Length * 4 * 8; for (var i = 0; i < subkeyCnt / 2; i++) { // compute round subkeys for PHT var a = F32((DWord)(i * SubkeyStep), k32E, keyLen); // A uses even key dwords var b = F32((DWord)(i * SubkeyStep + SubkeyBump), k32O, keyLen); // B uses odd key dwords b = RotateLeft(b, 8); _subKeys[2 * i] = a + b; // combine with a PHT _subKeys[2 * i + 1] = RotateLeft(a + 2 * b, SubkeyRotateLeft); } }
/// <summary> /// Decrypt block(s) of data using Twofish. /// </summary> internal void BlockDecrypt(byte[] inputBuffer, int inputOffset, byte[] outputBuffer, int outputBufferOffset) { var x = new DWord[BlockSize / 32]; var input = new DWord[BlockSize / 32]; for (var i = 0; i < BlockSize / 32; i++) { // copy in the block, add whitening input[i] = new DWord(inputBuffer, inputOffset + i * 4); x[i] = input[i] ^ _subKeys[OutputWhiten + i]; } var keyLen = _key.Length * 4 * 8; for (var r = Rounds - 1; r >= 0; r--) { // main Twofish decryption loop var t0 = F32(x[0], _sBoxKeys, keyLen); var t1 = F32(RotateLeft(x[1], 8), _sBoxKeys, keyLen); x[2] = RotateLeft(x[2], 1); x[2] ^= t0 + t1 + _subKeys[RoundSubkeys + 2 * r]; // PHT, round keys x[3] ^= t0 + 2 * t1 + _subKeys[RoundSubkeys + 2 * r + 1]; x[3] = RotateRight(x[3], 1); if (r <= 0) { continue; } // unswap, except for last round t0 = x[0]; x[0] = x[2]; x[2] = t0; t1 = x[1]; x[1] = x[3]; x[3] = t1; } for (var i = 0; i < BlockSize / 32; i++) { // copy out, with whitening x[i] ^= _subKeys[InputWhiten + i]; if (_cipherMode == CipherMode.CBC) { x[i] ^= _iv[i]; _iv[i] = input[i]; } outputBuffer[outputBufferOffset + i * 4 + 0] = x[i].B0; outputBuffer[outputBufferOffset + i * 4 + 1] = x[i].B1; outputBuffer[outputBufferOffset + i * 4 + 2] = x[i].B2; outputBuffer[outputBufferOffset + i * 4 + 3] = x[i].B3; } }
/// <summary> /// Encrypt block(s) of data using Twofish. /// </summary> internal void BlockEncrypt(byte[] inputBuffer, int inputOffset, byte[] outputBuffer, int outputBufferOffset) { var x = new DWord[BlockSize / 32]; for (var i = 0; i < BlockSize / 32; i++) { // copy in the block, add whitening x[i] = new DWord(inputBuffer, inputOffset + i * 4) ^ _subKeys[InputWhiten + i]; if (_cipherMode == CipherMode.CBC) { x[i] ^= _iv[i]; } } var keyLen = _key.Length * 4 * 8; for (var r = 0; r < Rounds; r++) { // main Twofish encryption loop var t0 = F32(x[0], _sBoxKeys, keyLen); var t1 = F32(RotateLeft(x[1], 8), _sBoxKeys, keyLen); x[3] = RotateLeft(x[3], 1); x[2] ^= t0 + t1 + _subKeys[RoundSubkeys + 2 * r]; // PHT, round keys x[3] ^= t0 + 2 * t1 + _subKeys[RoundSubkeys + 2 * r + 1]; x[2] = RotateRight(x[2], 1); if (r >= Rounds - 1) { continue; } // swap for next round var tmp = x[0]; x[0] = x[2]; x[2] = tmp; tmp = x[1]; x[1] = x[3]; x[3] = tmp; } for (var i = 0; i < BlockSize / 32; i++) { // copy out, with whitening var outValue = x[i] ^ _subKeys[OutputWhiten + i]; outputBuffer[outputBufferOffset + i * 4 + 0] = outValue.B0; outputBuffer[outputBufferOffset + i * 4 + 1] = outValue.B1; outputBuffer[outputBufferOffset + i * 4 + 2] = outValue.B2; outputBuffer[outputBufferOffset + i * 4 + 3] = outValue.B3; if (_cipherMode == CipherMode.CBC) { _iv[i] = outValue; } } }
private const uint RsGfFdbk = 0x14D; //field generator /// <summary> /// Use (12,8) Reed-Solomon code over GF(256) to produce a key S-box dword from two key material dwords. /// </summary> /// <param name="k0">1st dword</param> /// <param name="k1">2nd dword</param> private static DWord ReedSolomonMdsEncode(DWord k0, DWord k1) { var r = new DWord(); for (var i = 0; i < 2; i++) { r ^= i > 0 ? k0 : k1; // merge in 32 more key bits for (var j = 0; j < 4; j++) { // shift one byte at a time var b = (byte)(r >> 24); var g2 = (byte)((b << 1) ^ ((b & 0x80) > 0 ? RsGfFdbk : 0)); var g3 = (byte)(((b >> 1) & 0x7F) ^ ((b & 1) > 0 ? RsGfFdbk >> 1 : 0) ^ g2); r.B3 = (byte)(r.B2 ^ g3); r.B2 = (byte)(r.B1 ^ g2); r.B1 = (byte)(r.B0 ^ g3); r.B0 = b; } } return(r); }
private static DWord RotateRight(DWord x, int n) { return((x >> n) | (x << (32 - n))); }
private static DWord RotateLeft(DWord x, int n) { return((x << n) | (x >> (32 - n))); }