170 lines
3.2 KiB
Go
170 lines
3.2 KiB
Go
package set1
|
|
|
|
import (
|
|
"os"
|
|
"slices"
|
|
)
|
|
|
|
// 00aaaaaa 00aabbbb 00bbbbcc 00cccccc
|
|
// aaaaaaaa bbbbbbbb cccccccc
|
|
func Base64Decode(block []byte) (decoded []byte) {
|
|
|
|
base64Mapping := map[byte]byte{}
|
|
|
|
for i, b := range Base64Alphabet {
|
|
base64Mapping[byte(b)] = byte(i)
|
|
}
|
|
|
|
decoded = []byte{}
|
|
|
|
for i := 0; i < len(block); i += 4 {
|
|
decodedBytes := []byte{0b00000000, 0b00000000, 0b00000000}
|
|
|
|
encodedBytes := block[i : i+4]
|
|
|
|
for i, b := range encodedBytes {
|
|
if b == Base64PaddingCharacter {
|
|
encodedBytes[i] = 0b00000000
|
|
} else {
|
|
encodedBytes[i] = base64Mapping[b]
|
|
}
|
|
}
|
|
|
|
decodedBytes[0] = (encodedBytes[0] << 2) | (encodedBytes[1] >> 4)
|
|
decodedBytes[1] = (encodedBytes[1] << 4) | (encodedBytes[2] >> 2)
|
|
decodedBytes[2] = (encodedBytes[2] << 6) | encodedBytes[3]
|
|
|
|
for _, b := range decodedBytes {
|
|
if b != 0b00000000 {
|
|
decoded = append(decoded, b)
|
|
}
|
|
}
|
|
}
|
|
|
|
return decoded
|
|
}
|
|
|
|
func BruteForceXORSingleCharacterPlainEncodedBlock(block []byte) (score int, key byte, result []byte) {
|
|
for i := 0; i < 256; i++ {
|
|
decoded := XORDecodePlain(block, byte(i))
|
|
decodedScore := ScoreTextByAlphabeticFrequency(decoded)
|
|
|
|
if decodedScore > score {
|
|
score = decodedScore
|
|
key = byte(i)
|
|
result = decoded
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func HammingDistance(str1, str2 []byte) (distance int) {
|
|
for i := 0; i < len(str1); i++ {
|
|
if i >= len(str2) {
|
|
distance += CountBits(str1[i])
|
|
} else {
|
|
distance += CountBits(str1[i] ^ str2[i])
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// 1111
|
|
|
|
// 1111 -1 = 1110
|
|
// 1111 & 1110 = 1110
|
|
|
|
// 1110 -1 = 1101
|
|
// 1110 & 1101 = 1100
|
|
|
|
// 1100 -1 = 1011
|
|
// 1100 & 1011 = 1000
|
|
|
|
// 1000 -1 = 0111
|
|
// 0111 & 1000 = 0000
|
|
|
|
// ------------------
|
|
|
|
// 1010
|
|
|
|
// 1010 -1 = 1001
|
|
// 1010 & 1001 = 1000
|
|
|
|
// 1000 -1 = 0111
|
|
// 1000 & 0111 = 0000
|
|
|
|
func CountBits(x byte) (count int) {
|
|
// This does as many iterations as there are 1 bits in a bitset. Magic.
|
|
for x != 0 {
|
|
x &= x - 1
|
|
count++
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func ReadChallenge6CipherFromFile() (cipher []byte) {
|
|
file, err := os.ReadFile("./res/set-1-challenge-6.txt")
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return file
|
|
}
|
|
|
|
func NormalizedHammingDistance(strings ...[]byte) (normalizedDistance float32) {
|
|
for i := 1; i < len(strings); i++ {
|
|
normalizedDistance += float32(HammingDistance(strings[i-1], strings[i]))
|
|
}
|
|
|
|
normalizedDistance = normalizedDistance / float32(len(strings[0])) / float32(len(strings))
|
|
|
|
return
|
|
}
|
|
|
|
type KeySizeRank struct {
|
|
Size int
|
|
Rank float32
|
|
}
|
|
|
|
func RankKeySizeCandidates(minSize, maxSize int, cipher []byte) (keySizeCandidates []KeySizeRank) {
|
|
keySizes := []KeySizeRank{}
|
|
|
|
for keySize := minSize; keySize <= maxSize; keySize++ {
|
|
keySizes = append(keySizes, KeySizeRank{
|
|
Size: keySize,
|
|
Rank: NormalizedHammingDistance(
|
|
cipher[0:keySize],
|
|
cipher[keySize:keySize*2],
|
|
),
|
|
})
|
|
}
|
|
|
|
slices.SortFunc(keySizes, func(ksr1 KeySizeRank, ksr2 KeySizeRank) int {
|
|
if ksr1.Rank > ksr2.Rank {
|
|
return 1
|
|
} else if ksr1.Rank == ksr2.Rank {
|
|
return 0
|
|
} else {
|
|
return -1
|
|
}
|
|
})
|
|
|
|
return keySizes
|
|
}
|
|
|
|
func XORDecrypt(key []byte, cipher []byte) (result []byte) {
|
|
for i := 0; i < len(cipher); i += len(key) {
|
|
cipherBlock := cipher[i : i+len(key)]
|
|
|
|
for ib, b := range cipherBlock {
|
|
result = append(result, b^key[ib])
|
|
}
|
|
}
|
|
|
|
return
|
|
|
|
}
|