Detect single-character XOR

Stefan Alfbo - May 1 - - Dev Community

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

References

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .