-
Notifications
You must be signed in to change notification settings - Fork 0
/
Exercise14.scala
85 lines (70 loc) · 3.62 KB
/
Exercise14.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package Set2
import java.util.Base64
import Set2.Exercise10._
import Set2.Exercise11._
import Set2.Exercise12._
import scala.io.Source
import scala.util.Random
import Util.Util._
object Exercise14 {
val blocksize = 16
val prefix : Seq[Byte] = generateRandomBytes(Random.nextInt(255))
def encryption_oracle(data: Seq[Byte]) : Seq[Byte] = {
val unknownString : Seq[Byte] = Base64.getDecoder.decode(Source.fromURL(getClass.getResource("/Exercise12TestData.txt")).getLines().mkString)
encryptECB(prefix ++ data ++ unknownString, key, "AES/ECB/NoPadding")
}
def findPrefixBlockNumber(func: OracleFunction): Int = {
// the encryption oracle encrypts(randomBytes || user_input || secret)
// make userinput very large of a single character
// find when blocks start to duplicate
// before the duplication can be considered the random pre-amble
val input: Seq[Byte] = Seq.fill[Byte](blocksize*4)(0x41.toByte)
val output = func(input).grouped(blocksize)
output.sliding(2).indexWhere{ case Seq(o1, o2) => o1 == o2 }
}
def findPrefixByteNumber(func: OracleFunction): Int = {
// Find the first block that user input beings repeating.
// Then Find the least amount of user input to keep them repeating
// To complete the prefix to a whole block, take the answer above and minus 2 blocksizes
val firstBlockOfAllUserInput = findPrefixBlockNumber(func)
val numOfBytes : Seq[Int] = (0 to blocksize*4).filter{ num =>
val input: Seq[Byte] = Seq.fill[Byte](num)(0x41.toByte)
val output = func(input).grouped(blocksize)
firstBlockOfAllUserInput == output.sliding(2).indexWhere{ case Seq(o1, o2) => o1 == o2 }
}.toSeq
numOfBytes.sorted.head - (blocksize*2)
}
def recoverBlock(func: OracleFunction, blocksize: Int, previousRecoveredBytes: Seq[Byte], blockPosition: Int): Seq[Byte] = {
var recoveredBlock : Seq[Byte] = Seq[Byte]()
val fillerPadding = Seq.fill[Byte](findPrefixByteNumber(func))('B'.toByte)
for(i <- 1 to blocksize){
val fillerBytes: Seq[Byte] = fillerPadding ++ Seq.fill[Byte](blocksize-i)('A'.toByte)
val bytesToCreateMapFrom = fillerBytes ++ previousRecoveredBytes ++ recoveredBlock
val map = createAnswerMap(func, blocksize, blockPosition, bytesToCreateMapFrom)
var recoveredByte = 0x02.toByte
val ciphertext = func(fillerBytes).slice(blocksize*blockPosition, blocksize + (blocksize*blockPosition))
//The last block may have partial plaintext, but the block cipher will create a full block of ciphertext
// Once we have retrieved all of the secret the map will no longer have an answer
// Return in this case
// TODO, i thought the padding should make the plaintext a full block. Not sure why it isnt
val plaintextOption = map.get(ciphertext)
plaintextOption match {
case Some(plaintext) => recoveredByte = plaintext.last
case None => return recoveredBlock
}
recoveredBlock = recoveredBlock :+ recoveredByte
}
recoveredBlock
}
def recoverSecret(func: OracleFunction): Seq[Byte] = {
var secret : Seq[Byte] = Seq[Byte]()
val prefixPadding : Seq[Byte] = Seq.fill[Byte](findPrefixByteNumber(func))(0x41.toByte)
val numberOfBlocks = func(prefixPadding).grouped(blocksize).size + 1 // Add one block because we will be sending blocksize-1 'A' to decrypt
val numberOfBlocksToDecrypt = numberOfBlocks - findPrefixBlockNumber(func)
val startBlockNumber = findPrefixBlockNumber(func)
for(i <- startBlockNumber to (startBlockNumber + numberOfBlocksToDecrypt)){
secret = secret ++ recoverBlock(func, blocksize, secret, i)
}
removePadding(secret)
}
}