Skip to content

Commit b6d7385

Browse files
authored
Add TBC Padding (TheAlgorithms#413)
1 parent d65c7d1 commit b6d7385

File tree

3 files changed

+319
-0
lines changed

3 files changed

+319
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
using System;
2+
using Algorithms.Crypto.Paddings;
3+
using FluentAssertions;
4+
using NUnit.Framework;
5+
6+
namespace Algorithms.Tests.Crypto.Paddings;
7+
8+
public class TbcPaddingTests
9+
{
10+
private readonly TbcPadding padding = new TbcPadding();
11+
12+
[Test]
13+
public void AddPadding_WhenInputOffsetIsZero_ShouldPadWithLastBit()
14+
{
15+
var input = new byte[] { 0x01, 0x02, 0x03, 0x04 };
16+
var inputOffset = 0;
17+
18+
var result = padding.AddPadding(input, inputOffset);
19+
20+
result.Should().Be(4);
21+
input.Should().BeEquivalentTo(new byte[]{0xff, 0xff, 0xff, 0xff});
22+
}
23+
24+
[Test]
25+
public void AddPadding_WhenInputOffsetIsPositive_ShouldPadWithPreviousBit()
26+
{
27+
var input = new byte[] { 0x01, 0x02, 0x03, 0x04 };
28+
var inputOffset = 2;
29+
30+
var result = padding.AddPadding(input, inputOffset);
31+
32+
result.Should().Be(2);
33+
input.Should().BeEquivalentTo(new byte[] { 0x01, 0x02, 0xff, 0xff });
34+
}
35+
36+
[Test]
37+
public void AddPadding_WhenInputOffsetIsGreaterThanLength_ShouldThrowArgumentException()
38+
{
39+
var input = new byte[] { 0x01, 0x02, 0x03, 0x04 };
40+
var inputOffset = 5;
41+
42+
Action act = () => padding.AddPadding(input, inputOffset);
43+
44+
act.Should().Throw<ArgumentException>()
45+
.WithMessage("Not enough space in input array for padding");
46+
}
47+
48+
[Test]
49+
public void AddPadding_WhenLastBitIsZero_ShouldPadWith0xFF()
50+
{
51+
var input = new byte[] { 0x02 };
52+
const int inputOffset = 0;
53+
54+
var result = padding.AddPadding(input, inputOffset);
55+
56+
result.Should().Be(1);
57+
input.Should().BeEquivalentTo(new byte[] { 0xFF });
58+
}
59+
60+
[Test]
61+
public void AddPadding_WhenLastBitIsOne_ShouldPadWith0x00()
62+
{
63+
var input = new byte[] { 0x03 };
64+
const int inputOffset = 0;
65+
66+
var result = padding.AddPadding(input, inputOffset);
67+
68+
result.Should().Be(1);
69+
input.Should().BeEquivalentTo(new byte[] { 0x00 });
70+
}
71+
72+
[Test]
73+
public void RemovePadding_WhenCalledWithPaddedData_ShouldReturnUnpaddedData()
74+
{
75+
var paddedData = new byte[] { 0x01, 0x02, 0x03, 0xff, 0xff };
76+
var expectedData = new byte[] { 0x01, 0x02, 0x03 };
77+
78+
var result = padding.RemovePadding(paddedData);
79+
80+
result.Should().BeEquivalentTo(expectedData);
81+
}
82+
83+
[Test]
84+
public void RemovePadding_WhenCalledWithUnpaddedData_ShouldReturnsSameData()
85+
{
86+
var unpaddedData = new byte[] { 0x01, 0x02, 0x03 };
87+
88+
var result = padding.RemovePadding(unpaddedData);
89+
90+
result.Should().BeEquivalentTo(unpaddedData);
91+
}
92+
93+
[Test]
94+
public void RemovePadding_WhenCalledWithEmptyArray_ShouldReturnEmptyArray()
95+
{
96+
var emptyData = Array.Empty<byte>();
97+
98+
var result = padding.RemovePadding(emptyData);
99+
100+
result.Should().BeEquivalentTo(emptyData);
101+
}
102+
103+
[Test]
104+
public void RemovePadding_WhenCalledWithSingleBytePaddedData_ShouldReturnEmptyArray()
105+
{
106+
var singleBytePaddedData = new byte[] { 0xff };
107+
108+
var result = padding.RemovePadding(singleBytePaddedData);
109+
110+
result.Should().BeEmpty();
111+
}
112+
113+
[Test]
114+
public void RemovePadding_WhenCalledWitAllBytesPadded_ShouldReturnEmptyArray()
115+
{
116+
var allBytesPaddedData = new byte[] { 0xff, 0xff, 0xff };
117+
var emptyData = Array.Empty<byte>();
118+
119+
var result = padding.RemovePadding(allBytesPaddedData);
120+
121+
result.Should().BeEquivalentTo(emptyData);
122+
}
123+
124+
[Test]
125+
public void GetPaddingBytes_WhenCalledWithPaddedData_ShouldReturnCorrectPaddingCount()
126+
{
127+
128+
var paddedData = new byte[] { 0x01, 0x02, 0x03, 0xff, 0xff };
129+
const int expectedPaddingCount = 2;
130+
131+
var result = padding.GetPaddingBytes(paddedData);
132+
133+
result.Should().Be(expectedPaddingCount);
134+
}
135+
136+
[Test]
137+
public void GetPaddingBytes_WhenCalledWithUnpaddedData_ShouldReturnZero()
138+
{
139+
var unpaddedData = new byte[] { 0x01, 0x02, 0x03 };
140+
141+
Action action = () => padding.GetPaddingBytes(unpaddedData);
142+
143+
action.Should().Throw<ArgumentException>()
144+
.WithMessage("No padding found");
145+
}
146+
147+
[Test]
148+
public void GetPaddingBytes_WhenCalledWithEmptyArray_ShouldReturnZero()
149+
{
150+
var emptyData = Array.Empty<byte>();
151+
152+
Action action = () => padding.GetPaddingBytes(emptyData);
153+
154+
action.Should().Throw<ArgumentException>()
155+
.WithMessage("No padding found.");
156+
}
157+
}
+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
using System;
2+
3+
namespace Algorithms.Crypto.Paddings;
4+
5+
/// <summary>
6+
/// <para>
7+
/// Trailing-Bit-Complement padding is a padding scheme that is defined in the ISO/IEC 9797-1 standard.
8+
/// </para>
9+
/// <para>
10+
/// It is used for adding data to the end of a message that needs to be encrypted or decrypted by a block cipher.
11+
/// </para>
12+
/// <para>
13+
/// The padding bytes are either 0x00 or 0xFF, depending on the last bit of the original data. For example, if the last
14+
/// 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.
15+
/// The padding bytes are added at the end of the data block until the desired length is reached.
16+
/// </para>
17+
/// </summary>
18+
public class TbcPadding
19+
{
20+
/// <summary>
21+
/// Adds padding to the input array according to the TBC standard.
22+
/// </summary>
23+
/// <param name="input">The input array to be padded.</param>
24+
/// <param name="inputOffset">The offset in the input array where the padding starts.</param>
25+
/// <returns>The number of bytes that were added.</returns>
26+
/// <exception cref="ArgumentException">Thrown when the input array does not have enough space for padding.</exception>
27+
public int AddPadding(byte[] input, int inputOffset)
28+
{
29+
// Calculate the number of bytes to be padded.
30+
var count = input.Length - inputOffset;
31+
byte code;
32+
33+
// Check if the input array has enough space for padding.
34+
if (count < 0)
35+
{
36+
throw new ArgumentException("Not enough space in input array for padding");
37+
}
38+
39+
if (inputOffset > 0)
40+
{
41+
// Get the last bit of the previous byte.
42+
var lastBit = input[inputOffset - 1] & 0x01;
43+
44+
// Set the padding code to 0xFF if the last bit is 0, or 0x00 if the last bit is 1.
45+
code = (byte)(lastBit == 0 ? 0xff : 0x00);
46+
}
47+
else
48+
{
49+
// Get the last bit of the last byte in the input array.
50+
var lastBit = input[^1] & 0x01;
51+
52+
// Set the padding code to 0xff if the last bit is 0, or 0x00 if the last bit is 1.
53+
code = (byte)(lastBit == 0 ? 0xff : 0x00);
54+
}
55+
56+
while (inputOffset < input.Length)
57+
{
58+
// Set each byte to the padding code.
59+
input[inputOffset] = code;
60+
inputOffset++;
61+
}
62+
63+
// Return the number of bytes that were padded.
64+
return count;
65+
}
66+
67+
/// <summary>
68+
/// Removes the padding from a byte array according to the Trailing-Bit-Complement padding algorithm.
69+
/// </summary>
70+
/// <param name="input">The byte array to remove the padding from.</param>
71+
/// <returns>A new byte array without the padding.</returns>
72+
/// <remarks>
73+
/// This method assumes that the input array has padded with either 0x00 or 0xFF bytes, depending on the last bit of
74+
/// the original data. The method works by finding the last byte that does not match the padding code and copying all
75+
/// the bytes up to that point into a new array. If the input array is not padded or has an invalid padding, the
76+
/// method may return incorrect results.
77+
/// </remarks>
78+
public byte[] RemovePadding(byte[] input)
79+
{
80+
if (input.Length == 0)
81+
{
82+
return Array.Empty<byte>();
83+
}
84+
85+
// Get the last byte of the input array.
86+
var lastByte = input[^1];
87+
88+
// Determine the byte code
89+
var code = (byte)((lastByte & 0x01) == 0 ? 0x00 : 0xff);
90+
91+
// Start from the end of the array and move towards the front.
92+
int i;
93+
for (i = input.Length - 1; i >= 0; i--)
94+
{
95+
// If the current byte does not match the padding code, stop.
96+
if (input[i] != code)
97+
{
98+
break;
99+
}
100+
}
101+
102+
// Create a new array of the appropriate length.
103+
var unpadded = new byte[i + 1];
104+
105+
// Copy the unpadded data into the new array.
106+
Array.Copy(input, unpadded, i + 1);
107+
108+
// Return the new array.
109+
return unpadded;
110+
}
111+
112+
/// <summary>
113+
/// Returns the number of padding bytes in a byte array according to the Trailing-Bit-Complement padding algorithm.
114+
/// </summary>
115+
/// <param name="input">The byte array to check for padding.</param>
116+
/// <returns>The number of padding bytes in the input array.</returns>
117+
/// <remarks>
118+
/// This method assumes that the input array has been padded with either 0x00 or 0xFF bytes, depending on the last
119+
/// bit of the original data. The method works by iterating backwards from the end of the array and counting the
120+
/// number of bytes that match the padding code. The method uses bitwise operations to optimize the performance and
121+
/// avoid branching. If the input array is not padded or has an invalid padding, the method may return incorrect
122+
/// results.
123+
/// </remarks>
124+
public int GetPaddingBytes(byte[] input)
125+
{
126+
var length = input.Length;
127+
128+
if (length == 0)
129+
{
130+
throw new ArgumentException("No padding found.");
131+
}
132+
133+
// Get the value of the last byte as the padding value
134+
var paddingValue = input[--length] & 0xFF;
135+
var paddingCount = 1; // Start count at 1 for the last byte
136+
var countingMask = -1; // Initialize counting mask
137+
138+
// Check if there is no padding
139+
if (paddingValue != 0 && paddingValue != 0xFF)
140+
{
141+
throw new ArgumentException("No padding found");
142+
}
143+
144+
// Loop backwards through the array
145+
for (var i = length - 1; i >= 0; i--)
146+
{
147+
var currentByte = input[i] & 0xFF;
148+
149+
// Calculate matchMask. If currentByte equals paddingValue, matchMask will be 0, otherwise -1
150+
var matchMask = ((currentByte ^ paddingValue) - 1) >> 31;
151+
152+
// Update countingMask. Once a non-matching byte is found, countingMask will remain -1
153+
countingMask &= matchMask;
154+
155+
// Increment count only if countingMask is 0 (i.e., currentByte matches paddingValue)
156+
paddingCount -= countingMask;
157+
}
158+
159+
return paddingCount;
160+
}
161+
}

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ find more than one implementation for the same objective but using different alg
2323
* [Paddings](./Algorithms/Crypto/Paddings/)
2424
* [ISO 10125-2 Padding](./Algorithms/Crypto/Paddings/ISO10126d2Padding.cs)
2525
* [ISO 7816-4 Padding](./Algorithms/Crypto/Paddings/ISO7816d4Padding.cs)
26+
* [TBC Padding](./Algorithms/Crypto/Paddings/TbcPadding.cs)
2627
* [PKCS7 Padding](./Algorithms/Crypto/Paddings/PKCS7Padding.cs)
2728
* [Data Compression](./Algorithms/DataCompression)
2829
* [Burrows-Wheeler transform](./Algorithms/DataCompression/BurrowsWheelerTransform.cs)

0 commit comments

Comments
 (0)