diff --git a/Algorithms.Tests/Crypto/Paddings/TbcPaddingTests.cs b/Algorithms.Tests/Crypto/Paddings/TbcPaddingTests.cs new file mode 100644 index 00000000..1bf131d4 --- /dev/null +++ b/Algorithms.Tests/Crypto/Paddings/TbcPaddingTests.cs @@ -0,0 +1,157 @@ +using System; +using Algorithms.Crypto.Paddings; +using FluentAssertions; +using NUnit.Framework; + +namespace Algorithms.Tests.Crypto.Paddings; + +public class TbcPaddingTests +{ + private readonly TbcPadding padding = new TbcPadding(); + + [Test] + public void AddPadding_WhenInputOffsetIsZero_ShouldPadWithLastBit() + { + var input = new byte[] { 0x01, 0x02, 0x03, 0x04 }; + var inputOffset = 0; + + var result = padding.AddPadding(input, inputOffset); + + result.Should().Be(4); + input.Should().BeEquivalentTo(new byte[]{0xff, 0xff, 0xff, 0xff}); + } + + [Test] + public void AddPadding_WhenInputOffsetIsPositive_ShouldPadWithPreviousBit() + { + var input = new byte[] { 0x01, 0x02, 0x03, 0x04 }; + var inputOffset = 2; + + var result = padding.AddPadding(input, inputOffset); + + result.Should().Be(2); + input.Should().BeEquivalentTo(new byte[] { 0x01, 0x02, 0xff, 0xff }); + } + + [Test] + public void AddPadding_WhenInputOffsetIsGreaterThanLength_ShouldThrowArgumentException() + { + var input = new byte[] { 0x01, 0x02, 0x03, 0x04 }; + var inputOffset = 5; + + Action act = () => padding.AddPadding(input, inputOffset); + + act.Should().Throw() + .WithMessage("Not enough space in input array for padding"); + } + + [Test] + public void AddPadding_WhenLastBitIsZero_ShouldPadWith0xFF() + { + var input = new byte[] { 0x02 }; + const int inputOffset = 0; + + var result = padding.AddPadding(input, inputOffset); + + result.Should().Be(1); + input.Should().BeEquivalentTo(new byte[] { 0xFF }); + } + + [Test] + public void AddPadding_WhenLastBitIsOne_ShouldPadWith0x00() + { + var input = new byte[] { 0x03 }; + const int inputOffset = 0; + + var result = padding.AddPadding(input, inputOffset); + + result.Should().Be(1); + input.Should().BeEquivalentTo(new byte[] { 0x00 }); + } + + [Test] + public void RemovePadding_WhenCalledWithPaddedData_ShouldReturnUnpaddedData() + { + var paddedData = new byte[] { 0x01, 0x02, 0x03, 0xff, 0xff }; + var expectedData = new byte[] { 0x01, 0x02, 0x03 }; + + var result = padding.RemovePadding(paddedData); + + result.Should().BeEquivalentTo(expectedData); + } + + [Test] + public void RemovePadding_WhenCalledWithUnpaddedData_ShouldReturnsSameData() + { + var unpaddedData = new byte[] { 0x01, 0x02, 0x03 }; + + var result = padding.RemovePadding(unpaddedData); + + result.Should().BeEquivalentTo(unpaddedData); + } + + [Test] + public void RemovePadding_WhenCalledWithEmptyArray_ShouldReturnEmptyArray() + { + var emptyData = Array.Empty(); + + var result = padding.RemovePadding(emptyData); + + result.Should().BeEquivalentTo(emptyData); + } + + [Test] + public void RemovePadding_WhenCalledWithSingleBytePaddedData_ShouldReturnEmptyArray() + { + var singleBytePaddedData = new byte[] { 0xff }; + + var result = padding.RemovePadding(singleBytePaddedData); + + result.Should().BeEmpty(); + } + + [Test] + public void RemovePadding_WhenCalledWitAllBytesPadded_ShouldReturnEmptyArray() + { + var allBytesPaddedData = new byte[] { 0xff, 0xff, 0xff }; + var emptyData = Array.Empty(); + + var result = padding.RemovePadding(allBytesPaddedData); + + result.Should().BeEquivalentTo(emptyData); + } + + [Test] + public void GetPaddingBytes_WhenCalledWithPaddedData_ShouldReturnCorrectPaddingCount() + { + + var paddedData = new byte[] { 0x01, 0x02, 0x03, 0xff, 0xff }; + const int expectedPaddingCount = 2; + + var result = padding.GetPaddingBytes(paddedData); + + result.Should().Be(expectedPaddingCount); + } + + [Test] + public void GetPaddingBytes_WhenCalledWithUnpaddedData_ShouldReturnZero() + { + var unpaddedData = new byte[] { 0x01, 0x02, 0x03 }; + + Action action = () => padding.GetPaddingBytes(unpaddedData); + + action.Should().Throw() + .WithMessage("No padding found"); + } + + [Test] + public void GetPaddingBytes_WhenCalledWithEmptyArray_ShouldReturnZero() + { + var emptyData = Array.Empty(); + + Action action = () => padding.GetPaddingBytes(emptyData); + + action.Should().Throw() + .WithMessage("No padding found."); + } +} diff --git a/Algorithms/Crypto/Paddings/TbcPadding.cs b/Algorithms/Crypto/Paddings/TbcPadding.cs new file mode 100644 index 00000000..97b7a4b8 --- /dev/null +++ b/Algorithms/Crypto/Paddings/TbcPadding.cs @@ -0,0 +1,161 @@ +using System; + +namespace Algorithms.Crypto.Paddings; + +/// +/// +/// Trailing-Bit-Complement padding is a padding scheme that is defined in the ISO/IEC 9797-1 standard. +/// +/// +/// It is used for adding data to the end of a message that needs to be encrypted or decrypted by a block cipher. +/// +/// +/// The padding bytes are either 0x00 or 0xFF, depending on the last bit of the original data. For example, if the last +/// bit of the original data is 0, then the padding bytes are 0xFF; if the last bit is 1, then the padding bytes are 0x00. +/// The padding bytes are added at the end of the data block until the desired length is reached. +/// +/// +public class TbcPadding +{ + /// + /// Adds padding to the input array according to the TBC standard. + /// + /// The input array to be padded. + /// The offset in the input array where the padding starts. + /// The number of bytes that were added. + /// Thrown when the input array does not have enough space for padding. + public int AddPadding(byte[] input, int inputOffset) + { + // Calculate the number of bytes to be padded. + var count = input.Length - inputOffset; + byte code; + + // Check if the input array has enough space for padding. + if (count < 0) + { + throw new ArgumentException("Not enough space in input array for padding"); + } + + if (inputOffset > 0) + { + // Get the last bit of the previous byte. + var lastBit = input[inputOffset - 1] & 0x01; + + // Set the padding code to 0xFF if the last bit is 0, or 0x00 if the last bit is 1. + code = (byte)(lastBit == 0 ? 0xff : 0x00); + } + else + { + // Get the last bit of the last byte in the input array. + var lastBit = input[^1] & 0x01; + + // Set the padding code to 0xff if the last bit is 0, or 0x00 if the last bit is 1. + code = (byte)(lastBit == 0 ? 0xff : 0x00); + } + + while (inputOffset < input.Length) + { + // Set each byte to the padding code. + input[inputOffset] = code; + inputOffset++; + } + + // Return the number of bytes that were padded. + return count; + } + + /// + /// Removes the padding from a byte array according to the Trailing-Bit-Complement padding algorithm. + /// + /// The byte array to remove the padding from. + /// A new byte array without the padding. + /// + /// This method assumes that the input array has padded with either 0x00 or 0xFF bytes, depending on the last bit of + /// the original data. The method works by finding the last byte that does not match the padding code and copying all + /// the bytes up to that point into a new array. If the input array is not padded or has an invalid padding, the + /// method may return incorrect results. + /// + public byte[] RemovePadding(byte[] input) + { + if (input.Length == 0) + { + return Array.Empty(); + } + + // Get the last byte of the input array. + var lastByte = input[^1]; + + // Determine the byte code + var code = (byte)((lastByte & 0x01) == 0 ? 0x00 : 0xff); + + // Start from the end of the array and move towards the front. + int i; + for (i = input.Length - 1; i >= 0; i--) + { + // If the current byte does not match the padding code, stop. + if (input[i] != code) + { + break; + } + } + + // Create a new array of the appropriate length. + var unpadded = new byte[i + 1]; + + // Copy the unpadded data into the new array. + Array.Copy(input, unpadded, i + 1); + + // Return the new array. + return unpadded; + } + + /// + /// Returns the number of padding bytes in a byte array according to the Trailing-Bit-Complement padding algorithm. + /// + /// The byte array to check for padding. + /// The number of padding bytes in the input array. + /// + /// This method assumes that the input array has been padded with either 0x00 or 0xFF bytes, depending on the last + /// bit of the original data. The method works by iterating backwards from the end of the array and counting the + /// number of bytes that match the padding code. The method uses bitwise operations to optimize the performance and + /// avoid branching. If the input array is not padded or has an invalid padding, the method may return incorrect + /// results. + /// + public int GetPaddingBytes(byte[] input) + { + var length = input.Length; + + if (length == 0) + { + throw new ArgumentException("No padding found."); + } + + // Get the value of the last byte as the padding value + var paddingValue = input[--length] & 0xFF; + var paddingCount = 1; // Start count at 1 for the last byte + var countingMask = -1; // Initialize counting mask + + // Check if there is no padding + if (paddingValue != 0 && paddingValue != 0xFF) + { + throw new ArgumentException("No padding found"); + } + + // Loop backwards through the array + for (var i = length - 1; i >= 0; i--) + { + var currentByte = input[i] & 0xFF; + + // Calculate matchMask. If currentByte equals paddingValue, matchMask will be 0, otherwise -1 + var matchMask = ((currentByte ^ paddingValue) - 1) >> 31; + + // Update countingMask. Once a non-matching byte is found, countingMask will remain -1 + countingMask &= matchMask; + + // Increment count only if countingMask is 0 (i.e., currentByte matches paddingValue) + paddingCount -= countingMask; + } + + return paddingCount; + } +} diff --git a/README.md b/README.md index c4d228c3..9377089a 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ find more than one implementation for the same objective but using different alg * [Paddings](./Algorithms/Crypto/Paddings/) * [ISO 10125-2 Padding](./Algorithms/Crypto/Paddings/ISO10126d2Padding.cs) * [ISO 7816-4 Padding](./Algorithms/Crypto/Paddings/ISO7816d4Padding.cs) + * [TBC Padding](./Algorithms/Crypto/Paddings/TbcPadding.cs) * [PKCS7 Padding](./Algorithms/Crypto/Paddings/PKCS7Padding.cs) * [Data Compression](./Algorithms/DataCompression) * [Burrows-Wheeler transform](./Algorithms/DataCompression/BurrowsWheelerTransform.cs)