Cryptopals

杂谈 | 共 6724 字 | 2021/8/26 发表 | 2021/8/26 更新

做题笔记

惊了我前几天面试 Bitgo 正好问到了第三题,虽然本来也很简单。。。

Set 1

想了一下我那些解法似乎太暴力了而且只能处理 ASCII 的情况,如果是 UTF 应该就失效了。

1

func c01(str string) string {
	data, err := hex.DecodeString(str)
	if err != nil {
		panic(err)
	}
	return base64.StdEncoding.EncodeToString(data)
}

2

func c02(s1, s2 string) string {
	b1, err := hex.DecodeString(s1)
	if err != nil {
		panic(err)
	}
	b2, err := hex.DecodeString(s2)
	if err != nil {
		panic(err)
	}

	length := len(b1)
	if len(b1) < len(b2) {
		length = len(b2)
	}

	out := make([]byte, length)
	for i := 0; i < length; i++ {
		out[i] = b1[i] ^ b2[i]
	}
	return hex.EncodeToString(out)
}

3

func notValid(b byte) bool {
	//return b > 126
	return (b < 32 || b > 126) && b != 0 && b != 10
}

func c03(prefix, str string) {
	data, err := hex.DecodeString(str)
	if err != nil {
		panic(err)
	}

	length := len(data)
	var c uint8
	for c = 0; c != 255; c++ {
		out := make([]byte, length)
		valid := true
		for i := 0; i < length; i++ {
			b := data[i] ^ c
			if notValid(b) {
				valid = false
				break
			}
			out[i] = b
		}
		if valid {
			fmt.Printf("%s: %d: %s\n", prefix, c, string(out))
		}
	}
}

答案是Cooking MC's like a pound of bacon,用来加密的字符是 ASCII 值为 88 的X

4

func c04(path string) {
	file, err := os.Open(path)
	if err != nil {
		panic(err)
	}

	r := bufio.NewReader(file)
	n := 0
	for {
		n++
		line, _, err := r.ReadLine()
		if err == io.EOF {
			return
		} else if err != nil {
			panic(err)
		}

		c03(fmt.Sprintf("%d", n), string(line))
	}
}

直接用上一题即可,这里值得注意的是notValid这个判断,之前忘处理换行调试了一会。

答案是第 171 行的Now that the party is jumping\n,用来加密的字符是 ASCII 值为 53 的5

5

func c05(raw, key string) string {
	rawBytes := []byte(raw)
	keyBytes := []byte(key)
	n := len(rawBytes)/len(keyBytes) + 1

	length := len(rawBytes)
	keyBytes = bytes.Repeat(keyBytes, n)

	out := make([]byte, length)
	for i := 0; i < length; i++ {
		out[i] = rawBytes[i] ^ keyBytes[i]
	}
	return hex.EncodeToString(out)
}

6

func hamming(b1, b2 []byte) int {
	h := 0
	length := len(b1)
	if len(b1) < len(b2) {
		length = len(b2)
	}

	for i := 0; i < length; i++ {
		b := b1[i] ^ b2[i]
		h += bits.OnesCount8(b)
	}

	return h
}

按照问题里面的思路走,首先我们需要一个计算 Hamming Distance 的函数。

func c06(path string) {
	file, err := os.Open(path)
	if err != nil {
		panic(err)
	}

	dec := base64.NewDecoder(base64.StdEncoding, file)
	data, err := io.ReadAll(dec)
	if err != nil {
		panic(err)
	}

    keySize := 2
	lowestNormalized := float64(123456789)
	for i := 2; i <= 40; i++ {
		normalized := float64(hamming(data[:i], data[i:2 * i])) / float64(i)
		fmt.Printf("%d: %f\n", i, normalized)
		if lowestNormalized > normalized {
			lowestNormalized = normalized
			keySize = i
		}
	}
	fmt.Printf("%d: %f\n", keySize, lowestNormalized)

    ss := make([][]byte, keySize)

	for i := 0; i < keySize; i++ {
		c := uint8(0)
		for {
			valid := true
			for j := 0; i+keySize*j < len(data); j++ {
				if notValid(data[i+keySize*j] ^ c) {
					valid = false
					break
				}
			}
			if valid {
				fmt.Printf("%d: %d\n", i, c)
				if ss[i] == nil {
					ss[i] = []byte{c}
				} else {
					ss[i] = append(ss[i], c)
				}
			}

			if c == 255 {
				break
			} else {
				c++
			}
		}
	}

代码并不难写,值最低的密钥长度是 5,然而运行后却发现没有任何位置的任何字符是有效的。之后又试了几个值比较低的密钥长度也都不行,一气之下直接遍历所有长度,惊人地发现 29 是对的。

	for i := 0; i < keySize; i++ {
		if len(ss[i]) == 1 {
			fmt.Printf("%c", ss[i][0])
		} else {
			fmt.Printf("(%s)", string(ss[i]))
		}
	}
	fmt.Printf("\n")

因为一个位置可能存在多个有效的字符,所以我们可以打印一下所有情况然后人工分析。

	// Termi(dn)ator( *)(RX): B(gmpqrsuvwz{|)( !"#%&'()*+,-./02345789;<=>?i)ng th(`abcdefgikmpz}~)( sy)noi(sy)e
	// Terminator X: Bring the noise

	key := "Terminator X: Bring the noise"
	rawBytes := data
	keyBytes := []byte(key)
	n := len(rawBytes)/len(keyBytes) + 1

	length := len(rawBytes)
	keyBytes = bytes.Repeat(keyBytes, n)

	out := make([]byte, length)
	for i := 0; i < length; i++ {
		out[i] = rawBytes[i] ^ keyBytes[i]
	}
	fmt.Println(string(out))
}

