Implement PKCS#7 padding
Tags: 100daystooffload, cryptopals, security, challenge
Published: 2024-05-16 19:06:07

This is the first crypto challenge in set 2 from [cryptopals](cryptopals.com), which is a set on block cipher cryptography. This is bread-and-butter crypto, the kind you'll see implemented in most web software that does crypto.

The difficulty level is relatively easy, to solve this problem I have used the programming language Go.

# Problem description

A block cipher transforms a fixed-sized block (usually 8 or 16 bytes) of plaintext into ciphertext. But we almost never want to transform a single block; we encrypt irregularly-sized messages.

One way we account for irregularly-sized messages is by padding, creating a plaintext that is an even multiple of the blocksize. The most popular padding scheme is called PKCS#7.

So: pad any block to a specific block length, by appending the number of bytes of padding to the end of the block. For instance,

`"YELLOW SUBMARINE"`

... padded to 20 bytes would be:

`"YELLOW SUBMARINE\x04\x04\x04\x04"`

# Solution

We begin to create a unit test based on the input and output from the problem description above.

```golang
func TestPKCS7Padding(t *testing.T) {
	data := []byte("YELLOW SUBMARINE")

	t.Run("Padding needed", func(t *testing.T) {
		expected := []byte("YELLOW SUBMARINE\x04\x04\x04\x04")
		blockSize := 20

		actual, err := block.PKCS7Padding(data, blockSize)
		if err != nil {
			t.Errorf("Unexpected error: %v", err)
		}

		if string(actual) != string(expected) {
			t.Errorf("Expected %v, was %v", expected, actual)
		}
	})

	t.Run("No padding needed", func(t *testing.T) {
		blockSize := 16
		actual, err := block.PKCS7Padding(data, blockSize)
		if err != nil {
			t.Errorf("Unexpected error: %v", err)
		}

		if string(actual) != string(data) {
			t.Errorf("Expected %v, was %v", data, actual)
		}
	})
}
```

We are using `YELLOW SUBMARINE` and the block size `20` as input parameters to the function. The `actual` output is then confirmed against the `expected` variable which is, `YELLOW SUBMARINE\x04\x04\x04\x04`. There is also a test for when the data has the size so it can be evenly divided by the block size.

The implementation of the `PKCS7Padding` function could look like this.

```golang
func PKCS7Padding(data []byte, blockSize int) ([]byte, error) {
	rest := (len(data) % blockSize)
	if rest == 0 {
		return data, nil
	}

	paddingSize := blockSize - rest
	padded := make([]byte, len(data)+paddingSize)

	copy(padded, data)

	for i := len(data); i < len(padded); i++ {
		padded[i] = byte(paddingSize)
	}

	return padded, nil
}
```

First we calculate the padding size by using the modulus operator to see if the data can be evenly divided by the block size. If there is no `rest`, then we don't have to pad the data; otherwise we calculate how many bytes we need to pad to complete the last block.

The for loop is doing the padding, where each byte added as padding is set to the number of bytes that are added. This means the value of each padding byte is the same as the `paddingSize` variable.

The complete solution can be found on [GitHub](https://github.com/stefanalfbo/cryptopals).

# Conclusion

The padding scheme for PKCS#7 is simple to understand and straightforward to implement.

# References

* [Set 2 - Challenge 9](https://cryptopals.com/sets/2/challenges/9)
* [crypto/aes](https://pkg.go.dev/crypto/aes)
* [Padding - Wikipedia](https://en.wikipedia.org/wiki/Padding_(cryptography))
* [PKCS](https://en.wikipedia.org/wiki/PKCS)
* [PKCS #7](https://en.wikipedia.org/wiki/PKCS_7)
* [RFC 2315](https://datatracker.ietf.org/doc/html/rfc2315)