Stefan Alfbo

Posted on

# Detect single-character XOR

This is the fourth crypto challenge in set 1 from cryptopals, 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.

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.

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.

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:

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.

# Conclusion

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