答案太长了就不贴了。

7

func c07(path string, key []byte) {
	c, err := aes.NewCipher(key)
	if err != nil {
		panic(err)
	}

	rawData, err := ioutil.ReadFile(path)
	if err != nil {
		panic(err)
	}

	data, err := base64.StdEncoding.DecodeString(string(rawData))
	if err != nil {
		panic(err)
	}

	decoded := make([]byte, c.BlockSize())
	for len(data) > 0 {
		c.Decrypt(decoded, data)
		data = data[c.BlockSize():]
		fmt.Printf("%s", string(decoded))
	}
}

自己手动实现一下 ECB 就行。

8

func c08(path string) {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		panic(err)
	}

	lines := strings.Split(string(data), "\n")
	for _, line := range lines {
		str := line
		for len(str) > 0 {
			if strings.Contains(str[32:], str[:32]) {
				fmt.Println(line)
				return
			} else {
				str = str[32:]
			}
		}
	}
}

检测有没有重复的块。

答案是

d880619740a8a19b7840a8a31c810a3d08649af70dc06f4fd5d2d69c744cd283e2dd052f6b641dbf9d11b0348542bb5708649af70dc06f4fd5d2d69c744cd2839475c9dfdbc1d46597949d9c7e82bf5a08649af70dc06f4fd5d2d69c744cd28397a93eab8d6aecd566489154789a6b0308649af70dc06f4fd5d2d69c744cd283d403180c98c8f6db1f2a3f9c4040deb0ab51b29933f2c123c58386b06fba186a

Set 2

刚从青海回家,继续做题。考虑到 Go 不太适合写题以及马上要开始的工作,试试用 Python 写。

def xor(a1, a2):
    return bytes(a ^ b for (a, b) in zip(a1, a2))

9

def pkcs7(bytes : bytearray, length : int):
    n = length - len(bytes)
    return bytes + bytearray((n,)) * n

print(pkcs7(b'YELLOW SUBMARINE', 20))

没啥可说的。。。

10

AES_BLOCK_SIZE = 16
IV_EMPTY = bytearray((0,)) * AES_BLOCK_SIZE
from Crypto.Cipher import AES
import base64

def aescbcEnc(data : bytearray, iv : bytearray, key : bytearray):
    out = bytearray()
    last = iv
    cipher = AES.new(key, AES.MODE_ECB)
    for i in range(0, len(data), AES_BLOCK_SIZE):
        block = data[i:i + AES_BLOCK_SIZE]
        if len(block) < AES_BLOCK_SIZE:
            block = pkcs7(block, AES_BLOCK_SIZE)
            block = xor(block, last)

        ct = cipher.encrypt(block)
        last = ct
        out += ct
    return out

def aescbcDec(data : bytearray, iv : bytearray, key : bytearray):
    out = bytearray()
    last = iv
    cipher = AES.new(key, AES.MODE_ECB)
    for i in range(0, len(data), AES_BLOCK_SIZE):
        block = data[i:i + AES_BLOCK_SIZE]
        pt = cipher.decrypt(block)
        pt = xor(pt, last)
        last = block
        out += pt
    return out

print(aescbcDec(aescbcEnc(b'0123456789012345', IV_EMPTY, b'YELLOW SUBMARINE'), IV_EMPTY, b'YELLOW SUBMARINE'))
f10 = open('10.txt', 'r').read()
d10 = base64.b64decode(f10)
print(aescbcDec(d10, IV_EMPTY, b'YELLOW SUBMARINE'))

也没啥可说的。。。

11

还没看懂题