Detect single-character XOR
Tags: 100daystooffload, challenge, cryptopals
Published: 2024-05-01 17:23:39

This is the fourth crypto challenge in set 1 from [cryptopals](cryptopals.com), which is the qualifying set. 

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

# Problem description

There is a file with 327 lines, where each line is 60 characters long.

One of these lines has been encrypted by single-character XOR.

The challenge is to find out which line is the encrypted one.

# Solution

We begin with writing a function that can read the text file with all the strings. It will return a slice where each element holds a string from each line in the file.

```golang
func readFileLines(filename string) ([]string, error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	var lines []string
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
	}

	if err := scanner.Err(); err != nil {
		return nil, err
	}

	return lines, nil
}
```

When we have all the strings we can start to search for the encrypted string. Here we can reuse the `DecryptTheMessage` function from the previous challenge.

```golang
func DecryptTheMessage(cipher []byte) ([]byte, error) {
	bestKey := byte(0)
	bestScore := 0.0
	for k := range 256 {
		key := byte(k)
		decrypted := singleByteXOR(cipher, key)

		score := calculateScore(decrypted)

		if score > bestScore {
			bestScore = score
			bestKey = key
		}
	}

	decryptedMessage := singleByteXOR(cipher, bestKey)
	return decryptedMessage, nil
}
```

And we can also use a similar approach to finding the encrypted string as we did with the decrypted message above.

```golang
func FindEncryptedLine(filename string) ([]byte, error) {
	lines, err := readFileLines(filename)
	if err != nil {
		return nil, err
	}

	bestScore := 0.0
	var bestLine []byte
	for _, line := range lines {
		decoded, err := hex.DecodeString(line)
		if err != nil {
			return nil, err
		}

		decrypted, err := DecryptTheMessage(decoded)
		if err != nil {
			return nil, err
		}

		score := calculateScore(decrypted)
		if score > bestScore {
			bestScore = score
			bestLine = decrypted
		}
	}

	return bestLine, nil
}
```

Here we do keep track of the best line by looping through each line from the file. Pretty much the same approach as keeping track of the best decrypted message.

And finally a unit test for this challenge:

```golang
func TestFindEncryptedLine(t *testing.T) {
	bestLine, err := basics.FindEncryptedLine("4.txt")
	if err != nil {
		t.Errorf("Error: %v", err)
	}

	expected := []byte("Now that the party is jumping\n")
	if !bytes.Equal(bestLine, expected) {
		t.Errorf("Expected: %v, Got: %v", "Now that the party is jumping\n", bestLine)
	}
}
```

I have made some refactoring to the previous challenge so the code is more reusable in this challenge too. The complete code can be found on [GitHub](https://github.com/stefanalfbo/cryptopals).

# Conclusion

The solution to this challenge had a lot to win to keep building on the code from the previous challenge. 
 
# References

* [Set 1 - Challenge 4](https://cryptopals.com/sets/1/challenges/4)