// GenerateKeys creates a white-boxed version of the AES key `key`, with any non-determinism generated by `seed`. func GenerateKeys(key, seed []byte) (out Construction, inputMask, outputMask encoding.BlockAffine) { rs := random.NewSource("Toy Construction", seed) // Generate two completely random affine transformations, to be put on input and output of SPN. inputMask, outputMask = generateAffineMasks(&rs) // Steal key schedule logic from the standard AES construction. constr := saes.Construction{key} roundKeys := constr.StretchedKey() // Generate an SPN which has the input and output masks, but is otherwise un-obfuscated. out[0] = inputMask encoding.XOR(out[0].BlockAdditive[:], out[0].BlockAdditive[:], roundKeys[0]) for i := 1; i < 10; i++ { out[i] = encoding.BlockAffine{ BlockLinear: encoding.BlockLinear{round, unRound}, BlockAdditive: shiftRoundKey(roundKeys[i]), } } out[10] = encoding.BlockAffine{ BlockLinear: encoding.BlockLinear{lastRound, firstRound}, BlockAdditive: shiftRoundKey(roundKeys[10]), } out[10], _ = encoding.DecomposeBlockAffine(encoding.ComposedBlocks{out[10], outputMask}) // Sample a self-equivalences of the S-box layer and mix them into adjacent affine layers. label := make([]byte, 16) copy(label, []byte("Self-Eq")) r := rs.Stream(label) for i := 1; i < 11; i++ { a, bInv := generateSelfEquivalence(r) out[i-1], _ = encoding.DecomposeBlockAffine(encoding.ComposedBlocks{out[i-1], a}) out[i], _ = encoding.DecomposeBlockAffine(encoding.ComposedBlocks{bInv, out[i]}) } return }
// RecoverKey returns the AES key used to generate the given white-box construction. func RecoverKey(constr *xiao.Construction) []byte { round1 := round{ construction: constr, round: 1, } // Decomposition Phase constr1 := aspn.DecomposeSPN(round1, cspn.ASA) var ( first, last = affineLayer(constr1[0].(encoding.BlockAffine)), affineLayer(constr1[2].(encoding.BlockAffine)) middle = sboxLayer(constr1[1].(encoding.ConcatenatedBlock)) ) // Disambiguation Phase // The SPN decomposition naturally leaves the last affine layer without a constant part. We would push it into the // middle S-boxes if that wasn't the case. // Put the affine layers in diagonal form. perm := first.findPermutation() permEnc := encoding.NewBlockLinear(perm) first.rightCompose(encoding.InverseBlock{permEnc}) middle.permuteBy(perm, false) last.leftCompose(permEnc) // Whiten the S-boxes so that they are linearly equivalent to Sbar. mask := middle.whiten() encoding.XOR(first.BlockAdditive[:], first.BlockAdditive[:], mask[:]) // Fix the S-boxes so that they are equal to Sbar. in, out := middle.cleanLinear() first.rightCompose(in) last.leftCompose(out) // Add ShiftRows matrix to make search possible. last.rightCompose(encoding.NewBlockLinear(constr.ShiftRows[2])) // Clean off remaining noise from self-equivalences of Sbar. left := last.cleanLeft() right := encoding.ComposedBlocks{ middle, left, encoding.InverseBlock{middle}, } first.rightCompose(right) // Convert Sbar back to AES's "standard" S-box. for pos := 0; pos < 16; pos++ { first.BlockAdditive[pos] ^= 0x52 middle[pos] = encoding.ComposedBytes{encoding.ByteAdditive(0x52), middle[pos]} } // fmt.Println(encoding.ProbablyEquivalentBlocks( // encoding.ComposedBlocks{first, middle, last}, // encoding.ComposedBlocks{aspn.Encoding{round1}, encoding.NewBlockLinear(constr.ShiftRows[2])}, // )) // fmt.Println(encoding.ProbablyEquivalentBlocks( // aspn.Encoding{constr1}, // aspn.Encoding{round1}, // )) // // Output: // true // true roundKey := shiftrows{}.Decode(first.BlockAdditive) return backOneRound(roundKey[:], 1) }
// shiftRoundKey adds the fixed SubBytes constant to a round key and returns the result as an encoding.Block. func shiftRoundKey(key []byte) encoding.BlockAdditive { out := [16]byte{} encoding.XOR(out[:], subBytesConst, key) return encoding.BlockAdditive(out) }