This is the first crypto challenge in set 2 from cryptopals, 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.
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.
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.
Conclusion
The padding scheme for PKCS#7 is simple to understand and straightforward to implement.