blob: ae37e1f68aae798f7372204f43343bb47642d014 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
'''Implementation of PRINCE cipher for use in ROM/FLASH scrambling scripts.'''
from typing import List
PRINCE_SBOX4 = [
0xb, 0xf, 0x3, 0x2,
0xa, 0xc, 0x9, 0x1,
0x6, 0x7, 0x8, 0x0,
0xe, 0x5, 0xd, 0x4
]
PRINCE_SBOX4_INV = [
0xb, 0x7, 0x3, 0x2,
0xf, 0xd, 0x8, 0x9,
0xa, 0x6, 0x4, 0x0,
0x5, 0xe, 0xc, 0x1
]
PRINCE_SHIFT_ROWS64 = [
0x4, 0x9, 0xe, 0x3,
0x8, 0xd, 0x2, 0x7,
0xc, 0x1, 0x6, 0xb,
0x0, 0x5, 0xa, 0xf
]
PRINCE_SHIFT_ROWS64_INV = [
0xc, 0x9, 0x6, 0x3,
0x0, 0xd, 0xa, 0x7,
0x4, 0x1, 0xe, 0xb,
0x8, 0x5, 0x2, 0xf
]
PRINCE_ROUND_CONSTS = [
0x0000000000000000,
0x13198a2e03707344,
0xa4093822299f31d0,
0x082efa98ec4e6c89,
0x452821e638d01377,
0xbe5466cf34e90c6c,
0x7ef84f78fd955cb1,
0x85840851f1ac43aa,
0xc882d32f25323c54,
0x64a51195e0e3610d,
0xd3b5a399ca0c2399,
0xc0ac29b7c97c50dd
]
PRINCE_SHIFT_ROWS_CONSTS = [0x7bde, 0xbde7, 0xde7b, 0xe7bd]
def sbox(data: int, width: int, coeffs: List[int]) -> int:
assert 0 <= width
assert 0 <= data < (1 << width)
full_mask = (1 << width) - 1
sbox_mask = (1 << (4 * (width // 4))) - 1
ret = data & (full_mask & ~sbox_mask)
for i in range(width // 4):
nibble = (data >> (4 * i)) & 0xf
sb_nibble = coeffs[nibble]
ret |= sb_nibble << (4 * i)
return ret
def prince_nibble_red16(data: int) -> int:
assert 0 <= data < (1 << 16)
nib0 = (data >> 0) & 0xf
nib1 = (data >> 4) & 0xf
nib2 = (data >> 8) & 0xf
nib3 = (data >> 12) & 0xf
return nib0 ^ nib1 ^ nib2 ^ nib3
def prince_mult_prime(data: int) -> int:
assert 0 <= data < (1 << 64)
ret = 0
for blk_idx in range(4):
data_hw = (data >> (16 * blk_idx)) & 0xffff
start_sr_idx = 0 if blk_idx in [0, 3] else 1
for nibble_idx in range(4):
sr_idx = (start_sr_idx + 3 - nibble_idx) % 4
sr_const = PRINCE_SHIFT_ROWS_CONSTS[sr_idx]
nibble = prince_nibble_red16(data_hw & sr_const)
ret |= nibble << (16 * blk_idx + 4 * nibble_idx)
return ret
def prince_shiftrows(data: int, inv: bool) -> int:
assert 0 <= data < (1 << 64)
shifts = PRINCE_SHIFT_ROWS64_INV if inv else PRINCE_SHIFT_ROWS64
ret = 0
for nibble_idx in range(64 // 4):
src_nibble_idx = shifts[nibble_idx]
src_nibble = (data >> (4 * src_nibble_idx)) & 0xf
ret |= src_nibble << (4 * nibble_idx)
return ret
def prince_fwd_round(rc: int, key: int, data: int) -> int:
assert 0 <= rc < (1 << 64)
assert 0 <= key < (1 << 64)
assert 0 <= data < (1 << 64)
data = sbox(data, 64, PRINCE_SBOX4)
data = prince_mult_prime(data)
data = prince_shiftrows(data, False)
data ^= rc
data ^= key
return data
def prince_inv_round(rc: int, key: int, data: int) -> int:
assert 0 <= rc < (1 << 64)
assert 0 <= key < (1 << 64)
assert 0 <= data < (1 << 64)
data ^= key
data ^= rc
data = prince_shiftrows(data, True)
data = prince_mult_prime(data)
data = sbox(data, 64, PRINCE_SBOX4_INV)
return data
def prince(data: int, key: int, num_rounds_half: int) -> int:
'''Run the PRINCE cipher
This uses the new keyschedule proposed by Dinur in "Cryptanalytic
Time-Memory-Data Tradeoffs for FX-Constructions with Applications to PRINCE
and PRIDE".
'''
assert 0 <= data < (1 << 64)
assert 0 <= key < (1 << 128)
assert 0 <= num_rounds_half <= 5
k1 = key & ((1 << 64) - 1)
k0 = key >> 64
k0_rot1 = ((k0 & 1) << 63) | (k0 >> 1)
k0_prime = k0_rot1 ^ (k0 >> 63)
data ^= k0
data ^= k1
data ^= PRINCE_ROUND_CONSTS[0]
for hri in range(num_rounds_half):
round_idx = 1 + hri
rc = PRINCE_ROUND_CONSTS[round_idx]
rk = k0 if round_idx & 1 else k1
data = prince_fwd_round(rc, rk, data)
data = sbox(data, 64, PRINCE_SBOX4)
data = prince_mult_prime(data)
data = sbox(data, 64, PRINCE_SBOX4_INV)
for hri in range(num_rounds_half):
round_idx = 11 - num_rounds_half + hri
rc = PRINCE_ROUND_CONSTS[round_idx]
rk = k1 if round_idx & 1 else k0
data = prince_inv_round(rc, rk, data)
data ^= PRINCE_ROUND_CONSTS[11]
data ^= k1
data ^= k0_prime
return data