Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions submissions/challenge1/jan_opalski/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Language
The code was written in Go. These instructions are based on assumption that user trying to run this solution has Go on their machine.
# Running the solution
- enter the directory
- `go run .` (alternatively: `go build` and then `./challenge1`)
## Flags
By default all three solutions will be launched. If you want to see results of execution of only chosen tasks, you can achieve that by passing a subset of flags `-easy`, `-medium`, `-hard` respectively (passing all 3 is effectively the same as passing none of these).

Solution of medium task (implementation of Mersenne Twister RNG) uses a default seed of 19650218 (taken from [English Wikipedia](https://en.wikipedia.org/wiki/Mersenne_Twister)). It can be changed to arbitrary value (assuming it can be converted to unsigned 32-bit integer) using flag `-seed <value>`.

Example command with flags: `./challenge1 -medium -seed 42`. This will launch only the Mersenne Twister implementation with a starting seed of 42.

# Done tasks
- Easy: [PKCS#7 padding validation](https://cryptopals.com/sets/2/challenges/15)
- Medium: [Implement the MT19937 Mersenne Twister RNG](https://cryptopals.com/sets/3/challenges/21)
- Hard: [Recover the key from CBC with IV=Key](https://cryptopals.com/sets/4/challenges/27)
3 changes: 3 additions & 0 deletions submissions/challenge1/jan_opalski/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module challenge1

go 1.24.4
28 changes: 28 additions & 0 deletions submissions/challenge1/jan_opalski/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package main

import (
"challenge1/solutions"
"flag"
)

func main() {
easyFlag := flag.Bool("easy", false, "launch solution of easy task")
mediumFlag := flag.Bool("medium", false, "launch solution of medium task")
hardFlag := flag.Bool("hard", false, "launch solution of hard task")
mediumSeedFlag := flag.Int("seed", 19650218, "seed for medium task")
flag.Parse()
all := false
if !(*easyFlag || *mediumFlag || *hardFlag) {
flag.Usage()
all = true
}
if *easyFlag || all {
solutions.Easy()
}
if *mediumFlag || all {
solutions.Medium(uint32(*mediumSeedFlag))
}
if *hardFlag || all {
solutions.Hard()
}
}
47 changes: 47 additions & 0 deletions submissions/challenge1/jan_opalski/solutions/easy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package solutions

import (
"errors"
"fmt"
)

// https://cryptopals.com/sets/2/challenges/15
func Easy() {
fmt.Println("=============================\n\t EASY (15)\n=============================")
str1 := "ICE ICE BABY\x04\x04\x04\x04"
str2 := "ICE ICE BABY\x05\x05\x05\x05"
str3 := "ICE ICE BABY\x01\x02\x03\x04"
test_stripping(str1)
test_stripping(str2)
test_stripping(str3)
str4 := "\x02\x02"
test_stripping(str4)
}

func strip_padding(msg string) (string, error) {
if len(msg) == 0 {
return msg, nil
}
padding_len := msg[len(msg)-1]
first_pad := len(msg) - int(padding_len)

if first_pad < 0 {
return "", errors.New("padding invalid")
}
for i := first_pad; i < len(msg); i++ {
if msg[i] != padding_len {
return "", errors.New("padding invalid")
}
}
return msg[:first_pad], nil
}

func test_stripping(msg string) {
fmt.Printf("Testing stripping for %q\n", msg)
unpadded, err := strip_padding(msg)
if err != nil {
fmt.Println("Got error:", err)
} else {
fmt.Println("Correctly unpadded message", unpadded)
}
}
116 changes: 116 additions & 0 deletions submissions/challenge1/jan_opalski/solutions/hard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package solutions

import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"io"
"slices"
)

const blocklen = 16
const blockcount = 3

// https://cryptopals.com/sets/4/challenges/27
func Hard() {
fmt.Println("=============================\n\t HARD (27)\n=============================")
// create a 128-bit key, assume sender and receiver know it while attacker doesn't
key := sender_receiver_setKey()
// SENDER
message := sender_setupMessage()
encrypted := sender_encryptMessage(message, key)

// ATTACKER
modified := attacker_modifyMessage(encrypted)

// RECEIVER
err := receiver_decryptMessage(modified, key)
if err == nil {
fmt.Println("Receiver didn't raise error <- attack failed")
return
}

// ATTACKER
decrypted, err := hex.DecodeString(err.Error())
if err != nil || len(decrypted) == 0 {
fmt.Println("Didn't get decrypted \"plaintext\" of positive length <- attack failed")
return
}
att_key := attacker_guessKey(decrypted)

// verification
if !slices.Equal(key, att_key) {
fmt.Println("Attacker got the key wrong")
} else {
fmt.Println("Attacker got the key right")
}
}

func sender_receiver_setKey() []byte {
key := make([]byte, blocklen)
_, _ = io.ReadFull(rand.Reader, key)
fmt.Println("Key:\t\t\t", hex.EncodeToString(key))
return key
}

func sender_setupMessage() []byte {
message := []byte("This is a very important and very secret message")
if len(message) != blockcount*blocklen {
panic("Message not padded to be exactly 3-blocks long")
}
return message
}

// (P1, P2, P3) -> (C1, C2, C3)
func sender_encryptMessage(msg []byte, key []byte) []byte {
block, _ := aes.NewCipher(key)
// create CBC cipher which has unsafe behaviour of reusing key as iv
encrypter := cipher.NewCBCEncrypter(block, key)
encrypted := make([]byte, len(msg))
encrypter.CryptBlocks(encrypted, msg)
fmt.Println("Encrypted by sender:\t", hex.EncodeToString(encrypted))
return encrypted
}

// (C1, C2, C3) -> (C1, 0, C1)
func attacker_modifyMessage(msg []byte) []byte {
modified := make([]byte, len(msg))
copy(modified[:blocklen], msg[:blocklen])
copy(modified[2*blocklen:3*blocklen], msg[:blocklen])
fmt.Println("Modified by attacker:\t", hex.EncodeToString(modified))
return modified
}

// The assumption in case of processing error (decrypted plaintext containing invalid ASCII value)
// receiver returns some kind of error message which contains the decrypted plaintext.
// Here for the sake of simplicity receiver_ans error message is strictly either
// - hex string of decrypted "plaintext" if there was an invalid ASCII value (almost surely there'll be)
// - nothing if there was no invalid ASCII value
func receiver_decryptMessage(encrypted []byte, key []byte) error {
block, _ := aes.NewCipher(key)
decrypter := cipher.NewCBCDecrypter(block, key)
decrypted := make([]byte, len(encrypted))
decrypter.CryptBlocks(decrypted, encrypted)
for _, char := range decrypted {
if char > 0x7F {
// Invalid ASCII, return error answer
return errors.New(hex.EncodeToString(decrypted))
}
}
return nil // Kind of "OK" response
}

// decrypted "plaintext" block1 ^ block3 should be equal to iv aka key (it's the same here)
// will work always as long as we get "plaintext" from receiver
func attacker_guessKey(receiver_ans []byte) []byte {
fmt.Println("Receiver thorws error:\t", hex.EncodeToString(receiver_ans))
key := receiver_ans[:blocklen]
for i := range blocklen {
key[i] ^= receiver_ans[2*blocklen+i]
}
fmt.Println("Attacker guesses key:\t", hex.EncodeToString(key))
return key
}
53 changes: 53 additions & 0 deletions submissions/challenge1/jan_opalski/solutions/medium.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package solutions

import (
"fmt"
)

// https://cryptopals.com/sets/3/challenges/21
func Medium(seed uint32) {
fmt.Println("=============================\n\t MEDIUM (21)\n=============================")
rng := newMt(seed)
fmt.Println("First 5 integers generated for seed", seed)
for range 5 {
fmt.Println(rng.extractNumber())
}
}

type my_mtrng struct {
mt [624]uint32
index int
}

// used pseudocode from https://pl.wikipedia.org/wiki/Mersenne_Twister
func newMt(seed uint32) my_mtrng {
ans := my_mtrng{}
ans.mt[0] = seed
for i := uint32(1); i < 624; i++ {
ans.mt[i] = 0x6c078965*(ans.mt[i-1]^(ans.mt[i-1]>>30)) + i
}
return ans
}

func (rng *my_mtrng) extractNumber() uint32 {
if rng.index == 0 {
rng.generateNumbers()
}
y := rng.mt[rng.index]
y ^= (y >> 11)
y ^= ((y << 7) & 0x9d2c5680)
y ^= ((y << 15) & 0xefc60000)
y ^= (y >> 18)
rng.index = (rng.index + 1) % 624
return y
}

func (rng *my_mtrng) generateNumbers() {
for i := range 624 {
y := rng.mt[i]&(1<<31) | rng.mt[(i+1)%624]&(^uint32(1<<31))
rng.mt[i] = rng.mt[(i+397)%624] ^ (y >> 1)
if y%2 == 1 {
rng.mt[i] ^= 0x9908b0df
}
}
}