~artoj/cryptopals

88fa5d955b599789f7061adadde9bfeb5b3d0720 — Arto Jonsson 4 years ago 1012063
add set #2 challenge #12
4 files changed, 207 insertions(+), 0 deletions(-)

A cmd/s2c12/12.txt
A cmd/s2c12/main.go
A cmd/s2c12/oracle.go
M internal/pkg/utils/utils.go
A cmd/s2c12/12.txt => cmd/s2c12/12.txt +4 -0
@@ 0,0 1,4 @@
Um9sbGluJyBpbiBteSA1LjAKV2l0aCBteSByYWctdG9wIGRvd24gc28gbXkg
aGFpciBjYW4gYmxvdwpUaGUgZ2lybGllcyBvbiBzdGFuZGJ5IHdhdmluZyBq
dXN0IHRvIHNheSBoaQpEaWQgeW91IHN0b3A/IE5vLCBJIGp1c3QgZHJvdmUg
YnkK

A cmd/s2c12/main.go => cmd/s2c12/main.go +90 -0
@@ 0,0 1,90 @@
package main

import (
	"bytes"
	"errors"
	"fmt"
	"log"

	"git.sr.ht/~artoj/cryptopals/internal/pkg/utils"
)

// Number of duplicates used as threshold in ECB detection
const duplicateTreshold = 20

// Feed identical bytes, one at a time, to the oracle function.
// When CipherText_i[:i-1] and CipherText_i-i[:i-1] return the same
// result, the block size is i-1.
func detectBlockSize(e utils.EncryptionOracle) (int, error) {
	previousCipherText := e.Encrypt([]byte{})
	for i := 2; i < 64; i++ {
		cipherText := e.Encrypt(bytes.Repeat([]byte("A"), i))

		if bytes.Equal(previousCipherText[0:i-1], cipherText[0:i-1]) {
			return i - 1, nil
		}

		previousCipherText = cipherText
	}

	return 0, errors.New("unable to determine block size")
}

func createDictionary(prefix []byte, plainText []byte, e utils.EncryptionOracle) [][]byte {
	dict := make([][]byte, 256)
	for i := 0; i < 255; i++ {
		entry := append(prefix, plainText...)
		entry = append(entry, byte(i))
		dict[i] = e.Encrypt(entry)[0:16]
	}
	return dict
}

func search(needle []byte, haystack [][]byte) (byte, error) {
	for i, hay := range haystack {
		if bytes.Equal(needle, hay) {
			return byte(i), nil
		}
	}
	return byte(0), fmt.Errorf("search: cannot find %q in haystack", needle)
}

func main() {
	oracle := NewS2C11EncryptionOracle()
	blockSize, err := detectBlockSize(oracle)
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("detected block size: %d", blockSize)

	if utils.IsECB(oracle, blockSize, duplicateTreshold) {
		log.Print("ECB mode detected")
	} else {
		log.Fatal("ECB mode detection failed")
	}

	var plainText []byte

	for oracle.UnknownLen() > 0 {
		var blockPlainText []byte
		var blockPrefix []byte
		for i := 0; i < blockSize; i++ {
			blockPrefix = bytes.Repeat([]byte("A"), blockSize-1-len(blockPlainText))
			dict := createDictionary(blockPrefix, blockPlainText, oracle)

			cipherText := oracle.Encrypt(blockPrefix)[0:blockSize]

			needle, err := search(cipherText, dict)
			if err != nil {
				log.Fatal(err)
			}

			blockPlainText = append(blockPlainText, needle)
		}
		plainText = append(plainText, blockPlainText...)
		blockPlainText = []byte{}
		oracle = oracle.NextBlock()
	}
	log.Printf("%s", plainText)
}

A cmd/s2c12/oracle.go => cmd/s2c12/oracle.go +83 -0
@@ 0,0 1,83 @@
package main

import (
	"crypto/aes"
	crand "crypto/rand"
	"encoding/base64"
	"io/ioutil"
	"log"

	"git.sr.ht/~artoj/cryptopals/internal/pkg/mode"
	"git.sr.ht/~artoj/cryptopals/internal/pkg/pad"
	"git.sr.ht/~artoj/cryptopals/internal/pkg/utils"
)

const (
	inFile    = "12.txt"
	blockSize = 16
)

// S2C11EncryptionOracle is the oracle for the Set #2, exercice #11
type S2C11EncryptionOracle struct {
	key     []byte
	unknown []byte
}

func randRead(c []byte) {
	_, err := crand.Read(c)
	if err != nil {
		log.Fatal(err)
	}
}

func NewS2C11EncryptionOracle() utils.EncryptionOracle {
	in, err := ioutil.ReadFile(inFile)
	if err != nil {
		log.Fatal(err)
	}

	unknown, err := base64.StdEncoding.DecodeString(string(in))
	if err != nil {
		log.Fatal(err)
	}

	key := make([]byte, blockSize)
	randRead(key)

	oracle := S2C11EncryptionOracle{key: key, unknown: unknown}
	return oracle
}

func (e S2C11EncryptionOracle) Encrypt(input []byte) []byte {
	totalLen := len(input) + len(e.unknown)
	plainText := make([]byte, totalLen)

	copy(plainText, input)
	copy(plainText[len(input):], e.unknown)

	paddedPlainText := pad.ZeroPad(plainText, len(e.key))

	cipherText := make([]byte, len(paddedPlainText))
	aesCipher, err := aes.NewCipher([]byte(e.key))
	if err != nil {
		log.Fatal(err)
	}

	aesECB := mode.NewECBEncrypter(aesCipher)
	aesECB.CryptBlocks(cipherText, paddedPlainText)

	return cipherText
}

func (e S2C11EncryptionOracle) NextBlock() utils.EncryptionOracle {
	num := blockSize
	if len(e.unknown) < blockSize {
		num = len(e.unknown)
	}
	oracle := S2C11EncryptionOracle{key: e.key, unknown: e.unknown[num:]}
	return oracle
}

func (e S2C11EncryptionOracle) UnknownLen() int {
	return len(e.unknown)
}

M internal/pkg/utils/utils.go => internal/pkg/utils/utils.go +30 -0
@@ 1,12 1,20 @@
package utils

import (
	"bytes"
	"fmt"
	"math/bits"

	"git.sr.ht/~artoj/cryptopals/internal/pkg/pad"
)

// EncryptionOracle defines an oracle encryption function.
type EncryptionOracle interface {
	Encrypt(input []byte) []byte // Encrypt the given input with the oracle
	NextBlock() EncryptionOracle // Advance the unknown string by a block size and return new oracle
	UnknownLen() int             // Return the lenght of the unknown string
}

// Transpose performs a matrix transpose operation on blocks.
func Transpose(blocks [][]byte) [][]byte {
	result := make([][]byte, len(blocks[0]))


@@ 76,3 84,25 @@ func NumDuplicates(s []byte) int {
	}
	return num
}

// IsECB attempts to detect whether the given encryption oracle uses ECB mode or not.
// The function does this by encrypting an input with constant value and inspecting
// the resulting cipher text for duplicates. If the number of duplicates in the
// cipher text is greater than the given threshold, the oracle is using ECB mode.
func IsECB(e EncryptionOracle, blockSize, treshold int) bool {
	cipherText := e.Encrypt(bytes.Repeat([]byte("A"), blockSize*8))

	blocks := Split(cipherText, blockSize)
	transposed := Transpose(blocks)

	totalNumDuplicates := 0
	for _, t := range transposed {
		totalNumDuplicates += NumDuplicates(t)
	}

	if totalNumDuplicates > treshold {
		return true
	}

	return false
}