Skip to content

Add TBC Padding #413

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions Algorithms.Tests/Crypto/Paddings/TbcPaddingTests.cs
Original file line number Diff line number Diff line change
@@ -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<ArgumentException>()
.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<byte>();

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<byte>();

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<ArgumentException>()
.WithMessage("No padding found");
}

[Test]
public void GetPaddingBytes_WhenCalledWithEmptyArray_ShouldReturnZero()
{
var emptyData = Array.Empty<byte>();

Action action = () => padding.GetPaddingBytes(emptyData);

action.Should().Throw<ArgumentException>()
.WithMessage("No padding found.");
}
}
161 changes: 161 additions & 0 deletions Algorithms/Crypto/Paddings/TbcPadding.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
using System;

namespace Algorithms.Crypto.Paddings;

/// <summary>
/// <para>
/// Trailing-Bit-Complement padding is a padding scheme that is defined in the ISO/IEC 9797-1 standard.
/// </para>
/// <para>
/// It is used for adding data to the end of a message that needs to be encrypted or decrypted by a block cipher.
/// </para>
/// <para>
/// 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.
/// </para>
/// </summary>
public class TbcPadding
{
/// <summary>
/// Adds padding to the input array according to the TBC standard.
/// </summary>
/// <param name="input">The input array to be padded.</param>
/// <param name="inputOffset">The offset in the input array where the padding starts.</param>
/// <returns>The number of bytes that were added.</returns>
/// <exception cref="ArgumentException">Thrown when the input array does not have enough space for padding.</exception>
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;
}

/// <summary>
/// Removes the padding from a byte array according to the Trailing-Bit-Complement padding algorithm.
/// </summary>
/// <param name="input">The byte array to remove the padding from.</param>
/// <returns>A new byte array without the padding.</returns>
/// <remarks>
/// 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.
/// </remarks>
public byte[] RemovePadding(byte[] input)
{
if (input.Length == 0)
{
return Array.Empty<byte>();
}

// 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;
}

/// <summary>
/// Returns the number of padding bytes in a byte array according to the Trailing-Bit-Complement padding algorithm.
/// </summary>
/// <param name="input">The byte array to check for padding.</param>
/// <returns>The number of padding bytes in the input array.</returns>
/// <remarks>
/// 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.
/// </remarks>
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;
}
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down